Skip to content

Latest commit

 

History

History
292 lines (218 loc) · 11.5 KB

File metadata and controls

292 lines (218 loc) · 11.5 KB

DECISIONS — why the project is shaped the way it is

This file captures the reasoning behind each major design decision so future work doesn't re-litigate them. CONTEXT.md says what the system is. This file says why.

If you want to overturn a decision here, that's fine — but read the "why" first and have a real reason. Don't reverse it because the reasoning isn't obvious from the code alone.


1. Use Claude Code as the engine for every agent

Considered:

  • Hit the Anthropic API directly from Python.
  • Use a framework like LangGraph, CrewAI, or AutoGen.
  • Use Claude Code as the engine for each agent (subprocess per agent).

Picked: Claude Code per agent.

Why: The user's plan covers Claude Code. Direct API calls would burn API credits separately from the plan, which the user wanted to avoid. Frameworks add a learning surface and dependencies for benefits that matter mostly at production scale, which this isn't. Claude Code already handles tool use, sandboxing, and file operations — the hard parts — so wrapping it in subprocess management is much smaller than building those from scratch.

Tradeoffs accepted:

  • Subprocess management is gnarlier than in-process API calls.
  • One-shot -p mode means we can't easily mid-stream nudge a running agent. Workarounds (like delivering monitor advisories at next spawn approval) exist but aren't as clean as multi-turn would be.

2. No hardcoded "CEO" — dispatcher decides topology per task

