Skip to content

Trace schema

One JSONL file per session. The omp extension or a harness adapter writes it; only the binary reads it. A trace is the only input to reflection (the evidence boundary).

traces/<session_id>.jsonl, one JSON object per line, written append-only by the extension: {"seq": <u32>, "t": <unix ms>, "ev": "<name>", ...}. The extension never reads it back.

ev fields
session_start session_id, cwd, repo (git toplevel or null), branch_hint (basename), model {provider,id}, mode, agent {kind,name}, omp_version, loaded_entries [{id, version, sha}...] (match entries with a predicate hit, minus retrievehooks, which enter throughretrieval; sha= the entry'scontent_sha256; the plain [id…]of older traces is still read,trace::LoadedEntry), hooks_loaded [id...], staging_dirs [...], repo_remote (origin URL as git prints it, or null; fold_tracenormalizes it,identity::normalize_remote), subpath (cwd relative to repo, “”at the top), head_sha (HEAD at start, null before the first commit), visible_entries [id...] (entries behind the shared skills dir's links,SkillsSync::visible, §16) — ADR 001 identity; the omp extension takes toplevel and HEAD from one git rev-parse --show-toplevel --verify -q HEAD and the remote from one git remote get-url origin (3 s each, fail open: null)
input text (≤16 KB), source, idle (bool), after_deliverable (last assistant text ≤2 KB or null), prelabel ∈ {"none","interrupt","revert","reprompt","negation","mark:<class>"}, skills_invoked [names from /skill: tokens]
tool_result `tool, call_id, input (JSON ≤8 KB, keys kept, long strings truncated with “…[+N]”), args_hash (sha256 of canonical JSON of full input), result_bytes, is_error, error_class (null
tool_blocked tool, call_id, hook_id, reason (a block or stuck hook); args_hash when written by the Claude Code adapter, whose stuck window is read back from the trace
turn_end model {provider,id}, usage {input,output,cacheRead,cacheWrite,totalTokens,cost}, stop_reason, text_head (≤2 KB)
agent_end turns, is_terminal, yielded
model_change from {provider,id} , to {provider,id}, reason (from retry_fallback_applied or turn-to-turn change)
mark `class ∈ correction
retrieval entry_ids [retrieve hook ids], units [{entry_id, path (relative to the entry dir), bytes}], bytes — written when a retrieve hook appended a different unit set; trace::fold_trace adds entry_ids and every units[].entry_id to the session’s loaded_entries, so counting attributes the added context to the hook and to the packages it came from
session_shutdown reasons [...], flagged (bool), files_changed [[path, sha256]...] — the files successful edit/write/multiedit/notebookedit/apply_patch/ast_edit calls named (path/file_path/paths, and the paths inside an edit or patch payload), hashed at shutdown: the first 50 readable files ≤ 1 MB, repo-relative inside the repo; omitted when hashing overruns its budget (extension 1 s of the 2 s shutdown budget, Claude Code SessionEnd 500 ms) or nothing changed (trace::changed_paths, trace::hash_changed_files)

Pre-classification (extension, no model; regex constants live in rematch-core/src/redirect.rs and are copied verbatim into the extension): mark = /^\/rematch\s+mark\s+(correction|preference|scope)\b/i (the Claude Code adapter first spells its /rematch-mark <class> and /rematch:rematch-mark <class> as /rematch mark <class>, §18.1); interrupt = input while the agent loop is running (between agent_start and agent_end with willContinue; ctx.isIdle() is also false while a finished turn settles and over-counted takeovers in live use; the Claude Code adapter: a prompt delivered into a run that had no Stop and no Esc abort since its input, §18.1); revert = /\b(?:revert|undo|roll ?back|put it back|git restore)\b|\bgit checkout --(?:\s|$)/i; negation = /^(?:no|nope|don't|do not|stop|not that|wrong|that's (?:not|wrong)|that is wrong|this is wrong|incorrect|you didn't|you did not|not what i|i said|actually|instead)\b/i (leading whitespace trimmed); reprompt = Jaccard(tokens(text), tokens(previous input)) ≥ 0.6 with tokens [a-z0-9]+ on lowercased text and at least 6 tokens in the new input (“four” after “three” is not a redo). Precedence: mark > interrupt > revert > negation > reprompt > none. Session is flagged when any of: ≥1 prelabel ≠ none; is_error tool results ≥ 3 in any window of 6 consecutive tool results (tool-error cluster); a mark (trace::flag_reasons). Flag = write pending/<session_id>.json.

Imported sessions get the same trace after the fact (rematch_core::import::trace_records_of, the active branch of the omp JSONL): session_start (header id/cwd, repo by walking up to .git, model of the first assistant message, mode: "import", agent main or sub for subagent files, omp_version from runtime.version, no loaded entries/hooks/staging dirs); each user message not attributed to an agent → input with the rules above (idle unless the previous assistant stopped aborted; slash/!/% lines other than mark and /skill: dropped as live), preceded by agent_end for the run it ends; each toolResult → tool_result paired with its toolCall by id; each assistant message → turn_end after its tool results, preceded by model_change when provider/model changed; then agent_end and session_shutdown with the flag reasons. Not recoverable from omp JSONL: input.source (empty), branch_hint beyond the repo basename, loaded_entries/hooks_loaded/staging_dirs (empty), tool_blocked, mark, retry-fallback model_change reasons, agent_end.is_terminal (always true). Redirect and error-cluster seqs of an imported ledger row address this synthesized trace.

Claude Code and Codex sessions get the same schema: live from the Claude Code plugin’s hooks (trace-event, §18.1) and after the fact from their transcripts (rematch_core::import_claude::trace_records_of, import_codex::trace_records_of, §18).

No trace record carries the session’s domain (§6 “Domain”): session_start stays as above in the extension and both adapters. The binary decides it from the folded record — count (and reflect, create --from task, activate) when it folds a trace, the three importers when they import — so the policy, the branch’s domain.yml and the inference table in force at that moment apply, and lake migrate re-decides it for recorded sessions. Load time knows only the place: match reports the place’s declared domain as domain (§2), which a harness may read but need not echo.

Identity fields by writer (ADR 001): the Claude Code SessionStart fills repo_remote/subpath/head_sha by file reads (identity::identify: .git dir or worktree gitdir: file, the common dir’s config, HEAD and refs, no git process), versioned loaded_entries from match, visible_entries from its sync, and SessionEnd writes files_changed. The three importers fill repo_remote and subpath from the repo on disk at import time (identity::session_place), never head_sha or files_changed (the import runs after the fact); Codex records its offered rematch skills as visible_entries and the loaded ones with version and sha from their rematch.json. count fills a missing repo_remote/subpath from the repo on disk (identity::fill_session), e.g. an extension trace of a repo without origin.