event-modeling-engagement
Use when leading an engagement: building a named set of ratified event-modeling slices (`em engagement`) into one independent PR per slice, each against main, by dispatching the em-implementer, em-val
SKILL.md.claude/skills/event-modeling-engagement/SKILL.md
GitHubEvent Modeling — engagement (lead session)
Goal: take an engagement (a named chunk of the model, <model dir>/engagements/<slug>.md) from
its plan to merged PRs. You are the lead: you cut worktrees, dispatch scoped sub-agents,
record every step in the Ledger with em engagement set, and hand the human one PR per slice,
each cut from and targeting main, to merge in any order. You never write slice code, never
merge, and never rebase a slice branch.
An engagement is a related set of isolated slices — a workflow, a lifecycle — built together. It is not a stack: no slice branch is ever cut from another slice's branch, and no PR ever targets another PR (MIL-280). The foundation PR (implement.md §8) already holds every event, and a slice never reaches into a sibling's code (§8 rule 3), so each PR compiles, tests and merges on its own. Levels order the work for the human's review and your dispatch; they never gate a merge.
Read reference/engagement.md before the first dispatch. It holds the Ledger state machine,
the evidence bundle format, the gap protocol and the model-selection rule this file uses by
name. Each sub-agent runs event-modeling-implement's contract
(../event-modeling-implement/reference/implement.md) for its one slice; that contract is
theirs, not yours.
The sub-agents are em-implementer, em-validator, em-reviewer and em-critic. Under the
plugin their dispatch names are em:em-implementer, em:em-validator, em:em-reviewer and
em:em-critic. In a vendored repo they live in .claude/agents/; if they are missing, STOP and
tell the user to run em skill sync (or em upgrade).
Throughout, <model>.em is the model file and <slug> the engagement's name. Every command
below is real; run it as written.
1. Preconditions — STOP on each
Run the shared preconditions first (tool check, bundle currency, locate the model):
../event-modeling-shared/reference/operating-principles.md ("Preconditions"). Then:
- Constitution ratified.
em status <model>.emnames the constitution (constitution.mdbeside the model, or.specify/memory/constitution.md). STOP if it is missing or itsratifiedBy:is empty (spec-kit: no**Ratified**:footer). Say so and point atevent-modeling-implement's §7; never draft house rules to get moving. - Foundation is in the code. Do not ask the human whether the foundation PR (implement.md
§8) landed — check:
em engagement foundation <model>.em <slug> --code <repo root>(GH #240;--jsonwhen you need the fields). It reports every event the engagement's slices emit or consume aspresent,incomplete(which fields, which tag fields are missing — named), ormissing-event. Anymissing-event: STOP — the foundation has not landed for that event; ask for the foundation PR and confirm it merged intomain(gh pr view <n> --json stateshowsMERGED), then re-run. Anyincomplete: one question to the human, listing every gap the report names (an implementer never discovers these alone) — the fix is a foundation follow-up onmain, never a slice PR. Allpresent: go on. No slice branch is cut before this is clean. The check is a text scan of the target tree, not a compiler: apresentevent may still fail to type-check in the slice PR, and that is the slice PR's job to surface. - Agent models. Read the line
- **Agent models:** implementer=<model>, validator=<model>, reviewer=<model>, critic=<model|codex>. If it is absent, say once: "no Agent models line — using implementer=sonnet, validator=sonnet, reviewer=sonnet, critic=opus", and proceed. Apply the model-selection rule inreference/engagement.md(the critic never runs on the implementer's model). A constitution that still carries a- **Merge strategy:**line from em ≤ 1.14.2 is fine: the line is ignored, never a reason to stop — PRs merge with whatever strategy the project uses. - Worktrees are ignored.
git check-ignore -q .claude/worktrees/xmust succeed. If it fails, STOP and ask the human to add.claude/worktrees/to.gitignorein its own commit onmain. - The engagement exists.
em engagement status <model>.em <slug>. If there is no such engagement, ask the human which slices it covers (a list, a context, a storyline, or "everything downstream of X") and runem engagement new <model>.em <slug> --slices a,b,c(or--context <C>,--storyline <name>, or--downstream-of <ref>)--by "<their name>". First check the host repo's PR-title convention (a commitlint config, a PR-title workflow, or the last few merged PR titles) and pass--pr-title "<template>"when it differs from the default<key>: <slice name>; placeholders are{key},{slice}(display name) and{sliceKey}(kebab key, usually what a lint wants). Whennewwarns that the selection has several unconnected components, relay its suggestion (one engagement each) and let the human decide before going on.
2. Plan and confirm the ceiling once
Run em engagement plan <model>.em <slug> (--json when you need the fields). Present it in
plain terms: each level, its width against the ceiling (parallel), each slice's branch (every
base is main), and every held slice with its reason (not-ready,
upstream-outside-engagement-unmerged, or human). Then the plan's pre-flight: block (GH
#239): the model-wide gaps the engagement's slices share — a repeated-invariant with no single
enforcing slice, an event-without-tag whose consistency boundary is undeclared, a
shared-trigger (one ui, two slices, no endpoint owner), a doc-model-mismatch, a shape (a
em validate --shape lint fires on the slices; the answer is a shape-accepted: <rule> <refs> — <reason> bullet in .event-modeling.md ## Decisions log, or a model fix). These are
exactly what every implementer would otherwise stop on and report separately. Put them to the
human in the same single question round as the ceiling, one bullet each with the slices it
spans, and ask for a ruling on each (the answer goes where it belongs: the model, the foundation
PR, a slice doc, or .event-modeling.md for a deferral). Then ask exactly one question:
"Ceiling N from the plan; proceed?"
Ask it once per engagement. If the human names a lower number, use it for this session. Never ask "which slice first": the levels decide the order, and the ceiling decides how many at once. A pre-flight finding never holds a slice: if the human says "proceed anyway" on one, dispatch and make sure each implementer's brief carries the ruling, so no agent rediscovers it. A held slice is never started; the plan releases it when its hold clears.
3. Per level: cut the worktree, then dispatch the implementer
Work level by level, in order: a level's slices are dispatched before the next level's, so the
human sees upstream slices first. Nothing in level n+1 waits for a merge — every slice is cut
from main. Keep up to the ceiling of slices in flight (building, validating or review)
at once, dispatched in parallel; when a slot frees, take the next not-held slice in level order.
For each slice the plan lists as not held:
- Take
basefrom the plan for this slice. It is alwaysmain. Never the current HEAD, never another slice's branch, and never a branch you picked yourself. - If
impl/<key>already exists (git rev-parse --verify --quiet impl/<key>) or.claude/worktrees/<key>already exists, STOP for that slice and report it. Never delete, reuse or reset someone's branch. - Cut the worktree yourself:
git worktree add .claude/worktrees/<key> -b impl/<key> <base> - Record it:
em engagement set <model>.em <slug> <key> --state building --branch impl/<key> --base <base> - Dispatch
em-implementer(em:em-implementerunder the plugin) on the implementer model. Its prompt is ONLY the slice key, the absolute worktree path, the exact pull-request title and, when there is one, the tracking key. The title is the slice'sprTitlefromem engagement plan --json(GH #334), already resolved from the engagement's--pr-titletemplate or the default: pass it verbatim and never compose one. The tracking key is the plan'stracking(GH #241), already resolved: the slice doc's owntracking:wins over the engagement's--tracking, and it isnullwhen neither is set (then say nothing about tracking). Surface anypr-title-fallbackpre-flight entry to the human. It reads its own spec, the constitution and the implement contract. Give it no plan, no other slice, and no merge instructions.
4. Validate, review, criticise
When the implementer returns a PR URL (and a per-invariant test map):
em engagement set <model>.em <slug> <key> --state validating --pr <url>. Check the PR's base:gh pr view <url> --json baseRefNamemust bemain. If not, fix it withgh pr edit <url> --base main. That is a lead step; the implementer never changes a base.- Dispatch
em-validator(em:em-validator) with the slice key, the worktree path and the same tracking key (when the slice has one) and the sameprTitle. It returns one PASS/FAIL line per check andOVERALL: PASSor a failure. - On
OVERALL: PASS:em engagement set <model>.em <slug> <key> --state review. Dispatchem-reviewer(em:em-reviewer) with the slice key and the PR URL. Dispatchem-critic(em:em-critic) with the same two inputs, on the critic model, in fresh context. Never show the critic the reviewer's findings. When the critic iscodex, run the prerequisite checks and thecodex execinvocation inreference/engagement.md("Codex critic") in a fresh.claude/worktrees/review-<pr>, treat its output file as the critic's findings, and fall back toem-critic(saying so in the evidence bundle) if any prerequisite fails. - Compare the two findings lists. A blocker or major finding raised by only one of the two is itself a finding for the human (record it in the evidence bundle).
- Findings go back to the SAME implementer. On a validator FAIL, or any
blocker/majorfinding:em engagement set <model>.em <slug> <key> --state building, then continue the same implementer agent (same session, same branch, same worktree) with the failing check lines or the findings. Never start a fresh implementer for it, and never fix the code yourself. When it reports back, go back to step 4.1: the validator always re-runs before review does. If the same finding survives two rounds, stop the loop and put it in the evidence bundle for the human. minorfindings do not loop. They go in the evidence bundle.
5. Hand the human each PR as it passes
When a slice passes review: em engagement set <model>.em <slug> <key> --state awaiting-merge.
Hand the human the PR with its evidence bundle (reference/engagement.md): the validator table,
the reviewer's and critic's findings and any disagreement, and the per-invariant test map. Every
PR is independent — against main, carrying only its own slice — so the human merges them in
any order, with the project's usual merge strategy (squash included), and nothing else needs
retargeting or restacking. Suggest level order (upstream slices first) because it reads better,
not because anything depends on it.
You never merge.
6. After each human merge
- Update
mainin your own checkout:git switch main && git pull --ff-only. em engagement set <model>.em <slug> <key> --state merged, then remove the worktree:git worktree remove .claude/worktrees/<key>.- When every slice of a level is
mergedorgap, open one follow-up PR for that level frommain:git switch -c engagement/<slug>-level-<n> main, runem slice mark-implemented <model>.em <key> <pr-url>for each slice merged in the level, thenem slice index <model>.em, commit, andgh pr create --base main. The human merges it like any other PR. Ifmark-implementedprintedopened draft v<n+1>for a slice, its doc in this PR readsstatus: draft: say so in the hand-back and paste the command's notice into the PR body —shippedVersion:/implementedIn:are the record that v<n> was implemented,status:is the next version's state, so the reviewer does not read it as a slice that was never marked. - Recompute:
em engagement plan <model>.em <slug>. A merge can release anupstream-outside-engagement-unmergedhold once that upstream's doc readsimplemented. Continue with step 3 for anything newly startable.
7. Gaps
When the implementer stops on a gap (the doc is wrong, incomplete, or contradicts the
constitution), follow the gap protocol (reference/engagement.md):
em engagement set <model>.em <slug> <key> --state gap.- Add the question as a dated bullet under the model's
.event-modeling.md## Open questions / parking lot:- [ ] YYYY-MM-DD: <key> — <the question, citing the doc line> — source: engagement <slug>. - Continue with the other slices. Leave the gap slice's dependents unstarted, and tell the human which they are. The doc is fixed in a design session, never in this one.
8. End
em state log-usage <model>.em --phases engagement.em engagement status <model>.em <slug>. When it reportsclosable: true(every slicemergedorgap), runem engagement close <model>.em <slug>. Otherwise leave it open: the next lead session resumes from the Ledger.- Report: what merged (PR links), what is awaiting merge, what is held and why, each gap with its question, and every unresolved reviewer or critic disagreement.
Never do
| Rule | Because |
|---|---|
Never hand-edit engagements/<slug>.md |
em engagement set and close are the only write paths; the Ledger table is generated |
| Never cut a slice branch from another slice's branch, or make a PR target another PR | Every slice PR is cut from and targets main (MIL-280); a stack turns isolated slices into one landing and adds merge ceremony the contract never needed |
| Never rebase a slice branch | The branch is the implementer's; the lead orchestrates and records, it does not rewrite history |
| Never run the implementer's work yourself | The lead orchestrates; code comes from a scoped implementer working from its own read of the spec |
| Never dispatch beyond the ceiling | The ceiling was confirmed once for the engagement; exceeding it breaks that agreement |
| Never cut a slice branch from the current HEAD | The base is main from the plan; the lead's HEAD may be anything |
| Never hold a PR back for an upstream merge | Slices do not depend on each other's branches (§8 rules 2 and 3); level order is for reading, not gating |
| Never merge a PR | The human merges, in any order |
| Never show the critic the reviewer's findings | The critic's value is an independent view; disagreement is a finding |
| Never start a held slice, or ask "which slice first" | The plan decides order and holds; the human confirms only the ceiling |