@detergent-software/conductor 1.4.0-dev.1 → 1.4.0-dev.11

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 (103) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +86 -20
  3. package/build/brief-cli.js +105 -23
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/brief.js +107 -9
  6. package/build/brief.js.map +1 -1
  7. package/build/broker.js +40 -14
  8. package/build/broker.js.map +1 -1
  9. package/build/cli.js +49 -18
  10. package/build/cli.js.map +1 -1
  11. package/build/code-host-cli.js +46 -2
  12. package/build/code-host-cli.js.map +1 -1
  13. package/build/code-host.js +2 -0
  14. package/build/code-host.js.map +1 -1
  15. package/build/conditions.js +22 -13
  16. package/build/conditions.js.map +1 -1
  17. package/build/config.js +4 -0
  18. package/build/config.js.map +1 -1
  19. package/build/daemon.js +74 -19
  20. package/build/daemon.js.map +1 -1
  21. package/build/follow-ups.js +72 -0
  22. package/build/follow-ups.js.map +1 -0
  23. package/build/help.js +712 -208
  24. package/build/help.js.map +1 -1
  25. package/build/in-process.js +30 -0
  26. package/build/in-process.js.map +1 -0
  27. package/build/inbox.js +137 -11
  28. package/build/inbox.js.map +1 -1
  29. package/build/lib/providers/ado/code-host.js +8 -5
  30. package/build/lib/providers/ado/code-host.js.map +1 -1
  31. package/build/lib/providers/ado/pipelines.js +16 -10
  32. package/build/lib/providers/ado/pipelines.js.map +1 -1
  33. package/build/lib/providers/github/actions.js +11 -0
  34. package/build/lib/providers/github/actions.js.map +1 -1
  35. package/build/lib/providers/github/code-host.js +17 -0
  36. package/build/lib/providers/github/code-host.js.map +1 -1
  37. package/build/lib/providers/index.js +1 -1
  38. package/build/lib/providers/index.js.map +1 -1
  39. package/build/lib/providers/verdicts/pr.js +14 -12
  40. package/build/lib/providers/verdicts/pr.js.map +1 -1
  41. package/build/manager.js +116 -13
  42. package/build/manager.js.map +1 -1
  43. package/build/note.js +95 -14
  44. package/build/note.js.map +1 -1
  45. package/build/notes.js +104 -16
  46. package/build/notes.js.map +1 -1
  47. package/build/notify.js +2 -1
  48. package/build/notify.js.map +1 -1
  49. package/build/package-info.js +12 -0
  50. package/build/package-info.js.map +1 -0
  51. package/build/profile.js +64 -13
  52. package/build/profile.js.map +1 -1
  53. package/build/project-check-cli.js +85 -0
  54. package/build/project-check-cli.js.map +1 -0
  55. package/build/project-check.js +362 -0
  56. package/build/project-check.js.map +1 -0
  57. package/build/resolve.js +11 -3
  58. package/build/resolve.js.map +1 -1
  59. package/build/rule-actions.js +26 -6
  60. package/build/rule-actions.js.map +1 -1
  61. package/build/rules-service.js +33 -20
  62. package/build/rules-service.js.map +1 -1
  63. package/build/rules.js +31 -14
  64. package/build/rules.js.map +1 -1
  65. package/build/runs.js.map +1 -1
  66. package/build/scheduler.js +43 -1
  67. package/build/scheduler.js.map +1 -1
  68. package/build/server.js +76 -7
  69. package/build/server.js.map +1 -1
  70. package/build/spend.js +37 -4
  71. package/build/spend.js.map +1 -1
  72. package/build/store.js +42 -4
  73. package/build/store.js.map +1 -1
  74. package/build/tracker-writes.js +74 -0
  75. package/build/tracker-writes.js.map +1 -0
  76. package/build/trackers.js +20 -0
  77. package/build/trackers.js.map +1 -1
  78. package/build/watchers/ado.js +7 -1
  79. package/build/watchers/ado.js.map +1 -1
  80. package/build/watchers/boards-writes.js +182 -34
  81. package/build/watchers/boards-writes.js.map +1 -1
  82. package/build/watchers/boards.js +7 -2
  83. package/build/watchers/boards.js.map +1 -1
  84. package/build/watchers/github.js +20 -2
  85. package/build/watchers/github.js.map +1 -1
  86. package/build/watchers/index.js +32 -4
  87. package/build/watchers/index.js.map +1 -1
  88. package/build/watchers/issues-brief.js +48 -0
  89. package/build/watchers/issues-brief.js.map +1 -0
  90. package/build/watchers/issues-queue.js +85 -0
  91. package/build/watchers/issues-queue.js.map +1 -0
  92. package/build/watchers/issues-writes.js +233 -0
  93. package/build/watchers/issues-writes.js.map +1 -0
  94. package/build/watchers/issues.js +264 -0
  95. package/build/watchers/issues.js.map +1 -0
  96. package/build/watchers/linear-writes.js +198 -0
  97. package/build/watchers/linear-writes.js.map +1 -0
  98. package/build/watchers/source.js.map +1 -1
  99. package/build/worktrees.js +1 -0
  100. package/build/worktrees.js.map +1 -1
  101. package/mod/hooks/model.ts +11 -0
  102. package/mod/hooks/register.tsx +25 -3
  103. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,108 @@
2
2
 
3
3
  Releases are tagged `conductor-v<version>` and staged on npm by `atk-conductor-release` (`pipelines/release-conductor.yml`), which writes each section below. `scripts/check-changelog.mjs` fails when a tag has no section here.
4
4
 
