Skip to content

Latest commit

 

History

History
880 lines (781 loc) · 57.6 KB

architecture.md

File metadata and controls

880 lines (781 loc) · 57.6 KB

Dev-Team Architecture

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 the project-guidelines skill and the project-review conformance gate. This document remains the home for the full rationale behind each invariant.

One runtime: the roadmap runtime. Conductor.run_roadmap drives a roadmap graph through WhatsNextSpecialty + a realizer. Teams are declared as markdown (teams/<name>/), loaded into a TeamManifest, and run through the conductor; the scheduling specialty decides each step. Entry point: the /team-pipeline skill (the atp CLI). See planning/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 bespoke build_roadmap + realize (and optional mock_responses). The engine names no team and imports no team: dropping a team in or deleting one touches no engine code.

The former TeamPlaybook state-machine runtime (Conductor.run) and the engine-side services/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 is planning/2026-04-11-conductor-architecture.md; it remains the reference for inter-team requests, dispatcher semantics, and the event stream.

Roadmap Runtime (current work)

/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 — ConductorSpecialty protocol, 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_user for interview-style realizers.
  • engine/myteams-cli.py — the myteams CLI (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 myteams registry

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.md for 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's name and carrying its identifier, blurb, description, commands summary, attribution, and an enabled flag. team install writes the entry (enabled); team enable/team disable flip 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_prompt with its only_team pin, on the generic objective_planner). A bare myteams <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 the team run subcommand 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 myagenticteams plugin, rendered from the live registry. Install assembles a skills-directory plugin at ~/.claude/skills/myagenticteams/ (manifest + the static roster, config, and code-review-by-myagenticteams skills — the last a discoverable shortcut for myteams devteam review code --risk) and generates one skill per enabled team (skills/<team>/SKILL.md, surfacing as /myagenticteams:<team>) via the shared skill_renderer.render_plugin_skills. Each per-team skill lists that team's commands, pins dispatch with myteams <team>, and folds in the team's optional teams/<team>/skill.md fragment (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 monolithic myteams skill — it was retired in favour of this plugin; the myteams CLI 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 same myteams <team> <command> form (one level deep). This is skill-level delegation, distinct from the in-pipeline provides / consumes arbitrator 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/.

team.json blocks — where the team-specific strings live

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 in teams.json at team install time.
  • blurb — a one-line summary of the team, distinct from the longer description. Shown by team list alongside attribution and enabled/disabled status.
  • commands — the command surface: name, optional canonical id (keys internal dispatch and the llm-guides dir; e.g. code-review → review), optional aliases, description. Loaded as a CommandVocabulary via the engine's team_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) and urls, surfaced by team list and recorded in the registry so it renders rather than showing blank.
  • nl_router — opt-in freeform-prompt routing: commands_doc and argv_examples line 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 optional reviews_remote (the git remote the output repo is cloned from and pushed to). Read by the engine's toolkit/team_home.py; no block → no home, and code-review refuses 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 onto TeamManifest by team_loader. Applied on both paths via engine/execution/repo_guidance.py (the acceptance-contract fold pattern): in review (specialist_pipeline) as a shared context=True unit in every reviewer's slice; in roadmap planning (roadmap + generic_realizer) appended to each node's worker_focus, with a gated --repo surfaced 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. devteam sets it to project-guidelines — folded into its reviews by the engine, and (because devteam's plan code authoring is skill-driven, not a headless dispatch) read by teams/devteam/skill.md when 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 --llms flag wins for every team; otherwise a team's own llms pin in team.json is used; failing that, the global default (defaults.llms in teams.json, set with myteams config --default-llms set <list>, falling back to the built-in claude — claude-only for now; codex is registered and usable but off by default while the system is hardened, re-enabled with myteams config --default-llms set claude,codex). A team that needs specific providers (e.g. researchteam → claude only) declares "llms": ["claude"] in its team.json. Design: planning/2026-06-30-loop-engineering-resilience.md.
  • provides — the inter-team request kinds this team services (each: a namespaced kind, description, inline input_schema/response_schema, default_timeout_seconds, and a handler binding a specialty or objective). Read by team_loader.load_provided_kinds; registered on a run's session by session_convener. See Data Flow → Inter-team requests.
  • consumes — request kinds this team's agents may ask the arbitrator for (also allowed per-objective). Read by team_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"}; map selects finding fields → tool params via $.field). Read by team_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.

MCP server registry & findings 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).

Dispatch auth (claude token plumbing)

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 generic team_loader.load_consulting_sections.

The team subcommand

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

Interactive commands & the deliberation toolkit

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.

