@detergent-software/conductor 3.0.0-dev.33 → 3.0.0-dev.35

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +74 -78
  3. package/build/brief-cli.js +1 -1
  4. package/build/brief.js +2 -2
  5. package/build/brief.js.map +1 -1
  6. package/build/broker-cli.js +2 -2
  7. package/build/broker-cli.js.map +1 -1
  8. package/build/broker.js +1 -1
  9. package/build/broker.js.map +1 -1
  10. package/build/cli.js +10 -9
  11. package/build/cli.js.map +1 -1
  12. package/build/conditions.js +6 -6
  13. package/build/conditions.js.map +1 -1
  14. package/build/config.js +10 -14
  15. package/build/config.js.map +1 -1
  16. package/build/console.js +3 -3
  17. package/build/console.js.map +1 -1
  18. package/build/credentials.js +2 -2
  19. package/build/credentials.js.map +1 -1
  20. package/build/daemon.js +1 -1
  21. package/build/daemon.js.map +1 -1
  22. package/build/help.js +50 -52
  23. package/build/help.js.map +1 -1
  24. package/build/history.js +2 -2
  25. package/build/history.js.map +1 -1
  26. package/build/host/tmux.js +2 -2
  27. package/build/inbox.js +13 -14
  28. package/build/inbox.js.map +1 -1
  29. package/build/init.js +4 -5
  30. package/build/init.js.map +1 -1
  31. package/build/lib/providers/ado/writes.js +1 -1
  32. package/build/lib/providers/ado/writes.js.map +1 -1
  33. package/build/logins.js +1 -1
  34. package/build/logins.js.map +1 -1
  35. package/build/manager.js +4 -4
  36. package/build/manager.js.map +1 -1
  37. package/build/note.js +1 -1
  38. package/build/notes.js +7 -7
  39. package/build/notes.js.map +1 -1
  40. package/build/notify.js +3 -3
  41. package/build/notify.js.map +1 -1
  42. package/build/ownership.js +2 -2
  43. package/build/plugin.js +4 -2
  44. package/build/plugin.js.map +1 -1
  45. package/build/profile-fields.js +1 -1
  46. package/build/profile-fields.js.map +1 -1
  47. package/build/profile.js +12 -11
  48. package/build/profile.js.map +1 -1
  49. package/build/project-check.js +1 -1
  50. package/build/project-check.js.map +1 -1
  51. package/build/protocol.js +5 -21
  52. package/build/protocol.js.map +1 -1
  53. package/build/queue.js +4 -4
  54. package/build/queue.js.map +1 -1
  55. package/build/rule-actions.js +4 -4
  56. package/build/rule-actions.js.map +1 -1
  57. package/build/rules-service.js +3 -3
  58. package/build/rules-service.js.map +1 -1
  59. package/build/rules.js +8 -8
  60. package/build/rules.js.map +1 -1
  61. package/build/runs.js +1 -1
  62. package/build/runs.js.map +1 -1
  63. package/build/scheduler.js +6 -5
  64. package/build/scheduler.js.map +1 -1
  65. package/build/server.js +3 -3
  66. package/build/server.js.map +1 -1
  67. package/build/spend.js +1 -1
  68. package/build/spend.js.map +1 -1
  69. package/build/store.js +4 -5
  70. package/build/store.js.map +1 -1
  71. package/build/supervise.js +1 -1
  72. package/build/supervise.js.map +1 -1
  73. package/build/train.js +2 -2
  74. package/build/train.js.map +1 -1
  75. package/build/wait.js +1 -1
  76. package/build/watchers/linear.js +2 -2
  77. package/build/watchers/linear.js.map +1 -1
  78. package/build/why.js +1 -1
  79. package/build/why.js.map +1 -1
  80. package/build/worktrees.js +3 -3
  81. package/build/worktrees.js.map +1 -1
  82. package/package.json +1 -1
  83. package/plugin/.claude-plugin/plugin.json +1 -1
  84. package/plugin/hooks/model.ts +9 -6
  85. package/plugin/hooks/register.tsx +1 -1
  86. package/plugin/skills/conductor-console/SKILL.md +23 -24
  87. package/plugin/skills/work-item/SKILL.md +45 -46
  88. package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
  89. package/plugin/skills/work-item/trackers/github-issues.md +1 -1
  90. package/plugin/skills/work-item/trackers/linear.md +2 -2
  91. package/plugin/types/index.d.ts +1 -1
  92. package/protocol/PROTOCOL.md +1 -9
package/README.md CHANGED
@@ -69,7 +69,7 @@ conduct mode --tier S|M|L --files <a,b>
69
69
  conduct wait <condition> wait for an outside event (see Waits)
70
70
  conduct state [--repo <repo>] the worktree's branch, changes and PR, and its other repos' (see Worker verbs)
71
71
  conduct branch <type> <slug> [--item <item>]
72
- rename worktree-ATK-n to <type>/atk-n-<slug>
72
+ rename worktree-ENG-n to <type>/eng-n-<slug>
73
73
  conduct checkout <owner>/<repo> <type> <slug> [--item <item>]
74
74
  clone another GitHub repo into .scratch/repos/<repo> for the item
75
75
  conduct pr create --title <title> --body-file <file> [--repo <repo>] [--item <item>] [--dry-run]
@@ -103,7 +103,7 @@ conduct upgrade [--tag dev|latest] [--allow-downgrade]
103
103
  stop the daemon, npm install the tag's newest version, start it again
