Single source of truth for the dev-team system. For LLM consumption.
Design invariants: the enumerated, checkable list of the durable rules below lives in
docs/team-pipeline-invariants.md— the SSOT consumed by theproject-guidelinesskill and theproject-reviewconformance gate. This document remains the home for the full rationale behind each invariant.
One runtime: the roadmap runtime.
Conductor.run_roadmapdrives a roadmap graph throughWhatsNextSpecialty+ a realizer. Teams are declared as markdown (teams/<name>/), loaded into aTeamManifest, and run through the conductor; the scheduling specialty decides each step. Entry point: the/team-pipelineskill (theatpCLI). Seeplanning/2026-04-17-whats-next-specialty.md.Teams are self-contained data. By default a team runs through the generic one-node-per-specialty realizer. A team MAY also ship a
teams/<name>/playbook.py— discovered by convention (team_loader.load_playbook), never imported by name — supplying a bespokebuild_roadmap+realize(and optionalmock_responses). The engine names no team and imports no team: dropping a team in or deleting one touches no engine code.The former
TeamPlaybookstate-machine runtime (Conductor.run) and the engine-sideservices/conductor/playbooks/package have been removed.The arbitrator owns the state/gate/request/event tables and the roadmap graph schema (
planning/2026-04-17-atp-roadmap-design.md). The conductor architecture paper isplanning/2026-04-11-conductor-architecture.md; it remains the reference for inter-team requests, dispatcher semantics, and the event stream.
/atp run <team>
│
▼
atp_cli.py
│ load_team(teams/<team>/) → TeamManifest
│ build_roadmap(arbitrator) from manifest (one node per specialty)
│ open_session with roadmap_id
▼
Conductor.run_roadmap([WhatsNextSpecialty()], realize_primitive=…)
├── scheduler loop:
│ • deterministic short-circuit when unambiguous
│ • worker → verifier (with bounded retry) when not
│ • fail-path opens a `conflict` gate and awaits resolution
├── advance-to: parallel asyncio.gather over runnable primitives
├── decompose / await-gate / await-request / present-results
│ each call out to pluggable handlers
└── records node_state_event (running/done) per primitive
Files (all under pipeline/engine/source/):
engine/conductor/conductor.py—run_roadmap.engine/specialty/conductor_specialty.py—ConductorSpecialtyprotocol,ActionDecision.engine/specialty/whats_next.py— the scheduler specialty.engine/conductor/team_loader.py— team.md / specialist.md / specialty parser (area-pool resolution).engine/conductor/generic_realizer.py— manifest-driven realizer.engine/conductor/user_interaction.py—ask_userfor interview-style realizers.engine/myteams-cli.py— themyteamsCLI (manage the team registry; route commands).
Tests:
testing/unit/tests/conductor/test_whats_next_*.py— scheduler paths.testing/unit/tests/conductor/test_conductor_roadmap_e2e.py— linear / diamond / smoke.testing/unit/tests/conductor/test_run_roadmap_actions.py— all 7 action handlers.testing/unit/tests/conductor/test_crash_resume_roadmap.py— resume.testing/unit/tests/conductor/test_branch_point_gate.py— verifier-fail gate.testing/unit/tests/conductor/test_team_loader_and_generic_realizer.py— team markdown → run.
Gen-1 (the pre-conductor /dev-team skill pipeline) is archived in history/gen1-architecture.md; this document describes only the CURRENT system.
The composition model is myteams (registry) → team → specialist → specialty.
myteams is the single front door — a CLI + one Claude Code skill — and a team
is the unit of capability (teams/<name>/), flat and top-level, each carrying a
reverse-domain identifier. A team is a standalone unit: it functions when
installed on its own, with no membership in any larger grouping and no dependency
on another team being present. Teams register into the registry via the team
subcommand. The bundled teams are devteam (code review), projectteam
(planning), researchteam (research), storyteam (storytelling — grounded
markdown stories from a gathered source; see
docs/planning/2026-06-19-storytelling-team-synopsis.md), and cookbookteam
(cookbook authoring — recipes/principles/guidelines from code or intent, and
recipe/project → contract → the build loop). Teams ship no Python:
each team.json planner block points at the generic engine planner.
Authoring a new team? See
docs/team-authoring-guide.mdfor the step-by-step contract — the files, the two command modes (batch objective vs. interactive team-lead / deliberation), install, and the rules to follow.
The wiring is data-driven and the engine stays team-agnostic:
skill/plugin/ # the `myagenticteams` plugin scaffold
.claude-plugin/plugin.json # the plugin manifest
skills/roster/SKILL.md # static — wraps `myteams team`
skills/config/SKILL.md # static — wraps `myteams config`
skills/code-review-by-myagenticteams/SKILL.md # static — wraps `myteams devteam review code --risk`
# (per-team skills/<team>/ are generated at install)
teams/ # the team pool (flat, each standalone, by identifier)
devteam/ team.json (identifier + planner + commands + attribution) + team.md + specialists/ + specialities/<area>/
projectteam/ team.json + ...
researchteam/ team.json + ...
storyteam/ team.json + ... + objectives/narrate.json
cookbookteam/ team.json + ... + objectives/{recipe,principle,guideline,project}-author.json + team-leads/ + reference-manifest.json
~/.myteams/teams/teams.json # the registry: teams[] (by identifier, each with an `enabled` flag)
pipeline/engine/source/engine/conductor/
objective_planner.py # the generic planner every team runs on (make_planner)
command_conductor.py # run_prompt: dispatch a command pinned to the named team (only_team)
skill_renderer.py # render the single skill from the template + the live registry
registry.py # teams.json access: load + the enabled flag
- The registry is
~/.myteams/teams/teams.json—teams[], each entry keyed by the team'snameand carrying itsidentifier,blurb,description,commandssummary,attribution, and anenabledflag.team installwrites the entry (enabled);team enable/team disableflip the flag; the state persists across invocations. - Command vending is registry-driven, not hardcoded. The CLI hardcodes no
team or command names. The first parameter is a team (or a reserved builtin
—
team,help,config,session);myteams <team> <command>dispatches the command pinned to that one team (command_conductor.run_promptwith itsonly_teampin, on the genericobjective_planner). A baremyteams <team>lists that team's commands. The available surface is the union the enabled teams contribute — installing or removing a team changes it — but each invocation engages exactly the named team. (The old verb-first fan-out, where a bare command was broadcast to every team and every claimant convened, is gone, along with theteam runsubcommand it backed.) - Disabled / unknown teams are rejected with a clear message — a disabled named team gets an "enable it" hint — and help omits disabled teams. Command names live in the second position, so they never collide with the builtins; only the team name (first position) is reserved (builtins ∪ static-skill names). Cross-team collaboration is no longer a dispatch concern: the named team is primary and may ask other enabled teams for help mid-run via the arbitrator (Data Flow → Inter-team requests).
- The
myagenticteamsplugin, rendered from the live registry. Install assembles a skills-directory plugin at~/.claude/skills/myagenticteams/(manifest + the staticroster,config, andcode-review-by-myagenticteamsskills — the last a discoverable shortcut formyteams devteam review code --risk) and generates one skill per enabled team (skills/<team>/SKILL.md, surfacing as/myagenticteams:<team>) via the sharedskill_renderer.render_plugin_skills. Each per-team skill lists that team's commands, pins dispatch withmyteams <team>, and folds in the team's optionalteams/<team>/skill.mdfragment (build loop, interactive dialogue, review scopes). The CLI re-generates the per-team skills on every team mutation (install / uninstall / enable / disable) and prunes a removed/disabled team's skill, so the plugin tracks the live registry. The empty core (no teams) is the plugin with just the static skills. (There is no monolithicmyteamsskill — it was retired in favour of this plugin; themyteamsCLI lives on as the engine the skills shell out to.) Each per-team skill also carries a cross-team delegation roster — the other enabled teams with their commands — so a running team can hand a sub-task to another via the samemyteams <team> <command>form (one level deep). This is skill-level delegation, distinct from the in-pipelineprovides/consumesarbitrator asks a specialist makes mid-review.
The encapsulation invariants from teams-as-data hold: engine code never names a
team, teams declare their planner entry point in data, teams/<name>/ is
data-only, and there is no team-owned Python anywhere. Any team runs on the
generic planner
(pipeline.engine.source.engine.conductor.objective_planner) and binds commands
to objective specs in objectives/.
Beyond planner, a team's team.json carries every team-specific
string its planner and the engine consume; the Python on both sides is
generic:
identifier— the team's reverse-domain id (e.g.com.myagenticteams.development). Recorded inteams.jsonatteam installtime.blurb— a one-line summary of the team, distinct from the longerdescription. Shown byteam listalongside attribution and enabled/disabled status.commands— the command surface:name, optionalcanonicalid (keys internal dispatch and the llm-guides dir; e.g.code-review→review), optionalaliases,description. Loaded as aCommandVocabularyvia the engine'steam_loader.load_command_vocabulary; the summary (name + description + any subcommands) is also recorded in the registry so help and the skill can vend it.attribution—authors(name + email) andurls, surfaced byteam listand recorded in the registry so it renders rather than showing blank.nl_router— opt-in freeform-prompt routing:commands_docandargv_examplesline arrays that steer the generic engine router (toolkit/nl_router.py). No block → the team does no NL routing.home—dir(the team's home root for run output, e.g.~/.agentic-dev-team) plus optionalreviews_remote(the git remote the output repo is cloned from and pushed to). Read by the engine'stoolkit/team_home.py; no block → no home, andcode-reviewrefuses to run.repo_guidance_skill— optional: the name of a skill the engine reads from the repo being worked on (not the team's tree) and folds into the team's work, so specialists judge/plan against that repo's own stated rules. Read ontoTeamManifestbyteam_loader. Applied on both paths viaengine/execution/repo_guidance.py(the acceptance-contract fold pattern): in review (specialist_pipeline) as a sharedcontext=Trueunit in every reviewer's slice; in roadmap planning (roadmap+generic_realizer) appended to each node'sworker_focus, with a gated--reposurfaced only when the team opts in. Absent, or the named skill not present in the target repo → the run proceeds without it ("if it's not there, move on"). This is the only file the engine reads from the worked-on repo.devteamsets it toproject-guidelines— folded into its reviews by the engine, and (because devteam'splan codeauthoring is skill-driven, not a headless dispatch) read byteams/devteam/skill.mdwhen drafting an acceptance contract, so plan and review honor the same guidelines. Design:superpowers/specs/2026-07-01-repo-guidance-skill-fold-design.md.- LLM resolution is layered: an explicit
--llmsflag wins for every team; otherwise a team's ownllmspin inteam.jsonis used; failing that, the global default (defaults.llmsinteams.json, set withmyteams config --default-llms set <list>, falling back to the built-inclaude— claude-only for now; codex is registered and usable but off by default while the system is hardened, re-enabled withmyteams config --default-llms set claude,codex). A team that needs specific providers (e.g. researchteam → claude only) declares"llms": ["claude"]in itsteam.json. Design:planning/2026-06-30-loop-engineering-resilience.md. provides— the inter-team request kinds this team services (each: a namespacedkind,description, inlineinput_schema/response_schema,default_timeout_seconds, and ahandlerbinding a specialty or objective). Read byteam_loader.load_provided_kinds; registered on a run's session bysession_convener. See Data Flow → Inter-team requests.consumes— request kinds this team's agents may ask the arbitrator for (also allowed per-objective). Read byteam_loader.load_consumed_kinds. No block on either side → the team takes no part in inter-team requests.publish— optional: after a run, write the session's findings to a configured MCP server ({"server", "tool", "map"};mapselects finding fields → tool params via$.field). Read byteam_loader.load_publish_config; fired at the roadmap run finalization seam (RoadmapWorkflow._work→engine/execution/publish_run.py→engine/execution/publish.py). No block → no publish.
Install-wide MCP server configs live in ~/.myteams/config.json (mcp_servers[]:
id, name, description, docs_url, transport http|stdio, connection,
auth, enabled, status), beside teams/teams.json. Owned by
engine/conductor/registry.py (load_config/save_config + mcp_servers
accessors). Managed by the config mcp … CLI subcommands
(add|list|remove|enable|disable|set-secret|test|tools); auth secrets live in
the OS keychain via toolkit/secrets.py (keyring), with config storing only a
secret_ref — never the token. The generic outbound MCP client is
toolkit/mcp_client/ (Streamable HTTP + stdio, over the mcp SDK). The engine
publishes a team's structured findings to its bound server's write tool
deterministically (Approach B) — the model does not call the tool. MCP_ALLOWED_HOSTS
gates http hosts (permissive when unset).
Every claude dispatch is a claude -p child that authenticates from
CLAUDE_CODE_OAUTH_TOKEN in its environment or the CLI's stored login. When
myteams itself runs inside a Claude Code session, the harness strips that
variable from tool subshells — and a logged-out stored login then kills every
dispatch with "Not logged in". The supported path: mint a long-lived token
(claude setup-token), store it once with myteams config auth set-token claude (OS keychain via toolkit/secrets.py, ref myteams/auth/claude —
same store as MCP secrets), and toolkit/llm_providers/claude_auth.py
injects it into each child's environment at the single construction seam
(real_dispatcher). claude_auth.resolve_credential() is the ONE precedence
walk — process env var → keychain token → (keychain unreadable) → stored login
— and token_source/claude_child_env are projections over it, so the
diagnostic surface and the dispatch path can't disagree; the keychain read is
cached with a short TTL (_TOKEN_CACHE_TTL_SECONDS) so a large fan-out (and
each fallback retry) collapses to one read rather than one per dispatch, while a
long-lived host (serve_websocket/serve_stdio) still re-reads within the TTL
and self-heals to a rotated token — an out-of-process config auth set-token
can't reset a running host's cache, so the bounded TTL is what keeps it from
injecting a stale token until restart. config auth status|verify claude shows/proves the
resolved source (including a distinct "keychain unreadable" state for a
locked/denied keychain, so a stored-but-unreadable token is never misreported
as "no token"). An unauthenticated child raises the typed DispatchAuthError
— classified from the CLI's own credential phrases only, so unrelated
proxy/MCP "authentication" stderr isn't misdiagnosed — whose concrete re-auth
steps ride the RunBlocked halt banner on every workflow (review, roadmap,
authoring, via the shared execution/run_blocked.py). toolkit/secrets.py
owns the keyring error taxonomy (KeyringUnavailable with an install/backend
hint, SecretAccessError for a locked keychain, idempotent delete_secret),
so every secret-writing command fails with a remedy rather than a traceback and
config auth clear never mistakes a failed delete for success.
Consultant steering lives in the team tree as markdown, not JSON: the
confidence scorer reads consulting/finding-confidence.md (## Rubric
## False Positives) through the engine's genericteam_loader.load_consulting_sections.
myteams team manages the registry (state persists in ~/.myteams/teams/teams.json):
| Subcommand | What it does |
|---|---|
team install <path> [--name <name>] |
Copy the team at <path> into the registry and enable it; the name is read from the team spec when --name is omitted. (Replaced the old register command.) |
team uninstall <name> |
Confirm, then remove the team from the registry and delete its installed copy — only ever a path inside the registry root. |
team list |
List each team as name: blurb plus its attribution and enabled/disabled status. |
team describe <team> [<specialist> [<specialty>]] |
Show detail general→specific: team (blurb + description + specialists), then one specialist (its specialties), then one specialty (area/model/focus/verify). |
team enable <name> / team disable <name> |
Flip the team's enabled flag (persisted). |
A command is either batch or interactive. A batch command carries an
objective; the generic planner fans the team's specialists out against a target
and convenes findings (the code-review pipeline below). An interactive command
carries interactive: true + team_lead: "<lead>"; it is skill-orchestrated — the
skill runs the hidden builtin myteams session resolve <command> to find the
owning enabled team + its team-lead manual, then drives a live dialogue per that
manual (it never runs the batch planner — a guard intercepts an interactive command
run as a batch command).
Interactive teams that explore a space of options and converge on a choice run on
the generic deliberation toolkit (pipeline/engine/source/toolkit/deliberation/:
schema, graph, timeline, sources, session, render, crystallize). It is
content-neutral: it validates every operation against the team's deliberation
block (node_types/edge_relations/statuses/phases/labels/output_schema/
render_labels) and names no team. The skill drives it through hidden,
agent-facing myteams session builtins (omitted from --help): new, add-node, link, set-status, add-source, set-memory, crystallize, render-journey, list, show, resolve. The split is the three-piece model — deterministic generic Python (the
toolkit/verbs), team data (the deliberation block + the team-lead manual), and
the LLM (which reads the data and calls the verbs, supplying judgment: generate,
choose, narrate). The skill never hand-writes session JSON; the verbs are the only
state mutation. teams/brainstormteam/ is the worked example. Authoring guide:
docs/team-authoring-guide.md; design: docs/superpowers/specs/2026-06-28-deliberation-toolkit-design.md.
myteams review runs in pipeline/engine/source/review/ (pipeline stages +
generic prompts) on the shared execution kernel
(pipeline/engine/source/engine/execution/), fanned out per LLM in --llms
by MultiTeamExecutor. The path:
- Pre-checks (
pre_checks.py, objective-opt-in) — the TARGET repo's own linters/typecheckers (config-keyed AND binary-guarded) run over the changed files; the bounded diagnostics ride as a context unit ("already machine-caught — leads, never re-report") and land aspre-checks.txt. - Select — once per run (
selection.select_roster) — the roster is a property of the diff, not the LLM: a deterministic signal prefilter, an LLM interest poll (sees per-filechanged_linesmagnitude), and the always-on floor combine into ONE shared roster adopted by every LLM leaf (each leaf applies only its own resume/carry trim). Decisions are recorded asreview.selection.*events with a structuredfailed_openflag. - Review (
orchestrator.run_review→reviewer.review_specialist) — one reviewer per selected specialist, fanned out under a concurrency cap. The worker subprocess runs INSIDE the repo under review (AgentDefinition.working_dir) with advertised, bounded read tools — staged reasoning: read hunks → check the callers the diff does not show → verify → self-check (unmodified-line and linter-catchable noise dropped before emitting). Findings carry an anchored severity (1–100 rubric), a separate confidence, and acontext_gapchannel (what the reviewer needed but could not reach). The diff rides framed as DATA between BEGIN/END markers. Over-budget diffs are chunked (no content dropped); specialty focus text is joined under### area/nameheaders. - Verify (
verifier.py) — an independent verifier judges COVERAGE against each specialty's## Verifycriteria (full diff when it fits, else finding-adjacent slices + an uncovered-hunk recall sample); FAIL drives one bounded add-only re-review. A skip is recorded as verdictskipped, never a fake pass. Over-budget groups run in parallel. - Merge + memory (
arbitrator.upsert_finding,review_memory.py,reuse.py) — findings upsert intofinding/finding_attributionkeyed by (session, objective, anchor_key); cross-run memory lives in<home>/<project>/review-memory.db: refuted anchors (from finish-review verdicts / author handoffs) floor re-appearing findings with provenance; an unchanged specialist slice carries its findings forward instead of re-dispatching; an identical changeset fingerprint skips the run entirely (--freshdisables all of it). - Consult + adjudicate (
confidence_scorer.py) — the independent consultant re-scores every finding (verdictscored|not-applicable-as-abstention; it never sees finder scores) and, as the only judge who sees all findings together, marksduplicate_of— the analyzer folds duplicates under their canonical finding and the gate counts the defect once (rows are never destroyed). Scoring groups run in parallel. - Gate + handoff (deterministic, before any narrative phase) —
merge-readiness.json(blockers carry theircriterionwhen anchored) andfinish-review-handoff.json(findings carry the stableanchoralongside the display id). - Analyze + report —
issues-summary.mdpresents findings at validated confidence ≥ 80 with a labelled below-the-bar section (nothing dropped); per-LLMreport-{llm}.mdnarratives run in PARALLEL at the cheap tier through the run's REAL arbitrator (tokens/failures recorded). - Compare (
compare.py,--compare, dev-only) — runs the real/code-review maxand writescomparison.mdopening with MEASURED anchor-matched overlap numbers (eval_scoring.py) ahead of the LLM's narration. The same matcher drives the seeded-corpus eval harness (testing/functional/harness/eval_review.py+testing/fixtures/eval-corpus/) — team precision/recall and per-specialist precision against known-issue manifests.
Every dispatch funnels through engine/execution/dispatching.run_dispatch,
which closes the dispatch row truthfully (completed+token-usage /
timeout / failed) and enforces the per-run token wall
(--run-token-budget, env fallback MYTEAMS_RUN_TOKEN_BUDGET).
Debug mode / observability. Decision events + per-dispatch token/cost
metrics are always recorded to the run's issues.db; --debug additionally
captures the full claude -p event stream. RunLog tees the run narrative to
stdout and a committed review.log. myteams runs log <run> renders a
read-only timeline; runs findings re-renders at any confidence threshold
using the same effective (validation-aware) ranking as the report and gate.
Truthful status. The session status is derived (never a stored count)
from dispatch end-states + findings + verifier verdicts: completed,
partial, or failed; the CLI exits nonzero on anything but a clean run —
including a run whose ARCHIVE commit/push failed.
Storage: each run gets its own SQLite DB at
~/.agentic-dev-team/<project>/code-reviews/<timestamp>/issues.db, committed
alongside report-*.md / issues-summary.md / review.log /
pre-checks.txt / comparison.md. A discovery run also writes
finish-review-handoff.json (its findings in the shape finish-review
ingests, disposition unset) so the two commands compose.
Archive resilience. The output repo is checkpointed — committed AND
pushed (team_home.checkpoint_run) — at every write point, not once at the
end: run started, specialist phase done, verdict artifacts (gate + handoff),
reports, each suspended step wave, and a FINAL checkpoint that runs after
session close so the archived issues.db/review.log carry the persisted
status. A failure at any point strands at most the work since the last
checkpoint. Every failing path (plan crash, finalize crash, all providers
failed, blocked, incomplete) also writes failure-report.md into the run dir
— deterministic facts (exception + traceback + review.log tail + the
--resume hint) always, plus a best-effort LLM narrative (what happened /
likely cause / next steps) that is silently dropped when the provider is the
thing that failed — and checkpoints it on the way down. SQLite -wal/-shm
sidecars and the stepping counter are kept out of the archive by a committed
.gitignore; the non-fast-forward push recovery rebases with --autostash
so a dirty clone can never wedge it.
myteams review finish runs the same specialist pipeline, seeded with a prior
review's findings. The handoff JSON it ingests is the one a code-review run
emits (finish-review-handoff.json) — the author just fills in dispositions —
or a hand-written one for an external review. The changeset-with-handoff input
adapter folds that handoff (the review's findings + the author's dispositions)
into the changeset as a signal-less synthetic file, so each specialist — and the
finding-confidence
consultant — adjudicates every claim against the diff rather than discovering
from scratch. The objective (objectives/finish-review.json) extends the finding
schema with myteams' own score beside the review's, a verdict, and the deferral
fields, and uses the finish-review guides pack (adjudication framing) and the
dev-team-manager team-lead. myteams plays manager and never edits code: the CLI
pass is one stateless independent judgment; the multi-turn loop (present, discuss
deferrals, walk the IC through fixes, fix-review + cleanup gate, stop at
merge-ready) is driven by the skill. A future, separate command will let myteams
write the fixes itself.
Flags/outputs: --from-review <run-dir> resolves --findings to a code-review
run's emitted handoff; --strict re-judges only the handed-off findings (no
adjacent-issue discovery — churn control). A config-driven gate block on the
objective makes the engine compute a machine-readable merge-readiness.json
(review/merge_gate.py) at finalize — ready + the blocking findings — so the
skill branches on a verdict, not prose. The finding-confidence consultant now
re-scores severity as well as confidence (a nullable validated_severity on
finding_validation); FindingRow.effective_severity() feeds both the gate and
the emitted handoff, so an independent re-score actually moves the verdict —
benefiting code-review too.
myteams review conformance is a third capability on the same generic specialist
pipeline, on a different axis from the other two: not code quality (code-review)
and not adjudicating a prior review (finish-review), but conformance to stated
intent — did this diff do what it was supposed to do? It adjudicates a changeset
against an explicit acceptance contract (a list of criteria), judging each
criterion met / unmet (not done) / divergent (done wrong), and gates on
the gaps. This is the axis a quality review never covers: a clean review can still
ship the wrong feature.
- Contract authoring —
plan(a conversational skill + aplanbuiltin) persists a contract to~/.myteams/contracts/<team>/(user data, outside the installedteams/tree). Shared shape:engine/execution/acceptance_contract.py(objective-neutral — a compat alias remains atreview/acceptance_contract.py). - Input — the
changeset-with-contractadapter (ChangesetWithContractAdapter,consumes_handoff=True) folds the contract intoINPUT_TEXTas acontext=Trueunit (same mechanism finish-review uses to route a synthetic file into every worker's slice — no new prompt marker). - Roster — a dedicated always-on
conformancespecialist (walks the full criterion list — a coverage backstop) plus in-lane domain specialists, convened via theconformancescope; the skill passes--conformance. Objective:objectives/conformance-review.json(default_scope: conformance). - Findings + gate — findings are
gaps anchored bycriterion, carrying astatus(unmet/divergent). The objective'sgateblock (block_field: status) makes the engine emitmerge-readiness.jsonvia the same genericreview/merge_gate.pyfinish-review uses (made case-insensitive), blocking on unmet/divergent ≥ severity 50. One gate, three capabilities.
Design + research basis:
planning/2026-06-24-conformance-oracle-and-build-loop-design.md.
myteams build --contract <path> runs the conformance oracle automatically, once
per build increment — the CompPilot loop (propose → measure → feedback → refine,
arXiv 2511.00592) with the dev team as the
oracle. It attacks the late-divergence pain (work not done / done wrong, found 10×
too late): divergence is caught at the increment that introduces it, so worst-case
unwind = one increment. It is a skill-driven workflow (the build section in
teams/devteam/skill.md, folded into the /myagenticteams:devteam skill), never
engine Python — the engine ships generic builtins (write-loop-status, build stats) the skill calls.
One iteration:
0. guard write-loop-status guard — refuse to start if the run is over budget
1. build depth-0 builder subagent makes a small increment toward the open gaps
2. commit git commit WIP — each accepted increment is one revertible commit
3. gate conformance-review HEAD --base "$base" --conformance → merge-readiness.json
4. record write-loop-status record — write measured facts; return the progress signal
5. decide keep on progress / reset --hard HEAD~1 on regress / escalate-once on stall /
STOP when Python (decide_stop) says so
The invariant boundary: the generator (writes code) and the oracle
(conformance-review) stay separate — different subagents, by default different
models (anti-gaming). The review pipeline still never writes code; myteams build is
the only thing that orchestrates a coder, and only by dispatching a subagent and
gating it. Current branch only — never pushes, merges, or deploys.
Stopping is measured, not guessed. No human/formula can know the
diminishing-returns point up front, so the stop is empirical and decided in Python
(loop_status.decide_stop, never trusted to the skill). Order:
done > oscillating > budget > iteration-cap.
- Automatic logical stops (no tuning):
ready(gate verdict),oscillating(the repetition circuit breaker — debounced: a criterion halts only when fixed-and-re-broken ≥2× across the run's per-iteration history (loop_status.oscillation_flips); a single met→gap flip is treated as oracle variance and gets another iteration),regressed, drift — fromloop_status.compare+ the recorded history. An incomplete gate (the oracle run died; its merge-readiness gate saysincompleteor is missing) never counts:recordcarries every criterion forward untouched (--provenstill honored) and flagsgate_incompleteso the driver re-runs the gate instead of trusting partial findings. - Iteration cap — the learned diminishing-returns inflection per
n_criteriabucket when ≥3 completed runs of that size exist (loop_stats._recommended_for, ~90% resolved), else an arbitrary placeholderbackstop_iterations(n)=max(3,3·n). The magic number self-replaces with data (stop_reason: diminishing-returnsvsbackstop).
Instrumentation reads recorded facts, never self-report. Per-dispatch tokens are
already recorded in the arbitrator dispatch table; run_stats.compute_run_stats
rolls them up; cost is derived from _CLAUDE_PRICING, never stored. A persistent
cross-run store ~/.agentic-dev-team/build-metrics/loop-metrics.db (build_run
/ build_iteration / build_iteration_criterion) holds raw facts only (per the
db-schema-design rules — no stored cost/counts/ready; the curve is derived by
query). build stats reads it for the convergence analysis that tunes the backstop.
The spend wall is a separate thing — catastrophe-prevention, not the sweet spot.
A cumulative-token budget (token_budget(), default 10,000,000, override
MYTEAMS_BUILD_TOKEN_BUDGET) enforced at two CLI chokepoints (write-loop-status guard
pre-iteration + the post-gate record), plus an opt-in PreToolUse hook
(loop_guard.hook_main, registered via install --with-build-guard-hook,
default off) that denies the loop's gate call when over budget — pre-execution
interception (the TDD-Guard pattern). Generous by default so the measured stops fire
first; the wall only catches a true runaway (the $6K-overnight-bill failure mode).
Depth-0 builder subagents (no recursion) bound per-iteration spend by arithmetic.
Files: acceptance_contract.py (objective-neutral — now under
engine/execution/, compat alias at review/); loop_metrics.py,
loop_status.py, loop_stats.py, loop_guard.py stay under review/ (the
build loop is coupled to the review pipeline it uses as its oracle:
loop_status reads merge_gate / run_stats / findings_read). Builtins in
engine/workflows/builtins.py; commands + conformance scope in
teams/devteam/team.json. Design + full research basis:
planning/2026-06-24-conformance-oracle-and-build-loop-design.md.
Field notes + improvement backlog from a large external codebase (incl. the ARG_MAX
argv-transport hazard in the shared dispatcher):
planning/2026-06-29-build-loop-field-notes-and-backlog.md.
The default execution mode for skill-driven commands (the generated per-team
skills pass --step): a long run is driven as MANY short CLI invocations so no
single Bash call outlives Claude Code's 10-minute ceiling. Each invocation runs
one budgeted wave of dispatches (--step-size, default = the fan-out
concurrency); completed dispatches are memoized durably (dispatch_memo in the
run db, keyed by content: provider + agent + tools + prompt + model + schema),
so the next invocation replays them free and continues. When work remains, the
conductor prints a __MYTEAMS_NEXT__ <exact command> directive (team-pinned
--llms/--primary-llm so the continuation selects the same leaves and memo
keys) and the skill loops on it; __MYTEAMS_DONE__ ends the loop. Guards: a
durable per-run invocation cap (200) halts a non-converging loop (a volatile
prompt fragment defeats memo replay); the resume/step fingerprint hashes the
DIFF CONTENT so a mid-run commit fails fast instead of blending two diffs;
runs memo clears a poisoned memo. Files: toolkit/stepping/
(memo/budget/StepSuspended), conductor/command_conductor.py (the directive
loop).
The third generic workflow (engine/workflows/authoring.py), alongside
specialist-pipeline (review → findings) and roadmap (node DAG). Where those
analyse, authoring synthesises: it dispatches ONE tool-enabled claude -p
agent assembled from the objective's authoring block — the agent (its system
prompt = the team's team-leads/<artifact>.md playbook + the paths to the
sealed cookbook references), and a task (formalise the code at --from-code, or
draft from --intent, and WRITE the artifact to the target). Because the agent
has file tools it reads the source and writes the artifact itself, so authoring
needs no input adapter and no inter-node plumbing. Everything is data: the
objective names the agent, the playbook, and the sealed template/compliance/
references; teams ship no Python. It is terminal-native — myteams recipe new … dispatches the model directly — and the skill is just an interactive
front-end over the same command.
cookbookteam binds it for recipe new / principle new / guideline new /
project new, and seals the cookbook substrate (principles, guidelines,
compliance, templates, conventions) into the team via reference-manifest.json.
The reverse direction is deterministic: recipe build / project build
(builtins) parse a recipe's kebab-case named requirements + conformance vectors
into acceptance-contract criteria (review/recipe_contract.py) and hand off to
devteam's build loop — cookbookteam owns recipe→contract, devteam owns building
toward it.
For how to build a team like this from scratch — the construction kit, choosing a
workflow, and the reliability gotchas — see
team-authoring-guide.md.
This is the load-bearing contract for how the pipeline interacts with a team. It
is stated as MUST / MUST NOT because the implementation has drifted from it
before, and an enforcement test now guards it
(testing/unit/tests/boundaries/test_engine_does_not_select_specialists.py).
The team-lead is the pipeline's sole interface to a team. The engine hands a
team-lead (objective, target, focus, effort) and receives findings back. The
engine MUST NOT enumerate, select, or dispatch a specialist. Only the generic
team-lead runner (pipeline/engine/source/engine/conductor/team_lead_runner.py)
and the team loader (which builds the manifest) may touch manifest.specialists.
Teams ship data only. A team contributes no Python. The team-lead's behavior is the generic runner; its voice is its markdown doc (consumed only by the report writer).
Three peer pools make up a team — no ownership nesting:
team-leads/— conveners: poll interest, dispatch the interested specialists, aggregate, voice the report.specialists/— a shared pool; each declares interest (signals) and references specialties by qualified id. A specialist does not own specialty files.specialities/<area>/<name>.md— a shared pool grouped by area. The area dir is the file organization; the qualified idarea/name(reverse-domainarea.name) is globally unique. Scopes (below) select specialists + specialties as config, not by area.
Selection is bottom-up specialist interest — never a central triage. No actor has global knowledge of the roster. Selection is tiered:
- Tier 0 (deterministic, free): a tech-bound specialist is excluded only when
its declared signals are disjoint from the changeset's (web-design ⟂ swift).
Cross-cutting specialists (the
always_onfloor) are NEVER signal-excluded. - Tier 1 (agentic, scaled by effort): each Tier-0 survivor judges its own interest in this changeset, reasoning only about its own domain.
The team-lead aggregates interest into the roster. The --<scope> flags and
--effort are parameters of the team-lead request, not separate commands.
Scopes are an explicit, config-defined pre-filter (replacing area-derived
--focus). A review command is scoped by composable --<scope> flags
(--risk default / --security / --completeness / --all / …). A scope is
team data in two layers: team.json scopes: {<scope>: {specialists: [...], description, default}} (the roster) and each specialist.md scope_<name>:
(the specialty subset that specialist runs under the scope; absent ⇒ all of
them). The team-lead seam (team_lead_runner) validates the two layers are
coherent and resolves the selected scopes to a ScopeSelection (roster +
per-specialist active specialties) — a deterministic pre-filter applied before
the signal/interest tiers, which still narrow by changeset relevance. The roster
gate trims even an always_on specialist a scope doesn't convene (so risk
excludes testing-qa / ui-ux). It is one review, one finding shape — a scope
selects roster + specialties, never a different output. risk is the explicit
default; completeness (best-practice presence, not invention — the
specialties already carry the cookbook principles) is an opt-in scope excluded
from the default; all is the no-filter escape hatch. The scope catalog + CLI
live in engine/workflows/scopes.py (team-blind — no specialist enumeration);
roster resolution lives in the team-lead seam (validate_scopes /
scope_selection). With no scope flag, the skill renders a checkbox prompt (from
--list-scopes) and passes explicit flags; a bare terminal run prompts directly;
otherwise the default scope (risk) runs.
Communication stays tiered (see Data Flow below): user ↔ team-lead ↔ specialist ↔ specialty. The engine is not a participant in the team-lead ↔ specialist relationship; it invokes the team-lead and nothing below.
A command runs in ONE shared session. The team that owns the invoked command is the primary and drives the run; the other enabled teams that declare a request contract stand by as support providers. A specialist mid-run may ask the arbitrator for cross-team help; the arbitrator routes the request to the provider's handler, which runs the bound work under the provider team's id in the same DB.
- Declarative contract (no team Python). A provider team declares
provides[]inteam.json— each entry binds a namespacedkindto a handler ({specialist, speciality}today;{objective}is the heavier path) with inlineinput_schema/response_schemaand adefault_timeout_seconds. A consumer team declaresconsumes(team-wide inteam.jsonand/or per-objective) — the kinds its agents may ask for. - The arbitrator is the conduit.
register_request_kind/register_request_callableregister each provider's kinds on the run's session;create_requestvalidates(to_team, kind)at the boundary;run_requestservices one request under the kind's timeout, validates the response againstresponse_schema, and records a terminal status (COMPLETED / TIMEOUT / FAILED). Handler closures carry the LLM dispatcher (injected); the arbitrator itself never imports an LLM provider. - Agent-initiated, deterministic transport. A specialist worker decides
when/what to ask by returning a top-level
requests[]array; the engine services it and re-dispatches the worker with the answers appended (a turn guard bounds the loop). The kinds a worker may ask for are advertised in its prompt fromconsumes∩ the session's registered providers. - Group fan-out + barrier. A worker may ask several teams at once;
create_request_group/run_request_groupscatter the siblings (one sharedgroup_id) in parallel and barrier on all before the worker continues. - Two transports, one routing. Both turn an agent's asks into arbitrator
requests via
session_convener.service_asks:- emit-in-response (default): the worker returns a
requests[]array; the engine services it and re-dispatches the worker with the answers appended. - MCP (
MYTEAMS_ASK_TRANSPORT=mcp): an in-process MCP-over-HTTP server (toolkit/llm_providers/mcp_ask_server.py) exposes anasktool the worker calls MID-REASONING;claude_codepasses--mcp-config+--allowedTools. In-process so the live arbitrator + handlers are reachable. (The liveclaude -p→tool round-trip needs a real-CLI smoke test; the machinery is unit-tested in-process.)
- emit-in-response (default): the worker returns a
- Both workflows. Specialist-pipeline (the review consumer) and roadmap
(the conductor services callable requests in its
await-requeststep) can both do inter-team requests. Handlers bind to a specialty (tool-less) or an objective (tool-enabled). A multi-owned command can name a"primary"team to lead the shared run. - Gating. Entirely opt-in: with no team declaring a contract, a run is
unchanged. Wiring lives in
engine/conductor/session_convener.py(provider registration, routing, the MCP bridge),engine/conductor/command_conductor.py(support pool, primary tiebreaker),review/reviewer.py(the consumer ask loop / MCP path), andengine/conductor/conductor.py(roadmap drain). The request facility itself is ontoolkit/arbitrator/arbitrator.py. Live today: the devteam code-review objectiveconsumes research.factcheck, which researchteamprovides.
Configuration precedence — every knob, one table (highest wins). The review found nine scattered surfaces with precedence documented only in docstrings; this table is the SSOT.
| Setting | Resolution order (highest first) |
|---|---|
| LLM list | --llms flag → team.json llms pin → teams.json defaults.llms (myteams config --default-llms set …) → built-in ("claude",) |
| Primary LLM | --primary-llm flag → persisted state.json last_primary_llm → first of the resolved LLM list → built-in claude; then a per-team swap if that primary is not in the team's own list |
| Worker model/effort | objective effort block (--effort picks among its choices) → specialty frontmatter logical_model → engine _EFFORT_MAP |
| Verifier model | --verifier-model → objective verify.model_by_effort[effort] → engine _VERIFIER_EFFORT_MAP |
| Review token budget | --token-budget → DEFAULT_REVIEW_TOKEN_BUDGET (120k) |
| Build-loop spend wall | MYTEAMS_BUILD_TOKEN_BUDGET env → 10,000,000 |
| Any-run spend wall | MYTEAMS_RUN_TOKEN_BUDGET env (opt-in; RunBlocked past it) |
| Ask transport | MYTEAMS_ASK_TRANSPORT=mcp env → emit-in-response |
| MCP host gate | MCP_ALLOWED_HOSTS env (warn-only when unset) |
| Stepping | generated skills pass --step (default); raw CLI opt-in via --step/--iter/--step-size |
| Claude dispatch credential | CLAUDE_CODE_OAUTH_TOKEN in the myteams process env (inherited) → OS-keychain token (myteams config auth set-token claude, injected into each claude -p child) → (keychain present but unreadable → surfaced, not silently skipped) → the claude CLI's own stored login. Resolved by claude_auth.resolve_credential with a short-TTL cached keychain read (collapses a fan-out to one read; a long-lived host re-reads within the TTL to pick up a rotated token) |
Env vars in one place: MYTEAMS_BUILD_TOKEN_BUDGET, MYTEAMS_RUN_TOKEN_BUDGET,
MYTEAMS_BUILD_METRICS_DB, MYTEAMS_RUN_REPORT_DIR, MYTEAMS_BUILD_REPORT_DIR,
MYTEAMS_ASK_TRANSPORT, MCP_ALLOWED_HOSTS.
State roots: ~/.myteams/ (engine, registry teams/teams.json, config.json
MCP servers, user contracts/ — preserved by uninstall), ~/.claude/skills/ myagenticteams/ (generated plugin skills), and each team's home.dir (e.g.
~/.agentic-dev-team/: run dirs, build-metrics/loop-metrics.db,
team-insight/insight.db). runs prune is the retention verb for run dirs.
Engine store: the arbitrator persists sessions, state, gates, requests, events, and the roadmap graph through a swappable Storage provider. The bundled backend is SqliteBackend (pipeline/engine/source/toolkit/storage_provider/sqlite_storage.py); its live schema is the sibling schema.sql. The DB path is per-session — supplied via the run's home directory — not a single canonical file. (The Gen-1 ~/.agentic-cookbook/dev-team/dev-team.db is no longer used by the engine.)
The system is assembled at install time from three source buckets at the
repo root — teams/ (the standalone-team pool, data; each team may carry a
skill.md fragment), skill/plugin/ (the myagenticteams plugin scaffold —
manifest + the static roster, config, and code-review-by-myagenticteams skills), and pipeline/ (the generic
engine) — plus claude/ (legacy skill). install copies pipeline/ to
~/.myteams/pipeline/, installs each bundled team via team install (recorded by
identifier, with an enabled flag, into ~/.myteams/teams/teams.json), assembles
the plugin at ~/.claude/skills/myagenticteams/, and generates one per-team skill
there from the live registry (see The myteams registry, above). A team's
references/ bodies are committed, vendored from the cookbook submodule at
author time (pipeline.engine.source.toolkit.reference_vendor, run by
create-specialist / update-team and committed alongside the manifest edit) —
install and the runtime need no cookbook checkout.
skill/plugin/ # the `myagenticteams` plugin scaffold (manifest + static roster/config/code-review-by-myagenticteams skills)
# per-team skills/<team>/ are generated from the live registry at install
teams/ # the standalone-team pool — flat, each self-contained, by identifier (each may ship skill.md)
devteam/ # team.json (identifier + planner + commands + attribution) + team.md + specialists/ + specialities/<area>/ + consulting/ + team-leads/ + reference-manifest.json + references/ (committed, vendored at author time)
projectteam/ # team.json + team.md + specialists/ + specialities/<area>/ + objectives/
researchteam/ # team.json + team.md + specialists/ + specialities/<area>/ + team-leads/ + consulting/ + llm-guides/ + objectives/
storyteam/ # team.json + team.md + specialists/ + specialities/<area>/ + team-leads/ + consulting/ + llm-guides/ + objectives/narrate.json (narratives-corpus → per-project + ecosystem stories)
pipeline/ # the generic engine — names no team, imports no team
engine/source/
engine/
myteams-cli.py # the CLI — manage the team registry; vend + route the enabled teams' command union
conductor/ # routing + loading (the team-lead boundary lives here)
command_conductor.py # run_prompt: dispatch a command pinned to the named team (only_team)
skill_renderer.py # render the single skill from the template + the live registry (shared by install + the CLI's team mutations)
objective_planner.py # the generic per-team planner: command → objective spec → workflow
team_loader.py # teams/<name>/ → TeamManifest (area-pool resolution); load_execution_planner; load_playbook
registry.py # teams.json access: load + the per-team `enabled` flag
objective_loader.py # objectives/<name>.json → ObjectiveSpec
team_lead_runner.py # reviewable + scope validation/resolution (engine's sole specialist-selection seam)
roadmap_factory.py / generic_realizer.py / plan_realizer.py / user_interaction.py
execution/ # the shared execution kernel (formerly review/-resident):
# dispatching (dispatch bookkeeping), progress/debug_capture (event rendering),
# changeset + repo_guidance (input shaping), publish/publish_run (findings publish)
workflows/ # the workflow shapes objectives run on
specialist_pipeline.py # the specialist-pipeline workflow (code-review, finish-review, research)
scopes.py # scope catalog (team.json) + CLI flags/prompt — the config-driven review pre-filter
roadmap.py # the roadmap workflow (schedule)
authoring.py # the authoring workflow (cookbookteam: recipe/principle/guideline/project new) — one tool-enabled claude -p agent synthesises a document from the team's playbook + sealed references
builtins.py # engine utilities: run-log / findings-read / plan / write-loop-status / loop-stats / build / recipe-build / project-new / project-build (config is a CLI builtin, not here)
hooks.py / input_adapters.py / anchors.py / unit_schema.py
# input_adapters: git-changeset, topic, changeset-with-handoff (finish-review: diff + a prior-findings handoff), changeset-with-contract (conformance-review: diff + an acceptance contract)
integration_surface/ # protocol events + host renderers + projection
rollcall/ # discover + ping every team role
specialty/ # ConductorSpecialty protocol
llm_guides/ # guide-pack loading (load_guide)
review/ # the code-review capability (shared by any team that declares it)
orchestrator.py / selection.py (bottom-up interest) / reviewer.py / verifier.py / command.py
changeset.py / confidence_scorer.py / report_writer.py / run_log.py / run_stats.py / prompts/
merge_gate.py # the generic merge-readiness gate (finish-review + conformance-review)
acceptance_contract.py # compat alias → engine/execution/acceptance_contract.py (objective-neutral: conformance + finish-review + build-loop + cookbook)
loop_metrics.py / loop_status.py / loop_stats.py / loop_guard.py
# the build loop: cross-run metrics store, per-iteration instrument+signal, convergence analysis, spend guard/hook
toolkit/ # generic services (name no team)
arbitrator/ # Arbitrator: sessions, state, gates, requests, events, roadmap graph
storage_provider/ # Storage protocol + SqliteBackend (sqlite_storage.py) + schema.sql (live schema)
llm_providers/ # real provider dispatch + MockDispatcher; dispatch_decorators.py: ToolGrantingDispatcher (worker_tools grant), FallbackDispatcher (barrier → primary-LLM retry → RunBlocked halt)
nl_router.py / team_home.py
contract/ # cross-cutting contracts: dispatch.py (Claim), executor/, llm_guides/
claude/ # legacy only
skills/legacy-dev-team/ # Gen-1 dev-team skill + assets — reference-only, slated for deletion
# Repo root
install / uninstall # core install (engine + CLI + empty skill) then `team install` each bundled team; --core-only stops after the core
.claude/ # Claude Code local config, rules, dev skills (incl. create/lint-specialist, update-team)
external/agenticcookbook/ # cookbook reference pool (submodule)
docs/
architecture.md # THIS FILE
planning/ # Design specs and decision history
superpowers/ # Brainstorming specs and implementation plans
testing/
unit/ # Unit tests + harness (tests/ + harness/)
functional/ # Functional tests + harness (real-LLM gated by AGENTIC_REAL_LLM_SMOKE=1)
fixtures/ # Shared fixtures
- DB schema:
.claude/rules/db-schema-design.md— no blobs, no counts, meaningful names, searchable columns - Specialist format:
docs/specialist-spec.md— required sections, manifest format, specialty-team file format - Performance: Shell scripts for deterministic work, model selection for subagents, progressive disclosure
- Storage abstraction: Arbitrator for communication, project-storage-provider for PM data — both backend-swappable
- Naming:
-cookbook-projectsuffix for code specs, verb-based commands, symmetriccreate-X-from-Y