Context Protection: Hybrid Subagent Model
The #1 risk in yolo mode: a 20-task feature blows the context window by task 8. The solution is a lean orchestrator pattern where the main session never accumulates implementation context.
How it works
Main: Orchestrator (Holds plan + tracks progress) → T1: Subagent (TDD task 1 → commit → exit) → T2: Subagent (TDD task 2 → commit → exit) → T3: Subagent (TDD task 3 → commit → exit) → Main: Review (Spawn reviewer → finish)
Build orchestrator session (~5-8K tokens throughout):
- Reads: plan (task list), progress tracker, index.json
- Does NOT read: implementation files, test files, full spec content
- Spawns one subagent per task, waits for result: "task done, 3 files, tests pass"
- Never grows beyond ~8K tokens regardless of task count — a claim about the build orchestrator (one issue, N tasks); the marathon orchestrator has a different growth profile, see below
Task subagent (~30-50K tokens each, then disposed):
- Fresh context window — reads CLAUDE.md + task spec + relevant source files from disk
- Runs full TDD cycle: write tests → run (red) → implement → run (green) → refactor
- Commits changes to worktree branch
- Returns summary to orchestrator: files changed, test results, any warnings
- Context is completely destroyed after exit — zero accumulation
Safety rails in yolo mode:
- 3-strike rule: If a subagent fails tests 3 times, orchestrator pauses and asks for human input
- Targeted verification per task: only the task's own tests run in the worktree; the full suite runs exactly twice per issue (the
/spexso-finishgate and CI), which is where cross-task regressions are caught - Pilot/copilot modes: Use the same subagent pattern but add human checkpoints between spawns
Orchestrator pseudocode (yolo mode)
# Main session — stays lean forever
plan = load_plan_from_issue()
progress = { completed: [], current: null, failed: [] }
for task in plan.tasks:
progress.current = task.id
# Spawn disposable subagent for this task
result = spawn_task_agent(
agent: "spexso-builder", # includes TDD cycle
context: {
task: task, # just this task's spec
claude_md: "CLAUDE.md", # project patterns
# subagent reads source files from disk as needed
}
)
if result.status == "success":
progress.completed.push(task.id)
elif result.status == "failed_3x":
progress.failed.push(task.id)
ASK_HUMAN("Task {task.id} failed 3 times. How to proceed?")
# No full-suite sweeps between tasks: the full suite runs exactly twice
# per issue — the /spexso-finish gate and CI.
# All tasks done -> trigger review
spawn_reviewer_agent()The marathon orchestrator grows — bounded by CLI rails
The ~8K bound above is the build orchestrator's. The marathon orchestrator that loops the backlog does accumulate per-wave state over a long run: results[] (one short JSON summary per processed issue) and merged_files (the union of changed files, feeding the pre-plan fence) grow with the number of issues processed. What keeps that growth honest is that the rest of the run's state lives on disk behind deterministic CLI verbs instead of in context: npx spexso window computes each wave's window, npx spexso breaker keeps the consecutive-failure counter in .spexso/marathon-state.json, npx spexso changed-files produces the fence input mechanically inside the runner, and npx spexso marathon-log fold moves per-issue telemetry into .spexso/marathon-log.jsonl without it ever entering the orchestrator. Per-issue detail (plans, build output, review findings) never reaches the marathon session at all — each runner returns a single JSON line.
Doctrine injection
.spexso/rules/doctrine.md is one compact cross-cutting rulefile (KISS, verify-don't-guess, the TDD test-value rubric, context economy, explicit debt, ownership). Hooks in .claude/settings.json inject it automatically — at SessionStart for the orchestrator and at SubagentStart for every dispatched subagent — via a direct node "${CLAUDE_PROJECT_DIR}/node_modules/@flexso/spexso/bin/spexso.js" doctrine --hook <event> call (no npx: this hook blocks every subagent spawn, so it must be as cheap as possible).
Why hooks instead of prose: injection is cache-stable (the doctrine sits at the top of every context once, instead of being re-read mid-run) and unskippable (a subagent cannot forget to read it). The file is seeded by spexso init and never overwritten — teams tune it like any other rulebook. Domain rulebooks (cap.md, ui5.md, …) keep working exactly as before; the doctrine is the floor, not a replacement. Injection is fail-open: no doctrine file → no context added, never an error.