transcript

package
v0.69.0 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package transcript reads an AI coding agent's on-disk conversation transcript and renders it to a neutral, agent-agnostic Markdown document.

It supports Claude Code and Codex as source agents. The rendered output is handed to a different agent during an in-place migration (see docs/design/2026-06-24-cross-agent-conversation-migration-design.md) so the new agent can continue the work with the full readable history.

Reading is deliberately defensive: undocumented, drifting formats and partially-written (live) files are tolerated by skipping unparseable lines and counting them, rather than failing.

Index

Constants

View Source
const (
	AgentClaude = "claude"
	AgentCodex  = "codex"
)

Agent identifiers for supported source transcripts.

Variables

View Source
var ErrNoTurns = errors.New("transcript contains no usable turns")

ErrNoTurns is returned by Read when a transcript parsed successfully but contained no usable conversation turns. Callers use this to fail fast before disrupting a running session.

View Source
var ErrUnsupportedAgent = errors.New("unsupported source agent for migration")

ErrUnsupportedAgent is returned for source agents without a reader.

Functions

func BuildForkSeedPrompt added in v0.69.0

func BuildForkSeedPrompt(srcAgent, contextPath string) string

BuildForkSeedPrompt is the opening prompt for a cross-agent fork. Unlike a migration (which retains the source's worktree), a fork starts in a FRESH worktree branched from the base branch, so the source session's code changes — both uncommitted edits and commits on its branch — are NOT present on disk. The prompt says so explicitly to stop the new agent assuming they exist.

func BuildSeedPrompt

func BuildSeedPrompt(srcAgent, contextPath string) string

BuildSeedPrompt is the short instruction passed to the target agent as its opening prompt, pointing it at the rendered context file.

func CodexRolloutID

func CodexRolloutID(path string) (string, bool)

CodexRolloutID reads a rollout's session_meta id. Exported for the daemon's post-start id capture.

func CodexSessionIDSince added in v0.59.0

func CodexSessionIDSince(root, worktreePath string, since time.Time) (string, bool)

CodexSessionIDSince returns the native session id of the Codex rollout for a cwd created at/after `since`. Unlike LocateCodexSinceIn (newest-by-mtime), it refuses to guess when the (since, cwd) window contains rollouts with two or more DIFFERENT session ids — the concurrent mirror / in-place case — returning ("", false) so the caller falls back to a non-pinned resume rather than cross-assigning another session's conversation. Pass root "" for the daemon default.

func LocateCodexSince

func LocateCodexSince(worktreePath string, since time.Time) (string, bool)

LocateCodexSince returns the newest Codex rollout for a cwd whose mtime is at or after `since`. Used for post-start session-id capture: filtering by start time avoids picking a stale rollout from a prior session in the same cwd (a real hazard for in-place sessions and codex→codex migrations).

func LocateCodexSinceIn added in v0.59.0

func LocateCodexSinceIn(root, worktreePath string, since time.Time) (string, bool)

LocateCodexSinceIn is LocateCodexSince scoped to an explicit Codex state root (CODEX_HOME). Pass "" to use the daemon's default root. This matters because the daemon-side scrape runs in the daemon process, but CODEX_HOME can be set per-session via the agent's launch env — reading the daemon's os.Getenv would scan the wrong directory and silently miss the rollout.

func Supported

func Supported(agent string) bool

Supported reports whether an agent can be used as a migration source.

Types

type Conversation

type Conversation struct {
	SrcAgent     string
	Turns        []Turn
	DroppedLines int // unparseable/skipped lines (format drift, partial tail)
}

Conversation is the parsed, normalized transcript.

func Read

func Read(agent, agentSessionID, worktreePath string) (*Conversation, error)

Read locates and parses the transcript for a session. agentSessionID is the id graith tracks for the source agent (used to locate Claude transcripts); worktreePath is the session's working directory (used to locate Codex transcripts and as a fallback). Returns ErrNoTurns if the transcript parsed but yielded nothing usable.

func (*Conversation) Render

func (c *Conversation) Render(opts RenderOptions) string

Render produces the neutral Markdown context document handed to the target agent. Turns are selected newest-first within the size budget but rendered chronologically so the document reads as a narrative.

type RenderKind added in v0.69.0

type RenderKind int

RenderKind selects the framing of the rendered document's header, which differs between an in-place migration and a cross-agent fork.

const (
	// RenderMigrate frames the document for an in-place agent take-over: the
	// worktree is retained, so prior code changes are already on disk.
	RenderMigrate RenderKind = iota
	// RenderFork frames the document for a cross-agent fork: the new agent runs
	// in a FRESH worktree branched from base, so prior code changes are NOT on
	// disk and must be re-applied.
	RenderFork
)

type RenderOptions

type RenderOptions struct {
	// Kind selects the header framing (migrate vs fork). Defaults to
	// RenderMigrate (the zero value).
	Kind RenderKind
	// MaxBytes is the approximate size budget for the rendered body. When the
	// transcript exceeds it, the oldest turns are elided (newest kept). 0 uses
	// a default.
	MaxBytes int
	// MaxToolOutput caps each tool output block. 0 uses a default.
	MaxToolOutput int
}

RenderOptions tunes the Markdown output.

type Role

type Role string

Role classifies a turn in the neutral model.

const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
	// RoleContext is historical developer/system context (e.g. Codex
	// `developer` messages). It is rendered as background, never promoted to
	// live instructions.
	RoleContext Role = "context"
)

