Skip to content

feat(sources): admit ~/.claude/todos plan snapshots with a session read model - #3543

Merged
Sinity merged 1 commit into
masterfrom
feature/sources/admit-claude-code-todos-snapshots
Aug 2, 2026
Merged

feat(sources): admit ~/.claude/todos plan snapshots with a session read model#3543
Sinity merged 1 commit into
masterfrom
feature/sources/admit-claude-code-todos-snapshots

Conversation

@Sinity

@Sinity Sinity commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Ingests Claude Code's live plan-snapshot directory (~/.claude/todos/*.json) into the archive end-to-end: raw admission with provenance, a session-linked read model over every retained revision, and a registered plan-completion metric. ~/.claude/file-history/ is deferred as a scoped follow-up (see below).

Problem

polylogue-t0p: Claude Code writes several artifacts beyond the session JSONL transcript that answer questions the transcript alone cannot, and the harness prunes some of them on its own schedule -- unread state is eventually lost, not merely delayed. ~/.claude/todos/<session-id>[-agent-<agent-id>].json holds the agent's current TODO/plan list (task content/status/priority/id), overwritten wholesale on every TodoWrite call. Nothing in the archive captured this before.

Solution

  • Admission: a new todo_snapshot OriginArtifactRule in polylogue/sources/origin_specs.py classifies todos/*.json as a fact-tier Claude Code artifact, the same admission shape already used for workflow_run_snapshot/agent_sidecar_meta (PR feat(sources): report per-type sidecar coverage for Claude Code parse #3419/fix(sources): reconcile sidecar coverage with attachment evidence events #3437/fix(sources): stop claude-code tool-result sidecars over-counting debt #3448 sidecar-join precedent this bead pointed at). Fidelity is declared explicitly: preserved -- every item's content/status/priority/id and the agent's own list order; lost -- intermediate status transitions between two watcher-observed snapshots (each write is a full overwrite, no in-file timestamp), and anything pruned before observation.
  • Parser: polylogue/sources/parsers/claude/todos.py parses the bare JSON array and recovers session_id/agent_id from the filename (<uuid>[-agent-<uuid>].json, verified against the real on-disk shape under ~/.claude/todos/).
  • Watched root: ~/.claude/todos is a new configured/watched source (polylogue/config.py, polylogue/paths/_roots.py, polylogue/sources/live/watcher.py -- a second WatchSource sibling to claude-code, same pattern as the existing codex-state root), aliased to the claude-code provider (polylogue/core/provider_identity.py) so the existing _admit_non_session_origin_artifacts admission path and classify_artifact_path pick it up with zero bespoke wiring -- confirmed by tracing that gate's Provider.from_string(source.name) is Provider.CLAUDE_CODE filter.
  • Read model: polylogue/insights/claude_todo_projection.py is a storage-free, session-linked projection (same justification as insights/run_projection.py) over every retained todo_snapshot raw revision in raw_sessions (not just the current-pointer raw_artifacts row, which would collapse history to the latest snapshot) -- per-session plan state, latest completion rate, and per-item status-transition history across observed snapshots.
  • Measure registration: plan_completion_rate registered in insights/measurement/registered_metrics.py's DEFAULT_METRIC_REGISTRY (rxdo.9.1 identity layer), the same bounded slice session_cost_usd already uses -- reachable via MCP get(ref="metric:plan_completion_rate"). polylogue-9l5.7's full statistics/composition registry (checked: still open, MeasureSpec unimplemented) is not duplicated here; this registers identity only, same limitation session_cost_usd already documents.

Deferred: ~/.claude/file-history/

Investigated the real on-disk shape to save the follow-up lane research time:

  • Each session has a directory ~/.claude/file-history/<session-id>/<hash>@v<N> holding raw pre-edit file bytes with no wrapper (no path, no timestamp inside the file itself).
  • The mapping from <hash>@v<N> back to a real file path lives inside the session's own JSONL transcript, in a type: "file-history-snapshot" record's snapshot.trackedFileBackups: {<path>: {backupFileName, version, backupTime}} map (plus type: "file-history-delta" records for incremental updates).
  • This is already parsed today as an evidence-only session event (code_parser.py's _sidecar_evidence_payload for file-history-snapshot/file-history-delta, #2qx.4) -- it records the file list and backup pointers, but never acquires the actual snapshot bytes at ~/.claude/file-history/.
  • Follow-up scope: join the already-parsed trackedFileBackups pointers to the sibling ~/.claude/file-history/<session-id>/ directory, content-address-acquire each snapshot as a blob keyed to (session, path, version), and apply the same privacy/redaction discipline _acquire_attachment_blob uses for attachments (these are literal pre-edit file contents from real projects). This is a distinct acquisition shape from todo_snapshot (cross-referencing two directories, not a single self-contained file) and was intentionally left out of this PR per the bead's own note that if full scope is too large for one PR, todos should land alone.

Lower-priority artifacts the bead also names (history.jsonl, history-summaries/, debug/, mcp-logs/, ide/ locks, jobs/, ccusage/stats caches) were not investigated this session; still open.

Verification

  • devtools test tests/unit/sources/test_origin_specs.py tests/unit/sources/test_parsers_claude_todos.py tests/unit/insights/test_claude_todo_projection.py tests/unit/core/test_config.py tests/unit/sources/test_live_watcher.py -> 219 passed. (One pre-existing failure, test_live_batch_processor_records_durable_attempt, reproduces identically with this diff stashed on this same checkout -- unrelated codex-path assertion, not touched by this change.)
  • mypy polylogue -> Success: no issues found in 1113 source files.
  • ruff check / ruff format --check -> clean.
  • devtools render all --check -> OK (topology projection regenerated for the two new modules, devtools render topology-projection).
  • devtools verify --quick -> exit 0 (includes a cherry-picked, already-vetted-elsewhere ack commit for an unrelated pre-existing classifier-fingerprints gate failure from PR feat(sources): add per-file acquisition logging and tighten claude-ai detection #3537, tracked separately at issue classifier-fingerprint: looks_like_ai drift from PR #3537 not acknowledged #3540 -- confirmed via git stash that this failure predates and is independent of this branch's changes).

Anti-vacuity: removing the todo_snapshot OriginArtifactRule (or narrowing its path pattern) makes artifact_rule_for_path/classify_artifact_path return None for a real todos path, which test_origin_spec_admits_todos_directory_artifact_as_fact_tier and the end-to-end test_todos_admitted_and_materialized_into_session_linked_plan_states (via parse_sources_archive) both catch. Removing the claude-code-todos -> claude-code provider alias makes _admit_non_session_origin_artifacts's Provider.from_string(source.name) is Provider.CLAUDE_CODE gate skip the source entirely, caught by test_todos_source_name_resolves_to_claude_code_provider and the same end-to-end test (would return zero plan states).

Ref polylogue-t0p

Co-Authored-By: Claude noreply@anthropic.com

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 931f9ae0-2404-4fec-b0a7-57cfc7bcc0ff

📥 Commits

Reviewing files that changed from the base of the PR and between d1899bd and f4c2598.

📒 Files selected for processing (17)
  • docs/plans/topology-target.yaml
  • polylogue/archive/artifact_taxonomy/models.py
  • polylogue/config.py
  • polylogue/core/provider_identity.py
  • polylogue/insights/claude_todo_projection.py
  • polylogue/insights/measurement/registered_metrics.py
  • polylogue/paths/__init__.py
  • polylogue/paths/_roots.py
  • polylogue/sources/live/watcher.py
  • polylogue/sources/origin_specs.py
  • polylogue/sources/parsers/claude/__init__.py
  • polylogue/sources/parsers/claude/todos.py
  • tests/unit/core/test_config.py
  • tests/unit/insights/test_claude_todo_projection.py
  • tests/unit/sources/test_live_watcher.py
  • tests/unit/sources/test_origin_specs.py
  • tests/unit/sources/test_parsers_claude_todos.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Sinity
Sinity force-pushed the feature/sources/admit-claude-code-todos-snapshots branch from 35ccc2f to a9cd32a Compare August 2, 2026 11:36
…ad model

Problem: Claude Code writes several artifacts beyond the session JSONL
transcript that the archive never captured. ~/.claude/todos/*.json holds
the agent's live TODO/plan state per session (task lists with status),
overwritten wholesale on every TodoWrite call and pruned by the harness on
its own schedule -- unread state is eventually lost, not merely delayed
(polylogue-t0p).

Solution:
- New todo_snapshot OriginArtifactRule (polylogue/sources/origin_specs.py)
  admits ~/.claude/todos/<session-id>[-agent-<agent-id>].json as a
  fact-tier Claude Code artifact, following the same admission shape as
  the existing workflow_run_snapshot/agent_sidecar_meta rules, with an
  explicit fidelity note (preserved: content/status/priority/id and list
  order; lost: intermediate transitions between two watcher-observed
  snapshots, no in-file timestamp).
- polylogue/sources/parsers/claude/todos.py parses the bare JSON array,
  recovering session_id/agent_id from the filename.
- ~/.claude/todos is a new configured/watched source root
  (polylogue/config.py, polylogue/paths/_roots.py,
  polylogue/sources/live/watcher.py), aliased to the claude-code provider
  (polylogue/core/provider_identity.py) so the existing
  _admit_non_session_origin_artifacts admission path and OriginSpec
  classification pick it up with no bespoke wiring.
- polylogue/insights/claude_todo_projection.py is a storage-free,
  session-linked read model (mirrors insights/run_projection.py's own
  precedent) over every retained todo_snapshot raw revision: per-session
  plan state, latest completion rate, and per-item status-transition
  history across observed snapshots.
- plan_completion_rate registered in
  insights/measurement/registered_metrics.py's DEFAULT_METRIC_REGISTRY
  (rxdo.9.1 identity layer), the same bounded slice session_cost_usd
  already uses -- reachable via MCP get(ref="metric:plan_completion_rate").
  polylogue-9l5.7's full statistics/composition registry remains
  unstarted; this does not invent a second identity mechanism.

Deferred (see PR body for file-history follow-up scope and what this
session learned about its on-disk shape): ~/.claude/file-history/ content
acquisition, and the lower-priority history.jsonl/debug/mcp-logs/ide/jobs
artifacts the bead also names.

Verification:
- devtools test tests/unit/sources/test_origin_specs.py
  tests/unit/sources/test_parsers_claude_todos.py
  tests/unit/insights/test_claude_todo_projection.py
  tests/unit/core/test_config.py tests/unit/sources/test_live_watcher.py
  -> 219 passed (1 pre-existing failure, test_live_batch_processor_records_durable_attempt,
  reproduces identically on master with this diff stashed -- unrelated to
  this change).
- mypy polylogue -> Success: no issues found in 1113 source files.
- ruff check / ruff format --check -> clean.
- devtools render all --check -> OK (topology projection regenerated for
  the two new modules).
- devtools lab policy classifier-fingerprints reports one pre-existing
  drift (ai_parser.py:looks_like_ai) reproduced identically with this
  diff stashed -- not introduced by this change.

Ref polylogue-t0p

Co-Authored-By: Claude <noreply@anthropic.com>
@Sinity
Sinity force-pushed the feature/sources/admit-claude-code-todos-snapshots branch from a9cd32a to f4c2598 Compare August 2, 2026 11:41
@Sinity
Sinity merged commit 1bf2685 into master Aug 2, 2026
3 checks passed
@Sinity
Sinity deleted the feature/sources/admit-claude-code-todos-snapshots branch August 2, 2026 11:43
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.

1 participant