handoff

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 14 Imported by: 0

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

View Source
const IRFormat = "agent-session-v1"

IRFormat is the version tag stamped into an AgentSession.

Variables

This section is empty.

Functions

func DeterministicID

func DeterministicID(sourceID, targetAgent string) string

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.

type Turn

type Turn struct {
	Role Role   `json:"role"`
	Text string `json:"text"`
	// Tool is the tool/command name when Role is RoleTool (e.g. "shell").
	Tool string `json:"tool,omitempty"`
}

Turn is one conversation entry in the neutral representation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL