Documentation
¶
Overview ¶
Package handoff translates a coding-agent session from one agent's on-disk format into another's, via a small neutral intermediate representation (the AgentSession IR).
This is deliberately NOT a perfect clone. Codex and Claude Code do not share message ids, tool-call schemas, model metadata, or runtime/permission state, so a faithful byte-level conversion is impossible. Instead, handoff carries the useful *semantic* content — the conversation (user/assistant prose, with tool calls and command output flattened to short text summaries) plus project context (cwd, git) — and the target synthesizer emits a real, discoverable session in the destination agent that opens with a plain-language handoff preamble and then replays that conversation as text turns. The result is honest: a translated, continue-from-here session, not a claim of native fidelity.
Flow:
Codex rollout ─┐ ┌─► Claude transcript (ToClaude)
├─► AgentSession IR ───┤
Claude transcript ┘ └─► Codex rollout (ToCodex)
Index ¶
- Constants
- func DeterministicID(sourceID, targetAgent string) string
- func Preamble(s AgentSession) string
- func ToClaude(s AgentSession) ([]byte, string, error)
- func ToCodex(s AgentSession) ([]byte, string, error)
- func ToHTML(s AgentSession) []byte
- func ToMarkdown(s AgentSession) []byte
- type AgentSession
- type GitInfo
- type Role
- type Translation
- type Turn
Constants ¶
const IRFormat = "agent-session-v1"
IRFormat is the version tag stamped into an AgentSession.
Variables ¶
This section is empty.
Functions ¶
func DeterministicID ¶
DeterministicID derives a stable target session id from a source id and the target agent, so translating the same source twice yields the same destination session (idempotent re-import) instead of piling up duplicates. It is formatted as a UUID (version nibble 5) computed from a SHA-256 of the inputs.
func Preamble ¶
func Preamble(s AgentSession) string
Preamble builds the plain-language handoff message that leads a translated session. It tells the destination agent that this is an imported, best-effort translation (not a native replay), restates the project/git context so the code can be recovered, and asks it to continue from where the prior conversation left off. It is emitted as the first user turn of the synthesized session.
func ToClaude ¶
func ToClaude(s AgentSession) ([]byte, string, error)
ToClaude synthesizes a Claude Code transcript (JSONL) from the neutral AgentSession. The result is a real, discoverable session: it opens with the handoff preamble (as the first user message) and then replays the conversation as plain user/assistant text turns, chained by parentUuid. Tool turns become short assistant text notes (never replayed tool_use blocks, whose ids would not match anything). It returns the JSONL bytes and the deterministic session id.
func ToCodex ¶
func ToCodex(s AgentSession) ([]byte, string, error)
ToCodex synthesizes a Codex rollout (JSONL) from the neutral AgentSession. The result is a real, discoverable session: a session_meta line, then the handoff preamble and the prior conversation. Each turn is emitted both as the durable response_item message (what Codex replays to the model on resume) and, for user/assistant prose, as the event_msg the UI uses for the list preview and transcript. Tool turns become assistant text notes. It returns the JSONL bytes and the deterministic session id.
func ToHTML ¶ added in v0.9.0
func ToHTML(s AgentSession) []byte
ToHTML renders a parsed session as a self-contained, styled HTML document for reading or sharing a conversation outside the agent. Like ToMarkdown it is NOT re-importable. Every piece of session-derived text is HTML-escaped, so a conversation that itself contains markup or a "</script>" cannot break out of the page or inject script — important because sessions are untrusted content.
func ToMarkdown ¶ added in v0.8.0
func ToMarkdown(s AgentSession) []byte
ToMarkdown renders a parsed session as a human-readable Markdown document, for reading or sharing a conversation outside the agent (it is NOT re-importable). Tool/command turns are shown as fenced blocks; metadata is sanitized.
Types ¶
type AgentSession ¶
type AgentSession struct {
Format string `json:"format"` // IRFormat
SourceAgent string `json:"source_agent"` // "codex" | "claude"
ThreadID string `json:"thread_id,omitempty"`
CWD string `json:"cwd,omitempty"`
Git *GitInfo `json:"git,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
Conversation []Turn `json:"conversation"`
}
AgentSession is the neutral, agent-independent representation of a session.
func FromClaudeBytes ¶
func FromClaudeBytes(b []byte) (AgentSession, error)
FromClaudeBytes extracts the neutral AgentSession from in-memory Claude transcript bytes (e.g. a bundle entry).
func FromClaudeTranscript ¶
func FromClaudeTranscript(path string) (AgentSession, error)
FromClaudeTranscript reads a Claude Code transcript and extracts the neutral AgentSession: user/assistant prose, with tool_use blocks summarized to short text turns and internal "thinking" blocks dropped. Project context comes from the per-line cwd/gitBranch. Parsing is defensive.
func FromCodexBytes ¶
func FromCodexBytes(b []byte) (AgentSession, error)
FromCodexBytes extracts the neutral AgentSession from in-memory Codex rollout bytes (e.g. a bundle entry), so callers need not write a temp file.
func FromCodexRollout ¶
func FromCodexRollout(path string) (AgentSession, error)
FromCodexRollout reads a Codex rollout JSONL file and extracts the neutral AgentSession: the visible user/assistant conversation (from event_msg lines), with tool calls summarized to short text turns (from response_item function_call lines), plus project context from session_meta. Parsing is defensive — unknown or malformed lines are skipped, never fatal.
type GitInfo ¶
type GitInfo struct {
Branch string `json:"branch,omitempty"`
Commit string `json:"commit,omitempty"`
Remote string `json:"remote,omitempty"`
}
GitInfo is the project's git context, carried so the handoff preamble can tell the destination agent how to get the same code.
type Role ¶
type Role string
Role is the speaker of a conversation turn.
const ( RoleUser Role = "user" RoleAssistant Role = "assistant" // RoleTool is a flattened, text-summarized tool call or command (e.g. a shell // command and a short note about its result). It is rendered as context, never // replayed as a real tool call (the target agent's tools differ). RoleTool Role = "tool" )
type Translation ¶
type Translation struct {
TargetAgent string
SessionID string // deterministic id assigned in the target agent
CWD string
RelPath string // e.g. projects/<enc>/<id>.jsonl or sessions/Y/M/D/rollout-...jsonl
Content []byte
Turns int // conversation turns carried (excluding the preamble)
}
Translation is the result of converting one session into another agent's format: the synthesized file bytes plus where they belong inside the target agent's home (a forward-slash path relative to the home root, matching the shapes the importer/safety layer already validate).
func Translate ¶
func Translate(sourceBytes []byte, sourceAgent, targetAgent string) (Translation, error)
Translate converts a source agent's session bytes into the target agent's format. sourceAgent/targetAgent are "codex" or "claude". It returns the synthesized session and its destination path within the target home.
The output is deterministic (stable id and timestamps derived from the source), so re-translating the same session yields byte-identical bytes and re-import is an idempotent skip rather than a duplicate.