parser: capture sessionKind and promptSource from Claude Code transcripts#1253
parser: capture sessionKind and promptSource from Claude Code transcripts#1253TweedBeetle wants to merge 16 commits into
Conversation
roborev: Combined Review (
|
|
looking |
e35f71d to
0db83fc
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
…ipts Claude Code JSONL entries carry two launch/prompt-provenance fields that the indexer currently discards: - `sessionKind` (session-level, e.g. "bg"): distinguishes headless / background sessions from interactive ones. - `promptSource` (per user turn, e.g. "typed", "queued", "system", "sdk"): distinguishes a human-typed prompt from a queued, system, or SDK-injected one. Neither is currently recoverable from the archive, so downstream consumers cannot tell a headless run from an interactive one, nor a typed prompt from an injected one. Both are captured by mirroring the existing `entrypoint`/`agent_label` handling exactly: - `sessionKind` becomes a session-identity field alongside `entrypoint` (first-non-empty-wins, incremental identity escalation, sessions column). It is blanked for the Cowork and Qoder parsers that reuse the Claude parse body, matching how they blank `entrypoint`. - `promptSource` becomes a per-message column alongside `source_type` and `is_sidechain`, including the retained-field whitelist in compactClaudeEntry and the message token fingerprints that the PG/DuckDB push fast paths compare against. Both columns default to empty, so historical transcripts that predate the fields and other agents that never emit them are unaffected. Capture is mirrored across the SQLite, PostgreSQL, and DuckDB stores and the artifact manifest. No counting or aggregation logic changes; this is field capture only. The source of truth for both fields is the Claude Code transcript format itself, where `sessionKind` and `promptSource` appear as top-level keys on session JSONL entries.
The batched MessageTokenFingerprints query omitted prompt_source while tokenFingerprintRow.appendTo already hashed it, so batched fingerprints disagreed with the per-session MessageTokenFingerprint for any non-empty value, causing perpetual push mismatches. The fixture now carries a non-empty prompt_source so the batched/per-session identity test pins the field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows now bind 26 parameters, so 39 rows per statement overflowed the historic 999-variable limit (1014 binds). Reduce to 38 rows (988). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cile The full-resync orphan copy's message column allowlist omitted prompt_source, silently dropping it for sessions whose source files no longer exist. The transcript-revision reconcile comparison now treats prompt_source as a peer of source_subtype; as with that column's own introduction, sources predating the column skip the reconcile pass. Adds a round-trip test covering session_kind and prompt_source survival. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Parser output changed but the data version stayed at 71, so already archived Claude sessions would never re-parse and their session_kind and prompt_source columns would stay empty indefinitely. Bumping forces the standard full resync, matching how agent_label/entrypoint landed in v66. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
recomputeHeapBytes sums per-message string field sizes to bound heap release; the new prompt_source field was missing from the sum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the provider format provenance rule, note the 2026-07-24 local reverification that observed the two top-level keys the parser now captures; evidence class stays no-public-source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
segmentMessage mirrored source_subtype but omitted the new prompt_source field, so artifact exports dropped it. The canonical golden is unchanged because the field is omitempty and absent there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follows the dataVersion bump for session_kind/prompt_source backfill; the pin test's name and reason now reference the new version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SidebarSessionIndexRow carried agent_label and entrypoint but not the new session_kind, so the sidebar API could surface one identity field and not the other. Adds the column to the SQLite, PostgreSQL, and DuckDB sidebar queries and scans, keeping all backends in lockstep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
npm run generate:api picked up the new fields on DbMessage, DbSession, and DbSidebarSessionIndexRow. Unrelated generator drift from earlier merges (duckdb attach_timeout, vector push flags, source-layout stats) is left out to keep this change scoped; it needs its own regen chore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mirror gained sessions.session_kind and messages.prompt_source; per the mirror invariant, a schema change bumps SchemaVersion and forces a full rebuild. The column shape gate already rebuilds old mirrors, so this makes the version metadata agree with the shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
extractQueuedCommand dropped the top-level promptSource on type=attachment queued_command entries, so queued prompts stored an empty messages.prompt_source while standard user turns captured it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7219443 to
386d033
Compare
roborev: Combined Review (
|
What
Capture two Claude Code launch/prompt-provenance fields that the indexer currently reads past and discards:
sessionKind— session-level (e.g."bg"). Present on background/headless sessions, absent on interactive ones. Lands in a newsession_kindcolumn onsessions.promptSource— per user turn (e.g."typed","queued","system","sdk"). Marks whether a turn was a human-typed prompt or a queued/system/SDK-injected one. Lands in a newprompt_sourcecolumn onmessages.Why
Both signals exist on essentially every current Claude Code transcript but are thrown away at parse time, so the archive can't answer two questions it otherwise has the data for: was a session interactive or headless, and was a given prompt typed by a human or injected. This mirrors the recently-added
entrypointcapture — same shape of information, same reason to keep it.How
The change follows the existing
entrypoint/agent_labelandsource_type/is_sidechainhandling rather than inventing a new pattern:sessionKindis treated as a session-identity field alongsideentrypoint: first-non-empty-wins across the file, wired into the incremental identity-escalation check, and blanked by the Cowork and Qoder parsers that reuse the Claude parse body (exactly as they blankentrypoint).promptSourceis treated as a per-message source column alongsidesource_type/is_sidechain, including the retained-field whitelist incompactClaudeEntryand the message token fingerprints the PG/DuckDB push fast-paths compare against.Both columns are
NOT NULL DEFAULT ''and back-fill lazily on the next parse of each session, so transcripts that predate the fields and other agents that never emit them read empty and are otherwise untouched. Capture is mirrored across the SQLite, Postgres, and DuckDB stores and the artifact manifest.Scope / limitations
session_kind/prompt_sourceare populated going forward; existing rows stay empty until their session is next re-parsed (no forced resync, matching how additive columns are handled elsewhere).Where to look
internal/parser/claude.go— the capture site (sessionKindnext toentrypoint;promptSourcenext toisSidechain) and thecompactClaudeEntryretained-field whitelist.internal/parser/claude_provider.go,provider.go,internal/sync/engine.go— incremental identity threading forsessionKind.internal/db/{schema.sql,db.go,sessions.go,messages.go}— SQLite columns, migrations, and read/write plumbing.internal/postgres/*andinternal/duckdb/*— the mirrored columns and fingerprints.The source of truth for both fields is the Claude Code transcript format, where
sessionKindandpromptSourceappear as top-level keys on session JSONL entries.