Discrete phase pipeline¶
A plan step can split into discrete research → plan → implement → verify phases, each spawned as a fresh short-lived agent with its own context window. Between phases, only the distilled handoff - summary, decisions, constraints, open questions - passes forward. The implement phase never sees the research transcript.
Why it exists¶
When one long-running agent does research, planning, and execution in the same window, it burns 60 k tokens reading the codebase, emits a plan, then keeps reading on top of that bloat while implementing - hitting compaction and degrading quality. Discrete phases keep each context window small and let the router pick a different model per phase: a high-reasoning model for research, a cheaper one for implementation.
How to use it¶
Add a phases: list to a step:
stages:
- name: feature
steps:
- role: backend
goal: "Add streaming responses to the chat handler"
phases: [research, plan, implement, verify]
When the orchestrator reaches that step, it spawns one agent per phase. Each phase writes a structured artefact to .sdd/runtime/phase_artifacts/<task_id>/<phase>.json:
The next phase's prompt is seeded with that JSON only - not the prior phase's transcript.
Existing single-phase plans (no phases: field) run unchanged.
Routing per phase¶
phase_pipeline.route_for_phase(phase) returns a (model, effort) pair from the per-phase defaults (_DEFAULT_MODEL_BY_PHASE / _DEFAULT_EFFORT_BY_PHASE):
| Phase | Default model | Effort | Why |
|---|---|---|---|
research | opus | high | broad codebase reading, gap analysis |
plan | opus | high | cross-cutting design |
implement | sonnet | normal | bounded, high-throughput edits |
verify | sonnet | normal | structured assertion runner |
Manager-supplied task_model / task_effort overrides win over the phase default when present.
Configuration¶
| Knob | Default | Controls |
|---|---|---|
phase_pipeline.enabled | true | Honour phases: in plans. |
phase_pipeline.artefact_path | .sdd/runtime/phase_artifacts/ | Distilled handoff store. |
phase_pipeline.gc_on_close | true | Drop artefacts when the parent task closes. |
Limitations¶
- One level of phasing per task. No nested phases inside a phase.
- The handoff schema is a fixed dataclass; no LLM-based summariser filling in narrative prose.
- Mid-phase abort uses the existing
task_retry.pypath; there is no phase-specific restart granularity. - Cross-task phase pooling (sharing a research artefact across two unrelated tasks) is not supported.
Related¶
- Source:
src/bernstein/core/orchestration/phase_pipeline.py(route_for_phase,PhasedRunner,ArtifactStore) - Plan loader:
src/bernstein/core/planning/plan_loader.py - Model router:
src/bernstein/core/routing/router.py - PR #1000