Skip to content

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.

The svc, web, svc-java, svc-go and shared plugins ship a hooks/hooks.json with a SessionStart hook that:

  • Warns if devbox is missing from PATH and 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 a devbox.json is present in the working directory, so the dev environment is ready before any agent touches it. The svc hook additionally runs uv sync --all-extras for Python repos (when pyproject.toml exists); web runs devbox run install when node_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.

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.


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-approved gh 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 create is allowed: a pull request is how every change reaches main)
  • Anything destructive: git reset, git clean, git rebase, git branch -D, git checkout --, git commit --amend
  • Cluster writes: kubectl delete, kubectl rollout restart/undo (live kubectl apply also 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 * and copier copy * so /component:new and /platform:update can run without mid-flight prompts.

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.


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.


/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 change
tasks:
- 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:

  1. Topologically sorts tasks by depends_on.
  2. At each dependency level, greedily groups parallel_safe: true tasks into batches whose files sets are disjoint.
  3. 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.
  4. Merges sequentially back onto the base branch with cplat component build merge (git merge --no-ff). After each merge: devbox run lint-fix, then cplat spec test-diff <red commit> (acceptance tests may only be reformatted), devbox run quality and devbox run test-fast. On failure, hand back to that task’s coder for lint/type fixes only. Each merged task is marked done in the plan and committed on its own (one commit per task), so the next merge starts from a clean tree.
  5. 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).


Both /component:fix and /component:build include:

  • Phase 0 (pre-flight): if on main, create feature/<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 ask permission 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).

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 (build only)

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.


/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 ──► PRs

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

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

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

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