@deftai/directive-content 0.89.0 → 0.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/coding/coding.md CHANGED
@@ -4,6 +4,7 @@
4
4
  <!-- Regenerate with: task packs:render -->
5
5
  <!-- Edit the source, not this file. Slice instead of loading every coding doc: task packs:slice rules by-tier --tier <TIER> (or by-domain, list) -->
6
6
 
7
+
7
8
  # Coding Guidelines
8
9
 
9
10
  Software development specific guidelines for AI agents.
@@ -83,6 +84,13 @@ See [../scm/git.md](../scm/git.md) for:
83
84
  - ! Comments explain **why**, code shows **what**
84
85
  - ⊗ Clever code over clear code
85
86
 
87
+ **State & Data Modeling (#1695):**
88
+ - ! A field MUST encode exactly one fact. Do NOT overload a field's value — or its presence/absence — to also signal a second orthogonal concern. Smuggling decision-, config-, lifecycle-, or control-state through a data field is *in-band signaling*; give that signal its own out-of-band field.
89
+ - ! "Absence is not a decision." Distinguish "unset / never considered" from "deliberately set to the default." If a workflow must know a human made a choice, record the choice explicitly — never infer it from whether a value-field is present.
90
+ - ~ Orthogonality test: if two facts can vary independently (e.g. value==default while decided ∈ {true,false}), they MUST live in separate slots. If one fact strictly implies the other (true Optional<T>, tombstones), sharing a slot is fine.
91
+ - ⊗ Infer decision / onboarding / configuration state from the presence of a value field. Use an explicit out-of-band marker — cf. the resolver `source` provenance pattern (typed | default | default-on-error) directive already uses for *value*-provenance.
92
+ - See [../patterns/in-band-signaling.md](../patterns/in-band-signaling.md) for the full model, orthogonality procedure, and the wipCap worked example (#1694).
93
+
86
94
  ## Quality Standards
87
95
 
88
96
  **General:**
package/commands.md CHANGED
@@ -90,6 +90,7 @@ Scope xBRIEFs live under `xbrief/{proposed,pending,active,completed,cancelled}/`
90
90
  Common commands:
91
91
 
92
92
  - `task scope:promote -- xbrief/proposed/<file>.xbrief.json` -- move proposed work to `pending/` and set status to `pending`.
93
+ - `task scope:promote -- --batch` -- batch-promote **all** `xbrief/proposed/` scopes to `pending/` in one command (#3011 / epic #3009). Optional: `--batch <path>…` for an explicit list; `--force` overrides WIP cap (logged). Does **not** activate; implement remains one `scope:activate` at a time.
93
94
  - `task scope:activate -- xbrief/pending/<file>.xbrief.json` -- move accepted work to `active/` and set status to `running`.
94
95
  - `task scope:complete -- xbrief/active/<file>.xbrief.json` -- move running work to `completed/` and set status to `completed`.
95
96
  - `task scope:fail -- xbrief/active/<file>.xbrief.json` -- mark running work failed when the scope cannot complete.
@@ -109,7 +110,9 @@ deft xbrief:preflight -- xbrief/active/<file>.xbrief.json
109
110
 
110
111
  Gate 0 `task verify:story-ready` machine-checks working-tree cleanliness (or `--allow-dirty`), the target xBRIEF in `xbrief/active/` with `plan.status == "running"`, and the dispatch envelope's `## Allocation context` consent token (#1378). A `swarm-cohort` section is ready only when `allocation_plan_id` AND `batching_rationale` are non-null. Complete stories with `task scope:complete -- <active-story-path>`.
111
112
 
112
- **Story Start Gate (#1378):** Before starting any new implementation story or switching stories, run `git status --short --branch`. If the working tree is dirty, stop and summarize the current branch, modified/untracked files, and whether the changes appear related to the next story — ask the operator to choose: commit existing work, stash existing work, include existing work in the current story, or stop. ⊗ Do not begin a new story while unrelated dirty work is present without explicit operator approval. When invoked as part of a swarm cohort dispatch, the approved Phase 5 allocation plan satisfies batching consent (#954); between stories checkpoint-commit it and proceed — do not pause to ask the operator mid-cohort. Promote/activate via `task scope:promote -- <path>` / `task scope:activate -- <path>`; preflight with `deft xbrief:preflight -- <active-story-path>`.
113
+ **Story Start Gate (#1378):** Before starting any new implementation story or switching stories, run `git status --short --branch`. If the working tree is dirty, stop and summarize the current branch, modified/untracked files, and whether the changes appear related to the next story — ask the operator to choose: commit existing work, stash existing work, include existing work in the current story, or stop. ⊗ Do not begin a new story while unrelated dirty work is present without explicit operator approval. When invoked as part of a swarm cohort dispatch, the approved Phase 5 allocation plan satisfies batching consent (#954); between stories checkpoint-commit it and proceed — do not pause to ask the operator mid-cohort. Promote/activate via `task scope:promote -- <path>` (or `task scope:promote -- --batch` for multi-scope pins, #3011) / `task scope:activate -- <path>`; preflight with `deft xbrief:preflight -- <active-story-path>`.
114
+
115
+ **Multi-scope turn/cache budget (#3009):** After offline seed (init + pin scopes + session ritual), implement agents ⊗ re-run `directive init`, cold `session:start`, migrate, or re-copy scopes — recover with `session:ready` / re-arm only (#3010). Batch-promote the pin; activate+implement one scope at a time (#3011). Run full `task check` once at end of the multi-scope batch unless the last check failed (#3012). Init seeds a minimal render-ready `PROJECT-DEFINITION`; treat `project:render` as a one-shot lifecycle refresh, not identity research (#3013).
113
116
 
114
117
  The implementation gate succeeds only for active scope xBRIEFs with `plan.status == "running"`. Do not infer implementation intent from lifecycle vocabulary — require explicit action-verb directives (`build`, `implement`, `ship`, `swarm`, `run agents`, `start agent`) per #810.
115
118
 
@@ -140,9 +143,11 @@ flowchart TD
140
143
 
141
144
  Edit the xBRIEF source, then render the markdown view.
142
145
 
143
- - `task spec:render` -- render `xbrief/specification.xbrief.json` to `SPECIFICATION.md`.
146
+ - `task spec:render` -- render `xbrief/specification.xbrief.json` to a **compact** `SPECIFICATION.md` by default (#1566). Defaults: no lifecycle Scope outlook, no `LegacyArtifacts`. Opt in with engine flags passed after `--`:
147
+ - `--include-scopes=off` (default) / `current` (pending+active) / `all` (include completed archive)
148
+ - `--include-legacy-artifacts=on|off` (default off)
144
149
  - `task prd:render` -- render a stakeholder PRD view from the specification xBRIEF.
145
- - `task roadmap:render` -- render `ROADMAP.md` from lifecycle scope xBRIEFs.
150
+ - `task roadmap:render` -- render `ROADMAP.md` from lifecycle scope xBRIEFs (`pending/` + `proposed/` + `active/` forward; `completed/` capped).
146
151
  - `task project:render` -- refresh the `PROJECT-DEFINITION.xbrief.json` items registry from lifecycle folders.
147
152
  - `deft xbrief:validate` -- validate xBRIEF schema, filenames, folders, statuses, and cross-file consistency.
148
153
  - `deft migrate:xbrief` (or `task migrate:xbrief`) -- convert a legacy `vbrief/` project tree to `xbrief/` (v0.6→v0.8 semantic transforms; requires clean working tree unless `--force`). Legacy `vbrief/` and `x-vbrief/` tokens remain read-accepted until this runs.
@@ -237,7 +242,7 @@ When the workflow needs an Approach 1 monitor, scope the Cursor leaf `stop-at: p
237
242
  - Repair missing/drifted entries: `deft update`.
238
243
  - **Refresh and opt-out (#2790, #2752):** Upgrade `@deftai/directive`, then run `deft update` to refresh all four deposits to the fast path; do not hand-edit host hook files. Set `plan.policy.hostHooks.<host>` to `false` only when you deliberately need to disable a host's Tier-1 enforcement — it is not the performance fix. When a host is opted out, `deft update` / `directive init` skip creating or re-merging Directive-managed hook entries for that host; if a prior deposit left managed entries in the file, the next update strips only those entries and preserves unrelated settings. Inspect with `deft policy:show --field=hostHooks`. Doctor and `verify:hooks-installed --scope=agent` treat opted-out hosts as healthy — they do not recommend `deft update` to repair them.
239
244
  - **Claude matcher scope:** Once `.claude/settings.json` hooks are loaded, Claude's `PreToolUse` matcher keys on tool names (`Edit`, `Write`, …), not target paths — matched tools can be gated for the whole session, including writes outside the project tree. Opt out of Claude hook deposit when that posture is unwanted.
240
- - **Compact re-arm (#2113):** Cursor `preCompact` and Claude/Grok `PreCompact`/`PostCompact` call `deft-hook --event session.compact` to mark the gated session ritual stale after context compaction/resume; the existing PreToolUse gate then denies direct writes until `deft session:start` and `deft verify:session-ritual -- --tier=gated`. Codex has no native compact hook — operators must re-run the mutation ritual manually after compaction.
245
+ - **Compact re-arm (#2113 / #2992 / #2993):** Cursor `preCompact` and Claude/Grok `PreCompact`/`PostCompact` call `deft-hook --event session.compact` to mark the gated session ritual stale after context compaction/resume (`rearm_needed`). Prefer `deft session:ready` (#2993) as the one-shot recovery path. Multi-step remains valid: `deft session:start --rearm` (preferred when worktree/HEAD allow) or full `deft session:start`, then `deft verify:session-ritual -- --tier=gated`. Codex has no native compact hook — operators must re-run the mutation ritual manually after compaction.
241
246
  - Codex project hooks are trust-gated by Codex. Directive verifies only that the registrations are structurally current; after an install or changed hook hash, open `/hooks` in Codex and review/approve the project hook commands. Runtime trust cannot be inferred from the file alone.
242
247
  - Directive writes only `.codex/hooks.json`; it does not parse or modify `.codex/config.toml`. Codex can also load inline hooks from `config.toml`, so avoid defining duplicate Directive commands there or they may run more than once. See the [Codex hooks documentation](https://learn.chatgpt.com/docs/hooks).
243
248
  - The P0 hook slice does not classify shell-mediated *file* writes, richer unified-exec calls, or WebSearch by default. **Runtime authority (#1394 / #2711)** adds opt-in path allow/deny lists and graduated `scopes` (`edits`, `push`, `merge`) under `plan.policy.runtimeAuthority` — inspect with `deft policy:show --field=runtimeAuthority`. When `enabled: true`, PreToolUse denies classifiable direct-write targets outside `allowPaths` or matching `denyPaths` after ritual/scope/read-only gates; `scopes.edits` gates all direct writes. `scopes.push` / `scopes.merge` deny classifiable Shell/Bash (`git push`, `gh pr merge`) and classifiable MCP push/merge tool names; unclassifiable shell/MCP calls fail open (see `content/contracts/runtime-authority.md`). **Unified path write fence (#516 / #2443 / #2948 Wave 3):** PreToolUse also intersects project allow/deny with the active story’s `plan.metadata.swarm.file_scope` via `resolveWriteFence` (single evaluation SoT; optional `writeScope` alias normalizes at read-time only). Full contract: `content/contracts/path-write-fence.md`.
@@ -261,9 +266,14 @@ Full always-on contract for the interactive session-start ritual and its gated v
261
266
  ### Mutable ritual (mutation posture)
262
267
 
263
268
  - ! On **mutation** session start, run `deft session:start` (or `task session:start` in framework source) after loading AGENTS.md. Records quick-tier ritual in `.deft/ritual-state.json`: alignment confirmation, branch-policy disclosure, `deft verify:tools` guidance, default-branch sync warnings, and `deft triage:welcome` one-liner. State is worktree- and HEAD-bound; stale after `plan.policy.sessionRitualStalenessHours` hours (default 4).
264
- - ~ Mutable `deft session:start` also performs a bounded, non-fatal release-availability probe against the public npm registry after disclosing it. It skips read-only sessions, framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692.
269
+ - ! **Hot path latency (#2991):** by default, mutation `session:start` does **not** block ritual-state write on optional network. It skips the npm release-availability probe and triage cache empty-hydrate / self-heal (`ensureTriageCacheHydrated` / `maybeSelfHealCache`). Targets (operator-facing, not CI-hard): warm hot path typically under a few seconds once tools are on PATH; cold path dominated by local `verify:tools` and git, usually well under ~30s when optional network is off. Empty-cache GitHub fetch-all and npm `view` previously accounted for multi-minute hangs in the WWYSYDH pilot those stay off the critical path unless opted in.
270
+ - ! **Cold vs re-arm ceremony tiers (#2992):** default `session:start` is the **cold** (full) path. After age staleness or compact re-arm (#2113) on the **same worktree** with continuous HEAD and previously-passing quick steps, prefer `deft session:start --rearm` (alias `--tier=rearm`) to refresh the ritual clock + HEAD/worktree bind without `verify:tools`, triage welcome, release probe, or staleness tickler. Full cold remains required for missing/invalid state, worktree change, discontinuous HEAD, first install, or failed/missing quick steps. Compact marks `rearm_needed`; PreToolUse denial and inspect/verify messages prefer re-arm recovery when cold is unnecessary.
271
+ - ? Opt into optional network: `deft session:start -- --with-network` or `DEFT_SESSION_START_NETWORK=1`. When enabled, the bounded release-availability probe runs against the public npm registry (skips framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`). Default-mode triage welcome then also hydrates/self-heals the triage cache. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692 / #2991. Re-arm never runs optional network.
272
+ - ~ `session:start --json` includes `steps[]` with `name` + `duration_ms` for major phases (`alignment`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`), plus total `duration_ms`, `optional_network`, and `ceremony_tier` (`cold` | `rearm`). Skipped optional steps report `skipped: true` and `duration_ms: 0`. Use this for attribution when investigating ceremony wall-clock.
273
+ - ~ **Process-cost events (#2994):** on mutation `session:start` completion (cold or re-arm), Directive appends a local `session:start` behavioral event to `.deft-cache/events.jsonl` with `ceremony_tier`, `duration_ms`, `exit_code`, and optional `steps[]` (same labels as `--json`). When PreToolUse denies for `ritual-not-ready`, a local `session:ritual-blocked` event records `tool_name`, `code`, and optional `recovery_tier` / `detail`. Always-on best-effort (never blocks ceremony or deny path); not gated on `valueFeedback`; **no remote upload** (Product Insights #2603 is a separate opt-in). See § Process-cost events below.
265
274
  - ~ At safe idle points (clean tree, no in-flight story), mutation session start and `deft scope:complete` may also run the staleness tickler: an interactive, consent-based offer to upgrade Directive (`npm i -g @deftai/directive@latest`) and/or migrate xBRIEF (`deft migrate:xbrief`). Escalation tiers, snooze windows, and opt-out live under `plan.policy.stalenessTickler` — inspect with `deft policy:show --field=stalenessTickler`. State persists in `xbrief/.triage-cache/staleness-tickler-state.json`. Skips framework source checkouts, dirty trees, CI/headless (`DEFT_SESSION_RITUAL_SKIP=1`), and typed opt-out. Refs #2488 / #2489.
266
275
  - ! Before any code-writing tool call or `start_agent` implementation dispatch, run `deft verify:session-ritual -- --tier=gated`. Gated tier fails closed unless quick-tier state is fresh; lazily records `deft doctor` and `deft verify:cache-fresh` entrypoints. Step 0 of the pre-`start_agent` gate stack.
276
+ - ! **One-shot recovery (#2993):** when PreToolUse denies writes for a stale/missing gated ritual, run `deft session:ready` (or `task session:ready`). It composes `session:start` (only when quick-tier is not green) + `verify:session-ritual -- --tier=gated` + `cache fetch-all --force` when `cache_fresh` is the remaining blocker, then re-verifies. Idempotent fast path when gated inspect is already fresh (no unnecessary fetch-all). Flags: `--json`, `--repo OWNER/NAME`, `--with-network` (forwarded to session:start). Prefer this over juggling the multi-step recovery sequence under hook pressure.
267
277
  - ? Postpone with `deft session:start -- --defer step=reason` (`alignment`, `branch_policy`, `triage_welcome`, `doctor`, `cache_fresh`).
268
278
  - Headless workers / CI MAY set `DEFT_SESSION_RITUAL_SKIP=1`; verifier exits 0 but warns when bypass hides failure.
269
279
  - ⊗ Self-report ritual complete without fresh `deft session:start` state; ⊗ bypass `deft verify:session-ritual` before implementation dispatch; ⊗ reorder/skip/merge ritual tiers without operator override.
@@ -274,6 +284,17 @@ Full always-on contract for the interactive session-start ritual and its gated v
274
284
 
275
285
  Agents use this signal to prefer portable syntax and quote zsh-sensitive data such as globs, tildes, `~N`, `!`, and `#`. When a command requires Bash, zsh, PowerShell, or another shell's behavior, invoke that explicit shell rather than relying on implicit execution semantics.
276
286
 
287
+ ### SCM readiness orientation (#2275)
288
+
289
+ `session:start` also reports whether GitHub SCM tooling is usable **in this execution env** (not the install host). Human output includes `[deft scm]` lines; `--json` includes a `scm` object (`ready`, `binary`, `auth_state`, `github_auth_mode`, `runtime_mode`, `injected_token_present`, `skipped_gates`, `detail`, ...). Cold mutation records a `scm_readiness` step in `steps[]`.
290
+
291
+ - Shallow probe (default hot path): PATH ladder `ghx` > `gh`, injected-token env presence, short `gh auth status`.
292
+ - Deep probe when `--with-network` / `DEFT_SESSION_START_NETWORK=1`: full `github-auth-modes` validation (API + optional repo).
293
+ - Session-start never hard-blocks on SCM absence (framework-local gates still run). When not ready it lists skipped SCM-dependent gates (`triage:queue`, `issue:ingest`, `pr:*`, `reconcile:issues`, `cache:fetch-all`, `scm:*`, ...).
294
+ - Explicit probe: `deft scm:status` (alias `scm:readiness`) -- exit `0` ready / `1` not ready / `2` config; flags `--json`, `--deep` / `--shallow`.
295
+ - Credential bridging: host-gh (`gh auth login` in the execution env) or injected-token (`GH_TOKEN` / `GITHUB_TOKEN` / `GH_ENTERPRISE_TOKEN`). Never put token values in prompts or transcripts.
296
+ - Contract: `content/contracts/scm-readiness.md`; operator docs: `content/scm/github.md` § Mismatched/headless SCM readiness.
297
+
277
298
  **Pre-`start_agent` gate stack (#1149/#1348):** (0) `deft verify:session-ritual -- --tier=gated` → (1) `deft verify:story-ready` → (2) `deft xbrief:preflight` → (3) `deft verify:cache-fresh` → (4) `deft verify:branch` + hooks → (5) `start_agent`.
278
299
 
279
300
  ```mermaid
@@ -288,6 +309,23 @@ flowchart TD
288
309
 
289
310
  ---
290
311
 
312
+ ## Process-cost events (#2994)
313
+
314
+ Local ceremony cost signal for WWYSYDH / weekly process rollups. Emits to `.deft-cache/events.jsonl` only (same ledger as other behavioral events). Does **not** require Product Insights (#2603).
315
+
316
+ | WWYSYDH / ceremony label | Event name | When | Key payload fields |
317
+ |---|---|---|---|
318
+ | Session start (cold) | `session:start` | Mutation `session:start` cold path finishes | `ceremony_tier=cold`, `duration_ms`, `exit_code`, `steps[]` |
319
+ | Session re-arm | `session:start` | `session:start --rearm` finishes | `ceremony_tier=rearm`, `duration_ms`, `exit_code`, `steps[]` |
320
+ | PreToolUse ritual deny | `session:ritual-blocked` | Hook blocks write/spawn because gated ritual is not ready | `tool_name`, `code=ritual-not-ready`, `recovery_tier` (`cold`\|`rearm`) |
321
+ | Per-step wall-clock | (field on `session:start`) | Same emit as session start | `steps[].name` + `steps[].duration_ms` (`alignment`, `scm_readiness`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`) |
322
+
323
+ CLI mirror (no JSONL required): `deft session:start --json` already exposes the same `steps` / `duration_ms` / `ceremony_tier` fields for one-shot inspection.
324
+
325
+ Registry: `content/events/registry.json`. Helper: `packages/core/src/session/process-cost.ts`.
326
+
327
+ ---
328
+
291
329
  ## Framework behavioral events (#635 / #2631)
292
330
 
293
331
  Review-cycle merge-gate approval is recorded as a structural artifact, not prose-only.
@@ -299,6 +337,32 @@ Writes a `plan:approved` record to `.deft-cache/events.jsonl` with repository (d
299
337
 
300
338
  ---
301
339
 
340
+ ## Lifecycle folder stats (#2995)
341
+
342
+ Local, offline inventory of existing `xbrief/` (or legacy `vbrief/`) lifecycle folders `{proposed,pending,active,completed,cancelled}/` for weekly process rollups (WWYSYDH Section C). No network.
343
+
344
+ ```bash
345
+ deft lifecycle:stats --since=7d
346
+ deft lifecycle:stats --since=7d --json
347
+ task lifecycle:stats -- --since=7d --json
348
+ ```
349
+
350
+ | Field | Folder semantics |
351
+ |---|---|
352
+ | `promoted` | Currently in `pending/`, event time inside `--since` window |
353
+ | `activated` | Currently in `active/`, event time inside window |
354
+ | `completed` | Currently in `completed/` with status completed (or unset), event time inside window |
355
+ | `cancelled_or_failed` | Currently in `cancelled/`, or `completed/` with status `failed`, event time inside window |
356
+ | `still_active` | Snapshot of all briefs in `active/` (not filtered by `--since`) |
357
+
358
+ **Event time:** most recent of `plan.metadata.completedAt`, `plan.updated`, and `xBRIEFInfo`/`vBRIEFInfo`.`updated`; else file mtime. Window is `[as_of - since, as_of]`.
359
+
360
+ **Limitation:** counts are **current-folder membership**, not full transition history. A brief promoted then activated in the same week appears under `activated` / `still_active`, not `promoted`. Default `--since` is `7d` (`24h`, `1w`, ISO-8601 durations accepted).
361
+
362
+ `--json` includes the same counts plus `folder_totals`, `window_start` / `as_of`, and a `semantics` object documenting the definitions above.
363
+
364
+ ---
365
+
302
366
  ## Backlog Triage And Cache Tasks
303
367
 
304
368
  User-facing surface for the Phase 0 triage workflow and the unified content cache. These commands let agents work an existing backlog locally without repeatedly draining shared GitHub rate limits.
@@ -366,6 +430,7 @@ flowchart TD
366
430
 
367
431
  - `task packs:*` -- render and verify content packs.
368
432
  - `task pr:*` -- protected issue checks, closing-keyword checks, merge readiness, and merge helpers.
433
+ - `task pr:check-closing-keywords` -- Layer 0 FP lint (#737) **plus intent mode** (#3015 class D). Default `--mode both`: fails on negation/quote/example/code-block hits **and** on any real `Closes|Fixes|Resolves #N` unless allowlisted via `--allow-close N,M` (CLI only; body trailers are not an authorization path). Offline: `--body-file` / `--commits-file`. Prefer `Tracking: #N` / `Refs #N` until full issue DoD.
369
434
  - `task release:*` -- release, publish, rollback, and e2e release rehearsal.
370
435
  - Step 3 (`Pre-flight vBRIEF lifecycle sync`) fetches GitHub issue states via REST. On HTTP 403 rate-limit exhaustion it sleeps once (capped at 120s) and retries before failing.
371
436
  - When Step 3 still fails with rate-limit exhaustion, stderr includes a `gh api rate_limit` probe (`core.remaining`, reset time) and recovery guidance. After local `task vbrief:validate` (or `task xbrief:validate`) exits 0, operators may pass `--allow-vbrief-drift` to skip Step 3 for that cut — reserved for transient SCM bucket stalls, not unreviewed lifecycle drift.
@@ -67,6 +67,16 @@ OpenClaw cold-start steps live in the swarm host adapter (source of truth for sp
67
67
 
68
68
  Operator map (non-authoritative discovery): [`../docs/openclaw-agent-host.md`](../docs/openclaw-agent-host.md).
69
69
 
70
+ ### OpenClaw pin wire (doctor) (#3001)
71
+
72
+ Session-first hosts still need a **bridge** from the installed content package into the host workspace skills root. On OpenClaw:
73
+
74
+ - ! `deft doctor` detects missing always-pins under `$OPENCLAW_STATE_DIR/workspace/skills` or `~/.openclaw/workspace/skills` when OpenClaw signals are present.
75
+ - ! `deft doctor --fix` deposits/links the four always-pins (build, pre-pr, review-cycle, swarm) into that main skills root (symlink preferred; copy fallback). Non-destructive of other user skills.
76
+ - ⊗ Rewrite every `workspace-*` crew seat by default — multi-seat only with `deft doctor --fix --openclaw-all-agents`.
77
+
78
+ Full operator steps: [`../docs/openclaw-agent-host.md`](../docs/openclaw-agent-host.md) § Wire skills into OpenClaw workspace.
79
+
70
80
  ## Extension notes (out of first ship)
71
81
 
72
82
  | Host / class | Note |
@@ -0,0 +1,78 @@
1
+ # Contract: SCM readiness in mismatched / headless envs (#2275)
2
+
3
+ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
+
5
+ **Parent:** issue #2275 (follow-up to #2203 Decision 7). Sibling of USER.md
6
+ resolution (#2271).
7
+
8
+ ## Problem
9
+
10
+ In agentic sandboxes, execution-env often differs from install-env: `gh` /
11
+ `ghx` may be absent or unauthenticated even when the host desktop is fully
12
+ set up. SCM-dependent gates then fail opaquely (spawn errors, hung auth
13
+ prompts) while framework-local gates still work.
14
+
15
+ ## Gate classes
16
+
17
+ ### Framework-local (no SCM required)
18
+
19
+ `session:start`, `session:ready`, `verify:session-ritual`, `verify:tools`
20
+ (orientation), `verify:branch`, `xbrief:preflight`, `doctor` (offline),
21
+ `scope:*`, local cache-fresh / ritual state.
22
+
23
+ These ! run regardless of SCM readiness.
24
+
25
+ ### SCM-dependent (require binary + auth)
26
+
27
+ At minimum: `triage:queue`, `triage:welcome` network hydrate, `issue:ingest`,
28
+ `reconcile:issues`, `pr:*`, `cache:fetch-all`, `scm:*` issue ops,
29
+ `umbrella:current-shape` live fetch, deep `github-auth-modes`.
30
+
31
+ These ! either run when ready or fail loud with a named reason. They ⊗ hang
32
+ on interactive auth prompts in headless envs without a clear diagnostic.
33
+
34
+ ## Probe surface
35
+
36
+ | Surface | Depth | Blocks session? |
37
+ | --- | --- | --- |
38
+ | `session:start` default | shallow (PATH + token + `gh auth status`) | no |
39
+ | `session:start --with-network` | deep (API + optional repo) | no |
40
+ | `deft scm:status` | shallow default; `--deep` opt-in | n/a (exit 0/1/2) |
41
+ | `deft github-auth-modes` | mode validation (#1557) | n/a |
42
+
43
+ JSON field shape (`session:start --json` → `scm`, or `scm:status --json`):
44
+
45
+ - `ready` (bool)
46
+ - `binary` (`ghx` \| `gh` \| null)
47
+ - `binary_path`
48
+ - `auth_state` (`authenticated` \| `unauthenticated` \| `missing-token` \|
49
+ `binary-absent` \| `unknown`)
50
+ - `github_auth_mode` (`host-gh` \| `injected-token`)
51
+ - `runtime_mode`
52
+ - `injected_token_present` (bool; never the value)
53
+ - `depth` (`shallow` \| `deep`)
54
+ - `detail` (one-line human diagnostic)
55
+ - `remediation` (string \| null)
56
+ - `skipped_gates` (string[])
57
+ - `login` (string \| null; deep only)
58
+ - `failure_kind` (string \| null)
59
+
60
+ ## Remediation contract
61
+
62
+ When not ready, agents ! prefer one of:
63
+
64
+ 1. Install + auth in the **execution** env (`gh` / `ghx`, then
65
+ `gh auth login` for host-gh).
66
+ 2. Inject `GH_TOKEN` / `GITHUB_TOKEN` / `GH_ENTERPRISE_TOKEN` for
67
+ injected-token / cloud-headless mode.
68
+ 3. Run SCM-dependent gates from a matched authenticated environment.
69
+
70
+ ⊗ Put token values into prompts, dispatch envelopes, or logs.
71
+
72
+ ## Implementation anchors
73
+
74
+ - Probe: `packages/core/src/scm/readiness.ts`
75
+ - CLI: `packages/core/src/scm/readiness-cli.ts` (`scm:status`)
76
+ - Session orientation: `packages/core/src/session/session-start.ts`
77
+ - Auth modes: `packages/core/src/intake/github-auth-modes.ts` (#1557)
78
+ - Operator docs: `content/scm/github.md` § Mismatched/headless SCM readiness
@@ -0,0 +1,27 @@
1
+ # Inter-run learning surface
2
+
3
+ **Load when:** designing or implementing cross-session agent memory, hot/cold budgets, frozen snapshots, or retargeting memory pattern issues under epic #2741.
4
+
5
+ Legend (RFC2119): `!`=MUST, `~`=SHOULD, `⊗`=MUST NOT.
6
+
7
+ ## Pointer
8
+
9
+ Canonical contract (Wave 0 design for [#2742](https://github.com/deftai/directive/issues/2742), epic [#2741](https://github.com/deftai/directive/issues/2741)):
10
+
11
+ **[docs/analysis/2026-07-31-inter-run-learning-surface.md](../../docs/analysis/2026-07-31-inter-run-learning-surface.md)**
12
+
13
+ That note inventories Directive memory SoTs (`USER.md` Personal, lessons/packs, triage cache, session ritual, decision/continue), defines **hot / cold / operator-gated** tiers with freeze and budget rules, lists non-goals, and retargets #688, #978, #832–#835, and #479.
14
+
15
+ ## Rules (discovery only)
16
+
17
+ - ! Prefer the design note vocabulary over free-floating “agent-memory contracts” or Mem0-default RAG for Directive core.
18
+ - ! Attach Wave 1+ pattern work (#832–#834, #835, #479) to the tiers and SoTs in the design note.
19
+ - ⊗ Revive `x-vbrief/agent-memory` / `swarm/agent-memory.md` (#2700 solution shape abandoned).
20
+ - ⊗ Implement mid-session mutable always-in hot memory without freeze-at-session-start.
21
+
22
+ ## Related
23
+
24
+ - Session ritual: `content/commands.md` § Session-start ritual
25
+ - Continue checkpoints: `content/resilience/continue-here.md`
26
+ - Prompt assembly (freeze mechanism): `content/patterns/prompt-assembly-layer-ordering.md`
27
+ - Skills Index: `REFERENCES.md` → When Managing Context or Long Tasks
@@ -111,12 +111,50 @@ OpenClaw agents often act under a **bot / service identity** (e.g. `ape-deft`-cl
111
111
 
112
112
  1. Install / refresh Directive like any other host ([QUICK-START.md](../QUICK-START.md), `directive init` / `directive update`).
113
113
  2. Confirm skills resolve under the deposit (consumer: `.deft/core/.agents/skills/…`; framework checkout: `content/skills/…`).
114
- 3. On first PR shepherding request, open **`deft-directive-review-cycle`** and follow its Review Monitoring section for **your** install. Remember the epic expectation: OpenClaw → Approach 1 via `sessions_spawn` once skill wiring ships (#2875 / #2876).
115
- 4. For multi-story parallel work, follow **`deft-directive-swarm`** do not hand-roll worktree orchestration outside the skill.
116
- 5. Keep CHANGELOG / xBRIEF / branch gates the same as on Cursor or Warp; the host changes the **spawn surface**, not the Directive lifecycle.
114
+ 3. **Wire always-pin skills into the OpenClaw workspace** (see next section) so session `available_skills` can load cold-start paths.
115
+ 4. On first PR shepherding request, open **`deft-directive-review-cycle`** and follow its Review Monitoring section for **your** install. Remember the epic expectation: OpenClaw → Approach 1 via `sessions_spawn` once skill wiring ships (#2875 / #2876).
116
+ 5. For multi-story parallel work, follow **`deft-directive-swarm`** do not hand-roll worktree orchestration outside the skill.
117
+ 6. Keep CHANGELOG / xBRIEF / branch gates the same as on Cursor or Warp; the host changes the **spawn surface**, not the Directive lifecycle.
117
118
 
118
119
  ---
119
120
 
121
+ ## Wire skills into OpenClaw workspace (#3001)
122
+
123
+ OpenClaw is a **session-first** host: installing `@deftai/directive` puts pin skills in the content package / deposit, but the **main** OpenClaw workspace may still only list host-global skills until something bridges them into `~/.openclaw/workspace/skills` (or `$OPENCLAW_STATE_DIR/workspace/skills`).
124
+
125
+ Always-pins required for cold-start / process gates ([skill-pin-policy.md](./skill-pin-policy.md) #2508):
126
+
127
+ - `deft-directive-build`
128
+ - `deft-directive-pre-pr`
129
+ - `deft-directive-review-cycle`
130
+ - `deft-directive-swarm`
131
+
132
+ ### Detect
133
+
134
+ When OpenClaw signals are present (`OPENCLAW` / `DEFT_PROBE_OPENCLAW` / `DEFT_AGENT_RUNTIME=openclaw`, or `~/.openclaw` / `OPENCLAW_STATE_DIR`), `deft doctor` checks the **main** workspace skills root for those four pins.
135
+
136
+ - **Miss:** warning + remediation `deft doctor --fix` (plus pointers to this page and [host-lifecycle-duties.md](../contracts/host-lifecycle-duties.md)).
137
+ - **Hit:** success line — OpenClaw host pins present.
138
+
139
+ ### Fix
140
+
141
+ ```text
142
+ deft doctor --fix
143
+ ```
144
+
145
+ Doctor **copies** the four pin directories from the installed content package (`@deftai/directive-content` / `content/skills/…`) into the main OpenClaw skills root as **real directories** (#3008). It does **not** symlink into the npm tree — OpenClaw 2026.7.x skips workspace skills that resolve outside the skills root (`reason=symlink-escape`), which made pre-#3008 symlink installs look healthy in doctor while `openclaw skills list` never loaded the pins. It does **not** delete other user skills (e.g. a local `vbrief` skill stays). Divergent same-named directories **and escaping symlinks** are left alone unless you pass `--force` or confirm on a TTY (then replaced with a real copy).
146
+
147
+ After a successful wire: **restart the OpenClaw gateway or start a new session** so host `available_skills` refreshes. Confirm with `openclaw skills list` that the four always-pins are **ready**, not skipped.
148
+
149
+ ### Multi-seat / crew workspaces
150
+
151
+ Default scope is **main only** (`workspace/skills`). Crew seats (`workspace-scotty`, `workspace-pike`, …) are **not** rewritten unless you opt in:
152
+
153
+ ```text
154
+ deft doctor --fix --openclaw-all-agents
155
+ ```
156
+
157
+ ⊗ Silent rewrite of every `workspace-*` seat without that flag.
120
158
 
121
159
  ---
122
160
 
@@ -156,6 +194,8 @@ Full rules: [`skills/deft-directive-swarm/references/host-openclaw.md`](../skill
156
194
  - ⊗ Claiming this doc alone makes `sessions_spawn` a shipped register/matrix primitive — that is epic skill/engine work (#2875 / #2876).
157
195
  - ⊗ Multi-sentence progress-only first response after `subagent_announce` with zero tools / yield (#2943 text-repetition hang).
158
196
  - ⊗ Treating thin DONE (no PR URL / merge evidence) as success (#2943).
197
+ - ⊗ Assuming package install alone populates OpenClaw `available_skills` — wire main workspace pins via `deft doctor --fix` (#3001).
198
+ - ⊗ Auto-enumerating every `workspace-*` seat without `--openclaw-all-agents` (#3001).
159
199
 
160
200
  ---
161
201
 
package/events/README.md CHANGED
@@ -14,7 +14,7 @@ data-file-convention check follow-up
14
14
 
15
15
  - `registry.json` — single source of truth for every event name, category,
16
16
  payload contract, detector / emission pointer, and consumer pointers.
17
- Lists 5 `detection-bound` events and 9 `behavioral` events (14 total).
17
+ Lists 5 `detection-bound` events and 11 `behavioral` events (16 total).
18
18
  - `registry.schema.json` — schema validating the registry's shape, including
19
19
  the required `category` enum (`detection-bound` | `behavioral`).
20
20
  - `event-record.schema.json` — schema for individual emitted event records.
@@ -238,6 +238,42 @@
238
238
  "packages/core/src/events/attribution-ledger.ts",
239
239
  "(deferred) task deft:feedback:file / deft-directive-feedback skill (#1709 child 5)"
240
240
  ]
241
+ },
242
+ {
243
+ "name": "session:start",
244
+ "category": "behavioral",
245
+ "description": "Mutation session:start ceremony finished (cold or re-arm). Local process-cost observability for ceremony wall-clock (#2994 / WWYSYDH pilot). Always-on best-effort local JSONL; not gated on valueFeedback; no remote upload.",
246
+ "trigger": "packages/core/src/session/process-cost.ts::emitSessionStartProcessCost from packages/core/src/session/session-start.ts after cold or re-arm ceremony completes and ritual state is written.",
247
+ "payload": {
248
+ "ceremony_tier": "string enum -- cold | rearm",
249
+ "duration_ms": "integer -- total ceremony wall-clock milliseconds",
250
+ "exit_code": "integer -- 0 when ready, non-zero when a quick step failed",
251
+ "ready": "boolean (optional) -- true when exit_code is 0",
252
+ "optional_network": "boolean (optional) -- whether optional network steps ran (#2991)",
253
+ "steps": "array (optional) -- { name, duration_ms, skipped? } per major phase from session:start --json"
254
+ },
255
+ "consumers": [
256
+ "packages/core/src/session/process-cost.ts -- emit helper",
257
+ "packages/core/src/session/session-start.ts -- cold + re-arm completion call sites",
258
+ "content/commands.md § Process-cost events -- WWYSYDH ceremony label mapping (#2994)"
259
+ ]
260
+ },
261
+ {
262
+ "name": "session:ritual-blocked",
263
+ "category": "behavioral",
264
+ "description": "PreToolUse denied a mutation tool because the gated session ritual was not ready (missing/stale/invalid). Local process-cost / friction signal for ceremony blocks (#2994). Always-on best-effort local JSONL; not gated on valueFeedback; no remote upload.",
265
+ "trigger": "packages/core/src/session/process-cost.ts::emitSessionRitualBlockedProcessCost from packages/core/src/hooks/dispatcher.ts::inspectMutationGates when inspectSessionRitual returns non-zero or throws (ritual-not-ready deny path).",
266
+ "payload": {
267
+ "tool_name": "string -- host tool name that was denied",
268
+ "code": "string -- always ritual-not-ready for this event",
269
+ "recovery_tier": "string enum (optional) -- cold | rearm -- preferred recovery path (#2992)",
270
+ "detail": "string (optional) -- one-line inspect message (not the full recovery instruction)"
271
+ },
272
+ "consumers": [
273
+ "packages/core/src/session/process-cost.ts -- emit helper",
274
+ "packages/core/src/hooks/dispatcher.ts -- PreToolUse ritual deny path",
275
+ "content/commands.md § Process-cost events -- WWYSYDH ceremony label mapping (#2994)"
276
+ ]
241
277
  }
242
278
  ]
243
279
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-content",
3
- "version": "0.89.0",
3
+ "version": "0.91.0",
4
4
  "description": "Shippable Directive framework content in the consumer .deft/core/ layout (C1 flatten), plus the engine surfaces (.githooks/, Taskfile.yml, tasks/) the deposit wires. Python-free per #2022 Phase 3. Refs #11, #1669, #1967.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -226,7 +226,7 @@
226
226
  "swarm"
227
227
  ],
228
228
  "source": "PR #697 -- issue #642 (a tracking umbrella for PR #401) was auto-closed on squash merge despite the PR body intentionally avoiding closing keywords. Recurrence record extended in PR #735 (#737 deterministic encoding gap-closer); see the third bullet below.",
229
- "body": "**Source:** PR #697 -- issue #642 (a tracking umbrella for PR #401) was auto-closed on squash merge despite the PR body intentionally avoiding closing keywords. Recurrence record extended in PR #735 (#737 deterministic encoding gap-closer); see the third bullet below.\n\n**1. GitHub's closing-keyword parser is substring-based -- the literal token `Closes #N` MUST NOT appear in the PR body even inside a negation, quotation, or example**\n\nThe PR body for #697 contained the parenthetical `` (Intentionally not `Closes #642` -- #642 is a tracking umbrella that should remain open until PR #401's full scope is merged or split into linked follow-up issues.) ``. The text was negating the closing keyword in plain English, but GitHub's auto-close parser operates on token presence, not surrounding semantics. The squash merge processed the literal `Closes #642` substring and closed the issue. The same hazard applies to `Fixes #N`, `Resolves #N`, and the past-tense / lowercase variants (`closed`, `fixed`, `resolved`, etc. -- see GitHub's full keyword list).\n\n**Rule:** When a PR body needs to discuss what it intentionally does NOT close (e.g. tracking umbrellas, partial scope, deferred follow-ups), MUST phrase the disclaimer without using any closing keyword token at all. Use phrasings like:\n\n- \"Intentionally NOT using a closing keyword for #N\" (omits the trigger token entirely)\n- \"Refs #N (tracking umbrella; remains open)\" (use `Refs` only)\n- \"#N stays open as the umbrella anchor for follow-up work\" (no trigger token)\n\n\u2297 MUST NOT write `Closes #N`, `Fixes #N`, `Resolves #N`, or any GitHub closing keyword in a PR body even when negating, quoting, or showing as an example -- the parser does not respect surrounding context.\n\n**2. Post-merge issue-state verification MUST run for every squash merge regardless of intent**\n\nThe existing post-merge verification rule (Lesson: PR Merge Hygiene #1, #167) was framed around closing-keyword failures (issues that should have closed but didn't). The opposite failure mode -- issues that should have stayed open but were auto-closed -- is just as real and is caught by the same check. After every squash merge, MUST verify the state of every issue mentioned in the PR body matches intent: closed if a closing keyword was used; open if only `Refs` was used. If an issue was closed in error, reopen it with a comment referencing the PR and explaining why it should remain open (e.g. tracking umbrella, partial scope). (#697)\n\n**3. Recurrence (#735): the squash-commit body for that PR contained a negation-context clause referencing #734 in a way that auto-closed it on merge despite intent (the issue was the parent for the in-flight #737 work and had to be reopened manually). #737 closes this gap structurally with a deterministic pre-PR lint (`scripts/pr_check_closing_keywords.py`, surfaced via `task pr:check-closing-keywords`) that scans both the PR body AND every commit message for closing-keyword tokens followed by `#\\d+` in negation / quotation / example / code-block contexts and refuses to push when findings surface. The lint is wired into `skills/deft-directive-pre-pr/SKILL.md` Phase 4 (Diff) and cross-referenced from `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1 as the Layer 0 (prevention) surface alongside the existing Layer 3 (recovery) `task pr:check-protected-issues` (#701).**\n\n**Cross-reference:** existing lesson \"PR Merge Hygiene\" #1 (#167); `scm/github.md` PR conventions; `skills/deft-directive-review-cycle/SKILL.md` Post-Merge Verification; `skills/deft-directive-pre-pr/SKILL.md` Phase 4 (Layer 0 prevention, #737); `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1 (Layer 3 recovery, #701)."
229
+ "body": "**Source:** PR #697 -- issue #642 (a tracking umbrella for PR #401) was auto-closed on squash merge despite the PR body intentionally avoiding closing keywords. Recurrence record extended in PR #735 (#737 deterministic encoding gap-closer); see the third bullet below.\n\n**1. GitHub's closing-keyword parser is substring-based -- the literal token `Closes #N` MUST NOT appear in the PR body even inside a negation, quotation, or example**\n\nThe PR body for #697 contained the parenthetical `` (Intentionally not `Closes #642` -- #642 is a tracking umbrella that should remain open until PR #401's full scope is merged or split into linked follow-up issues.) ``. The text was negating the closing keyword in plain English, but GitHub's auto-close parser operates on token presence, not surrounding semantics. The squash merge processed the literal `Closes #642` substring and closed the issue. The same hazard applies to `Fixes #N`, `Resolves #N`, and the past-tense / lowercase variants (`closed`, `fixed`, `resolved`, etc. -- see GitHub's full keyword list).\n\n**Rule:** When a PR body needs to discuss what it intentionally does NOT close (e.g. tracking umbrellas, partial scope, deferred follow-ups), MUST phrase the disclaimer without using any closing keyword token at all. Use phrasings like:\n\n- \"Intentionally NOT using a closing keyword for #N\" (omits the trigger token entirely)\n- \"Refs #N (tracking umbrella; remains open)\" (use `Refs` only)\n- \"#N stays open as the umbrella anchor for follow-up work\" (no trigger token)\n\n\u2297 MUST NOT write `Closes #N`, `Fixes #N`, `Resolves #N`, or any GitHub closing keyword in a PR body even when negating, quoting, or showing as an example -- the parser does not respect surrounding context.\n\n**2. Post-merge issue-state verification MUST run for every squash merge regardless of intent**\n\nThe existing post-merge verification rule (Lesson: PR Merge Hygiene #1, #167) was framed around closing-keyword failures (issues that should have closed but didn't). The opposite failure mode -- issues that should have stayed open but were auto-closed -- is just as real and is caught by the same check. After every squash merge, MUST verify the state of every issue mentioned in the PR body matches intent: closed if a closing keyword was used; open if only `Refs` was used. If an issue was closed in error, reopen it with a comment referencing the PR and explaining why it should remain open (e.g. tracking umbrella, partial scope). (#697)\n\n**3. Recurrence (#735): the squash-commit body for that PR contained a negation-context clause referencing #734 in a way that auto-closed it on merge despite intent (the issue was the parent for the in-flight #737 work and had to be reopened manually). #737 closes this gap structurally with a deterministic pre-PR lint (`scripts/pr_check_closing_keywords.py`, surfaced via `task pr:check-closing-keywords`) that scans both the PR body AND every commit message for closing-keyword tokens followed by `#\\d+` in negation / quotation / example / code-block contexts and refuses to push when findings surface. The lint is wired into `skills/deft-directive-pre-pr/SKILL.md` Phase 4 (Diff) and cross-referenced from `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1 as the Layer 0 (prevention) surface alongside the existing Layer 3 (recovery) `task pr:check-protected-issues` (#701).**\n\n**Cross-reference:** existing lesson \"PR Merge Hygiene\" #1 (#167); `scm/github.md` PR conventions; `skills/deft-directive-review-cycle/SKILL.md` Post-Merge Verification; `skills/deft-directive-pre-pr/SKILL.md` Phase 4 (Layer 0 prevention, #737); `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1 (Layer 3 recovery, #701).\n\n**4. Class D (#3015): premature *real* `Closes #N` with conditional / multi-phase English is a different failure than class A negation FP**\n\nenterprize PR #30 body contained `Closes #29 Phase A intake only if you want intake closed\u2026`. Layer 0 FP mode (#737) exited 0 (no negation/quote/example/code context). GitHub still matched `Closes #29` and completed multi-phase issue #29 while Phase B/C remained undone.\n\n- Class A = keyword in negation/quote/example/code (FP) \u2014 #737\n- Class D = bare or conditional real closing keyword without full DoD \u2014 #3015 intent mode\n\n**Rule:** Default PR body uses `Tracking: #N` / `Related: #N` / `Refs #N`. Use `Closes`/`Fixes`/`Resolves #N` only at full issue DoD, and pass `--allow-close N` on the lint (CLI allowlist only). \u2297 `Closes #N Phase A` / `only if` / partial English around a real closing keyword.\n\n`task pr:check-closing-keywords` default mode is `both` (FP + intent). `--mode fp` is the class-A-only regression path."
230
230
  },
231
231
  {
232
232
  "id": "github-closing-keyword-false-positive-layer-3-persistent-closingissuesreferences-link-2026-04",
@@ -13,6 +13,18 @@
13
13
  "path": "patterns/executor-layer-credentials.md",
14
14
  "body": null
15
15
  },
16
+ {
17
+ "id": "in-band-signaling",
18
+ "title": "No in-band signaling / absence is not a decision (#1695)",
19
+ "description": "Do not overload one field (or its presence/absence) to carry two orthogonal facts. Separate value from decision-provenance. Load when modeling config/policy fields or onboarding/decision/lifecycle state.",
20
+ "triggers": [
21
+ "in-band-signaling",
22
+ "decision-provenance",
23
+ "absence-is-not-a-decision"
24
+ ],
25
+ "path": "patterns/in-band-signaling.md",
26
+ "body": null
27
+ },
16
28
  {
17
29
  "id": "llm-app",
18
30
  "title": "LLM application standards (#481)",
@@ -31,7 +43,7 @@
31
43
  "multi-agent"
32
44
  ],
33
45
  "path": "patterns/multi-agent.md",
34
- "body": "# Multi-agent identity separation pattern (#983)\n\n> Status: stable pattern, v1 docs-only. v1 deliberately scopes to a single\n> shared bot/App credential for all workers; per-worker token rotation is\n> deferred to a follow-up. Per #983 non-goals.\n\nThis pattern lays out *whose* GitHub identity dispatched swarm workers\nshould authenticate as, *why* that should not be the maintainer's PAT,\nand *how* operators provision and rotate the worker credential.\n\nIt is the credential-separation track that was carved out of #976\n(`fix(scm,tests): eliminate remaining GraphQL-backed scm.py / live SCM\nsmoke bucket drain after #961`). #976 owns the REST migration; this\npattern owns the orthogonal identity separation. The two tracks are\ncomplementary: REST migration reduces GraphQL demand on the shared\nbucket, identity separation prevents human and machine traffic from\nsharing one bucket in the first place.\n\n## Why this matters\n\nToday, swarm workers and the maintainer share a single GitHub Personal\nAccess Token (PAT). One PAT serves the maintainer's interactive review\nflow AND every worker spawned by `task swarm:*` / `start_agent` /\n`oz agent run`. The operational consequences observed in the\n2026-05-07 multi-agent session and again on 2026-05-08 are concrete:\n\n- **Bucket coupling**: GitHub's GraphQL bucket is **5,000 req/hr per\n identity**. A swarm of N workers + the maintainer's terminal share\n one bucket. When workers exhaust it (the documented failure mode for\n the PR #652 / #561-class merge cascades), the maintainer's\n `gh issue create` and `gh pr view` start failing with\n `GraphQL: API rate limit already exceeded` -- the human is rate-\n limited by their own swarm.\n- **Audit conflation**: every commit, comment, PR open, issue close,\n and review submission attributes to the same `actor.login`. Forensic\n separation (\"which action was the maintainer vs. an autonomous\n worker?\") requires correlating timestamps and dispatch-envelope\n prompts rather than reading the GitHub event stream directly.\n- **Credential blast radius**: a worker's prompt envelope is by\n definition reviewable / loggable / inspectable -- prompts leak more\n readily than secrets. Today a leaked worker prompt acts with the\n full scope of the maintainer's PAT (issue close, PR merge, release\n publish, branch protection bypass), not the narrow scope a worker\n actually needs (issues:write, pulls:write, contents:read).\n\nReducing GraphQL demand (#976 REST migration) only solves bucket\n*pressure*. It does not solve bucket *coupling*, audit conflation, or\nblast radius. Those require partitioning the identity itself.\n\n## The pattern\n\n### Identity model\n\n- ! Workers MUST authenticate as a **distinct GitHub identity** from\n the maintainer. Two acceptable shapes:\n 1. **GitHub App installation token** (preferred). The deft project\n installs a GitHub App granting the minimum scopes; workers\n consume an installation token derived from the App's private key.\n Tokens are short-lived (~1 hour) and auto-rotated by GitHub.\n 2. **Bot account PAT**. A dedicated GitHub user account (e.g.\n `deft-swarm-bot`) with its own PAT. Lower setup cost, but tokens\n are long-lived until manually rotated, and the bot account\n consumes a real GitHub seat.\n- ! Maintainer PATs MUST be reserved for human-driven work: review,\n merge, release publication, manual triage. Worker dispatch envelopes\n MUST NOT inject a maintainer PAT.\n- \u2297 Workers MUST NOT fall back to the host's `gh auth status` token.\n The dispatch envelope is the contract; an implicit fallback re-\n introduces the coupling this pattern eliminates.\n\n### Bucket partitioning model\n\n| Bucket | Owner | Use |\n|----------------------|----------------------|----------------------------------------------------|\n| Maintainer GraphQL | Maintainer PAT | Human review, PR open/merge/ready, manual triage |\n| Maintainer core REST | Maintainer PAT | `gh api` reads outside swarm context |\n| Worker GraphQL | Bot account / App | `markPullRequestReadyForReview`, `addPullRequestReview` (the only GraphQL surfaces a worker should hit; see #976 + #961) |\n| Worker core REST | Bot account / App | All other reads / mutations via `gh_rest.py` REST helpers |\n\nEach bucket is 5,000 req/hr (REST `core` and GraphQL each). With\nidentity separation the maintainer's two buckets are untouched by\nswarm activity, and N workers share *their own* two buckets.\n\nThe bucket partition only delivers value when paired with the #976\nREST migration: workers that still route reads through GraphQL\n(`gh issue view --json`, `gh pr view --json`, `gh pr ready`) will\nexhaust their own GraphQL bucket within minutes under N concurrent\npoll loops. REST-by-default per `templates/agent-prompt-preamble.md`\nS5 keeps the worker GraphQL bucket reserved for the unavoidable PR\nstate-mutation operations.\n\n### Dispatch-envelope credential rule\n\nThe canonical preamble at `templates/agent-prompt-preamble.md` carries\nthis rule verbatim alongside the existing REST-default,\nmax-1-Draft-toggle, and rate-limit-throttle rules:\n\n> Workers MUST consume the GitHub credential injected by the dispatcher\n> (typically `GH_TOKEN` in the prompt-supplied env). Workers MUST NOT\n> fall back to the host's `gh auth status` token. If `GH_TOKEN` is\n> unset and no other dispatcher-supplied credential is present, the\n> worker MUST fail loud with a clear error rather than silently\n> running under the host identity.\n\nThe rule is enforced at two points:\n\n1. **Dispatch time** (orchestrator side). The orchestrator (swarm\n skill, monitor agent, scheduled run) injects the worker\n credential into the dispatch envelope -- usually as `GH_TOKEN` in\n the env, with the maintainer's `gh auth` state untouched. Workers\n inherit this env at spawn time.\n2. **Worker side**. The worker's first action (after AGENTS.md read)\n verifies `GH_TOKEN` is set and matches the expected bot/App\n identity (e.g. `gh api user --jq .login` returns the bot login).\n Mismatch is a hard refusal: stop the tool loop, send `BLOCKED:\n identity mismatch` to the parent.\n\nThe two-sided enforcement means a missing/malformed credential surfaces\nat the earliest possible point rather than mid-dispatch when the worker\nhas already started writing files.\n\n### Permission scoping\n\n! The worker credential MUST be scoped down to the minimum needed:\n\n- `issues:write` -- open / close / comment / label\n- `pulls:write` -- open / merge / review / comment\n- `contents:read` -- read repo contents (for diff context)\n- `metadata:read` -- standard for any GitHub App\n- \u2297 `contents:write` -- workers MUST NOT push commits directly under\n the worker identity. Pushes happen via the maintainer (interactive\n review of the worker's diff) or via the GitHub App's restricted\n contents:write scope on a specific bot-owned ref pattern.\n- \u2297 `admin:*` / `repo` (broad) -- workers MUST NOT have admin scopes.\n Branch protection bypass, label/milestone management, secret\n rotation -- all maintainer-only.\n\nThe maintainer PAT keeps the broader scope it has today (`repo`,\n`workflow`, etc.). The narrowness of the worker scope is half the\nblast-radius mitigation; the other half is short-lived credentials\n(GitHub App installation tokens auto-rotate ~1 hour; bot PATs MUST be\nrotated quarterly per the runbook below).\n\n## Operator runbook\n\n### Provisioning a worker identity\n\n#### Option A: GitHub App (preferred)\n\n1. Create a new GitHub App at\n `https://github.com/organizations/<org>/settings/apps/new`\n (account-level path: `https://github.com/settings/apps/new`).\n2. Grant the **minimum** permissions enumerated in\n \"Permission scoping\" above. Decline every other permission.\n3. Subscribe to no events (workers consume the API; they do not need\n webhook delivery).\n4. Generate and download a private key (`.pem`). Store it in\n `secrets/swarm-app.pem` outside the repo (the deft `secrets/`\n convention is `.gitignore`d by default per the repo conventions).\n5. Install the App on the deft repository.\n6. Note the App ID and Installation ID; both are required to mint\n installation tokens at dispatch time.\n7. The dispatcher mints an installation token via\n `POST /app/installations/<installation-id>/access_tokens` (the\n private-key-signed JWT flow). Each minted token lives ~1 hour;\n spawn-burst refreshes are cheap.\n\n#### Option B: bot account PAT\n\n1. Create a new GitHub user account (e.g. `deft-swarm-bot`). Email\n address can be a `+swarmbot` alias on the maintainer's account.\n2. Add the bot account as a repository collaborator with **Write**\n access (NOT Maintain or Admin).\n3. Generate a fine-grained PAT under the bot account with the scopes\n enumerated in \"Permission scoping\". Set expiry to 90 days.\n4. Store the PAT in `secrets/swarm-bot.env` as\n `SWARM_GH_TOKEN=<value>`. Reference from dispatcher via\n `Get-Content secrets/swarm-bot.env` or equivalent at dispatch\n time; never paste inline.\n\n### Wiring the dispatcher\n\nThe orchestrator (swarm skill, monitor agent, `start_agent`/`oz`\ninvocation, scheduled run) loads the worker credential and injects it\ninto the worker's env:\n\n```pwsh path=null start=null\n# Option A (GitHub App): mint installation token at dispatch time.\n$jwt = uv run python scripts/swarm_mint_jwt.py --app-id $env:DEFT_SWARM_APP_ID --pem secrets/swarm-app.pem\n$inst_token = (gh api -X POST \"/app/installations/$env:DEFT_SWARM_INSTALLATION_ID/access_tokens\" -H \"Authorization: Bearer $jwt\" --jq .token)\nstart_agent ... -e GH_TOKEN=$inst_token\n\n# Option B (bot PAT): inject from secrets/swarm-bot.env.\n$bot_token = (Get-Content secrets/swarm-bot.env | Where-Object { $_ -like 'SWARM_GH_TOKEN=*' }) -replace '^SWARM_GH_TOKEN=', ''\nstart_agent ... -e GH_TOKEN=$bot_token\n```\n\nToken-mint plumbing (`scripts/swarm_mint_jwt.py`) is intentionally not\nshipped in v1 -- the runbook above is operator-facing guidance, not\ndeft-supplied automation. v1 is documentation-only per #983 non-goals.\nOperators MAY automate token minting in their own dispatcher; the\ncontract this pattern pins is the env-var injection.\n\n### Rotation and recovery\n\n#### Routine rotation\n\n- **GitHub App tokens**: auto-rotate ~1 hour. No operator action\n needed unless the App's private key is compromised.\n- **Bot PAT**: quarterly rotation. Update `secrets/swarm-bot.env`\n and revoke the prior PAT. Verify the new token via\n `gh api user --jq .login` returns the bot login.\n\n#### Compromise recovery (leaked worker token)\n\n1. Immediately revoke the leaked credential:\n - GitHub App: rotate the private key (Settings -> Apps -> Your\n deft-swarm App -> Generate a new private key, then delete the\n compromised key).\n - Bot PAT: revoke at\n `https://github.com/settings/tokens` under the bot account.\n2. Audit the bot's recent activity: `gh api\n /users/<bot-login>/events/public --jq\n '.[] | {repo, type, created_at}'`. Investigate any action outside\n the expected swarm workflow.\n3. Provision a replacement credential per the provisioning steps\n above and update the dispatcher.\n4. File an incident vBRIEF in `vbrief/proposed/` documenting the\n compromise, scope of activity, and remediation. The vBRIEF\n becomes the audit artifact.\n\nThe maintainer PAT MUST NOT be used as a fallback during the\ncompromise window -- doing so re-couples the buckets and re-introduces\nthe blast-radius problem this pattern solves.\n\n## Cross-references\n\n- #976 -- remaining GraphQL drain in `scripts/scm.py` + smoke (the\n REST-migration track this pattern complements; this pattern was\n carved out of #976's \"Complementary mitigation\" section)\n- #588 -- agent identity pattern (distinct attestable identity per\n agent; the conceptual frame this pattern operationalises)\n- #806 -- executor-layer-credentials (secrets bound at invocation\n layer, not in agent context; the architectural pattern this\n pattern lands)\n- #585 -- credential hygiene standard for cloud agent dispatch\n (overlapping with v1, but #585 covers the broader cloud-agent\n surface; this pattern scopes specifically to swarm workers)\n- #519 -- rate limiting as a first-class architectural constraint\n for multi-agent guidance (the \"shared bucket is the bottleneck\"\n framing that motivates partitioning by identity)\n- #520 -- multi-provider routing as the production solution for\n large-scale swarms (the LLM-side analogue of this pattern -- LLM\n providers also rate-limit per identity, and the same partitioning\n argument applies upstream)\n- #954 / #966 -- canonical orchestrator preamble; the\n `templates/agent-prompt-preamble.md` rule enforcing the\n dispatch-envelope credential contract is part of #954's preamble\n surface and is updated by this issue (#983)\n- AGENTS.md `## Multi-agent orchestration discipline (#954)` -- the\n REST-default / no-Draft-retoggle / rate-limit-throttle rules this\n pattern complements\n\n## Non-goals (per #983)\n\n- Per-worker token rotation infrastructure -- a single shared\n bot/App credential for all workers is acceptable in v1; per-worker\n identities are deferred to a follow-up.\n- Cross-provider identity (Anthropic, OpenAI, etc.) -- this pattern\n is GitHub-specific; the LLM-side analogue is #520.\n- Replacing the SCM/REST migration -- #976 owns that track; this\n pattern is complementary, not a substitute.\n"
46
+ "body": "# Multi-agent identity separation pattern (#983)\n\n> Status: stable pattern, v1 docs-only. v1 deliberately scopes to a single\n> shared bot/App credential for all workers; per-worker token rotation is\n> deferred to a follow-up. Per #983 non-goals.\n\nThis pattern lays out *whose* GitHub identity dispatched swarm workers\nshould authenticate as, *why* that should not be the maintainer's PAT,\nand *how* operators provision and rotate the worker credential.\n\nIt is the credential-separation track that was carved out of #976\n(`fix(scm,tests): eliminate remaining GraphQL-backed scm.py / live SCM\nsmoke bucket drain after #961`). #976 owns the REST migration; this\npattern owns the orthogonal identity separation. The two tracks are\ncomplementary: REST migration reduces GraphQL demand on the shared\nbucket, identity separation prevents human and machine traffic from\nsharing one bucket in the first place.\n\n## Why this matters\n\nToday, swarm workers and the maintainer share a single GitHub Personal\nAccess Token (PAT). One PAT serves the maintainer's interactive review\nflow AND every worker spawned by `task swarm:*` / `start_agent` /\n`oz agent run`. The operational consequences observed in the\n2026-05-07 multi-agent session and again on 2026-05-08 are concrete:\n\n- **Bucket coupling**: GitHub's GraphQL bucket is **5,000 req/hr per\n identity**. A swarm of N workers + the maintainer's terminal share\n one bucket. When workers exhaust it (the documented failure mode for\n the PR #652 / #561-class merge cascades), the maintainer's\n `gh issue create` and `gh pr view` start failing with\n `GraphQL: API rate limit already exceeded` -- the human is rate-\n limited by their own swarm.\n- **Audit conflation**: every commit, comment, PR open, issue close,\n and review submission attributes to the same `actor.login`. Forensic\n separation (\"which action was the maintainer vs. an autonomous\n worker?\") requires correlating timestamps and dispatch-envelope\n prompts rather than reading the GitHub event stream directly.\n- **Credential blast radius**: a worker's prompt envelope is by\n definition reviewable / loggable / inspectable -- prompts leak more\n readily than secrets. Today a leaked worker prompt acts with the\n full scope of the maintainer's PAT (issue close, PR merge, release\n publish, branch protection bypass), not the narrow scope a worker\n actually needs (issues:write, pulls:write, contents:read).\n\nReducing GraphQL demand (#976 REST migration) only solves bucket\n*pressure*. It does not solve bucket *coupling*, audit conflation, or\nblast radius. Those require partitioning the identity itself.\n\n## The pattern\n\n### Identity model\n\n- ! Workers MUST authenticate as a **distinct GitHub identity** from\n the maintainer. Two acceptable shapes:\n 1. **GitHub App installation token** (preferred). The deft project\n installs a GitHub App granting the minimum scopes; workers\n consume an installation token derived from the App's private key.\n Tokens are short-lived (~1 hour) and auto-rotated by GitHub.\n 2. **Bot account PAT**. A dedicated GitHub user account (e.g.\n `deft-swarm-bot`) with its own PAT. Lower setup cost, but tokens\n are long-lived until manually rotated, and the bot account\n consumes a real GitHub seat.\n- ! Maintainer PATs MUST be reserved for human-driven work: review,\n merge, release publication, manual triage. Worker dispatch envelopes\n MUST NOT inject a maintainer PAT.\n- ⊗ Workers MUST NOT fall back to the host's `gh auth status` token.\n The dispatch envelope is the contract; an implicit fallback re-\n introduces the coupling this pattern eliminates.\n\n### Bucket partitioning model\n\n| Bucket | Owner | Use |\n|----------------------|----------------------|----------------------------------------------------|\n| Maintainer GraphQL | Maintainer PAT | Human review, PR open/merge/ready, manual triage |\n| Maintainer core REST | Maintainer PAT | `gh api` reads outside swarm context |\n| Worker GraphQL | Bot account / App | `markPullRequestReadyForReview`, `addPullRequestReview` (the only GraphQL surfaces a worker should hit; see #976 + #961) |\n| Worker core REST | Bot account / App | All other reads / mutations via `gh_rest.py` REST helpers |\n\nEach bucket is 5,000 req/hr (REST `core` and GraphQL each). With\nidentity separation the maintainer's two buckets are untouched by\nswarm activity, and N workers share *their own* two buckets.\n\nThe bucket partition only delivers value when paired with the #976\nREST migration: workers that still route reads through GraphQL\n(`gh issue view --json`, `gh pr view --json`, `gh pr ready`) will\nexhaust their own GraphQL bucket within minutes under N concurrent\npoll loops. REST-by-default per `templates/agent-prompt-preamble.md`\nS5 keeps the worker GraphQL bucket reserved for the unavoidable PR\nstate-mutation operations.\n\n### Dispatch-envelope credential rule\n\nThe canonical preamble at `templates/agent-prompt-preamble.md` carries\nthis rule verbatim alongside the existing REST-default,\nmax-1-Draft-toggle, and rate-limit-throttle rules:\n\n> Workers MUST consume the GitHub credential injected by the dispatcher\n> (typically `GH_TOKEN` in the prompt-supplied env). Workers MUST NOT\n> fall back to the host's `gh auth status` token. If `GH_TOKEN` is\n> unset and no other dispatcher-supplied credential is present, the\n> worker MUST fail loud with a clear error rather than silently\n> running under the host identity.\n\nThe rule is enforced at two points:\n\n1. **Dispatch time** (orchestrator side). The orchestrator (swarm\n skill, monitor agent, scheduled run) injects the worker\n credential into the dispatch envelope -- usually as `GH_TOKEN` in\n the env, with the maintainer's `gh auth` state untouched. Workers\n inherit this env at spawn time.\n2. **Worker side**. The worker's first action (after AGENTS.md read)\n verifies `GH_TOKEN` is set and matches the expected bot/App\n identity (e.g. `gh api user --jq .login` returns the bot login).\n Mismatch is a hard refusal: stop the tool loop, send `BLOCKED:\n identity mismatch` to the parent.\n\nThe two-sided enforcement means a missing/malformed credential surfaces\nat the earliest possible point rather than mid-dispatch when the worker\nhas already started writing files.\n\n### Permission scoping\n\n! The worker credential MUST be scoped down to the minimum needed:\n\n- `issues:write` -- open / close / comment / label\n- `pulls:write` -- open / merge / review / comment\n- `contents:read` -- read repo contents (for diff context)\n- `metadata:read` -- standard for any GitHub App\n- ⊗ `contents:write` -- workers MUST NOT push commits directly under\n the worker identity. Pushes happen via the maintainer (interactive\n review of the worker's diff) or via the GitHub App's restricted\n contents:write scope on a specific bot-owned ref pattern.\n- ⊗ `admin:*` / `repo` (broad) -- workers MUST NOT have admin scopes.\n Branch protection bypass, label/milestone management, secret\n rotation -- all maintainer-only.\n\nThe maintainer PAT keeps the broader scope it has today (`repo`,\n`workflow`, etc.). The narrowness of the worker scope is half the\nblast-radius mitigation; the other half is short-lived credentials\n(GitHub App installation tokens auto-rotate ~1 hour; bot PATs MUST be\nrotated quarterly per the runbook below).\n\n## Operator runbook\n\n### Provisioning a worker identity\n\n#### Option A: GitHub App (preferred)\n\n1. Create a new GitHub App at\n `https://github.com/organizations/<org>/settings/apps/new`\n (account-level path: `https://github.com/settings/apps/new`).\n2. Grant the **minimum** permissions enumerated in\n \"Permission scoping\" above. Decline every other permission.\n3. Subscribe to no events (workers consume the API; they do not need\n webhook delivery).\n4. Generate and download a private key (`.pem`). Store it in\n `secrets/swarm-app.pem` outside the repo (the deft `secrets/`\n convention is `.gitignore`d by default per the repo conventions).\n5. Install the App on the deft repository.\n6. Note the App ID and Installation ID; both are required to mint\n installation tokens at dispatch time.\n7. The dispatcher mints an installation token via\n `POST /app/installations/<installation-id>/access_tokens` (the\n private-key-signed JWT flow). Each minted token lives ~1 hour;\n spawn-burst refreshes are cheap.\n\n#### Option B: bot account PAT\n\n1. Create a new GitHub user account (e.g. `deft-swarm-bot`). Email\n address can be a `+swarmbot` alias on the maintainer's account.\n2. Add the bot account as a repository collaborator with **Write**\n access (NOT Maintain or Admin).\n3. Generate a fine-grained PAT under the bot account with the scopes\n enumerated in \"Permission scoping\". Set expiry to 90 days.\n4. Store the PAT in `secrets/swarm-bot.env` as\n `SWARM_GH_TOKEN=<value>`. Reference from dispatcher via\n `Get-Content secrets/swarm-bot.env` or equivalent at dispatch\n time; never paste inline.\n\n### Wiring the dispatcher\n\nThe orchestrator (swarm skill, monitor agent, `start_agent`/`oz`\ninvocation, scheduled run) loads the worker credential and injects it\ninto the worker's env:\n\n```pwsh path=null start=null\n# Option A (GitHub App): mint installation token at dispatch time.\n$jwt = uv run python scripts/swarm_mint_jwt.py --app-id $env:DEFT_SWARM_APP_ID --pem secrets/swarm-app.pem\n$inst_token = (gh api -X POST \"/app/installations/$env:DEFT_SWARM_INSTALLATION_ID/access_tokens\" -H \"Authorization: Bearer $jwt\" --jq .token)\nstart_agent ... -e GH_TOKEN=$inst_token\n\n# Option B (bot PAT): inject from secrets/swarm-bot.env.\n$bot_token = (Get-Content secrets/swarm-bot.env | Where-Object { $_ -like 'SWARM_GH_TOKEN=*' }) -replace '^SWARM_GH_TOKEN=', ''\nstart_agent ... -e GH_TOKEN=$bot_token\n```\n\nToken-mint plumbing (`scripts/swarm_mint_jwt.py`) is intentionally not\nshipped in v1 -- the runbook above is operator-facing guidance, not\ndeft-supplied automation. v1 is documentation-only per #983 non-goals.\nOperators MAY automate token minting in their own dispatcher; the\ncontract this pattern pins is the env-var injection.\n\n### Rotation and recovery\n\n#### Routine rotation\n\n- **GitHub App tokens**: auto-rotate ~1 hour. No operator action\n needed unless the App's private key is compromised.\n- **Bot PAT**: quarterly rotation. Update `secrets/swarm-bot.env`\n and revoke the prior PAT. Verify the new token via\n `gh api user --jq .login` returns the bot login.\n\n#### Compromise recovery (leaked worker token)\n\n1. Immediately revoke the leaked credential:\n - GitHub App: rotate the private key (Settings -> Apps -> Your\n deft-swarm App -> Generate a new private key, then delete the\n compromised key).\n - Bot PAT: revoke at\n `https://github.com/settings/tokens` under the bot account.\n2. Audit the bot's recent activity: `gh api\n /users/<bot-login>/events/public --jq\n '.[] | {repo, type, created_at}'`. Investigate any action outside\n the expected swarm workflow.\n3. Provision a replacement credential per the provisioning steps\n above and update the dispatcher.\n4. File an incident vBRIEF in `vbrief/proposed/` documenting the\n compromise, scope of activity, and remediation. The vBRIEF\n becomes the audit artifact.\n\nThe maintainer PAT MUST NOT be used as a fallback during the\ncompromise window -- doing so re-couples the buckets and re-introduces\nthe blast-radius problem this pattern solves.\n\n## Cross-references\n\n- #976 -- remaining GraphQL drain in `scripts/scm.py` + smoke (the\n REST-migration track this pattern complements; this pattern was\n carved out of #976's \"Complementary mitigation\" section)\n- #588 -- agent identity pattern (distinct attestable identity per\n agent; the conceptual frame this pattern operationalises)\n- #806 -- executor-layer-credentials (secrets bound at invocation\n layer, not in agent context; the architectural pattern this\n pattern lands)\n- #585 -- credential hygiene standard for cloud agent dispatch\n (overlapping with v1, but #585 covers the broader cloud-agent\n surface; this pattern scopes specifically to swarm workers)\n- #519 -- rate limiting as a first-class architectural constraint\n for multi-agent guidance (the \"shared bucket is the bottleneck\"\n framing that motivates partitioning by identity)\n- #520 -- multi-provider routing as the production solution for\n large-scale swarms (the LLM-side analogue of this pattern -- LLM\n providers also rate-limit per identity, and the same partitioning\n argument applies upstream)\n- #954 / #966 -- canonical orchestrator preamble; the\n `templates/agent-prompt-preamble.md` rule enforcing the\n dispatch-envelope credential contract is part of #954's preamble\n surface and is updated by this issue (#983)\n- AGENTS.md `## Multi-agent orchestration discipline (#954)` -- the\n REST-default / no-Draft-retoggle / rate-limit-throttle rules this\n pattern complements\n\n## Non-goals (per #983)\n\n- Per-worker token rotation infrastructure -- a single shared\n bot/App credential for all workers is acceptable in v1; per-worker\n identities are deferred to a follow-up.\n- Cross-provider identity (Anthropic, OpenAI, etc.) -- this pattern\n is GitHub-specific; the LLM-side analogue is #520.\n- Replacing the SCM/REST migration -- #976 owns that track; this\n pattern is complementary, not a substitute.\n"
35
47
  },
36
48
  {
37
49
  "id": "prompt-assembly-layer-ordering",