Code Review Pipeline (devteam — current)

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:

  1. 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 as pre-checks.txt.
  2. 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-file changed_lines magnitude), 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 as review.selection.* events with a structured failed_open flag.
  3. 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 a context_gap channel (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/name headers.
  4. Verify (verifier.py) — an independent verifier judges COVERAGE against each specialty's ## Verify criteria (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 verdict skipped, never a fake pass. Over-budget groups run in parallel.
  5. Merge + memory (arbitrator.upsert_finding, review_memory.py, reuse.py) — findings upsert into finding/finding_attribution keyed 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 (--fresh disables all of it).
  6. Consult + adjudicate (confidence_scorer.py) — the independent consultant re-scores every finding (verdict scored | not-applicable-as-abstention; it never sees finder scores) and, as the only judge who sees all findings together, marks duplicate_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.
  7. Gate + handoff (deterministic, before any narrative phase) — merge-readiness.json (blockers carry their criterion when anchored) and finish-review-handoff.json (findings carry the stable anchor alongside the display id).
  8. Analyze + report — issues-summary.md presents findings at validated confidence ≥ 80 with a labelled below-the-bar section (nothing dropped); per-LLM report-{llm}.md narratives run in PARALLEL at the cheap tier through the run's REAL arbitrator (tokens/failures recorded).
  9. Compare (compare.py, --compare, dev-only) — runs the real /code-review max and writes comparison.md opening 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.

finish-review — the step after the review

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.

conformance-review — the intent oracle

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 + a plan builtin) persists a contract to ~/.myteams/contracts/<team>/ (user data, outside the installed teams/ tree). Shared shape: engine/execution/acceptance_contract.py (objective-neutral — a compat alias remains at review/acceptance_contract.py).
  • Input — the changeset-with-contract adapter (ChangesetWithContractAdapter, consumes_handoff=True) folds the contract into INPUT_TEXT as a context=True unit (same mechanism finish-review uses to route a synthetic file into every worker's slice — no new prompt marker).
  • Roster — a dedicated always-on conformance specialist (walks the full criterion list — a coverage backstop) plus in-lane domain specialists, convened via the conformance scope; the skill passes --conformance. Objective: objectives/conformance-review.json (default_scope: conformance).
  • Findings + gate — findings are gaps anchored by criterion, carrying a status (unmet/divergent). The objective's gate block (block_field: status) makes the engine emit merge-readiness.json via the same generic review/merge_gate.py finish-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.

The build loop (myteams build)

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 — from loop_status.compare + the recorded history. An incomplete gate (the oracle run died; its merge-readiness gate says incomplete or is missing) never counts: record carries every criterion forward untouched (--proven still honored) and flags gate_incomplete so the driver re-runs the gate instead of trusting partial findings.
  • Iteration cap — the learned diminishing-returns inflection per n_criteria bucket when ≥3 completed runs of that size exist (loop_stats._recommended_for, ~90% resolved), else an arbitrary placeholder backstop_iterations(n)=max(3,3·n). The magic number self-replaces with data (stop_reason: diminishing-returns vs backstop).

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.

Stepped execution (--step / __MYTEAMS_NEXT__)

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 authoring workflow (cookbookteam)

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.

Selection & the team-lead boundary (normative)

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 id area/name (reverse-domain area.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_on floor) 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.

Data Flow

Inter-team requests (shared session)

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[] in team.json — each entry binds a namespaced kind to a handler ({specialist, speciality} today; {objective} is the heavier path) with inline input_schema / response_schema and a default_timeout_seconds. A consumer team declares consumes (team-wide in team.json and/or per-objective) — the kinds its agents may ask for.
  • The arbitrator is the conduit. register_request_kind / register_request_callable register each provider's kinds on the run's session; create_request validates (to_team, kind) at the boundary; run_request services one request under the kind's timeout, validates the response against response_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 from consumes ∩ the session's registered providers.
  • Group fan-out + barrier. A worker may ask several teams at once; create_request_group / run_request_group scatter the siblings (one shared group_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 an ask tool the worker calls MID-REASONING; claude_code passes --mcp-config + --allowedTools. In-process so the live arbitrator + handlers are reachable. (The live claude -p→tool round-trip needs a real-CLI smoke test; the machinery is unit-tested in-process.)
  • Both workflows. Specialist-pipeline (the review consumer) and roadmap (the conductor services callable requests in its await-request step) 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), and engine/conductor/conductor.py (roadmap drain). The request facility itself is on toolkit/arbitrator/arbitrator.py. Live today: the devteam code-review objective consumes research.factcheck, which researchteam provides.

Configuration

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

File Map

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

Design Rules

  • 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-project suffix for code specs, verb-based commands, symmetric create-X-from-Y