104
104
  ```
105
105
 
106
- A `<worker>` is its item (`ATK-309`) or its name (`probe-ATK-309`).
106
+ A `<worker>` is its item (`ENG-309`) or its name (`probe-ENG-309`).
107
107
 
108
108
  `conduct status --json` exits 1 with `running: false` when no daemon listens on its port, such as while it restarts or upgrades. It still lists the workers in flight, read from the store with `workersFrom: "store"` and activity `daemon not running`; a store it can't read leaves `workers` out. A running daemon's answer says `workersFrom: "daemon"`.
109
109
 
@@ -115,7 +115,7 @@ The session manager owns the `claude --bg` sessions it launches, and only those:
115
115
  1. the broker gives it a port for each name in the profile's `ports` (see Broker);
116
116
  2. `git worktree add`, run in the project's clone (see Projects), makes `~/.conductor/projects/<project>/worktrees/<name>` on `worktree-<name>` from `origin/<base>`, fetched first (after `git worktree prune`, and on Windows with `core.longpaths` on). It doesn't fetch, pull, add worktrees to or change a checkout of yours (a `localFilesFrom` checkout is only read). A worktree git still reads there is reused, its own changes and all; one with a `.git` git can't read is never deleted, and fails the launch. A `worktree-<name>` branch that is left is checked out again, with any commits it kept;
117
117
  3. with `localFilesFrom` (an absolute path to a checkout the operator keeps), the gitignored files there that match its `.worktreeinclude` are copied in, as they are now;
118
- 4. the profile's `setup` commands run in order in the worktree, through the shell, with `CONDUCTOR_PORT_<NAME>` (such as `CONDUCTOR_PORT_WEB=41000`), `CONDUCTOR_ITEM`, `CONDUCTOR_PROJECT` and `CONDUCTOR_WORKTREE` set. They build its gitignored files from what the repo commits (ATK's: `pnpm install`, `atk setup --tool claude-code --yes`, `node scripts/dev-ports.mjs assign`, `atk install`). The first that fails fails the launch, with the end of its output. So does a setup that leaves changes git sees, such as an `.atk-lock.json` that `atk install` rewrote: the worker would take them for its own;
118
+ 4. the profile's `setup` commands run in order in the worktree, through the shell, with `CONDUCTOR_PORT_<NAME>` (such as `CONDUCTOR_PORT_WEB=41000`), `CONDUCTOR_ITEM`, `CONDUCTOR_PROJECT` and `CONDUCTOR_WORKTREE` set. They build its gitignored files from what the repo commits (such as `pnpm install` and a script that writes the dev ports). The first that fails fails the launch, with the end of its output. So does a setup that leaves changes git sees, such as a committed lockfile that an install step rewrote: the worker would take them for its own;
119
119
  5. the ports go into the worktree's `.claude/settings.local.json` as `env`, merged into what the file had. A `claude --bg` session doesn't inherit the environment it is started from, but Claude Code applies that file's `env` to every session in the folder and to each resume;
120
120
  6. the profile's work and verify skills must be there: a `conductor:<skill>` in the plugin, any other in the worktree's `.claude/skills` or `~/.claude/skills`.
121
121
 
@@ -135,7 +135,7 @@ The session manager owns the `claude --bg` sessions it launches, and only those:
135
135
  - **restarts:** a launch the daemon was stopped in adopts its session if one was started, and otherwise fails and removes its clean worktree.
136
136
  - **attach:** a parked worker is resumed with no event text, so it wakes idle and keeps running when its tab closes. An attached worker isn't parked until it registers a new wait, or until its turn has been over `sessions.idleMinutes` (`worker.detached`). The daemon can't see the tab, so this also delivers a wait that resolved while it was attached. A wait that resolves after the attach, on a worker whose transcript shows no turn since then, is delivered at once: the worker is detached (`reason: wait resolved`), parked, which closes its tab, and resumed with the event. A worker someone woke outside the daemon counts as attached since its stop, so the turn that woke it counts. One that took a turn since the attach (or has no transcript to tell), or whose wait resolved before the attach, keeps it until the idle release, logs `worker.resume-held` (`reason: attached`) once, and `conduct status` shows it as `idle, holding resolved waits for the attached session`.
137
137
  - **park failures:** a session whose process outlives `claude stop` by 30 s logs one `worker.park-failed` per spell, which raises a `stall` in the inbox (see Inbox), and the park is tried again every 5 minutes.
138
- - **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell, and `worker.stall-cleared` when that spell ends. `claude agents` names no command, so a permission stall reads the session's transcripts (its own and its subagents') for the call still open: its tool, the command or path, the subagent that made it, and the auto-mode classifier denials just before it. The inbox item and its push say them, for example "ATK-297 has waited 5 min on a permission prompt: Bash `git -C … diff …` in subagent general-purpose (Review ATK-297 diff vs plan), after 2 auto-mode denials". Auto mode asks a person after 3 classifier denials in a row (20 in a session), even in a background subagent, so that count is usually why the prompt came. An open item of a Handoff kind with nothing to watch (`abandoned`, `blocked`, `failed`, `human`, `operator`, `scope`) on the worker, or on its item with no worker, counts as a wait: its idle clock starts once that item resolves. A `merge`, `npm-otp` or `plan` item doesn't: those come with a wait, so a worker idle with only the item still stalls.
138
+ - **stalls:** a permission prompt open `sessions.permissionMinutes` (5), or idle `sessions.idleMinutes` (20) with no wait, logs one `worker.stalled` event per spell, and `worker.stall-cleared` when that spell ends. `claude agents` names no command, so a permission stall reads the session's transcripts (its own and its subagents') for the call still open: its tool, the command or path, the subagent that made it, and the auto-mode classifier denials just before it. The inbox item and its push say them, for example "ENG-312 has waited 5 min on a permission prompt: Bash `git -C … diff …` in subagent general-purpose (Review ENG-312 diff vs plan), after 2 auto-mode denials". Auto mode asks a person after 3 classifier denials in a row (20 in a session), even in a background subagent, so that count is usually why the prompt came. An open item of a Handoff kind with nothing to watch (`abandoned`, `blocked`, `failed`, `human`, `operator`, `scope`) on the worker, or on its item with no worker, counts as a wait: its idle clock starts once that item resolves. A `merge`, `npm-otp` or `plan` item doesn't: those come with a wait, so a worker idle with only the item still stalls.
139
139
  - **spend:** tokens from the session's transcript and its subagents' (`~/.claude/projects/*/<session>.jsonl`), each message counted once, go into `spend` per worker and local day. It keeps input, output, cache read and cache write; `tokens` is their sum. A worker whose tokens in all, not counting cache reads, pass `inbox.runawayTokens` logs `worker.runaway` once per daemon run. Cache reads are left out because they are nearly all of a session's tokens (97% on ATK-312) at a tenth of the price. The spend `conduct status`, `GET /fleet` and the console's fleet pane show (`spend.today`, `spend.total`) and the `spend.*` rule fields count the same tokens: input, output and cache writes. Status shows the worker's cache reads beside them, labelled (`spend 417k today, 1.0M total, 25M cache reads`; `spend.cacheRead` in `GET /fleet`).
140
140
  - **context:** the session's context is its main transcript's newest model call: input, cache read, cache write and output. Subagents have contexts of their own, so theirs don't count. After a compaction it is unknown until the next call: the boundary counts only the conversation it kept, and the call adds the system prompt and tools back (a probe that kept 2.8k next read 68k). Status shows it as `context 312k` (`context` in `GET /fleet`), and the rules read it as `worker.context`.
141
141
  - **probes:** `--probe` names the worktree and session `probe-<item>` and gives it a prompt that works no issue, so tests stay out of the live fleet.
@@ -151,13 +151,13 @@ Workers are background sessions, and a tab is only a view of one: the session ru
151
151
  ```
152
152
 
153
153
  - `tab` (false): open a tab on each launch. `conduct attach <worker>` opens one whatever it says. It was the profile's `workers.tab` until ATK-529: a profile may still set it, for older daemons, but it is ignored, and `conduct project check` warns about it. Set it in `config.json` on each machine that wants tabs.
154
- - `terminal` (`auto`): `windows-terminal` (`wt.exe`, a window per project, "Conductor · <project>"), `iterm` (a tab in its front window), `terminal` (macOS Terminal, a window per tab) or `tmux` (a detached session per window, such as `conductor-atk`, and a tmux window per tab). `auto` is Windows Terminal on Windows, iTerm on macOS where it is installed and Terminal where not, and tmux elsewhere.
154
+ - `terminal` (`auto`): `windows-terminal` (`wt.exe`, a window per project, "Conductor · <project>"), `iterm` (a tab in its front window), `terminal` (macOS Terminal, a window per tab) or `tmux` (a detached session per window, such as `conductor-web`, and a tmux window per tab). `auto` is Windows Terminal on Windows, iTerm on macOS where it is installed and Terminal where not, and tmux elsewhere.
155
155
 
156
156
  A tmux tab runs where no desktop is, so `conduct attach` and `conduct console` print the `tmux attach -t <session>` that reaches it. With no terminal to open in (no tmux on a server), `conduct attach` prints the `claude attach <session>` to run yourself, and `conduct console` refuses with `conduct console --here --force` (`--force` because the refused start is claimed for a minute). A worker's tab whose `claude attach` fails stays open on the failure, until Enter, so you can read it. Tabs on macOS and tmux run their command through `sh -c`, whatever the login shell. macOS asks once whether the conductor may control Terminal or iTerm (Automation, in Privacy & Security). Terminal opens a window per tab, and closes it when the tab's shell exits only if the profile's "When the shell exits" says so (Settings, Profiles, Shell); otherwise a parked worker leaves its window behind.
157
157
 
158
158
  ## Notes
159
159
 
160
- A worker writes each phase note (`## <work skill> · <Phase>[ · <kind>: <one line>]`, such as `## work-issue · PR opened`) with `conduct note <item> <file>`. The daemon posts the file as a comment on the item in the tracker, then records the phase at once: on the worker (`phase` in `GET /fleet` and `conduct status`), on the item, and as a `note.posted` event. A PR opened note also records the worker's PR (`pr`, such as `ado:Emergent.AgenticToolkit#1850` or `github:<owner>/<repo>#458`) from its first PR link, replacing an earlier one; until then, the worker's first `pr:` wait records it. `conduct status` and the console's fleet pane show it. The worker is `--worker <worker>`, else the one whose worktree is the current checkout, else the project's live worker on that item. The project is the worker's, else the one whose tracker key (the Linear team, or the Boards or GitHub Issues `key`) is the item's key prefix.
160
+ A worker writes each phase note (`## <work skill> · <Phase>[ · <kind>: <one line>]`, such as `## work-item · PR opened`) with `conduct note <item> <file>`. The daemon posts the file as a comment on the item in the tracker, then records the phase at once: on the worker (`phase` in `GET /fleet` and `conduct status`), on the item, and as a `note.posted` event. A PR opened note also records the worker's PR (`pr`, such as `ado:Contoso.Web#1850` or `github:<owner>/<repo>#458`) from its first PR link, replacing an earlier one; until then, the worker's first `pr:` wait records it. `conduct status` and the console's fleet pane show it. The worker is `--worker <worker>`, else the one whose worktree is the current checkout, else the project's live worker on that item. The project is the worker's, else the one whose tracker key (the Linear team, or the Boards or GitHub Issues `key`) is the item's key prefix.
161
161
 
162
162
  **State follows the notes** (providers design note, section 5). A `Started`, `PR opened` or `Landed` note (not a Handoff) also moves the item, to the state its phase maps to in the profile's `tracker.transitions`:
163
163
 
@@ -171,7 +171,7 @@ A phase mapped to `null` moves nothing (`started` still assigns). A `Started` ne
171
171
 
172
172
  **Worker protocol 2.** The notes, their phases and kinds, the waits and the wake text are the package's spec, [`protocol/PROTOCOL.md`](protocol/PROTOCOL.md), held as data in `src/protocol.ts`. `conduct note` checks each note against it before anything is posted, on the daemon and with no daemon alike. It refuses one with an unknown phase, a Handoff without a kind and its line, a kind its phase doesn't carry (`jason` is `operator` since protocol 2), or a Plan without its `**Size tier**` and `**Landing:**` lines, and the error names the rule. The readers (watchers, briefs, history) still read older notes and people's comments.
173
173
 
174
- The work skill is the profile's `skills.work`. A note headed with one of `skills.aliases` counts the same, there and wherever the daemon reads notes (approvals, `conduct wait item … note`, the review-thread checks of `conduct wait pr … comment`). So when a project renames its work skill (ATK's `work-issue` became `work-item`), the old name goes in `aliases` and items in flight keep their phases.
174
+ The work skill is the profile's `skills.work`. A note headed with one of `skills.aliases` counts the same, there and wherever the daemon reads notes (approvals, `conduct wait item … note`, the review-thread checks of `conduct wait pr … comment`). So when a project renames its work skill, the old name goes in `aliases` and items in flight keep their phases.
175
175
 
176
176
  | Exit | Means | The worker then |
177
177
  |---|---|---|
@@ -209,7 +209,7 @@ A brief is kept for 30 seconds. Anything the daemon sees happen to the item drop
209
209
 
210
210
  The rest of what a work skill and the console read from the tracker, so neither needs a tracker's MCP tools (design note v2, section 4). Each works for Linear, ADO Boards and GitHub Issues.
211
211
 
212
- - `conduct notes <item>` prints the item's phase notes with their whole text, oldest first, each with when and who. `--phase` and `--kind` keep the ones that match (case aside), and `--last` the newest of those: `conduct notes ATK-312 --phase Handoff --kind conductor --last` is the Handoff a console escalation is about.
212
+ - `conduct notes <item>` prints the item's phase notes with their whole text, oldest first, each with when and who. `--phase` and `--kind` keep the ones that match (case aside), and `--last` the newest of those: `conduct notes ENG-312 --phase Handoff --kind conductor --last` is the Handoff a console escalation is about.
213
213
  - `conduct comments <item>` prints every comment the same way, and whether an approver wrote it; `--since <time>` keeps the newer ones.
214
214
 
215
215
  Both read the item's brief (the comments now carry their author and their note heading), so they read as the daemon reads, else here, and refuse as `conduct brief` does. `--json` prints them as one object.
@@ -233,7 +233,7 @@ Protocol 2's verbs on git and the code host (providers design note, section 4),
233
233
  - **Which runs.** On Azure Pipelines (the profile's `ci: ado-pipelines`), they are the pipelines whose YAML trigger the merge fires. The YAML and the merge's changed files are read in the worktree, which fetches the base when it hasn't got the commit. On GitHub Actions (`--repo github:<owner>/<repo>`, or `ci: github-actions` for a GitHub repo), they are the runs the merge's push started.
234
234
  - **CI apart from the code host (ATK-530).** Azure Pipelines can build the project's GitHub repo: `"ci": { "kind": "ado-pipelines", "organization": "https://dev.azure.com/<org>", "project": "<project>" }` names where its pipelines live (beside an Azure DevOps code host, both are refused: its pipelines are the repo's own project's). They are the definitions there whose repository is the GitHub repo, named `<owner>/<repo>` and of type `GitHub`. `code-host.ts`'s `ciOf` picks a repo's CI: the profile's for its own repo, its own host's for any other. The runs, `wait merge` (on the ADO source, as the project's ADO account, with the facts from the worktree's git), `wait run`, the grace period (by the CI: 15 minutes for Azure Pipelines) and `project check` (which also asks the ADO credential) follow it. Its PRs stay GitHub's. `--requeue` queues the code host's own CI, so it refuses a GitHub repo Azure Pipelines builds: queue the run in Azure Pipelines, and `conduct wait merge` follows it. A profile without the organization and project still reads, as it did before they existed, and its CI is refused where it is read.
235
235
  - **The verdict.** It is `runsVerdict`: `succeeded`, `failed`, `cancelled`, `waiting-for-approval`, `missing`, `none` or `waiting`. On Azure Pipelines `none` comes at once when nothing is triggered. On GitHub it comes after 5 minutes with no run, and on Azure Pipelines a triggered pipeline gets 15 minutes to queue before it is `missing`. The note gives a `missing` pipeline's `--requeue` line.
236
- - **A batch behind a run (ATK-434).** A `batch: true` pipeline holds a push while its run on the base still goes, and builds the newest commit when that run ends. So a merge with no run of such a pipeline yet reads that pipeline's newest builds on the base, and the newest that doesn't carry the merge is the run its batch queues after (`behind`: its `run`, and `finished`, null while it goes; state `not-queued`). The verdict is `waiting` for as long as that run goes and 2 minutes after it ends, while Azure DevOps lists the batch's run, past the 15 minutes too: a long deploy, or a `main` run waiting on its prod approval, can hold it longer. A `[skip ci]` release commit pushed in that time isn't in the deploy pipelines' batch (it touches only `apps/cli/` or `apps/conductor/`), and the batch still runs.
236
+ - **A batch behind a run (ATK-434).** A `batch: true` pipeline holds a push while its run on the base still goes, and builds the newest commit when that run ends. So a merge with no run of such a pipeline yet reads that pipeline's newest builds on the base, and the newest that doesn't carry the merge is the run its batch queues after (`behind`: its `run`, and `finished`, null while it goes; state `not-queued`). The verdict is `waiting` for as long as that run goes and 2 minutes after it ends, while Azure DevOps lists the batch's run, past the 15 minutes too: a long deploy, or a `main` run waiting on its prod approval, can hold it longer. A `[skip ci]` release commit pushed in that time isn't in the deploy pipelines' batch (it touches only the released package's files), and the batch still runs.
237
237
  - **A cancelled run (ATK-547).** On Azure Pipelines a canceled build is `cancelled`, not `failed`. A later run on the base whose commit contains the merge stands in for it: the first that wasn't cancelled is the merge's run, with `supersedes` naming the cancelled one. So `cancelled` means no newer run carries the merge, and the note gives the `--requeue` line. GitHub reads runs by the merge's SHA alone, so its `cancelled` says nothing about newer runs: its note says a newer run may carry the merge, and gives the line to re-run it while none does.
238
238
  - **A failed run (ATK-638).** A failed run stands until a run queued by hand after it (`reason: manual`: `--requeue`, or a person's Run pipeline) contains the merge: that one is then the merge's run, with `supersedes` naming the failed one. A newer push's run never stands in for a failed one, nor for a run queued again for it and then cancelled, so another merge's pass doesn't hide a failure; each run queued again for a failure names it (`retryOf`). The note on `failed` gives the `--requeue` line, to run once when the log shows a flake that no change caused (another merge's real failure isn't one); it leaves it out for a run with `retryOf`, and gives the lines of any cancelled or missing pipelines beside it.
239
239
  - **`--requeue <pipeline>`.** It queues that pipeline on the base at its tip (`POST build/builds` with the tip as `sourceVersion`), after checking that the tip contains the merge: a cancelled run no newer run carries, a failed one, or a missing one (`replaces: null`). It is a write, for the item's own merge only: the checkout's branch carries the item's key, and that branch's PR merged as `<sha>`. It refuses a run that is waiting, running or succeeded, so a run already listed is never queued twice, and a batch still behind a run, which queues when that run ends. A failed run is queued again once: it refuses a failed run that was itself queued again for a failed one (`retryOf`), so a real failure can't loop. The record is the merge's runs on Azure DevOps, so it holds with or without the daemon. A failed run needn't wait out the 15 minutes, since no push's run stands in for it. It also refuses a cancelled or missing run under 15 minutes after the merge, since Azure DevOps can still list a newer run that carries it. A newer merge's batched run that ADO hasn't listed yet isn't caught. It refuses a pipeline the merge didn't trigger. The daemon queues it for the worker that owns the checkout (`POST /workers/:id/requeues`) and logs `code-host.run-requeued`; with no daemon it runs here. `conduct wait merge <sha>` then follows the new run, which carries the merge.
@@ -284,7 +284,7 @@ The console gives each link a button of its own, `open <label>`, and `conduct in
284
284
 
285
285
  - A pull request is `PR <n>`, or `<repo> PR <n>` when it isn't the project's repo.
286
286
  - An Azure Pipelines or GitHub Actions run is `run <n>`.
287
- - A tracker item is its key: `ATK-217`, `CAD-1234` for a Boards work item, `PAR-12` for an issue of the project's GitHub Issues repo. A comment on an item or a PR adds ` note` (`ATK-217 note`, `PR 2078 note`). A work item or issue of another project is `work item <id>` or `<repo> #<n>`.
287
+ - A tracker item is its key: `ENG-312`, `CAD-1234` for a Boards work item, `PAR-12` for an issue of the project's GitHub Issues repo. A comment on an item or a PR adds ` note` (`ENG-312 note`, `PR 2078 note`). A work item or issue of another project is `work item <id>` or `<repo> #<n>`.
288
288
  - Anything else is its host, or a path's last segment.
289
289
 
290
290
  Labels longer than 40 characters are cut. Schema 9 named the links stored before this from their shape alone, with each item's project's profile, so a migrated checkout root reads as its folder's name rather than `checkout`.
@@ -305,22 +305,22 @@ An item resolves itself when its condition is seen, and records what resolved it
305
305
  | `login` | `provider.login-failed`: a project's provider login stopped working (see Accounts), once per provider and account however many projects and calls find it, with the command that signs in again | the login working again, found by the daemon's re-probe, any call, or a `login` wait (`provider.login-restored`), `conduct done`, or a daemon restart (the spell starts over) |
306
306
  | `runaway` | a worker passing `inbox.runawayTokens` (1.5M) input, output and cache-write tokens in all, once per worker | `conduct done`, or its retirement |
307
307
  | `context` | `worker.compact-failed`: the daemon couldn't compact a worker whose context passed the `compact-on-park` rule (see Workers), while one is open, with `conduct attach` (run `/compact` in it) and `conduct retire` | the session compacting (`worker.compacted`), `conduct done`, or its retirement |
308
- | `release`, `queue-drained` | the scheduler's default rules (see Scheduler): `ready-to-release` raises `release` with the profile's `release.handoff` text (ATK's says to run `/release-atk`) | `conduct done` |
308
+ | `release`, `queue-drained` | the scheduler's default rules (see Scheduler): `ready-to-release` raises `release` with the profile's `release.handoff` text | `conduct done` |
309
309
  | `triage` | the scheduler's default rule `triage`: an item with the profile's triage label, once per item (see Scheduler) | a read of its project that no longer shows the label on the item (removed, or the item closed or left the queue), in `scheduler.mode` `on`; or `conduct done`, after which it is raised again the next day while the label stays. Once resolved, a label put back raises it again at once |
310
310
  | `console` | the console's finding on an escalation, with `conduct escalations done <id> --ask` (see Console) | `conduct done` |
311
311
  | `console` (no console open) | escalations wait and no console has followed the stream for 30 s, with `conduct console` and `conduct escalations` (see Console) | a console opening, the last escalation handled, or `conduct done` |
312
312
 
313
- Worker protocol 2 renamed the kind `jason` to `operator` and merged `prod-approval` and `ready-to-release` into `release`; schema 10 moved the stored items. The daemon never approves prod itself. When a worker is retired, its open items resolve with it.
313
+ The daemon never approves prod itself. When a worker is retired, its open items resolve with it.
314
314
 
315
315
  For an item raised from a note:
316
316
 
317
317
  - The commands are the inline code on the note's heading and the lines of its shell-tagged fenced code blocks (`bash`, `sh`, `shell`, `powershell`, `pwsh`, `ps1`, `cmd`, `console`). An untagged block is output, not a command.
318
318
  - The links are the comment's URL, named `<key> note`, and every URL in the note. A URL a Markdown link gives (`[registry PR #539](…)`) takes that link's text as its label; the others are named from their shape.
319
319
 
320
- `conduct approve ATK-312` (or the inbox id written `#12`; a bare number is refused, since it could mean an issue) posts `approve` on the item through the tracker, or `approve: <text>` with `--text`. It uses the same key as notes, so the comment carries Jason's name and counts as his answer. `conduct revise ATK-312 "<text>"` posts `revise: <text>`; every word after the item is the text, quoted or not. Either one then:
320
+ `conduct approve ENG-312` (or the inbox id written `#12`; a bare number is refused, since it could mean an issue) posts `approve` on the item through the tracker, or `approve: <text>` with `--text`. It uses the same key as notes, so the comment carries the operator's name and counts as their answer. `conduct revise ENG-312 "<text>"` posts `revise: <text>`; every word after the item is the text, quoted or not. Either one then:
321
321
 
322
322
  1. resolves the plan item and the worker's open `approval` wait;
323
- 2. has the session manager resume a parked worker with `[conductor] event: approval ATK-312: approve HH:MM`. The worker reads the comment on the tracker before it acts.
323
+ 2. has the session manager resume a parked worker with `[conductor] event: approval ENG-312: approve HH:MM`. The worker reads the comment on the tracker before it acts.
324
324
 
325
325
  If the tracker refuses the comment, nothing is resolved. Only a plan takes approve or revise; `conduct done <id>` resolves any item.
326
326
 
@@ -335,12 +335,12 @@ The notifier routes each new inbox item to the phone, or anywhere else that take
335
335
  ```jsonc
336
336
  {
337
337
  "event": "raised", // raised, reminder, summary (quiet hours), rule (a rule's notify) or test
338
- "title": "conductor · ATK-5 npm-otp",
339
- "text": "ATK-5 · approve the npm stage", // the push in one line, as the other channels show it
338
+ "title": "conductor · ENG-5 npm-otp",
339
+ "text": "ENG-5 · approve the npm stage", // the push in one line, as the other channels show it
340
340
  "link": "https://…", // its first link, or null
341
- "project": "atk",
341
+ "project": "web",
342
342
  // the inbox item whole for raised and reminder, null otherwise
343
- "inbox": { "id": 12, "kind": "npm-otp", "item": "ATK-5", "project": "atk", "text": "…", "commands": ["npm stage approve … --otp=<code>"], "links": [{ "label": "run 7", "href": "https://…" }], "raisedAt": "2026-10-18T12:00:00.000Z", "worker": 3 }
343
+ "inbox": { "id": 12, "kind": "npm-otp", "item": "ENG-5", "project": "web", "text": "…", "commands": ["npm stage approve … --otp=<code>"], "links": [{ "label": "run 7", "href": "https://…" }], "raisedAt": "2026-10-18T12:00:00.000Z", "worker": 3 }
344
344
  }
345
345
  ```
346
346
 
@@ -348,7 +348,7 @@ The notifier routes each new inbox item to the phone, or anywhere else that take
348
348
 
349
349
  ```jsonc
350
350
  {
351
- "operator": { "name": "Jason" }, // the one person this daemon serves; messages name them ("the operator" by default)
351
+ "operator": { "name": "Sam" }, // the one person this daemon serves; messages name them ("the operator" by default)
352
352
  "quietHours": { "from": "22:00", "to": "07:00" }, // local; omit for none
353
353
  "notify": {
354
354
  "profile": "default", // the profile when none is switched to
@@ -357,7 +357,7 @@ The notifier routes each new inbox item to the phone, or anywhere else that take
357
357
  "away": [{ "to": ["pushover"] }], // built in
358
358
  "focus": [{ "kinds": ["npm-otp", "permission", "failed", "conflict", "operator", "login"], "to": ["remote-control", "pushover"] }], // built in
359
359
  "workday": [
360
- { "projects": ["cadence"], "days": ["sat", "sun"], "to": [] }, // keep these off every channel
360
+ { "projects": ["portal"], "days": ["sat", "sun"], "to": [] }, // keep these off every channel
361
361
  { "kinds": ["npm-otp"], "to": ["pushover", "remote-control"], "remind": { "afterMinutes": 10, "times": 5 } },
362
362
  { "hours": "08:00-18:00", "to": ["remote-control"] }
363
363
  ]
@@ -368,7 +368,7 @@ The notifier routes each new inbox item to the phone, or anywhere else that take
368
368
 
369
369
  A rule matches on `kinds` (inbox kinds), `projects`, `hours` (local, `HH:MM-HH:MM`, may run past midnight) and `days` (`sun` … `sat`, the day when the item is routed, so 01:00 on Saturday is `sat` even inside a Friday `22:00-02:00`); a rule with none of them matches everything. `conduct notify profile away` switches at once, without a restart, and the profile survives one. `--until 17:00` (the next 17:00), `--until 2026-10-12` (local midnight) or `--until 2026-10-12T09:00` hands back to the profile that was in force before. Switching to config.json's own `notify.profile` without `--until` hands the choice back to config.json. Each switch is a `notify.profile` event.
370
370
 
371
- - **Quiet hours** (`quietHours`): pushes are held (`notify.routed` with `outcome: "held"`). When they end, each channel gets one summary of the held items still open, such as `2 waiting since quiet hours: ATK-1 npm-otp, ATK-2 plan; 1 resolved meanwhile`, routed by the profile then in force. `conduct notify test` pushes at any hour.
371
+ - **Quiet hours** (`quietHours`): pushes are held (`notify.routed` with `outcome: "held"`). When they end, each channel gets one summary of the held items still open, such as `2 waiting since quiet hours: ENG-1 npm-otp, ENG-2 plan; 1 resolved meanwhile`, routed by the profile then in force. `conduct notify test` pushes at any hour.
372
372
  - **Dedupe:** the same project, item, kind and text pushed within `notify.dedupeMinutes` (30) to the channels it would go to now isn't pushed again; a newer note that replaces an item is the usual case. Its reminders carry on from the first push, on the newer item only. A copy that never reached the phone (held, or a failed push) gives way to the newer one.
373
373
  - **Reminders:** an open item is pushed again `notify.remind.afterMinutes` (30) after its last push, at most `notify.remind.times` (2) times, as `reminder 1/2: …`, on the channels the profile in force then gives it. A rule's own `remind` overrides that. A resolved item is never reminded. A first push that reached no channel (Pushover down, no credential) is tried again at the same interval without using up a reminder.
374
374
  - **Events:** `notify.routed` (the profile, the rule, the channels, `sent`, `held`, `deduped` or `none`), `notify.requested`, `notify.sent` and `notify.failed` (Pushover and the webhook), `notify.channel` (a channel's state changed), `notify.profile`.
@@ -390,14 +390,14 @@ Rules say what the conductor does when something happens (design note, section 9
390
390
  ]
391
391
  ```
392
392
 
393
- - **Sources, in order:** the item's project's `rules`, its machine profile's (`~/.conductor/projects/<project>.json`, named `project.<project>.machine:<id>`) before its repo's (`.claude/conductor.json`, changed by PR, named `project.<project>:<id>`; see Projects), then `~/.conductor/rules.json`, then the built-in defaults: conduct-atk's behavior written as rules (`launch`, `retire-closed`, `park-on-wait`, `resume-on-wait`, `compact-on-park`, `runaway`, `daily-cap`, `ready-to-release`, `queue-drained`, `triage`, which raises a new item that waits on the profile's triage label in the inbox, `conductor-handoff`, which escalates a worker's `Handoff · conductor` note to the console, and `hold-pre-start-handoff`, which adds the human label when a worker hands off as `human`, `operator`, `blocked` or `scope` before its `Started` note, so the scheduler never launches work no worker can do again; remove the label if the item should be picked later), and `release-pre-start-hold`, which takes that label off again when the worker clears the hold itself (its `Resumed`, or its `Started` after the hold), and leaves a human label the hold didn't add (ATK-575). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences, and a machine-profile rule replaces the repo's rule of its id (`"do": "none"` switches it off).
393
+ - **Sources, in order:** the item's project's `rules`, its machine profile's (`~/.conductor/projects/<project>.json`, named `project.<project>.machine:<id>`) before its repo's (`.claude/conductor.json`, changed by PR, named `project.<project>:<id>`; see Projects), then `~/.conductor/rules.json`, then the built-in defaults: the conductor's own behavior written as rules (`launch`, `retire-closed`, `park-on-wait`, `resume-on-wait`, `compact-on-park`, `runaway`, `daily-cap`, `ready-to-release`, `queue-drained`, `triage`, which raises a new item that waits on the profile's triage label in the inbox, `conductor-handoff`, which escalates a worker's `Handoff · conductor` note to the console, and `hold-pre-start-handoff`, which adds the human label when a worker hands off as `human`, `operator`, `blocked` or `scope` before its `Started` note, so the scheduler never launches work no worker can do again; remove the label if the item should be picked later), and `release-pre-start-hold`, which takes that label off again when the worker clears the hold itself (its `Resumed`, or its `Started` after the hold), and leaves a human label the hold didn't add (ATK-575). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences, and a machine-profile rule replaces the repo's rule of its id (`"do": "none"` switches it off).
394
394
  - **when:** `and`, `or`, `not` and parentheses over comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`, `in [a, b]`, `<label> in item.labels`, `Started in item.phases`) and events. An event rule fires when one of its events happens: `note`, `plan`, `wait.resolved`, `run.failed` (a failed launch, a session that stopped unexpectedly, or a PR's red checks: `pr.checks-failed`), `worker.idle`, `worker.exited`, `worker.permission`, `worker.waiting`, `worker.parked` (a park; the session manager also asks it of each parked worker on a sweep, for `compact`), `inbox.raised`, `pr.comment`, `item.unblocked`, `item.closed`, `provider.login-failed` (a project's login lost, once per spell; see Accounts), `queue.pick`, `queue.drained`, `queue.ready-to-release`, `queue.triage`. A rule without one is a state rule, checked on each scheduler tick against each wait, inbox item, worker or project, or once for the machine, by the fields it reads. Fields: `project`, `item.id/area/labels/tier/priority`, `item.phases` (the phases of every note the item has had, the one being posted too), `item.labelled-by` (the ids of the rules whose label add still stands on the item: a label they added, not one it had already, that no rule removed since), `plan.tier/landing`, `note.phase/kind`, `wait.kind/age`, `inbox.kind/age`, `worker.retries/phase`, `worker.context` (the session's context in tokens, see Workers), `spend.worker/item/project/today` (input, output and cache-write tokens, without cache reads: the worker's and the item's in all, the project's and the machine's today), `time`, `day`, `notify.profile`. Values carry units: `15m`, `2h`, `500k`, `2M`, `22:00`, `saturday`.
395
395
  - **do:** `launch`, `retire`, `park`, `resume worker`, `retry`, `pause launches [in <project>] [until <when> | for <duration>]`, `pause all launches …`, `unpause launches`, `focus [<project>] weight|slots <n> until <when>`, `auto-approve now`, `auto-approve after <duration>`, `inbox <kind> ["<text>"]`, `remind operator`, `notify <channel> ["<text>"]`, `notify profile <name> [until <when>]`, `label add|remove human|triage|shipped-open`, `flag shipped-open`, `escalate console "<ask>"`, `none`. One string, or a list.
396
396
  - **Guardrails:** rules act inside Area locks, slots, quiet hours and config.json's limits. A rule that would go past one is refused when it loads: `focus … slots` over `scheduler.slots`, an override with no end or one over 14 days, `retire` on anything but `item.closed`, `launch` outside `queue.pick` / `item.unblocked`, `park` outside `worker.waiting`, `resume` outside `wait.resolved` / `pr.comment`, `compact` outside `worker.parked`, `retry` outside `run.failed`. `merge`, `push`, `publish`, `approve`, `delete` and `run` aren't in the vocabulary: rules never merge, push, publish, approve prod, delete or run commands, and the daemon writes to a code host only when the owning worker asks (see Writes).
397
397
  - **Reload:** saving `rules.json` or a project's machine profile reloads its rules, and so does a fetch of its clone that brings another `.claude/conductor.json` on the base branch. A profile changed either way, or while the daemon was down, is taken whole: the daemon stores it and logs `project.updated` with the fields that `changed`, and the scheduler, watchers and the rest use it from then on. A project keeps the name its folder gives it: the repo's `name` is only what `project add` takes when no `--name` is given, and a machine profile that names another project is refused. To rename one, `conduct project remove <old name>`, then `conduct project add <url> --name <new name>` (in that order: with both registered, both would read the same queue and could launch the same item). The remove drops the old registration and its queue read, resolves its open inbox items (the refusal's among them) and logs `project.removed`, on which the daemon drops its rules and its watch of the file. It is refused while the project has workers in flight, and works without the daemon. A file the conductor refuses leaves its last good rules (for a profile, the whole last good profile) in force, logs `rules.rejected` and raises a `failed` inbox item until it loads again (`rules.loaded`), including after a restart. `conduct project add` refuses a profile whose rules don't load, with or without the daemon.
398
- - **Checking:** `conduct rules` lists the rules in force (`project.atk:#2`, `machine:runaway`, `default:launch`) and what replaces what. `conduct rules test <file>` dry-runs a sample, such as `{ "event": "pr.comment", "project": "atk", "fields": { "wait.kind": "merge", "wait.age": "3h" } }` (leave out `event` for a state rule), and shows what fires, each action's guardrail, and why every other rule of the trigger didn't. Both read the files directly when the daemon isn't running.
398
+ - **Checking:** `conduct rules` lists the rules in force (`project.web:#2`, `machine:runaway`, `default:launch`) and what replaces what. `conduct rules test <file>` dry-runs a sample, such as `{ "event": "pr.comment", "project": "web", "fields": { "wait.kind": "merge", "wait.age": "3h" } }` (leave out `event` for a state rule), and shows what fires, each action's guardrail, and why every other rule of the trigger didn't. Both read the files directly when the daemon isn't running.
399
399
 
400
- - **Why:** every action the conductor takes is an event that names its rule, directly (`rule`) or through the event that caused it (`data.cause`). `conduct why <event id>` follows that chain back and shows the rule as it fired; `conduct why ATK-315` does it for the item's newest actions.
400
+ - **Why:** every action the conductor takes is an event that names its rule, directly (`rule`) or through the event that caused it (`data.cause`). `conduct why <event id>` follows that chain back and shows the rule as it fired; `conduct why ENG-312` does it for the item's newest actions.
401
401
 
402
402
  **How actions are carried out.** Every action is a `rule.fired` event that names its rule, then `rule.done` or `rule.failed`.
403
403
 
@@ -418,16 +418,16 @@ What each action does:
418
418
  - `auto-approve now` approves the plan as its note arrives: no inbox item, no push. `conduct note` asks the rules before it records the Plan and puts the rule on `note.posted` (`autoApprove`), so the inbox raises nothing. The conductor then posts `approve: auto-approved by the conductor: rule "<id>", at once` as the key's owner and records `plan.auto-approved`, and the worker's approval wait resolves as soon as it is added (or as the comment lands). If the comment can't be posted (`plan.auto-approve-failed`), the plan's inbox item is raised and it waits on you. You can still write `revise: …` on the item: the worker's next `conduct note` reads the item, finds it after the approval, records `plan.revised`, posts nothing and exits 4 with your words, and the worker re-plans (see Notes). A Handoff note and a new Plan still go through, and an `approve` after the revise takes it back. Once you have revised a plan of an item, its later plans wait on you whatever the rules say, `auto-approve after` included. The same holds after a plan you approved yourself. A worker that posts no note meanwhile (parked, or only Handoffs) still has the revise counted: every 5 minutes the daemon reads the items whose newest Plan is approved and records `plan.revised` for one with a `revise:` after its approval. A worker parked on its PR sees the revise only when it wakes; past the PR, the brake is a PR comment. If the daemon stopped between recording the Plan and posting the approval, the approval wait raises nothing, and the next rules tick approves it once the note is 2 minutes old.
419
419
  - No default rule auto-approves. A Plan note's `**Size tier: S**` (or the older `**Size: S**`) gives `plan.tier` (and `item.tier` later), and its `**Landing:** auto|review` gives `plan.landing`: work-item lands its review paths (infra, pipelines, auth) and review tiers in `review`, so `plan.landing == auto` keeps them out of an auto-approve rule. A Plan that names neither fails the comparison and waits for you.
420
420
  - `launch` on `item.unblocked` brings the scheduler's next tick forward.
421
- - `launch` and `retry` start sessions, so they wait for `scheduler.mode` `on`, as the scheduler's own launches do. In `observe` they are logged, not done; every other action acts in any mode.
421
+ - `launch` and `retry` start sessions, so they wait for `scheduler.mode` `on`, as the scheduler's own launches do. In `off` they are logged, not done. Every other action acts in any mode, except those the scheduler's own tick fires (`item.closed`, `queue.*`), which `off` only logs on a queue read on demand.
422
422
 
423
423
  The defaults `runaway` and `daily-cap` are the budget: a runaway inbox item past `inbox.runawayTokens` for one worker, and no new launches past `scheduler.dailyTokens` today until midnight. Both count tokens without cache reads, as every `spend.*` field does and as `conduct status` shows them. On that measure a worker's median to 2026-10-14 was 442k and its most 1.68M, and a busy day ran 16M to 26M (ATK-464).
424
424
 
425
425
  ## Scheduler
426
426
 
427
- The scheduler moves conduct-atk's tick into the daemon (design note, section 8). Every `scheduler.tickSeconds` (120), and 10 s after a phase note, a retire, a failed launch or a cleared inbox item, it does three things.
427
+ The scheduler is the work loop's tick, in the daemon (design note, section 8). Every `scheduler.tickSeconds` (120), and 10 s after a phase note, a retire, a failed launch or a cleared inbox item, it does three things.
428
428
 
429
429
  1. **Read.** It reads each conducted project's queue from its tracker: for Linear, the team's projects with a priority (or the label of a label queue), the Area labels, every open item with its blockers, and the in-flight items (see Linear). Each read is one request (plus pages), within the Linear watcher's rate budget. For ADO Boards, the profile's saved query (see ADO Boards), within the ADO watcher's budget.
430
- 2. **Plan.** It works out the picks across projects, with the same order and reasons as conduct-atk's `planQueue`. A replay of conduct-atk's own Linear read is in the tests.
430
+ 2. **Plan.** It works out the picks across projects, with an order and a reason for each item. A replay of a recorded Linear read is in the tests.
431
431
  - **Order:** the tracker project's priority, then the conductor project's `weight` (profile, or `conduct focus --weight`), then the item's priority, then when it was created.
432
432
  - **Areas:** one item per Area per project until its PR merges. An Area is held by every worker in flight and every started item, and by the Areas it covers or is covered by. An item whose Plan lands in `auto` holds it no more once its PR has merged, so the next item in its Area starts from a base branch that has its work, while it waits on the dev check or the npm OTP; `conduct queue` and `conduct why` name the item it shares the Area with. The profile's `areaRelease` sets when (ATK-555):
433
433
  - `merge` (the default): when the code host (Azure DevOps or GitHub) reports the PR merged, or at the item's `Landed` note. The PR is its worker's (from `conduct pr create` or its newest `PR opened` note), else, with no worker in flight, the one its newest `PR opened` note names. Each tick reads it in one request (the PR alone, without its checks or threads), and a merged PR isn't read again. A PR still open, one closed without merging, one the daemon can't read (logged once) or an item with no PR known keeps the Area held.
@@ -435,20 +435,19 @@ The scheduler moves conduct-atk's tick into the daemon (design note, section 8).
435
435
 
436
436
  A `review` plan, or an item the daemon has no notes for, holds its Area until the item closes.
437
437
  - **Not picked:** items labelled with the profile's human label, and items whose blockers are still open, which wait (pending, or "not being worked" when nothing will clear them).
438
- - **Triage:** a profile's `tracker.triageLabel` (such as `triage`, ATK-277) marks a new item a worker filed that Jason hasn't looked at. It is never picked (`labelled triage: new, waits for Jason to triage it`, not on its own; the label matches in any case, as Boards tags do), an item it blocks waits as blocked by an item nobody works on, and `queue.triage` raises it in the inbox. Remove the label to queue it, or swap it for the human label. A rule's `label add triage` adds this label. Without the setting the label means nothing to the queue. A conductor older than the setting refuses a profile that has it (the profile is strict), so set it only once every daemon and `conduct` that reads the profile has been upgraded.
439
- - **Release waits:** a profile's `tracker.releaseWait: { "label": "waits-release", "shipped": "release-atk · Shipped" }` marks work that needs a dependency released, not only merged (a CLI change on npm `latest`, a prod deploy). It works on every tracker: the label is a Linear label, a Boards tag or a GitHub Issues label (matched without regard to case on the last two), and the blockers are Linear's blocking relations, Boards Predecessors or GitHub's issue dependencies. An item with that label waits, once its open blockers have closed, until every Done blocker has a comment whose first line starts with the `shipped` text, under any heading or bold (`waits for ATK-391 to ship`), and starts on the first tick after. On Boards that is a discussion comment, markdown or the web UI's HTML. A comment that only quotes the text doesn't count. A blocker still open in another team, or in a Boards state the profile doesn't map, waits the same way; a canceled one (Removed on Boards, closed as not planned or a duplicate on GitHub) holds nothing. A Boards Predecessor in another ADO project, or a GitHub blocker in another repo, has its comments read there. A blocker whose comments can't be read (a 403, a 404, a provider error) holds and is asked again next read, while the rest of the read goes on; so does a Boards Predecessor ADO doesn't return at all (deleted, or in a project the account can't read). A blocker that shipped before release-atk wrote its notes (2026-10-06) holds until you add the note or remove the label. With the label and no blocker at all it waits until you act (`labelled waits-release with no blocker to wait on`), for something outside the tracker such as a Claude Code release. Such an item doesn't keep its tracker project from `queue.ready-to-release`, since the release is what it waits for. On Linear the read asks about the Done blockers in one more request, only when a labelled item has one. On Boards and GitHub Issues it reads the comments of a Done blocker that has any, and again only once its comment count or revision moves (its comment count or update time on GitHub), as a new comment does; on GitHub a labelled issue also reads its blocker list every read, since GitHub's summary counts only open blockers. Without the setting, a Done blocker never holds anything.
440
- - **Slots:** `scheduler.slots` (4) across projects and the profile's `slots` per project (ATK's is 4), or `conduct focus --slots`, never more than the global ones. Slots count running workers only: a parked worker keeps its Area and frees its slot.
438
+ - **Triage:** a profile's `tracker.triageLabel` (such as `triage`, ATK-277) marks a new item a worker filed that the operator hasn't looked at. It is never picked (`labelled triage: new, waits for the operator to triage it`, not on its own; the label matches in any case, as Boards tags do), an item it blocks waits as blocked by an item nobody works on, and `queue.triage` raises it in the inbox. Remove the label to queue it, or swap it for the human label. A rule's `label add triage` adds this label. Without the setting the label means nothing to the queue. A conductor older than the setting refuses a profile that has it (the profile is strict), so set it only once every daemon and `conduct` that reads the profile has been upgraded.
439
+ - **Release waits:** a profile's `tracker.releaseWait: { "label": "waits-release", "shipped": "release · Shipped" }` marks work that needs a dependency released, not only merged (a CLI change on npm `latest`, a prod deploy). It works on every tracker: the label is a Linear label, a Boards tag or a GitHub Issues label (matched without regard to case on the last two), and the blockers are Linear's blocking relations, Boards Predecessors or GitHub's issue dependencies. An item with that label waits, once its open blockers have closed, until every Done blocker has a comment whose first line starts with the `shipped` text, under any heading or bold (`waits for ENG-311 to ship`), and starts on the first tick after. On Boards that is a discussion comment, markdown or the web UI's HTML. A comment that only quotes the text doesn't count. A blocker still open in another team, or in a Boards state the profile doesn't map, waits the same way; a canceled one (Removed on Boards, closed as not planned or a duplicate on GitHub) holds nothing. A Boards Predecessor in another ADO project, or a GitHub blocker in another repo, has its comments read there. A blocker whose comments can't be read (a 403, a 404, a provider error) holds and is asked again next read, while the rest of the read goes on; so does a Boards Predecessor ADO doesn't return at all (deleted, or in a project the account can't read). A blocker that shipped with no such note holds until you add the note or remove the label. With the label and no blocker at all it waits until you act (`labelled waits-release with no blocker to wait on`), for something outside the tracker such as a Claude Code release. Such an item doesn't keep its tracker project from `queue.ready-to-release`, since the release is what it waits for. On Linear the read asks about the Done blockers in one more request, only when a labelled item has one. On Boards and GitHub Issues it reads the comments of a Done blocker that has any, and again only once its comment count or revision moves (its comment count or update time on GitHub), as a new comment does; on GitHub a labelled issue also reads its blocker list every read, since GitHub's summary counts only open blockers. Without the setting, a Done blocker never holds anything.
440
+ - **Slots:** `scheduler.slots` (4) across projects and the profile's `slots` per project, or `conduct focus --slots`, never more than the global ones. Slots count running workers only: a parked worker keeps its Area and frees its slot.
441
441
  - **Holds:** quiet hours and `conduct pause` turn picks into waits that say why.
442
442
  3. **Act.** It runs the rules on what it found: `queue.pick` → `launch` (default `launch`), `item.closed` → `retire` (default `retire-closed`; a refused retire raises an inbox item once, except one refused while the worker's turn runs or its session starts, which is tried again each tick and raised only after 15 minutes; a partial retire is tried again after 15 minutes, and after 3 in a row it raises one inbox item that names the failed steps and any worktree left on disk, which the conductor never deletes, and is tried once a day), `queue.drained` and `queue.ready-to-release` → an inbox item once a day, `queue.triage` → a `triage` inbox item per item (default `triage`), resolved once the label goes. Each action is a `rule.fired` event, then `rule.done` or `rule.failed`. A launch that failed isn't tried again while its inbox item is open.
443
443
 
444
444
  `scheduler.mode` decides how far it goes:
445
445
  - **`on`** (the default): it acts.
446
- - **`observe`:** it reads, plans and logs each action once, and does none of them. Set it on a machine where conduct-atk's loop still launches.
447
- - **`off`:** it doesn't run; `conduct queue --fresh` still works out a plan on request.
446
+ - **`off`:** no timed queue, so nothing is launched or retired and the queue raises no inbox items. A queue read on demand (`conduct queue --fresh`, or the first `conduct queue`) works out a plan and logs each action it would take once, and does none of them.
448
447
 
449
448
  `conduct queue` shows the last plan (`--fresh` reads again): slots, holds, picks with their Area, and why each other item waits. A change of plan is a `scheduler.planned` event: its picks, the number waiting, `waitingBy` (how many wait for each reason: `in-flight`, `human`, `blocked`, `release`, `area`, `held`, `slot`) and `areaHolders` (each Area that holds an item up, with its project, its holder and the items waiting on it). In `GET /queue`, each waiting item carries the same `kind`, and an Area wait its `holder`. A project whose read fails picks nothing that tick and logs `scheduler.read-failed` once, then `scheduler.read-recovered`. An item whose last open blocker closed is an `item.unblocked` event.
450
449
 
451
- **Overrides** end on their own: `conduct pause atk --until 17:00`, `conduct pause --until tomorrow` (every project), `conduct unpause atk`, `conduct focus atk --slots 3 --until monday`. `--until` takes `17:00` (the next one), `tomorrow`, a day, or a date and time. Each is an `override.set` event, which `conduct why` attributes to the command, and they show in `conduct rules`.
450
+ **Overrides** end on their own: `conduct pause web --until 17:00`, `conduct pause --until tomorrow` (every project), `conduct unpause web`, `conduct focus web --slots 3 --until monday`. `--until` takes `17:00` (the next one), `tomorrow`, a day, or a date and time. Each is an `override.set` event, which `conduct why` attributes to the command, and they show in `conduct rules`.
452
451
 
453
452
  ## Projects
454
453
 
@@ -475,7 +474,7 @@ A conducted project is a clone the daemon owns (design note v2, sections 2 and 7
475
474
  3. It writes a starter profile to `~/.conductor/projects/<name>.json`: the code host, CI, base, tracker, `conductor:work-item` and the verify skill, 2 slots, the plan and merge gates, a release handoff and `setup`, with the `conductor` minimum its fields need. `--shared` writes `.claude/conductor.json` in the checkout instead, which the conductor reads only on `origin/<base>`: commit it, push it and run `conduct init` again. A profile file that is there is never overwritten, and a repo profile on `origin/<base>` that names the project means nothing is written; one only in the working tree stops `init` until it is pushed.
476
475
  4. It runs `conduct project add` on the checkout. For a new clone it prints the trust step: run `claude` in the clone once and accept the prompt. In a terminal it waits for Enter; with `--yes` it stops there.
477
476
  5. It runs `conduct project check`. With no verify skill, the worktree check fails as a launch would, and `init` points at the conductor plugin's `/conductor:create-verification-skill`, which loads only with the plugin: run `claude --plugin-dir <the plugin>` in the checkout (`init` prints the folder), make the skill under the name the profile's `skills.verify` gives, commit it and push it.
478
- 6. Run `conduct init` again after each step: it keeps what is done, fetches the clone and checks again. On a project that is set up, ATK among them, it writes nothing and only fetches and checks. Once the check passes, give the tracker's projects a priority (Linear) or label items `conduct` (GitHub Issues), and `conduct launch <item>`.
477
+ 6. Run `conduct init` again after each step: it keeps what is done, fetches the clone and checks again. On a project that is set up, it writes nothing and only fetches and checks. Once the check passes, give the tracker's projects a priority (Linear) or label items `conduct` (GitHub Issues), and `conduct launch <item>`.
479
478
 
480
479
  **Adding one.** `conduct project add <url | path> [--name <name>]` clones the remote into `<name>/repo`, in the CLI's own process, so its output shows and no API timeout applies. Given a path, it reads that checkout's `origin` and clones from the remote: the folder itself is never cloned, and nothing the conductor does touches it again. The name is `--name`, else the `name` the repo's `.claude/conductor.json` gives on its default branch; a repo with no profile needs `--name`. The clone is made in a staging folder first, so one that fails leaves nothing. Adding a project again fetches its clone (its origin must be the same remote) and updates the registration in place: its inbox, items and workers stay. Then the daemon registers it (or the store, with none running). Claude Code trusts a worktree by its repository, and nobody has opened a new clone in Claude Code yet, so `add` ends with the one step that trusts it: run `claude` in the clone once and accept the trust prompt, then `conduct project check <name>`.
481
480
 
@@ -494,9 +493,9 @@ Objects merge key by key and the machine's value wins; arrays (`ports`, `setup`,
494
493
 
495
494
  ## Skill trains
496
495
 
497
- Each skill change ships as its own registry publish, and each publish is a GitHub PR that Jason merges. A train turns several of them into one merge (ATK-285). A profile turns it on with `"train": { "repo": "<owner>/<repo>", "minimum": 3, "maxHours": 24 }`.
496
+ Each skill change ships as its own registry publish, and each publish is a GitHub PR that the operator merges. A train turns several of them into one merge (ATK-285). A profile turns it on with `"train": { "repo": "<owner>/<repo>", "minimum": 3, "maxHours": 24 }`.
498
497
 
499
- 1. A worker publishes as before (`atk publish`, which opens registry PR #n). It then marks #n with a note headed `## <work skill> · Train`, or `## <work skill> · Train · urgent: <why>` to ask for a train now. It waits on #n's merge as before: parked, with its slot free.
498
+ 1. A worker publishes as before (its publish opens registry PR #n). It then marks #n with a note headed `## <work skill> · Train`, or `## <work skill> · Train · urgent: <why>` to ask for a train now. It waits on #n's merge as before: parked, with its slot free.
500
499
  2. Every 15 minutes, the first a minute after the start, the daemon reads the repo's open PRs and their comments. While the marked ones not yet on a train reach `minimum`, one is urgent, or the oldest has waited `maxHours`, one `operator` inbox item asks for `conduct train <project>`. While a train PR is open, a `merge` item asks you to merge it. Each resolves itself once it no longer holds.
501
500
  3. `conduct train` takes each marked PR that targets the default branch, has passed its checks, and changes no file another member changes. It creates `train/<yyyymmdd-hhmm>` (UTC) from the default branch and merges each member's head into it with GitHub's merges API, so nothing is checked out. It then opens one PR, which lists the members as `- #n …` lines, and notes `<work skill> · Train opened · merge: <url>` on each member. A merge GitHub refuses deletes the branch, and nothing is opened. `--dry-run` only reads. It lists what waits, what a train would take, and why the rest stay out.
502
501
  4. Merge the train with a merge commit. Each member's head is then on the default branch, so GitHub shows the member merged and its worker wakes. A member pushed to after the train opened stays open, and rides the next train.
@@ -509,20 +508,20 @@ A worker waits by posting its note, running `conduct wait …` and ending the tu
509
508
 
510
509
  | Verb | Full form | Holds when |
511
510
  |---|---|---|
512
- | `pr 1850` | `pr:ado:Emergent.AgenticToolkit#1850:verdict` | the PR's verdict isn't `waiting` (see Worker verbs); the daemon keeps a recorded landing armed meanwhile |
513
- | `pr 1850 merged` | `pr:ado:Emergent.AgenticToolkit#1850:merged` | the PR is completed (or abandoned: that resolves it too, as a fact) |
511
+ | `pr 1850` | `pr:ado:Contoso.Web#1850:verdict` | the PR's verdict isn't `waiting` (see Worker verbs); the daemon keeps a recorded landing armed meanwhile |
512
+ | `pr 1850 merged` | `pr:ado:Contoso.Web#1850:merged` | the PR is completed (or abandoned: that resolves it too, as a fact) |
514
513
  | `pr 1850 checks` | `…#1850:checks` | no blocking build of the PR's current commit is queued or running (passed or failed; a path-filtered build it doesn't touch counts as passed) |
515
514
  | `pr 1850 comment` | `…#1850:comment` | a thread is open whose last word isn't a work-skill note |
516
515
  | `pr 458 merged --repo <owner>/<repo>` | `pr:github:<owner>/<repo>#458:merged` | the same on GitHub; `comment` there is a person's comment or review after the wait was registered. Without `--repo`, a project whose `codeHost` is `{ "kind": "github", "owner", "repo" }` means its own repo |
517
516
  | `pr 1850 merged checks comment` | one wait per event | registers the three in one call; the first that holds wakes the worker. `verdict` can't be combined with other events |
518
- | `merge dda5b26c` / `--for deploy-api` | `merge:ado:Emergent.AgenticToolkit@dda5b26c:for=deploy-api` | the merge's runs settled as `conduct runs` reads them (its verdict isn't `waiting`). The wake names the outcome, the failed pipelines and each release's staged version with its approve line. The ADO watcher reads the merge's git in the worker's worktree, once per wait |
517
+ | `merge dda5b26c` / `--for deploy-api` | `merge:ado:Contoso.Web@dda5b26c:for=deploy-api` | the merge's runs settled as `conduct runs` reads them (its verdict isn't `waiting`). The wake names the outcome, the failed pipelines and each release's staged version with its approve line. The ADO watcher reads the merge's git in the worker's worktree, once per wait |
519
518
  | `merge 5426434c --repo <owner>/<repo>` | `merge:github:<owner>/<repo>@5426434c` | the same on GitHub Actions: the runs the merge's push started settled |
520
- | `run atk-api-deploy 12345 done` | `run:ado:atk-api-deploy#12345:done` | the run completed, with its result |
521
- | `npm 8.5.0-dev.2 --tag dev` | `npm:@detergent-software/atk@8.5.0-dev.2:dev` | the dist-tag points at the version, read through the `npm-stage` release adapter |
519
+ | `run web-api-deploy 12345 done` | `run:ado:web-api-deploy#12345:done` | the run completed, with its result |
520
+ | `npm 8.5.0-dev.2 --tag dev` | `npm:@contoso/cli@8.5.0-dev.2:dev` | the dist-tag points at the version, read through the `npm-stage` release adapter |
522
521
  | `health dev --version 1.2.3` / `--commit <sha>` | `health:dev:version=1.2.3` | the profile's `health.dev` URL answers with that version or commit |
523
- | `approval` | `approval:linear:ATK-310` (`approval:ado-boards:CAD-12` on Boards, `approval:github-issues:PAR-12` on GitHub Issues) | an `approve` / `revise:` comment by an approver after the item's newest `<work skill> · Plan` note: the Linear key's owner (on Boards, the `az login` identity; on GitHub Issues, the gh account), or an account the profile's `approvers` names by email (Linear), unique name (Boards: the UPN, usually the email) or login (GitHub). An answer by anyone else is ignored, and the plan's inbox item says whose it was (`approval.ignored`) |
524
- | `item ATK-271 done` / `label human` / `note Landed` | `item:linear:ATK-271:state=done` | the state (name or type), a label (a tag on Boards), or a phase note; on GitHub Issues the state is `open`, `closed`, a phase label or its type |
525
- | `login ado` / `github` / `linear` | `login:ado:atk` | the project's login to the provider works again: a probe as its account (see Accounts) succeeds |
522
+ | `approval` | `approval:linear:ENG-312` (`approval:ado-boards:CAD-12` on Boards, `approval:github-issues:PAR-12` on GitHub Issues) | an `approve` / `revise:` comment by an approver after the item's newest `<work skill> · Plan` note: the Linear key's owner (on Boards, the `az login` identity; on GitHub Issues, the gh account), or an account the profile's `approvers` names by email (Linear), unique name (Boards: the UPN, usually the email) or login (GitHub). An answer by anyone else is ignored, and the plan's inbox item says whose it was (`approval.ignored`) |
523
+ | `item ENG-312 done` / `label human` / `note Landed` | `item:linear:ENG-312:state=done` | the state (name or type), a label (a tag on Boards), or a phase note; on GitHub Issues the state is `open`, `closed`, a phase label or its type |
524
+ | `login ado` / `github` / `linear` | `login:ado:web` | the project's login to the provider works again: a probe as its account (see Accounts) succeeds |
526
525
  | `time 2026-10-10T09:00` | `time:2026-10-10T14:00:00.000Z` | the time has passed (no zone means local time) |
527
526
 
528
527
  - **With the daemon**, in a worker's worktree (or with `--worker <worker>`), `conduct wait` registers the wait and returns at once. The daemon parks the session when its turn ends and resumes it with `[conductor] event: <resolution> HH:MM` once the condition holds. The text is a fact, never an approval: the worker reads the source again. The daemon checks waits against the project's stored profile (the last good one read from disk), and refuses one it can't check, such as a `health` environment the profile has no URL for. A condition the worker already waits on (not resolved yet) is that wait, so registering every condition again after a wake adds no duplicates; `wait.added` says `existing: true` then.
@@ -540,8 +539,8 @@ Accounts belong to the person running the daemon, not to the repo, so each proje
540
539
 
541
540
  ```jsonc
542
541
  "projects": {
543
- "atk": { "ado": { "token": "conductor-atk-ado" }, "linear": { "credential": "conductor-atk-linear" } },
544
- "cadence": { "ado": { "tenant": "<tenant id or domain>" }, "github": { "user": "<gh account>" } }
542
+ "web": { "ado": { "token": "conductor-web-ado" }, "linear": { "credential": "conductor-web-linear" } },
543
+ "portal": { "ado": { "tenant": "<tenant id or domain>" }, "github": { "user": "<gh account>" } }
545
544
  }
546
545
  ```
547
546
 
@@ -561,7 +560,7 @@ Accounts belong to the person running the daemon, not to the repo, so each proje
561
560
  | macOS | the login keychain (generic passwords, the target as service) | `security delete-generic-password -s <target>; security add-generic-password -s <target> -a pat -w` |
562
561
  | Linux | libsecret (the Secret Service: GNOME Keyring, KeePassXC …), attributes `service=conductor target=<target>` | `secret-tool clear service conductor target <target>; secret-tool store --label=<target> service conductor target <target> user pat` |
563
562
 
564
- Linear's key is saved with the user name `linear` instead of `pat`. On macOS and Linux the old item is deleted first: an item there is kept per user name (macOS) or per set of attributes (Linux), so saving under another user name would leave two. `conduct project check` prints the line for its platform. **Env fallback:** where the store has no such credential, or can't be read (`secret-tool` missing, or a headless Linux box with no D-Bus session or keyring), the conductor reads `CONDUCTOR_SECRET_<TARGET>` instead: the target in upper snake case, so `conductor-atk-ado` is `CONDUCTOR_SECRET_CONDUCTOR_ATK_ADO`, with its user name, when one is needed (Pushover), in the same name plus `_USER`. Targets that differ only in punctuation (`a.b`, `a-b`) share a variable, so give them names that differ in letters or digits.
563
+ Linear's key is saved with the user name `linear` instead of `pat`. On macOS and Linux the old item is deleted first: an item there is kept per user name (macOS) or per set of attributes (Linux), so saving under another user name would leave two. `conduct project check` prints the line for its platform. **Env fallback:** where the store has no such credential, or can't be read (`secret-tool` missing, or a headless Linux box with no D-Bus session or keyring), the conductor reads `CONDUCTOR_SECRET_<TARGET>` instead: the target in upper snake case, so `conductor-web-ado` is `CONDUCTOR_SECRET_CONDUCTOR_WEB_ADO`, with its user name, when one is needed (Pushover), in the same name plus `_USER`. Targets that differ only in punctuation (`a.b`, `a-b`) share a variable, so give them names that differ in letters or digits.
565
564
 
566
565
  **The names lost their ATK prefix in ATK-529:** the machine's Linear key is `conductor-linear` (it was `atk-conductor-linear`), Pushover's `conductor-pushover` (it was `atk-conductor-pushover`), and `CONDUCTOR_LINEAR_CREDENTIAL` replaces `ATK_CONDUCT_LINEAR_CREDENTIAL`, which is no longer read. On Windows, the first read of a new name that finds none copies the old Credential Manager entry to it, with its user name and secret, and leaves the old one in place for an older conductor; after that the new one is found, so the old name is never read again. Delete the old ones with `cmdkey /delete:atk-conductor-linear` and `cmdkey /delete:atk-conductor-pushover` once no older conductor runs.
567
566
 
@@ -584,16 +583,16 @@ Each credential is kept per account: an az token 20 minutes, renewed when ADO an
584
583
  2. While the spell lasts, the account's calls fail at once, asking nothing. Every `watchers.login.intervalSeconds` (60) the daemon probes each lost account again: a fresh az token for ADO (a PAT is asked `_apis/connectionData` on the project's organization, since az can't vouch for it), the token and `gh api user` for GitHub, and the key and `{ viewer { id } }` for Linear. That is the spell's back-off.
585
584
  3. The first probe the provider answers (an answer other than a refusal; a timeout, a network error or a 5xx says nothing) logs `provider.login-restored`, which resolves the item, and calls go through again.
586
585
 
587
- `conduct wait login <provider>` waits on the same probe for the worktree's project (`login:<provider>:<project>`), within the `login` watcher's budget. A worker that can't go on without a login posts its note, waits on it and ends its turn; once Jason signs in, the daemon wakes it. **Exit code 5** ("login needed") is what the worker verbs (`state`, `pr`, `land`, `reply`) exit with when they hit a lost login, here or in the daemon.
586
+ `conduct wait login <provider>` waits on the same probe for the worktree's project (`login:<provider>:<project>`), within the `login` watcher's budget. A worker that can't go on without a login posts its note, waits on it and ends its turn; once the operator signs in, the daemon wakes it. **Exit code 5** ("login needed") is what the worker verbs (`state`, `pr`, `land`, `reply`) exit with when they hit a lost login, here or in the daemon.
588
587
 
589
588
  ## Linear
590
589
 
591
- A project whose items are a Linear team's issues (ATK) sets `tracker.kind` to `linear`. Only the team is required; the rest are defaults written as data (ATK-530). A conductor before 3.0.0-dev.32 requires `areaLabelGroup`, `humanLabel` and `queue` and refuses a null `areaLabelGroup`, and the minimum check (`FIELD_SINCE`) sees only fields a profile writes, not ones it leaves out or sets to null. So keep them written, as `conduct init` does, until every daemon and `conduct` that reads the profile is on 3.0.0-dev.32 or newer.
590
+ A project whose items are a Linear team's issues sets `tracker.kind` to `linear`. Only the team is required; the rest are defaults written as data (ATK-530). A conductor before 3.0.0-dev.32 requires `areaLabelGroup`, `humanLabel` and `queue` and refuses a null `areaLabelGroup`, and the minimum check (`FIELD_SINCE`) sees only fields a profile writes, not ones it leaves out or sets to null. So keep them written, as `conduct init` does, until every daemon and `conduct` that reads the profile is on 3.0.0-dev.32 or newer.
592
591
 
593
592
  ```jsonc
594
593
  "tracker": {
595
594
  "kind": "linear",
596
- "team": "ATK", // items are ATK-<n>
595
+ "team": "ENG", // items are ENG-<n>
597
596
  "queue": "prioritized-projects", // the default: the team's projects with a priority are the queue
598
597
  // "queue": { "label": "conduct", "priority": 3 }: the team's open issues carrying the label, in one queue
599
598
  "areaLabelGroup": "Area", // the default: the label group whose labels are the Areas; null for none
@@ -616,7 +615,7 @@ The tracker seams (the brief, labels, notes, the queue and the writes) are one `
616
615
 
617
616
  ## ADO Boards
618
617
 
619
- A project whose work items live in Azure DevOps Boards (Cadence) sets `tracker.kind` to `ado-boards` (design note, section 12; ATK-319):
618
+ A project whose work items live in Azure DevOps Boards sets `tracker.kind` to `ado-boards` (design note, section 12; ATK-319):
620
619
 
621
620
  ```jsonc
622
621
  "tracker": {
@@ -624,10 +623,10 @@ A project whose work items live in Azure DevOps Boards (Cadence) sets `tracker.k
624
623
  "key": "CAD", // items are CAD-<work item id>: ADO's ids are numbers alone
625
624
  "query": "Shared Queries/Conduct", // the saved query that is the queue, by path or id
626
625
  "humanLabel": "human", // a tag here
627
- "triageLabel": "triage", // optional, a tag here too: new items Jason hasn't triaged (see Scheduler)
626
+ "triageLabel": "triage", // optional, a tag here too: new items the operator hasn't triaged (see Scheduler)
628
627
  "priority": 3, // the queue's priority against other projects' (1 urgent … 4 low)
629
- "areaRoot": "Emergent Software\\Cadence", // optional: an item's Area is its Area Path below it
630
- "states": { "Blocked": "started" } // optional, over the built-in state names
628
+ "areaRoot": "Contoso\\Portal", // optional: an item's Area is its Area Path below it
629
+ "states": { "Blocked": "started" } // optional, over the built-in state names
631
630
  // "organization", "project": the Azure DevOps code host's by default; required beside a GitHub one
632
631
  }
633
632
  ```
@@ -652,7 +651,7 @@ A project whose items are a GitHub repo's issues sets `tracker.kind` to `github-
652
651
  "kind": "github-issues",
653
652
  "key": "PAR", // items are PAR-<issue number>: issue numbers are numbers alone
654
653
  "humanLabel": "human", // a label
655
- "triageLabel": "triage", // optional, a label too: new items Jason hasn't triaged (see Scheduler)
654
+ "triageLabel": "triage", // optional, a label too: new items the operator hasn't triaged (see Scheduler)
656
655
  "queueLabel": "conduct", // the default: the open issues carrying it are the queue
657
656
  "areaPrefix": "area:", // the default: area:docs is the Area docs
658
657
  "priority": 3, // the queue's priority against other projects' (1 urgent … 4 low)
@@ -668,23 +667,23 @@ A project whose items are a GitHub repo's issues sets `tracker.kind` to `github-
668
667
  | blockers | GitHub's issue dependencies (`blocked by`) still open, inside the queue or not; one in another repo is listed as `<owner>/<repo>#<n>` |
669
668
  | human, triage, labels | labels, matched in any case |
670
669
  | states | open → unstarted; open with a `transitions` label → started (named after the label); closed as completed → completed; closed as not planned or a duplicate → canceled |
671
- | phase notes, `approve` / `revise:` | issue comments; the gh account's own comments count as Jason's, so the note heading tells them apart |
670
+ | phase notes, `approve` / `revise:` | issue comments; the gh account's own comments count as the operator's, so the note heading tells them apart |
672
671
 
673
672
  `Closes #n` would close an issue only on a merge to the default branch, so the conductor closes it on the `Landed` note instead, and a project on a `develop` branch works the same. Phase labels are added and taken off one at a time, never replaced as a set, so a label someone else added meanwhile stays. GitHub makes a label it doesn't have yet when a phase or rule first adds it; `conduct file` refuses an Area with no label.
674
673
 
675
- Everything goes through the project's gh account (`projects.<name>.github.user`, else gh's active account; see Accounts). The reads (the scheduler's queue and the watchers' waits) take from the `github` watcher's budget, as PR and merge waits on GitHub do. A wait pass reads each waited issue once, and its comments only when its update time or comment count moved. Notes, label edits and the tracker writes are outside the budget, as on Linear. That account's rate limit is shared with anything else it runs (ATK's prod API reads with Jason's), so the `github` watcher's `perMinute` bounds it.
674
+ Everything goes through the project's gh account (`projects.<name>.github.user`, else gh's active account; see Accounts). The reads (the scheduler's queue and the watchers' waits) take from the `github` watcher's budget, as PR and merge waits on GitHub do. A wait pass reads each waited issue once, and its comments only when its update time or comment count moved. Notes, label edits and the tracker writes are outside the budget, as on Linear. That account's rate limit is shared with anything else it runs, so the `github` watcher's `perMinute` bounds it.
676
675
 
677
676
  ## Broker
678
677
 
679
678
  The broker shares what every project on the machine uses (ATK-318).
680
679
 
681
- **Ports.** Each profile names the ports a worktree's dev servers need, such as ATK's `"ports": ["web", "api"]` (design note v2, section 2; ATK-526).
680
+ **Ports.** Each profile names the ports a worktree's dev servers need, such as `"ports": ["web", "api"]` (design note v2, section 2; ATK-526).
682
681
  - A launch gives the worktree one port per name from the machine's pool, `ports.first` to `ports.last` in config.json (41000 to 41999): each the pool's first port that no other worktree of any project holds, nothing listens on (IPv4 or IPv6 loopback), and that binds, which also skips a range Windows reserves. They reach `setup` and the worker as `CONDUCTOR_PORT_<NAME>` (see Workers).
683
- - The pool sits away from the usual dev ports and their slot schemes, such as ATK's 3000+N and 7071+N for a person's own worktrees, so those never take a port a worker was given.
682
+ - The pool sits away from the usual dev ports and their slot schemes, such as 3000+N for a person's own worktrees, so those never take a port a worker was given.
684
683
  - A worktree keeps its ports while the profile names the same ports; a changed list gives it new ones. Retiring the worker frees them, and so does a worktree whose directory is gone (at the next assignment).
685
684
  - `conduct ports` lists the assignments, and `conduct ports <path>` gives one worktree's (`--json` for either). Only a launch assigns. `conduct project check` previews the ports a new worktree would get, saving nothing.
686
685
 
687
- **Locks.** `conduct lock acquire chrome` takes the lock every project's verify runs share. It answers the work-issue helper's JSON (`acquired`, `heldBy`, `since`, `refreshed`; `released` for a release; `verdict` for a status).
686
+ **Locks.** `conduct lock acquire chrome` takes the lock every project's verify runs share. It answers JSON: `acquired`, `heldBy`, `since`, `refreshed`; `released` for a release; `verdict` for a status.
688
687
  - The owner is `--owner`, else the item of the worker whose worktree this is.
689
688
  - Acquiring again refreshes it. 30 minutes after its holder last acquired it, another owner may take it over.
690
689
  - `--wait <minutes>` asks again every 15 s until then.
@@ -731,7 +730,7 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
731
730
  | File | Holds |
732
731
  |---|---|
733
732
  | `projects/` | each conducted project's machine profile, clone and worktrees (see Projects) |
734
- | `config.json` | machine settings, JSONC (comments allowed), validated at startup; `clones.fetchMinutes` (10, see Projects); `inbox.runawayTokens` (1500000, the `runaway` rule's number); `notify` and `quietHours` (see Notifications); `scheduler` holds `mode` (`on`, `observe` or `off`), `slots` (4, across projects, and the most any rule can give one project), `dailyTokens` (20000000, the `daily-cap` rule's number) and `tickSeconds` (120); `sessions` holds `pollSeconds` (10), `settleSeconds` (5), `permissionMinutes` (5), `idleMinutes` (20), `compactTokens` (300000, the `compact-on-park` rule's number); `watchers` holds `ado`, `github`, `health`, `linear` and `npm`, each `{ intervalSeconds, perMinute }` (30/60, 30/30, 30/30, 30/10, 20/30), `statsMinutes` (10) and `conflictMinutes` (30); `workers` holds `tab` (false: workers run headless) and `terminal` (`auto`; see Tabs) |
733
+ | `config.json` | machine settings, JSONC (comments allowed), validated at startup; `clones.fetchMinutes` (10, see Projects); `inbox.runawayTokens` (1500000, the `runaway` rule's number); `notify` and `quietHours` (see Notifications); `scheduler` holds `mode` (`on` or `off`), `slots` (4, across projects, and the most any rule can give one project), `dailyTokens` (20000000, the `daily-cap` rule's number) and `tickSeconds` (120); `sessions` holds `pollSeconds` (10), `settleSeconds` (5), `permissionMinutes` (5), `idleMinutes` (20), `compactTokens` (300000, the `compact-on-park` rule's number); `watchers` holds `ado`, `github`, `health`, `linear` and `npm`, each `{ intervalSeconds, perMinute }` (30/60, 30/30, 30/30, 30/10, 20/30), `statsMinutes` (10) and `conflictMinutes` (30); `workers` holds `tab` (false: workers run headless) and `terminal` (`auto`; see Tabs) |
735
734
  | `rules.json` | machine rules, JSONC, reloaded on save (see Rules) |
736
735
  | `token` | the API token, readable by this user only; every request sends `Authorization: Bearer <token>` |
737
736
  | `conductor.pid`, `port` | the single-instance lock and where the API listens (`127.0.0.1`) |
@@ -748,15 +747,15 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
748
747
  - `POST /console/cache` with `{ "action": "warm" | "cold" | "failed", "contextTokens", "usage": { "cacheRead", "cacheWrite", "input", "output" } | null, "warms" }` or `{ "action": "clear", "contextTokens", "reason" }`: the mod's report on the idle console's prompt cache (see Mod); 201 with the `console.cache` event's id.
749
748
  - `POST /plugin/seen` with `{ "sessionId", "version"? }`: the mod's report that the conductor plugin loaded in a session (see Plugin); 200 with `{ "worker": <name> | null }`, the worker that session belongs to.
750
749
  - `GET /fleet`: the workers in flight, with session activity, phase, PR, waits and spend (today, total).
751
- - `POST /workers` with `{ "item": "ATK-309", "project"?, "probe"?, "prompt"?, "conductor"? }`: launch; answers 202 with the `launching` worker. `GET /workers/:id` shows it with its latest events.
750
+ - `POST /workers` with `{ "item": "ENG-312", "project"?, "probe"?, "prompt"?, "conductor"? }`: launch; answers 202 with the `launching` worker. `GET /workers/:id` shows it with its latest events.
752
751
  - `DELETE /workers/:id` (`?force=true`): retire; 409 with the unfinished work (the worktree's, and each `.scratch/repos` checkout's under `checkouts`) when it refuses.
753
752
  - `POST /workers/:id/attach`: wake if parked, mark attached, open the tab; `tab` is why none opened, else null, and `hint` how to reach one off the desktop (`tmux attach -t <session>`).
754
753
  - `POST /workers/:id/prs` with `{ "title", "body", "repo"? }`: open the worker's PR after the ownership checks (see Worker verbs); 201 with the PR, 409 refused, 503 a lost login (with `login.provider` and `login.command`), 502 the host failed.
755
754
  - `POST /workers/:id/landings` with `{ "pr", "reason", "mode"?, "rearm"?, "repo"?, "requeue"? }`: `conduct land` for the worker; 200 with the landing and its verdict. `POST /workers/:id/replies` with `{ "pr", "body", "thread"?, "repo"? }`: `conduct reply`; 201. Both answer 409, 503 and 502 as `/prs` does.
756
- - `POST /items/:id/notes` with `{ "body": "## work-issue · …", "worker"?: 1 }`: post a phase note (see Notes); 201 with the note and the worker. 409 when it won't post it; 502 when the tracker didn't take it, with `posted: "no"` or `"unknown"`.
755
+ - `POST /items/:id/notes` with `{ "body": "## work-item · …", "worker"?: 1 }`: post a phase note (see Notes); 201 with the note and the worker. 409 when it won't post it; 502 when the tracker didn't take it, with `posted: "no"` or `"unknown"`.
757
756
  - `GET /items/:id/brief` (`?fresh=true`): the item's brief (see Briefs), as `{ "brief": … }`. 404 when the tracker has no such item, 409 when no project tracks it, 502 when the tracker couldn't be read.
758
757
  - `GET /inbox`: the open inbox items, oldest first.
759
- - `POST /inbox/:id/resolve`: Jason's answer (see Inbox). The body is `{ "action": "approve", "text"? }`, `{ "action": "revise", "text" }` or `{ "action": "done" }`.
758
+ - `POST /inbox/:id/resolve`: the operator's answer (see Inbox). The body is `{ "action": "approve", "text"? }`, `{ "action": "revise", "text" }` or `{ "action": "done" }`.
760
759
  - 200 with the item, the comment and the waits it resolved.
761
760
  - 404 when there is no such item.
762
761
  - 409 when the item is resolved already or isn't a plan.
@@ -769,11 +768,11 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
769
768
  - `GET /queue`: the scheduler's last plan; `?fresh=true` reads the trackers and plans now (see Scheduler).
770
769
  - `GET /overrides`, and `POST /overrides` with `{ "action": "pause", "project"?, "until"? }`, `{ "action": "unpause", "project"? }` or `{ "action": "focus", "project", "slots"?, "weight"?, "until" }` (and `by`, who set it): 201 with the overrides; 409 for an unknown project, a time past, or a focus over `scheduler.slots`.
771
770
  - `GET /history?since=<ISO time>&until=<ISO time>&project=<name>`: the report for that window (see History). Every parameter is optional; the default window is the 24 hours before `until` (default: now). 400 for a time it can't read, a window whose start isn't before its end, or an unknown parameter.
772
- - `GET /why/:id`: the event's chain of causes and the rule behind it; `GET /why?item=ATK-315` the same for the item's newest actions.
773
- - `POST /rules/test` with `{ "event"?: "pr.comment", "project"?: "atk", "fields"?: { … } }`: a dry run; 400 for a sample it can't read.
771
+ - `GET /why/:id`: the event's chain of causes and the rule behind it; `GET /why?item=ENG-312` the same for the item's newest actions.
772
+ - `POST /rules/test` with `{ "event"?: "pr.comment", "project"?: "web", "fields"?: { … } }`: a dry run; 400 for a sample it can't read.
774
773
  - `GET /ports`: every worktree's port assignment. `POST /ports` with `{ "worktree": "<absolute path>", "prefer"?: 3 }`: the checkout's (see Broker); 200 with `assignment`, 409 when the path is in no registered project or no slot is free.
775
- - `GET /locks`: the locks held. `POST /locks/:name` with `{ "action": "acquire" | "release" | "status", "owner"? }`: 200 with the work-issue helper's lock JSON; 409 for an acquire or release without an owner.
776
- - `POST /waits` with `{ "worker": 1, "condition": "pr:ado:Emergent.AgenticToolkit#1850:merged" }`: a wait in its full form (see Waits), stored in its normal form; 201 with the wait. `GET /waits/:id` shows one, with its resolution once it has one.
774
+ - `GET /locks`: the locks held. `POST /locks/:name` with `{ "action": "acquire" | "release" | "status", "owner"? }`: 200 with the lock's JSON (see Broker); 409 for an acquire or release without an owner.
775
+ - `POST /waits` with `{ "worker": 1, "condition": "pr:ado:Contoso.Web#1850:merged" }`: a wait in its full form (see Waits), stored in its normal form; 201 with the wait. `GET /waits/:id` shows one, with its resolution once it has one.
777
776
  - A refusal (a duplicate launch, a bad condition, a busy worker) is a 409 with `error` and `details`.
778
777
 
779
778
  ## Restarts
@@ -788,7 +787,7 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
788
787
 
789
788
  ## On PATH
790
789
 
791
- Install the package globally, next to `atk`. `dev` follows `develop`; `latest` follows `main`:
790
+ Install the package globally. `dev` follows `develop`; `latest` follows `main`:
792
791
 
793
792
  ```bash
794
793
  npm install --global @detergent-software/conductor@dev
@@ -800,12 +799,12 @@ conduct project add <the project's remote URL, or a checkout of it>
800
799
 
801
800
  A profile that uses new fields says which conductor reads them: `"conductor": "3.0.0-dev.9"`, the version that first read the newest of them (a `-dev.N` while it is only on `dev`; a stable minimum such as `3.0.0` is newer than every `3.0.0-dev.N`, as semver orders them). Every conductor from the one that knows this key (ATK-573) checks it before the rest of the profile, and an older one refuses the file with one line instead of a list of fields: `conductor: needs 3.0.0-dev.9 or newer; this is 3.0.0-dev.7: run \`conduct upgrade --tag dev\`` (`--tag latest` for a stable minimum). The worker verbs that read the profile in a worktree (`conduct state`, `conduct branch`, `conduct search`, a lock with no daemon) say the same and exit 1, and the daemon's inbox item carries the line and the upgrade command. While a project's profile on disk doesn't read (a minimum above this conductor, a schema problem, another protocol), the scheduler launches none of the project's items: each waits in `conduct queue` as `profile refused: <its first problem>`, since a new worktree would hold the same file and its verbs would refuse it. A profile that reads but whose rules are refused, or that renames the project, keeps the last good profile and launches as before. Workers already running carry on, and launches resume on the first tick after the file loads.
802
801
 
803
- `src/profile-fields.ts` records the conductor that first read each field as it is written today (`FIELD_SINCE`, ATK-577), and its type makes a schema field without a record a typecheck error. Fields that 2.0.0 read, the first conductor that reads protocol 2, need no minimum: an older one refuses the profile on `protocol` first. `minimumProblems` reads a profile as written, so a field a default fills in doesn't count. It names each field the profile writes that is newer than its minimum, or newer than 2.0.0 when the profile sets none. A conductor test holds ATK's `.claude/conductor.json` to it, and `atk-conductor-ci` runs on changes to that file. Any other project hears it from `conduct project check`, as a `warn` on its profile check.
802
+ `src/profile-fields.ts` records the conductor that first read each field as it is written today (`FIELD_SINCE`, ATK-577), and its type makes a schema field without a record a typecheck error. Fields that 2.0.0 read, the first conductor that reads protocol 2, need no minimum: an older one refuses the profile on `protocol` first. `minimumProblems` reads a profile as written, so a field a default fills in doesn't count. It names each field the profile writes that is newer than its minimum, or newer than 2.0.0 when the profile sets none. A conductor test holds this repo's own `.claude/conductor.json` to it, and `atk-conductor-ci` runs on changes to that file. Any other project hears it from `conduct project check`, as a `warn` on its profile check.
804
803
 
805
804
  A PR that adds or reshapes a field can't know the number semantic-release will give it, so it takes the next one:
806
805
 
807
806
  1. Find the newest conductor tag: `git tag -l 'conductor-v*' --sort=-v:refname` (after a `git fetch --tags`). On `develop` the next release is its `-dev.N` plus one, such as `3.0.0-dev.12` after `3.0.0-dev.11`, unless the PR's commits make a bigger bump than that series: after a stable release, the first `-dev.1` of the version its commit type makes (a fix's patch, a feature's minor), and for a breaking change the next major's `-dev.1`, as `2.2.0-dev.3` was followed by `3.0.0-dev.1`.
808
- 2. Record that version for the field in `FIELD_SINCE`. If ATK's profile writes the field in the same PR, raise its `conductor` to it too. Its launches are then held, with the upgrade line, until the daemon runs that release (`conduct upgrade --tag dev`, once Jason has approved its stage).
807
+ 2. Record that version for the field in `FIELD_SINCE`. If this repo's own profile writes the field in the same PR, raise its `conductor` to it too. Its launches are then held, with the upgrade line, until the daemon runs that release (`conduct upgrade --tag dev`, once a maintainer has approved its stage).
809
808
  3. After the merge, `conduct runs <merge>` gives the version `atk-conductor-release` staged. If another conductor PR took the number first, the record and the minimum are one too low: fix both in a follow-up. Too low lets the version before the field read the profile, and fail with the field list this key replaces. Too high holds launches until that version exists.
810
809
 
811
810
  A `test`, `chore` or `docs` change stages no release and records nothing.
@@ -818,8 +817,8 @@ Then `conduct project check <name>` says whether the project can be conducted, b
818
817
  - **auth:** which credential each provider the project uses signs in with (project token, machine token or CLI login; see Accounts), probed as the daemon would. For each that isn't the project's own token, a detail line gives the scopes, the line that saves a token in the platform's store (`cmdkey`, `security` or `secret-tool`) and the config key that would scope it; that is advice, and the check passes. A token that is there but refused, or no credential at all, fails.
819
818
  - **tracker:** the tracker's login answers, and the queue reads (Linear: the team, and whether any of its projects has a priority; Boards: the saved query; GitHub Issues: the labelled issues).
820
819
  - **code-host** and **ci:** the code host's login answers and the base branch exists; each release's `pipeline` names exactly one of the repo's pipelines.
821
- - **worktree:** the launch's worktree steps on a throwaway `conduct-check-<time>` in the project's worktrees folder: `git worktree add` in the clone from `origin/<base>`, `localFilesFrom`'s files, the ports the broker would give, and `setup`. Then the profile's skills must be there after it, and a `warn` says when git doesn't ignore `.claude/settings.local.json`, where the ports go. A `claude --bg` with no prompt then checks that Claude Code would start a session there: it starts idle, costs no tokens and is removed at once. Claude Code keeps workspace trust per repository, so a clone nobody has opened in Claude Code fails here with the one step that trusts it, `claude` run in the clone once with its trust prompt accepted (ATK-571). It is removed with its branch, whatever happened. The one check that writes; `--no-worktree` skips it, since setup can take a while (ATK's runs `pnpm install`).
822
- - **skills:** a `conductor:<skill>` is in this package's plugin, and `skills.work` declares `protocol: 2` in its SKILL.md frontmatter (worker protocol 2). A project's own skills arrive through its `setup` (ATK's runs `atk install`), not in the clone, so the worktree check looks for them.
820
+ - **worktree:** the launch's worktree steps on a throwaway `conduct-check-<time>` in the project's worktrees folder: `git worktree add` in the clone from `origin/<base>`, `localFilesFrom`'s files, the ports the broker would give, and `setup`. Then the profile's skills must be there after it, and a `warn` says when git doesn't ignore `.claude/settings.local.json`, where the ports go. A `claude --bg` with no prompt then checks that Claude Code would start a session there: it starts idle, costs no tokens and is removed at once. Claude Code keeps workspace trust per repository, so a clone nobody has opened in Claude Code fails here with the one step that trusts it, `claude` run in the clone once with its trust prompt accepted (ATK-571). It is removed with its branch, whatever happened. The one check that writes; `--no-worktree` skips it, since setup can take a while (such as a `pnpm install`).
821
+ - **skills:** a `conductor:<skill>` is in this package's plugin, and `skills.work` declares `protocol: 2` in its SKILL.md frontmatter (worker protocol 2). A project's own skills arrive through its `setup` (a command that installs them), not in the clone, so the worktree check looks for them.
823
822
  - **ports:** the machine's pool has a free port for each of the profile's `ports`, saving nothing; `skip` for a profile with none.
824
823
  - **health:** each `health` URL answers; a `warn` at most, since an environment can be down.
825
824
 
@@ -890,9 +889,6 @@ It writes the plugin copy if none exists yet (see Plugin), then runs `claude --p
890
889
  **Escalations.** A rule's `escalate console "<ask>"` appends `console.escalated` (the default `conductor-handoff` does it for a worker's `Handoff · conductor` note). The mod wakes the model with `[conductor] escalation #<id> on <item>: <ask>`. The console does the job and reports back with `conduct escalations done <id> "<what it found or did>"`, which logs `console.handled` with the escalation as its cause, so `conduct why <that event>` leads to the rule. With `--ask`, the finding also goes in the inbox as a `console` item, pushed like any other, for something only you can do. `conduct escalations` lists those not yet handled, from the last 14 days, including the ones raised while no console was open; the console reads it when it starts.
891
890
 
892
891
  **With no console open.** The daemon counts the console's streams of `/events` (the mod's `conduct events --follow --console`). When escalations are open and no console stream has been open for 30 s, it raises one `console` inbox item, "escalations wait for the console and none is open: start one with conduct console", pushed like any other. The 30 s cover a console's own reconnects (the mod restarting `conduct events`, a daemon restart). The item resolves when a console opens or the last escalation is handled. The built-in `focus` profile doesn't push the `console` kind. The daemon never opens a console itself: that would wake a model on every escalation, unattended.
893
-
894
- **Moving from conduct-atk.** Stop the conduct-atk session, `conduct upgrade` (the scheduler now launches by default), then `conduct console`. A machine that keeps conduct-atk's loop sets `"scheduler": { "mode": "observe" }` in `config.json`.
895
-
896
892
  ## Plugin
897
893
 
898
894
  `plugin/` is the conductor plugin (design note v2, section 4), one Claude Code plugin named `conductor` that holds:
@@ -921,9 +917,9 @@ The mod is the plugin's hooks module, code that costs no tokens, which does the
921
917
  - **Every session** sends its plugin report on start, and nothing else: no status line, and no other calls to the daemon.
922
918
  - **The console**, the session started with `CONDUCTOR_CONSOLE=1` (`conduct console` starts it; see Console), reads `GET /fleet`, `GET /inbox` and `GET /notify` every 30 s and gets:
923
919
  - **the band** above the prompt while the inbox has items: one hotkey per item (a plan approves after a second press, `y`, since a digit typed into an empty prompt presses a band button; an npm OTP copies its `npm stage approve` line; the rest open the pane), and `o` for the pane;
924
- - **the pane** (`/conductor`, `o`, or opened at the start, docked as a sidebar on a fullscreen terminal of 144 columns or more): the notification profile (a picker; buttons on mobile) and Remote Control's state, the inbox with each item's actions (approve, revise, copy a command, open a link, done; on a Windows terminal, open is a button that opens a web link in the default browser once, since a ctrl+click on a link in a pane opened it twice, ATK-494), the workers by project (each project's rule has [pause] and [focus]), and today's spend. The header counts the workers running and parked and the inbox in words. Each worker is one line in columns that line up across projects, cut to the pane's width so it never wraps: a glyph, the item, its phase, its PR, what it waits on in words (`PR #557 to merge (agentic-toolkit-registry)`, `npm 8.9.0-dev.1 on dev (atk)`, `login (ado)`) or else its activity, and the age. As the pane narrows, the age goes first, then the wait, then the PR and the phase (ATK-521). The glyphs are one cell wide: `●` in a turn, `◌` up between turns, `!` stuck on a prompt, `○` parked, `×` failed or gone, `·` starting. A row expands to attach and why, its tokens today, its whole PR ref and its raw wait keys;
920
+ - **the pane** (`/conductor`, `o`, or opened at the start, docked as a sidebar on a fullscreen terminal of 144 columns or more): the notification profile (a picker; buttons on mobile) and Remote Control's state, the inbox with each item's actions (approve, revise, copy a command, open a link, done; on a Windows terminal, open is a button that opens a web link in the default browser once, since a ctrl+click on a link in a pane opened it twice, ATK-494), the workers by project (each project's rule has [pause] and [focus]), and today's spend. The header counts the workers running and parked and the inbox in words. Each worker is one line in columns that line up across projects, cut to the pane's width so it never wraps: a glyph, the item, its phase, its PR, what it waits on in words (`PR #557 to merge (skills-registry)`, `npm 8.9.0-dev.1 on dev (cli)`, `login (ado)`) or else its activity, and the age. As the pane narrows, the age goes first, then the wait, then the PR and the phase (ATK-521). The glyphs are one cell wide: `●` in a turn, `◌` up between turns, `!` stuck on a prompt, `○` parked, `×` failed or gone, `·` starting. A row expands to attach and why, its tokens today, its whole PR ref and its raw wait keys;
925
921
  - **commands answered in code**, never by the model: `/conductor`, `/inbox`, `/approve <item | #id>`, `/revise <item | #id> <text>`, `/attach <item | worker>`, `/notify [<profile> [--until <when>]]`, `/pause [<project>] [--until <when>]`, `/unpause [<project>]`, `/focus-project <project> [--until <when>]` (puts its items first, until midnight without `--until`; not `/focus`, which is Claude Code's own);
926
- - **a check that `/conductor:conductor-console` ran**: a console started without it (by hand, as `claude --plugin-dir … -n conductor`) shows `console without its skill: run /conductor:conductor-console` in the band, and each prompt, Jason's or an escalation's, carries a line telling the model to load the skill before it answers. Running the skill (`/conductor:conductor-console`, or the model's Skill tool) clears both, until a `/clear` or a resume starts a conversation without it;
922
+ - **a check that `/conductor:conductor-console` ran**: a console started without it (by hand, as `claude --plugin-dir … -n conductor`) shows `console without its skill: run /conductor:conductor-console` in the band, and each prompt, the operator's or an escalation's, carries a line telling the model to load the skill before it answers. Running the skill (`/conductor:conductor-console`, or the model's Skill tool) clears both, until a `/clear` or a resume starts a conversation without it;
927
923
  - **a warm prompt cache while it idles** (ATK-386). The console's cache lasts an hour after its last use, and a wake after that writes the whole context again. So 55 minutes after the console's last turn, the mod sends one ping (`$.model.fork`, the console's own last request plus a one-word ask, no tools, no turn in the transcript). The API serves the ping from the cache for a tenth of a write's price, and the hour starts over. It pings again every 55 minutes until a turn comes, and each turn starts the timer over. At an idle point it runs `/clear` instead when the context is 150k or more, or after 12 pings in a row (the 13th idle point, about 12 hours after the last turn), and the band says `fresh console (cleared while idle: …)` until the next prompt loads the skill. A ping that read less than half the context from the cache (the cache was gone anyway, as under a 5-minute TTL) stops the pings until the next turn. Each ping and clear goes to `POST /console/cache`, which logs a `console.cache` event with the context size and what the ping cost. A fork isn't in the transcript, so that event is the only place its cost shows;
928
924
  - **the stream**: it runs `node <the cli.js that conductor.json names> events --follow --console` (`--console` tells the daemon a console is open), redraws on each event, toasts each new inbox item, and acts on three events:
929
925