5
+ ## [1.4.0-dev.11](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.10&targetVersion=GTconductor-v1.4.0-dev.11) (2026-10-15)
6
+
7
+ ### Features
8
+
9
+ * **conductor:** compact a parked worker whose context grew too big ([4259ec2](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/4259ec2d570e4dcc13760a8941a219cb60968302))
10
+
11
+ ### Bug Fixes
12
+
13
+ * **conductor:** address review of the context compaction ([51ebe19](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/51ebe1937f83e6cd448b028afac52b6ecb585eef))
14
+ * **conductor:** leave a worker's context unknown after a compaction ([e6c79d3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/e6c79d3b48677620403bd9cc32bc1a9599d90b19))
15
+
16
+ ## [1.4.0-dev.10](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.9&targetVersion=GTconductor-v1.4.0-dev.10) (2026-10-15)
17
+
18
+ ### Bug Fixes
19
+
20
+ * **conductor:** back off a partial retire and raise it once (ATK-496) ([7fe082a](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/7fe082a0b211b95eddc5e7fd93ced4bd3ff0a925))
21
+
22
+ ## [1.4.0-dev.9](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.8&targetVersion=GTconductor-v1.4.0-dev.9) (2026-10-15)
23
+
24
+ ### Features
25
+
26
+ * **conductor:** `conduct project check <name>` (ATK-490) ([393868c](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/393868c9d346f661c1fddb55788551cf98d66faf))
27
+ * **conductor:** read a branch's tip and a repo's pipelines (ATK-490) ([40af3c1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/40af3c1362b16b07ea3c135a69880adcbdb36626))
28
+
29
+ ### Bug Fixes
30
+
31
+ * **conductor:** keep a failed atk audit on one check line (ATK-490) ([ae78d30](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/ae78d3012bb68d178f85772f6303f5c1b97d4e32))
32
+ * **conductor:** project check review fixes (ATK-490) ([7518d91](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/7518d9155066c4e7f7b301b4bee703b3d3a4a0e8))
33
+
34
+ ## [1.4.0-dev.8](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.7&targetVersion=GTconductor-v1.4.0-dev.8) (2026-10-15)
35
+
36
+ ### Bug Fixes
37
+
38
+ * **conductor:** open an inbox link once from the terminal pane (ATK-494) ([1634517](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/16345173815330b2cea5361e964ebdf4f2921c15))
39
+ * **conductor:** open only web links, on Windows only (ATK-494) ([8d2f608](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/8d2f6084cc99900b2c012cddbb5e287b488f1213))
40
+
41
+ ## [1.4.0-dev.7](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.6&targetVersion=GTconductor-v1.4.0-dev.7) (2026-10-15)
42
+
43
+ ### Features
44
+
45
+ * **conductor:** take the whole profile from disk, not only its rules ([b22705a](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b22705ad2adb2bb6e886e5f99e011ccc0767b5c0))
46
+
47
+ ### Bug Fixes
48
+
49
+ * **conductor:** keep a project's name, and say only what is in force ([51efeb0](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/51efeb0977e005d0daa84c67f6590c7817339b87))
50
+ * **conductor:** refuse an unreadable stored profile's bad file once ([4e100f1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/4e100f14fb2a9ed36b701cdb8f2a36d91bacacb7))
51
+
52
+ ## [1.4.0-dev.6](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.5&targetVersion=GTconductor-v1.4.0-dev.6) (2026-10-15)
53
+
54
+ ### Bug Fixes
55
+
56
+ * **conductor:** guard the red-checks inbox paths found in review (ATK-468) ([c567a4d](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/c567a4d63192c1e7b6c630e4292846b4a78166e8))
57
+ * **conductor:** hold merges on red checks by item, not by worker (ATK-468) ([78c16ea](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/78c16ea33c78e6b8dd1d94b5b2f5d58a3c950b5a))
58
+ * **conductor:** raise red PR checks as a failure and hold the merge (ATK-468) ([5897adb](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/5897adb9033b89d575fcd26f391c012637dd602b))
59
+
60
+ ## [1.4.0-dev.5](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.4&targetVersion=GTconductor-v1.4.0-dev.5) (2026-10-15)
61
+
62
+ ### Features
63
+
64
+ * **conductor:** easier-to-scan conduct help (ATK-478) ([8c34660](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/8c34660ba7ecbac439312e317eb359767efedb21))
65
+
66
+ ### Refactoring
67
+
68
+ * **conductor:** help examples a shell can run, review fixes (ATK-478) ([f1be136](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/f1be1361a1874a26a91dcfafd42626e25c307943)), closes [#12](https://emergentcode.visualstudio.com/Emergent%20Software/_workitems/edit/12) [#-word](https://emergentcode.visualstudio.com/Emergent%20Software/_workitems/edit/-word)
69
+
70
+ ## [1.4.0-dev.4](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.3&targetVersion=GTconductor-v1.4.0-dev.4) (2026-10-15)
71
+
72
+ ### Features
73
+
74
+ * **conductor:** a GitHub Issues tracker kind (ATK-458) ([6028a13](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6028a136cc39e1ed51072571629e68dead466d35))
75
+
76
+ ### Bug Fixes
77
+
78
+ * **conductor:** review fixes for the github-issues tracker (ATK-458) ([79abe60](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/79abe602f80138714098a61821d16dfc015787c0))
79
+
80
+ ## [1.4.0-dev.3](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.2&targetVersion=GTconductor-v1.4.0-dev.3) (2026-10-15)
81
+
82
+ ### Features
83
+
84
+ * **conductor:** a brief with the phase, the PR and the comments (ATK-456) ([cfd0f11](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/cfd0f11e8881f43eb0d4d4d4f93800738f81d57f))
85
+ * **conductor:** conduct file, in-process notes and the PR reference (ATK-456) ([0b3311b](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/0b3311b7ec877cc7846d1c5607c35c653c43b58d))
86
+ * **conductor:** phase notes move the tracker state (ATK-456) ([6c93b91](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6c93b91fc7f0487c41ddfe357248a3bfdc97a463))
87
+
88
+ ### Bug Fixes
89
+
90
+ * **conductor:** guard the brief found in review (ATK-456) ([5d7debe](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/5d7debe7cd670bc20798f7d55438bfc24d1095be))
91
+ * **conductor:** guard the note and follow-up paths found in review (ATK-456) ([6d881da](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/6d881daca5d5c5103943fba6a1560f7ae4db36de)), closes [AB#12](https://emergentcode.visualstudio.com/Emergent%20Software/_workitems/edit/12)
92
+
93
+ ### Refactoring
94
+
95
+ * **conductor:** the brief names its PR with the notes' helper (ATK-456) ([7b3f009](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/7b3f009715cce46a4ac02697bdca3680ab9f78f5))
96
+
97
+ ## [1.4.0-dev.2](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.4.0-dev.1&targetVersion=GTconductor-v1.4.0-dev.2) (2026-10-15)
98
+
99
+ ### Features
100
+
101
+ * **conductor:** tracker writes for Linear and ADO Boards (ATK-456) ([b86911e](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/b86911ea478c8c4da90b6b5f8cf8586094b1fc4a))
102
+
103
+ ### Bug Fixes
104
+
105
+ * **conductor:** guard the tracker writes found in review (ATK-456) ([50c9987](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/commit/50c998778997f5eb2b914339ed3c58f726fde83c))
106
+
5
107
  ## [1.4.0-dev.1](https://emergentcode.visualstudio.com/Emergent%20Software/_git/Emergent.AgenticToolkit/branchCompare?baseVersion=GTconductor-v1.3.1-dev.1&targetVersion=GTconductor-v1.4.0-dev.1) (2026-10-15)
6
108
 
7
109
  ### Features
package/README.md CHANGED
@@ -12,6 +12,8 @@ conduct supervise [--fail-fast] run the daemon and restart it when it dies (se
12
12
  conduct status [--json] the daemon's pid, port, projects, workers and held locks
13
13
  conduct project add <path> register the project whose checkout holds .claude/conductor.json
14
14
  conduct project list [--json] the registered projects
15
+ conduct project check <name> [--no-worktree] [--json]
16
+ whether the project can be conducted, before a launch finds out
15
17
  conduct launch <item> start a worker: worktree, background session, tab
16
18
  [--project <name>] [--probe] [--prompt <text>] [--conductor <name>]
17
19
  conduct retire <worker> [--force] stop a worker, remove its worktree and session
@@ -92,30 +94,42 @@ The session manager owns the `claude --bg` sessions it launches, and only those:
92
94
  - **launch:** the worktree comes first, through the project's `.claude/hooks/worktree-hooks.mjs`. In a repo with `.atk-lock.json`, the profile's work and verify skills must match the lockfile (`atk install` fixes them when it can). Then `claude --bg --worktree <name> -n <name> --disallowedTools EnterPlanMode ExitPlanMode --permission-mode auto -- <prompt>` runs from the main checkout. A profile with `"workers": { "tab": true }` also gets a Windows Terminal tab attached to the session.
93
95
  - **park:** a worker with a wait (`POST /waits`) is stopped once `claude agents` shows its turn has ended. Its status becomes `parked`, and its tab closes.
94
96
  - **resume:** when a wait resolves, the daemon lets the stop settle (`sessions.settleSeconds`). It checks the process has exited, then runs `claude --bg --resume <session> -- "[conductor] event: …"`. The session keeps its name, model, permission mode and disallowed tools. A resume of a session that still runs starts a copy; the daemon stops and removes the copy, then tries again.
97
+ - **compact:** a parked worker with no resolved wait, whose context the rules find too big (the default `compact-on-park`: past `sessions.compactTokens`, 300k), is resumed with `/compact <what to keep>` once its stop has settled (`worker.compacting`). It runs until its transcript shows the compaction's boundary (`worker.compacted`, with the context before it and the conversation it kept), then parks again on its waits, which resume it as usual. Each turn of a session re-reads its whole context from cache, so a worker left to grow pays for it every turn (ATK-454 reached 883k). A resume that starts a copy is tried again on a later sweep (`worker.compact-deferred`), as a resume is. A compaction whose resume fails, or whose turn ends with no boundary 10 minutes on, logs `worker.compact-failed`, which raises a `context` item in the inbox. The worker still parks, and the daemon doesn't try it again until it restarts or the session compacts some other way: any boundary in its transcript, Claude Code's own included, logs `worker.compacted` and resolves that item. A worker whose context is unknown is never compacted, so even a rule with no threshold can't compact it on every park.
95
98
  - **retire:** refuses uncommitted or unpushed work, and a worker whose turn still runs, unless `--force`. It never retires a session that is still starting. Then it stops the session, removes the worktree and runs `claude rm`. If the stop fails, nothing is removed.
96
99
  - **restarts:** a launch the daemon was stopped in adopts its session if one was started, and otherwise fails and removes its clean worktree.
97
100
  - **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.
98
101
  - **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.
99
102
  - **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`, `jason`, `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.
100
103
  - **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`).
104
+ - **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`.
101
105
  - **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.
102
106
 
103
107
  Worker statuses: `launching`, `running`, `parked`, `stopped-unexpectedly`, `failed`, `exited`. Every step is an event (`worker.launched`, `worker.parked`, `worker.resumed`, …) on `GET /events`.
104
108
 
105
109
  ## Notes
106
110
 
107
- 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 `key`) is the item's key prefix.
111
+ 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.
112
+
113
+ **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`:
114
+
115
+ | Phase | Linear default | ADO Boards default (Scrum) | GitHub Issues default |
116
+ |---|---|---|---|
117
+ | `started` | In Progress, assigned to the key's owner | Committed, assigned to the `az` identity | the label `in-progress`, assigned to the gh account |
118
+ | `pr-opened` | In Review, the PR attached | no change, the PR linked (an `ArtifactLink` for an ADO PR, a `Hyperlink` for a GitHub one) | the label `in-review` in place of `in-progress`; a GitHub PR is linked by the `Refs <owner>/<repo>#<n>` its body carries, any other by a comment with its URL |
119
+ | `landed` | Done | Done | closed as completed, the phase label taken off |
120
+
121
+ A phase mapped to `null` moves nothing (`started` still assigns). A `Started` never reopens a closed item (`kept`); a `PR opened` on one does, for a follow-up PR. The PR linked is the worker's, else the note's first PR link. A write the tracker refuses is a warning on the note's answer and stderr, and a `tracker.transition-failed` event; the note stands, and the verb still exits 0. A write that took is `tracker.transitioned`. With no daemon, `conduct note` posts from the item's project checkout as its account, and moves the item the same way; nothing is recorded then.
108
122
 
109
123
  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.
110
124
 
111
125
  | Exit | Means | The worker then |
112
126
  |---|---|---|
113
127
  | 0 | posted | goes on |
114
- | 3 | nobody posted it: no daemon, one without notes, or the tracker refused it (such as a read-only key) | posts the note to the tracker itself, as before protocol v1 |
128
+ | 3 | nobody posted it: the tracker refused it (such as a read-only key), or no daemon and this isn't the item's project checkout | posts the note to the tracker itself, as before protocol v1 |
115
129
  | 4 | not posted: Jason wrote `revise: …` after the item's newest Plan was approved, by him (a comment, `conduct approve`, the worker's tab) or by the conductor (`auto-approve now`, see Rules); stdout has his words | stops, re-plans with them, posts a new Plan and waits for approval, which the gate now holds |
116
130
  | 1 | refused (no heading, no project) or unclear (the daemon or Linear didn't answer the write) | reads the message; when it says the note may be there, looks before posting again |
117
131
 
118
- On Linear the daemon uses the key the watchers read (below), so that key needs **Create comments** as well as Read. Comments it writes carry the key owner's name, as the Linear MCP's do. On ADO Boards it posts a markdown discussion comment as the `az login` identity.
132
+ On Linear the daemon uses the key the watchers read (below), so that key needs **Create comments** as well as Read. Comments it writes carry the key owner's name, as the Linear MCP's do. On ADO Boards it posts a markdown discussion comment as the `az login` identity, and on GitHub Issues an issue comment as the project's gh account.
119
133
 
120
134
  ## Briefs
121
135
 
@@ -123,20 +137,23 @@ On Linear the daemon uses the key the watchers read (below), so that key needs *
123
137
  - the title, URL, status (name and type), the tracker's project, Area and labels;
124
138
  - the blockers, the items it blocks and the related items, each with its status and title;
125
139
  - its phase notes (time, phase, kind and line), the newest Plan, and the `approve` / `revise:` after it with its words;
140
+ - the work skill's `phase`: `orient`, `plan`, `awaiting-approval`, `implement`, `wait`, `land`, `done`, `abandoned` or `canceled`, from the status, the notes and the PR;
141
+ - the `pr`: its worker's, else the newest `PR opened` note's link, read on the project's code host, with its state and verdict (or why it couldn't be read);
126
142
  - the description.
127
143
 
128
- `--json` prints it as one object.
144
+ `--json` prints it as one object. `--comments` adds every comment's words, time, and whether the key's owner wrote it. With no daemon it is read here, from the item's project checkout as its account.
129
145
 
130
146
  The queue's snapshot has no descriptions, relations or comments, so the daemon reads the item itself:
131
147
  - **Linear:** one GraphQL request with the watchers' key, and more only for comments past the first 100.
132
148
  - **ADO Boards:** the work item with its links, its linked items' titles and states, its discussion comments and the `az login` identity. Predecessor is a blocker, Successor an item it blocks.
149
+ - **GitHub Issues:** the issue, its comments, the issues it is blocked by and blocks (GitHub's issue dependencies; one in another repo is `<owner>/<repo>#<n>`) and the gh account's login. GitHub has no related relation, so the brief lists none.
133
150
 
134
151
  A brief is kept for 30 seconds. Anything the daemon sees happen to the item drops it: a note it posted, an `approve` or `revise` it posted, a wait resolved. `--fresh` reads the tracker again, as a worker checking for an approval after a wake should.
135
152
 
136
153
  | Exit | Means | The worker then |
137
154
  |---|---|---|
138
155
  | 0 | the brief | uses it |
139
- | 3 | no daemon, one without the brief API, or one that couldn't read the tracker (no key, the tracker down) | reads the item from the tracker itself (the Linear MCP, or the work skill's helper) |
156
+ | 3 | no daemon and this isn't the item's project checkout, or the tracker couldn't be read (no key, the tracker down) | reads the item from the tracker itself (the Linear MCP, or the work skill's helper) |
140
157
  | 1 | refused: no project tracks the item, or the tracker has no such item | reads the message |
