Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,29 @@ A Python-generated fixture was also opened with the official JavaScript
JavaScript saver rejected Python's `msgpack` serializer type. The production
adapter therefore delegates to the official Python saver and message reducer
instead of decoding SQLite blobs or assuming cross-language wire compatibility.

## Deep Agents Code boundary

Deep Agents Code `0.1.36` was inspected against its official persistence code
and pinned `langgraph-checkpoint-sqlite` `3.1.0` / `langgraph-checkpoint`
`4.1.1` dependencies. Root and subagent conversations share a thread ID and
are separated by `checkpoint_ns`; current message state may require replaying
the parent chain and `messages` writes through the Deep Agents reducer rather
than reading only the latest checkpoint blob.

An interoperability probe confirmed that a database written by the official
Python `SqliteSaver` `3.1.0` is not readable by the official JavaScript
`SqliteSaver` `1.0.3`: the JavaScript implementation rejects Python's
`msgpack` serialization tag. Consequently, this package does not decode the
SQLite store or its blobs. `normalizeDeepAgentsCode` is a thin fixed-path wrapper
over the generic official-Python checkpoint adapter: it selects an explicit
thread from `~/.deepagents/.state/sessions.db`, forwards namespace/checkpoint
options and bounds, and retags only the leading metadata source. Integration
tests copy the generic Python-generated fixture beneath a temporary redirected
`HOME`; they cover latest and explicit checkpoint selection, non-root
namespaces, default-path resolution, and metadata retagging without touching a
user database.

The separate `deepagents-code` transcript adapter continues to normalize a
versioned plain-JSON envelope when callers already have reconstructed message
dictionaries. It performs no local database access.
120 changes: 116 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,121 @@ and is empty when the transcript required no recoverable cleanup.
| --- | --- | --- |
| `claude-code` | Native Claude Code JSONL | `claude-code` |
| `codex` | Native Codex rollout JSONL | `codex` |
| `deepagents-code` | Local default database via `normalizeDeepAgentsCode`, or a version 1 safe JSON envelope via `normalizeTranscript` | `deepagents-code` |
| `letta` | Cloud/API message array or local conversation JSONL (legacy and v3) | `letta` |
| `openhands` | JSON event array or an events-API `{ "items": [...] }` envelope | `openhands` |
| `deepagents` | User-supplied Python LangGraph `SqliteSaver` database plus `threadId` | `deepagents` |

### Deep Agents Code

Deep Agents Code stores its LangGraph sessions at
`~/.deepagents/.state/sessions.db`. Normalize one explicitly selected thread
with the async convenience wrapper:

```ts
import {
DEEP_AGENTS_CODE_DEFAULT_DATABASE_PATH,
normalizeDeepAgentsCode,
} from "@letta-ai/trajectory";

const result = await normalizeDeepAgentsCode({
threadId: "thread-123",
checkpointNamespace: "", // optional; root namespace by default
// checkpointId: "...", // optional; latest checkpoint by default
// pythonExecutable: "/path/to/venv/bin/python",
});
```

`threadId` is always required; the wrapper does not list or guess threads and
does not accept a custom database path. The exported display constant is
`DEEP_AGENTS_CODE_DEFAULT_DATABASE_PATH` (`~/.deepagents/.state/sessions.db`).
The actual home directory is resolved when the function is called. Internally,
the wrapper delegates to `normalizeCheckpoint({ source: "deepagents", ... })`,
so checkpoint selection, serializer decoding, parent traversal, and reducer
semantics stay in the shared official-Python helper. Only the leading
`meta.source` is changed from `deepagents` to `deepagents-code`.

The Python wrapper uses the same fixed path and its current Python interpreter
by default:

```python
from trajectory import normalize_deepagents_code

result = normalize_deepagents_code(
thread_id="thread-123",
checkpoint_namespace="", # optional
checkpoint_id=None, # optional; latest when omitted
)
```

Install the Python `deepagents` extra, or select a Python environment containing
`langgraph` and `langgraph-checkpoint-sqlite`, as described below.

#### Safe envelope transcripts

`deepagents-code` accepts a JSON string containing a safe, already-decoded
thread envelope through `normalizeTranscript`. This is separate from the local
database convenience wrapper:

```json
{
"type": "deepagents-code-thread",
"version": 1,
"thread_id": "019b0000-0000-7000-8000-000000000001",
"checkpoint_ns": "",
"metadata": {
"cwd": "/workspace/project",
"git_branch": "feature/example",
"created_at": "2026-02-01T12:00:00Z",
"updated_at": "2026-02-01T12:00:04Z"
},
"messages": [
{
"message": {
"type": "human",
"content": "Inspect the project.",
"id": "human-1"
},
"timestamp": "2026-02-01T12:00:00Z"
},
{
"message": {
"type": "ai",
"content": "I will inspect it.",
"id": "ai-1"
},
"timestamp": "2026-02-01T12:00:01Z"
}
]
}
```

Pass the serialized envelope through the regular API:

```ts
const result = normalizeTranscript({
source: "deepagents-code",
transcript: JSON.stringify(envelope),
});
```

`checkpoint_ns` identifies the selected root (`""`) or subagent namespace.
Each `message` is an ordinary JSON dictionary in LangChain message shape;
`human`, `ai`, `system`, `tool`, `function`, and `remove` types are recognized.
Decoded class names may also be supplied as `__langgraph_class` (for example,
`"AIMessage"`). AI reasoning in `additional_kwargs.reasoning_content`, prose,
tool calls, linked tool results, model metadata, and optional per-message
timestamps are preserved. System messages are omitted with a diagnostic because
trajectory-v1 has no system role. Removal records are expected to have already
been applied while reconstructing checkpoint state and are ignored defensively.

