parallel-work · git:20261019.bf1f384 · 2026-10-19 · sha256 ac94611fc4ec606e
parallel-work git:20261019.bf1f384A
Immutable. This exact content never changes and is served at /api/v1/blob/ac94611fc4ec606e.
--- name: parallel-work description: >- Splits the planned slices between several agents or people so that they cannot collide. One worker per bounded context, contracts and skeleton committed first, a git worktree per worker, shared files touched by the conductor only and regenerated instead of merged, a fixed merge order, and a short briefing per worker. Use when the user wants several agents or developers to build at the same time, asks how to parallelise or share the work, or before launching more than one context-builder agent. To order the work first use plan-slices. argument-hint: "[contexts to build in parallel]" allowed-tools: - Read - Write - Edit - Agent - SendMessage - Bash(npx ddd-ts-kit status) - Bash(npx ddd-ts-kit status --workers) - Bash(npx ddd-ts-kit plan-waves) - Bash(npx ddd-ts-kit plan-waves *) - Bash(npx ddd-ts-kit story-check *) - Bash(npx ddd-ts-kit worker-scope *) - Bash(npx ddd-ts-kit generate *) - Bash(npx ddd-ts-kit docs) - Bash(npx ddd-ts-kit depcruise-config --write) - Bash(npx ddd-ts-kit check *) - Bash(npx ddd-ts-kit verify-merge) - Bash(npx ddd-ts-kit verify-merge *) - Bash(npx ddd-ts-kit merge-trunk) - Bash(npx ddd-ts-kit merge-trunk *) - Bash(npm run regenerate) - Bash(npm run docs:generate) - Bash(npm run verify) - Bash(npm ci) - Bash(git worktree *) - Bash(git status *) --- # Work in parallel without collisions Contexts asked for: `$ARGUMENTS` You are the conductor: you own the trunk, the shared files and the merges. Workers own one bounded context each. Input: `docs/architecture/slices.md`. Launch nothing before the owner approved the split. ## 1. Decide whether it pays Work cut by user story rather than by context is `/ddd-ts-kit:story`, run by the owner, one session and one terminal per story: it reuses the worktrees, the briefings and the merge rules below. This skill splits one plan between workers of different contexts. Parallel work pays when at least two contexts each have three or more ready slices and their contracts are settled. It does not when: there is one context; a boundary or a payload is still debated; the slices form one chain; the walking skeleton is not merged yet (build it alone first, it proves the stack for everyone). Workers: one per context, **two or three at a time**, four at most. Each has its own worktree, dependencies and test runs, and every merge is read by one conductor: past that, the merges become the bottleneck. Inside one context, two workers only on two different aggregates, and expect to merge its route list and its permissions file by keeping both sides' lines; otherwise, one context, one worker, slices in sequence. Fewer on a machine that slows down: each worker runs its own compiler, linter and test runner. When the user reports a stalled or restarting machine, launch one worker at a time and point to `docs/guides/agent-sandbox.md` of the kit (a resource cap per session, NVIDIA OpenShell when the security of the session matters). A worker is one context and its worktree; an agent holds **one row, two at most**, before its turns or its context run out. Inside a worktree, launch one `context-builder` agent per row, in sequence, each on the commits of the previous one. Do not batch and order by hand: `npx ddd-ts-kit plan-waves [<context>]` (`--json` for data) puts the rows not done in waves (wave 1: every dependency done; wave n: after earlier waves), cuts each wave into batches of one owner and one or two rows on one aggregate, prints the `worker-scope` command of each batch, marks two batches of a wave likely to touch the same files (same aggregate: run them one after the other) or unknown (no aggregate named: read their rows before running them side by side), and gives the merge order. It refuses a cycle, an unknown id or a row without owner: fix `slices.md` first. Launch a wave's batches side by side, the next wave once they merged. ## 2. Prepare the trunk (conductor, before any worker starts) 0. **Your own worktree.** The guard refuses the code of a context in the main checkout of the trunk: `npx ddd-ts-kit worktree <plan>` opens `.worktrees/<plan>` on `work/<plan>`, cut from `origin/main`, installed. Every step below runs there (`cd <worktree> && ...`, every file by its absolute path under it). 1. **Contracts first.** For every `contract` row a worker depends on: generate it (`generate integration-event`, `generate gateway`), write its payload schema or read contract in the upstream's `published-language/` with the owners of both sides, commit. A downstream starts only against a committed contract. Route paths and methods of each context are the ones written in the slices table. 2. **Skeleton next.** Run every `skeleton` row: `generate aggregate` with its `generate repository`, `generate job`, `generate policy`, any new `generate context`, and, when a slice plans an adapter and the project has none yet, the first `generate adapter` (it writes `platform/resilience/own-policy.ts`, which the next ones only import). These kinds write shared files (`generate --help --json` says which, under `writesShared`); run before any worker, they never conflict. Leave the `TODO(domain)` markers to the workers. 3. `npm run verify`, commit (one commit per contract and per aggregate), push `work/<plan>` and land it as its own pull request: the workers are cut from `origin/main` once it merged, as `plan-waves` prints them. To start them before it merges, cut them from your branch instead (`--base work/<plan>`); `merge-trunk` later finds the commits they share with it. Tell the owner the trunk is ready. `.ddd-ts-kit/` and `.worktrees/` are in the `.gitignore` that `init` writes: keep them there. ## 3. One worktree and one scope per worker ```bash npx ddd-ts-kit worker-scope <context> --base origin/main --rows <id> # as plan-waves prints it; --dry-run to read it first ``` Run it from the checkout of the trunk or from your worktree, in your own session: the guard refuses it to a sub-agent unless `--dry-run`, which prints the briefing and creates nothing. From the base (by default the current branch; it refuses a worker's worktree), it adds the git worktree `.worktrees/<context>` of the main checkout (ignored by git and by every tool, never nested in another worktree) on the branch `build/<context>`, or keeps it when it contains the base and refuses it when it does not, writes `.ddd-ts-kit/worker.json` (its context, rows, base and branch) in it, runs the clean install of the project in a worktree it creates (`--no-install` leaves it to you; a note then says what to run), and prints the briefing of that worker. With that file, the guard refuses any write of a `context-builder` agent outside `src/contexts/<context>/` and its end-to-end specs (in an acceptance spec, anything but removing `pending(...)`), any generator that writes a shared file, any dependency change, and any shell command that writes outside the scope (`sed -i`, `>`, `tee`, `cp`, `mv`, `rm`, or a path only the shell knows); inline code (`node -e`, a heredoc) runs, and a file it changed outside the scope stops the worker until it is put back; `npx prettier` and a bare `npx biome` are refused too. Before briefing a worker in a context that already has one, run `npx ddd-ts-kit status --workers` from the trunk: it lists each worker (worktree, branch, context, rows, commits ahead, dependencies not installed), its state (rows committed, the row under way, the last `verify-slice`, the `--resume` command of a stopped one) and the files it changed, then the files more than one worker changed. Give the new worker rows on other files, or wait for the merge of the first. In a worktree made otherwise, or when a worker stops on its environment, run `npx ddd-ts-kit doctor --strict` in it: it names a Node that `engines.node` refuses, a missing or stale `node_modules`, packages of the kit at another version and submodules not initialised. ## 4. Shared files: who touches them, how they stay conflict-free | Shared | Touched by | Kept conflict-free by | |---|---|---| | `ddd-ts-kit.config.json`, `src/main.ts`, `src/platform/**`, `src/apps/**`, `src/shared-kernel/**` | conductor, on the trunk | workers report the need and wait for the trunk | | registries the generators fill between markers (composition root, database schema index, audited events, job settings, access policies) | generators, run by the conductor | skeleton first; at a merge, `merge-trunk` keeps both sides' lines | | `<context>.declarations.ts`, `docs/context-map.md`, `openapi/*`, `docs/integration/*`, `.dependency-cruiser.cjs`, `clients/*` | `npm run regenerate`, a worker's included | a worker regenerates and commits them with its slice; never merged by hand: `merge-trunk` takes the trunk's side and regenerates | | `package.json`, the lockfile | conductor | a worker asks for a dependency; `merge-trunk` takes the trunk's lockfile and installs again when the branch changed `package.json` | | migrations and their order | conductor | a worker generates a working one only (`db:generate working-<what>`); `merge-trunk` replays the drizzle ones of both sides, and `merge-trunk --to-land` gives them their real names before the squash merge | | `docs/architecture/plan.md`, `slices.md` | conductor | workers report, the conductor marks; `merge-trunk` merges `slices.md` row by row | | `docs/architecture/stories/*.md`, `test/acceptance/*` (the acceptance of a story and its lock) | conductor, before the first row of the story | a worker removes `pending(...)` from the scenarios its row satisfies, nothing else; a scenario it finds wrong is a need of its report | | `docs/adr/` | conductor; a worker for a decision of its slices (`generate adr`, without `--deviation`) | a new file per decision; `merge-trunk` renumbers a number the trunk took meanwhile | Another context's `published-language/` is read-only for a worker: a missing field is reported, agreed, and added on the trunk. ## 5. Brief and launch `worker-scope` prints the briefing of the rows of `--rows` (by default the first batch `plan-waves` cuts for the context, one or two rows on one aggregate, with a note naming the next `--rows`; a note past two rows asked for), each with its rules from `slices.md` and the documents of its design (`design:`): send it as it is ([briefing-template.md](briefing-template.md) is the same text, to write one by hand). When the design cites names the base removed, refuses or replaced, the briefing lists them (eight lines at most) and a note tells you the lines: correct them on the trunk before launching, or launch as is; the worker builds on the trunk's names and reports each line as a note, never edits `docs/`. A worker's `verify-slice` and the reviewer's `npx ddd-ts-kit cited --removed-since <base>` name what a slice removes that another row's design still cites: correct those documents at the merge. Launch the `context-builder` agents of different contexts in one message so they run together, without the isolation option (the worktree already exists). In one context, launch the next row's agent once the previous one reported. The guard warns each `context-builder` twice before its turn limit (finish the slice under way, then stop, commit what passed and report) and records its runs in its worktree; a session start names the workers to resume. **When an agent stops**, whatever the reason: 1. On a notification "stopped at its N-turn limit", "failed: ... API error", "was stopped", or a report naming an unfinished row: run `npx ddd-ts-kit worker-scope <context> --resume` from the trunk. It reads the worktree (rows committed by their `Slice:` trailer, the files of the row under way, the markers, the last `verify-slice`, the report, the agent) and changes nothing. Never inspect the worktree by hand, never clean or reset it, never relaunch a fresh agent there without the resume briefing. 2. `next: SendMessage to <id>`: send the printed message as it is, without asking the owner. The agent keeps its history and gets a new turn budget. 3. `next: launch a new context-builder`, a SendMessage refused, or "Prompt is too long" (run it again with `--fresh`): launch a new `context-builder` with the printed briefing, which holds the row under way only and the files the earlier agent left. 4. A rate or session limit: wait for the reset, then step 1. 5. Bounded autonomy: after two stops on one row without a new commit, stop resuming it: split the row in `slices.md` (plan-slices), or ask the owner. A report sorts its needs. A **blocking** one stops that row: do it in your worktree and land it, have the worker bring the trunk in (`npx ddd-ts-kit merge-trunk`), then launch the next agent. A **note** waits for the merge of the branch (step 6): the run goes on. Each agent commits its rows. When the rows of a branch are committed: bring the trunk into the branch, `npx ddd-ts-kit merge-trunk --cwd <worktree>`, never `git merge` alone. It computes the merge from the newest commit of the branch the trunk holds (a first row already squash-merged, a branch it was stacked on), settles the derived files, generated regions and registries, the rows of `slices.md`, the drizzle-kit journal and the lockfile itself, and leaves each source or test hunk in a merge in progress with what each side did there (exit 1). Resolve each hunk keeping both sides, never by taking one side of a source file or a test (`-X theirs`, `-X ours`, `checkout --theirs` erase the other side without a conflict, and the guard refuses them to a sub-agent), `git add` it, then `npx ddd-ts-kit merge-trunk --continue`: it replays the migrations, regenerates, runs `verify-merge` on the result and commits only once no line, row or test is lost (a removal decided on purpose: `--continue --accept-lost`; `--abort` undoes). Then one review of the batch (`architecture-reviewer`, `--changed-since <base>`), `npm run verify` on the merged branch, then the branch merges or opens its pull request. The blocking findings of the review are fixed on the branch; each non-blocking one is settled at once or written, before the merge, as the `TODO(review): #<row id> <remark>` line the review gives, at its `file:line`, by you or the agent that fixes the branch, never left in the body of a pull request. `check --changed-since` sees the files of the branch only: a cycle, an unused export or a rule broken by what the trunk gained meanwhile shows only in `verify` on the whole tree. A host without the agents of the plugin (Codex) runs one session per worktree, its briefing as the first prompt: the guard does not hold that session to its context, the briefing and the review do. A person takes the same briefing and the same branch. ## 6. Merge, in order In the merge order `plan-waves` gives: by wave, upstream first. `npx ddd-ts-kit status` tells a row ready on the trunk from one ready once a worker's branch merges, and lists what a waiting row waits for. Before each merge, `npx ddd-ts-kit status --workers`: a file the branch shares with another worker is where the second merge can conflict. For each branch: 1. land it: `npx ddd-ts-kit merge-trunk --to-land --cwd <worktree>` brings the trunk in and gives its migrations their real names (replayed under drizzle-kit, renamed after the trunk's last under knex, Prisma and TypeORM; a migration of the trunk and one of the branch on the same table stop it: generate that one again on the trunk with `npm run db:generate <what-it-does>`), renumbers a decision record whose number the trunk took meanwhile, regenerates and checks nothing was lost; `npm run verify` in the worktree, push, then the squash merge (a squash made outside GitHub or GitLab: `npx ddd-ts-kit verify-merge <squash commit> --theirs <head of the branch>`). Only you run `--to-land`: the guard refuses it to a sub-agent; 2. `npm run verify` on the trunk; a red trunk is fixed before the next merge; 3. mark its rows `done` in `slices.md`, commit (`npx ddd-ts-kit status` shows what is left); a slice merges with `TODO(review)` markers, but a story or a milestone is closed only when `npx ddd-ts-kit story-check <code>` exits 0 (`status` names a story whose rows are all done while markers of its own keep it open): each marker it says is the story's handled in a slice, or dismissed by a decision record (`npx ddd-ts-kit generate adr "<title>"`, quoting the remark without its marker), the marker deleted in the same commit. A marker belongs to the story of the row its text names, else of the row the commit that wrote it names (its `Slice:` trailer); one that belongs to no row blocks only when no other open story works in its context, and is listed otherwise: settle it or name its row in it; `--strict` blocks on every marker of the story's contexts; 4. the other workers bring the trunk into their branch with `merge-trunk` before their next slice; a branch reused for its next row does the same, from where its first row landed. A branch cut from another branch (a second batch of one context, started before the first merged) is stacked: when the first merges by squash, its commits are rewritten into one, and a plain merge of the trunk into the second replays them as conflicts. `merge-trunk` finds where the first branch landed (by patch id, on the branch recorded as its base or any local branch sharing commits) and merges from there; when the first branch is gone and nothing proves it, it falls back to an older base (more conflicts, never a silent loss) and names the `--since <commit>` to confirm. `status --workers` shows, per worker, where it landed, what is ahead and behind, and what `merge-trunk` would leave conflicted now. Never resolve by taking the trunk's side of a whole file. A worker never grants a permission to a role: the roles live in `src/platform/auth/`. When its report says who holds a new permission, grant it on the trunk in `roles.ts` (by the entry of the set) as you merge its branch, before `npm run verify`: `authz/granted` warns until then. When a worker reports a need on a shared file: do it in your worktree, land it, tell the worker to merge the trunk. Remove each worktree once its branch is merged (`git worktree remove .worktrees/<context>`). Token rules for everyone: [../start/token-economy.md](../start/token-economy.md).