141
158
 
142
159
  ## Worker verbs
@@ -147,6 +164,8 @@ Protocol 2's verbs on git and the code host (providers design note, section 4),
147
164
  - `conduct pr create` checks everything before it pushes, `--dry-run` included: the title carries the item key, the branch carries it too and isn't the base, the description fits the host (ADO 4,000 characters, GitHub 65,536), no PR is open from the branch yet, nothing is uncommitted, the branch is ahead of its base, and commitlint passes (skipped in a clone without it installed, whose own CI lints the PR). Then it pushes from the checkout and has the daemon open the PR (`POST /workers/:id/prs`). The daemon checks ownership again from its own records and the worktree, never from the request: the worker is live, the branch the worktree is on now (or the `--repo` checkout's) carries the worker's item, and the title carries it. It records the PR on the worker (`pr`; another repo's only while the worker has none) and logs `code-host.pr-created`. `conduct branch` renames only the item's own branch, never the base, `main` or another item's.
148
165
  - `conduct land <pr>` sets the worker's PR up to land, as the helper's `land` did, and is safe to repeat. The PR must be the worker's (the same checks, and from the branch the checkout is on now, into its base). A brake already on it (a comment, a failed build, conflicts) is reported, and nothing is armed. A Landing note whose landing armed something, with that gone now, means someone cancelled it: `auto-merge-cancelled`, until `--rearm`. `--mode` is held to the profile's landing rules (`work.landing`) for the PR's files as pushed (`origin/<base>...origin/<head>`, fetched first) and the plan's tier. The daemon reads the tier from the item's Plan note; in-process it takes `--tier`, and auto is refused without one. Without `--mode` the rules pick it. Then it arms the host's own landing behind its capability check (`landingCheck`). On ADO that is a +10 vote in auto mode, with auto-complete and noFastForward. On GitHub it is auto-merge with a merge commit in auto mode, where the repo allows it; in review mode nothing is armed. It posts the Landing note last, and a new one whenever the mode changes, then prints the verdict. A failed build comes with its log, and a merge with whether it has two parents. The daemon logs `code-host.landing-armed` with the mode.
