engagement-protocol
Skill4 starsChanged 29 days agoMITView on GitHub
[PROTOCOL] Canonical contract for agency engagement lifecycle: artefact schemas, state files, iteration budget, escalation rules, cross-domain handoff, observability. Single source of truth referenced by agency-intake, leads, managers, directors, and pipeline skills. Pure reference — no triggers, not invoked by the secretary as a router target.
- Reads credentials
- Deletes or force-pushes
In this repository48 other files it gives its agents.
- skills/dev-methodology/references/agents.mdAGENTS.md
- skills/skill-authoring/references/agents.mdAGENTS.md
- acceptance-protocolSkill
- agency-intakeSkill
- ai-visibility-methodologySkill
- banner-design-guideSkill
- benchmark-researchSkill
- brand-methodologySkill
- code-reviewingSkill
- code-writingSkill
- codex-bridgeSkill
- deploy-pipelineSkill
- design-assets-guideSkill
- design-system-methodologySkill
- design-task-decompositionSkill
- dev-methodologySkill
- docs-pipelineSkill
- documentation-writingSkill
- engagement-contractSkill
- feature-researchSkill
- human-voiceSkill
- infrastructure-setupSkill
- marketing-task-decompositionSkill
- post-deploy-qaSkill
- pre-deploy-qaSkill
- presentation-designSkill
- project-planningSkill
- prompt-engineeringSkill
- security-auditingSkill
- semantic-drift-methodologySkill
- seo-auditingSkill
- skill-authoringSkill
- skill-test-designSkill
- skill-testingSkill
- system-optimization-protocolSkill
- task-decompositionSkill
- tech-spec-planningSkill
- testing-methodologySkill
- ui-styling-guideSkill
- ui-ux-methodologySkill
- user-spec-planningSkill
- validation-pipelineSkill
- yandex-analytics-methodologySkill
- yandex-direct-guideSkill
- yandex-metrika-guideSkill
- yandex-search-guideSkill
- yandex-webmaster-guideSkill
- yandex-wordstat-guideSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
39 rules. About 0 are common, 18 are distinctive, and 21 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈18
Distinctive · estimate
This author's own choices. Worth a look.
21
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 28
Every agency engagement lives in an engagement/ directory under the project working directory.
- Distinctive: line 80
If you feel pressure to create one of these — the content already belongs in an existing whitelist file:
- Very specific: line 169
Before secretary hands off to lead, verify every tool in tools_required is reachable:
- Very specific: line 224
When A.contracts is enabled, before a task is implemented the owner proposes ≥1 checkable assertion per cited crit_ref (IN-BAND — writes no …
- Distinctive: line 412
If version equals current (4) — proceed normally.
- Very specific: line 413
If version is older than MIN_SUPPORTED (currently 3) — REJECT with engagement uses obsolete protocol.
- Very specific: line 414
If version is older but ≥ MIN_SUPPORTED — WARN.
- Distinctive: line 415
If version is newer than current script — REJECT.
- Very specific: line 440
If the engagement crosses thresholds during execution, lead promotes the size in scope-sync.md (S→M→L only, never demote).
- Distinctive: line 538
Pre-final-iteration trigger: before starting the last allowed iteration, escalate to user with current blockers.
- Distinctive: line 544
Never start an iteration beyond the hard limit without user authorisation.
- Very specific: line 550
If acceptance-log.md shows iter-N REJECT and iter-(N+1) REJECT with the same blocker (matched by criterion ID OR by reject-reason …
- Distinctive: line 552
STOP rework. Do not start iter-(N+2) on this criterion.
- Distinctive: line 558
Wait for secretary to either:
- Very specific: line 563
If lead skips this and just keeps reworking → director rejects with loop-to-intake skipped — repeating blocker not escalated.
- Distinctive: line 567
When dispatched mid-engagement to fix criteria, secretary has authority for non-scope edits without user touch (matching lead's Phase 1a …
- Very specific: line 592
When the loop is "criteria are wrong, not the work", route back to agency-intake for new/updated criteria.md instead of throwing more …
- Very specific: line 599
If any acceptance bar cannot be verified by the director with available tools (Docker not running, Playwright not installed, BD not …
- Very specific: line 632
Run python ~/.claude/scripts/engagement-archive.py --reason aborted (sanity checks skipped under abort flag).
- Very specific: line 642
Run python ~/.claude/scripts/engagement-archive.py (idempotent).
- Distinctive: line 653
look like "stalled" to operators waiting for first heartbeat
- Distinctive: line 724
Specialists: do NOT heartbeat — they are short-lived and return synchronously to the Workflow's wave step.
- Very specific: line 730
If validation-log.md's most recent Heartbeat is older than:
- Distinctive: line 741
Don't backfill heartbeats — write them when the phase actually completes, not in handoff.md after the fact.
- Distinctive: line 742
Don't heartbeat mid-phase — only at phase boundaries.
- Distinctive: line 743
Don't heartbeat from specialists — they have no phases.
- Very specific: line 783
Run python ~/.claude/scripts/token-budget.py engagement/ --json after each Phase-4+ heartbeat.
- Very specific: line 784
Run python ~/.claude/scripts/size-detect.py engagement/ --mode runtime --auto-promote --json after Phase 2 and every heartbeat.
- Very specific: line 886
Every director iteration appends one JSON line to ~/.claude/projects/{project}/metrics.jsonl:
- Distinctive: line 902
No live dashboard. Raw JSONL for retrospective analysis.
- Distinctive: line 921
When two sources of behavior disagree (e.g., agent body says X, loaded skill says Y.
- Very specific: line 959
If resolution=None, the event is blocking: caller MUST halt dispatch and escalate to the human judge before continuing.
- Distinctive: line 965
Every lead and dispatched specialist follows this contract — it keeps engagement state predictable across the dispatch tree:
- Very specific: line 967
Read engagement/criteria.md first.
- Very specific: line 968
Write only to engagement/executor-reports/{your-name}.md (single output channel, append-only across iterations with ## Iteration N …
- Very specific: line 987
If a specialist's deliverable doesn't satisfy its cited criteria, the per-task review returns rework and the SAME specialist is …
- Distinctive: line 999
Don't let the plan leave a criterion with no owning task — that's the gap the coverage mapping exists to prevent.
- Distinctive: line 1000
Don't accept a handoff whose §4a cross-validation is "all match" without the verbatim claim list (§4a rules apply).
- Very specific: line 1001
Don't paraphrase criteria into task prompts — crit_refs bind verbatim.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
engagement-protocol
This is the shared contract every agency role follows. The secretary captures intake, the lead orchestrates, the manager accepts. All of them read from and write to the artefacts defined here.
Naming note (2026-05-28 refactor): the per-engagement acceptor role is the
*-manageragent. Throughout this document, legacy mentions of "director" / "the director" denote that manager/acceptor role — read them as "manager". The*-directoragents are now a separate role — system-optimizers that improve the skill/agent corpus (seesystem-optimization-protocol); they never accept engagements. Script names (e.g.director-verdict-check.py) and theacceptance-log.mdowner column are unchanged.
Workflow-migration note (2026-06-05): engagement DISPATCH is now the
engagement-workflowWorkflow, not Task-tool routing. The*-leadis a planning agent (lead:plan) that returns a plan (tier / specialists / waves / tasks / validators); the Workflow SCRIPT then fans out specialists bytask.ownerin waves, consolidates each wave, runs validators, assembles the handoff, and stops at the human-gate seam. There is no mid-lead routing layer — the 8*-engineering/product/quality/brand/product-design/traffic/analytics/content-leadagents were retired (archived under_archive/agents/). Legacy mentions of "mid-lead", "top-lead → mid-lead → specialists", or "dispatch via the Task tool" below describe the retired topology; read them as the Workflow's wave dispatch — waves replace the mid-lead coordination layer (see §"Specialist dispatch — the workflow wave model" and §"Criteria propagation — workflow dispatch"). The human-gate (consilium → human directive →*-manageracceptance) is UNCHANGED.
Engagement = a directory
Every agency engagement lives in an engagement/ directory under the project working directory. Files in this directory are the engagement's state. Nothing about the engagement is conversational — if it matters, it's in a file.
Whitelist (allowed paths only)
The list below is closed. No files outside it may be created inside engagement/. Existing files outside this list are a protocol violation and the director MUST treat them as red flag (likely phantom-claims hideout).
engagement/
├── criteria.md # secretary, semi-immutable (additions/removals locked; rephrasing allowed via lead/secretary, see "Mutability rules")
├── scope-sync.md # director, optional, append-only after create
├── plan.md # lead, mutable until first dispatch, then frozen
├── specs/ # dev only — SDD feature folder (feature_path for spec agents): user-spec.md / tech-spec.md / research-verdict.md / code-research.md / project-brief.md (bootstrap) / logs/{userspec,techspec,research}/
├── tasks/*.md # ALL domains — atomic task files (mandatory L; recommended M with ≥2 specialists; skip S)
├── tasks/INDEX.md # optional manifest; mandatory if size: L
├── brand/ # design only — voice / tokens / guidelines
├── design-system/ # design only — components / tokens
├── ui/ # design only — screens / flows
├── executor-reports/ # specialists, one file per specialist, append-only
│ └── {specialist}.md
├── code-research.md # OPTIONAL — code-researcher output (existing dev projects, before tech-spec)
├── design-research.md # OPTIONAL — design-system-researcher output (existing design system, before UI/UX work)
├── brand-research.md # OPTIONAL — brand-context-researcher output (existing brand history, before brand/marketing work)
├── validation-log.md # lead, append-only — every validator run
├── validation-outputs/ # MANDATORY — JSON proof-of-run for each validator AND adversary role (incl. preliminary)
│ └── {validator|adversary-role}-iter-{N}[-preliminary]-{timestamp}.json
├── consilium-summary.md # auto-written by consilium-synth.py (M/L only); aggregates adversary findings
├── human-directive.md # MANDATORY on M/L between consilium and director verdict; human as supreme judge
├── codex-outputs/ # OPTIONAL — assets generated by Codex via mcp__codex__codex tool calls
│ └── {NN}-{slug}.{png|svg|jpg}
├── iteration # plain-text counter file (lead inc on handoff, director inc on reject)
├── screens/ # MANDATORY for ux_heavy=true — Playwright captures
│ └── {iteration}/{theme}/ # e.g. iter-1/dark/dashboard.png
├── traces/ # MANDATORY for ux_heavy=true — exercised flow logs (+ ux-review drive-mode evidence)
│ └── {iteration}/{flow}.json # producer flow traces; ALSO {iteration}/ux-review-{flow}.json (ux-review-authored in drive mode — S1)
├── deploy-log.md # dev only when deploy boundary crossed
├── docs-diff.md # docs pipeline only
├── handoff.md # lead, REPLACED per iteration
├── acceptance-log.md # director, append-only
├── engagement-reflections.md # manager, append-only on M/L verdict — ≤3 actionable lessons targeting skill/agent rules (see acceptance-protocol §"Per-engagement reflection")
└── events.jsonl # append-only event ledger — lifecycle facts emitted by lib/ledger.py: phase transitions, validator runs, interrupts, verdicts, reflections, replan (S3 replan hatch). Schema in scripts/lib/ledger.py. Forward-only; pre-ledger engagements get a synthetic legacy_import event at first write.Per-task contract (S2 — gated A.contracts, M/L only): tasks/{id}.md MAY additionally carry a ## Contract (co-signed) section (the per-task acceptance contract). On M/L the engine MAY create a contract-only tasks/{id}.md even when decompose=false — it is a whitelisted path, not a new artefact type, so this is not a whitelist violation. S-tier never creates contracts. Full schema below (§"tasks/{id}.md → ## Contract (co-signed)").
Forbidden (do NOT create)
These names appear in past engagements as "supporting docs". They duplicate whitelist content, hide phantom claims, and bloat the bookkeeping. Forbidden:
preview.md,compliance.md,review-log.md,visual-review.md,summary.md,notes.md,report.md,tldr.mdrework-N-brief.md,iter-N-summary.md,wave-N-recap.md, anything resembling a per-iteration sidecarfindings.md,issues.md,audit.mdoutsidevalidation-log.md
If you feel pressure to create one of these — the content already belongs in an existing whitelist file:
- "compliance" / "audit" / "review" →
validation-log.md - "preview" / "summary" / "report" →
handoff.md(it IS the summary) - "rework brief" → new
## Iteration Nsection insidevalidation-log.mdand rewrittenhandoff.md - "exercised flow" / "smoke run" →
traces/directory + reference insidehandoff.md§"Exercised"
Director's red-flag scan (per iteration): ls engagement/ — any file outside whitelist = REJECT with reason "out-of-whitelist artefact, content must move into canonical file".
Artefact schemas
criteria.md mutability rules (resolves the "immutable vs lead-sharpening" tension)
criteria.md is semi-immutable:
| Action | Allowed by | When | Audit |
| Adding a new "Done when" / "Deliverables" bullet | user only | scope-sync with director | director records in scope-sync.md |
| Removing a bullet that has independent user value | user only | scope-sync with director | director records in scope-sync.md |
| Changing measurement bar (e.g. "tests green" → "tests + 90% coverage") | user only | scope-sync with director | director records in scope-sync.md |
| Rephrasing for clarity (no scope change) | lead OR secretary autonomously | Phase 1a sharpening, or loop-to-intake | scope-sync.md auto-edit log |
| Dropping no-value filler (would-always-be-zero metric, never-read deliverable) | lead OR secretary autonomously | Phase 1a sharpening | scope-sync.md auto-edit log |
| Splitting compound bullet into atoms | lead OR secretary autonomously | Phase 1a sharpening | scope-sync.md auto-edit log |
Frontmatter size/ux_heavy promotion (S→M, false→minor→true) | lead | mid-engagement when scope grows | scope-sync.md note |
| Frontmatter demotion | NEVER (would discard accumulated rigour) | — | — |
The directive resolves the apparent contradiction: criteria are immutable to scope, mutable for clarity. Lead does not need user touch to drop a "ошибки за период (always 0)" filler — that's not scope reduction, that's noise removal. Lead does need user touch to drop "тесты после миграции" — that has independent value.
criteria.md (secretary)
---
engagement: {engagement-name}
created: {YYYY-MM-DD}
domain: marketing | dev | design
size: S | M | L # see Engagement size tier
ux_heavy: false | minor | true # see UX-heavy engagements
tools_required: [docker, playwright, postgres, ...] # see Pre-flight check
# sdd: bootstrap | spec | impl | off # OPTIONAL, dev only — see "SDD engagements" below
# spec_from: engagement-archived/{date}-{name}-spec/primary # REQUIRED when sdd: impl — the archive leaf holding the approved specs (primary-2, … on collision)
# protocol_version: 4 # OPTIONAL — kept for future migration story; not required today
---
# Acceptance criteria — {engagement-name} — {YYYY-MM-DD}
## Scope
{one paragraph, verbatim-aligned with user brief}
## Deliverables expected
- {deliverable 1 with measurable bar}
- {deliverable 2 with measurable bar}
## Done when
- {observable condition 1}
- {observable condition 2}
## Explicitly out of scope
- {thing excluded with one-line justification}
## Review mode
`lean` | `full` | `solo`
## Iteration budget
Guidance: 2 rework cycles, then user escalation. Counter is informational — escalate immediately on repeating critique. Hard cap 4.
## Cross-domain dependency (optional)
- Primary domain: {marketing | dev | design}
- Secondary domain: {marketing | dev | design} — invoked at phase {N} by primary leadSDD engagements (dev, sdd: frontmatter)
Spec-driven development runs an M/L dev feature or refactor as two engagements; a new project
first runs a bootstrap engagement, then its features take the same two (rationale: dev-methodology §"Spec-Driven Development"; conducting: agency-intake step 5c):
sdd: | Input | Output | Approval |
| (phase 0, no engagement run; new project only) | project interview in intake (project-planning Part A) | specs/project-brief.md, status: approved | the user, in chat |
bootstrap | approved project brief | PK docs + CLAUDE.md (project-planning Part B, dev-product-analyst), then infrastructure (dev-devops-engineer); code mode, no feature code | the tier's normal acceptance |
| (phase 1, no engagement run) | user interview in intake | specs/user-spec.md, AC ids AC-N, status: approved | the user, in chat |
spec | approved user-spec | specs/tech-spec.md, status: draft; artefact mode, one task owned by dev-tech-architect | the tier's normal acceptance; human directive PROCEED + dev-manager ACCEPT |
impl | approved user-spec + tech-spec (status: approved, approved_by set by intake) | code; tasks map one to one from tech-spec Implementation Tasks; Done-when bullets read crit-N (AC-k) / crit-N (TC-k) | the tier's normal acceptance |
off | — | ordinary engagement; the user's reason for skipping specs is under Scope | — |
Invariants: the approved user-spec and project brief change only through the user; status: approved on a
tech-spec is written only by intake when it opens {name}-impl; a deviation the impl lead is
forced into is recorded as SDD drift in scope-sync.md and adjudicated by the manager.
Pre-flight check (intake-time, blocking)
Before secretary hands off to lead, verify every tool in tools_required is reachable:
| Tool | Check | Action if missing |
docker | docker info exits 0 | Ask user to start Docker Desktop. Block until done. |
playwright | playwright binary present (project or npx playwright --version) | Ask user / lead to install. Block until done. |
postgres / db | connection string in .env and pg_isready (or equivalent) succeeds | Ask user to start DB. Block. |
node, python, bun, etc. | --version succeeds | Ask user to install. Block. |
Secrets (e.g. OPENAI_API_KEY) | env var or .env entry present (presence only — don't read value) | Ask user to add. Block. |
Pre-flight failure → secretary records the blocking tool in criteria.md "out of scope" line "BLOCKED at intake until {tool} available", and pauses engagement until user resolves. Do NOT hand off to lead with known-broken validation environment — that is the root cause of CONDITIONAL accepts that never resolved.
scope-sync.md (director, optional)
# Scope sync — {engagement-name} — {YYYY-MM-DD HH:MM}
## Criteria frozen as of
{timestamp}
## Ambiguities resolved
- Q: {question raised by director or lead}
- A: {resolution}
## Criteria locked by
{director-name}plan.md (lead)
# Engagement plan — {engagement-name}
## Shape
{engagement-type classification + rationale}
## Phases / waves
1. Wave / phase — owner (specialist agentType) — deliverable — dependencies
2. ...
## Validators planned
- {validator-name} on {artefact} — triggered by {rule}
## Cross-domain handoff (if applicable)
- Trigger: {condition}
- Secondary lead: {lead-agent-name}
- Artefact passed: {path}plan.md replan appendix (S3 — gated A.replan)
plan.md is "mutable until first dispatch, then frozen". ONE exception: when A.replan is enabled and a wave hard-stops, the engine appends a ## Replan N — {reason} section describing the re-planned remaining-waves graph, and emits one replan event to events.jsonl. This is the only permitted post-first-dispatch mutation of plan.md — append-only and audit-logged. Completed waves are locked and never re-scheduled; replanned task ids are suffixed -r{n} and must not collide with any prior id. Max one replan per run; a second hard-stop returns the existing error contract.
tasks/{id}.md → ## Contract (co-signed) (per-task contract, M/L only — S2)
When A.contracts is enabled, before a task is implemented the owner proposes ≥1 checkable assertion per cited crit_ref (IN-BAND — writes no engagement file), a neutral reviewer amends/accepts AND writes the converged contract into tasks/{id}.md, and the owner may contest. Format — one line per assertion: - {id} [{crit_ref}] ({status}): {assertion} — check_how: {check_how}.
Binds the per-task review rubric ONLY. A contract NEVER waives criteria.md or any PROTOCOL gate. If a deliverable satisfies every co-signed assertion yet violates a cited criterion, the per-task review verdict is STILL rework. The validate phase, consilium, and manager keep judging against criteria.md, not the contract. Every assertion MUST cite a crit-N parent; a parentless assertion is a scope-creep flag.
Reviewer's rubric-co-author role (analogous to the acceptor's scope-sync freeze): the co-signing reviewer has last word on the rubric; the owner has last word on contest. The per-task review JUDGE is code-reviewer (code mode) / reality-checker (artefact mode, contract path only; without A.contracts the artefact judge is the engine's default reviewer, which is why SDD spec engagements list reality-checker in plan.validators); the contract WRITER is reality-checker in both modes (it has Write — code-reviewer is read-only, so in code mode the writer and the later judge are deliberately different parties). Contested assertion ids are recorded in the per-task review verdict (and the owner's executor-report) and judged against criteria.md text DIRECTLY.
validation-log.md (lead, append-only)
Schema defined in validation-pipeline. Append one section per validator run.
handoff.md (lead, replaced per iteration)
The first section is ALWAYS the diff. The rest is supporting evidence. If a director reads only section 1, they should already know roughly what happened — prose is for context, not for the primary source of truth.
# Handoff — iteration {N} — {engagement-name} — {YYYY-MM-DD HH:MM}
## 1. Diff summary (PRIMARY)
For dev engagements, paste raw output:
- `git diff --stat {base}..HEAD` (file-level changes)
- `git log --oneline {base}..HEAD` (commit list)
For marketing/design engagements (no git diff available):
- Created files: list with sizes (`wc -l` or byte count)
- Modified files: before/after delta (lines added/removed or visual diff)
- Removed files: list with reason
This section is the **source of truth** for what changed. All other sections describe WHY and HOW WELL — they do not redefine what changed.
## 2. Deliverable manifest
- {artefact path 1} — {short description}
- {artefact path 2} — ...
## 3. Criteria trace
| # | Criterion | Status | Evidence (path + line/section) |
|---|---|---|---|
| 1 | {criterion text} | ✅ / ⚠️ / ❌ | {pointer} |
## 4. Executor reports
- {specialist-name} — `executor-reports/{specialist}.md`
### 4-pre. Specialist contract enforcement (lead duty)
Before composing §4 and §4a, lead verifies every dispatched specialist wrote to the canonical place:
```bash
# For each specialist dispatched:
ls engagement/executor-reports/{specialist-name}.md # must exist
ls engagement/ # no rogue filesIf a specialist:
- Wrote nothing → re-dispatch with reminder, do NOT pretend they reported.
- Wrote to wrong path (
/tmp/,engagement/notes.md, project-rootreport.md) → re-dispatch + delete wrong file. - Created a rogue file in
engagement/(e.g.findings-frontend.md) → consolidate into their canonical executor-report, delete rogue.
This enforcement is the lead's job, not the director's. Director treats engagement/ as authoritative — anything outside whitelist locations doesn't exist for acceptance purposes (and is a REJECT trigger).
4-iter. Iteration structure inside executor-reports
When the same specialist is re-dispatched on iter N+1 to address director feedback, they MUST append to their existing executor-report — NOT overwrite it. Each iteration in the report is structurally separated:
# {specialist-name} executor report
## Iteration 1 — 2026-05-06 14:30
### Criteria acknowledgement
- Addresses crit-1 (foo.py exports add)
### What was done
- Implemented add() at src/foo.py L4
- Added unit test in tests/test_foo.py L12-L20
### Cross-contract claims
- Returns int, not None on edge cases (frontend should expect int)
### Anti-pattern self-disclosure
- None
## Iteration 2 — 2026-05-07 11:15
### What changed since iter-1
- Director rejected: edge-case test for negative inputs missing.
- Added negative-input test at tests/test_foo.py L22-L30.
### Anti-pattern self-disclosure
- NoneLead enforces the format on dispatch: include in Task prompt explicit reminder to append ## Iteration {N} heading. Without iteration headings on iter ≥ 2, handoff-precheck flags executor-report-iteration-missing as REJECT trigger — earlier work would be silently overwritten and audit trail lost.
4a. Cross-validation table (REQUIRED if ≥2 specialists touched shared contracts)
Free-form "all match" claims are not accepted — director cannot verify them. Use this table with verbatim quotes from each specialist's executor report; without the table the cross-validation is presumed unrun and director treats it as such (REJECT).
| Contract | Specialist A — claim (verbatim) | Specialist B — claim (verbatim) | Verdict |
POST /api/items | dev-backend-engineer.md L42: "returns 201 + {id, name}" | dev-frontend-engineer.md L18: "expects 201, body {id, name}" | MATCH |
User.email field type | dev-backend-engineer.md L67: "VARCHAR(254) NOT NULL" | dev-tech-architect.md L23: "string, max 320" | DIVERGE |
DIVERGE = blocker. Resolve (re-dispatch the wrong specialist for fix, re-run code-reviewer) BEFORE handoff. Submitting with unresolved DIVERGE = REJECT.
If no shared contracts in this engagement, write: "N/A — no specialists overlap on shared contracts (only single-domain artefacts produced)." This explicit N/A is required — silence is not an answer.
5. Validation log
Summary — see validation-log.md for full entries.
- {validator}: {verdict}, {N findings resolved / N deferred}
6. Exercised (MANDATORY for ux_heavy, optional otherwise)
Narrative of what happened when the lead actually used each touched control. Each bullet must reference a verifiable artefact (Playwright trace, network log, screenshot, DOM snapshot path) — prose alone is reject-worthy because it can be hallucinated.
Format per bullet:
- Action → observed result → evidence path
- Example: "Clicked 'Квартал' preset at 14:32 → date popover set 2026-04-01 → 2026-04-24 (24 days). Evidence:
traces/iter-2/quarter-preset.jsonline 45-52,screens/iter-2/dark/dashboard-quarter.png."
If section absent on a ux_heavy: true engagement → handoff INCOMPLETE (returned unread, no iteration burned).
7. Self-acceptance rehearsal (MANDATORY — structural gate, not prose)
Lead simulates director's acceptance sweep before handoff. This section is REQUIRED to be non-empty in a structured way:
7a. Mechanical pre-check (paste verbatim JSON output)
$ python ~/.claude/scripts/handoff-precheck.py engagement/ --json
{
"engagement": "...",
"fail_count": 0,
"skip_count": 0,
"status": "pass",
"checks": [...]
}status MUST be "pass". If any check failed, STOP — fix the underlying issue and re-run. Do NOT submit handoff with a red exit code; the director's first action will be running the same script and seeing the same red.
7b. Honest concerns list (concerns must bind to criteria)
The point of this section is to catch the LLM-honesty hole: if you write "no concerns" you are signalling either dishonesty or absence of thought. Force yourself to find real concerns — and tag each one with the criterion it relates to, so vague "naming could be better" filler can be detected:
Format:
1. [crit-{N} | non-criteria | scope-creep] {concern}: {what I'd push back on if I were the director} — {why I'm submitting anyway: in-scope deferral / pre-discussed waiver / honest gap disclosed in §11 deferrals}
2. [crit-{N} | non-criteria | scope-creep] {second concern, real one}
3. (optional more)Tag meanings:
crit-{N}— concern about how this engagement covers criterion N (the strongest tag, hardest to fake).non-criteria— concern about something orthogonal to criteria.md (allowed but capped: at most 1 of N).scope-creep— concern that something done isn't strictly required by criteria (warning of over-engineering).
Threshold by engagement size:
- Size S: ≥1 concern, at least one tagged
crit-{N}ORscope-creep. - Size M: ≥2 concerns, at least one tagged
crit-{N}. - Size L: ≥2 concerns, plus delta from prior iteration ("what's new vs iter-{N-1}").
A rehearsal section that fails the tag-distribution check is structurally invalid — director rejects with self-acceptance concerns ducked criteria.md, bad-faith pattern. The tagging makes "naming could be better" / "docstring short" filler harder, because those are non-criteria and only one is allowed.
7c. ux_heavy auto-promote check
If your work touched any of *.tsx, *.jsx, *.vue, *.svelte, *.html, or styling (*.css, *.scss, Tailwind classes in JSX), AND criteria.md has ux_heavy: false:
- Either ux_heavy was misclassified at intake → state explicitly "AUTO-PROMOTE: ux_heavy → true (UI files touched in diff §1)" and produce screens/traces accordingly.
- OR your work is genuinely non-UX (tooling on UI infra without visible change) → state explicitly why ux_heavy stays false.
Don't quietly skip screens just because intake said ux_heavy: false. This auto-promote keeps a misclassification at intake from disabling all UX defences downstream.
8. Deploy log
- See
deploy-log.mdOR "N/A — no deploy in this engagement"
9. Docs diff
- See
docs-diff.mdOR "N/A — no docs change"
10. Iteration counter
Current round: {N}. Prior round(s): {1, 2, ...}. (informational — escalation triggered by repeating critique, not by counter)
Source of truth: engagement/iteration plain-text file (single integer). Lead increments it before submitting handoff; director increments on REJECT. handoff-precheck verifies the counter agrees with ## Iteration N headings in acceptance-log.md. Mismatch = REJECT (someone falsified the count).
11. Known deferrals
- {deferred item}: justified by {criteria.md out-of-scope | explicit user waiver | director-approved scope note}
### `acceptance-log.md` (director, append-only)
Schema defined in `acceptance-protocol`. One section per iteration.
## Protocol versioning
`protocol_version` in `criteria.md` frontmatter pins the engagement to the protocol version it was created under. handoff-precheck reads this field and:
- If version equals current (4) — proceed normally.
- If version is older than `MIN_SUPPORTED` (currently 3) — REJECT with `engagement uses obsolete protocol; run engagement-migrate.py`.
- If version is older but ≥ `MIN_SUPPORTED` — WARN; engagement continues with old rules where they differ. Migration recommended at next safe break.
- If version is newer than current script — REJECT; scripts are out of date.
Current version log:
| Version | Introduced | Major changes |
|---|---|---|
| 1 | 2026-04-20 | initial agency model |
| 2 | 2026-04-24 | post-Wave-2 fixes (binary verdict, whitelist, ux_heavy, screens/traces) |
| 3 | 2026-05-05 | machine-checked gates (preflight, paths-check, danger-scan, validator-outputs) |
| 4 | 2026-05-06 | engagement size tier (S/M/L), ux_heavy gradient, structured trace schema, abort workflow, cross-domain secondary, protocol versioning, error budget gradient |
Version bumps happen when handoff-schema sections change OR new mandatory sub-checks are added. Backward compatibility is preserved as long as old engagements declare their version explicitly.
## Engagement size tier (S / M / L)
Different engagements need different rigour. Forcing a 30-line CSS tweak through the same 11-section handoff as a multi-wave refactor invites corner-cutting. Size is set by the secretary at intake and recorded in `criteria.md` frontmatter as `size: S | M | L`.
### Sizing rules (mechanical)
| Tier | Diff size | Specialists | UI scope | Deploy |
|---|---|---|---|---|
| **S** | ≤2 changed files OR ≤50 LOC | 1 | none / `ux_heavy: false` | none |
| **M** | 3–10 files OR 50–500 LOC | 2–3 | `ux_heavy: false` or `minor` | preview/staging only |
| **L** | >10 files OR >500 LOC, OR multi-wave | ≥4, OR cross-domain | `ux_heavy: true`, OR new design system | production deploy |
If the engagement crosses thresholds during execution, lead promotes the size in `scope-sync.md` (S→M→L only, never demote). Promotion = additional rigour applies retroactively from next handoff.
### Schema relaxations by tier
| Section / gate | S | M | L |
|---|---|---|---|
| Handoff §1 Diff summary | **single line OK** ("+/- N files") | required (full git stat) | required (full git stat + commits) |
| Handoff §2 Deliverable manifest | required | required | required |
| Handoff §3 Criteria trace | **inline** (1 line per crit, no table needed) | required (table) | required (table) |
| Handoff §4 Executor reports | merged into §2 if 1 specialist | required | required |
| §4a Cross-validation table | N/A (1 specialist) | required if ≥2 specialists touch shared contract | required |
| Handoff §5 Validation log | brief summary line OK | full | full |
| Validator output JSON files | required | required | required |
| Handoff §6 Exercised | only if `ux_heavy: minor \| true` | only if `ux_heavy: minor \| true` | required if `ux_heavy: minor \| true` |
| Handoff §7 Self-acceptance | **abbreviated: ≥1 concern** | full: ≥2 concerns | full: ≥2 concerns + delta-from-iter-N-1 |
| Handoff §8 Deploy log | N/A | only if deploy crossed | required |
| Handoff §9 Docs diff | optional | mandatory if `src/` touched | mandatory if `src/` touched |
| Handoff §10 Iteration counter | optional (informational) | optional | optional |
| Handoff §11 Known deferrals | optional | required (or "None") | required (or "None") |
| Director scope sync | optional | optional | mandatory if `ux_heavy: true` or weak criteria |
| Specialist dispatch (engagement-workflow) | single task's owner, 1 wave | wave(s) of parallel specialists by `task.owner` (disjoint files) | multi-wave dispatch with dependencies (decompose mandatory) |
| Director phase | **NONE** (producer self-attest + mechanical + human) | lightweight (judge between producer + 1 adversary) | full (judge between producer + 5-reviewer consilium) |
| Adversary pass | none | Opus adversary in fresh subprocess (`adversary_lg.py --consilium M`) | Consilium: peer-Opus + 2× Codex + Sonnet + Haiku (`adversary_lg.py --consilium L`) |
| Iteration budget | 1 (one shot expected) | 2 | 3 |
| Iteration budget on auto-promote | n/a | +1 (so promoted-S becomes M with 3 max) | +1 (so promoted-M becomes L with 4 max) |
### S-tier minimum-viable handoff template
For `size: S` engagements, this 5-line shape is acceptable as `handoff.md`:
```markdown
# Handoff — iter 1 — {engagement-name}
## 1. Diff summary
+/- {N} files: {paths}; {one-line of what changed}
## 2. Deliverables + criteria trace
- crit-1: {bullet text} ✅ {evidence path}
- crit-2: {bullet text} ✅ {evidence path}
## 5. Validation log
{validator}: {verdict} — see validation-outputs/
## 7. Self-acceptance
1. [crit-N|scope-creep] {one honest concern} — {justification}That's it. No §3 table when 2-3 criteria fit inline. No §4 separate reports section when one specialist's work is in §2 already. No §6 unless UI. No §11 if there are no deferrals. The point: small engagement = small handoff. Director still runs handoff-precheck.py — schema-aware checks know about size: S and don't demand the larger-tier sections.
| tasks/*.md atomic decomposition | N/A (skip) | recommended if ≥2 specialists OR ≥3 distinct deliverables | required (tasks/INDEX.md mandatory) |
Tier-relaxations are the ONLY allowed deviation from the canonical schema. Any other deviation = whitelist violation = REJECT. The point: small engagements feel small, large engagements stay rigorous, and corner-cutting goes from individual lead choice to protocol-recognised path.
Specialist dispatch — the workflow wave model
The engagement-workflow Workflow dispatches specialists directly by task.owner — there is no mid-lead routing layer at any tier. The coordination a mid-lead used to provide is now structural, encoded in the lead's plan as WAVES:
- A wave is a group of tasks whose
filesare strictly disjoint; the Workflow runs them in PARALLEL (each as itstask.owneragentType) and consolidates at the wave barrier (code mode: octopus merge of the parallel worktrees, overlap → sequential-resolve; artefact mode: manifest-verify of the written files). - Cross-task coordination that needed a coordinating lead is expressed as wave ORDER + dependencies: a shared/foundational module (API contract, type/schema, design tokens, brand voice) goes ALONE in an earlier wave; the specialists that consume it go in a LATER wave (which sees the earlier wave's merged work). Two specialists on a shared contract therefore never need a live coordinator — they are serialized through the shared module's wave.
- Per-task review→rework runs inside each task (code mode:
code-revieweras criteria-guardian; artefact mode: a critique pass), bounded by tier (S=1, M=2, L=3 attempts).
Tier shapes the plan, not a routing layer:
| Tier | Plan shape |
| S | 1 task, 1 wave, single owner. |
| M | 1–few tasks; ≥2 specialists → split by surface into disjoint-file tasks, the shared contract in an earlier wave alone, consumers in a later wave. |
| L | multi-wave: foundational wave → parallel feature waves → integration. decompose mandatory. |
Anti-pattern: planning two same-wave tasks that touch the same file. The octopus/manifest consolidation assumes disjoint files; overlap forces the slow conflict-resolve fallback. Merge them into one task or split into sequential waves.
Why: waves are the coordination unit. What used to be "invoke a mid-lead to coordinate ≥2 specialists" is now "put the shared dependency in an earlier wave" — the dependency graph does the coordinating, deterministically, with no extra agent hop.
Task decomposition rule (cross-domain)
tasks/*.md files are atomic deliverable units that let lead re-dispatch a single broken atom on iter-2 instead of re-running the whole phase. Each domain has its own decomposition methodology:
- dev —
task-decomposition(tech-spec → atomic tasks with TDD anchors). Existing. - marketing —
marketing-task-decomposition(criteria + plan → atomic tasks: keyword-cluster, ad-group, landing-block, metric-pull, banner-variant, etc.). New. - design —
design-task-decomposition(criteria + plan → atomic tasks: voice-axis, logo-direction, token-group, component-spec, screen-variant, etc.). New.
Authority and timing:
- Lead invokes the matching skill at Phase 2.5 (after
plan.mdis frozen, before any specialist dispatch). - Skip permitted only on size: S engagements OR size: M with a single specialist.
- Director will REJECT a size: L handoff with no
tasks/directory or with emptytasks/INDEX.md. Same gate as missingvalidation-log.md.
On REJECT loop: lead identifies failing crit-N from director verdict, finds tasks where crit_refs includes those crit values, re-dispatches ONLY those tasks. Specialist appends ## Iteration {N} to their executor-report (per §4-iter rules). The whole-phase re-run is the failure mode this exists to prevent.
How handoff-precheck.py adapts
The script reads size from criteria.md frontmatter and skips checks not applicable to that tier (e.g. for size: S with 1 specialist, cross-val table check is automatically skipped — not because of bypass, but because the schema declares it N/A at this size). Same logic gates the tasks/ requirement: the precheck tolerates an empty tasks/ on S, warns on M without it, and FAILs on L without it.
Iteration budget
The counter is informational, not a quota. Do not use language like "slot 1/2 used" or "last attempt" — it creates psychological pressure to accept marginal work. Escalation triggers are root-cause based, not slot-based:
- Iteration 1 — lead's first handoff. Director writes ACCEPT or REJECT (binary, see "Verdict is binary" below).
- Iteration 2 — lead's revision. Director reviews again.
- Repeating-critique trigger (highest priority): if the same blocking item from iteration N appears again in iteration N+1 — escalate to user IMMEDIATELY, regardless of which iteration this is. The loop is the signal, not the count.
- Pre-final-iteration trigger: before starting the last allowed iteration, escalate to user with current blockers. Wait for explicit "продолжаем" or "пересматриваем scope".
- Hard limit by tier (tiered acceptance refactor):
- S: 1 iteration. REJECT → human directive (rework / abandon), no auto-loop.
- M: 2 iterations. After round 2, escalate.
- L: 3 iterations. After round 3, escalate.
- Auto-promoted engagements: budget +1 (promoted-S becomes M with 3 max; promoted-M becomes L with 4 max).
- Never start an iteration beyond the hard limit without user authorisation.
Mechanical loop-to-intake trigger
"Criteria are wrong, not the work" is a real failure mode but agents miss it without an explicit rule. Trigger:
If acceptance-log.md shows iter-N REJECT and iter-(N+1) REJECT with the same blocker (matched by criterion ID OR by reject-reason text-similarity ≥ 70%), the lead is OBLIGATED to:
- STOP rework. Do not start iter-(N+2) on this criterion.
- Dispatch
agency-intakevia Task tool with payload:- Path to
engagement/criteria.md. - Path to
engagement/acceptance-log.md. - The repeating blocker text.
- Lead's diagnosis: "criterion
{id}is unbuildable as written because {reason}; suggest revision to {proposed-rewording}".
- Path to
- Wait for secretary to either:
- Edit
criteria.md(touching only the broken criterion, keeping rest intact, recording change inscope-sync.mdwith director's signature) and unblock. - Escalate to user: "criterion {id} cannot be met as stated, revise scope or close engagement".
- Edit
- Resume from current iteration with revised criteria.
If lead skips this and just keeps reworking → director rejects with loop-to-intake skipped — repeating blocker not escalated. The mechanical trigger (same blocker twice) is the signal, regardless of how confident the lead feels about a fix.
Secretary's authority during loop-to-intake
When dispatched mid-engagement to fix criteria, secretary has authority for non-scope edits without user touch (matching lead's Phase 1a sharpening rules):
- Rephrasing a vague bullet to a measurable one (no value change).
- Dropping a redundant / no-value filler bullet that the lead's experience reveals.
- Splitting a compound bullet into testable atoms.
These are recorded in scope-sync.md as "secretary auto-edit during loop-to-intake" with diff. User sees them at ACCEPT.
User touch required only when:
- The criterion is unbuildable for principled reasons (not just "lead struggled twice").
- Removing a deliverable that has independent user value.
- Major scope change that shifts the engagement to a different domain.
In those cases, secretary escalates with the standard Russian template:
Critеrии для engagement {name} нуждаются в существенной правке. {N} итерация(и) rework не закрыли блокер:
- {blocker text}
Предложение: {proposed-rewording or scope reduction}.
Альтернатива: закрыть engagement как unresolvable.
Выбери: подтвердить правку / переформулировать / закрыть engagement.When the loop is "criteria are wrong, not the work", route back to agency-intake for new/updated criteria.md instead of throwing more rework at the lead.
Verdict is binary
The director's verdict is exactly ACCEPT or REJECT. Conditional accepts are forbidden — they defeat the agency model's purpose of reducing user QA burden:
- No
ACCEPT CONDITIONAL, noACCEPT pending X, noACCEPT with TODO, noACCEPT — user to verify Y. - If any acceptance bar cannot be verified by the director with available tools (Docker not running, Playwright not installed, BD not reachable, secrets missing): the verdict is REJECT with reason
validation incomplete: <what was unverifiable>. Do NOT defer the verification to the user. - Tooling unavailability is itself a blocker. The lead and the director coordinate to make the validation environment work (escalate to user once if a manual step is required, e.g. "запусти Docker Desktop"), then continue. They do NOT submit work for user-side validation.
The only acceptable form of "deferral" is something already listed in criteria.md "Explicitly out of scope" or in a director-approved scope-sync.md waiver.
UX-heavy engagements
Detail moved to references/ux-heavy.md in v0.2 (the reference split) — load that file when criteria.md frontmatter has ux_heavy ∈ {minor, true} OR the engagement plan introduces a UI surface. Hot-path summary:
ux_heavyis a 3-level gradient:false(no UI artefacts),minor(one screenshot per touched surface, single theme, traces optional),true(Playwright screens both themes + structured trace JSON per flow).- Set by secretary at intake from visual / layout / typography / color signals. Lead may promote
false → minor → trueviascope-sync.md; never demote. ux_heavy: truemandatesscreens/{iteration}/{theme}/+traces/{iteration}/{flow}.json+ handoff §6 referencing real paths +ux-reviewvalidator in validation-log. Missing any of these on submission = handoff INCOMPLETE (returned unread, doesn't burn iteration budget).- Trace JSON requires structured
steps[]withaction / selector / expected / observed / verdict ∈ {PASS, FAIL}. Submitting with any FAIL verdict attached as evidence = REJECT. Unstructured prose traces = REJECT.
→ Full gradient table / minor vs true rules / trace JSON schema / lead-side and director-side gates: references/ux-heavy.md.
Dangerous operations registry
Detail moved to references/dangerous-ops.md in v0.2 (the reference split) — load that file when danger-scan.py produces a non-empty finding OR when the engagement diff touches schema / migrations / production deploy / secret rotation. Hot-path summary:
- 9 operation classes always require explicit user OK (DROP TABLE, force-push, prod deploy, secret rotation, public publish, recursive delete on parents, bulk DELETE without WHERE, migration without rollback, infra teardown).
- Lead runs
danger-scan.pybefore handoff;handoff-precheck.pycalls it as a sub-check. - Non-empty findings → handoff blocked until matched user-OK entry exists in
scope-sync.mdunder## Dangerous-op user OK — {timestamp}. - Silence is NOT consent. Backup verification required for schema destruction + bulk delete.
→ Full operation table / detection / user-OK protocol / backup rules / what does NOT count: references/dangerous-ops.md.
Engagement abort (user pulls the plug mid-engagement)
Detail moved to references/abort.md in v0.2 (the reference split) — load that file when the user issues an explicit abort directive. Hot-path summary:
- Trigger: explicit "стоп / забей / закрой / отменяю / не делай это". NOT triggered by "не уверен / подожди / давай по-другому" (those are scope clarification → loop-to-intake or lead-redispatch).
- Floor-holder writes stub
acceptance-log.mdwith### Verdict: ABORTED+ verbatim user quote + state snapshot. - Run
python ~/.claude/scripts/engagement-archive.py --reason aborted(sanity checks skipped under abort flag). - Print one user-facing line confirming archive path; engagement is closed.
→ Full stub template / archive flow / not-an-abort cases: references/abort.md.
Engagement archival (after ACCEPT)
Detail moved to references/archival.md in v0.2 (the reference split) — load that file when writing ACCEPT verdict OR retrying a previously-failed archival. Hot-path summary:
- Order is strict: verdict → user-facing summary → archival LAST.
- Run
python ~/.claude/scripts/engagement-archive.py(idempotent). Never hand-rollmv. - Archival failure never undoes the verdict. Log as
## Archival pending — {timestamp}inacceptance-log.mdand retry later. - Archive path:
engagement-archived/{YYYY-MM-DD}-{engagement-name}/.
→ Full ordering rules / failure recovery / sanity checks: references/archival.md.
Task-tool prompts: minimum viable content
Long Task-tool prompts (200+ lines) duplicate what the dispatched agent already knows from its skills frontmatter (engagement-protocol, domain methodology, Engagement-mode contract). Verbose prompts:
- inflate token usage with no signal gain
- delay agent's first action (more tokens to parse before responding)
- look like "stalled" to operators waiting for first heartbeat
Minimum-viable Task prompt (canonical)
Engagement: {engagement-name}
Iteration: {N}
Criteria: {absolute path to engagement/criteria.md}
Review mode: {lean | full | solo}
Read criteria.md first. Engagement context, source paths, and constraints
are inside it — do not re-paste here.
Begin {phase-name}. Heartbeat per phase per protocol.
Return summary on completion (or escalation).That's it. 5–8 lines.
What NOT to include in Task prompts
- Full criteria text — agent reads
criteria.mditself. - Source artefact paths — listed in
criteria.md. - Protocol reminders ("don't create files outside whitelist", "use heartbeat", "11-section handoff") — those are in
engagement-protocolskill which the agent loads via frontmatter. Re-pasting them in prompts is noise. - Anti-pattern lists — already in agent's
## Anti-patternssection. - Backstory about why the engagement exists — irrelevant to execution.
When MORE prompt content IS justified
- Specific phase-only constraint (e.g. "this iteration is rework — focus only on F-02 token mismatch from previous reject").
- One-off override that contradicts protocol default (e.g. "skip docs-pipeline for this engagement — out of scope per scope-sync").
- New convention the agent's skills don't yet teach (very rare; if it recurs, document in skill instead).
Even in these cases, additional content stays under 30 lines.
Why this matters
In the actual landing-hybrid-header-hero test we ran: a 200-line dispatch prompt to design-lead caused the agent to spend its first 20-40 seconds parsing instead of acting. Operator interpreted this as a stall and interrupted. Friction was self-inflicted by verbose prompt, not by agent behaviour.
Resume policy (interrupted iterations)
Detail moved to references/resume.md in v0.2 (the reference split) — load that file when resuming an iteration that was interrupted (Task tool cancelled, context compaction, manual user stop). Hot-path summary:
- Inspect existing engagement state first; prior-session artefacts are neither automatically valid nor automatically invalid.
- Heartbeat at resume point MUST list which artefacts you reuse vs regenerate vs delete.
- Reuse rules (each strictly stale-checked): traces if
captured_atnewer than code change; screens if surface unchanged; validator outputs if artefact unchanged; executor reports if work isn't being redone. - Stale = regenerate. No shortcut. Trace
captured_atolder than referenced artefact mtime = regenerate.
→ Full per-artefact reuse table / heartbeat resume template: references/resume.md.
Lead heartbeat (mandatory — every lead, every phase)
Long-running orchestrator-agents (a lead run standalone, or the engagement-workflow itself) can stop streaming tokens for many minutes while doing internal planning, file edits, or sub-dispatches. The user/operator has no signal whether the agent is alive or stuck. (Inside the Workflow path, the script's own log() progress + the /workflows live view are the primary heartbeat; the validation-log lines below remain the heartbeat for a lead run standalone.)
To prevent silent stalls: every lead appends one heartbeat line to engagement/validation-log.md after each completed phase. The presence + recency of these lines is the heartbeat.
Heartbeat entry format
## Heartbeat — Phase {phase-name} — completed {YYYY-MM-DD HH:MM:SS}
- iteration: {N}
- artefacts updated: {list of paths touched in this phase}
- next phase: {name | "handoff" | "done"}Inserted at the top of validation-log.md (newest first), so observers tail validation-log.md get latest signal.
Cadence
- Lead (planning) phase: the lead returns a plan; the Workflow then logs discovery / decompose / deliver / validate / handoff / gate boundaries (one
log()per wave + per validator). - Specialists do NOT heartbeat — they are short-lived and return synchronously to the Workflow's wave step.
Heartbeats are append-only, never modified. They are NOT validation findings — validation-log.md retains its existing role as validator output index, the Heartbeat sections live alongside the per-validator sections.
When user / operator can confirm "stuck"
If validation-log.md's most recent Heartbeat is older than:
- 5 minutes for an
Sengagement - 15 minutes for
M - 30 minutes for
L(the engagement-workflow may legitimately spend ≥30min in a single complex wave)
→ engagement is presumed stalled. Recommended action: open the engagement directory, check git status / file mtimes for any artefact created since last heartbeat. If lead made progress without writing heartbeat — that's a protocol violation (REJECT trigger). If no progress visible — abort and re-dispatch from last completed phase.
engagement-doctor.py --check stalled automates this signal (Tier 11.6).
Anti-patterns
- Don't backfill heartbeats — write them when the phase actually completes, not in handoff.md after the fact.
- Don't heartbeat mid-phase — only at phase boundaries.
- Don't heartbeat from specialists — they have no phases.
Event emission (events.jsonl) — machine-queryable twin of the heartbeat
The prose heartbeat above is human-readable but not machine-queryable, and it only covers leads. The sub-engines (validator_lg / adversary_lg / handoff-precheck) already emit structured events to engagement/events.jsonl, but the orchestration layer (lead phases, specialist completion) was invisible there. To close that gap, every lead emits a structured ledger event at each phase boundary, and every specialist emits one on report completion (the specialist side lives in engagement-contract).
Markdown agents cannot import lib.ledger, so emit via the thin CLI. It is best-effort and never blocks: if python or the ledger module is unavailable it prints a stderr warning and exits 0, and your real work continues.
python ~/.claude/scripts/ledger-emit.py engagement/ --agent dev-lead --tier M \
--type phase_completed --phase plan --note "3 specialists planned"| Boundary | --type | Extra flags |
| First action (Phase 1, criteria locked) | engagement_started | — |
| Entering a phase | phase_started | --phase {name} |
| Phase done (pair with the prose heartbeat) | phase_completed | --phase {name} --note "..." |
| Dispatching a specialist (Workflow wave step) | specialist_dispatched | --specialist {agent} |
| Writing handoff.md | handoff_submitted | --verdict ACCEPT |
| Optional tick inside a long phase | heartbeat | --phase {name} |
This is additive — keep writing the prose heartbeat line to validation-log.md (the stall-detection above still reads it). --agent is your own agent name; --tier is the criteria.md size:. The same CLI is what specialists use for specialist_completed.
Authority rules 6/7 (same CLI, two extra modes). At engagement start, prefer --snapshot-skills over a plain engagement_started — it emits the same event plus a snapshot of loaded skill names + content hashes (rule 7, protects against silent skill drift mid-engagement). On a rule-6 authority conflict during dispatch, emit it instead of only prose:
python ~/.claude/scripts/ledger-emit.py engagement/ --agent dev-lead --tier M \
--authority-conflict --conflict-kind skill_vs_agent_body \
--sources-json '[{"kind":"skill","name":"engagement-protocol","claim":"..."},{"kind":"agent","name":"dev-lead","claim":"..."}]' \
--resolution "skill wins per precedence rule 4" --decided-by auto-precedenceOmit --resolution for a blocking conflict — it emits verdict=REJECT; you still halt dispatch and escalate to the human judge per §"Authority and conflict resolution".
Token budget guard + size auto-promote (Tier 14)
Detail moved to references/budget.md in v0.2 (the reference split) — load that file when entering Phase 4 (costly subagent waves) or running a heartbeat thereafter. Hot-path summary:
- Per-tier per-iter budget: S=100k, M=500k, L=1.5M tokens (lead + director combined).
- Run
python ~/.claude/scripts/token-budget.py engagement/ --jsonafter each Phase-4+ heartbeat. Exit 1 = over budget; lead chooses auto-promote / scope-sync escalation / accept-partial. - Run
python ~/.claude/scripts/size-detect.py engagement/ --mode runtime --auto-promote --jsonafter Phase 2 and every heartbeat. Exit 1 with--auto-promoterewritescriteria.mdsize:and appends## Auto-promotetoscope-sync.md. - Auto-promote is retroactive: iteration budget bumps +1, adversary pass becomes required at handoff regardless of when promotion occurred, validators applicable to new tier MUST run before handoff.
handoff-precheck.pysub-checksize-driftREJECTs at acceptance if lead failed to promote.
→ Full budget table / exit-code semantics / auto-promote retroactive consequences / scope-sync template: references/budget.md.
Machine-checked gates
Two distinct points where machine checks fire. Both use scripts at ~/.claude/scripts/:
At intake (secretary, BEFORE handoff to lead)
python ~/.claude/scripts/preflight.py --criteria engagement/criteria.md --json --auto-fixPurpose: ensure validation environment is ready before the engagement starts. Wave-2-style "Docker missing for the whole engagement, then user is asked to validate by hand" is what this prevents.
Before handoff (lead, then director on receipt)
python ~/.claude/scripts/handoff-precheck.py engagement/ --jsonPurpose: ensure the engagement state itself is ready for review — whitelist clean, paths exist, validators ran, no banned language, no undisclosed dangerous ops, etc. Includes a re-run of preflight (environment may have degraded mid-engagement: Docker crashed, secret rotated, package broke).
Hard-gate tier dispatch (tiered acceptance refactor):
- S-tier: 6 critical checks (criteria-frontmatter, whitelist, preflight, handoff-paths, danger-scan, verdict-canonical)
- M-tier: 13 checks (S + handoff-sections, self-acceptance-thinness, iteration-counter, validator-outputs, size-drift, human-directive, director-verdict)
- L-tier: 21 checks (all)
The script reads size: from criteria.md frontmatter and runs only the tier-applicable checks (no soft-skip overhead). Use --all-checks for debug or --override-checks NAME1,NAME2 for ad-hoc additional checks.
At acceptance (M/L tiers only — S has no director phase)
# M-tier: single Opus adversary
python ~/.claude/scripts/adversary_lg.py engagement/ --consilium M
# L-tier: 5-reviewer consilium (peer-Opus + 2× Codex + Sonnet + Haiku)
python ~/.claude/scripts/adversary_lg.py engagement/ --consilium L
# Aggregate:
python ~/.claude/scripts/consilium-synth.py engagement/
# Human reads consilium-summary.md as supreme judge, writes human-directive.md.
# THEN director acts per directive (PROCEED_TO_VERDICT | REJECT_NOW | DIRECTED_VERDICT).
# After director writes verdict, mechanical post-check:
python ~/.claude/scripts/handoff-precheck.py engagement/
# (M/L tier sets include `human-directive` and `director-verdict` checks.)Two-pass adversary protocol (filesystem-isolated): each role runs Pass 1 in subprocess on a CURATED copy of engagement/ that physically excludes handoff.md, acceptance-log.md, consilium-summary.md, human-directive.md, and validation-outputs/. Pass 1 writes preliminary findings. Pass 2 runs on full engagement with preliminary findings injected; reads handoff and finalizes verdict. Outputs:
engagement/validation-outputs/{role}-iter-N-preliminary-{ts}.json(Pass 1)engagement/validation-outputs/{role}-iter-N-{ts}.json(Pass 2 final)
consilium-synth.py aggregates Pass 2 outputs into engagement/consilium-summary.md with two-stage dedup (cluster by evidence_path first, then issue similarity), convergent findings, cross-family disagreements, naive-layer catches, similarity matrix.
Human-as-supreme-judge step: human reads consilium-summary.md, writes engagement/human-directive.md with Decision: PROCEED_TO_VERDICT | REJECT_NOW | DIRECTED_VERDICT. This filters adversary noise (false positives, filler) before director processes. Director then acts per directive — adjudicating remaining signals with explicit markers (SUSTAINED/OVERRULED, SIDED WITH X, REAL/FALSE_POSITIVE, ACKNOWLEDGED). director-verdict-check.py mechanically verifies markers exist for every consilium signal.
See acceptance-protocol skill for full per-tier procedure.
One canonical entry point bundles every handoff machine check
python ~/.claude/scripts/handoff-precheck.py engagement/ --jsonThe wrapper runs:
- whitelist —
ls engagement/against the closed allowed list (no roguepreview.md/compliance.md). - criteria-frontmatter —
criteria.mdhasengagement,domain,ux_heavy,tools_required. - preflight — every tool in
tools_requiredis reachable (delegates topreflight.py). - handoff-paths — every path cited in
handoff.mdexists on disk (delegates tohandoff-paths-check.py). - handoff-sections — required sections §1–§5, §7, §8, §11 present; §6 present if
ux_heavy: true. - self-acceptance-thinness — §7 concerns sublist has ≥2 real items (catches LLM "no concerns" bad-faith).
Non-zero exit = INCOMPLETE handoff. Never submit / accept with red exit codes — these checks exist precisely because the underlying bug classes (broken tooling, phantom paths, rogue files, thin rehearsal) cannot be reliably caught by reading prose.
The individual sub-scripts (preflight.py, handoff-paths-check.py) can also be run directly if you want a focused check (e.g. mid-engagement Docker sanity), but at gate time always go through the wrapper.
Escalation template (director → user, Russian)
После двух кругов правок директор не принимает работу. Текущая блокировка:
- {blocking item 1}
- {blocking item 2}
Продолжать ещё круг или пересмотреть scope / criteria?Cross-domain handoff
Two-domain engagements (primary + secondary). Detail moved to references/cross-domain.md in v0.2 (the reference split) — load that file when an engagement crosses domains. Hot-path summary:
- Primary in
engagement/; secondary inengagement-secondary/{domain}/to avoid state collision. - Workflow: primary lead → primary director ACCEPT → primary lead initiates secondary (manual) → secondary runs its own full cycle → unified user message after both ACCEPT.
- Three-domain engagements rejected at intake — user must split.
→ Full workflow / archival rules / anti-patterns: references/cross-domain.md.
Observability
Every director iteration appends one JSON line to ~/.claude/projects/{project}/metrics.jsonl:
{
"ts": "2026-04-20T15:30:00Z",
"engagement": "{engagement-name}",
"domain": "marketing|dev|design",
"director": "{director-agent}",
"iter": 2,
"verdict": "accept|reject",
"blocking_count": 3,
"reran_validators": ["reality-checker", "skeptic"],
"duration_s": 180
}No live dashboard. Raw JSONL for retrospective analysis.
Role boundaries (authoritative)
| Stage | Secretary | Lead | Director | User |
| Intake capture | ✓ | — | — | source |
| criteria.md draft | ✓ | — | reads | approves |
| Scope sync (optional) | — | raises Q | writes Q/A + freeze | — |
| Planning | — | ✓ | — | — |
| Specialist dispatch | — | ✓ | — | — |
| Cross-cutting pipelines | — | ✓ | re-runs on sweep | — |
| Handoff package | — | ✓ | — | — |
| Accept/reject verdict | — | — | ✓ | — |
| User-facing delivery | — | — | ✓ (on accept) | receives |
| Escalation | — | — | ✓ (at round 3) | decides |
Authority and conflict resolution
When two sources of behavior disagree (e.g., agent body says X, loaded skill says Y; criteria.md asks for behavior protocol forbids; two loaded skills overlap on the same topic), the following invariant decides:
- Normative precedence (highest → lowest):
CLAUDE.md> explicit judge decision >criteria.md> PROTOCOL skills > METHODOLOGY skills > agent body > frontmatter. - criteria.md may add scope / quality bars / preferences, but may not waive mandatory PROTOCOL gates unless an explicit judge decision records the waiver (logged via §"Dangerous operations registry" or human-directive.md). Likewise a co-signed per-task contract (S2) may TIGHTEN the per-task review rubric but may NOT waive a
criteria.mdbar or a PROTOCOL gate — the validate phase / consilium / manager always judge againstcriteria.md, not the contract. - Frontmatter has zero behavioral authority — it only declares what must be loaded. Skill
description:text is not enforceable behavior. - Agent body may specialize role behavior only where loaded skills are silent. It never overrides a loaded skill on the same topic; if it appears to, the skill wins.
- Between same-tier skills (two PROTOCOLs, two METHODOLOGYs), the narrower scope wins unless it weakens a mandatory check; then the stricter rule wins.
- Any unresolved conflict is a blocking
authority_conflictevent. Dispatch halts; escalate to the human judge before proceeding. Manager logs the conflict + resolution inacceptance-log.md§"Authority adjudications" (free-form list for now; typed payload once event ledger lands). - Each engagement snapshots loaded skill names, versions, and content hashes at start (criteria.md frontmatter). Mid-engagement edits to skills do not apply to the in-flight engagement unless the human judge approves and the change is recorded. This protects against silent rule drift across multi-day engagements.
Cross-references: §"Dangerous operations registry" for protocol-gate waivers. CLAUDE.md §"Anti-patterns" for top-level overrides.
Producers:
from lib.ledger import EventLedger
led = EventLedger(engagement_path, agent="dev-lead", tier="M")
# Rule 7 — skill snapshot at engagement start (Phase 1, after criteria locked):
skills_snapshot = EventLedger.snapshot_skills(Path.home() / ".claude" / "skills")
led.emit_skill_snapshot(skills=skills_snapshot)
# Writes engagement_started event with full {name, content_hash, lines} per skill.
# Rule 6 — authority conflict during dispatch:
led.emit_authority_conflict(
conflict_kind="skill_vs_agent_body", # or two_skills_overlap / criteria_waives_protocol_gate / skill_version_drift_mid_engagement
sources=[
{"kind": "skill", "name": "engagement-protocol", "claim": "heartbeat per phase"},
{"kind": "agent", "name": "dev-lead", "claim": "heartbeat per phase + per sub-task"},
],
resolution="skill wins per precedence rule 4", # or None → BLOCKING; escalate to judge
decided_by="auto-precedence", # or "judge" / "<agent-name>"
)
# Writes authority_conflict event; verdict=REJECT and blocking=True when resolution=None.If resolution=None, the event is blocking: caller MUST halt dispatch and escalate to the human judge before continuing. The manager picks up the event during acceptance sweep and records adjudication in acceptance-log.md.
Markdown agents (leads) vs Python orchestrators. The
emit_skill_snapshot/emit_authority_conflicthelpers above are the Python API used by the Python LG orchestrators (adversary_lg.py/validator_lg.py). A lead invoked viaclaude -p --agentis a markdown agent and cannot run them inline — it emits lifecycle events (phase / dispatch / handoff) vialedger-emit.py(see §"Event emission" under Lead heartbeat). Both are now also exposed to markdown leads vialedger-emit.py --snapshot-skills(rule 7) andledger-emit.py --authority-conflict(rule 6) — see §"Event emission" under Lead heartbeat. A blocking rule-6 conflict (omit--resolution) emitsverdict=REJECT; the lead still halts dispatch and escalates to the human judge.
Engagement-mode contract (when dispatched inside an engagement)
Every lead and dispatched specialist follows this contract — it keeps engagement state predictable across the dispatch tree:
- Read
engagement/criteria.mdfirst; respectux_heavyandtools_required. - Write only to
engagement/executor-reports/{your-name}.md(single output channel, append-only across iterations with## Iteration Nheadings). The only otherengagement/paths a specialist writes are the ones its task names: declaredfilesin artefact mode (e.g.specs/tech-spec.md), anddocs-diff.mdfordev-technical-writer(owned bydocs-pipeline). - Source/project files in their normal paths; never create rogue files in
engagement/(whitelist enforced in § "Engagement = a directory"). - Open report with Criteria acknowledgement (bullets citing
crit-Nverbatim, not paraphrased). - Disclose anti-patterns explicitly: skipped tests, hidden elements, mocks for real fns, try-except swallow.
- State cross-contract claims verbatim — top-lead reconciles in handoff §4 / §4a.
Criteria propagation — workflow dispatch
Criteria must reach each specialist intact and come back as verifiable coverage. In the Workflow model this is done by the SCRIPT, not a mid-lead conduit.
Dispatching DOWN (the Workflow's per-task prompt)
Each task the Workflow dispatches carries, in the specialist's prompt:
- The engagement dir +
engagement/criteria.mdpath. - The task's
crit_refs— the criteria-bullets this task addresses (the lead's plan binds each task to itscrit_refsverbatim; the specialist opens its executor-report with a "Criteria acknowledgement" citing them, per §"Engagement-mode contract"). - The
ux_heavyflag and the disjointfilesthe task owns. - The canonical executor-report path (
engagement/executor-reports/{owner}.md).
If a specialist's deliverable doesn't satisfy its cited criteria, the per-task review returns rework and the SAME specialist is re-dispatched (bounded by the tier attempt cap) — enforcement is inside the task loop, not escalated up.
Coming back UP (plan crit_refs + handoff trace)
What the mid-lead's "coverage matrix / return-up" used to assert is now structural:
- Coverage = the plan's task→
crit_refsmapping (every criterion is owned by ≥1 task) plus the handoff §3 Criteria trace (each criterion → status → evidence path). A criterion with no owning task is a planning gap the lead must close before dispatch. - Cross-contract claims = specialists state cross-contract claims verbatim in their executor-reports (per §"Engagement-mode contract"); the Workflow's wave consolidation + the handoff §4a Cross-validation table reconcile them (DIVERGE = blocker).
- Blockers surface in the executor-report and the per-task review verdict; the handoff carries them in §11.
Anti-patterns
- Don't let the plan leave a criterion with no owning task — that's the gap the coverage mapping exists to prevent.
- Don't accept a handoff whose §4a cross-validation is "all match" without the verbatim claim list (§4a rules apply).
- Don't paraphrase criteria into task prompts —
crit_refsbind verbatim; paraphrase causes specialist drift.
Anti-patterns
- Don't invent new artefact sections. Schemas are fixed. Add fields via this skill's next revision, not per-engagement.
- Don't create files outside the whitelist. Forbidden list (preview/compliance/review-log/etc.) is closed. Wanting one of those = signal that content belongs in an existing whitelist file. Director treats out-of-whitelist files as REJECT.
- Don't skip
criteria.md. Directors cannot accept against fuzzy intent. - Don't let the lead write
acceptance-log.md. That's self-acceptance. - Don't let the director write
handoff.md. That's author-as-reviewer. - Don't talk to the user mid-engagement (lead / director) — lead surfaces via director; director surfaces only on accept or escalation. Pre-flight tooling unavailability is the only mid-engagement user touch allowed (and it's blocker disclosure, not work consultation).
- Don't issue
ACCEPT CONDITIONAL. Verdict is binary. Tool unavailability = REJECT with reasonvalidation incomplete, not a deferral to user. - Don't defer Playwright capture / exercised flow logs to the user. UX validation artefacts are part of hand-off. If they're missing, hand-off is incomplete.
- Don't silently exceed iteration budget. Escalate before round 3, OR earlier on repeating critique.
- Don't use slot language. "Slot 1/2 used", "last attempt", "final round" — banned. Escalation is root-cause based.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

