diff --git a/.claude/skills/work-decompose/SKILL.md b/.claude/skills/work-decompose/SKILL.md
new file mode 100644
index 0000000..9e4de56
--- /dev/null
+++ b/.claude/skills/work-decompose/SKILL.md
@@ -0,0 +1,50 @@
+---
+name: work-decompose
+description: Use when an approved PRD must be broken into an epic with story and task files under docs/work/. Triggers — the user asks to decompose a PRD, invokes /work-decompose, or wants a PRD turned into the work tree.
+---
+
+# work-decompose
+
+Decompose an `approved` PRD into an epic + story files under `docs/work/epics/`, by dispatching a **decomposer sub-agent** whose role is defined by the existing Sandcastle prompt.
+
+This is the in-session, skill form of `pnpm work decompose --execute`. It adds a path; it changes nothing about `.sandcastle/` or `pnpm work`.
+
+## Single source of truth — do not copy the prompt
+
+The decomposer's role is defined in **`.sandcastle/decomposer.prompt.md`** — the same file `pnpm work decompose --execute` consumes. This skill **reads that file at dispatch time and passes it verbatim**. Never paraphrase, summarise, or inline it here. If this skill and the prompt ever disagree, **the prompt wins** — fix this skill, not the prompt.
+
+## Process
+
+1. **Resolve the PRD.** The user names a PRD (slug or path); otherwise list `docs/work/prds/*.prd.md` and ask which. Read the file.
+
+2. **Refuse drafts.** If the PRD's frontmatter `status:` is not `approved`, **stop** — tell the user to flip it after review. The decomposer refuses drafts; catch it early.
+
+3. **Build the prompt.** Read `.sandcastle/decomposer.prompt.md`. Substitute its `{{PRD_FILE_CONTENT}}` placeholder with the full PRD file contents.
+
+4. **Dispatch the decomposer sub-agent.** Use the Agent tool, `general-purpose`. Its instructions are the substituted prompt, followed by this environment-adaptation note (the note adapts the environment — it is not a prompt edit):
+
+ > **Environment:** you are a Claude Code sub-agent, not running inside Sandcastle. Ignore the `COMPLETE` marker instruction — there is no iteration loop; just return your final summary. Write the epic + story files to `docs/work/epics/`. **Do not commit** — leave the files for the human to review and commit, per your own "offer them a chance to review + edit" step.
+
+5. **Report.** Relay the epic folder path the sub-agent created. Remind the user to review/edit the stories, then commit, and that `pnpm work rebuild-state` (or the pre-commit hook) refreshes `_state.json`. The next step in the pipeline is `/work-dispatch`.
+
+## Why a sub-agent
+
+Decomposition is a self-contained, read-heavy job — the whole PRD, the slice-rule reasoning, the file-writing. Running it in a sub-agent keeps all of that out of the main session; you get back only the epic path.
+
+## Quick reference
+
+| | |
+| ----------------- | ------------------------------------------------------------------ |
+| Input | an `approved` PRD in `docs/work/prds/` |
+| Role prompt | `.sandcastle/decomposer.prompt.md` — read, never copied |
+| Sub-agent | one `general-purpose` agent |
+| Output | `docs/work/epics//` — `_epic.md` + `NN-/_story.md` |
+| Upstream | `to-prd` / `grill-with-docs` produce the PRD |
+| Downstream | `/work-dispatch` runs the tasks |
+| Sandcastle parity | mirrors `pnpm work decompose --execute` |
+
+## Common mistakes
+
+- **Copying the prompt into this skill.** `.sandcastle/decomposer.prompt.md` is the source of truth — read it at dispatch time, every time.
+- **Decomposing a `draft` PRD.** Check `status: approved` first.
+- **Letting the sub-agent commit.** It writes files; the human reviews and commits.
diff --git a/.claude/skills/work-dispatch/SKILL.md b/.claude/skills/work-dispatch/SKILL.md
new file mode 100644
index 0000000..05fb2cd
--- /dev/null
+++ b/.claude/skills/work-dispatch/SKILL.md
@@ -0,0 +1,76 @@
+---
+name: work-dispatch
+description: Use when a task in the docs/work/ tree should be implemented and reviewed. Triggers — the user asks to dispatch a task, run the next task, run the implement-review loop, or invokes /work-dispatch.
+---
+
+# work-dispatch
+
+Run one work-tree task through the **implement → review loop**, using two separate sub-agents whose roles are defined by the existing Sandcastle prompts.
+
+This is the in-session, skill form of `pnpm work dispatch --execute`. It adds a path; it changes nothing about `.sandcastle/` or `pnpm work`.
+
+## Single source of truth — do not copy the prompts
+
+Two role definitions, two files, read at dispatch time — **never copied**:
+
+- implementer role → **`.sandcastle/implementer.prompt.md`**
+- reviewer role → **`.sandcastle/reviewer.prompt.md`**
+
+`pnpm work dispatch --execute` (Sandcastle) and this skill consume the **same** files. Never paraphrase or inline them. If a prompt and this skill disagree, **the prompt wins**. This skill is only the _wiring_: pick the task, substitute placeholders, dispatch the sub-agents, run the loop.
+
+## Separate sub-agents — non-negotiable
+
+The implementer and the reviewer are **distinct sub-agents**, dispatched separately:
+
+- The **implementer** writes code. Dispatch a `general-purpose` Agent with `isolation: "worktree"` — it works on an isolated git worktree + branch, so bad output never touches your main tree.
+- The **reviewer** must be a _different_ agent and must _not_ write. Dispatch an `Explore` Agent (read-only by construction) — it cannot Edit/Write the repo even by accident; it only verifies the diff.
+
+Never collapse the two into one agent. An agent that writes code and then grades its own work has not been reviewed.
+
+## Process
+
+1. **Resolve the task.** Default: run `pnpm work next`, then read the first unchecked task file of that story (`docs/work/epics///NN-.task.md`). Or the user names a task id. Read the task file; note its `max-attempts` frontmatter (default 3).
+
+2. **Dispatch the implementer.** Read `.sandcastle/implementer.prompt.md`, substitute `{{TASK_FILE_CONTENT}}` with the task file. Dispatch a `general-purpose` Agent with `isolation: "worktree"`; instructions = the substituted prompt + this environment-adaptation note:
+
+ > **Environment:** a Claude Code sub-agent in a fresh, isolated git worktree — NOT a Sandcastle Docker sandbox. Run `pnpm install` as your first step (the worktree has no `node_modules`). Commit your slice on a branch. **Report that branch name** in the `notes` field of your output JSON. Ignore the `COMPLETE` marker — there is no iteration loop; just return the structured JSON as your final message.
+
+ Read the returned JSON: `status`, `commit_sha`, `files_changed`, `notes` (which carries the branch name).
+
+3. **Handle the implementer's status.** `complete` → step 4. `blocked` or `needs-clarification` → surface the `notes` to the user and stop; do not proceed to review.
+
+4. **Compute the diff.** `git diff main...` from the main tree — git worktrees share `.git`, so the implementer's branch is visible.
+
+5. **Dispatch the reviewer.** Read `.sandcastle/reviewer.prompt.md`, substitute `{{TASK_FILE_CONTENT}}` and `{{DIFF}}`. Dispatch an `Explore` Agent (read-only); instructions = the substituted prompt + this environment-adaptation note:
+
+ > **Environment:** a Claude Code sub-agent reviewing a LOCAL branch — there is no pull request or CI run yet. Where the prompt says to trust Sandcastle's CI step: the implementer has already run and reported the five conformance gates + coverage as its commit precondition — verify AC coverage, out-of-scope discipline, and slice discipline by **reading the diff**, as your checks describe. Run the library-trace check directly (`node scripts/library-decisions/check.mjs`). The Socket and CodeQL steps require a CI run — note them as "deferred to CI", do not block on them. Ignore the `COMPLETE` marker; return the JSON decision.
+
+ Read the returned JSON: `decision`, `scope_violations`, `notes`.
+
+6. **Run the loop.**
+ - **`approve`** → merge `` into `main`; remove the worktree. Then run `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance` once on `main` as a post-merge safety check. Print the suggested task-checkbox / `_state.json` mutation for the user to apply — **do not write state yourself.**
+ - **`reject`** → re-dispatch the implementer (step 2) with the reviewer's `notes` appended to its instructions. Repeat until `approve`, or until the task's `max-attempts` is reached — then stop and surface the last reviewer notes.
+
+## Why this shape
+
+State mutation stays manual (the skill suggests, the human applies) — exactly as Sandcastle's v1 orchestrator does, and consistent with the implementer prompt's "the orchestrator handles state writes." Worktree isolation gives the implementer a clean room without Docker. The read-only reviewer makes "the reviewer does not modify the repo" a property of the tool, not a promise.
+
+## Quick reference
+
+| | |
+| ----------------- | ------------------------------------------------------------------------------------- |
+| Implementer | `.sandcastle/implementer.prompt.md` · `general-purpose` · `isolation: "worktree"` |
+| Reviewer | `.sandcastle/reviewer.prompt.md` · `Explore` (read-only) |
+| Placeholders | implementer: `{{TASK_FILE_CONTENT}}` · reviewer: `{{TASK_FILE_CONTENT}}` + `{{DIFF}}` |
+| Loop cap | the task's `max-attempts` frontmatter (default 3) |
+| State writes | suggested to the user, never applied by the skill |
+| Upstream | `/work-decompose` produces the tasks |
+| Sandcastle parity | mirrors `pnpm work dispatch --execute` |
+
+## Common mistakes
+
+- **Copying a prompt into this skill.** The `.sandcastle/*.prompt.md` files are the source of truth — read them at dispatch time.
+- **One agent for both roles.** Implementer and reviewer are separate sub-agents; the reviewer is `Explore` (read-only).
+- **Writing `_state.json` or ticking the checkbox.** The skill prints the suggested mutation; the human applies it.
+- **Skipping the environment-adaptation note.** Without it the sub-agent follows Sandcastle-only instructions — the `` marker, "trust CI" — that do not apply in-session.
+- **Reviewing before the implementer says `complete`.** A `blocked` status stops the loop; do not review a partial slice.