149
166
  - `conduct wait pr <pr>` (no event word) is the verdict wait, `pr:<host>:<repo>#<n>:verdict`: it holds on the first verdict that isn't `waiting`, with a line such as `pr 2030 build-failed: <pipeline>`. While it waits on a PR whose landing the daemon recorded, the ADO watcher keeps it armed. It queues an expired build again (once per wait) and, in auto mode, restores a cleared vote (never over a negative one), logged as `code-host.kept-armed`. A local wait (no daemon) gives the same verdicts but keeps nothing armed.
167
+ - `conduct pr create` ends the description with the tracker's reference when it has one, before the length check: `AB#<id>` for an ADO Boards item on a GitHub PR, so the Boards GitHub app links it, and `Refs <owner>/<repo>#<n>` for a GitHub Issues item on a GitHub PR. Linear has none.
168
+ - `conduct file --title … --body-file … --area … [--related …] [--blocked-by …] [--human]` files a follow-up of the checkout's item: in Backlog, in the item's project, with one Area (a label of the profile's Area group, a path below a board's `areaRoot`, or the name after a GitHub Issues `areaPrefix` label), the profile's `triageLabel`, and its `humanLabel` with `--human`. It is related to the item and each `--related`, and waits on each `--blocked-by` (keys, repeated or comma-separated). A key of another project, or an Area outside the group, is refused before anything is written. On Linear a relation that fails after the issue exists is a warning; on Boards the item and its links are one request. On GitHub Issues the follow-up also carries the `queueLabel`, the related items are a `Related: #n` line in its body (GitHub has no related relation), and a dependency that fails after the issue exists is a warning. The daemon files it for the worker (`POST /workers/:id/follow-ups`) and logs `tracker.filed`. The worker searches for a duplicate first, with the tracker's own tools: this only files.
150
169
  - `conduct pr threads <pr>` lists the PR's threads (on GitHub each comment and review) with `needsAnswer` (ATK-384). `conduct reply <pr> [<thread>]` answers on the worker's own PR: in the ADO thread named, or as a note on the PR. The text is headed `## <work skill> · Reply` unless it starts with a note heading. The daemon logs `code-host.replied`. `--repo` takes `github:<owner>/<repo>` (from its `.scratch/repos` checkout, into its default branch) or `ado:<repository>`; the profile's `codeHost` is the default.
