Skip to content

read_conversations: author column, Claude/Codex lineage parity, tool-result content - #3

Merged
asubbarao merged 73 commits into
mainfrom
luna/author-column
Oct 7, 2026
Merged

asubbarao merged 73 commits into
mainfrom
luna/author-column

Conversation

@asubbarao

Copy link
Copy Markdown
Owner

Summary

read_conversations() now says who wrote each row, and Claude and Codex agree on subagent lineage. A subagent's brief from its calling agent used to arrive as message_role = 'user', so downstream queries counted agent-written briefs as the person's words (about 1,900 rows / 3.5M characters in one real corpus).

  • author column (new, never NULL): user, caller, agent, tool, system. message_role is unchanged, because it is the raw provider fact and adding a value to it would change what WHERE message_role = 'user' returns for every existing user. Decided in one function per system (claude_author, codex_author).
  • Claude client is now filled from the record's entrypoint (cli, claude-desktop, sdk-cli), matching Codex's client.
  • Codex is_agent now follows every native parent pointer (thread_spawn, source.subagent.*, parent_thread_id), matching Claude's subagent handling. Forks stay top-level.
  • Tool results (Claude): carry is_error as status (failed / completed), keep text from array content recursively, and emit placeholders for images, tool_reference and other non-text blocks, so no tool result comes out as an empty string.
  • docs/lineage.sql: recovers the caller of shell-launched codex exec / claude -p sessions by joining the child's first message and working folder to the caller's tool call. Cross-system (Claude calling Codex and the reverse). It is documentation, not reader behaviour.

The branch also carries the earlier unmerged work it sits on (Claude thinking blocks and attachment content, record retention, MinGW build fixes); the diff against main is large for that reason. The author change itself is about 160 lines of Rust plus docs and fixtures.

Verification

  • make debug && make test_debug (DuckDB v1.5.5): 36/36 SQLLogicTest files and 4 build-script tests pass; cargo test --lib: 16 passed.
  • New tests: claude_lineage_author.test, codex_lineage_author.test (every author value; interactive, in-process subagent, exec, sdk-cli, Desktop and fork cases; message_role unchanged).
  • Real-corpus probe (read-only): 691/691 Claude subagent files and 190 Codex child threads have a parent. The shell-launch join linked 70 of 115 known launches, 69 correct.

Known limits

  • A person running codex exec or claude -p by hand is still caller.
  • Claude records older than the promptSource / turnOrigin flags fall back to isMeta; about 290 command-output rows stay user.
  • Shell-launch recall is 61%; the misses are not diagnosed.

🤖 Generated with Claude Code

asubbarao and others added 30 commits June 11, 2026 17:15
Sub-agent transcripts live at projects/<enc>/<session-id>/subagents/
agent-*.jsonl — one level deeper than discover_conversation_files
walked — so they were never returned and is_agent was never set
(0 rows with is_agent=true on real-world ~/.claude data).

Extract the projects/ walk into discover_project_jsonl_files and add a
discover_subagent_files pass that descends into <session-id>/subagents/,
tagging those transcripts is_agent=true. Adds a regression fixture
(test/data_claude_subagents) and claude_subagents.test.
Adds a ClaudeDesktop provider that reads Claude Desktop ("Cowork")
sessions. Desktop stores each transcript using the same camelCase schema
as Claude Code, nested under
local-agent-mode-sessions/**/.claude/projects/<enc>/<session>.jsonl, so
the provider is a discovery change only and delegates to the existing
Claude line-parser. Auto-detected via the local-agent-mode-sessions/
directory, or forced with source='claude-desktop'.

- detect.rs: add Provider::ClaudeDesktop + detection/source parsing
- utils.rs: discover_claude_desktop_files() reusing the shared
  projects/ walk (subagent discovery included)
- conversations.rs: reuse the Claude parser via a shared loader, tagged
  source='claude-desktop'
- plans/todos/history/stats: empty arms (Desktop has no such files)
- tests: claude_desktop_conversations.test with fixtures mirroring the
  real on-disk layout
Rows without a sessionId payload (summary rows, parse errors, future
variants without base fields) in projects/<enc>/<session>/subagents/
agent-*.jsonl previously fell back to the agent-* file stem, inventing
fake sessions and breaking the documented session_id join key across
conversations/history/todos.

The fallback now derives the parent session id from the path
(parent of the subagents/ directory). Adds a sessionId-less summary row
to the subagent fixture plus assertions that fail without the fix
(COUNT(DISTINCT session_id) returned 2, and the summary row's
session_id was 'agent-omega01').

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deeply-nested desktop fixture paths exceeded ~250 chars (workspace/
space/local-session/.claude/projects/<long-encoded>/<uuid>/subagents/...),
risking checkout failures on Windows without long-path support. Shorten the
intermediate directory names (ws-1, sp-2, local-e1, ditto-f1, -Users-
testuser-Claude-lams). Discovery only keys on the top-level
local-agent-mode-sessions/ dir, so detection is unaffected; all 15 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tions in README

- Add Claude Desktop path example to Default Behavior section
- Add Claude Desktop to Provider Detection SQL example
- Fix source column description in all function tables to include claude-desktop
Adds a parser for OpenAI Codex CLI rollout transcripts, fulfilling the
README's "OpenAI Codex … Coming Soon™" roadmap item.

Conversation data is read ONLY from the rollout JSONL files at
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl. Each line is
{type, timestamp, payload}. The parser uses the same "session metadata
backfill" technique as the Copilot provider: session_meta (first line)
and the latest turn_context.model are carried forward and applied to
every emitted row; session_meta / turn_context / token_count lines are
not emitted as rows.

response_item payloads map to rows: message (role from payload),
reasoning (own message_type, matching Copilot), function_call and
function_call_output (tool fields). event_msg user_message/agent_message
are treated as a fallback only, to avoid double-counting the text that
also appears in response_item messages.

The ~/.codex/*.sqlite files (codex-dev.db, logs_*, state_*, goals_*,
memories_*) are app automation / logging / inbox data and are
intentionally ignored.

Codex is the richest of the providers for column coverage: per-line
timestamp, model (turn_context), git_branch, cwd, version, and
repository (session_meta) are all populated; uuid/parent_uuid/slug/
stop_reason are NULL, consistent with the existing NULL-gap convention.

Auto-detection: a sessions/ directory containing all-digit (year) subdirs
resolves to Codex. Explicit source='codex' override is also supported.
No new build dependencies.

Adds an anonymized synthetic fixture
(test/data_codex/sessions/2026/03/11/rollout-*.jsonl) exercising every
payload type and test/sql/codex_conversations.test (row/session counts,
source, message_type and role mapping, per-line timestamps, carried-
forward session metadata, tool fields, and NULL invariants).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a Cursor provider that reads chat history from Cursor's SQLite KV
store (state.vscdb) and surfaces it through read_conversations().

Format (reverse-engineered, see src/types/cursor.rs):
- table cursorDiskKV (key TEXT, value BLOB)
- composerData:<id>               -> a conversation/session
- bubbleId:<composerId>:<bubbleId> -> an individual message
- message order comes from each composer's fullConversationHeadersOnly
- bubble type 1 = user, 2 = assistant
- tool fields from toolFormerData; timestamp from createdAt (epoch ms)

Implementation notes:
- Gated behind a default-on `cursor` cargo feature that pulls in
  `rusqlite` (bundled SQLite). `--no-default-features` drops it cleanly
  with a no-op fallback; both feature states compile warning-free.
- Defensive parsing: tolerates missing keys/rows, never panics
  (every field falls back to NULL via ..Default::default()).
- v1 uses plain `text`; the Lexical `richText` tree is left as a future
  enhancement. git_branch/cwd/version are NULL (not persisted by Cursor),
  consistent with the existing NULL-gap convention.
- Only read_conversations() is implemented for Cursor; plans/todos/
  history/stats return no rows for source='cursor'.

Test fixture test/data_cursor/state.vscdb (1 composer, 2 bubbles) is
committed and reproducible via test/data_cursor/make_fixture.py. All
content is synthetic. test/sql/cursor_conversations.test adds 25
assertions mirroring claude_desktop_conversations.test (counts, type/role
mapping, header-ordering via uuid/parent_uuid, ISO-8601 timestamps, tool
extraction, NULL invariants, and empty results for the other tables). The
full suite passes (16/16 .test files).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ount

Match the structure of the merged claude_desktop_conversations.test:
- NOT NULL invariants (source/session_id/file_name/message_type, line_number >= 1)
- ISO 8601 timestamp-format validation (Codex's per-line timestamp strength)
- "other tables return empty" block asserting read_plans/read_todos/
  read_history/read_stats all return 0 for source='codex'

Codex test now has 24 assertions (matching claude_desktop). Bump the
README pinned-assertion count from 231/15 files to 255/16 files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds Gemini CLI as a fourth provider alongside Claude Code, Claude Desktop,
and GitHub Copilot CLI, following the same Provider enum + per-table match-arm
pattern (no changes to lib.rs / vtab.rs / Cargo.toml deps).

On-disk format (read-only verified across ~320 local sessions, Nov 2025 - Jun
2026, stable schema): one JSON file per session at
  ~/.gemini/tmp/<project-hash>/chats/session-<ts>-<id>.json
with root { sessionId, projectHash, startTime, lastUpdated, messages[], kind?,
summary? }. Messages are { id, timestamp, type, content }; gemini (assistant)
messages add model, tokens, thoughts[], and toolCalls[].

Mapping:
- type=gemini -> message_type='assistant'; user/info/error pass through.
- Each toolCall also emits a tool_call row, parent_uuid linking to its message.
- kind='subagent' -> is_agent=true.
- tokens.cached -> cache_read_tokens; input/output map directly.
- project-hash resolved to a real path via projects.json alias map when present.
- read_plans / read_todos / read_history / read_stats return empty for Gemini
  (no side files; plan/todo state lives inline in the transcript), consistent
  with the existing NULL-gap convention.

Auto-detect marker: a tmp/ directory plus an installation_id file (both
Gemini-CLI-specific). Explicit override: source='gemini'.

Tests: test/data_gemini/ fixtures (main + subagent sessions) and
test/sql/gemini_conversations.test (32 assertions: counts, per-type/role
mapping, tool_call splitting, is_agent, alias resolution, token mapping,
NULL-gap invariants). cross_source.test and column_validation.test extended
with Gemini cases. All 16 test files pass.

Refs axsaucedo#13.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the cross-source coverage the Grok and Gemini provider PRs add,
for consistency across all the new-provider PRs:

- cross_source.test: append a Cursor section — 3-source UNION ALL
  conversation count (claude 180 + copilot 53 + cursor 2 = 235), the
  per-source breakdown, distinct-source count, a session-id no-collision
  check against Copilot, and a Cursor-only file_name='state.vscdb' check.
- column_validation.test: append Cursor NULL/type invariants (source/
  session_id/message_type never NULL, line_number BIGINT > 0, is_agent
  BOOLEAN, git_branch/version always NULL) and a GROUP BY source row.
- README: bump the pinned-assertion line to 270 across 16 test files.

These cases run under the normal `make test` because the `cursor` cargo
feature is default-on; the section headers note the feature requirement.
Full suite green (16/16); cargo check --no-default-features still clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the structure the sibling provider PRs (axsaucedo#11 Grok, axsaucedo#14 Gemini) use
so all provider PRs are consistent:

- cross_source.test: claude + copilot + codex UNION ALL — total count
  (180 + 53 + 5 = 238), per-source breakdown, distinct-source count (3),
  session-id non-collision with claude/copilot, and a check that only
  codex rows carry the function_call message_type.
- column_validation.test: a "NULL pattern validation: Codex" block
  (source/session_id/message_type never NULL, line_number BIGINT and
  positive, uuid always NULL) plus a GROUP BY source case (codex = 5).

Bump README pinned-assertion count from 255 to 266 across 16 test files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI built the extension for DuckDB v1.5.3 (workflow duckdb_version /
Makefile DEFAULT_TARGET_DUCKDB_VERSION) but the c_api test runner's venv
installs the latest stable duckdb from PyPI, which is now v1.5.4. With
USE_UNSTABLE_C_API=1 the binary is pinned to the exact build version, so
loading the v1.5.3 extension into the v1.5.4 test runner failed with:

  InvalidInputException: ... built specifically for DuckDB version
  'v1.5.3' and can only be loaded with that version ... (this version of
  DuckDB is 'v1.5.4').

Bump everything to v1.5.4 via scripts/update_duckdb_release.py so the
build target matches the latest-stable test runner: workflow input,
Makefile default, duckdb-release.toml, Cargo deps (1.10504.0), Cargo.lock,
and example pyprojects.

Verified locally: builds, stamps duckdb_version=v1.5.4, loads into duckdb
1.5.4, and all 16 sqllogic tests pass.
The extension built for v1.5.3 but the test runner installs the latest stable
duckdb from PyPI (now v1.5.4), causing a load-time version mismatch. Bump the
build target (workflow, Makefile, Cargo, duckdb-release.toml) to v1.5.4 via the
repo's own scripts/update_duckdb_release.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The extension built for v1.5.3 but the test runner installs the latest stable
duckdb from PyPI (now v1.5.4), causing a load-time version mismatch. Bump the
build target (workflow, Makefile, Cargo, duckdb-release.toml) to v1.5.4 via the
repo's own scripts/update_duckdb_release.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ows-gnu

The Cursor provider pulls in rusqlite with the `bundled` feature, which
compiles sqlite3.c. libsqlite3-sys builds it with -DSQLITE_THREADSAFE=1 and
without -DHAVE_LOCALTIME_R on Windows targets, and mingw gcc emits __chkstk_ms
stack probes. On the windows_amd64_mingw arch (Rust target x86_64-pc-windows-gnu)
these MSVCRT/libgcc symbols are not otherwise on the link line, causing LNK2019
unresolved externals: __imp__beginthreadex, __imp__endthreadex, __imp__localtime64,
__chkstk_ms.

Add a [target.x86_64-pc-windows-gnu] section to .cargo/config.toml that links
msvcrt, mingwex, mingw32, gcc and kernel32. Scoped to the gnu target only; the
MSVC/Linux/macOS builds are unaffected. Keeps SQLITE_THREADSAFE=1 for correctness
(DuckDB invokes table functions from worker threads).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…them)

The Gemini provider commit (5e07de2) committed only test/data_gemini/
installation_id and projects.json. The actual session transcripts, which
the on-disk Gemini layout nests under tmp/<project-hash>/chats/*.json, were
silently excluded by the bare `tmp` pattern in .gitignore. With no chat
files present, read_conversations(path='test/data_gemini') returned 0 rows,
so gemini_conversations.test (and the gemini cases in cross_source.test /
column_validation.test) failed expecting 11.

Fix:
- Negate the `tmp` ignore for test/data_gemini/tmp/** so the fixtures track.
- Add the two fixture sessions the test pins to:
  - tmp/alpha/chats/...  main session 11111... (6 messages -> 9 rows;
    two assistant turns carry 2 and 1 toolCalls -> 3 tool_call rows).
  - tmp/beta01/chats/... subagent session 22222... (kind=subagent -> is_agent,
    2 rows). beta01 has no projects.json alias so project_path stays empty.
  Shaped to satisfy all 32 assertions: per-type/role counts, 3 distinct
  models, 3 tool names (run_shell_command/read_file/write_file), is_agent
  split 9/2, alias resolution to /Users/testuser/project-alpha, and
  SUM(cache_read_tokens)=12000 (5000+4000+3000 via tokens.cached).

No parser changes: src/conversations.rs gemini loader was already correct;
only the data fixtures were missing from version control.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hain mismatch)

The Cursor provider depends on libsqlite3-sys (rusqlite 'bundled'), which
compiles sqlite3.c. On the windows_amd64_mingw arch that C is built with mingw
gcc and emits libgcc/MSVCRT symbols (__chkstk_ms, _beginthreadex, _endthreadex,
_localtime64) that the link step cannot resolve — an unsupportable gcc-compile /
link mismatch for a bundled-C crate. Every other arch (MSVC Windows, Linux
x64/arm64, macOS x64/arm64) builds and tests cleanly, so exclude only mingw.

Reverts the earlier ineffective .cargo/config.toml link-flag attempt (the flags
could not provide libgcc's __chkstk_ms to the linker).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture the existing behaviour where each assistant turn surfaces its
first tool call inline on the assistant row in addition to emitting a
dedicated tool_call row. This causes the first tool of every turn to be
counted twice in an unfiltered tool_name aggregate (run_shell_command and
write_file each report 2 uses for a single invocation).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the aggregation assertions to the desired behaviour: assistant rows
no longer carry tool_* fields, tool_name appears exactly once per call, and
an unfiltered tool-usage aggregate reports a single use per invocation. This
test fails until the inline duplication on the assistant row is removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Gemini loader surfaced each assistant turn's first tool call inline on
the assistant row in addition to emitting a dedicated tool_call row for every
call. Because Gemini, unlike Claude, always emits dedicated tool_call rows,
the inline copy made the first tool of each turn appear twice, inflating
unfiltered tool_name aggregates. Leave the assistant row's tool_* fields unset
so each invocation is represented exactly once by its tool_call row.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
feat: add Gemini CLI conversation parser (source='gemini')
Extend the Codex rollout fixture so a user/assistant turn is stored in
both response_item/message and event_msg/{user,agent}_message, mirroring
real Codex sessions. Pin the conversation row counts and a dedicated
single-count section to the current parser output, which surfaces each
duplicated turn as an extra user/assistant row.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…turns

Flip the Codex conversation counts to treat response_item/message as the
single source of truth, so a turn duplicated across event_msg/{user,agent}_message
is counted once. These assertions fail until the loader stops emitting the
event_msg copies when canonical response_item rows are present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Real Codex sessions store each user/assistant turn twice: once as
response_item/message and again as event_msg/{user,agent}_message. The
loader emitted both, inflating user/assistant turn counts on every real
session.

Buffer the event_msg user/agent rows per file and append them only when
the session contains no response_item/message rows, so they act as a true
fallback for sessions that lack canonical turns while normal sessions are
counted once.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the current behaviour where both user and assistant Codex turns
surface under a literal 'message' message_type, so cross-source filters on
message_type = 'user' / 'assistant' return no Codex rows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Flip the Codex message_type assertions so user and assistant turns carry
their role-specific type instead of the generic 'message', matching the
Claude, Copilot, and Gemini providers and the cross-source filter contract.
These assertions fail until the loader derives message_type from the turn role.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codex response_item/message turns were all emitted with a literal 'message'
message_type, so cross-source filters on message_type = 'user' / 'assistant'
silently skipped Codex while matching Claude, Copilot, and Gemini.

Map the role to a role-specific message_type (user/assistant, falling back to
the raw role) to match the other providers. Detect canonical message turns by
the response_item payload type so the event_msg deduplication fallback keeps
working after the message_type change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring the Gemini provider and DuckDB v1.5.4 changes from main into the
Codex branch. Conflicts were all from both branches registering a new
provider at the same dispatch points; resolved by keeping both providers:

- detect.rs: add Codex and Gemini to the Provider enum, detection
  heuristics, and source parsing.
- conversations.rs / utils.rs: keep both provider loaders and discovery
  helpers and both read_conversations dispatch arms.
- history.rs / plans.rs / todos.rs / stats.rs: list Codex and Gemini in
  the shared empty-result arms (Copilot retained for stats).
- README.md: list all supported providers and drop the now-stale
  "Coming Soon" note.
- cross_source.test / column_validation.test: keep both providers'
  coverage sections.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
feat: add OpenAI Codex CLI conversation parser (source='codex')
axsaucedo and others added 29 commits July 28, 2026 08:04
Applied via scripts/update_duckdb_release.py --apply and validated
locally with scripts/validate_duckdb_release.sh (clean debug build,
make test, cargo test, TUI suite, and python smoke test all green).

Opened manually because the scheduled monitor cannot push this branch:
the update touches .github/workflows/MainDistributionPipeline.yml and
the default GITHUB_TOKEN lacks workflow-file push permission.
Publishing actually happens in duckdb/community-extensions (users
install from there), but until now the descriptor ref bump was a manual
prepare_community_extension_pr.py run per release. Trigger it on pushes
to main that change duckdb-release.toml: sync the fork, bump the
descriptor ref to the new main commit, push with RELEASE_PR_TOKEN, and
open the upstream PR. Completes the chain: detect drift -> internal PR
-> merge -> upstream publish PR.
A static branch name would be rejected as non-fast-forward once a
previous release's branch exists on the fork.
A fine-grained PAT cannot open PRs on duckdb/community-extensions, and a
classic PAT with account-wide write is a bigger credential than this
step deserves. Instead, when a release lands on main the workflow files
an assigned issue (plain GITHUB_TOKEN, issues: write) which triggers an
email notification, and the upstream PR is opened locally via the new
/create-upstream-release skill wrapping prepare_community_extension_pr.py.
Un-ignore .claude/skills so project skills are versioned.
* feat: add Grok CLI conversation parser (source='grok')

Adds a parser for xAI Grok CLI (~/.grok) to read_conversations and
read_plans, following the existing Claude/Copilot/Claude-Desktop pattern.

- detect.rs: Provider::Grok, auto-detect (sessions/<%encoded-cwd>/),
  parse_source("grok").
- types/grok.rs: GrokMessage tagged enum (user/assistant/tool_result/
  system) + GrokSummary (summary.json session metadata).
- utils.rs: discover_grok_session_files, read_grok_summary, url_decode.
- conversations.rs: load_grok_rows + grok_message_to_row (session metadata
  from summary.json backfilled onto every row, like Copilot).
- plans.rs: load_grok_rows (per-session plan.md).
- todos/history/stats: empty arms (no equivalent data in v1).
- README: list Grok and document its NULL gaps.
- Fixtures: test/data_grok/ (2 anonymized sessions) + grok_conversations.test
  and grok_plans.test.

Known gaps (left NULL, per the existing NULL-gap convention): chat_history
.jsonl carries no per-message uuid/parent_uuid/timestamp; timestamp is
backfilled from summary.created_at; uuid/parent_uuid are NULL. No new build
dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: lock Grok schema in cross_source + column_validation; fix README assertion count

Adds Grok UNION ALL schema-compatibility cases to cross_source.test
(conversations + plans counts, source breakdown, session-id isolation,
distinct-source count) and Grok NULL-pattern / type / GROUP BY cases to
column_validation.test, mirroring the existing Claude/Copilot sections.

Updates the README pinned-assertion line to 274 assertions across 17 test
files (was a stale 231/15 from before the Claude Desktop + subagents PRs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(grok): map reasoning, multi-tool calls, effort, and subagents

Enhance the Grok conversation parser without schema changes:

- Parse reasoning lines (summary text only; never encrypted_content)
- Emit one tool_call row per tool_calls entry (Gemini-style fan-out)
- Map reasoning_effort → stop_reason (message, else session summary)
- Backfill slug/version from summary generated_title / chat_format_version
- Mark subagent child sessions via meta.json (is_agent + parent_uuid)
- Leave token columns NULL (chat_history has no usage; updates.jsonl OOS)

Fixtures, SQLLogicTests, and README field-map/gaps updated. make test green
(417 pinned assertions / 20 files).

* feat(conversations): add reasoning_effort column for Grok

* feat(grok): fill token columns from updates.jsonl turn_completed

Parse sibling updates.jsonl for sessionUpdate=turn_completed usage and map
inputTokens/outputTokens/cachedReadTokens onto existing token columns. Add
nullable reasoning_tokens (Grok only; other providers stay NULL). v1 stamps
the last usable usage snapshot on every row of the session as a
session/prompt aggregate, not per-message.

* feat(grok): synthetic uuids, session timestamps, stats from signals

* docs: refresh pinned assertion count for Grok follow-ups

21 test files / 433 query blocks after reasoning_effort, reasoning_tokens,
and grok_stats coverage.

* feat(grok): stamp conversation timestamps from updates.jsonl

chat_history has no time fields; the CLI event clock is updates.jsonl.
Walk transcript lines and assign ISO timestamps from matching wire events
(user_message_chunk, agent_thought_chunk, agent_message_chunk, tool_call*)
so the shared `timestamp` column matches Claude/Copilot shape. Fall back
to summary session stamps when updates are missing.

Tests pin system/user/reasoning/assistant times from fixture updates.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lt placeholders

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…pointer

A Codex thread spawned by another thread records it in one of several forms:
source.subagent.thread_spawn, source.subagent.other (guardian review) plus a
top-level parent_thread_id, or an index entry. Only the thread_spawn form set
is_agent, so guardian children and parent-only metadata read as top-level
sessions while their Claude equivalents (subagents/agent-*.jsonl) did not.

is_agent now holds whenever the thread has a parent pointer or any
source.subagent variant, matching Claude. parent_session_id is unchanged;
forks stay top-level.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8MSou6d752jzQTJwxYLtJ
A subagent's brief arrives as message_role = user, so every query that counts
the person's words also counts the briefs its agents wrote for each other
(this machine: ~1,900 rows, 3.5M characters). message_role is the provider's
own role and stays untouched; a new author column carries the derived answer:

  user    a person typed it
  caller  the program that launched the session wrote it into the user slot:
          Claude nested subagents and sdk-cli (claude -p) turns, Codex child
          threads (is_agent) and codex exec runs, replayed parent prompts
          included
  agent   assistant text, thinking/reasoning, plans, tool calls
  tool    tool results
  system  harness and lifecycle records, including user-slot injections:
          Claude isMeta / isCompactSummary / promptSource = system, Codex
          developer messages and its fixed envelopes (<environment_context>,
          # AGENTS.md instructions, <turn_aborted>, ...)

The evidence is native: Claude records entrypoint, promptSource, turnOrigin,
isMeta and isCompactSummary; Codex records source and the child pointers that
set is_agent. Claude rows now also fill client from entrypoint (cli,
claude-desktop, sdk-cli), the same column Codex uses for cli/desktop/exec.
Providers without launch evidence derive author from message_role.

A new column rather than new message_role values: every candidate existing
column is a raw provider fact, and folding a judgement into it would change
what WHERE message_role = 'user' returns for everyone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8MSou6d752jzQTJwxYLtJ
…sions

In-process children carry their parent natively; a child started from another
agent's shell tool is a top-level session with no pointer, in its own home or
the other system's. docs/lineage.sql joins such a child to the launching tool
call: by the brief text appearing verbatim (JSON-escaped) in the caller's tool
input, else by the launch command naming the child's cwd within the window
before it started. Kept as SQL because it reads both homes at once and its
evidence is a text match, not a provider field.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8MSou6d752jzQTJwxYLtJ
The CI test harness pip-installs the latest duckdb (now 1.5.6) unpinned,
while the extension was built against v1.5.5, so loading the built
extension in sqllogictests failed with a version-mismatch error on
Windows and macOS. Bump the build target to v1.5.6 and pin the test
venv's DuckDB to the build version via DUCKDB_TEST_VERSION, so the
next DuckDB patch release cannot re-break the load check.
Bump duckdb-release.toml, Cargo.toml and example pyprojects to v1.5.6 so
the release-drift consistency check passes against the Makefile/workflow
bump. Mirrors scripts/update_duckdb_release.py --apply.
@asubbarao
asubbarao merged commit bab33e9 into main Oct 7, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants