@nickysagan/issue-orchestrator 0.1.2 → 0.2.0

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.
package/README.md CHANGED
@@ -6,18 +6,21 @@ subscription-authenticated `ccode` implementation workers alive — each in its
6
6
  tmux window running:
7
7
 
8
8
  ```bash
9
- ccode --print --permission-mode auto --model claude-opus-4-8 "/github-issue <number>"
9
+ ccode --print --permission-mode auto --model claude-opus-5 "/github-issue <number>"
10
10
  ```
11
11
 
12
+ The full model name pins implementation workers to Opus 5 rather than the
13
+ Claude Code default or the moving `opus` alias. Non-interactive print mode
14
+ skips the workspace-trust prompt, and each process exits — closing its tmux
15
+ window — once its command finishes.
16
+
12
17
  Usage limits are enforced by
13
18
  [Usage Sentinel](https://github.com/Sadotu/usage-sentinel), which pauses this
14
19
  orchestrator's entire Docker container — supervisor and workers together — rather
15
- than this repository stopping workers itself. The full model name pins issue
16
- workers to Opus 4.8 instead of the Claude Code default or the moving `opus`
17
- alias.
20
+ than this repository stopping workers itself.
18
21
 
19
- A finished PR is not merged automatically. It first passes through the managed
20
- review gate below, which uses a third, separately reserved worker slot.
22
+ A finished implementation PR is marked ready for the repository owner. The
23
+ supervisor does not review, repair, approve, or merge it automatically.
21
24
 
22
25
  ## Orchestrator
23
26
 
@@ -44,37 +47,24 @@ issue-orchestrator
44
47
  ```
45
48
 
46
49
  The shared [`agent-devcontainer`](https://github.com/Sadotu/agent-devcontainer)
47
- also exposes that command through the literal alias:
48
-
49
- ```bash
50
- start work
51
- ```
52
-
53
- The supervisor resolves the `origin` of that current working directory,
54
- checks GitHub App authentication, registers its own container with Sentinel, and
55
- starts autonomous workers itself:
56
-
57
- ```bash
58
- ccode --print --permission-mode auto --model claude-opus-4-8 "/github-issue <number>"
59
- ```
50
+ also exposes that command through the literal alias `start work`.
60
51
 
61
- Non-interactive print mode skips the workspace-trust prompt, and each process
62
- exits after its command finishes. A supervisor that cannot resolve its container
63
- ID or register its lease exits nonzero rather than run unenforced. Do not start
64
- Claude separately.
52
+ The supervisor resolves the `origin` of that working directory, checks GitHub App
53
+ authentication, registers its own container with Sentinel, and starts the
54
+ autonomous workers itself. Do not start Claude separately. A supervisor that
55
+ cannot resolve its container ID or register its lease exits nonzero rather than
56
+ run unenforced.
65
57
 
66
58
  Only one supervisor per repository per Linux network namespace may run at a
67
- time. A second invocation in the same namespace for the same resolved
68
- `owner/repo` exits nonzero with an `already running` error before
69
- authentication, the container lease, tmux, or worker startup. Ownership is held by a
70
- kernel-owned Linux abstract Unix socket, so it is released on normal exit and
71
- automatically when the supervisor process dies. Devcontainers normally have
72
- distinct network namespaces and therefore independent guards; containers that
73
- share a network namespace also share this guard.
74
-
75
- Neither the image build nor devcontainer startup launches the supervisor or
76
- an LLM. Work begins only when a user explicitly runs `issue-orchestrator` or
77
- its `start work` alias.
59
+ time; a second invocation for the same resolved `owner/repo` exits nonzero with
60
+ `already running` before authentication, the lease, tmux, or worker startup.
61
+ Ownership is a kernel-owned Linux abstract Unix socket, released on normal exit
62
+ and automatically when the process dies. Devcontainers normally have distinct
63
+ network namespaces and therefore independent guards; containers sharing a
64
+ namespace share this guard.
65
+
66
+ Neither the image build nor devcontainer startup launches the supervisor or an
67
+ LLM. Work begins only when a user runs `issue-orchestrator` or `start work`.
78
68
 
79
69
  ### Repository-checkout usage
80
70
 
@@ -86,227 +76,121 @@ tmux attach -t orchestrator # watch the workers directly
86
76
 
87
77
  ### How it works
88
78
 
79
+ - **Admission** — before anything is claimed, the primary worktree is checked
80
+ with a read-only `git status --porcelain`. If it has pending changes — or if
81
+ that check cannot be read — no issue is claimed and no worker starts for that
82
+ poll; nothing is stashed, reset, cleaned, or committed. Live workers keep
83
+ running and are reconciled as usual.
89
84
  - **Claim** — the lowest-numbered `agent-ready` issue has its label swapped
90
- `agent-ready` → `agent-running`, then a tmux window `issue-<n>` opens running
91
- this autonomous command:
92
-
93
- ```bash
94
- ccode --print --permission-mode auto --model claude-opus-4-8 "/github-issue <n>"
95
- ```
96
-
97
- Non-interactive print mode skips the workspace-trust prompt; the process exits
98
- and its tmux window closes after the command finishes.
99
- - **Complete** a worker finishes by leaving its PR in draft and putting
100
- `agent-review` on the issue. Reaching any phase label is what marks
101
- implementation done: the supervisor closes the tmux window and frees the slot,
102
- and `agent-running` stays. An already-closed window is treated as closed; a
103
- failure at any later step is logged and retried by the next poll.
104
- - **Vanished** if a worker window disappears before the issue reaches a phase
105
- label, the workflow died. There is no repair worker, so the supervisor fails
106
- closed: it comments on the issue including the vanished worker's log path
107
- and the tail of its output and sets `agent-blocked`. The issue is never
108
- restarted automatically, because its draft PR or worktree may still exist.
109
- - **Worker logs** every launch `tee`s the window's combined output to
110
- `<git-common-dir>/issue-orchestrator/logs/<issue|review>-<n>.<attempt>.log`,
111
- keeping the latest three attempts. Surfaced tails are scrubbed of secrets. A
112
- run that reaches its end stamps a final `[orchestrator <ISO>] ... finished ...`
113
- line into its log, so a tail can be placed in time without the file's mtime.
114
- - **Active-container lease** — at startup the supervisor resolves its own exact
115
- 64-character Docker container ID (from `/proc/self/mountinfo`; the short
116
- `hostname` form is not accepted) and registers it with
117
- `PUT /managed-containers/<id>` on Sentinel. Registration is a lease, not an
118
- admission decision: Sentinel never allows or denies a start. Every later poll
119
- re-PUTs the same ID as its heartbeat, well inside the five-minute lease; a
120
- clean exit `DELETE`s it as best-effort cleanup, and an abrupt exit is handled
121
- by Sentinel's lease expiry. A failed heartbeat is logged and retried on the
122
- next poll. The supervisor keeps a local guard of two implementation workers
123
- plus one reviewer.
124
- - **Usage enforcement lives in Sentinel** — when Claude Code's five-hour or
125
- weekly window reaches 95%, Sentinel `docker pause`s every registered container,
126
- freezing this supervisor and every worker process in place, and unpauses only
127
- after a fresh valid reading puts both windows below 95%. This repository reads
128
- no usage telemetry, applies no thresholds, calls no Docker API, and never kills
129
- a worker because of usage.
130
- - **Stop** — the supervisor exits when nothing is queued, no worker or reviewer
131
- window is live, and every managed issue rests in `agent-blocked` or
132
- `user-merge-review`. Merge and post-merge cleanup monitoring are not yet
133
- implemented.
85
+ `agent-ready` → `agent-running`, then a tmux window `issue-<n>` opens.
86
+ - **Complete** — Phase 6 marks the implementation PR ready for the owner. The
87
+ next poll closes any remaining worker window, removes `agent-running` from
88
+ the issue, and frees the slot.
89
+ - **Vanished** if a worker disappears while its PR is still draft or absent,
90
+ the claim is released and the draft/worktree is preserved. It is not
91
+ restarted automatically.
92
+ - **Worker logs** every implementation launch writes combined output to
93
+ `<git-common-dir>/issue-orchestrator/logs/issue-<n>.<attempt>.log`, keeping
94
+ the latest three attempts and scrubbing secrets from surfaced tails.
95
+ - **Usage enforcement** Usage Sentinel owns pause/unpause thresholds. This
96
+ repository reads no usage telemetry and never kills a worker for usage.
97
+ - **Pause notices** the managed-container heartbeat carries Sentinel's
98
+ enforcement state. A pending pause prints one flushed line naming the
99
+ triggering window, both observed percentages and the reset time, and is then
100
+ acknowledged so Sentinel may pause; the first heartbeat back to `running`
101
+ prints one resume line. Ordinary refreshes stay quiet, and a payload that is
102
+ present but malformed is reported rather than guessed at.
103
+ - **Stop** the supervisor exits when nothing is queued and no implementation
104
+ window and no legacy window (below) is live.
134
105
 
135
106
  Live container pause/unpause check:
136
107
  [docs/smoke-checks/README.md](docs/smoke-checks/README.md).
137
108
 
138
- ## Managed review gate
139
-
140
- A managed PR receives an independent automated review and then either stops with
141
- documented blockers or becomes ready for the owner. The supervisor never
142
- approves and never merges.
143
-
144
- ### Labels
145
-
146
- At startup — after the repository is resolved and the GitHub App auth check
147
- passes, and before the first poll — the supervisor creates any of these labels
148
- that the repository is missing. Labels that already exist are never renamed,
149
- recoloured, or re-described, and no GitHub mutation ever happens at image-build
150
- time (the build has neither a target repository nor runtime credentials).
109
+ ## Implementation-only handoff
151
110
 
152
- | Label | Colour | Meaning |
153
- |---|---|---|
154
- | `agent-ready` | `0e8a16` | Ready for issue-orchestrator claim |
155
- | `agent-running` | `1d76db` | Managed by issue-orchestrator |
156
- | `agent-review` | `5319e7` | Waiting for or undergoing automated review |
157
- | `agent-blocked` | `d73a4a` | Automated workflow blocked; findings posted |
158
- | `user-merge-review` | `fbca04` | Automated review passed; waiting for owner review and merge |
111
+ `agent-running` remains the active ownership marker: it is applied when an
112
+ issue is claimed and removed only after the implementation PR is ready for the
113
+ owner, or when a vanished/failed launch is safely released. Existing review
114
+ labels are left untouched in repositories, but the supervisor does not create a
115
+ reviewer or repair worker and does not transition issues through them.
159
116
 
160
- `agent-running` is the durable ownership marker: it is applied at claim and
161
- stays until post-merge cleanup (not yet implemented). Exactly one phase label
162
- `agent-review`, `agent-blocked`, or `user-merge-review` — is active alongside
163
- it. Issue labels are canonical and the PR mirrors them; every poll repairs a
164
- mismatch, issuing an edit only when the sets actually differ.
117
+ Manual and managed `/github-issue` runs both finish by marking the PR ready.
118
+ Human review and merge begin there. The supervisor exposes no approve or merge
119
+ operation.
165
120
 
166
- A PR with no linked `agent-running` issue is ignored entirely — manual
167
- `/github-issue` PRs and unrelated `agent/*` branches are never touched. More
168
- than one PR closing an issue, a branch that disagrees with its closing
169
- reference, or an `agent-review` issue with no PR all fail closed: an actionable
170
- comment plus `agent-blocked`.
171
-
172
- ### Capacity
173
-
174
- Two reserved implementation slots plus one reviewer slot that is never borrowed
175
- for implementation. The pools are separate tmux window namespaces — `issue-<n>`
176
- and `review-<n>` — each counting only its own live windows. The oldest eligible
177
- `agent-review` PR gets a fresh reviewer window running:
178
-
179
- ```bash
180
- ccode --print --permission-mode auto --model claude-opus-4-8 "/review-pr <pr>"
181
- ```
182
-
183
- The reviewer slot frees as soon as that window exits. Waiting on CI holds no
184
- slot, because it is pure label state with no window.
185
-
186
- ### Review marker
187
-
188
- The reviewer (`Sadotu/agent-skills`, `review-pr`) records each pass in one
189
- immutable PR comment carrying a single-line marker:
190
-
191
- ```
192
- <!-- review-pr:v1 {"fingerprint":"<64-hex>","head":"<40-hex>","base":"<40-hex>","issueUpdatedAt":"<ISO>","prUpdatedAt":"<ISO>","issue":41,"pr":72,"pass":1,"verdict":"PASS"} -->
193
- ```
121
+ Legacy `review-<n>` and `repair-<n>` windows are allowed to finish without being
122
+ restarted or killed; both keep the supervisor active, and repairs consume an implementation slot.
194
123
 
195
- The reviewer is the producer and owns this format; this repository only consumes
196
- it. Verdicts are exactly `PASS` and `BLOCKING`.
197
-
198
- A marker is applied only when all three hold:
199
-
200
- - **Authorship** — the comment has `viewerDidAuthor: true`, so the same GitHub
201
- App identity the supervisor authenticates as wrote it. Every fingerprint input
202
- is public, so without this check any commenter could forge a `PASS`.
203
- - **Identity** — its `issue` and `pr` are the managed pair being gated.
204
- - **Freshness** — its `fingerprint` equals the live fingerprint,
205
- `sha256(head 0x1e base 0x1e issue-body 0x1e pr-body)`. Bodies decide
206
- freshness, not `updatedAt`: a new comment or a label edit moves the timestamps
207
- while reviewing the same content, whereas any real change to the head, the
208
- base, the issue text, or the PR text invalidates the pass. `issueUpdatedAt`
209
- and `prUpdatedAt` are recorded for audit only.
210
-
211
- Comparing against live state rather than a snapshot captured at launch needs no
212
- persistence, so the same content is handled once even across a supervisor
213
- restart or a crash between the comment and the label transition. A marker that
214
- fails any check is discarded silently: nothing is posted, nothing transitions,
215
- and the PR simply queues a fresh pass. Parsing fails closed on an unsupported
216
- tag, malformed JSON, or a missing or ill-typed field. A trusted, applicable
217
- marker carrying an unsupported *verdict* is the one case that is not discarded —
218
- it blocks, so a contract break surfaces instead of looping.
219
-
220
- ### Transitions
221
-
222
- **`BLOCKING`** — the PR is kept or returned to draft and `agent-review` becomes
223
- `agent-blocked` on issue and PR. The reviewer's findings stand alone in its own
224
- comment; the supervisor adds nothing and stops. There is no automatic repair.
225
-
226
- **`PASS`** — the PR is marked ready for review first, so repositories whose CI
227
- triggers on `ready_for_review` start their checks. Checks are read on the
228
- following poll, never in the same one that opened the PR:
229
-
230
- | Checks | Outcome |
231
- |---|---|
232
- | None configured | Green → `user-merge-review` |
233
- | All complete and successful, neutral, or skipped | Green → `user-merge-review` |
234
- | Any still running | Keep `agent-review`; reviewer slot stays free |
235
- | Any failed | Return to draft, `agent-blocked`, post the failing check names and URLs |
236
- | Data unreadable | Transition nothing; retry next poll |
237
-
238
- Every check GitHub reports is treated as required. "No checks configured" and
239
- "could not read the checks" are deliberately distinct: only the former counts as
240
- green.
241
-
242
- ### GitHub authentication
124
+ ## GitHub authentication
243
125
 
244
126
  `gh` does not auto-consume the GitHub App credential, so the supervisor mints a
245
- short-lived App installation token (via `gh-app-token.sh`, overridable with
127
+ short-lived installation token (via `gh-app-token.sh`, overridable with
246
128
  `GH_APP_TOKEN_SCRIPT`) and injects it as `GH_TOKEN` for every `gh` call,
247
- re-minting each poll. At startup it runs an authenticated `gh repo view` smoke
248
- check and exits with a clear message if the App is not authenticated rather
249
- than churning on unauthenticated calls every poll.
129
+ re-minting each poll. At startup an authenticated `gh repo view` smoke check
130
+ exits with a clear message if the App is not authenticated, rather than churning
131
+ on unauthenticated calls every poll.
250
132
 
251
133
  ### Configuration
252
134
 
253
- The local concurrency guard is fixed at two implementation slots plus one
254
- reviewer slot. Two knobs are environment-tunable:
135
+ The local concurrency guard is fixed at two implementation slots. Two knobs
136
+ are environment-tunable:
255
137
 
256
138
  | Var | Default | Meaning |
257
139
  |-----|---------|---------|
258
140
  | `SENTINEL_URL` | `http://usage-sentinel:4317` | Usage Sentinel base URL for the managed-container lease, on the shared container network; set this explicitly (for example, `http://host.docker.internal:4317`) only when Sentinel is exposed on the host |
259
141
  | `POLL_MS` | `60000` | Poll interval |
260
142
 
261
- Labels are created at startup — see the managed review gate below.
143
+ Labels are created at startup — see the managed review gate above.
262
144
 
263
145
  ## Repo contents
264
146
 
265
147
  | Path | Purpose |
266
148
  |------|---------|
267
149
  | `agents.toml` / `agents.lock` | [dotagents](https://github.com/Sadotu/agent-skills) manifest — declares which skills are installed and pins their source commits |
268
- | `.agents/skills/` | Installed skills (`github-issue`, `setup`) — managed artifacts, restored from the manifest, not committed |
150
+ | `.agents/skills/` | Installed skills (`address-review`, `github-issue`, `github-pr-cleanup`, `review-pr`, `setup`) — managed artifacts, restored from the manifest, not committed |
269
151
  | `.claude/skills` | Symlink to `.agents/skills` so Claude Code picks the skills up |
270
152
  | `CLAUDE.md` | Agent instructions and gotchas for working in this repo |
271
153
 
272
154
  ## Skills
273
155
 
274
- - **`github-issue`** — runs a GitHub issue end to end: select issue, open a
275
- draft PR immediately, self-resolve design decisions (logged to the PR),
276
- implement in an isolated worktree, verify against the issue, mark ready.
156
+ - **`github-issue`** — runs an issue end to end, opens a draft PR, implements
157
+ in an isolated worktree, verifies the result, and marks the PR ready for the
158
+ owner.
277
159
  - **`setup`** — connects the repo to the `container-coding-agent` GitHub App
278
160
  and verifies `git`/`gh` authenticate as the App.
161
+ - **`github-pr-cleanup`** — cleans the worktree, branch and session artifacts of
162
+ one merged or closed pull request. Worktree Warden is the automatic caller: it
163
+ watches for terminal pull requests and runs the skill's cleanup script, so
164
+ cleanup happens outside this repository's supervisor, which ends at "PR ready
165
+ for the owner". The skill is installed here so that script exists in a
166
+ checkout, and so it can also be run by hand for a single pull request.
279
167
 
280
- Both are sourced from [`Sadotu/agent-skills`](https://github.com/Sadotu/agent-skills).
168
+ The standalone `review-pr` and `address-review` skills remain installed for
169
+ manual use, but the production supervisor route does not invoke them.
281
170
 
282
171
  ## Releasing
283
172
 
284
- `package.json` holds the version, and merging the bump is the whole release.
285
- Bump it in a normal pull request; when that lands on `main`,
286
- `.github/workflows/publish.yml` sees a version the registry does not carry,
287
- runs the tests, publishes, and pushes the matching `v<version>` tag. A merge
288
- that does not bump the version finds its version already published and exits
289
- without releasing anything.
173
+ `package.json` holds the version, and merging the bump is the whole release. Bump
174
+ it in a normal pull request; when that lands on `main`,
175
+ `.github/workflows/publish.yml` sees a version the registry does not carry, runs
176
+ the tests, publishes, and pushes the matching `v<version>` tag. A merge without a
177
+ bump finds its version already published and exits without releasing. Pushing a
178
+ `v*` tag by hand takes the same path, with one extra guard: the tag must equal
179
+ `v<version>` or the run fails. A published version cannot be republished; bump
180
+ and merge again.
290
181
 
291
182
  Publishing goes to npmjs via
292
- [trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm
293
- token is stored anywhere: npm exchanges the workflow's OIDC identity for a
294
- short-lived credential. Tagging is a second job, so the job holding an npm
295
- credential cannot write to this repository and the job that can push a tag
296
- holds no npm credential. A published version cannot be republished; bump and
297
- merge again.
298
-
299
- Pushing a `v*` tag by hand still works and takes the same path, with one extra
300
- guard: the tag must equal `v<version>` or the run fails.
301
-
302
- Trusted publishing can only be configured on a package that already exists, so
303
- `0.1.0` was published by hand (`npm login && npm publish`) to bootstrap it.
304
- Every later release goes through the workflow.
305
-
306
- Package visibility is set on the package, not inherited from this private
307
- repository, and the setting appears only once a version exists. So after the
308
- **first** release, set it once under **Sadotu → Packages →
309
- issue-orchestrator → Package settings**.
183
+ [trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm token
184
+ is stored anywhere npm exchanges the workflow's OIDC identity for a short-lived
185
+ credential. Tagging is a separate job, so the job holding an npm credential
186
+ cannot write to this repository and the job that can push a tag holds no npm
187
+ credential.
188
+
189
+ Two one-time bootstraps, both because they need a package that already exists:
190
+ `0.1.0` was published by hand (`npm login && npm publish`) to enable trusted
191
+ publishing, and package visibility set on the package, not inherited from this
192
+ private repository — must be set once after the first release under **Sadotu →
193
+ Packages issue-orchestrator Package settings**.
310
194
 
311
195
  ## Development environment
312
196