151
170
  - `conduct runs <sha>` is one look at a merge's runs, as the helper's `runs-watch --once` and `stage` were (ATK-455). It is a read, so it always runs here.
152
171
  - **Which runs.** On Azure Pipelines (the profile's `ci: ado-pipelines`, for its Azure DevOps repo), 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.
@@ -161,7 +180,9 @@ Every write the conductor makes outside its own store, with what triggers it and
161
180
 
162
181
  | Write | Trigger | Identity |
163
182
  |---|---|---|
164
- | A phase note on the item | `conduct note` (a worker) | the Linear key's owner; on Boards, the project's `az` identity |
183
+ | A phase note on the item | `conduct note` (a worker) | the Linear key's owner; on Boards, the project's `az` identity; on GitHub Issues, its gh account |
184
+ | The item's state, its assignee on `started`, and the PR's link (see Notes) | a `Started`, `PR opened` or `Landed` note | the Linear key's owner, the `az` identity, or the gh account |
185
+ | A follow-up item and its relations | `conduct file` → `POST /workers/:id/follow-ups` | the Linear key's owner, the `az` identity, or the gh account |
165
186
  | `approve` / `revise:` on the item | `conduct approve` / `revise`, the console | the Linear key's owner, or the `az` identity |
166
187
  | An auto-approval comment | a rule's `auto-approve now` / `after` | the Linear key's owner, or the `az` identity |
167
188
  | A label added or removed (`human`, `triage`, `shipped-open`) | a rule's `label add` / `remove` | the Linear key's owner, or the `az` identity (a tag) |
@@ -172,7 +193,7 @@ Every write the conductor makes outside its own store, with what triggers it and
172
193
  | A reply on the worker's PR | `conduct reply` → `POST /workers/:id/replies` | the project's `az` identity, or its gh account |
173
194
  | A train: its branch, the members' merges into it, its PR, a note on each member | `conduct train` (you) | the project's gh account |
174
195
 
175
- Accounts are per project (see Accounts). Each code-host write is an event: `code-host.pr-created`, `code-host.landing-armed`, `code-host.kept-armed`, `code-host.replied`.
196
+ Accounts are per project (see Accounts). Each code-host write is an event: `code-host.pr-created`, `code-host.landing-armed`, `code-host.kept-armed`, `code-host.replied`. So is each tracker write a worker causes: `tracker.transitioned` (or `tracker.transition-failed`) and `tracker.filed`. Rules still never change an item's state: only its phase notes do.
176
197
 
177
198
  ## Inbox
178
199
 
@@ -189,15 +210,17 @@ An item resolves itself when its condition is seen, and records what resolved it
189
210
  | Kind | Raised by | Resolved by |
190
211
  |---|---|---|
191
212
  | `plan` | a `Plan` note (`conduct note`), or an `approval` wait; not a Plan an `auto-approve now` rule approves (unless its comment fails) | the approval wait resolving (an `approve` or `revise:` comment), a `Plan approved` note, a newer Plan, or `conduct approve` / `revise` |
192
- | `merge` | a `Handoff · merge: <PR>` note; a `pr … merged` wait attaches to it | its PR wait resolving as merged, abandoned or closed, or a `Landed` note. A PR wait without the note raises nothing, because auto landing needs nobody |
213
+ | `merge` | a `Handoff · merge: <PR>` note; a `pr … merged` wait attaches to it. Held while its PR's checks are red, and held at once (no push) when the note names a PR whose checks are red: raised again as it was when a checks wait on the PR resolves passed | its PR wait resolving as merged, abandoned or closed, a `Landed` note, or the PR's checks failing (`pr.checks-failed`, resolved as `held: …`). A PR wait without the note raises nothing, because auto landing needs nobody |
193
214
  | `npm-otp` | a `Handoff · npm-otp` note (the `npm stage approve …` on its heading is the command), or an `npm` wait | the version on its tag, or a `Resumed` or `Landed` note |
194
215
  | `jason`, `human`, `scope`, `blocked`, `abandoned`, `failed` | a Handoff note of that kind | any later note on the item (such as `Resumed`), or `conduct done` |
195
216
  | `permission`, `stall` | `worker.stalled` (no idle `stall` while the worker's Handoff item with nothing to watch is open; see Workers) | `worker.stall-cleared`, a park or resume, a new wait or Handoff (stall only), or a daemon restart (it is raised again if it persists) |
196
217
  | `stall` (park failing) | `worker.park-failed`, with `conduct attach` and `conduct retire` | a park, the session stopping unexpectedly (which raises `failed`), its retirement, or a daemon restart (the park is tried again, and fails again if it still does). A new wait doesn't resolve it. A retire stops the session the same way, so when the pid outlives `claude stop`, end that process: the next sweep finds the session gone |
197
218
  | `failed` | a failed launch, or a session that stopped unexpectedly | a new launch, the worker running again (a retry's revive included), or its retirement |
219
+ | `failed` (red checks) | `pr.checks-failed`: a `pr … checks` wait resolved as failed, with each failed run's name, id and link, the PR's link and `conduct attach`; one per PR | a checks wait on the PR resolving passed, the PR merged, abandoned or closed, a `Landed` or `Done` note, a newer `Handoff · failed` note, its retirement, or `conduct done`. Not the worker waking for it, or another note: the checks are still red |
198
220
  | `conflict` | `pr.conflict-stuck`: a watched PR has conflicted with its base for `watchers.conflictMinutes` (30) since its worker was woken for it, and the wake never reached the worker or a pass still sees the conflict; with `conduct attach` and the PR link | the PR merging cleanly again, merged or abandoned (`pr.conflict-cleared`), its retirement, or a daemon restart (the spell starts over) |
199
221
  | `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) |
200
222
  | `runaway` | a worker passing `inbox.runawayTokens` (1.5M) input, output and cache-write tokens in all, once per worker | `conduct done`, or its retirement |
223
+ | `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 |
201
224
  | `ready-to-release`, `queue-drained` | the scheduler's default rules (see Scheduler) | `conduct done` |
202
225
  | `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 |
203
226
  | `console` | the console's finding on an escalation, with `conduct escalations done <id> --ask` (see Console) | `conduct done` |
@@ -269,11 +292,11 @@ Rules say what the conductor does when something happens (design note, section 9
269
292
  ]
270
293
  ```
271
294
 
272
- - **Sources, in order:** the item's project's `rules` (in its `.claude/conductor.json`, changed by PR), 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`, `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`, `jason`, `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). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences.
273
- - **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`, `worker.idle`, `worker.exited`, `worker.permission`, `worker.waiting`, `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), `plan.tier/landing`, `note.phase/kind`, `wait.kind/age`, `inbox.kind/age`, `worker.retries/phase`, `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`.
295
+ - **Sources, in order:** the item's project's `rules` (in its `.claude/conductor.json`, changed by PR), 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`, `jason`, `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). A rule with an `id` replaces the rule with the same id further down, so a file holds only the differences.
296
+ - **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), `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`.
274
297
  - **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 jason`, `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.
275
- - **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`, `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).
276
- - **Reload:** saving `rules.json` or a project's profile reloads its rules. A profile save takes only the `rules`; the rest of a profile changes with `conduct project add`. A file the conductor refuses leaves its last good rules 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.
298
+ - **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).
299
+ - **Reload:** saving `rules.json` or a project's profile reloads its rules. A profile save, or a profile changed 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 profile that renames the project is refused: a project keeps the name it was registered under. 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.
277
300
  - **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.
278
301
 
279
302
  - **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.
@@ -314,7 +337,7 @@ The scheduler moves conduct-atk's tick into the daemon (design note, section 8).
314
337
  - **Release waits:** a Linear 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). 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. A comment that only quotes the text doesn't count. A blocker still open in another team waits the same way; a canceled one holds nothing. 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. The read asks about the Done blockers in one more request, only when a labelled item has one. Without the setting, a Done blocker never holds anything.
315
338
  - **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.
316
339
  - **Holds:** quiet hours and `conduct pause` turn picks into waits that say why.
317
- 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), `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.
340
+ 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.
318
341
 
319
342
  `scheduler.mode` decides how far it goes:
320
343
  - **`on`** (the default): it acts.
@@ -363,12 +386,12 @@ A worker waits by posting its note, running `conduct wait …` and ending the tu
363
386
  | `run atk-api-deploy 12345 done` | `run:ado:atk-api-deploy#12345:done` | the run completed, with its result |