Considered:

  • Always spawn a CEO agent that decides what to do (step 1's design).
  • A dispatcher that runs once and picks fast-path / solo / team.
  • Let the user manually pick the topology each run.

Picked: Dispatcher decides per task. Three paths: FAST_PATH, SOLO, TEAM.

Why: The always-CEO design wasted tokens and added latency on trivial tasks ("what's the capital of France" doesn't need a CEO). It also forced every task into the same shape regardless of complexity, which is exactly the rigidity the user wanted to avoid. The user explicitly asked for "more dynamic — bounce between one agent and a bunch more depending on complexity." A dispatcher is the cleanest way to make topology a per-task decision.

Tradeoffs accepted:

  • The dispatcher will sometimes guess wrong. Empirical tuning required.
  • Adds a layer of latency before any work starts (the dispatcher has to run before the lead/solo can run). Mitigated by giving the dispatcher a small token budget.

3. Spawning routes through the Coordinator, not directly between agents

Considered:

  • Agents spawn child agents directly (peer-to-peer).
  • Agents request spawns from a central Coordinator that approves/denies.

Picked: Central Coordinator with approval authority.

Why: Direct spawning has no chokepoint to enforce budget, depth, or permission limits. Without that chokepoint you get runaway systems — agents spawning agents that spawn agents until tokens are exhausted. A central Coordinator gives one place to enforce limits, log decisions, and let the operator intervene. Adds minimal latency (the approval is a function call, not another model invocation).

Tradeoffs accepted:

  • Slightly more code than peer-to-peer would be.
  • The Coordinator becomes a bottleneck for very high spawn rates. Not a problem at this scale.

4. The monitor is external and uses tiered authority

Considered:

  • The lead self-evaluates whether it's stuck or making progress.
  • An external monitor with hard authority (can kill the run anytime).
  • An external monitor with tiered authority (advisory first, hard stop only after at least one prior advisory).

Picked: Tiered external monitor on Haiku.

Why: Self-evaluation doesn't work — agents (like people) are bad at noticing they're stuck. They report progress when they're flailing. External monitor solves that. But hard authority from turn one produces monitors that nuke runs over false alarms; tiering forces the monitor to warn first, which gives the lead a chance to adjust and gives the operator visibility into a building problem before it terminates. Haiku because the monitor doesn't need deep reasoning — it's reading a snapshot and pattern-matching for trouble signals.

Tradeoffs accepted:

  • Haiku-on-snapshot misses subtle failures (an agent confidently writing wrong code). Catches obvious ones (stuck loops, runaway tokens). This is a partial safeguard, not a real one — see CONTEXT.md item 7.
  • Tiering means truly catastrophic runs eat one extra advisory cycle before being killed. Acceptable cost.

5. Two creative thinkers with deliberately different cognitive strategies

Considered:

  • One creative thinker.
  • Two copies of the same creative thinker prompt.
  • Two thinkers with deliberately divergent cognitive strategies.

Picked: Two thinkers, one inverter, one analogist.

Why: The user explicitly asked for two creative thinkers. Two identical prompts would produce duplicate ideas — pointless. Different cognitive strategies (inversion vs cross-domain analogy) at least structurally push toward divergent outputs. Whether they actually produce meaningfully different ideas in practice is unverified — see CONTEXT.md item 10. This is a hypothesis dressed as a design choice; test it on real runs before committing to it.

Tradeoffs accepted:

  • Doubles the cost of the creative phase.
  • May produce similar output anyway, in which case one thinker is enough.

6. The two thinkers run in parallel via SPAWN_BATCH

Considered:

  • Sequential: spawn thinker A, get result, spawn thinker B.
  • Parallel: spawn both concurrently with asyncio.gather.

Picked: Parallel via a new SPAWN_BATCH protocol.

Why: Sequential execution means thinker B sees thinker A's output through the lead's context, and converges on it. The whole point of having two thinkers is divergence — having them see each other's work defeats the design. Parallel execution preserves divergence.

Tradeoffs accepted:

  • Required adding a new protocol message (SPAWN_BATCH) and concurrent execution path. ~30 lines of code. Worth it.

7. Creative thinkers are leaf agents (cannot delegate)

Considered:

  • Let creative thinkers spawn researchers to validate ideas.
  • Lock them as leaf agents with no spawning capability.

Picked: Leaf agents.

Why: Creative ideation that recursively delegates becomes a philosophy seminar. The thinkers' job is to produce 2-3 concrete strategies in under 400 words and get out of the way. If their ideas need validation, that's the lead's job to assign to a researcher afterward, not the thinker's job to do inline.

Tradeoffs accepted:

  • Thinkers can't fact-check themselves. Their output is unverified ideation, which the lead has to evaluate.

8. Topology decided once at start (not continuously re-decided)

Considered:

  • Continuously re-evaluate topology — every N seconds, ask "do we need more / fewer / different agents?"
  • Decide once at dispatch, only restructure on explicit signals (ESCALATE from solo, advisory threshold from monitor).

Picked: Decide once, restructure only on explicit signals.

Why: Continuous re-evaluation is itself a major cost center. If a re-planner runs every minute on top of all active agents, you stack 30-50x the token cost of single-agent. The user originally asked for continuous re-evaluation; we discussed this and landed on signal-driven restructuring instead, which captures the same intent (the system adapts to changing complexity) without the constant overhead.

Note: The complexity restructuring loop itself isn't fully built — see CONTEXT.md item 9. The decision here is about the approach; the implementation is incomplete.

Tradeoffs accepted:

  • A task whose complexity gradually creeps up without crossing any signal threshold won't trigger a restructure. May result in a team that's the wrong size for the task halfway through. Acceptable for now; revisit if it shows up in real runs.

9. Append-only event log in SQLite as source of truth

Considered:

  • In-memory state with periodic snapshots.
  • Live state in SQLite tables, mutated as things happen.
  • Append-only event log; tables are derived summaries.

Picked: Append-only event log, with summary tables maintained alongside.

Why: Append-only logs are dramatically easier to debug — you can replay the entire run from the events table. They survive crashes, support long-running workflows that span days, and let a future dashboard reconstruct any past state. The summary tables (tasks, agents) exist for fast lookup but aren't the source of truth; if they get out of sync, the events are authoritative.

Tradeoffs accepted:

  • Slightly more storage than mutating-state-only would use. Trivial at this scale.
  • Two ways to access state (event scan vs summary tables) — have to be careful which one to read in any given context.

10. "Agents that create agents at runtime" — deliberately not built

Considered:

  • Let any agent invent a new role at runtime via a CREATE_ROLE protocol.
  • Only the dispatcher creates dynamic roles; the lead picks from a fixed list of specialists.

Picked: Only the dispatcher creates dynamic roles.

Why: The user originally asked for full runtime role creation. After discussion, we agreed that runtime-generated role prompts tend to be much lower quality than handcrafted ones — the meta-agent isn't great at writing system prompts under task pressure — and recursive failure modes (mediocre agent creates mediocre agent creates mediocre agent) are hard to bound. The dispatcher creating one dynamic role at start gets most of the value (per-task customization) without most of the risk. Full runtime creation is revisitable if needed.

This decision was the user's call to defer, after the assistant laid out the failure modes. If real runs show the fixed specialist list is too constraining, this is the first thing to revisit.

Tradeoffs accepted:

  • Specialists are constrained to the static registry plus what the dispatcher invents. Can't have a "database migration specialist" appear mid-run without code changes to roles.py.

11. Stdlib only — no external dependencies

Considered:

  • Use FastAPI, pydantic, anyio, click, rich, etc. for ergonomics.
  • Stick to Python stdlib.

Picked: Stdlib only.

Why: This is a personal tool the user runs on their laptop, not a production service. Every dependency is a thing that can break, need updating, conflict with other tools, or stop being maintained. Stdlib is enough for everything we're doing — sqlite3, asyncio, subprocess, re, json, dataclasses. Adding a dependency requires updating CONTEXT.md with a real reason.

Tradeoffs accepted:

  • Slightly more code than using nicer libraries would require. Roughly 100-200 LoC of "I'd use rich for this in production" boilerplate. Worth it for zero-friction installs.

How to use this file when overturning a decision

  1. Find the decision in this file.
  2. Read the "Why" and "Tradeoffs accepted" sections.
  3. Articulate what changed since the decision was made — new information, real-run data, the user's priorities shifted, etc.
  4. Update the decision in this file with a "Reversed on YYYY-MM-DD because..." note. Keep the original reasoning visible so the next reader can see the history.
  5. Update CONTEXT.md if the change affects what's built / not built.

Don't silently change behavior that contradicts a decision here. The next reader will be confused, and the user will lose trust in the documentation.