diff --git a/Sources/Dev/synthesis-layer.md b/Sources/Dev/synthesis-layer.md new file mode 100644 index 0000000..04292be --- /dev/null +++ b/Sources/Dev/synthesis-layer.md @@ -0,0 +1,71 @@ +--- +created: '2026-05-30' +path: Sources/Dev +project: synthesis-layer +tags: +- mcp +- dispatch +- lovebug +- claude-code +- automation +type: project-plan +--- + +# Synthesis Layer + +Short-term memory for agent orchestration. The execution journal is the durable long-term record (immutable, complete, append-only). The synthesis layer is the small, bounded, fast working view that the harness actually reads when deciding the next move. Workers write journal entries; the synthesis layer is how those entries become consumable context for Lovebug without bloating Lovebug's own window. + +This plan is **not fully baked** — captured here so the design discussion doesn't get lost. Iteration expected. Companion to `execution-journal`. + +## Goal + +Give the harness (Lovebug) a bounded, current, queryable view of project state that: + +- Stays small regardless of how long a project runs — the harness's per-cycle context cost is roughly constant whether step 3 or step 50. +- Carries forward the things that matter (active constraints, rule-outs, current state) so a new actor doesn't re-propose what's already been tried and rejected. +- Is recoverable from the journal — the synthesis layer can be lossy *safely* because the journal is the system of record. +- Doesn't require Lovebug to read raw worker output. Ever. Lovebug consumes conclusions; the synthesis layer produces them. + +## Locked Decisions + +- **Long-term / short-term split.** Journal = long-term (durable, complete, immutable). Synthesis layer = short-term (small, current, volatile). The harness reads short-term; nobody holds long-term in context. The journal is queryable on disk but not normally read in the hot path. +- **Per-step summaries, not cumulative.** Each summary describes its own step and is immutable once written. No fold-forward, no rewriting, no carry-forward discipline. The "history" is the ordered list itself. Trades read-time cost (read several summaries) for write-time safety (no drift surface inside any summary). +- **Why not cumulative.** A cumulative rolling summary introduces per-step drift at the synthesis pass and the recovery story is circular — "regenerate from the journal" is itself the hard synthesis problem we don't fully trust. Per-step summaries sidestep this: no artifact gets rewritten, so no artifact can drift. +- **Synthesis is deferred until the list is long enough to need compression.** At current scope (small projects, ~10–30 steps), reading the full list of per-step summaries is fine — token budget is not the constraint. A compression layer is added *later*, when project length forces it, with real data about what's worth keeping. Premature compression is a synthesis-prompt-design problem we don't have yet. +- **Synthesizer (when it eventually exists) is a stateless function over durable state.** Same pattern as every other layer in the stack. Spawn → read inputs → emit output → terminate. Never accumulates context across invocations. +- **Drift is bounded, not eliminated.** 100% accurate history is not the goal; it's never been a thing. Good checks and balances anchored to the journal (immutable reference) are. Bounded sawtooth via mechanical comparison against ground truth, not unbounded ramp. + +## Open Items + +- **What exactly is a per-step summary?** Probably very close to the worker's own structured `handoff` field from the journal — goal, current state, next step, ruled-out + why. The synthesis layer may collapse to "lightly normalize the worker's handoff and append to a list" for v1. If so, no separate synthesizer agent is needed yet — the worker's handoff *is* the summary. +- **When does a real synthesizer get built?** Trigger: the per-step list grows past the point where reading it all on every harness cycle is comfortable. At that point, add a compression pass that produces a bounded rolling brief from the list. Defer until it's a real problem with real numbers. +- **Read shape for the harness.** `get_project_summaries(slug)` returning the ordered list, newest-first, with optional limit. Probably the same MCP surface as the journal — synthesis layer may just be a view over journal handoff fields rather than a separate table. To be decided. +- **Drift detection for v2 (cumulative).** If/when compression is added, drift checks are mechanical and anchor to the journal: structural invariants (active rule-outs must persist unless explicitly reversed), periodic reconciliation (rebuild from journal, diff against rolling brief), harness sanity checks (claimed task done → testable plan condition holds). None of these are "another agent's second opinion" — they're measurements against immutable references. Not building this for v1; documenting it so we don't have to re-derive when we do. +- **Speed/volume asymmetry.** The reason this *feels* harder than human institutional drift is correction-loop tempo, not novelty. Agents can compound 50 generations of lossy retelling in an afternoon while the correction loop (Travis reading on Tuesday) is slow. Implication: checks need to run automatically and frequently *if* we ever go cumulative. Another reason per-step summaries are the right v1. + +## Phases + +### Phase 1 — Trivial synthesis (v1) +- [ ] Confirm that the worker's structured `handoff` from the journal can serve directly as the per-step summary. If yes, no new layer is built — the synthesis layer is a *read pattern* over the journal, not a separate write path. +- [ ] Define the MCP read: `get_project_summaries(slug, limit?, order=desc)` — ordered list of handoffs across the project. Newest-first by default. +- [ ] Harness reads the summary list + immutable plan to decide the next step. No synthesizer agent in this phase. + +### Phase 2 — Compression (only when triggered) +- [ ] Trigger condition: summary list is long enough that reading it all on every harness cycle is uncomfortable. Define "uncomfortable" with real numbers when we get there, not now. +- [ ] Design the synthesizer agent: stateless, spawns per harness cycle (or per N worker completions), reads recent summaries, emits a bounded rolling brief, terminates. +- [ ] Open question deferred to this phase: cumulative rolling brief vs. windowed (last-N summaries compressed). Cumulative is smaller-steady-state but reintroduces drift; windowed is simpler but doesn't bound size as tightly. Decide with data. + +### Phase 3 — Drift detection (only if Phase 2 is cumulative) +- [ ] Structural invariant check: active rule-outs from prior brief persist in current brief unless a journal entry explicitly reverses them. Mechanical, deterministic. +- [ ] Periodic reconciliation: regenerate brief from journal, diff against rolling brief, flag divergence. +- [ ] Anchor every check to the journal (immutable) or the plan (immutable). Never anchor a drift check to another synthesis pass — that's the regress. + +## Notes + +**Why this is a separate plan from `execution-journal`.** The journal is the ground-truth store. The synthesis layer is the consumption pattern on top. Journal is build-now; synthesis layer is build-as-needed. Keeping them separate prevents the journal scope from creeping into "and also a synthesizer" when the journal stands on its own. + +**On the "history" framing.** The instinct that motivated this whole thread — "doomed to repeat history if you don't know it" — resolves cleanly under per-step summaries: history *is* the list, no information is dropped, the harness can read as far back as it needs. Under cumulative summaries the same instinct produces a real problem (silently dropped rule-outs cause re-proposal) which is why cumulative needs the carry-forward discipline + drift checks. Per-step is the simpler answer that doesn't trade away the safety property. + +**On scope.** This whole discussion was over-scoped relative to current project size. At ~10–30 steps per project, the simple read-the-whole-list approach is correct and finishes the problem. The deeper analysis (cumulative summaries, drift detection, synthesizer regress) is captured here so when scale forces a revisit, the reasoning isn't redone from scratch — and so the simple v1 is "simple because we reasoned to it," not "simple because we didn't look." + +**Companion plan.** See `execution-journal` for the durable-store side of this architecture. The two were discussed together and split intentionally — journal is concrete and near-term; synthesis is a pattern that may or may not need its own agent depending on how the journal-handoff field shapes up. \ No newline at end of file