Architecture¶
What runs where (today)¶
graph TB
subgraph "Entry points"
CLI["caloron sprint <goal>"]
SCHED[noether-scheduler]
end
subgraph "Production path (Python)"
ORCH[orchestrator.py]
end
subgraph "Composition path (Noether)"
FULL[full_cycle.json]
TICK[sprint_tick_stateful.json]
CORE[sprint_tick_core.json]
TICK -.wraps.-> CORE
end
subgraph "Stage Layer (~28 Python stages)"
PHASE[Phase POs<br/>architect/dev/review/design]
DOMAIN[Domain stages<br/>github/dag/supervisor/retro/kickoff]
SHIM[Sprint glue<br/>load_/save_/build_tick + reshape]
end
subgraph "Storage"
KV[(Caloron KV<br/>~/.caloron/kv/)]
GITEA[(Gitea / GitHub)]
end
subgraph "Process management"
SHELL[caloron-shell<br/>~200 LOC Rust]
end
CLI --> ORCH
SCHED --> TICK
ORCH --> DOMAIN
ORCH --> SHELL
FULL --> PHASE
TICK --> SHIM
CORE --> DOMAIN
SHIM --> KV
DOMAIN --> GITEA
SHELL --> GITEA
Two parallel paths¶
Caloron has two ways to drive a sprint, by design — they target different operational models and have different maturity.
caloron sprint (Python orchestrator) — production today¶
orchestrator/orchestrator.py is a monolithic Python loop that:
- Calls a PO agent (configured CLI, e.g.
claude) - Decomposes a goal into tasks
- Resolves agents via AgentSpec when installed (or HR-agent keyword-matching as a fallback)
- Validates
required_skillsdeclared on each task — blocks if missing - Spawns each agent in a sandbox (
scripts/sandbox-agent.shon Linux,sandbox-passthrough.shelsewhere) - Manages the review cycle (up to 3 cycles before force-merge)
- Persists retro learnings into
learnings.jsonfor the next sprint - Talks to Gitea via
docker exec gitea wget
Every field report so far has come from this path. It's battle-tested and the right thing to use today.
Noether composition path — the destination¶
The composition path expresses the same logic as Noether stages
chained via Let bindings and reshape stages. Two flavours:
full_cycle.jsonruns the planning pipeline:design_po → architect_po → dev_po → review_po → phases_to_sprint_tasks, emitting a typed{tasks: [...]}ready for the orchestrator (or forcaloron sprint --graphto consume).sprint_tick_stateful.jsonis the per-tick loop thatnoether-scheduleris meant to fire on a cron. Pure composition; state persists in~/.caloron/kv/<sprint_id>.json.
These type-check end-to-end and individual stages run correctly. Live end-to-end pilot against a real Gitea is the remaining piece.
What lives where¶
| Concern | Implementation |
|---|---|
| Production sprint loop | orchestrator/orchestrator.py (Python) |
| Composition-driven sprint loop | compositions/sprint_tick_stateful.json (Noether graph) |
| Phase-based planning | compositions/full_cycle.json + stages/phases/* |
| State persistence | Caloron KV directory (one JSON per sprint) |
| Per-task framework selection | orchestrator.FRAMEWORKS registry (claude-code, gemini-cli, codex-cli, opencode, aider, cursor-cli, stub) |
| Process spawning | caloron-shell (Rust HTTP server, 3 endpoints) |
| Organisation-wide conventions | caloron/organisation.py + ~/.caloron/organisation.yml |
| Required-skill enforcement | _enforce_required_skills in orchestrator.py |
Sprint tick — sprint_tick_core data flow¶
Each tick of sprint_tick_core.json:
input: { repo, since, token_env, host, state, agents,
interventions, sprint_id, shell_url, stall_threshold_m }
↓ Let { poll: github_poll_events }
↓ Let { eval: Sequential[project_poll_to_eval, dag_evaluate] }
↓ Let { health: check_agent_health }
↓ Let { supervisor: Sequential[project_health_to_intervention,
decide_intervention] }
↓ Let { execute_result: Sequential[project_all_to_execute,
execute_actions] }
↓ build_tick_output
output: { actions_taken, errors, state, polled_at, interventions }
Each Let binding accumulates outputs into the body's scope under the
binding name; reshape stages between Sequential steps realign data so
each domain stage sees its own narrow input shape via structural
subtyping. See Compositions for why this design over
the alternatives.
The shell¶
Three HTTP endpoints (POST /heartbeat, POST /spawn, GET /status)
in ~200 lines of Rust. No business logic — only OS-process management
and proxying heartbeats. See Shell API.