364
387
  | `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 |
365
388
  | `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 |
366
- | `approval` | `approval:linear:ATK-310` (`approval:ado-boards:CAD-12` on Boards) | an `approve` / `revise:` comment by the Linear key's owner (on Boards, the `az login` identity) after the item's newest `<work skill> · Plan` note |
367
- | `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 |
389
+ | `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 the Linear key's owner (on Boards, the `az login` identity; on GitHub Issues, the gh account) after the item's newest `<work skill> · Plan` note |
390
+ | `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 |
368
391
  | `login ado` / `github` / `linear` | `login:ado:atk` | the project's login to the provider works again: a probe as its account (see Accounts) succeeds |
369
392
  | `time 2026-10-10T09:00` | `time:2026-10-10T14:00:00.000Z` | the time has passed (no zone means local time) |
370
393
 
371
- - **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 profile as registered (`conduct project add`), and refuses one it can't check, such as a `health` environment the profile has no URL for.
394
+ - **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.
372
395
  - **Without a daemon**, with `--local`, or when the daemon doesn't answer, it checks the condition itself and blocks. It prints `[conductor] event: …` and exits 0 when the condition holds, or exits 2 after `--timeout` minutes (29: Monitor ends a command at 30) so the caller can run it again. A wait that can't be checked (a PR that doesn't exist, no Linear key, `az` not signed in) exits 1, with the command that signs in again for a lost login. A `login` wait instead waits through a failing probe, after saying which account it waits for and the command. `--timeout` applies only here.
373
396
 
374
397
  **Watchers.** One per API: Linear (GraphQL), Azure DevOps (REST with an `az` token), GitHub (`gh api`), npm (the registry's dist-tags, no cache), `/health`, and `login` for login waits. Each provider call uses the waiting project's account (see Accounts). Time waits are the session manager's sweep. An API is asked nothing while no wait needs it. Otherwise it's asked once per `watchers.<api>.intervalSeconds`, within `perMinute` requests in any rolling minute shared by every project. Each pass covers all of that API's waits in as few requests as it can; Linear's is one request, filtered on `updatedAt` (with a 2-minute overlap) after the first. A pass that runs out of budget stops, and the next one starts a wait further on. A failing API waits twice as long each time (up to 10 minutes) and logs `watcher.failed` once, then `watcher.recovered`. A wait that can't be checked logs `wait.check-failed` once and stays open. Every `watchers.statsMinutes` (10) in which anything waited, a `watcher.stats` event records each API's passes, requests, budget skips, failures and waits.
@@ -433,6 +456,37 @@ A project whose work items live in Azure DevOps Boards (Cadence) sets `tracker.k
433
456
 
434
457
  Everything goes through the project's `az` token (see Accounts). The reads (the scheduler's queue and the watchers' waits) take from the `ado` watcher's budget, as PR and pipeline waits do. A wait pass reads every waited item of a board in one batch, and an item's comments only when its rev or comment count moved. Notes and the rules' `label` edits (a patch of `System.Tags` alone, refused if the item changed since it was read) are writes, outside the budget, as on Linear.
435
458
 
459
+ ## GitHub Issues
460
+
461
+ A project whose items are a GitHub repo's issues sets `tracker.kind` to `github-issues` (providers design note, section 5; ATK-458):
462
+
463
+ ```jsonc
464
+ "tracker": {
465
+ "kind": "github-issues",
466
+ "key": "PAR", // items are PAR-<issue number>: issue numbers are numbers alone
467
+ "humanLabel": "human", // a label
468
+ "triageLabel": "triage", // optional, a label too: new items Jason hasn't triaged (see Scheduler)
469
+ "queueLabel": "conduct", // the default: the open issues carrying it are the queue
470
+ "areaPrefix": "area:", // the default: area:docs is the Area docs
471
+ "priority": 3, // the queue's priority against other projects' (1 urgent … 4 low)
472
+ "transitions": { "started": "in-progress", "pr-opened": "in-review" } // the defaults
473
+ // "owner", "repo": the GitHub code host's by default; required beside an Azure DevOps one
474
+ }
475
+ ```
476
+
477
+ | Tracker concept | On GitHub Issues |
478
+ |---|---|
479
+ | the queue | the open issues labelled `queueLabel`, oldest first, one tracker project named after the repo with the profile's `priority`. PRs, which the issues API lists too, are left out. In-flight issues are read as well, so a closed one retires its worker. |
480
+ | Area | the name after `areaPrefix` on the issue's first such label |
481
+ | blockers | GitHub's issue dependencies (`blocked by`) still open, inside the queue or not; one in another repo is listed as `<owner>/<repo>#<n>` |
482
+ | human, triage, labels | labels, matched in any case |
483
+ | 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 |
484
+ | phase notes, `approve` / `revise:` | issue comments; the gh account's own comments count as Jason's, so the note heading tells them apart |
485
+
486
+ `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.
487
+
488
+ 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.
489
+
436
490
  ## Broker
437
491
 
438
492
  The broker shares what every project on the machine uses (ATK-318).
@@ -492,7 +546,7 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
492
546
 
493
547
  | File | Holds |
494
548
  |---|---|
495
- | `config.json` | machine settings, JSONC (comments allowed), validated at startup; `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); `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) |
549
+ | `config.json` | machine settings, JSONC (comments allowed), validated at startup; `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) |
496
550
  | `rules.json` | machine rules, JSONC, reloaded on save (see Rules) |
