Observed since 20 Oct, 23:35 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Normalize native agent transcripts into a shared trajectory format.
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/.
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
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": []
}
source | Accepted input format | Normalized meta.source |
|---|---|---|
atif | ATIF-v1.0 through ATIF-v1.7 whole-trajectory JSON | atif |
claude-code | Native Claude Code JSONL | claude-code |
codex | Native Codex rollout JSONL | codex |
copilot-cli | Native GitHub Copilot CLI event JSONL | copilot-cli |
cursor | Cursor role/message content-block JSONL capture | cursor |
droid | Native Droid session JSONL | droid |
gemini-cli | Native Gemini CLI whole-session JSON | gemini-cli |
hermes | Session-store message-row array or a { "session": {...}, "messages": [...] } envelope | hermes |
letta-code | Letta Code client transcript.jsonl | letta-code |
omp | Native OMP (Oh My Pi) coding-agent session JSONL (pi-agent session format) | omp |
openclaw | Native OpenClaw session JSONL (pi-agent session format) | openclaw |
opencode | Native OpenCode { "info": ..., "messages": [...] } session JSON | opencode |
openhands | JSON event array or an events-API { "items": [...] } envelope | openhands |
pi | Native pi-coding-agent session JSONL | pi |
deepagents | Deep Agents CLI LangGraph SQLite store plus threadId | deepagents |
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.
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);
A trajectory is an ordered array containing:
meta record identifying the source and available session
metadata.filters.systemMessages is explicitly
set to "include"; system messages are omitted by default.observation records for environment feedback that cannot be
attributed to one specific tool call, such as merged terminal output.user and assistant prose records.reasoning records when the source exposes reasoning.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
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.
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.
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.
Apache-2.0. See LICENSE.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.4.3 | 25 Sept 2026 | 2 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:35 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:5db347835d952c1a41ea333fdf22967190f71b44f5be8279c272de47b1c09f35