6.8 KiB
| created | path | project | tags | type | |||||
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-04 | Sources/Dev | synthesis-layer |
|
session-notes |
Working session refining synthesis-layer from record-keeping draft toward a v3-executable plan (v3 of petal-dispatch). Goal: surface data-model and interface decisions now, since petal-dispatch's Phase 0 schema and read surface will be designed around them. These notes supersede parts of the original synthesis-layer draft; a full superseding project-plan is expected once the extraction contract (Open Item below) is settled.
Outcome
Resolved the core handoff model. Five decisions locked; the extraction contract remains open and is the next work item.
1. Handoff is derived, not worker-emitted (Option B). The worker stays dumb — one prompt, one response, exit. An extractor reads the worker's raw final output and produces a structured handoff. Rejected Option A (worker emits its own structured handoff event) as fragile: it makes journal correctness depend on a language model electing to emit well-formed output, the same "worker elects to signal" failure mode petal-dispatch Principle #5 rejects for completion.
2. This means there IS a per-step synthesizer in v1. The original draft's "no synthesizer agent in Phase 1, synthesis layer is a pure read pattern" was only true under Option A. Under B, the extractor is a real (small, per-step) producer. The phase model splits cleanly: the per-step extractor exists from day one (one task's raw output → one structured handoff); the cumulative compression layer stays correctly deferred to Phase 2, still scale-triggered. "No synthesizer in v1" was a fiction that only held if the worker did the extractor's job for free.
3. One store, two tiers — not two stores. Everything lives in the journal. Records carry a tier: raw (machine-emitted: stdout, spawns, exits, deltas — high-volume, never read in the hot path) vs derived (extractor-produced — the handoff, low-volume, what the harness actually reads). The handoff is the first derived-tier record type. The "report"/read surface is a query filtered to the derived tier. Kept as one store so derived records reconcile by construction (own seq, append-only, point back at the raw events they distilled) — avoids the second-source-of-truth / sync problem a separate store would create.
4. ID hierarchy, all id fields, never slugs. Hard constraint: every identifier is an id. Hierarchy: project_id 1—<many session_id 1—<many task_id, plus handoff_id on the derived record. Consequence: the read surface is keyed by id, not slug — get_handoffs(scope_id) (the original get_project_summaries(slug) is wrong on its face). project_id becomes a first-class dimension/column in the journal, so task_id → project_id is a foreign key in the one store — no separate task→project mapping table, no second source of truth. The id hierarchy IS the join path.
5. Handoff is a recursive ROLE, not a single grain. A handoff is "a derived record one layer produces for the layer that reads it." Applied at every tier: extractor → task handoff (read by harness); harness → session/project handoff (read by harness-above or by Travis as the final layer). Same shape, different producer/consumer/grain. Lovebug returning its feedback to Travis IS a project handoff. Read surface generalizes: get_handoffs(scope_id) resolves by which id you pass.
6. Handoff generation is task logic, not journal machinery. Who produces a handoff and when is owned by whatever task logic is executing at that layer — it is not a system-level trigger the journal models. The journal's only jobs: store the typed derived record, key it by id, serve it by scope. This dissolves the session/project-handoff trigger question entirely — synthesis-layer never needs to know when handoffs fire, only how to store and serve them. Critically, this also means synthesis-layer does NOT depend on petal-dispatch settling the orchestrator turn-boundary (task.done vs task.idle) question.
Topics Covered
- Why "payload is the handoff" is the wrong mental model:
payloadis a per-event union type (one shape perevent_type, many per task); a handoff is a per-task conclusion at a different grain. "Payload is the handoff" silently assumed a clean structured handoff already exists in the stream (Option A). - Option A vs B fork for handoff production; chose B (derive).
- One-store/two-tier (
rawvsderived) vs two physical stores; chose one store, tier as a record property. - ID hierarchy and the all-
id-never-slug constraint; its consequence for the read surface and for collapsing the task→project mapping into a foreign key. - Handoff as recursive role across task/session/project grains.
- Handoff generation as task logic, decoupling synthesis-layer from petal-dispatch's orchestrator-lifecycle question.
Key Learnings
- Deriving the handoff (B) is more stable than emitting it (A) for the same reason petal-dispatch derives completion from process exit rather than a worker-elected signal: don't make correctness depend on an LLM choosing to emit well-formed output.
- Naming B honestly reshapes the phase model: the per-step extractor is v1, not deferred; only cumulative compression is deferred. The original "pure read pattern in v1" framing was load-bearing on the rejected Option A.
- The all-
idconstraint does real architectural work: it forcesproject_idto be a journal dimension, which collapses the task→project association into a foreign key and kills a would-be second source of truth. - Putting handoff generation in task logic keeps the journal dumb and severs synthesis-layer's dependency on petal-dispatch's unsettled orchestrator turn-boundary.
Follow-ons
- #3 — Define the extraction contract (NEXT). What a handoff record must contain: guaranteed-present fields (candidate shape: goal / current-state / next-step / ruled-out + why), and the structural form of
ruled-outspecifically, since the harness branches on rule-outs persisting. Needs EARS-style tightening for the fields the harness keys behavior on. This is the gating item before a superseding project-plan can be written. - Confirm
derived-tier record type +project_id/session_id/task_id/handoff_idcolumns as a Phase 0 ask topetal-dispatch(the two real upstream dependencies — a place for derived handoff records, and the id columns to key on). - Decide handoff write timing: durable-write at task close (leaning this — consistent with "DB is truth") vs computed on-demand at read time.
- Reconcile against
execution-journalvault plan (exists; to be read next session — these notes likely reshape it too). - Eventually: write superseding
synthesis-layerproject-plan once #3 is closed; original draft to be retired.