The envelope adapter performs no database access or blob deserialization.
Callers using this lower-level form must supply an envelope from a trusted
exporter. Do not use ad hoc object construction, pickle, or executable
deserialization on checkpoint blobs. For the standard Deep Agents Code local
database, prefer `normalizeDeepAgentsCode`, which delegates to the shared
official-Python helper instead.

Letta messages use native `message_type` values such as `user_message`,
`reasoning_message`, `assistant_message`, `tool_call_message`,
`approval_request_message`, and `tool_return_message`. The adapter orders a
Expand Down Expand Up @@ -198,10 +309,11 @@ result = normalize_checkpoint(
)
```

`loadDeepAgentsCheckpoint(location)` is also exported for integrations such as
Deep Agents Code that need to discover a location before reusing the same
decoder. `FilesystemBackend` is unrelated to this trajectory: it persists
agent-created files, not the LangGraph message/checkpoint state.
`loadDeepAgentsCheckpoint(location)` is also exported for integrations that
need decoded checkpoint data before normalization. `normalizeDeepAgentsCode`
is the fixed-path convenience layer over this generic implementation.
`FilesystemBackend` is unrelated to this trajectory: it persists agent-created
files, not the LangGraph message/checkpoint state.

An unknown source, an invalid source-level container, or a transcript that
cannot form a valid trajectory throws `NormalizationError`. Recoverable
Expand Down
62 changes: 62 additions & 0 deletions fixtures/deepagents-code/cleanup/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"records": [
{ "role": "meta", "source": "deepagents-code" },
{
"role": "user",
"content": "Continue safely.",
"timestamp": "2026-02-02T08:00:00.000Z"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_2",
"name": "unknown_tool",
"args": "{\"_raw\":\"[\\\"unexpected\\\",\\\"shape\\\"]\"}"
}
],
"timestamp": "2026-02-02T08:00:15.000Z"
},
{
"role": "assistant",
"content": "Done.",
"timestamp": "2026-02-02T08:00:30.000Z"
}
],
"diagnostics": [
{
"code": "system_message_dropped",
"message": "Dropped a Deep Agents Code system message because trajectory-v1 has no system role."
},
{
"code": "system_message_dropped",
"message": "Dropped a synthetic Deep Agents Code system notification."
},
{
"code": "tool_call_id_synthesized",
"message": "Synthesized tool-call ID \"call_2\".",
"recordIndex": 2
},
{
"code": "unknown_tool_name",
"message": "Substituted \"unknown_tool\" for a missing tool name.",
"recordIndex": 2
},
{
"code": "tool_arguments_reshaped",
"message": "Reshaped arguments for tool call \"call_2\" into a JSON object.",
"recordIndex": 2
},
{
"code": "orphan_tool_result",
"message": "Dropped a tool result without a preceding call for \"\".",
"recordIndex": 3
},
{
"code": "timestamps_synthesized",
"message": "Synthesized timestamps for 3 normalized records.",
"count": 3
}
]
}
40 changes: 40 additions & 0 deletions fixtures/deepagents-code/cleanup/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"type": "deepagents-code-thread",
"version": 1,
"thread_id": "cleanup-thread",
"checkpoint_ns": "task:subagent-1",
"metadata": { "created_at": "2026-02-02T08:00:00Z" },
"messages": [
{
"message": {
"__langgraph_class": "SystemMessage",
"content": "Subagent system prompt"
}
},
{
"message": {
"type": "human",
"content": "[SYSTEM] Tool execution was cancelled."
}
},
{
"message": { "type": "human", "content": "Continue safely." }
},
{
"message": {
"type": "ai",
"content": "",
"tool_calls": [{ "args": ["unexpected", "shape"] }]
}
},
{
"message": { "type": "tool", "content": "unlinked output" }
},
{
"message": { "type": "remove", "id": "old-message" }
},
{
"message": { "type": "ai", "content": "Done." }
}
]
}
73 changes: 73 additions & 0 deletions fixtures/deepagents-code/tool-calls/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"records": [
{
"role": "meta",
"source": "deepagents-code",
"cwd": "/workspace/deep-agent",
"git_branch": "feature/checkpoints",
"model": "example-model"
},
{
"role": "user",
"content": "Inspect the project and the screenshot.\n[image]",
"timestamp": "2026-02-01T12:00:00.000Z"
},
{
"role": "reasoning",
"content": "First inspect the manifest and source.",
"timestamp": "2026-02-01T12:00:01.000Z"
},
{
"role": "assistant",
"content": "I will inspect both files.",
"timestamp": "2026-02-01T12:00:01.000Z"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call-read",
"name": "read_file",
"args": "{\"file_path\":\"package.json\"}"
}
],
"timestamp": "2026-02-01T12:00:01.000Z"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call-search",
"name": "search_files",
"args": "{\"query\":\"checkpoint\",\"path\":\"src\"}"
}
],
"timestamp": "2026-02-01T12:00:01.000Z"
},
{
"role": "tool",
"tool_call_id": "call-read",
"content": "{\"name\":\"trajectory\"}",
"timestamp": "2026-02-01T12:00:02.000Z"
},
{
"role": "tool",
"tool_call_id": "call-search",
"content": "src/core.ts",
"timestamp": "2026-02-01T12:00:03.000Z"
},
{
"role": "assistant",
"content": "The project contains the checkpoint integration point.",
"timestamp": "2026-02-01T12:00:04.000Z"
}
],
"diagnostics": [
{
"code": "system_message_dropped",
"message": "Dropped a Deep Agents Code system message because trajectory-v1 has no system role."
}
]
}
Loading