Author a new beads issue (bead) with field-separated, capability-anchored, concretely-verifiable acceptance criteria. Invoke when the user wants to file a bead, create an issue, make a new task, or run `bd create` — also when a planning skill (/plan-to-beads) needs to emit beads programmatically. Internally runs /ac-quality-review on the draft acceptance_criteria with a 2-iteration tightening budget; refuses to emit `bd create` if the gate still returns UNDERSPEC on the third pass. Files ever...
Installs into .claude/skills of the current project.
Are you the author of Create Bead?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/kurofune-create-bead-claudit)
---
# Generated by djinn-gen-adapters from theme=djinn. DO NOT EDIT — regenerate via djinn-gen-adapters.
# djinn-compat: 1
name: create-bead
description: "Author a new beads issue (bead) with field-separated, capability-anchored, concretely-verifiable acceptance criteria. Invoke when the user wants to file a bead, create an issue, make a new task, or run `bd create` — also when a planning skill (/plan-to-beads) needs to emit beads programmatically. Internally runs /ac-quality-review on the draft acceptance_criteria with a 2-iteration tightening budget; refuses to emit `bd create` if the gate still returns UNDERSPEC on the third pass. Files every gate-approved bead through one atomic `bd create --graph` plan (a lone bead is a one-node plan), so no bead is ever ready before its blocker edges land. Supports two modes: args (structured inputs for programmatic invocation) and interactive (asks clarifying questions and builds the bead conversationally). This skill is the single source of truth for bead-authoring rules — the always-loaded project rules (.claude/rules/beads.md) carry only the invariants (claim-before-code, lifecycle, naming)."
---
<!-- djinn-compat-check: begin -->
**Before anything else:** run `djinn skills check --here` in the repo you are working in. If it exits non-zero, stop: relay its output to the user verbatim — it names the fix — and run no other step of this skill. On exit 0, continue.
<!-- djinn-compat-check: end -->
# Create-Bead
## Overview
This skill is the **authoring path** for new beads. It carries the field-separation rules, the acceptance-criteria format, the depth requirements for feature beads, the Capability Gate, the concrete-verification rule, the worked good/bad examples, and the gotchas — the procedural content for bead authoring. `/plan-to-beads` defers to this skill in its Execute Mode (one `/create-bead` invocation per emitted bead, each returning its gate-approved node for the planner's single plan).
**Why a skill, not always-loaded rules.** The procedural content is verbose (~12KB) and only relevant when actually creating a bead. Moving it behind invocation keeps the always-loaded context lean while making the rules discoverable through a single named entry point.
**One transaction per filing.** Every gate-approved bead is filed as a node in a graph plan through ONE `bd create --graph <plan.json>` call — a lone bead is a one-node plan, and its parent and blockers ride the same plan. bd applies a plan in one transaction, so a bead never exists without its blocker edges: a loaded workshop drains a bead the moment it is ready, and a bead created before its edge is ready — and dispatched — in the gap.
**Quality by construction.** Before emitting `bd create`, the skill invokes `/ac-quality-review` on the draft acceptance_criteria. On UNDERSPEC — or on a non-empty `operator_required[]` — it rewords the flagged criteria and re-runs. The budget is **2 tightening attempts** (so at most 3 total review passes: an initial check plus 2 retries); on the third UNDERSPEC the skill halts with the `findings[]` surfaced as an actionable error and does **not** emit `bd create`. This is the filing gate the work contract (§2, in the `AGENTS.md` djinn block) relies on: no step after the claim re-checks acceptance-criteria quality.
**The cardinal rule.** A bead is a design artifact that carries the plan's intent to the executing agent. That agent has no memory of the planning session, no conversation history, no awareness of the larger vision — only what the bead itself provides. Every bead must answer:
1. **What** — what specifically needs to be built
2. **Why** — what user/developer problem it solves and how it fits the larger whole
3. **Done** — what the finished result looks like from the user's perspective
A bead that answers only "what" produces a skeleton that compiles. A bead that answers all three produces work with depth.
## Invocation modes
### Args mode (programmatic — for /plan-to-beads composition)
The invoker passes a structured bead spec. Required keys: `title`, `type`, `priority`, `description`, `acceptance`. Optional: `key`, `parent`, `design`, `notes`, `labels`, `blocked_by`, `touches`, `agent`, `crucible`.
- `key` — the node's plan-local key (default `bead`). `/plan-to-beads` passes a unique key per bead so it can wire `parent_key` and edges between nodes.
- `parent` — an EXISTING bead id; becomes the node's `parent_id`.
- `blocked_by` — EXISTING bead ids this bead waits on; each becomes an edge `{"from_key": <key>, "to_id": <id>, "type": "blocks"}`.
```yaml
title: "Add JWT validation middleware"
type: task
priority: 2
parent: djinn-auth1
blocked_by: ["djinn-abc12"]
description: |
Create Chi middleware that validates JWT access tokens on protected routes...
acceptance: |
Returns 401 for expired tokens
Returns 401 for malformed tokens
Passes valid tokens to next handler
Test coverage for all three cases
design: "See internal/middleware/ for existing patterns; use auth.ValidateToken()"
notes: "Source: docs/auth-design.md §Authentication"
labels: "auth,middleware"
touches: ["internal/middleware/", "internal/auth/token.go"]
agent: "codex gpt-6-astra high" # optional; see `agent` below
# crucible: {...} # only on a formula:crucible bead; see `crucible` below
```
The skill validates the input, runs the AC-quality gate, iterates if needed, and builds the gate-approved node. With `--execute` it files the one-node plan; without it, it returns the node and files nothing — the `/plan-to-beads` path, where the planner files every node in one plan (see Output).
### Interactive mode (bare invocation — for human use)
When invoked without args, the skill asks clarifying questions in this order:
1. **Title** — short summary, a plain descriptive phrase (no bracket prefix).
2. **Type** — `epic` / `feature` / `task` / `chore` / `bug`.
3. **Priority** — P0-P4 (0=critical, 4=backlog).
4. **Parent** — bead id, or `none`.
5. **Blocked by** — existing bead ids this bead waits on, or `none`.
6. **Intent** — free-prose: what is this, why does it matter, what should the executing agent understand? The skill drafts `description` from this.
7. **Done** — what does the finished result look like from the user's perspective? The skill drafts `acceptance_criteria` from this and runs the gate. For feature beads, the skill walks the depth-fields checklist (Diagnostic Question, Reference Standard, Interaction Inventory, Scalability Assumption) before drafting.
8. **Design hints** *(optional)* — file paths, patterns to follow, data sources.
9. **Touches** *(optional)* — which repo-relative paths or directories will this bead change? Directories end in a trailing slash. Answer `none` to omit the field. Derive the default from the answer to 8 and offer it back for confirmation rather than asking cold.
10. **Source / context** *(optional)* — plan reference, sibling beads, constraints.
11. **Agent** *(optional, operator's call)* — ask only when the operator named who should do the work (e.g. "codex gpt-6-astra high"); otherwise omit. See `agent` below.
When the ask is to move a number rather than reach a done-state, run the crucible interview (`crucible` below) instead of questions 7-9.
The skill drafts each field, runs the gate, presents the assembled plan for confirmation, then files it.
## Preflight — bd 1.3.0 floor
Before drafting, run `bd --version`. Below **1.3.0**, `bd create --graph` does not carry every bead field — `acceptance_criteria`, `design`, `notes`, `labels` and `metadata` are lost. **Stop before any filing** with an error naming both versions: `bd <installed> is below the 1.3.0 floor bd create --graph needs; upgrade bd and retry.` Do not fall back to one-at-a-time `bd create`.
## Field separation — critical
Every plan node MUST carry each field under its own key. Never concatenate fields.
| Content | Plan key | What goes here |
|---|---|---|
| What + Why | `description` | What needs to be built AND why it exists. Include the purpose, the user problem, and how it fits the parent feature/epic. NOT a build instruction — a design brief. |
| Done looks like | `acceptance_criteria` | What the user SEES or EXPERIENCES when complete. Outcomes, not test assertions. One criterion per line (see below). |
| How to build it | `design` | File paths, existing patterns, data sources, API endpoints |
| Source & context | `notes` | Plan traceability, background constraints, sibling-bead relationships |
| Files it will change | `metadata` | The `touches` path list, as JSON. Read at dispatch, not by the executing agent — see below |
| Who does the agent work | `metadata` | The `agent` override, as JSON. Read by the harness, not by the executing agent — see below |
| The number a crucible bead moves | `metadata` | The `crucible` spec, as JSON. Read by the harness's judge — see below |
## Field mapping — bead field to plan key
A plan is `{"commit_message": ..., "nodes": [...], "edges": [...]}`. bd drops an unknown key with only a warning — `acceptance` is NOT a plan key, and a node carrying it files a bead with no acceptance criteria.
| Bead field (args key) | Plan key | Shape |
|---|---|---|
| title | `title` | string |
| type | `type` | `epic` / `feature` / `task` / `chore` / `bug` |
| priority | `priority` | integer 0-4 |
| description | `description` | string (markdown) |
| design | `design` | string (markdown); omit when absent |
| acceptance | `acceptance_criteria` | string, one criterion per line |
| notes | `notes` | string (markdown); omit when absent |
| labels | `labels` | array of strings |
| touches | `metadata.touches` | `"metadata": {"touches": [...]}`; omit `metadata` when absent |
| agent | `metadata.agent` | `"metadata": {"agent": {...}}` beside `touches`; omit when absent |
| crucible | `metadata.crucible` | `"metadata": {"crucible": {...}}` beside `touches`; only with the `formula:crucible` label |
| key | `key` | plan-local node key, unique in the plan |
| parent in the same plan | `parent_key` | the parent node's `key` |
| parent that already exists | `parent_id` | the existing bead id |
| blocked by a node in the same plan | edge `from_key` / `to_key` | `{"from_key": <blocked>, "to_key": <blocker>, "type": "blocks"}` |
| blocked by an existing bead | edge `from_key` / `to_id` | `{"from_key": <blocked>, "to_id": <blocker id>, "type": "blocks"}` |
A lone bead is a one-node plan:
```json
{
"commit_message": "file: Add JWT validation middleware",
"nodes": [
{
"key": "bead",
"title": "Add JWT validation middleware",
"type": "task",
"priority": 2,
"description": "Create Chi middleware that validates JWT access tokens on protected routes. ...",
"acceptance_criteria": "Returns 401 for expired tokens\nReturns 401 for malformed tokens\nPasses valid tokens to next handler\nTest coverage for all three cases",
"design": "See internal/middleware/ for existing patterns; use auth.ValidateToken()",
"notes": "Source: docs/auth-design.md §Authentication",
"labels": ["auth", "middleware"],
"metadata": {"touches": ["internal/middleware/", "internal/auth/token.go"]},
"parent_id": "djinn-auth1"
}
],
"edges": [
{"from_key": "bead", "to_id": "djinn-abc12", "type": "blocks"}
]
}
```
## Required fields
Every plan node MUST include:
- `title` — short summary, a plain descriptive phrase (no bracket prefix)
- `description` — what to build AND why
- `type` — `epic`, `feature`, `task`, `chore`, or `bug`
- `priority` — integer 0-4 (0=critical, 4=backlog). Never "high"/"medium"/"low"
- `acceptance_criteria` — what "done" looks like, one criterion per line
Strongly recommended:
- `design` — file paths, data sources, existing patterns, API endpoints
- `notes` — plan source reference, sibling relationships, constraints
## `touches` — the file-overlap path list
`touches` is the optional list of paths a bead is expected to change. It rides in the node's `metadata` object (as does `agent`) rather than a top-level key. The summoner reads it at dispatch: two ready beads whose `touches` lists overlap are never worked side by side, so parallel workers in separate worktrees stop colliding in the same files. The judgment belongs here, at filing time, where the design is in front of the author — the summoner can only enforce what it is handed.
**Shape.** A JSON array of strings under the `touches` key. Paths are **repo-relative** — no leading `./`, no absolute paths, no globs. A **directory ends in a trailing slash** (`internal/djinn/drainstate/`) and covers everything beneath it; anything without a trailing slash is one file (`cmd/summoner/spawner.go`).
**Emission.** When `touches` is present, the node carries exactly one `metadata` key:
```json
"metadata": {"touches": ["cmd/summoner/", "internal/djinn/drainstate/"]}
```
When `touches` is absent — not passed in args mode, or `none` in interactive mode — **omit the `touches` key**, and with no `agent` either, omit the `metadata` key entirely: do not emit an empty array, and do not emit `{}`. An empty list is a claim ("this bead overlaps nothing") that an unfilled field has no standing to make.
**What to list.** The paths the work will edit, at the coarsest honest granularity: the directory when the bead owns the whole package, the file when it touches one corner of a shared one. Listing too wide costs parallelism — the bead serializes against siblings it would never have collided with. Listing too narrow costs a collision. When unsure, take the directory: a lost slot is cheaper than a lost worktree.
Round-trip check after filing: `bd show <id> --json | jq '.[0].metadata.touches'`.
## `agent` — who does the agent work
`agent` names the adapter, model and effort that run a bead's agent steps, on whatever formula the bead runs. It beats the formula's own per-step pins, a `formula:*` label's included. It is the operator's call: set it only when the operator named the agent.
**Shorthand.** Operators say it as `agent: <adapter> <model> <effort>` — e.g. `agent: codex gpt-6-astra high`. Expand it to the metadata form below.
**Bead-wide form** — `agent {adapter, model, effort}` overrides every agent step:
```json
"metadata": {"agent": {"adapter": "codex", "model": "gpt-6-astra", "effort": "high"}}
```
**Per-step form** — `agent.steps.<step-id> {adapter, model, effort}` overrides one step and beats the bead-wide form on that step:
```json
"metadata": {"agent": {"adapter": "codex", "model": "gpt-6-astra", "effort": "high",
"steps": {"review": {"adapter": "claude-code", "model": "opus[1m]", "effort": "high"}}}}
```
Here review runs on claude-code opus[1m] high and every other agent step on codex gpt-6-astra high. `steps` may appear without the bead-wide keys; unnamed steps then keep the formula's pins.
**Rules.** Every selection names all three knobs. Valid values are the adapter's own: `claude-code` models `opus[1m]`, `opus`, `sonnet[1m]`, `sonnet`, `haiku`, `fable` and the full `claude-*` ids, efforts `low|medium|high|xhigh|max`; `codex` models `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, efforts `none|minimal|low|medium|high|xhigh|max`. A step id must name an agent step of the bead's formula (default: `implement`, `fix-review`, `review`, `fix-regression`; crucible: `try`, `review`, `fix-regression`). An invalid override defers the bead `needs-operator` before any step runs, with a reason naming the bad value and the valid ones.
Round-trip check after filing: `bd show <id> --json | jq '.[0].metadata.agent'`.
## `crucible` — a bead that moves a number
Use it when the operator wants a number a script can measure to get better — slow tests faster, a binary smaller, more checks passing — rather than a done-state. Label the bead `formula:crucible`: an agent makes one change per try, the harness measures it, keeps a real gain, resets the rest, and stops at the target, after `flat` tries in a row are not kept, or at the dollar cap. The kept chain then gets one review pass whose findings are filed as follow-up beads — no fix step, and the verdict never blocks — then the regression loop; if anything committed past the last kept try, the harness re-measures the final tree and lands it only if it still beats the baseline by more than the baseline spread, else defers the bead `needs-operator`.
**The interview.** Ask the operator only these four, in order:
1. **What number?** — what is being measured, in their words ("slow-tier wall time").
2. **Which direction?** — `lower` or `higher` is better.
3. **Target** — the value that means done, or `none`.
4. **Budget** — the dollar cap on the tries. Required: nothing else bounds the loop, so never file a crucible bead without it. The steps after the loop (one review pass, a regression loop of at most 2 iterations) run whatever the tries spent and add to the run's spend.
**Draft the rest from the repo**, then show the whole spec back for confirmation:
- `measure` — the command whose LAST stdout line is the number. Find an existing script (`scripts/`, the `Makefile`), and run it once to confirm the last line parses as a number. When none exists, file a blocker bead that writes it, and block this bead on it.
- `runs` — how many times each measurement runs; the score is the median. Default `3`; raise it when one run of the measure is noisy.
- `protect` — every path a try could edit to move the number without improving the thing: the measure script, the fixtures and test lists it reads, the guard's own config. An entry may be a glob (`*`, `?`, `[`; `**` crosses directories): `**/*_test.go` makes every test file read-only, so the tests become the evaluator. A glob covers its whole match set — adding or deleting a matching file is a violation too.
- `guard` — the command every try must keep green, e.g. the repo's fast test tier (`go test -short ./...` here).
- `touches` — the code the improvement may change. The guard enforces it as an allowlist: a try whose diff since the last kept commit changes any path outside it is rejected unmeasured (an entry ending in `/` covers everything beneath it, any other entry one file); a bead with no `touches` enforces none. No literal `protect` path may sit inside it; a protect glob may match inside it. The guard also rejects a try that leaves the working tree dirty.
- `stop.flat` — consecutive non-kept tries before giving up. Default `5`.
**Shape.** `crucible {measure, better, runs, protect, guard, stop {target, flat, usd}}` in `metadata`, beside `touches`. `measure` and `stop.usd` are required: a bead missing either defers `needs-operator` at claim, before any agent runs. Omit `stop.target` for no target.
**Acceptance criteria** name the number and its record, e.g. `The kept chain lands with the slow-tier median at or below 120 s, with one row per try in .djinn/crucible/<bead-id>.tsv on the branch and the bead's djinn-crucible summary comment reading baseline and best`.
Worked example — "make the slow tier faster, under 120 seconds, $25":
```json
"labels": ["formula:crucible"],
"metadata": {"touches": ["internal/", "cmd/"],
"crucible": {"measure": "scripts/bench-slow.sh", "better": "lower", "runs": 3,
"protect": ["scripts/bench-slow.sh", "**/*_test.go"],
"guard": "go test -short ./...",
"stop": {"target": 120, "flat": 5, "usd": 25}}}
```
Round-trip check after filing: `bd show <id> --json | jq '.[0].metadata.crucible'`.
## Acceptance-criteria format — one criterion per line
The `acceptance_criteria` field MUST be one criterion per line. No semicolons separating criteria, no paragraph prose with embedded labels, no inline-list separators. Each non-blank line is one independently-verifiable acceptance criterion. Blank lines are allowed for visual grouping and do not count.
```text
Returns 401 for expired tokens
Returns 401 for malformed tokens
Passes valid tokens to next handler
Test coverage for all three cases
```
**Why this rule exists.** Each criterion gets independently verified, and downstream tooling (the Djinn harness, `/verify-acceptance`) writes one evidence row per criterion. A strict line-per-criterion convention reduces criterion-counting to `len(non_blank_lines)` — no semicolon-vs-paragraph mode detection, no `; vs ;\n` ambiguity, no false rollbacks because a criterion happens to contain an inline semicolon.
**For feature beads:** the depth fields (Interaction Inventory items, Scalability Assumption, empty/loading/error states) each become their own line. They are no longer labeled subsections of paragraph prose — each interaction, each state, each scale constraint is one criterion on one line.
### Compact precondition form — `Given X → Y`
When a criterion's meaning depends on its precondition, state the precondition in the same line with the compact form `Given X → Y`. This is Gherkin's one useful discipline — an explicit given-state and trigger — without Gherkin's multi-line blocks, which would break the line-per-criterion counting contract.
**Before (rejected — precondition unstated, meaning ambiguous):**
> "Shows an error message"
**After (compact form — the precondition carries the meaning):**
> "Given a 5xx from the tickets API → list panel shows a retry banner with the error code"
Rules:
- One criterion, one line, always — never split a `Given` onto its own line.
- Use the form only where the precondition changes the cheapest satisfying implementation. "Returns 401 for expired tokens" already states its precondition in plain prose; rewriting it as `Given expired token → 401` is allowed but not required.
- The concrete-verification rule still applies: the `Y` side must name an observable outcome, and empirical thresholds still need a verification surface.
- The `/ac-quality-review` gate flags behavioral criteria with a meaning-changing unstated precondition as `UNDERSPEC`; this form is the standard fix.
## Description, design, and notes format — markdown structure
The `description`, `design`, and `notes` fields should use Markdown structure when content is long enough to benefit: **bolded labels** for named sections, bullet lists for enumerable content (file paths, action items, requirements), prose paragraphs for context and explanation. Staged in a file and read with `jq --rawfile` (see the quoting gotcha), multi-line markdown reaches the plan unchanged. Short single-sentence content can stay plain prose — don't add bolded labels just because the convention exists.
**Note:** This applies to `description`, `design`, and `notes` only. `acceptance_criteria` stays plain-text one-criterion-per-line — markdown bullets there would break the harness's line-counting contract.
**Pointers over payloads.** Inline prose is paid unconditionally by every agent that loads the bead — the implementer, the AC-quality gate, both reviewers, and verify-acceptance. A pointer (`Read docs/support-workspace.md §3.3 before starting`) is paid only by the implementer, and only when followed. When description or design content runs past a few sentences and already exists in a committed doc, cite the file + section instead of inlining it. This is also why the `Source:` line in `notes` must carry the file path, not just a section name — a pointer the agent cannot navigate is not a pointer.
## Feature beads — additional depth fields
Any bead with `-t feature` MUST carry these, folded into `description` and `acceptance_criteria`. Skipping them is the most common cause of skin-deep output.
**In `description`:**
- **Diagnostic Question** — the single question this feature helps the user answer. If you can't state one, the feature is underspecified.
- **Reference Standard** *(when aspiring to a known bar)* — name the external tool whose affordances define "done," and the specific affordances being matched.
**In `acceptance_criteria`:** each on its own line —
- **Interaction Inventory** — every hover/click/keyboard action, one line each.
- **Scalability Assumption** — the realistic data volume the feature must remain usable under.
- **Empty / loading / error states** — what the user sees in each.
- **Exclusions** *(UI features)* — named visual patterns to omit, one line each (`No italic text anywhere in the panel`, `No pill-shaped buttons`), so verify-acceptance checks each in the browser. Name the pattern; never "avoid a generic look".
## Task sizing — vertical slices
Every bead must deliver a complete vertical slice — work that produces user- or developer-visible value on its own.
### Plumbing beads
DB migrations, API endpoints, config changes, type definitions. One bead per consumer-facing capability; each endpoint, migration, or method is its own `acceptance_criteria` line. Split only when two units serve different consumers. A plumbing bead must name the feature bead that consumes it (`notes`) — no speculative infrastructure.
### Feature beads
UI features, user-visible capabilities, interactive workflows. The unit is the **capability**, not the file. A feature bead SHOULD touch multiple files (component + store + test + styles) — that's a vertical slice. Split only if it delivers two genuinely independent capabilities.
### Execution-weight routing — stamp NO `formula:*` label by default
There is no per-bead review-weight dial any more. The default `iter` formula runs ONE single-lens review pass for every bead, so a plumbing bead and a feature bead are already reviewed the same way and there is nothing to downgrade. **Author beads with no `formula:*` label.**
`formula:crucible` is not a weight either: it changes what the bead IS — a number to move instead of a done-state — and is stamped only by the crucible interview (above).
The two other labels that DO exist select a PROVIDER, never a review weight, and are the operator's call rather than the author's: `formula:claude` moves the `implement` step back onto Claude when the codex adapter is unavailable, and `formula:codex` runs every agent step on codex so a drain can proceed with no Claude quota. Stamp one only when the operator has asked for it by name. To name the exact model and effort instead, use `agent` (above) — it works on every formula.
### Context budget — the binding sizing constraint
A bead is right-sized when one agent can hold the bead's fields, the files `design` points it at, and the diff it will produce. Splitting signals; any one means the bead is too big:
- `acceptance_criteria` exceeds ~10 non-blank lines. Each criterion is paid again by every downstream gate — one AC-quality finding slot, one evidence-YAML row, one verify-acceptance check — so criteria cost roughly 4× what they appear to.
- `description` exceeds ~250 words. Move the prose to a durable doc and cite it (see "Pointers over payloads" above).
When the overflow is *shared* context — the same WHY paragraph every sibling bead would repeat — don't split; hoist it to the parent epic's brief instead (next section).
## Children of an epic — inherit the brief, don't duplicate it
When a bead is one of several children under a feature epic, the shared context — Diagnostic Question, Reference Standard, architecture overview, plan pointer — lives ONCE in the epic's `description`. That epic description is the **molecule-level brief**. Each child carries only its delta:
- 1-3 sentences of local what + why — enough to orient a reader without the parent open.
- `design` pointers specific to this slice.
- A `notes` line naming the brief: `Context: parent epic <id> carries the shared brief.`
The executing agent recovers the brief with one `bd show <parent>` — the work contract (§2) requires reading the parent's description before implementing, so leaning on it is safe, not hopeful. Restating the epic's brief in every child is exactly the bloat this rule prevents: the same paragraph paid in N children × every downstream gate.
The feature-bead depth fields follow the same split: state the Diagnostic Question and Reference Standard on the epic when they are shared across children, and restate one on a child only when that child's answer genuinely differs.
A standalone bead — no parent, or a parent that is a pure phase-gate container with no brief — still carries its full intent inline. There is no brief to inherit, so self-containment falls back to the cardinal rule.
## The Capability Gate
Before locking a feature bead, run two checks:
**Capability test:** each criterion describes what the user can DO, not just what they can SEE. "Hovering a bar reveals a tooltip" is capability; "displays a bar chart" is structure (satisfied by static markup).
**Minimum-output simulation:** mentally write the cheapest implementation that literally satisfies the criteria — if it would embarrass you to ship, the criteria are underspecified, usually missing hover affordances, scale behavior, empty/loading/error states, or keyboard navigation.
TDD test assertions are implementation artifacts, not acceptance criteria — write what the user sees, let the agent decide which assertions verify it.
## Concrete-verification rule — every AC names what's verified and how
Every acceptance criterion MUST name a **concrete verification surface**: what specifically is verified, and HOW. The surface is one of:
- A **test name or file:line** (`web/src/lib/components/Foo.test.ts:14` or `vitest run src/foo.test.ts`).
- A **fixture path** at the named scale (`testdata/500-ticks.json`).
- An **integration test gated behind an env var** when the surface is a long-running smoke (`DJINN_INTEGRATION=1`).
- An **observable user behavior** the agent can verify via playwright-cli (`Hovering a bar reveals a tooltip with {period, revenue}`).
- The **literal phrase "verified by dogfooding"** when no test surface can plausibly hold the check (subprocess spawn time, e2e first-frame latency).
A criterion that names a behavior, a render, an interaction, or an empirical threshold — but does NOT name where/how the result is checked — is a planning bug. The internal `/ac-quality-review` gate flags it as `UNDERSPEC` and the skill iterates the tightening (see "Internal AC-quality gate" below).
**Rejection criterion — this is mandatory, not advisory:** reject any AC that uses an abstract noun ("idempotence contract", "well-defined behavior", "passes correctness", "scales well") without naming the operation, equivalence, scope, or volume that defines what's being claimed.
**The rule applies to both feature and plumbing beads.** Plumbing beads name structural verification (`POST /api/tokens returns 201 with {token, expires_at}` — the verification surface is the integration test that calls the endpoint and asserts the response shape). Feature beads name capability verification (`Hovering a bar reveals tooltip with {period, revenue, YoY_delta}` — the verification surface is the playwright-cli action the bead-work contract §4 browser walk runs). An empirical threshold (`under 5s`, `500 ticks`, `< 100ms at 10k entries`) MUST name where the number is checked.
**Examples:**
- ❌ "Scalability: subprocess spawns under 5s" — no surface named
- ✅ "Scalability: subprocess spawns under 5s, verified by dogfooding"
- ✅ "Scalability: timeout configurable; happy-path fixture exercises 12 criteria"
- ❌ "Remains readable with 500 ticks" (when test exercises 87)
- ✅ "Remains readable with 500 ticks; fixture at `testdata/500-ticks.json`"
Prefer restating the claim as a structural fact when the empirical number isn't testable: "report.Parse imposes no cap on criterion count" beats "handles a dozen criteria per report" because the former is verifiable by reading the diff and the latter requires a real-world load test the unit suite can't host.
## Worked good/bad examples — AC tightening
These three pairs show the specific tightening rubric the rejection criterion enforces. Match each new bead's AC against the failure shape on the left; if your draft matches, rewrite to the shape on the right.
### Example 1 — abstract noun → named operation + equivalence + scope
**Before (rejected — abstract noun):**
> "Render is idempotent — the contract holds."
**After (concrete — names the operation, the equivalence, and the scope):**
> "Render(Render(F)) bit-identical to Render(F) including Title/Description/Acceptance/Command/Argv on every Step."
The original "idempotence contract" is rejected because the cheapest satisfying implementation can be `func Render(f) { return f }` (the identity function is idempotent under any equivalence). The rewrite names the operation (`Render`), the equivalence (`bit-identical`), the field scope (`Title/Description/Acceptance/Command/Argv`), and the iteration scope (`on every Step`). Now the cheapest satisfying implementation is the actual deterministic renderer.
### Example 2 — empirical threshold → named verification surface
**Before (rejected — no verification surface):**
> "Subprocess spawns under 5 seconds."
**After (concrete — names where the number is checked):**
> "Subprocess spawns under 5 seconds, verified by dogfooding."
OR:
> "Subprocess timeout configurable via DJINN_SPAWN_TIMEOUT env var; happy-path fixture at `djinn/internal/djinn/spawn/testdata/12-criteria.json` exercises one realistic invocation."
The original "under 5s" is rejected because algorithmic reasoning ("the code calls exec.Command without sleep, ergo it's fast") passes the criterion in prose — no test or human ever runs the timer. The rewrite either names the human-in-the-loop verification step (`verified by dogfooding`) or restructures the claim into something a fixture can hold.
### Example 3 — structural visualization → render primitive + payload + interaction
**Before (rejected — admits a CSS-div minimum output):**
> "Horizontal bar chart shows revenue for the last 20 periods."
**After (concrete — names the render primitive, hover payload, click target, and scale):**
> - Periods render as SVG horizontal bars (via d3-scale or equivalent), ranked by revenue descending
> - X-axis shows revenue with labeled ticks; y-axis shows period labels
> - Hovering a bar reveals tooltip with {period, revenue, order_count, avg_order_value, YoY_delta}
> - Clicking a bar filters any coupled workspace to that period
> - Scalability: readable with 20 periods without crowding labels
The original is rejected because the cheapest satisfying implementation is `<div style="width:43%">` literal-percentage divs — this exact failure shipped in a past bead. The rewrite forecloses that minimum by naming the render primitive, the hover payload, the click target, and the scalability bound.
## Filing — one `bd create --graph` call
Filing runs only after the gate (below) returns CONCRETE with an empty `operator_required[]`, and only after the bd 1.3.0 preflight passed.
1. **Resolve every existing id.** For each `parent` and `blocked_by` id, run `bd show <id> --json`. Stop if any is missing: bd accepts a `parent_id` or `to_id` that names no bead, files the bead anyway, and a dangling blocker edge leaves the bead ready.
2. **Stage every field in a per-invocation scratch dir** (see the quoting gotcha) and assemble the plan with `jq --rawfile` — never hand-write multi-line markdown into a JSON string.
3. **Dry-run.** `bd create --graph "$d/plan.json" --dry-run --json`. Stop on any error or any `unknown field(s)` warning: bd drops an unknown key (e.g. `acceptance`) and files the bead without it.
4. **File.** `bd create --graph "$d/plan.json" --json` returns `{"ids": {"<key>": "<new-id>"}}`. Whole plan or nothing: on an error no bead was created, so fix the plan and re-run — never file the nodes one at a time.
5. **Read back.** Run the post-filing verification (Output).
### Worked example — feature bead with a parent and a blocker
```bash
d="$(mktemp -d)"
cat > "$d/description.md" <<'EOF'
Build the customer detail view that serves as the primary surface for support debugging.
**Diagnostic Question:** Why is this customer stuck, and what have we already tried?
**Reference Standard:** Match the affordances of a mature support console (Zendesk user view, Linear customer view) — chronological activity, clickable cross-links, visible SLA status, keyboard-navigable ticket list.
**Why now:** Replaces the flat text dump with a navigable dossier — the highest-leverage feature in the workspace.
EOF
cat > "$d/acceptance.txt" <<'EOF'
Activity shown as chronological timeline with event icons and source labels
Open tickets listed with priority badges and age-in-days
All active tags shown as styled badges with context tooltip
Account relationships listed with role (owner/admin/member) and color-coded access level
Parent account and billing owner render as clickable links that open their detail views
Hover ticket row reveals full subject and assignee tooltip
Click any chip opens entity in right drawer
Arrow keys move ticket selection
Enter opens selected ticket
Readable with 200 tickets and 50 tags per customer without layout breaking
EOF
cat > "$d/design.md" <<'EOF'
**Data sources:**
- customerStore.current — all customer fields (model: internal/models/customer.go)
- customerStore.relationships — account/owner links
- tagsStore filtered by target_id — active tags
**Pattern to follow:** AccountOverview.svelte for store-reading layout.
EOF
cat > "$d/notes.md" <<'EOF'
**Source:** docs/support-workspace.md §3.3 Customer View
EOF
jq -n \
--rawfile description "$d/description.md" \
--rawfile acceptance "$d/acceptance.txt" \
--rawfile design "$d/design.md" \
--rawfile notes "$d/notes.md" \
'def t: rtrimstr("\n");
{commit_message: "file: Customer detail dossier",
nodes: [{key: "bead",
title: "Customer detail dossier with activity, open tickets, tags, and account relationships",
type: "feature", priority: 2,
description: ($description|t), acceptance_criteria: ($acceptance|t),
design: ($design|t), notes: ($notes|t),
labels: ["support", "web"],
metadata: {touches: ["web/src/lib/customer/"]},
parent_id: "djinn-supp1"}],
edges: [{from_key: "bead", to_id: "djinn-store2", type: "blocks"}]}' \
> "$d/plan.json"
bd create --graph "$d/plan.json" --dry-run --json # stop on any error or unknown-field warning
bd create --graph "$d/plan.json" --json # → {"ids": {"bead": "<new-id>"}}
```
Its 10 acceptance lines sit exactly at the context-budget cap — one more interaction or state would be the signal to split the bead or hoist shared criteria to a parent brief. Drop a key from the `jq` object when its field is absent (no `design`, no `touches`, no parent, no blockers → `edges: []`).
## Gotcha: edge direction
An edge reads **from the waiting bead to the bead it waits on**: `{"from_key": "B", "to_key": "A", "type": "blocks"}` keeps B out of `bd ready` until A closes. Plans say "B is blocked by A" — `from` is B.
**Verify:** after filing, `bd ready --exclude-type=epic` should surface only the first wave's starting beads. If a later-wave bead appears, an edge is reversed or dangling. (`bd show` labels look normal either way — only `bd ready` and `bd dep cycles` validate direction.)
## Gotcha: multiline quoting
For multiline `description` / `design` / `notes` / `acceptance_criteria` containing markdown structures like `[[steps]]` patterns, apostrophes, code blocks, quotes, or other shell- or JSON-sensitive content: write each field to a file with a quoted heredoc (`<<'EOF'`) and read it into the plan with `jq --rawfile`, which does all JSON escaping. Heredoc-in-`$(...)` breaks on `[[` patterns and apostrophes, and hand-escaped JSON breaks on the first stray quote.
**Parallel-safety:** stage every file under a per-invocation scratch dir from `mktemp -d` — never fixed `/tmp/bead-*` names. `/plan-to-beads` Execute Mode dispatches one `/create-bead` subagent per bead **concurrently**; fixed shared paths let two invocations clobber each other's staged content between the write and the read, so a node can carry another bead's field. `mktemp -d` gives each invocation its own directory, making concurrent authoring correct by construction.
## Internal AC-quality gate — iteration budget
Before filing, this skill invokes `/ac-quality-review` on the drafted `acceptance_criteria`. This is the only place the gate runs: the bead-work contract (§2) settles acceptance-criteria quality at filing, and no shipped formula re-checks it after the claim.
<!-- djinn-dispatch: claude-code -->
**Invoking the gate:** run `/ac-quality-review` through the Skill tool, in this conversation.
<!-- djinn-dispatch: end -->
**A CONCRETE verdict does not end this skill.** `/ac-quality-review` returns a verdict and hands control back. In the same turn, continue to the `bd create --graph` call (Filing) and report the new bead id(s) — or, in args mode without `--execute`, return the gate-approved node. A run that stops at the verdict has filed nothing.
**Iteration protocol:**
1. **Attempt 1.** Draft `acceptance_criteria` per the rules above. Invoke `/ac-quality-review` with the draft.
2. **If verdict is CONCRETE and `operator_required[]` is empty:** proceed to Filing.
3. **If verdict is UNDERSPEC or `operator_required[]` is non-empty (attempt 1):** apply each `findings[i].rewrite_hint` to the corresponding criterion, and reword each `operator_required[i].criterion` per the rule below. Preserve unflagged criteria in their original order. Invoke `/ac-quality-review` again with the rewritten draft.
4. **If verdict is CONCRETE and `operator_required[]` is empty:** proceed to Filing.
5. **If verdict is UNDERSPEC or `operator_required[]` is non-empty (attempt 2):** apply the same two rewrites again. Invoke `/ac-quality-review` a third time.
6. **If verdict is CONCRETE and `operator_required[]` is empty:** proceed to Filing.
7. **If verdict is UNDERSPEC (attempt 3):** **HALT.** Do NOT emit `bd create`. Surface the `findings[]` array to the invoker as an actionable error, naming each embarrassing criterion's `minimum_output` and `rewrite_hint`. The bead is fundamentally under-spec'd and needs human triage — either the intent isn't clear enough to express as concrete criteria, or the criteria are themselves the wrong shape for the work.
**`operator_required[]` is the second half of the gate.** A non-empty `operator_required[]` is handled exactly like `UNDERSPEC`: reword the flagged criterion so the unattended harness can satisfy it — drop the interactive step, replace an external action with the observable it would produce, or split the human part out into its own separately-filed bead — and re-run inside the SAME 2-iteration tightening budget, not a fresh one. If `operator_required[]` is still non-empty on the third pass, **HALT**: do NOT emit `bd create`. Surface the `operator_required[]` entries the same way as `findings[]`, naming each `manual_step`.
**Why filing time is where this is decided:** no drain step re-checks an operator-required criterion, so a bead filed with one spends implementation iterations on work no unattended agent can finish, and comes back to the operator days later. At filing time the operator is already here, and one reworded line avoids it. A bead filed with a live `operator_required[]` is a bead the drain cannot finish.
**Why budget is 2 tightening attempts** (3 total review passes including the initial draft check): beads that need more than 2 rounds usually signal a deeper planning gap — the rewrite hints converge in 1-2 passes when the intent is clear, and diverge when it isn't.
**On HALT:** the skill returns control to the invoker with a structured error. No bead was created — there is no `bead_id` yet. The returned error names the `draft_title` (verbatim from the args/interactive payload), the `findings[]` from the third review, and a recommendation. For `/plan-to-beads` composition, the planner should fold the findings back into its rubric and re-draft against the same title. For interactive use, the human decides whether to re-state the intent, split the bead, or escalate.
## Output
### Args mode
- **Without `--execute`** (the `/plan-to-beads` path): file nothing. Return the gate-approved node as a fenced ```json``` block — the node object exactly as the Field mapping builds it (`key`, every present field, `acceptance_criteria` as the gate approved it), plus `blocked_by` ids as a sibling list. The planner merges it into its one plan.
- **With `--execute`**: file the one-node plan (Filing), run the post-filing verification, and return `{bead_id, acceptance}`.
- **On HALT**: emit a fenced ```json``` block with the third-review `findings[]` and a one-line summary; build no node and file nothing.
### Interactive mode
Show the assembled plan JSON for human review. Confirm with the user. File it (Filing), run the post-filing verification, then surface the new bead id and a one-line summary (title + type + priority).
### Post-filing verification — every returned id
For each id in the returned `ids` map, run `bd show <id> --json` and confirm on `.[0]`:
- `acceptance_criteria` is non-empty and equals the gate-approved text.
- `metadata.touches` equals the drafted list, or is absent when none was drafted.
- `metadata.agent` equals the drafted override, or is absent when none was drafted.
- `metadata.crucible` equals the drafted spec with `stop.usd` set, or is absent when none was drafted.
- `parent` equals the drafted parent (`parent_id`, or the id the plan's `parent_key` resolved to).
- `dependencies` holds a `dependency_type: "blocks"` entry for every drafted blocker.
Fix a wrong field with `bd update <id>`; an `acceptance_criteria` repair must pass `/ac-quality-review` before it is written.
## Failure-mode handling
- **bd below 1.3.0.** Stop before any filing, naming the installed version and the 1.3.0 floor (Preflight).
- **Missing required field.** In interactive mode, stop and ask. In args mode (programmatic, no human in the loop), return a structured error naming the missing keys; do NOT proceed and do NOT invent titles, priorities, or types. The caller (typically `/plan-to-beads`) decides whether to retry with the missing fields or to escalate.
- **Parent epic unclear.** Run `bd list --type=epic --status=open --no-parent` to enumerate top-level epics. Ask the user which one — or whether to file a new top-level epic in the SAME plan: a second node, gated like any other, with the bead's `parent_key` pointing at it.
- **Existing id not found.** A `parent` or `blocked_by` id that `bd show` cannot find stops filing; ask (interactive) or return the error (args mode).
- **`/ac-quality-review` unavailable.** Surface the error. Do NOT emit `bd create` without the gate — the whole point of this skill is enforced AC quality.
- **`bd` binary unavailable.** Surface the error; the operator repairs `PATH` and retries.
## Universal constraints
- **One bead per invocation.** This skill authors one bead at a time (plus a new parent epic filed in the same plan, when asked). For burst creation, `/plan-to-beads` collects each invocation's gate-approved node and files the whole tree as one plan.
- **Read-only on the AC gate.** The skill never edits an existing bead. AC tightening happens on the draft, in-skill, before filing.
- **No silent dropped fields.** If the invoker passes `acceptance` but omits `design`, the node carries no `design` key (don't invent design notes). The required-field check enforces only the five mandatory fields; the optional ones pass through verbatim or omit.