Catalog PyPI
PACKAGE EXPLORERView in registry

agent-trajectory

PyPI

Normalize native agent transcripts into a shared trajectory format.

agentsnormalizationtrainingtranscripts
Versions collected1
Direct dependencies2From release 0.4.3
Last observed20 Oct, 23:35 UTC

README

Version 0.4.3 documentation

trajectory

Normalize agent transcripts from different runtimes into one validated, model-ready record format.

Agent tools represent the same concepts—messages, reasoning, tool calls, and tool results—in incompatible native formats. trajectory provides one TypeScript API that turns those formats into deterministic, structured records for training, evaluation, analysis, and inference.

The caller supplies a transcript string and its source. The one exception is Deep Agents, whose sessions normalizeCheckpoint reads from its local LangGraph SQLite store by thread ID; see src/adapters/deepagents/.

Installation

The TypeScript package is published as @letta-ai/trajectory:

npm install @letta-ai/trajectory

The Python wrapper is published as agent-trajectory and imports as trajectory:

pip install agent-trajectory

Quick start

import { normalizeTranscript } from "@letta-ai/trajectory";

const { records, diagnostics } = normalizeTranscript({
  source: "codex",
  transcript: rawJsonl,
});

records contains the normalized trajectory. diagnostics is always present and is empty when the transcript required no recoverable cleanup.

{
  "records": [
    { "role": "meta", "source": "codex" },
    {
      "role": "user",
      "content": "Check the current directory.",
      "timestamp": "2026-07-10T12:00:00.000Z"
    },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_1",
          "name": "exec_command",
          "args": "{\"cmd\":\"pwd\"}"
        }
      ],
      "timestamp": "2026-07-10T12:00:01.000Z"
    },
    {
      "role": "tool",
      "tool_call_id": "call_1",
      "content": "/workspace",
      "timestamp": "2026-07-10T12:00:02.000Z"
    }
  ],
  "diagnostics": []
}

Supported sources

sourceAccepted input formatNormalized meta.source
atifATIF-v1.0 through ATIF-v1.7 whole-trajectory JSONatif
claude-codeNative Claude Code JSONLclaude-code
codexNative Codex rollout JSONLcodex
copilot-cliNative GitHub Copilot CLI event JSONLcopilot-cli
cursorCursor role/message content-block JSONL capturecursor
droidNative Droid session JSONLdroid
gemini-cliNative Gemini CLI whole-session JSONgemini-cli
hermesSession-store message-row array or a { "session": {...}, "messages": [...] } envelopehermes
letta-codeLetta Code client transcript.jsonlletta-code
ompNative OMP (Oh My Pi) coding-agent session JSONL (pi-agent session format)omp
openclawNative OpenClaw session JSONL (pi-agent session format)openclaw
opencodeNative OpenCode { "info": ..., "messages": [...] } session JSONopencode
openhandsJSON event array or an events-API { "items": [...] } envelopeopenhands
piNative pi-coding-agent session JSONLpi
deepagentsDeep Agents CLI LangGraph SQLite store plus threadIddeepagents

Tool result records may include ok: boolean when the source exposes an authoritative structured outcome, such as Pi/OpenClaw isError, Claude Code is_error, Letta Code resultOk, OpenHands/Cursor is_error, OpenCode/Gemini terminal state, or Copilot CLI success. The field is omitted when the source does not expose a reliable status; result text is never interpreted as success or failure.

Each adapter lives in its own folder under src/adapters/ with a README documenting the exact input contract, decoding behavior, and what the adapter drops.

Listing local trajectories

listTrajectories() enumerates the sessions in a source's standard local store, newest first, with cursor pagination. It is a discovery layer beside normalization — normalizeTranscript() itself never touches the filesystem. ATIF, Copilot CLI, Cursor, Gemini CLI, and OpenCode are export-only input contracts and intentionally return listing_unavailable; callers locate and read the exports themselves.

import { listTrajectories } from "@letta-ai/trajectory";

let cursor: string | undefined;
do {
  const page = await listTrajectories({ source: "claude-code", limit: 100, cursor });
  for (const item of page.items) {
    // item.id, item.path, item.updatedAt?, item.title?, item.sizeBytes?
  }
  cursor = page.nextCursor;
} while (cursor);

Normalized records

A trajectory is an ordered array containing:

  • One leading meta record identifying the source and available session metadata.
  • Optional system message records when filters.systemMessages is explicitly set to "include"; system messages are omitted by default.
  • Generic observation records for environment feedback that cannot be attributed to one specific tool call, such as merged terminal output.
  • user and assistant prose records.
  • Optional reasoning records when the source exposes reasoning.
  • Assistant tool-call records with stable IDs and stringified JSON-object arguments.
  • tool records linked to earlier calls by tool_call_id.

Every conversational record has an ISO timestamp. The complete contract is available as both runtime validation and schema/trajectory-v1.schema.json.

The public function is:

normalizeTranscript(input: NormalizeInput): NormalizeResult

Imported conversations (separate API)

Slack channels are multi-party conversations, not agent execution traces. They use an independent conversation schema and API:

import { normalizeConversation } from "@letta-ai/trajectory/conversations";

const { records, diagnostics } = normalizeConversation({
  source: "slack",
  transcript: rawJsonl, // one channel's messages, as fetched
  channel: "C0AB…",
  channelName: "eng-deploys", // optional
  users, // optional users.list rows, for display names and bot flags
});

One channel is one conversation: a leading meta with a participants table, then top-level posts in time order with thread replies nested once under their root. Python exposes normalize_conversation from trajectory.conversations. The format does not extend NormalizedRecord, normalizeTranscript, or normalizeToCanonical; agent schemas and canonical schema version remain unchanged.

See CONVERSATIONS.md for the contract and Slack for thread assembly and supported content. Slack is the only conversation adapter in V0. Speaker names, reaction counts, and file placeholders are supported; recipients, file contents, and other message metadata remain deferred.

Adding a source

Each native format is implemented as a focused adapter that decodes source events into the shared internal message/tool contract. Common validation, linking, repair, timestamp handling, and bounds remain in the normalization core.

Use prompts/add-source.md with a coding agent to add a source from a local transcript corpus. The prompt covers privacy-safe corpus inspection, sanitized fixtures, compatibility checks, and the transcript-only API boundary.

Development

Requires Node.js 20+ and Bun for development:

bun install
bun run check

bun run check runs typechecking, the complete test suite, and the package build. It also regenerates the JavaScript runtime embedded in the Python wheel and fails if the committed bundle was stale. Run the Python parity suite with:

PYTHONPATH=python/src python3 -m unittest discover -s python/tests -v

See PARITY.md for compatibility checks performed against real transcript corpora and production source adapters. See SOURCE_VERSION_AUDIT.md for the privacy-safe source-version inventory, observed format families, and current decoder gaps.

License

Apache-2.0. See LICENSE.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.4.325 Sept 20262

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
letta-ai/trajectory

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 23:35 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:5db347835d952c1a41ea333fdf22967190f71b44f5be8279c272de47b1c09f35