The agent orchestration model
This document describes the orchestration model used by the svc, web, svc-java, svc-go, shared and system plugins, and by the noun plugins component, api, resource, instance, issue and platform, whose commands carry no agents. Read this first when adding or modifying agents.
Golden rule: everything goes through devbox run
Section titled “Golden rule: everything goes through devbox run”Every agent — human, CI, or AI — runs shell commands via devbox run <script>. The canonical recipes (test, test-fast, lint, lint-fix, typecheck, quality, security, image-build, image-scan, deploy-check, … — the same names in every shape, wrapping that shape’s tools) are defined once in devbox.json and reused everywhere. Each shape’s Copier template ships the full canonical set; consumer repos start with that and add project-specific recipes as needed.
If a recipe is missing, add it to devbox.json and commit — never run an ad-hoc pip install, uv add, pnpm add, go get, mvn …, pytest …, ruff …, or similar (one-off tool calls go through devbox run -- <tool> …). This keeps the dev shell, CI pipeline, and agents producing identical results.
SessionStart hooks
Section titled “SessionStart hooks”The svc, web, svc-java, svc-go and shared plugins ship a hooks/hooks.json with a SessionStart hook that:
- Warns if
devboxis missing fromPATHand prints the install command:curl -fsSL https://get.jetify.com/devbox/install.sh | bash. The hook exits 0 — it never blocks the session. - Runs
devbox install(idempotent) if adevbox.jsonis present in the working directory, so the dev environment is ready before any agent touches it. Thesvchook additionally runsuv sync --all-extrasfor Python repos (whenpyproject.tomlexists);webrunsdevbox run installwhennode_modules/is missing.
A consumer repo that installs any of these plugins is therefore devbox-aware out of the box; a repo with no devbox.json sees the hook as a silent no-op.
Stop hook (shared plugin)
Section titled “Stop hook (shared plugin)”While a /component:ship run is unfinished, the shared plugin’s Stop hook keeps the session going (spec 066, ADR-028: Unattended delivery without branch protection). It looks for <git dir>/cplat-ship.json in shell first (the worktree’s own git dir, so one worktree’s run never blocks a session in another, ADR-051 — Run state is per worktree), so sessions without a run never start Python. With a run it calls cplat component ship gate, which blocks the stop with the next step, and in the build phase the untraced criteria and the red devbox run test-fast output. It lets the session stop when the run is done, when the orchestrator escalated (cplat component ship escalate '<reason>': a merge conflict after its re-plan (or the 4th re-plan of a build), a changed acceptance test, a security finding with no fix released or a bump that breaks a gate, a decision the spec does not answer, the user asked to stop), or after two stops without progress (recorded as the escalation). Who is held and when: the session that started or resumed the run (its owner) is held; any other session is not, and a run without an owner holds nobody and says so at each stop. Progress is a new commit on the feature branch or a phase change (for a /system:ship run also a repo’s state change). After two stops of the owner without progress the run escalates as no progress: 2 stops in phase <phase> without a new commit or phase change, with the last message as an excerpt. The cause comes from a fixed rule: a matching escalation rule on the last message, else code when the evidence is red (untraced criteria, red tests, red required checks), else decision. See ADR-070: The Stop hook holds only the run’s owning session, and only while it makes progress.
Unit sessions (spec 131, ADR-075: An unattended ship or build runs as one session per unit, each saved at a checkpoint). cplat headless runs a ship or build as one claude -p session per unit (plan, red-tests, task:<id>, verification, land) and passes --unit <unit> to the command, which then reads plugins/component/references/unit-mode.md and does only that unit. The hook also runs when CPLAT_UNIT is set: it holds a unit session until the unit has its checkpoint, once per HEAD, and a second stop without a new commit is let through and counted as a stall. Two sessions in a row without a commit on one unit escalate the run as no progress: unit <unit> after 2 sessions without a commit. Before the first session a preflight checks the repo allowlist, the settings rules, the git identity, gh login and push access, and a failure prints preflight failed: <category>: <detail> and escalates with cause environment. The sessions get a run allowlist (ADR-076: An unattended run gets its git and gh permissions per session from a checked run allowlist, never from a settings file): the feature branch, commit -s, push without force and gh pr create. Budgets follow the plan size and verification gets one grace (ADR-077: Run budgets scale with the plan, and verification gets one grace). Details and the categories: unattended-delivery.md.
Permissions allowlist
Section titled “Permissions allowlist”Each consumer repo ships a .claude/settings.json (templated by Copier) with a committed allowlist that pre-approves the safe, frequent operations the pipeline needs — so /component:build and /component:fix don’t pause for permission prompts mid-flight.
Auto-allowed (in every template; the Python-only uv/python entries exist only in the Python templates, pnpm add/pnpm remove prompt in the web template):
- Every
devbox run <recipe>(the canonical entry point) - Read-only git (
status,diff,log,show, …) - Local-only git writes (
add,commit -m,merge --no-ff,worktree add/remove,branch -d,stash,tag -a) - Read-only inspection (
ls,find,grep,rg,jq,cat,head,tail,kubectl get/describe/logs/diff,kubectl apply --dry-run=*) - Pushing to non-main feature/fix/chore/docs/refactor/release branches and version tags
- Read-only
gh(pr view,issue list,run view, …) and pre-approvedgh pr merge --merge release/*
Always prompts (explicitly listed in ask):
- Anything that touches a remote:
git push origin main,git pull,gh release *,docker push(gh pr createis allowed: a pull request is how every change reachesmain) - Anything destructive:
git reset,git clean,git rebase,git branch -D,git checkout --,git commit --amend - Cluster writes:
kubectl delete,kubectl rollout restart/undo(livekubectl applyalso prompts — it’s covered by default since only--dry-run=*is allowlisted) - Out-of-band installs:
pip install,uv add,uv remove(Python) /pnpm add,pnpm remove(web)
Pre-approved by the shared plugin specifically:
gh repo create * --private *,gh repo edit * --add-topic *,git clone --depth 1 *,uv run * shapes.py *andcopier copy *so/component:newand/platform:updatecan run without mid-flight prompts.
How commands run the platform script
Section titled “How commands run the platform script”Commands never contain logic that can be code: they call cplat <noun> <verb> and relay its output. cplat is the shared plugin’s bin/cplat launcher, on the Bash PATH while the plugin is enabled. It runs scripts/cplat from the installed marketplace checkout, so commands use the platform version the plugins were installed from, offline; $CPLAT_PLATFORM (a local checkout) and $CPLAT_REF (a tag) override it, and only a missing checkout makes it fetch main. /platform:doctor reports the source.
Shapes and agent dispatch
Section titled “Shapes and agent dispatch”Every /component:* command starts by detecting the repo’s shape (.copier-answers.yml, falling back to file sniffing) and looks it up in shapes.yml. Coder, tester, deployment, observability and release agents are then spawned from the plugin that owns the shape (web:coder, svc-java:tester, svc-go:release, …; svc:* for Python); product-manager and architect (svc) and quality and security (shared) are shared by all shapes. gitops-app repos do not use /component:spec, /component:plan or /component:build; they use /component:add|set|remove, /resource:add, /instance:promote and /system:spec|plan|ship (cplat shape prints the routing). The multi-repo flow: /system:spec (system spec, approved) → /system:plan (criteria per repo, contract) → /system:ship runs /component:spec --from-plan <plan> <repo-id>, /component:plan, /component:build in each ready repo in parallel → you merge → /instance:promote.
Parallel implementation via git worktrees
Section titled “Parallel implementation via git worktrees”/component:build Phase 2 fans coder agents out in parallel using the Agent tool’s isolation: "worktree" mode. Each parallel coder works in its own git worktree on its own branch; the orchestrator merges branches back onto the base branch sequentially with a quality gate between each merge.
Worktrees must start at the feature branch, where the spec, the plan and the red acceptance tests are: every template sets worktree.baseRef: "head" in .claude/settings.json (Claude Code’s default branches from main), and the probe before the first batch checks the worktree’s HEAD; on a mismatch the build runs sequentially and says why.
The flow only works when the architect’s plan is machine-readable. docs/specs/<id>/plan.md starts with a YAML metadata block (exact format: plugins/svc/skills/spec-format/references/plan.md; cplat spec check validates it):
---spec_id: <NNN>-<slug>shape: <shape-id> # service-python | web-nextjs | service-java | service-go | …spec_hash: <from cplat spec hash> # the plan is invalid once the criteria changetasks: - id: t1 title: ... files: [<paths the task will touch>] covers: [AC-<NNN>.1] # every active criterion is covered by some task parallel_safe: true depends_on: [] # done: true # written by the build as tasks merge; a re-run resumes---The orchestrator:
- Topologically sorts tasks by
depends_on. - At each dependency level, greedily groups
parallel_safe: truetasks into batches whosefilessets are disjoint. - For each batch ≥ 2: spawns one coder per task in one assistant turn (multiple Agent tool calls in parallel), each in its own worktree. Each coder commits inside its worktree before returning.
- Merges sequentially back onto the base branch with
cplat component build merge(git merge --no-ff). After each merge:devbox run lint-fix, thencplat spec test-diff <red commit>(acceptance tests may only be reformatted),devbox run qualityanddevbox run test-fast. On failure, hand back to that task’s coder for lint/type fixes only. Each merged task is markeddonein the plan and committed on its own (one commit per task), so the next merge starts from a clean tree. - Cleans up worktrees and branches as it merges.
A merge conflict is re-planned once per task (at most 3 re-plans per build), then escalated; it is never resolved by hand. A conflict means the architect’s files declarations were inaccurate, so the fix is to update the plan. cplat component build merge aborts the merge and sets the task aside; the architect amends only files / depends_on / parallel_safe of tasks that are not done; the task runs again on top of the merged work. This happens without asking, attended or not.
Merge conflicts, files outside the plan and hand-backs are recorded per agent, shape and model (spec 089); cplat platform runs report --agents shows which agent causes rework and whether a release changed it (--by-version).
Feature branch and PR lifecycle
Section titled “Feature branch and PR lifecycle”Both /component:fix and /component:build include:
- Phase 0 (pre-flight): if on
main, createfeature/<slug>branch before any work starts. - Final PR phase: after all quality/test/security gates pass, push the branch and open a PR. Both push and PR creation are in the
askpermission list — user must confirm.
/component:ship goes further without asking (spec 067): cplat component ship pr pushes only the spec’s feature branch and opens the PR, cplat component ship land merges it after every reported check passed (never with a pending, failed or missing check), and cplat component ship deploy waits for the gitops-app’s pin PR. None of it needs branch protection or auto-merge, which private repos on GitHub Free lack (ADR-028: Unattended delivery without branch protection); the gitops-app’s pin-dev workflow merges pin PRs the same way (spec 065).
Automatic issue linking and closing
Section titled “Automatic issue linking and closing”The PR phase explicitly scans for GitHub issue references — #NNN patterns — in:
- The task/feature description (
$ARGUMENTS) - The last 10–20 commit messages on the branch
- the spec’s
tracks:issues (buildonly)
Each candidate issue is verified via gh issue view to confirm it is OPEN. Live Closes #NNN lines (not HTML comments) are injected into the PR body. When the PR merges into main, GitHub automatically closes each linked issue.
The /component:release pipeline adds a second safety net: Phase 7 scans all commits included in the release (since the previous tag) for #NNN references and calls gh issue close on any that are still open, with a comment linking to the release version.
Overview
Section titled “Overview”/component:spec ──► product-manager ──► your answers to its open questions ──► your approval │ ▼ docs/specs/<NNN>-<slug>/spec.md (criteria AC-<NNN>.<n>, non-goals)
/component:plan ──► architect ──► design.md (contract) + plan.md (tasks covering every criterion; cplat spec check)
/component:build ──► tester (acceptance mode) ──► coders ║parallel║ ──► merge+quality ──► quality ‖ tester ‖ security ──► reviewer ──► deployment ──► PR │ │ (1 worktree per task) │ │ │ ▼ ▼ ▼ ▼ ▼ failing tests per AC code until they pass lint, coverage, CVEs verification.md Dockerfile image
/component:verify ──► cplat spec trace + reviewer (no code changes)
/component:ship ──► [plan] ──► [build Phases 0–6] ──► cplat component ship pr ──► cplat component ship land ──► cplat component ship deploy │ red: logs → coder → push │ pin PR merged, │ (max 3 rounds) │ Argo healthy └── merge only when every check passed (no branch protection needed) Stop hook: no stop until done or `cplat component ship escalate`; `cplat headless` resumes it in fresh sessions
/component:fix ──► coder (diagnose) ──► coder (fix) ──► tester (verify) └──────────── loop (max 3) ──────────────┘
/component:check ──► quality + security (parallel, read-only)
/component:release ──► quality gate ──► tester gate ──► security gate ──► release agent │ ▼ changelog + tag + PR
/instance:promote <component...> --to <env> ──► cplat instance promote ──► PR (Argo reconciles on merge)/component:add|set|remove <component...> ──► cplat component add|set|remove ──► PR (system.yaml + generated ApplicationSets)/system:spec <desc> → /system:plan <id> ──► product-manager ──► system:planner ──► docs/specs/<id>/ + docs/plan/<id>.md/system:ship <id> ──► one agent per ready repo: /component:spec --from-plan → /component:plan → /component:build ──► PRsAgents by plugin
Section titled “Agents by plugin”svc plugin
Section titled “svc plugin”| Agent | Model | Job |
|---|---|---|
product-manager |
opus | Specs (docs/specs/<id>/spec.md): stories, acceptance criteria AC-<NNN>.<n>, non-goals, open questions for the user; folds triaged issues into specs |
architect |
opus | design.md (decisions, contract), plan.md (tasks with files, covers, parallel_safe, depends_on), ADRs |
reviewer |
opus | Checks a build against its spec (criteria, non-goals, plan, contract); writes verification.md; read-only on code |
coder |
sonnet | Python implementation; done when the acceptance tests of its task pass |
tester |
sonnet | Acceptance tests from the spec before the code; pytest, coverage, bandit |
migrations |
sonnet | Alembic migrations |
observability |
sonnet | structlog + Prometheus + OTel scaffolding |
release |
sonnet | Semver, CHANGELOG, release branch + PR |
deployment |
sonnet | Dockerfile and GHCR CI (image only — manifests live in the gitops-app repo, ADR-017) |
Orchestrators dispatch coder, tester, deployment, observability and release to the plugin that owns the repo’s shape (<plugin>:<role>; cplat shape prints the routing); for service-python / library-python that is svc. product-manager, architect, reviewer, quality and security are shape-agnostic.
The spec formats (spec.md, design.md, plan.md, verification.md, test tagging) live in one place, the svc:spec-format skill (plugins/svc/skills/spec-format/). Agents read its reference files through ${CLAUDE_PLUGIN_ROOT} instead of restating them; testers in other plugins carry only their language’s tagging idiom (ADR-026: Feature specs drive the build).
web plugin (web-nextjs)
Section titled “web plugin (web-nextjs)”| Agent | Model | Job |
|---|---|---|
coder |
sonnet | App Router + strict TypeScript; sonnet is enough for pattern-driven UI/route work |
tester |
sonnet | Vitest + Testing Library, Playwright when enabled |
deployment |
sonnet | Standalone-output Dockerfile and CI (image only) |
observability |
sonnet | /api/metrics, OpenTelemetry instrumentation, alerts |
release |
sonnet | package.json version, CHANGELOG, release PR |
svc-java plugin (service-java)
Section titled “svc-java plugin (service-java)”| Agent | Model | Job |
|---|---|---|
coder |
sonnet | Spring Boot 3 / Java 21 implementation |
tester |
sonnet | JUnit 5 + Spring Boot Test, JaCoCo gate |
deployment |
sonnet | Distroless Java image, JVM-aware probes |
observability |
sonnet | Actuator + Micrometer + OTel agent |
release |
sonnet | pom.xml version via versions:set |
svc-go plugin (service-go)
Section titled “svc-go plugin (service-go)”| Agent | Model | Job |
|---|---|---|
coder |
sonnet | Idiomatic Go (chi, slog) |
tester |
sonnet | stdlib testing + httptest, race detector |
deployment |
sonnet | distroless/static image, ldflags version |
observability |
sonnet | slog + Prometheus client_golang |
release |
sonnet | CHANGELOG + PR; version is the git tag |
Noun plugins (component, api, resource, instance, issue, platform)
Section titled “Noun plugins (component, api, resource, instance, issue, platform)”They have no agent. /instance:promote, /component:add|set|remove, /resource:add|remove, /instance:secret, /instance:verify, /api:list and /platform:update run cplat instance promote|secret|verify, cplat component add|set|remove, cplat resource add|remove, cplat api list and cplat platform update and relay its output; /component:spec|plan|build|ship|verify|fix|release dispatch the svc agents above.
system plugin
Section titled “system plugin”| Agent | Model | Job |
|---|---|---|
system:planner |
opus | Multi-repo plan (docs/plan/<slug>.md, ADR-011): which repos change, in what order, with paste-ready prompts. opus because decomposition across repos is the hard judgement call |
shared plugin
Section titled “shared plugin”| Agent | Model | Job |
|---|---|---|
quality |
sonnet | Runs devbox run quality for the repo’s shape (ruff + mypy, ESLint + tsc, Spotless + Checkstyle, golangci-lint, kustomize/kubeconform, …) |
security |
sonnet | Runs devbox run security (pip-audit/pnpm audit/OWASP DC/govulncheck + detect-secrets) and trivy on images |
File conventions in consumer repos
Section titled “File conventions in consumer repos”| Path | Purpose |
|---|---|
docs/specs/<NNN>-<slug>/ |
Feature specs: spec.md, design.md, plan.md, verification.md (ADR-026: Feature specs drive the build) |
docs/backlog.md |
One-line index of the specs (generated table) plus your own notes |
docs/plan/<slug>.md (gitops-app repo) |
Multi-repo plans (ADR-011) |
docs/adr/<nnn>-<title>.md |
Architecture decision records |
docs/env-vars.md |
Required environment variables |
docs/security/scan-<date>.md |
Security scan reports |
docs/migrations/runbook.md |
Migration runbook |
applications/<system>/system.yaml (gitops-app repo) |
The whole system: environments, components, APIs and resources; the manifests under overlays/ are generated from it |
migrations/ |
Alembic migration scripts |
tests/ |
pytest test suite |
.github/workflows/ |
CI/CD pipelines |
pyproject.toml |
Python project config |
.pre-commit-config.yaml |
Pre-commit hooks |
CHANGELOG.md |
Release changelog |
Model selection convention
Section titled “Model selection convention”model: opus— agents whose primary output is decisions downstream agents consume (product-manager,architect,system:planner) or that judge the result (reviewer). One bad plan poisons the whole run, so the reasoning headroom pays for itself.model: sonnet— execution agents (everything else).
When adding a new agent that needs to run shell commands, copy the devbox rule from coder.md or quality.md into the system prompt so the new agent inherits the same convention: shell only via devbox run <script>, recipes pinned in devbox.json.