This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Guidance for working in this repo (kv-skills — Maestro, the KeyValue AI-SDLC pack).
A deterministic workflow orchestrator for agentic coding harnesses (Claude Code, Cursor, Codex), shipped with an example AI-SDLC pack. The engine is the product; the SDLC skills + workflows are a worked example meant to be forked or replaced. The load-bearing separation: the workflow owns what/where/when (instruction, inputs, artifact path, output fields, ordering) and a skill owns only how — so any agent step's skill can be swapped (ours, the user's, or a third party's like Obra/Superpowers) without touching the graph, and the engine injects the paths/inputs/contract into each subagent prompt at runtime. It ships:
- AI skills (
skills/**/SKILL.md) — the example pack, one per SDLC step (plan, design, implement, review, QA…) plusskills/maestro, the lead agent. Markdown prompts with YAML frontmatter (name,description,tags,allowed-tools). Devs invoke them as slash commands (/plan,/backend-impl, …) or Maestro invokes them via subagents. Skills are portable: they must NOT hardcode artifact paths or their position in a pipeline — that lives in the workflow node. Two kinds live here beyond the SDLC steps: bootstrap skills (detect-stack,maestro-init,build-knowledge,retrospect) and per-stack reference skills (golang-patterns,react-testing,springboot-security, …) — the "how" style guides. See the stack-tag note below. Source layout — theskills/tree is organized into category folders (skills/maestrothe lead agent;skills/maestro-discussits side-chat partner for one interview question;skills/core/sdlc/*the SDLC steps;skills/core/bootstrap/*; andskills/stacks/<stack>/*grouped bystack:tag) so new skills have an obvious home. A skill's IDENTITY is its frontmattername(unique across the tree), never its path — workflows pin by name, harness discovery and stack filtering key offname/tags:. The folder is organization only;install.shflattens the tree on install — the installed.claude/skills/<name>/,.cursor/skills/<name>/, or.agents/skills/<name>/stays one dir per skill. - Stack tags & per-stack packs. A skill/agent tagged
stack:<x>(e.g.stack:go,stack:react) is a per-stack pack; anything WITHOUT astack:tag is core and always installs.install.sh --stack go,reactinstalls core + only those stacks;/maestro-initrunsdetect-stackto pick the filter from the repo automatically. The per-stack reference skills + per-stack agents (*-reviewer) are generated byscripts/import_ecc.py(only their frontmatter is normalised —tags/allowed-tools); they are NOT pinned in any workflow node, so they are exempt from the SDLC-step contract (no artifact path, no Output-contract requirement). - The engine (
engine/) — stdlib-only Python.maestroctl.pyis the CLI the lead agent shells out to:validate · init · next · complete · complete-batch · gate-record · fail · reset · rebase · status · todo · graph · note · upgrade-run · correction-record. The resolver serves exactly ONE next action as JSON; the LLM never interprets the graph and never edits state. No dependencies, ever. - Workflows (
workflows/*.yaml) — the example pack (sdlc-main,design,impl,qa) in the custom spec. Large flows are split into small stage children so no graph is overwhelming in the builder (target: ≤ ~12 nodes each):sdlc-maincomposesdesign→design-review→delivery→archive;designcomposesdesign-import/design-intake/design-prd/design-map/design-hld/design-lld/design-contract. A gate decision that leaves its stage ends the child and is reported in itsoutputs:; the parent routes on it (gate-driven, so not visit-capped). Spec (docs/workflow-spec.md, machine contractengine/schemas/workflow.schema.json). - Agents (
agents/*.md) — Claude Code subagent definitions (tools + role prompt) installed into.claude/agents/. Codex uses its native subagents, driven by the exact prompt and named skill served by the engine; Cursor uses inline mode. - The builder UI (
ui/builder.html) — single-file visual workflow editor, plus a read-only Runs view that reads.maestro/runs/<slug>/state.yamland colours each run's graph by step status (done/running/failed/pending). Works offline fromfile://(browser File System API) OR folder-aware behindengine/ui_server.pywhen served bymaestro ui(probes/api/health; then loads runs/workflows over HTTP — see the branch points guarded bywindow.MAESTRO_SERVER).ui/embed.pyrefreshes its embedded schema + lint-rule blocks. bin/maestro— a repo-local dev wrapperinstall.shdrops at the consumer repo root as./maestro. Exactly two subcommands:maestro ui(serve the builder viaengine/ui_server.py) andmaestro install(proxy toinstall.sh). It is dev tooling only — it NEVER drives a run or writes run state; the Maestro skill + engine stay the sole execution path and only state writer.install.sh— the installer. There is deliberately NO execution CLI and NO config file: workflows only ever run through the Maestro skill + engine (themaestrobin above is viewer/installer tooling, not an executor). Artifact paths, inputs and the output contract are declared on the workflow node and injected into the subagent prompt by the engine — skills do NOT hardcode paths (that keeps them swappable). Accepts an optional--stack go,reactfilter: it copies the core skills/agents plus only those whose frontmattertags:carry a matchingstack:<x>token (parses thetags:line directly — no YAML dep). No flag /--stack all= everything. Skills/agents are the only filtered trees; commands and the runtime files always copy whole. Install layout: everything Maestro ships nests under one.maestro/parent in the consumer repo —.maestro/{engine,ui}(regenerated),.maestro/{workflows,docs}(committed), plus the engine-created.maestro/memory/and.maestro/runs/<slug>/. Only themaestrowrapper andinstall.shsit at the repo root. Workflow files reference sibling workflows and engine scripts by the installed path (.maestro/workflows/…,.maestro/engine/…), so this pack repo — whose source lives atengine/,workflows/,ui/,docs/at root — mirrors the installed layout via committed symlinks (.maestro/engine → ../engine, etc.) sovalidate .maestro/workflows/…and dogfood runs resolve here too. The e2e test builds the same layout in a tmp dir.
There is no headless runner and no API-key dependency — workflows execute inside the user's interactive session (Claude Code, Cursor, Codex). Conductor is gone.
maestroctl.py is a thin CLI; the logic lives in modules it imports. Where things are
(so you don't grep blind):
resolver.py(by far the largest — the centerpiece) — computes the ONE next action, and owns nearly all state mutation: frontier/cursors, resume rules,whenevaluation, visit counting + cap enforcement, back-edge cascade reset (_cascade_reset/_ancestors— deliberate under-reset; read the docstring before touching it), parallel branch joins, subworkflow entry, placeholder substitution (resolve_text/ref/value,missing_ok=Truefor prompt / route-condition / step-input rendering so an unset step ref renders empty rather than aborting — butmodel:/max_visits:and typed init inputs resolve strictly), andrender_agent_prompt(assembles instruction + inputs + skill pin + artifact list + the last-line-JSON output contract into the agent prompt). It also owns lead-executed agents (execution: lead) and durable interviews (record_interview), including validated dynamic question queues and cumulative decision artifacts.complete_step,record_gate,initlive here too.state.py— the ledger: load/save withfcntllock + atomic tmp/rename,step_entry, step wall-clock timing,sha256_file,new_state. The ONLY writer of.maestro/runs/<slug>/state.yaml.todo.py— the run's todo list, read-only: stages (subworkflow/parallel nodes) in order, the running stage expanded, optional stages only once entered, plus what the run is doing or waiting on the human for.maestroctl todoprints it;initand anynextwhose picture changed (digest inrun.todo_digest) carry it astodo, which the lead mirrors verbatim into the harness's task list. Engine-built so every surface shows the same thing.validate.py— schema check + graph lint (start/route-target existence, reachability, default-route-on-branches, placeholder resolvability, subworkflow depth, cycle lint).condctl.py— the ~4-form route-condition grammar (==,!=,in […], truthy); parses BEFORE substitution so values can't inject operators.norm()canonicalises bools/numbers; guards use== true/== false, never!= true(prose would slip through).wf.py— the zero-dep YAML-subset loader/emitter (see its own section below).oq_serve.py/oq_record.py— the open-questionsscript-node helpers (the stdout-JSON-becomes-routable-outputs pattern);validate_tasks.py/validate_open_questions.py— standalone artifact-format validators.validate_prd.py/validate_prd_questions.py/validate_hld.py/validate_lld.py— deterministic PRD, HLD, and repository LLD structure, acceptance-ID, readability, question-queue, and deferred-ledger checks. They never make product or architecture decisions.lld_repo_pool.py/workflows/repo-lld.yaml— create one child run per selected repo, run a lead-owned bounded clarification and single-write LLD flow, publish only human-approved LLDs back to the parent, bind approvals to the current HLD hash, selectively reopen only the repositories named by an approved architecture correction, and let the parent join only after all repo-owned ledgers are complete.implementation_pool.py— separate approved design scope from the current implementation batch, preserve verified repository handoffs across batches, keep unselected repositories as durable deferred work, and build the QA manifest only from exact verified worktree commits.run_upgrade.py— inspect and apply the explicit one-time semantic run-format upgrade. Backs up old state/artifacts, stamps compatible ledgers, and rebuilds legacy ledgers from validated PRD/HLD plus independently re-approved imported LLD drafts; never maps removed steps. Format 3 re-nests a ledger's inline steps under the stage child that now declares them (design/author_hld→design/hld/author_hld,qa/qa_run→delivery/qa/qa_run, ids unchanged) so the exact cursor survives.entry_serve.py— the route-onlyscriptnode a multi-entry child workflow starts with; itsroutes:branch on the child's inputs (e.g. design-import enters at the baseline approval or the repair gate). Does no work, never fails.design_corrections.py— immutable per-correction approval receipts, deterministic effective-design rendering for downstream consumers, repository-batch receipts for the architecture-review LLD-only path, and validated archive-time folding into final document copies without mutating original hash-bound approvals.workspace_sync.py— parallel current-upstream fetch/status, tamper-evident fast-forward-only apply, living-doc commit provenance (.maestro/index/), and per-feature exact-SHA locks. It owns sync safety; the knowledge skill owns only doc-writing judgement.install_workflows.py—install.sh's workflow upgrader: per file, add / replace an byte-identical stock copy (strict hash, CRLF-normalised, against.maestro/workflow-manifest.jsonandengine/workflow_releases.json, every version ever shipped) / keep a local edit and write<name>.upstream. Never writes to an edited workflow.workflow_releases.jsonis generated byscripts/gen_workflow_releases.pyfrom git history — rerun it after editingworkflows/*.ui_server.py— the stdlibhttp.serverbehindmaestro ui: servesbuilder.htmland exposes the repo (/api/workflowsrecursively lists every YAML (skipping host, vendored andtestdata/fixture dirs), tagging maestro workflows;/api/workflowGET+PUT;/api/runs;/api/health) so the builder is folder-aware. Reusesstate.load+wf.load_file; binds 127.0.0.1 only; path-traversal guarded (_safe_repo_yaml); reads any YAML under root except.git; writes any.yamlunder root EXCEPT.git/.maestro(run state stays engine-only) — workflow SOURCE only.
The action loop (engine ↔ lead agent): maestroctl next prints exactly ONE action as
JSON — run_agent, run_agents (a parallel wave), run_lead, run_script,
ask_interview, ask_interview_batch, ask_gate, ask_input, wait, done, or failed. Every
serving call first runs engine-owned script steps (python3 .maestro/engine/*.py) itself, outside
the ledger lock, and lists them in auto_scripts; only other scripts reach the lead. Served worker
steps are claimed (dispatched_at), so later calls return only newly ready work (ready workers
before gates) or wait; init on resume releases claims, next --peek re-reads. run_lead
is the explicit, bounded exception to worker dispatch; interview actions record one answer or an
atomic batch without dropping unresolved questions. Interview discussions never run in the lead's
context: interview-brief renders a read-only brief for an isolated discussant subagent (the lead
only relays) or a /maestro-discuss side session that records with --peek (no worker claim).
Input-bearing gate choices deliberately become a second durable ask_input action before
routing. The lead reports back via complete, complete-batch (a whole wave in one call),
interview-record, interview-record-batch, gate-record,
gate-input-record, or fail. The LLM never interprets the graph or writes state.
python3 engine/tests/run_all.py # the whole engine suite, incl. the no-LLM
# full-SDLC simulation (the correctness proof)
python3 testdata/test_ui_schema_sync.py # UI <-> engine anti-drift + cross-parser test
python3 engine/maestroctl.py validate workflows/sdlc-main.yaml # recursive lint
python3 engine/validate_tasks.py testdata/tasks.valid.json
python3 engine/validate_open_questions.py testdata/open-questions.valid.jsonRun one test: python3 engine/tests/test_resolver.py BackEdgeTest.test_happy_path_no_loop.
The invalid-* fixtures in testdata/ and testdata/workflows/ exist to prove the
validators reject the hard cases — a validator that accepts one is a regression.
After editing engine/schemas/workflow.schema.json or any workflows/*.yaml, run
python3 ui/embed.py (the sync test fails otherwise). After editing any workflows/*, also run
python3 scripts/gen_workflow_releases.py (test_install_workflows fails otherwise).
Full spec: docs/workflow-spec.md. The load-bearing rules:
- Minimal authoring defaults: only
nodes:is required —versiondefaults to 1,startto the first node, nodetypetoagent, omitted routing tonext: end. Keep these defaults working; they are the "simple workflow creation" promise. - 6 node types:
agent(instruction required, optionalexecution: lead,skill:optional pin — omitted = harness auto-discovery),interview(ordered, durable human clarification with an engine-written JSON artifact),gate(options ARE the edges),script(stdout JSON becomes routable outputs — theoq_servepattern),parallel(branches may contain agent/gate/script/subworkflow, never nested parallel),subworkflow(child steps namespacedparent/childin state; depth ≤ 4). - Loops are back-edges, not a construct: any route may target an earlier node.
Entering a
donenode cascade-resets it plus reachable done steps. Every node has a visit cap (max_visits→defaults.max_visits→ 10); exceeding it routes toon_exhausted(default: a synthesized ask-gate). A route taken through a REAL gate option bypasses the cap — the human is the loop bound. - Placeholders are pure text substitution (
${inputs.x},${steps.id.outputs.f},${steps.id.branches.k.outputs.f};${config.path}reads an optional user-created maestro.config.yaml — the pack ships none) — no templating engine.when:conditions support only==,!=, truthy,in [a, b](engine/condctl.py). - Artifacts gate completion:
completerefuses to mark an agent step done unless itsartifact:files exist non-empty ("proof, not promises"). - Only the engine writes
.maestro/runs/<slug>/state.yaml(fcntl-locked, atomic). The workflow file's sha256 is recorded; edits mid-run halt untilrebaseorreset.
Zero-dep hand-rolled loader/emitter. Supported: block maps/lists, one-line flow
collections, plain/quoted scalars, |/|- literals, comments. Rejected by design:
anchors, tags, folded scalars (>), multi-doc. The UI's vendored js-yaml must emit
only this subset (lineWidth: -1 prevents folding); the cross-parser test in
testdata/test_ui_schema_sync.py enforces agreement. If you touch wf.py, run the
whole suite — the parser underpins everything.
- Editing a skill = editing its
SKILL.md. Keep the frontmatter contract and each skill's Standards, Safety and## Output contractsections. Skills describe only how to do the job — they must NOT hardcode artifact paths or pipeline sequencing ("runs after X", "in parallel with Y"). The workflow node supplies the artifact path, inputs and output fields; the engine renders them into the prompt (render_agent_promptinresolver.py). A step skill must NOT name Maestro's.maestro/runs/<slug>/…layout at all (not even as a fallback — that couples a swappable skill to this orchestrator); when a standalone/skillrun has no path from its instructions, tell it to write to "a sensible path you choose (and tell the user where)". A step skill also must NOT restate the engine's dispatch contract (the "last-line JSON, short scalars" instruction) —render_agent_promptinjects that into every subagent prompt at runtime; the skill's## Output contractstates only the conceptual output FIELD NAMES. Those field names ARE a machine contract —testdata/test_workflow_skill_contracts.pychecks every workflow node's declared outputs appear in its pinned skill. (skills/maestrois exempt from all of this — it IS the Maestro lead agent, not a swappable step.) - A skill is never the source of truth for workflow functionality. Skills are
swappable (ours, the user's, a third party's), so any logic a workflow's guarantees
depend on — counting, thresholds, promotion/gating rules, dedup, state transitions,
anything deterministic and load-bearing — must live in the engine (a stdlib module
invoked as a
scriptnode, e.g.engine/mem_consolidate.py/engine/oq_serve.py), NOT inside aSKILL.md. The skill supplies only judgement (what to write), emitting a structured artifact the engine consumes; swapping the skill must not be able to change the functionality. Litmus test before putting behaviour in a skill: if someone replaced this skill with their own, would a guarantee the workflow relies on break? If yes, it belongs in the engine. (This is why memory consolidation isengine/mem_consolidate.py, not the retiredconsolidate-memoryskill.) skills/is the source of truth for skill prose..claude/skills/,.cursor/skills/,.agents/skills/etc. are installed copies (gitignored, regenerated byinstall.sh) — never edit those. The source is organized into category folders (core/sdlc,core/bootstrap,stacks/<stack>, plusmaestro); a new skill goes in the folder matching its role (stacks/<stack>= itsstack:tag). Install flattens all of them into the selected host's skills directory, so the path is organization only — never reference a skill by folder path, only byname.skills/maestro/SKILL.mdis the orchestrator's contract. Its hard rules (never edit state, never read artifacts into context, never skip gates, dispatch verbatim) are what keep long runs reliable — change them only deliberately.- Workflows name skills directly (
skill: plan); placeholders are allowed in the skill name (skill: "${inputs.stack}-implement"replaces the old dispatch glue). Model choice: per-nodemodel:→ workflowdefaults.model→haiku; values pass to the harness as-is (haiku/sonnet/opus). - The workflow merge-for-test / contract-check / archive script steps are POC stubs
(
echo+ exit 0) — a downstream user wires them to their real runners. .maestro/runs/<slug>/holds a feature's requirement input and all generated artifacts + the state ledger. Git-tracked on purpose.install.shusesset -uo pipefail(not-e) on purpose — one failed skill install must not abort the rest.
Repo/umbrella-level, git-tracked, shared across slugs. Three tiers: incoming/<slug>.json
(per-run structured drops, race-free), candidates/<domain>.json (the engine-owned ledger:
lessons + distinct source slugs, NOT injected), knowledge/<domain>.md (engine-RENDERED,
injected via ${memory.knowledge.<domain>}). A lesson promotes into knowledge only after
≥3 distinct runs corroborate it (bootstrap lessons are authoritative and render
immediately).
There is a second knowledge surface: the committed living docs under docs/ —
per-domain docs/technical/<domain>.md + docs/functional/<domain>.md, and
docs/architecture.md (Mermaid + how services connect). build-knowledge authors it from
the codebase; retrospect refreshes touched domains after a feature. Human-facing, read by
subagents as ordinary workspace context. It coexists with the injected lessons store.
The load-bearing separation (swappable skills): the consolidation functionality —
counting distinct runs, the threshold, promotion, pruning, rendering — is deterministic
engine code in engine/mem_consolidate.py, invoked as a script node. Skills
(build-knowledge, retrospect) only PRODUCE judgement outputs — the docs/ knowledge base
and the structured incoming/*.json lessons; swapping a skill cannot change how corroboration
works. Reads: the resolver only READS the lessons knowledge tier, once, at init, freezing a
per-run memory-snapshot.json (engine/memory.py). The pre-merge archive phase of
sdlc-main.yaml runs the harvest by default. Full conventions: docs/memory.md.
Docs here are dense and deliberately worded (README, config comments, SKILL bodies, docs/workflow-spec.md). Match the existing terse, precise register; keep the ASCII flow diagrams and tables in sync when the flow changes.