Skip to content

feat(cli): multi-harness batch reflection#3274

Draft
sarahwooders wants to merge 9 commits into
mainfrom
worktree-dream-multi-harness
Draft

feat(cli): multi-harness batch reflection#3274
sarahwooders wants to merge 9 commits into
mainfrom
worktree-dream-multi-harness

Conversation

@sarahwooders

@sarahwooders sarahwooders commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

letta dream can now form memory from recorded coding sessions across multiple harnesses — Claude Code, Codex, OpenHands, Letta conversations, or pre-normalized files — not just the agent's own conversation history. It follows the batch-reflection prototype's design (normalize → batch → reflect → aggregate), adapted to letta-code's production subagent and memfs machinery.

How it works

 --from claude --from codex ...
        │
        ▼
 ┌─ normalize ─┐   sessions from each harness's local store are converted to a
 │             │   shared "normalized-v1" transcript format (one isolated
 └─────────────┘   module, src/agent/trajectories/)
        │
        ▼
 ┌─── batch ───┐   sessions are sorted by start time and greedily packed into
 │             │   batches under a token budget (default 60k, measured on the
 └─────────────┘   normalized content); an oversized session gets a solo batch
        │
        ▼
 ┌── reflect ──┐   ALL batches run at once, each as a fresh conversation on one
 │  (parallel) │   persistent hidden "dream-reflector" agent. Each batch edits
 └─────────────┘   its own GIT CLONE of the agent's real memory (taken at a
        │          shared base revision): it reconciles learnings against what
        │          is already stored — updating files in place, skipping what's
        │          already captured — and commits. Its diff vs the base is the
        ▼          batch's distilled output.
 ┌─ aggregate ─┐   one pass, by a persistent hidden "dream-aggregator" agent
 │             │   (default system prompt + the prototype's aggregator persona).
 └─────────────┘   Working in a memory worktree of the real memfs, it surveys
        │          the per-file change map across all diffs, decides structure
        │          changes (reorganize / combine / add / delete), optionally
        ▼          dispatches subagents for focused aspects (e.g. all human.md
     commit        changes), and lands ONE synthesized edit — no git merges.
                   The commit is merged back with recompile via the existing
                   reflection-finalize path.

Sources (--from, repeatable):

Spec Selects
claude / codex every session in ~/.claude/projects / ~/.codex/sessions
claude:<session-id-or-prefix> that session and everything after it (time cursor)
openhands:<conversation-dir> that conversation's events
letta / letta:<conv-id> / letta:<agent-id> a recorded Letta conversation transcript
transcript:<file|dir> already-normalized transcript files
<conversation-id> (bare) the agent's own history — unchanged legacy single-reflection path

Every run is recorded under ~/.letta/transcripts/<agent>/.dream/runs/<run-id>/, one self-contained directory per batch (input/, output/ clone, diff.patch, trajectory.json, report.json — trajectories are fetched from the conversations API and normalized to the same format as the inputs), plus aggregate/ and a self-contained viz.html showing every agent's trajectory, report, inputs, memory tree, diffs, and agent/conversation ids.

Commands

# 1. Preview: which sessions would be selected, and how they pack into batches
letta dream --plan --from claude --from codex

# 2. Dream: reflect + aggregate into the agent's memory
letta dream --from claude --from codex                  # everything in both stores
letta dream --from "claude:<session-id>" --timeout 3000 # that session onwards

# 3. Inspect: regenerate/open the visualization for a recorded run
letta dream --viz latest        # also accepts a run id or path

# 4. Iterate: re-run ONLY the aggregation over a recorded run's batch outputs
letta dream --aggregate latest  # e.g. after changing the aggregator prompt

# Optional: --budget / --max-sessions (batch packing), --concurrency (cap
# parallel batches; default = all at once), -i "<instruction>" (focus the
# pass), --to ./AGENTS.md (maintain a doc from memory as part of the run)

Verified end-to-end on Letta Cloud (opus-4.6), on a fresh agent: 20 sessions (10 Claude Code + 10 Codex) → 9 concurrent batch reflections, 9/9 committed, 683 diff lines total → one synthesized aggregation commit producing 7 cohesive memory files, ~15 min wall clock.

Changes outside the new modules (kept minimal)

  • subagents/manager.ts — reflection deploys get the same prompt-surface flags as fresh reflection agents; provider-fallback retry now forwards memoryScope (latent bug).
  • tools/impl/shell-env.ts — the cross-agent memory guard recognizes the parent's .dream/ directory as parent-scoped (batch clones live there; without this the guard silently unset $MEMORY_DIR).
  • headless.ts — subagent sessions no longer auto-enable memfs on cloud for deployed memfs-less workers.
  • agent/create.tsasSubagent option to create harness-managed worker agents (hidden, no memfs) from a non-subagent process.
  • utils/transcript-paths.tsgetDreamRootDir (shared with the memory guard).
  • Removed cli/subcommands/dream-sources/ — replaced by the trajectories module; transcript: now means normalized-v1 files.

Test plan

  • bun run check — 10/10 pass
  • Full unit suite — 0 fail (55 new tests: normalizer fixtures per harness, message conversion, batching, source-spec parsing, CLI arg validation)
  • Live cloud runs exercising: multi-source selection + cursors, concurrent batch conversations against memfs clones, diff synthesis by the persona aggregator, aggregation-only re-runs, viz generation

… aggregation

letta dream can now ingest recorded sessions from multiple coding harnesses
(claude, codex, openhands, letta conversations, normalized transcripts) in one
run, reflect on them in parallel, and aggregate the results into the agent's
memory filesystem.

- src/agent/trajectories/: normalized-v1 as the single interchange format —
  Claude Code and Codex store normalizers, OpenHands events (migrated from
  dream-sources), letta conversation transcripts, and listed conversation
  messages, all through one shared row pipeline (caps, timestamps, shapes)
- src/cli/subcommands/dream-pipeline/: select (repeatable --from, session-id
  cursors, per-agent ingest ledger), normalize-first packing (batch on
  normalized sizes), batch reflection into fresh seeded memfs trees, and one
  aggregation pass that owns the final commit via the existing memory-worktree
  merge-back + recompile path
- one persistent hidden reflector agent per primary agent; every batch and the
  aggregator run as fresh concurrent conversations on it, and trajectories are
  recorded by listing each conversation's messages and normalizing them
- --plan (batch preview), --aggregate <run-id|latest> (re-run integration
  over recorded batches), --force (bypass ledger), and a self-contained
  viz.html per run (trajectories, output trees, git diffs, reports)
- --from is now repeatable, fixing the silent last-flag-wins parse bug; the
  old dream-sources staging path is removed
- cross-agent memory guard now recognizes the parent's .dream/ directory as
  parent-scoped so batch output trees survive into subagent shell envs;
  deployed subagent sessions no longer auto-enable memfs on cloud; reflection
  deploys get the same prompt-surface flags as fresh reflection subagents;
  provider-fallback retry now forwards memoryScope
…agent, md prompts, single pass

- Aggregator is its own persistent hidden agent (default system prompt +
  aggregator persona block from the batch-reflection prototype); the
  aggregation pass runs as a fresh conversation on it, deployed
  general-purpose. Reflections stay as concurrent conversations on the
  reflector.
- One aggregation pass over the run's batches/ directory (path given
  inline in the prompt); no payload manifests, no $TRANSCRIPT_PATH for
  the aggregator, no inlined parent-memory snapshot.
