Design

How a fire becomes a reviewed PR

One hivectl fire <ISSUE> renders a per-fire pod that writes code, reviews itself, and opens a PR — while everything stays scoped per project.

01Three roles

Two roles are ephemeral pods; the third is a local session. The pod that writes the code never ships it.

RoleWhereModelWhat it does
driverlocal Claude Code sessionOpus 4.8Orchestrator, human-in-the-loop. Plans issues, fires workers and qa, reads verdicts, merges PRs, course-corrects. Never writes app code.
workerephemeral k8s podSonnetDoes the work in one of four modes. Writes code, self-reviews in the same pod, opens a PR. Exits when done.
qaephemeral k8s podSonnetDrives the running app from the outside. Never clones or reads source. Files type:qa-feedback issues that re-enter the loop.
No separate reviewer pod. Review and merge are split between the worker (gates its own PR via a fresh in-pod review process) and the driver (performs the merge), so the author never ships its own code.

02The per-project model

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.

LocationOwnerHolds
$HIVE_ROOT (install dir)read-onlybin/hivectl, Makefile, assets/, manifests/, ops/, driver.md, labels.md
$HIVE_STATE_DIR ($HOME/.hive)per-userfallback .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.

03Definition of Done & Plan

## Definition of Done

≥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.

Exception — type:qa-feedback. Filed by qa against shipped work, so the finding text is the human intent. For these only, the driver drafts the DoD (one verifiable line per concrete defect). Every other type keeps the no-auto-stub rule.

## Plan

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).

04The in-pod review contract

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

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.

05Issue lifecycle

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 --> [*]
  

06Sandbox & image flow

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.

qa isolation. The qa role never reads source: no clone, no 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.

07Models & caps

worker (all modes)qadriver
ModelSonnetSonnetOpus 4.8 (claude-opus-4-8)
CPU / memory limit1 / 2Gi2 / 4Gin/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.