Skip to content

feat(muse): index Muse Code sessions as a first-class source - #259

Open
barryollama wants to merge 12 commits into
mainfrom
feat/muse-code-source
Open

barryollama wants to merge 12 commits into
mainfrom
feat/muse-code-source

Conversation

@barryollama

@barryollama barryollama commented Sep 24, 2026 •

Copy link
Copy Markdown

What

Adds Muse Code (Meta's muse CLI) as a first-class source, muse, so its sessions go through discovery, sync, targeted hydration and live capture like every other harness.

  • Location: $XDG_DATA_HOME/muse/sessions/YYYY/MM/DD/<id>/session.jsonl (~/.local/share/muse/sessions by default). ProviderRoots::muse, and the sync service now forwards XDG_DATA_HOME.
  • Parser: crates/ai-hist/src/ingest/muse.rs holds the record interpretation shared by discovery, sync and hydration.
  • Evidence written:
    • history rows for typed prompts, at Muse's own recorded_at (µs → ms). No timestamps are inferred.
    • session_events: prose, readable thinking, tool_use and tool_result, with request_id (response_id), stop_reason (finish_reason), turn_id (run_id) and agent version.
    • tool_calls and tool-result fidelity. Status comes from tool_batch.effect.terminal joined by call id; a bash call with a non-zero exit_code is an error.
    • file_edits for write_file / edit_file.
    • Token usage: one model_completed usage per model step, stored verbatim on the first assistant row that step committed. It normalizes as per-request, with cache reads made exclusive of input.
    • Markers: turn_end, session_start, session_resumed, session_end, model_switch, encrypted_reasoning.
  • Filtering: only records on the session's own stream.id are read. Muse copies subagent and reminder task streams into the parent file, and those would otherwise show up as typed prompts. subagent/ child transcripts are never enumerated as sessions.
  • ai-hist resume prints muse resume <id>. muse is also added to the CLI, napi, TS SDK and MCP source lists.

Why

Muse users should get the same searchable, resumable history as the other harnesses. Per the sourcing ADR, providers are added here and nowhere else.

Reviewer notes

  • Subagent linking. Every Muse child agent writes subagent/<dir>/session.jsonl beside its parent, possibly nested. Reading a session reads its whole tree:
    • Each child is indexed under the session id its own metadata names (never the directory name).
    • Each child is linked to its parent as delegated (evidence_kind = "muse_subagent_log"). Role (worker / reminder), label, model and task id come from the parent's task_stream_linked / memory_reminder_child_session_linked record, plus spawn_depth.
    • As for Codex subagent rollouts, a child's history and catalog rows are removed, so it is not a typed prompt or a session of its own. Its events stay reachable through the edge and related_session_ids.
    • The change stamp covers child logs, so a background subagent that keeps writing is re-read. A child whose log disappears loses its edge and evidence.
    • Relationship capability is always, and Muse hydration reports full.
    • The subagent layout is exercised only by the authored fixture. The real 0.2.1 capture carries no child logs.
  • Refactor: Grok's "re-attribute then delete history" step moves into a shared replace_session_history(conn, source, session_id), which both whole-session readers now use. The SQL is equivalent: e.source = h.source replaces the 'grok' literal. Grok snapshots are unchanged.
  • Fixtures:
    • muse/cli-capture is a trimmed transcript that the real Muse 0.2.1 CLI wrote, taken from xhluca/session-migrate (MIT). Every kept line is byte-identical to upstream.
    • muse/tools-session is authored, and covers edits, a failing bash, encrypted reasoning, a model switch, a mirrored task stream, and subagent/ logs: a worker with its own nested child, and a reminder.
  • Not verified against a real install: Muse isn't installed on the machine this was written on, so the parser has not been run against current Muse 1.3 sessions. The format was checked against the published Muse Code SDK and independent readers (tokscale, SpecStory, agent-manager).
  • public-api.txt: updated by hand (cargo public-api wasn't installed locally), adding Source::Muse and ProviderRoots::muse. Both types are #[non_exhaustive], so the change is additive. CI's check will confirm it.
  • Docs updated: ADR capture matrix (new muse column plus a rationale paragraph), session-catalog tables and a new Muse section, the sourcing-sdk population table, architecture, README, CHANGELOG (Added and Rust API).

Merge with main

  • change_feed.rs: main replaced parse_source with Source::parse, which covers Source::Muse through Source::ALL.
  • CHANGELOG.md: both Rust API entries kept.
  • The Muse sync pass now goes through main's retention gate (ensure_headroom) and the new report.capture(conn, …)? signature.

Testing

  • cargo fmt --check, cargo clippy --workspace --all-targets --all-features -D warnings, and cargo build -p ai-hist --no-default-features: all clean.
  • New tests:
    • parser unit tests
    • usage normalization
    • sync idempotency: a re-read after growth adds only the new turn; an unchanged transcript whose rows were deleted is re-indexed
    • fixture snapshots, plus corpus assertions
    • the relocated-root watch test now covers the Muse root
    • subagent linking: three edges with type, depth and model; children stay out of catalog and history; a child log that grows on its own is re-read; a removed child loses its edge and events
  • TS SDK: 198/198 pass.
  • Four Rust tests fail locally on macOS and fail identically on a clean main. None of them are caused by this change:
    • an_unreadable_codex_rollout_does_not_retire_the_backfill_pass: socket path over SUN_LEN under the long macOS temp dir
    • a_linked_worktree_resolves_an_onbranch_include_against_its_own_branch and global_and_conditional_git_config_resolve_a_rewritten_remote: git config path quirks on macOS
    • corpus_snapshots_match_committed_evidence for the Grok fixtures: a marker payload is cut at 128 chars, and the longer macOS temp path changes where it is cut

🤖 Generated with Claude Code


Note

Medium Risk
Large new ingest/discovery surface and subagent linking behavior, but changes are additive (#[non_exhaustive] APIs) with extensive fixture tests; risk is mainly parser correctness on real Muse log variants.

Overview
Adds Muse Code (muse) as a first-class harness: sessions under $XDG_DATA_HOME/muse/sessions are discovered, synced, hydrated, and live-watched like other providers, with XDG_DATA_HOME forwarded through the sync service.

A shared ingest/muse.rs parser maps session.jsonl into history, events, tool calls/results (outcomes from tool_batch.effect.terminal, bash exit codes), file edits, per-step token usage, lifecycle markers, and muse resume support across Rust/Node/MCP/TS.

Subagent/reminder logs under subagent/ are indexed only when related evidence is requested: children get their own session id and delegated edges, are excluded from catalog enumeration and shallow reads, and fingerprinting includes child logs so background writes still trigger re-sync.

Reviewed by Cursor Bugbot for commit f93c832. Bugbot is set up for automated code reviews on this repo. Configure here.

Add `muse` as a source so Meta's Muse Code CLI sessions reach discovery,
sync, targeted hydration and live capture like every other harness.

Muse writes one append-only, event-sourced `session.jsonl` per session
under `$XDG_DATA_HOME/muse/sessions/YYYY/MM/DD/<id>/`. Every record
carries its own `recorded_at` (microseconds), so no timestamp is
inferred. The parser (`ingest/muse.rs`) reads prompts, prose, readable
thinking, tool calls and results, file edits, per-model-step usage,
models and lifecycle markers, filters records to the session's own
stream (mirrored subagent/reminder task streams are not prompts), and
takes tool status from `tool_batch.effect.terminal` plus a non-zero
`bash` exit code. `subagent/` child transcripts are not sessions of
their own; linking them as delegation is left for a follow-up, so Muse
hydration reports `partial`.

Usage normalizes as `per-request` with cache reads made exclusive.
Grok's history re-attribution is factored into a shared helper both
whole-session readers use. Fixtures include a trimmed transcript the
real CLI wrote (MIT, xhluca/session-migrate), with snapshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 26 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 63af03e7-1dca-4490-80ed-a59f17ea5bd6

📥 Commits

Reviewing files that changed from the base of the PR and between d0a4b96 and f93c832.

📒 Files selected for processing (6)
  • crates/ai-hist/src/ingest.rs
  • crates/ai-hist/src/ingest/hydrate.rs
  • crates/ai-hist/src/ingest/muse.rs
  • crates/ai-hist/tests/fixture_corpus.rs
  • crates/ai-hist/tests/snapshots/muse/tools-session.json
  • docs/session-catalog.md
📝 Walkthrough

Walkthrough

This change adds Muse Code as a session source. It discovers and parses Muse transcripts, captures and hydrates session evidence, indexes delegated sessions, and exposes Muse through source APIs, CLI and SDK operations, and documentation.

Changes

Muse session support

Layer / File(s) Summary
Source registration, roots, and discovery
crates/ai-hist/src/paths.rs, crates/ai-hist/src/session_store.rs, crates/ai-hist/src/discover.rs, crates/ai-hist/tests/live_capture.rs, crates/ai-hist-cli/src/lib.rs, crates/ai-hist/public-api.txt
Adds Muse source identity and session-root resolution. Discovery scans top-level transcripts, watches child logs, and extracts shallow session metadata.
Transcript parsing and usage facts
crates/ai-hist/src/ingest/muse.rs, crates/ai-hist/src/ingest/tool_result_facts.rs, crates/ai-hist/src/usage.rs
Parses Muse JSONL records, pairs model completions with session events, derives tool-result status, and normalizes per-request usage.
Capture, indexing, and evidence replacement
crates/ai-hist/src/ingest.rs, crates/ai-hist/src/relationship_graph.rs, crates/ai-hist/tests/fixture_corpus.rs, crates/ai-hist/tests/fixtures/muse/*, crates/ai-hist/tests/snapshots/muse/*, crates/ai-hist/tests/fixtures/README.md, docs/architecture.md
Adds Muse sync and transactional evidence replacement, including recursive child-session links and cleanup. Adds fixtures and snapshots for transcript capture, tool results, usage, and delegated sessions.
Targeted hydration and diagnostics
crates/ai-hist/src/ingest/hydrate.rs
Hydration can include related subagent logs, validates transcript identity, and reports diagnostics for incomplete or unparsed evidence.
CLI, SDK, and provider documentation
README.md, CHANGELOG.md, crates/ai-hist/src/store.rs, crates/ai-hist-napi/src/lib.rs, sdk-ts/src/*, docs/*, plugins/relayhistory/rust/src/source/tests.rs
Adds Muse to supported source choices and resume operations. Updates source validation, provider documentation, changelogs, and related tests.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant MuseSync
  participant MuseSessionFiles
  participant read_muse_tree
  participant ingest_muse_session_tree
  participant SessionDatabase
  MuseSync->>MuseSessionFiles: Enumerate top-level transcripts and child logs
  MuseSync->>read_muse_tree: Read transcript tree
  read_muse_tree->>MuseSessionFiles: Read session JSONL records
  MuseSync->>ingest_muse_session_tree: Replace indexed session tree
  ingest_muse_session_tree->>SessionDatabase: Write history, events, and relationships
Loading

Suggested reviewers: claude, willwashburn

Merge Risk: 🟡 Moderate · up to d0a4b

Muse sessions can record per-step token usage, model, and stop reason against the wrong assistant message, and the last message in a step can lose its usage. Usage accounting for Muse is therefore unreliable until the step pairing is fixed. Hydration metrics also under-report the bytes read when subagent logs are included. Fix the step pairing before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to d0a4b

Muse sessions gain the same searchable and resumable treatment as other sources. The new source can also follow links outside its configured session directory during discovery and sync, potentially indexing unintended readable transcripts. The risk is limited by the indexing process’s filesystem permissions; the available evidence does not establish cross-user access.

Retained concerns

  • Medium · security · inferred: A link beneath the configured Muse root can lead discovery and sync to read a Muse-shaped transcript outside that root and index it as a session. The traversal behavior is inherited, but enabling Muse adds a newly reachable source directory; targeted hydration’s containment check does not protect discovery or sync.
Security review details

Security Blast Radius

  • inferred — The independently influenceable scope is a Muse-root entry the indexing process can traverse. A linked, readable Muse-shaped transcript outside the selected root could enter the local searchable store; the evidence does not establish access beyond that process’s filesystem permissions or a cross-tenant deployment.

Security Findings and Attack Paths

  • inferred — An actor able to place a symlink beneath the Muse root can point a matching session path outside it. Discovery accepts the lexical path and sync reads it without the canonical containment check used by targeted hydration. This is a newly exposed route through an inherited collector behavior, not evidence of a new filesystem privilege.

Trust Boundaries and Controls

  • observed — Metadata must identify a session stream, records from other streams are discarded, and hydration checks both canonical path containment and catalog identity. These controls limit stream confusion and targeted hydration but do not establish root containment for discovery or sync.

Resilience and Maintainability Implications

  • observed — Transactional tree replacement and post-commit stamping contain partial indexing failures. The recursive collector nevertheless follows directory symlinks without a visited-directory or canonical-root guard, leaving traversal containment dependent on the filesystem tree.

Hardening Proposals

  • proposed — Apply canonical containment against the configured Muse root before discovery or sync reads a candidate, and bound recursive traversal against symlink cycles. Preserve the existing catalog-ID and stream-ID checks.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ❓ Inconclusive Docstring coverage is 67.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 96 functions across 20 files. (12 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: adding Muse Code sessions as a first-class source.
Description check ✅ Passed The description directly explains the Muse integration, parser, ingestion behavior, subagent linking, testing, and documentation changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 67.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 96 functions across 20 files. (12 skipped: 11 unsupported, 1 too large.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

A rabbit reads the session trail,
Through model turns and logs in rows.
It links the workers, stamps each file,
And marks the tools where errors rose.
Then hops along to Muse’s glow.

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

cursor[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

barryonthecape and others added 2 commits September 24, 2026 20:30
Conflicts:
- change_feed.rs: main replaced `parse_source` with `Source::parse`,
  which covers `Source::Muse` through `Source::ALL`; take main's side.
- CHANGELOG.md: keep both Rust API entries.

Also adapt the Muse sync pass to main's retention gate:
`report.ensure_headroom(conn, "muse")` before it and the new
`report.capture(conn, …)?` signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cursor[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

barryonthecape and others added 2 commits September 24, 2026 22:08
Conflict: CHANGELOG.md Rust API entries; both kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pair each model step's usage with its assistant record in either
  order. Muse logs `model_completed` before a tool step's calls but
  after a prose step's reply, so final replies were losing their usage
  (every text step in the real capture). Model and finish_reason now
  belong to the owning record only.
- Guard Muse with the destination marker (REPAIRABLE_EVENT_SOURCES),
  so rows lost under an unchanged stamp are re-read by the next sync,
  including a parent whose linked subagent is short; a deregistered
  Muse subagent is not a catalog shortfall.
- Include subagent logs in the discovery fingerprint, so a child that
  grows on its own is not hidden behind the sync fast path.
- Read a session's whole tree before writing: an unreadable transcript
  or child log is that session's failure and the pass continues; a
  write failure (retention) still ends it. Check capture headroom per
  session and propagate cancellation from the stamp walk.
- Hydration with include_related=false no longer reads or rewrites
  subagent evidence.
- Only newline-terminated records are parsed.
- Only a `bash` result's exit_code marks a call failed.
- Catalog first_prompt / last_assistant_text are assigned from the
  transcript, so a rewrite does not keep stale text.
- Shorten the authored fixture's session ids: the nested subagent
  paths exceeded Windows' MAX_PATH and broke checkout on the Windows
  jobs. Add `muse` to the plugin's SOURCE_CHOICES assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cursor[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

…ostic

- A Muse session also known remotely shares its history identity with
  that evidence. A whole-transcript re-read no longer replaces its
  history, a linked subagent's objective is removed only when nothing
  remote stands behind the id (as for Codex), a removed child with a
  remote presence keeps its evidence, and the destination marker's
  deregistration exemption requires no remote presence.
- MUSE_SUBAGENT_LOG_MISSING compares the session's own subagent_spawn
  calls with its direct children that it typed as workers, instead of
  workers at every depth plus logs it never described.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…oval

- A re-read removes the history rows its previous read wrote — the ones
  its stored prompt events name — when the session is also known
  remotely, instead of skipping cleanup. A prompt the transcript
  dropped stops being searchable; remote-only prompts stay. Sessions
  with no remote presence keep the whole-identity replacement.
- A subagent's run-start prompt is ingested as an event only
  (MuseRole::Subagent), never as history, whether or not the child is
  known remotely.
- Forgetting a removed child always forgets the children it linked
  from the missing directory and drops its edges to them; only the
  child's own events are kept when it is known remotely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

Conflict: CHANGELOG.md Rust API entries; both kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cursor[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

barryonthecape and others added 2 commits September 24, 2026 23:06
…d role

- The remote-scoped history cleanup now goes through
  replace_session_history_scoped, so a shared prompt row another
  session still evidences is re-attributed instead of deleted.
- A subagent with no catalog row and no events left is named for repair
  through its parent's muse_subagent_log edge, so the next plain sync
  re-reads the parent's tree and restores it.
- A transcript at …/subagent/<id>/session.jsonl is always read as a
  subagent, including when a remotely known child is hydrated directly,
  so its objective never becomes history.
- A removed child that is known remotely loses its local presence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- MuseProvider::read_shallow returns no row for a log at
  …/subagent/<id>/session.jsonl, so a watch hit or by-path read cannot
  catalogue a child the enumeration already skips.
- A subagent log read directly (hydrating a remote child's raw_path) is
  deregistered from the catalog like a linked child, unless a remote
  presence stands behind it.
- Forgetting a removed child also re-attributes or deletes history still
  filed under it, unless it is known remotely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
coderabbitai[bot]

This comment was marked as resolved.

A step can commit readable reasoning and then its tool calls. The
previous pairing reopened a new step at the second commit, so every
later step's usage shifted one record forward and the last reply lost
its own (visible in the authored fixture). Each run is now cut into
segments at `started`, `tool_result_batch_committed` and `terminal` —
the points after which the model is called again — and a segment's
first assistant record owns its `model_completed`, in either order. A
second usage-bearing completion in one segment is counted as unattached.

Hydration with related evidence also reports the subagent logs it read
in `bytes_read`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

🐛 1 issue in files not directly in the diff

🐛 Unreadable child log erases subagent evidence

When an existing child log cannot be statted, is_file() silently omits it from the tree. The changed stamp triggers forget_muse_subagent, deleting its indexed events and relationship as though the log vanished.

5 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

direct_muse_child_logs used `is_file()`, which reads any stat error as
"not a file". A child log that exists but cannot be statted (permission
denied, I/O error) dropped out of the tree, the changed stamp re-read
the parent, and forget_muse_subagent deleted the child's evidence and
edge. Only NotFound / NotADirectory now mean absent; any other error
fails that session's read, which keeps no stamp and is retried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@barryollama

Copy link
Copy Markdown
Author

Re Devin's out-of-diff finding "Unreadable child log erases subagent evidence" (ingest.rs, direct_muse_child_logs): fixed in f93c832. Only NotFound/NotADirectory now count as absent. Any other stat error fails that session's read, which keeps no stamp and is retried, so the child's evidence and edge are never forgotten. Covered by an_unreadable_muse_subagent_log_is_not_forgotten, which fails with the old is_file() check.

🤖 Addressed by Claude Code

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.

2 participants