- Prompt text moved to dream-pipeline/prompts/*.md with the prototype's
  {{var}} templating; batch prompts list their input transcripts inline.
- Dead code trimmed: unused runId/ledger/skippedEmpty fields, viz
  merge-stage loader.
@cpacker
cpacker marked this pull request as draft July 8, 2026 02:52
@cpacker cpacker changed the title feat(cli): letta dream — multi-harness batch reflection with single-pass aggregation feat(cli): multi-harness batch reflection Jul 8, 2026
…at they select

Every run reflects whatever its --from specs select; cursors
(claude:<session>) are the way to narrow repeat runs. Removes ledger.ts,
--force, and the skippedByLedger plumbing.
…gate archiving

- No manifest.json: --aggregate reruns rebuild batch results from each
  batch directory's report.json; the viz derives everything from batch
  artifacts (reports now record the reflector agent id).
- Workers are reused whenever their stored agent still exists; model is
  matched only at creation time.
- --aggregate overwrites the previous aggregate artifacts instead of
  archiving them as aggregate-prev-<n>.
… synthesizes diffs

Each batch reflection now works in a git clone of the primary agent's
memory filesystem at a shared base revision, reconciling its batch's
learnings against existing memory in place (dedupe happens at the edge,
not the center). Each batch records diff.patch against the base; the
aggregator's primary input is the set of diffs — it surveys the
file-change map, decides structure changes, dispatches subagents for
focused aspects (e.g. all human.md changes), and lands ONE synthesized
edit rather than attempting any git merge. Replaces fresh-seeded batch
trees (seed.ts) and reverts the renderInitialMemoryFile export.
…rofile

Deploying the aggregator as general-purpose dropped the launch profile
that points $MEMORY_DIR/cwd/sandbox at the memory worktree; the agent
then resolved the real memfs path itself and committed directly,
bypassing finalize/recompile. Deploy as subagentType reflection — the
agent's own (default + persona) system prompt is unaffected.
@sarahwooders
sarahwooders force-pushed the worktree-dream-multi-harness branch from 0ee629b to 7de1e2a Compare July 8, 2026 06:03
…ession id = conversation id

The per-harness implementations (claude-code, codex, openhands, letta,
normalized-file) move to src/agent/trajectories/sources/, leaving the
shared pieces (types, normalize-core, store-utils, registry,
letta-messages) at the module root. A letta conversation IS the session,
so its DiscoveredSession.sessionId is now the conversation id rather
than the <agent>/<conversation> locator.
…p letta-messages

One-shot headless runs now append the reflection transcript on success,
mirroring bidirectional mode — so dream workers (deployed subagent
conversations) record locally like any session. Trajectory recording
then reads that transcript through the same letta source used for
ingestion, replacing the conversations-API fetch: letta-messages.ts,
its wire-shape handling, and the pagination helper are gone.
The source's name now matches its scheme (transcript:<file|dir>), like
claude-code.ts matches claude:, and stops colliding with
normalize-core.ts — the shared conversion engine it never uses.
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