One hivectl fire <ISSUE> renders a per-fire pod that writes code, reviews
itself, and opens a PR — while everything stays scoped per project.
Two roles are ephemeral pods; the third is a local session. The pod that writes the code never ships it.
| Role | Where | Model | What it does |
|---|---|---|---|
| driver | local Claude Code session | Opus 4.8 | Orchestrator, human-in-the-loop. Plans issues, fires workers and qa, reads verdicts, merges PRs, course-corrects. Never writes app code. |
| worker | ephemeral k8s pod | Sonnet | Does the work in one of four modes. Writes code, self-reviews in the same pod, opens a PR. Exits when done. |
| qa | ephemeral k8s pod | Sonnet | Drives the running app from the outside. Never clones or reads source. Files type:qa-feedback issues that re-enter the loop. |
hivectl init scaffolds .hive/ from the package's assets/hive/ template with placeholder substitution (app name, repo URL, kubectl context). Every later invocation reads .hive/config.yaml and tags what it creates with app=<that app>, so projects sharing one cluster never collide.
| Location | Owner | Holds |
|---|---|---|
$HIVE_ROOT (install dir) | read-only | bin/hivectl, Makefile, assets/, manifests/, ops/, driver.md, labels.md |
$HIVE_STATE_DIR ($HOME/.hive) | per-user | fallback .env, machine-wide bookkeeping |
<project>/.hive/ | per-project (gitignored) | config.yaml, runs/…/run.log, handoff/sprint-N.md |
Project-scoped commands bail with "run hivectl init first" when the cwd has no .hive/config.yaml; cluster-wide commands (setup, doctor, init, help, …) don't.
≥1 verifiable - [ ] checklist line (a command that exits 0, an endpoint returning a known shape, files staying in scope). The driver refuses to fire without one and comments asking for it — no auto-stub, because an LLM-written DoD that's subtly wrong is worse than none.
Goal, constraints, format of done, failure mode, files likely touched — the authoritative scope. The driver may draft a ## Plan (proposed by driver) block and flip status to needs-human; a human renames the heading to plain ## Plan to approve. The exact heading is load-bearing: ## Plan fires, ## Plan (proposed by driver) does not (the pod double-checks at startup).
On worker success, a second, fresh claude process (no shared reasoning trail) reviews the change and gates it. It grades each DoD line, emits a **DoD verdict** table, and ends with a load-bearing verdict line:
REVIEW: PASS | CHANGES_REQUESTED
PASS → issue flips to status:in-review; the driver merges.CHANGES_REQUESTED → issue flips to status:changes-requested; the driver re-fires a worker, which reads the review's **Changes requested:** block.Every harness-posted body and model-filled brief renders from a template in k8s-sandbox/image/templates/ via envsubst with a strict variable whitelist, so formatting never drifts between agents or fires.
Each issue carries exactly one status:* and one type:* label (plus an optional sprint-N). Transitions always remove the old status label in the same edit — the mutex is driver-enforced and pod-double-checked.
stateDiagram-v2
[*] --> ready: triaged, has DoD + Plan
ready --> in_progress: driver fires a worker
in_progress --> in_review: review PASS
in_progress --> changes_requested: review CHANGES_REQUESTED
in_progress --> needs_human: blocked / open questions
changes_requested --> in_progress: driver re-fires
needs_human --> ready: human resolves
in_review --> done: driver merges PR
done --> [*]
A fire renders a per-fire Sandbox CR and applies it. Pods have no docker socket, no kubectl, no hivectl — they only edit files, push, comment on the PR, and update the issue via gh. Two images are built and imported into the cluster once by hivectl agent-setup:
flowchart LR agentsetup["hivectl agent-setup"] -->|"build + import once"| coding["coding-agent
worker · lean, no browser"] agentsetup -->|"build + import once"| qaimg["web-qa-agent
qa · + playwright + chromium"] fire["hivectl fire"] -->|"references image already in cluster
IfNotPresent — no rebuild"| pod[("Sandbox pod")] coding -.-> pod qaimg -.-> pod
A fire does not build or re-load — it references the image already in the cluster (imagePullPolicy: IfNotPresent), which shares the secret's lifecycle: recreate the cluster, then re-run hivectl agent-setup. Pods auto-clean (shutdownPolicy: Delete); the Sandbox CR is GC'd by hive-gc (every 10 min, age > 30 min) or hivectl gc.
main checkout. Its GH token only (a) pushes QA artifacts to an orphan branch qa-artifacts/issue-<N>/<ts>/* and (b) opens a type:qa-feedback issue. Never edits issues, opens PRs, or pushes elsewhere.| worker (all modes) | qa | driver | |
|---|---|---|---|
| Model | Sonnet | Sonnet | Opus 4.8 (claude-opus-4-8) |
| CPU / memory limit | 1 / 2Gi | 2 / 4Gi | n/a (local) |
| Per-fire cost cap | $5 (coding + research) | $2 | --budget (default $10) |
The worker's in-pod review also runs Sonnet and carries its own $2 budget on top of the task cap; reported cost_usd = primary_cost_usd + selfreview_cost_usd. Never Fable 5, never Haiku. Caps are per-fire ceilings, not expected spend — overridable via HIVE_DRIVER_MODEL / MODEL / COST_LIMIT_USD / CPU_LIMIT / MEM_LIMIT.