[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.
Scanned 6/1/2026
Install via CLI
openskills install AgentShekel/agentic-workflow---
name: engagement-protocol
domain: meta
triggers:
- "loaded by every lead / manager / mid-lead via skills frontmatter"
- "writing or updating engagement/ artefacts (criteria, plan, handoff, acceptance-log, etc.)"
- "tier dispatch decisions (S/M/L acceptance shape)"
- "authority precedence question (skill vs agent vs criteria)"
- "engagement archival / abort / resume policy"
description: |
[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.
---
# 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 `*-manager` agent. Throughout this document, legacy mentions of "director" / "the director" denote that **manager/acceptor** role — read them as "manager". The `*-director` agents are now a *separate* role — system-optimizers that improve the skill/agent corpus (see `system-optimization-protocol`); they never accept engagements. Script names (e.g. `director-verdict-check.py`) and the `acceptance-log.md` owner column are 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 — user-spec.md / tech-spec.md / research-verdict.md
├── 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
│ └── {iteration}/{flow}.json # network/console/dom snapshots
├── 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. Schema in scripts/lib/ledger.py. Forward-only; pre-ledger engagements get a synthetic legacy_import event at first write.
```
### 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.md`
- `rework-N-brief.md`, `iter-N-summary.md`, `wave-N-recap.md`, anything resembling a per-iteration sidecar
- `findings.md`, `issues.md`, `audit.md` outside `validation-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 N` section inside `validation-log.md` and rewritten `handoff.md`
- "exercised flow" / "smoke run" → `traces/` directory + reference inside `handoff.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)
```markdown
---
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
# 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 lead
```
### 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)
```markdown
# 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)
```markdown
# Engagement plan — {engagement-name}
## Shape
{engagement-type classification + rationale}
## Phases
1. Phase name — owner (mid-lead / specialist) — 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}
```
### `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.
```markdown
# 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 files
```
If a specialist:
- Wrote nothing → re-dispatch with reminder, do NOT pretend they reported.
- Wrote to wrong path (`/tmp/`, `engagement/notes.md`, project-root `report.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:
```markdown
# {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
- None
```
Lead 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.json` line 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}` OR `scope-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.md` OR "N/A — no deploy in this engagement"
## 9. Docs diff
- See `docs-diff.md` OR "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 |
| Mid-lead routing layer | **NONE** (top-lead → specialist direct) | optional (top-lead bypasses mid-lead when phase has 1 specialist; uses mid-lead only when ≥2 specialists need coordination) | required (top-lead → mid-lead → specialists; mid-lead coordinates ≥2 specialists per track) |
| 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.
### Mid-lead dispatch policy (anti-overengineering rule)
Mid-leads exist to coordinate ≥2 specialists working on related deliverables. They are NOT a mandatory routing layer.
**Hard rules:**
| Tier | Mid-lead use | Top-lead must... |
|---|---|---|
| **S** | **FORBIDDEN** | Dispatch the single specialist directly via Task tool. No mid-lead. Period. |
| **M** | **Optional, default-off** | Dispatch specialists DIRECTLY when phase has 1 specialist. Invoke mid-lead ONLY when phase has ≥2 specialists that need coordination (e.g. backend+frontend on shared API contract; copywriter+banner-designer on shared brand voice; ux-designer+ui-designer on shared component decisions). |
| **L** | **Required** | Always route through mid-lead. L-tier means ≥4 specialists / cross-track work — mid-lead coordination is the point. |
**Examples (when mid-lead is REQUIRED vs SKIPPED on M-tier):**
| M-tier scenario | Mid-lead? | Reason |
|---|---|---|
| Dev: copy fix in 1 file by `dev-fullstack-engineer` | **SKIP** `dev-engineering-lead` — direct dispatch | 1 specialist, no coordination overhead |
| Dev: backend (`dev-backend-engineer`) + frontend (`dev-frontend-engineer`) on new API + UI | **USE** `dev-engineering-lead` | 2 specialists, shared contract |
| Dev: only `code-reviewer` + `security-auditor` (validators) | **SKIP** `dev-quality-lead` — direct validator dispatch | Validators run on rules, no leader judgement needed |
| Design: only `design-ui-designer` updating 1 component | **SKIP** `design-product-design-lead` | 1 specialist |
| Design: full redesign with `design-ux-designer` + `design-ui-designer` | **USE** `design-product-design-lead` | 2 specialists, shared design decisions |
| Marketing: landing copy by `marketing-copywriter` only | **SKIP** `marketing-content-lead` | 1 specialist |
| Marketing: campaign with `marketing-copywriter` + `marketing-banner-designer` + `marketing-seo-specialist` | **USE** mid-leads | 3 specialists across 2 tracks |
**Anti-pattern:** invoking mid-lead "for consistency" or "because the role exists" when you have 1 specialist. Direct dispatch is preferred — saves 1 hop, 1 prompt round-trip, 1 layer of message-passing.
**Why this rule:** mid-leads are coordination layer, not routing layer. If there's nothing to coordinate (1 specialist), the routing is pure overhead.
### 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.md` is 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 empty `tasks/INDEX.md`. Same gate as missing `validation-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:
1. STOP rework. Do not start iter-(N+2) on this criterion.
2. Dispatch `agency-intake` via 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}".
3. Wait for secretary to either:
- Edit `criteria.md` (touching only the broken criterion, keeping rest intact, recording change in `scope-sync.md` with director's signature) and unblock.
- Escalate to user: "criterion {id} cannot be met as stated, revise scope or close engagement".
4. 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`, no `ACCEPT pending X`, no `ACCEPT with TODO`, no `ACCEPT — 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`** (now in `references/`) — load that file when `criteria.md` frontmatter has `ux_heavy ∈ {minor, true}` OR the engagement plan introduces a UI surface. Hot-path summary:
- `ux_heavy` is 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 → true` via `scope-sync.md`; never demote.
- `ux_heavy: true` mandates `screens/{iteration}/{theme}/` + `traces/{iteration}/{flow}.json` + handoff §6 referencing real paths + `ux-review` validator in validation-log. Missing any of these on submission = handoff INCOMPLETE (returned unread, doesn't burn iteration budget).
- Trace JSON requires structured `steps[]` with `action / 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`** (now in `references/`) — 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.py` before handoff; `handoff-precheck.py` calls it as a sub-check.
- Non-empty findings → handoff blocked until matched user-OK entry exists in `scope-sync.md` under `## 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`** (now in `references/`) — 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.md` with `### 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`** (now in `references/`) — 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-roll `mv`.
- Archival failure never undoes the verdict. Log as `## Archival pending — {timestamp}` in `acceptance-log.md` and 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.md` itself.
- Source artefact paths — listed in `criteria.md`.
- Protocol reminders ("don't create files outside whitelist", "use heartbeat", "11-section handoff") — those are in `engagement-protocol` skill which the agent loads via frontmatter. Re-pasting them in prompts is noise.
- Anti-pattern lists — already in agent's `## Anti-patterns` section.
- 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`** (now in `references/`) — 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_at` newer 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_at` older 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 (top-leads, mid-leads) dispatched via Task tool 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.
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
```markdown
## 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
- **Top-lead phases:** intake-understanding, plan, dispatch (per mid-lead), cross-cutting validation, docs-pipeline, self-acceptance, handoff. → 6-8 heartbeats per iteration.
- **Mid-lead phases:** intake-from-top, plan, dispatch (per specialist), aggregate, return-up. → 4-5 heartbeats per dispatch.
- **Specialists** do NOT heartbeat — they are short-lived and return synchronously to mid-lead.
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 `S` engagement
- 15 minutes for `M`
- 30 minutes for `L` (mid-lead may legitimately spend ≥30min in a single complex phase)
→ 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.
```bash
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 / mid-lead | `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:
```bash
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-precedence
```
Omit `--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`** (now in `references/`) — 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/ --json` after 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 --json` after Phase 2 and every heartbeat. Exit 1 with `--auto-promote` rewrites `criteria.md` `size:` and appends `## Auto-promote` to `scope-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.py` sub-check `size-drift` REJECTs 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)
```bash
python ~/.claude/scripts/preflight.py --criteria engagement/criteria.md --json --auto-fix
```
Purpose: 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)
```bash
python ~/.claude/scripts/handoff-precheck.py engagement/ --json
```
Purpose: 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: 11 checks (S + handoff-sections, self-acceptance-thinness, iteration-counter, validator-outputs, size-drift)
- L-tier: 19 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)
```bash
# 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
```bash
python ~/.claude/scripts/handoff-precheck.py engagement/ --json
```
The wrapper runs:
1. **whitelist** — `ls engagement/` against the closed allowed list (no rogue `preview.md` / `compliance.md`).
2. **criteria-frontmatter** — `criteria.md` has `engagement`, `domain`, `ux_heavy`, `tools_required`.
3. **preflight** — every tool in `tools_required` is reachable (delegates to `preflight.py`).
4. **handoff-paths** — every path cited in `handoff.md` exists on disk (delegates to `handoff-paths-check.py`).
5. **handoff-sections** — required sections §1–§5, §7, §8, §11 present; §6 present if `ux_heavy: true`.
6. **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`** (now in `references/`) — load that file when an engagement crosses domains. Hot-path summary:
- Primary in `engagement/`; secondary in `engagement-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`:
```json
{
"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:
1. **Normative precedence (highest → lowest):**
`CLAUDE.md` > explicit judge decision > `criteria.md` > PROTOCOL skills > METHODOLOGY skills > agent body > frontmatter.
2. **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).
3. **Frontmatter has zero behavioral authority** — it only declares what must be loaded. Skill `description:` text is not enforceable behavior.
4. **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.
5. **Between same-tier skills (two PROTOCOLs, two METHODOLOGYs), the narrower scope wins** unless it weakens a mandatory check; then the stricter rule wins.
6. **Any unresolved conflict is a blocking `authority_conflict` event.** Dispatch halts; escalate to the human judge before proceeding. Manager logs the conflict + resolution in `acceptance-log.md` §"Authority adjudications" (free-form list for now; typed payload once event ledger lands).
7. **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:**
```python
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_conflict` helpers above are the Python API used by orchestrators (`engagement_lg.py`, the LG engines). A lead invoked via `claude -p --agent` is a markdown agent and cannot run them inline — it uses the thin `ledger-emit.py` CLI instead: *lifecycle* events (phase / dispatch / handoff) plus, now, the two Authority modes `--snapshot-skills` (rule 7) and `--authority-conflict` (rule 6) — see §"Event emission" under Lead heartbeat. A blocking rule-6 conflict (omit `--resolution`) emits `verdict=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.md` first; respect `ux_heavy` and `tools_required`.
- Write only to `engagement/executor-reports/{your-name}.md` (single output channel, append-only across iterations with `## Iteration N` headings).
- 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-N` verbatim, 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 (mid-lead duty)
Mid-leads sit between the top-lead (engagement-aware) and specialists (variably engagement-aware). The mid-lead is the conduit that prevents criteria from being lost mid-flight.
### Dispatching DOWN
Every Task a mid-lead spawns for a specialist MUST include in its prompt:
- Absolute path to `engagement/criteria.md`.
- The 1–3 criteria-bullets this specialist's work directly addresses (verbatim from criteria.md, not paraphrased).
- The `ux_heavy` flag value and `tools_required` list.
- Where the specialist must write their executor report (`engagement/executor-reports/{their-name}.md`).
If a specialist returns without addressing the assigned criteria → mid-lead re-dispatches with explicit reminder. Mid-lead does NOT escalate up to top-lead with "specialist didn't follow criteria" — enforcement is the mid-lead's job one level down.
### Returning UP
Mid-lead's return-to-top-lead summary MUST contain:
1. **Criteria coverage matrix** — table mapping each `criteria.md` bullet the mid-lead owned to:
- Specialist who addressed it
- Their executor-report path + relevant section anchor
- Status: covered / partial / blocked
Example:
| Criterion | Specialist | Evidence | Status |
|---|---|---|---|
| Done-when item 2: API returns 201 | dev-backend-engineer | executor-reports/dev-backend-engineer.md §"POST /items" | covered |
| Done-when item 4: dark theme | (none assigned in my scope) | — | n/a — handled by other mid-lead |
2. **Cross-contract claims passed up** — verbatim claims from specialists that touch contracts other mid-leads' specialists may also touch. Top-lead reconciles these into handoff §4a cross-validation table.
3. **Blockers raised by specialists** — passed up as-is, not buried.
### Anti-patterns (mid-lead duty)
- Don't paraphrase criteria when dispatching. Specialists need verbatim text or they drift.
- Don't return "all done ✓" without the coverage matrix. Top-lead cannot verify your assertion without the table.
- Don't filter specialists' cross-contract claims thinking "those don't conflict" — pass them up; reconciliation is the top-lead's job.
## 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 reason `validation 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.
No comments yet. Be the first to comment!