type Source added in v0.69.0

type Source struct {
	Path    string
	Size    int64
	ModTime time.Time
}

Source is one on-disk transcript file contributing to a session's usage, with the stat fields the daemon fingerprints to decide whether a re-parse is needed (unchanged size+mtime ⇒ reuse the cached Usage without reading).

func Locate added in v0.69.0

func Locate(agent, agentSessionID, worktreePath string) ([]Source, error)

Locate returns every readable transcript file for the current agent of a session, with stat metadata for fingerprinting. It resolves without parsing so the caller can skip a re-read when nothing changed.

Claude resolves to a single file (its transcript is one JSONL keyed by the agent session id). Codex resolves to the single located rollout in v1; summing across a resumed session's multiple rollouts is deferred until the resume append-vs-fork behaviour is confirmed with a real fixture (#644), to avoid over-counting unrelated same-cwd sessions in the meantime. The []Source return keeps that a drop-in extension.

type ToolCall

type ToolCall struct {
	Name   string
	Args   string
	Output string
	Failed bool
}

ToolCall is a single tool invocation and its result, flattened from whatever nested or call-id-linked representation the source agent used.

type Turn

type Turn struct {
	Role     Role
	Text     string
	Tool     *ToolCall // non-nil when Role == RoleTool
	SrcAgent string    // source agent that produced the turn
}

Turn is one neutral conversation turn.

type Usage added in v0.69.0

type Usage struct {
	Input         int64 // fresh, uncached input tokens
	Output        int64 // completion tokens
	CacheCreation int64 // cache-write tokens (Claude only; Codex has no such concept)
	CacheRead     int64 // cache-hit input tokens
	Unclassified  int64 // provider aggregate we can't break down (legacy Codex)

	// Found reports whether at least one valid usage record was observed. A
	// transcript with no usage records yields Found=false — distinct from a
	// genuine zero — so callers never present a confident zero for a file they
	// couldn't read usage from.
	Found bool
	// Dropped counts records that were skipped or conflicted (format drift,
	// duplicate usage with differing values, un-deduplicatable records). A
	// nonzero value flags the total as approximate.
	Dropped int
}

Usage is aggregated token usage parsed from one or more transcript files.

The four token categories plus Unclassified are MUTUALLY EXCLUSIVE by construction, so Total() is a real total rather than a sum of overlapping buckets. Each reader is responsible for producing an exclusive breakdown; a provider aggregate that can't be broken down (legacy Codex) lands in Unclassified rather than being misattributed to a semantic category.

See docs/design/2026-07-13-per-session-token-accounting.md.

func UsageFrom added in v0.69.0

func UsageFrom(agent string, sources []Source) (Usage, error)

UsageFrom parses the given sources and returns their combined usage. Unlike Read it never returns ErrNoTurns: a transcript with no usage records yields a zero Usage with Found=false, not an error.

func (*Usage) Total added in v0.69.0

func (u *Usage) Total() int64

Total is the grand total across the exclusive categories, saturating at MaxInt64 rather than wrapping to a negative (which the CLI would then render as a confident "0").

Jump to

Keyboard shortcuts

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