497
551
  | `token` | the API token, readable by this user only; every request sends `Authorization: Bearer <token>` |
498
552
  | `conductor.pid`, `port` | the single-instance lock and where the API listens (`127.0.0.1`) |
@@ -501,7 +555,7 @@ Everything lives in `~/.conductor` (`CONDUCTOR_HOME` overrides it):
501
555
 
502
556
  ## API
503
557
 
504
- - `GET /projects`: the registered projects. `POST /projects` with `{ "path": "<absolute path>" }` registers one.
558
+ - `GET /projects`: the registered projects. `POST /projects` with `{ "path": "<absolute path>" }` registers one. `POST /projects/<name>/check` with `{ "worktree": false }` or `{}` runs `conduct project check` as the daemon's accounts: `{ project, results: [{ check, status, summary, details, fix }] }`, or 404 for a name no project is registered under.
505
559
  - `GET /events`: the event log as server-sent events. Add `?since=<id>` (or send `Last-Event-ID`) to replay from an id. Without either, the stream starts at the live end. `conduct events` prints it one JSON event per line: the events after `--since` (all of them without it), or with `--follow` everything from there on (live without `--since`). `--follow` reconnects with `Last-Event-ID` when the daemon goes away, so a restart loses nothing, and prints the stream's state as lines without an id: `{"kind":"conduct.connected"}` and, once per outage, `{"kind":"conduct.disconnected","error":…}`. `?console=1` (`conduct events --follow --console`, which the conductor mod runs, or `conduct events --follow` with `CONDUCTOR_CONSOLE=1`, as a mod from before `--console` runs it until `/reload-plugins`) marks the console's stream: while one is open, escalations have a console to wake (see Console).
506
560
  - `POST /shutdown`: a clean stop (`conduct stop`).
507
561
  - `GET /console/escalations`: the open escalations, oldest first (see Console).
@@ -556,7 +610,19 @@ conduct install-startup
556
610
  conduct project add <path to the project's main checkout>
557
611
  ```
558
612
 
559
- `conduct project add` walks up from the path it is given to the directory that holds `.claude/conductor.json`, and maps a worktree to its main checkout. The daemon reloads a profile's `rules` when the file changes; every other change, such as a new `skills.work`, takes effect at the next `conduct project add`. An older daemon refuses a profile with fields it doesn't know, so upgrade the daemon before a profile that uses new ones lands. The same holds for the copy the daemon stored at `conduct project add`: after an `--allow-downgrade` to a version without those fields, the project shows `the stored profile no longer validates` and isn't conducted until a profile without them is added again.
613
+ `conduct project add` walks up from the path it is given to the directory that holds `.claude/conductor.json`, and maps a worktree to its main checkout. After that the daemon takes the whole profile from disk when it starts and each time the file is saved, so a `git pull` that changes `slots` or `skills.work` needs no `conduct project add`. An older daemon refuses a profile with fields it doesn't know, so upgrade the daemon before a profile that uses new ones lands. After an `--allow-downgrade` to a version without those fields, the stored copy no longer validates either: the project shows `the stored profile no longer validates` and isn't conducted until its file, saved without them, is read again.
614
+
615
+ Then `conduct project check <name>` says whether the project can be conducted, before its first launch finds out. Each check prints one line, `ok`, `warn`, `fail` or `skip`, with a fix under a warn or a fail, and the command exits 1 when any check fails (`--json` prints the same as data):
616
+
617
+ - **profile:** the file reads with this build's schema and keeps the registered name; a `warn` lists the fields that differ from the daemon's stored copy, which it takes on the next save or start. The later checks use the file when it reads, else the stored copy; with neither they are `skip`.
618
+ - **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).
619
+ - **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.
620
+ - **worktree:** the launch's first steps on a throwaway `conduct-check-<time>`: the worktree hooks create it, its skills match `.atk-lock.json`, and it is removed, whatever happened. The one check that writes; `--no-worktree` skips it, since the create hook runs `pnpm install`.
621
+ - **skills:** in the main checkout, `skills.work` is installed and declares `worker protocol v1`, `skills.verify` is installed, and `atk audit` finds both unchanged; a missing alias is a `warn`.
622
+ - **ports:** the broker has a free slot for a new worktree, up to `ports.maxSlot`, saving nothing.
623
+ - **health:** each `health` URL answers; a `warn` at most, since an environment can be down.
624
+
625
+ With the daemon running it checks through the API, as the daemon's own accounts; without it, here, as the machine's.
560
626
 
561
627
  The profile's optional `work` section is for the project's work skill; the daemon only checks it:
562
628
 
@@ -615,7 +681,7 @@ It runs `claude -n conductor --permission-mode auto -- /conductor-console` with
615
681
  - **Other sessions** get nothing from it: no status line, and no calls to the daemon.
616
682
  - **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:
617
683
  - **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;
618
- - **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), the workers by project (each project's rule has [pause] and [focus]; a row expands to attach and why), and today's spend;
684
+ - **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]; a row expands to attach and why), and today's spend;
619
685
  - **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);
620
686
  - **a check that `/conductor-console` ran**: a console started without it (by hand, as `claude -n conductor`) shows `console without its skill: run /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-console`, or the model's Skill tool) clears both, until a `/clear` or a resume starts a conversation without it;
621
687
  - **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;