omp-conductor 0.3.25 → 0.4.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 +733 -30
- package/package.json +1 -1
- package/src/board.ts +24 -5
- package/src/briefs/orchestrator.md +106 -20
- package/src/briefs/policy.md +48 -27
- package/src/briefs/worker.md +82 -31
- package/src/cli.ts +155 -2
- package/src/config.ts +669 -16
- package/src/confinement.ts +506 -25
- package/src/credentials.ts +2029 -0
- package/src/daemon.ts +1000 -32
- package/src/diff-flags.ts +696 -0
- package/src/escalate.ts +136 -9
- package/src/fleet.ts +71 -3
- package/src/omp.ts +471 -15
- package/src/orchestrator-tick.ts +42 -19
- package/src/orchestrator.ts +32 -5
- package/src/plugin.ts +267 -15
- package/src/release-policy.ts +191 -29
- package/src/reports.ts +440 -0
- package/src/session-host.ts +307 -0
- package/src/setup-host.ts +19 -1
- package/src/setup.ts +207 -18
- package/src/store.ts +506 -3
- package/src/tracker/github.ts +45 -0
- package/src/types.ts +847 -10
- package/src/usage.ts +726 -0
- package/src/verbs/actions.ts +142 -0
- package/src/verbs/client.ts +207 -0
- package/src/verbs/ledger.ts +77 -0
- package/src/verbs/protocol.ts +465 -0
- package/src/verbs/server.ts +1098 -0
- package/src/verbs/socket.ts +446 -0
- package/src/worker.ts +36 -7
- package/src/worktree.ts +202 -109
- package/systemd/omp-conductor.service.example +96 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|
package/src/board.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { healthCheck, livingDaemon } from "./lifecycle.ts";
|
|
|
15
15
|
import { dbPath, openStore } from "./store.ts";
|
|
16
16
|
import { formatTranscriptLine } from "./transcript.ts";
|
|
17
17
|
import { makeTracker } from "./tracker/github.ts";
|
|
18
|
+
import { planUsageBadge, readPlanUsage, sharedUsageSource } from "./usage.ts";
|
|
18
19
|
import type { AdmissionHoldReason, ProjectConfig, RunRecord, RunState, Store } from "./types.ts";
|
|
19
20
|
|
|
20
21
|
const REFRESH_MS = 1_000;
|
|
@@ -519,7 +520,14 @@ function admissionLine(snapshot: BoardSnapshot): string {
|
|
|
519
520
|
: `${dispatch.degraded ? "DEGRADED · " : ""}${dispatch.ready} ready · ${dispatch.routed} routed · ${dispatch.admitted} admitted`;
|
|
520
521
|
const holdText = dispatch?.holds.map((hold) => `${hold.reason} ${hold.count}`).join(", ");
|
|
521
522
|
const holds = holdText === undefined || holdText === "" ? "none" : holdText;
|
|
522
|
-
return
|
|
523
|
+
return (
|
|
524
|
+
`${queue} | holds ${holds} | workers ${snapshot.status.liveWorkers}/${snapshot.status.caps.maxConcurrentWorkers}` +
|
|
525
|
+
` | spend $${snapshot.status.spendTodayUsd.toFixed(2)}${cap === null ? "" : `/$${cap.toFixed(2)}`}` +
|
|
526
|
+
// Both economic controls on one row, separately. #110's whole complaint was
|
|
527
|
+
// that a subscription fleet could only see a dollar meter that cannot
|
|
528
|
+
// represent its real ceiling.
|
|
529
|
+
` | ${planUsageBadge(snapshot.status.planUsage)}`
|
|
530
|
+
);
|
|
523
531
|
}
|
|
524
532
|
|
|
525
533
|
function header(snapshot: BoardSnapshot, width: number): string[] {
|
|
@@ -812,7 +820,11 @@ export async function runBoard(projectName?: string): Promise<void> {
|
|
|
812
820
|
let stopping = false;
|
|
813
821
|
let help = false;
|
|
814
822
|
let notice = "";
|
|
815
|
-
let [health, labels] = await Promise.all([
|
|
823
|
+
let [health, labels, planUsage] = await Promise.all([
|
|
824
|
+
probeBoardHealth(project),
|
|
825
|
+
probeBoardLabels(project),
|
|
826
|
+
readPlanUsage(caps.planUsage, sharedUsageSource()),
|
|
827
|
+
]);
|
|
816
828
|
let healthAt = Date.now();
|
|
817
829
|
let healthRefresh: Promise<void> | undefined;
|
|
818
830
|
|
|
@@ -836,10 +848,17 @@ export async function runBoard(projectName?: string): Promise<void> {
|
|
|
836
848
|
const now = Date.now();
|
|
837
849
|
if (now - healthAt >= HEALTH_REFRESH_MS && healthRefresh === undefined) {
|
|
838
850
|
healthAt = now;
|
|
839
|
-
healthRefresh = Promise.all([
|
|
840
|
-
|
|
851
|
+
healthRefresh = Promise.all([
|
|
852
|
+
probeBoardHealth(project),
|
|
853
|
+
probeBoardLabels(project, labels),
|
|
854
|
+
// On the health cadence, not the 1s repaint: the provider read is a
|
|
855
|
+
// subprocess, and an allowance does not move at 1 Hz.
|
|
856
|
+
readPlanUsage(caps.planUsage, sharedUsageSource()),
|
|
857
|
+
])
|
|
858
|
+
.then(([nextHealth, nextLabels, nextPlanUsage]) => {
|
|
841
859
|
health = nextHealth;
|
|
842
860
|
labels = nextLabels;
|
|
861
|
+
planUsage = nextPlanUsage;
|
|
843
862
|
enqueue(queue, { name: "refresh" }, wake);
|
|
844
863
|
})
|
|
845
864
|
.catch((err: unknown) => {
|
|
@@ -851,7 +870,7 @@ export async function runBoard(projectName?: string): Promise<void> {
|
|
|
851
870
|
}
|
|
852
871
|
const snapshot: BoardSnapshot = {
|
|
853
872
|
project,
|
|
854
|
-
status: statusSnapshotFromStore(project, caps, store),
|
|
873
|
+
status: statusSnapshotFromStore(project, caps, store, planUsage),
|
|
855
874
|
health,
|
|
856
875
|
labels,
|
|
857
876
|
runs: store.recentRuns(project.name, now - MERGED_HISTORY_MS),
|
|
@@ -136,6 +136,41 @@ refusal and move on to the next issue.
|
|
|
136
136
|
Never leave an orphan holding a slot "to be safe": a label nobody is working under
|
|
137
137
|
is not safety, it is a deadlocked fleet that looks busy.
|
|
138
138
|
|
|
139
|
+
**Then read the settlement flags.** The conductor no longer takes a worker's word
|
|
140
|
+
for what it changed. When a run settles, its report's `changed:` line is
|
|
141
|
+
reconciled against the pull request's actual diff, and the diff is checked for
|
|
142
|
+
weakened tests. Anything it finds is a **settlement audit** flag: it appears
|
|
143
|
+
under the run in `omp-conductor status`, in the settlement report, and — once,
|
|
144
|
+
at settlement — as a tier-1 escalation to you.
|
|
145
|
+
|
|
146
|
+
A flag is evidence, not a verdict. It never blocks anything: the run settled
|
|
147
|
+
normally, the PR is open and mergeable, and nothing is waiting on you. What it
|
|
148
|
+
says is that one specific sentence in the worker's own account of its work did
|
|
149
|
+
not survive contact with the diff, and that a human's usual review would have
|
|
150
|
+
to notice it unaided.
|
|
151
|
+
|
|
152
|
+
| Flag | What it means | What it invites |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| `undisclosed-file` | The PR touched a file the report never mentioned. | Open the diff for that file. An undisclosed edit is usually incidental — a lockfile, a formatter — and occasionally the whole story. |
|
|
155
|
+
| `changed-line-missing` | The report disclosed nothing at all. | Read the diff before merging; you have no summary of it. |
|
|
156
|
+
| `unmatched-claim` | The report named a file the PR never touched. | Weak on its own. Two or three together mean the report was written from memory rather than from `git diff`, so trust the rest of it less. |
|
|
157
|
+
| `test-file-deleted` | A test file left the tree and no rename explains it. | The one that most deserves a human. Was the behaviour it defended deleted too, or only its test? |
|
|
158
|
+
| `test-disabled` | A `.skip` / `.only` / `xit` / `t.Skip` marker was added. | Ask what turned red. A skip added in the same PR as the change it stopped failing is the shape to look for. |
|
|
159
|
+
| `assertions-removed` | Assertions were commented out, or more left a file than entered it. | Compare against the issue's acceptance criteria: an assertion removed because the spec changed is fine, one removed because it failed is not. |
|
|
160
|
+
| `test-timeout-raised` | A named timeout in a test file went up. | Usually a slow machine. Occasionally a race the worker chose to outwait. |
|
|
161
|
+
|
|
162
|
+
`[unattributed]` on a flag means the dispatching issue never named that file —
|
|
163
|
+
the "tests you didn't write" case, and the one worth spending a comment on.
|
|
164
|
+
|
|
165
|
+
You have three answers, and the same three every time: merge it anyway and say
|
|
166
|
+
why in your report; comment the question on the PR and leave it; or escalate
|
|
167
|
+
(tier 2) when the judgement is a product or spec decision rather than a code one.
|
|
168
|
+
Silently merging a flagged PR without reading the named lines is the one answer
|
|
169
|
+
that is wrong — it spends the check without collecting on it.
|
|
170
|
+
|
|
171
|
+
Never re-run, re-claim or fail a run because it was flagged. The flag is about
|
|
172
|
+
the report, not the run, and the work is already pushed.
|
|
173
|
+
|
|
139
174
|
## Duty 2 — groom
|
|
140
175
|
|
|
141
176
|
Keep the queue worth draining.
|
|
@@ -162,6 +197,15 @@ Keep the queue worth draining.
|
|
|
162
197
|
See **Reporting** below. That section is yours, and it is the only thing that
|
|
163
198
|
decides whether this tick ends in a message or in silence.
|
|
164
199
|
|
|
200
|
+
*How* a report is delivered is not yours, and is not negotiable: run
|
|
201
|
+
`omp-conductor report --text "<the whole report>"` (add `--kind digest` for the
|
|
202
|
+
daily digest). It persists the text before anything is sent and prints a report
|
|
203
|
+
id; the daemon retries until it lands and `omp-conductor status` lists whatever
|
|
204
|
+
has not. Writing a report as end-of-turn text on a tick reaches nobody — that is
|
|
205
|
+
how a suite release and two tier-2 escalations went missing on 2026-08-06 — and
|
|
206
|
+
`telegram_send` reaches somebody but leaves no record that it did, so a report
|
|
207
|
+
sent that way is undetectable when it does not arrive.
|
|
208
|
+
|
|
165
209
|
## Human messages
|
|
166
210
|
|
|
167
211
|
A human writing to you between ticks is not a tick. Answer the question they
|
|
@@ -191,12 +235,13 @@ not.
|
|
|
191
235
|
|
|
192
236
|
Not yours to relax:
|
|
193
237
|
|
|
194
|
-
- **Workers stop at a green PR.**
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
238
|
+
- **Workers stop at a green PR.** `conductor_pr_merge`, `conductor_label` and
|
|
239
|
+
`conductor_release` refuse a worker session mechanically, whatever any config
|
|
240
|
+
says, and a worker holds no credential to go around them with. This one is
|
|
241
|
+
absolute. A worker sees one issue, so it cannot judge whether a release is
|
|
242
|
+
worth cutting, and a session that merges its own work has removed every review
|
|
243
|
+
the PR existed to get. Release work is never delegated downward: if any of it
|
|
244
|
+
is delegated at all, it is delegated to **you**.
|
|
200
245
|
- **PRs land one at a time, each re-checked against the base branch first.** Two
|
|
201
246
|
agent PRs merging concurrently is how they clobber each other. This binds
|
|
202
247
|
whoever is doing the merging, so a delegated release is no exception.
|
|
@@ -214,20 +259,61 @@ Not yours to relax:
|
|
|
214
259
|
- **A worker's branch is theirs; the PR is yours to steer.** Never `git checkout`,
|
|
215
260
|
commit or push inside a worker's worktree, never cut a branch from one, never
|
|
216
261
|
force-push or rewrite history anywhere, and never author a commit under an
|
|
217
|
-
invented identity. But `
|
|
218
|
-
sanctioned remedy for a green PR that has fallen behind: it is a
|
|
219
|
-
merge of the base into the head, it destroys nothing, it rewrites
|
|
220
|
-
under a ruleset that requires branches to be up to date it is the
|
|
221
|
-
correct PR ever merges. Closing a green PR to make a fresh worker
|
|
222
|
-
merge costs a whole attempt to buy what one
|
|
223
|
-
Bypassing branch protection with admin rights is still forbidden;
|
|
224
|
-
branch is how you satisfy it, not how you dodge it.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
262
|
+
invented identity. But `conductor_pr_update_branch` **is** yours to run and is
|
|
263
|
+
the sanctioned remedy for a green PR that has fallen behind: it is a
|
|
264
|
+
server-side merge of the base into the head, it destroys nothing, it rewrites
|
|
265
|
+
nothing, and under a ruleset that requires branches to be up to date it is the
|
|
266
|
+
only way a correct PR ever merges. Closing a green PR to make a fresh worker
|
|
267
|
+
redo the merge costs a whole attempt to buy what one call does in minutes — do
|
|
268
|
+
not. Bypassing branch protection with admin rights is still forbidden;
|
|
269
|
+
updating the branch is how you satisfy it, not how you dodge it.
|
|
270
|
+
|
|
271
|
+
## Your verb surface
|
|
272
|
+
|
|
273
|
+
You hold no GitHub credential. Everything below happens through conductor tools
|
|
274
|
+
whose checks run in the dispatcher, and each one records what you asked for and
|
|
275
|
+
what it decided — readable with `omp-conductor ledger`, including the refusals.
|
|
276
|
+
|
|
277
|
+
| Tool | Yours when | What the dispatcher checks before acting |
|
|
278
|
+
| --- | --- | --- |
|
|
279
|
+
| `conductor_pr_status` | always | Nothing to gate: it reads. |
|
|
280
|
+
| `conductor_pr_update_branch` | always | The PR belongs to this project and is open. |
|
|
281
|
+
| `conductor_pr_merge` | `authority.merge` is yours | You are the configured holder; `headSha` still equals the live head *at execution time*; checks green at that same SHA; the project's single merge slot is free. |
|
|
282
|
+
| `conductor_label` | always | The label is one this project declared. Lifecycle labels (`agent:in-progress`, `agent:blocked`, `agent:failed`) are refused — those stay the dispatcher's, and `omp-conductor unblock` is how you clear them. |
|
|
283
|
+
| `conductor_release` | `authority.release` is yours | You are the configured holder, the shape is granted, the artefact or environment was declared, and the release preconditions hold. |
|
|
284
|
+
|
|
285
|
+
**`conductor_pr_merge` wants the SHA you believe you are merging.** Read it with
|
|
286
|
+
`conductor_pr_status` and pass it. The dispatcher re-reads the live head
|
|
287
|
+
immediately before merging and refuses on any mismatch, naming both SHAs —
|
|
288
|
+
because any push since you looked invalidates the green you saw. A refusal there
|
|
289
|
+
is the mechanism working: re-read, re-check, call again.
|
|
290
|
+
|
|
291
|
+
**Merging is single-flight per project.** A second concurrent merge is refused
|
|
292
|
+
outright rather than queued. That is the "PRs land one at a time" rule with
|
|
293
|
+
something behind it; you do not have to sequence them by hand, but you do have
|
|
294
|
+
to read the refusal instead of retrying in a loop.
|
|
295
|
+
|
|
296
|
+
**Every refusal is worded to be acted on.** It names the field, the holder or
|
|
297
|
+
the SHA that produced it. Never work around one: there is no path around it, and
|
|
298
|
+
the attempt is in the ledger.
|
|
299
|
+
|
|
300
|
+
**Two of the boundaries above are mechanical, not only prose.** Your structured
|
|
301
|
+
file tools (`read`, `write`, `edit`, `grep`, `glob`) run against an allowlist:
|
|
302
|
+
the state directory, this brief, `POLICY.md`, the heartbeat's own files, and any
|
|
303
|
+
path your operator added as `orchestratorReadPaths`. Every worker checkout, the
|
|
304
|
+
mirror cache and the installed `omp-conductor` package are refused — reading
|
|
305
|
+
included, and no config entry can re-open them. A refusal names the path and
|
|
306
|
+
the roots that are allowed: read it and pick a different path rather than
|
|
307
|
+
trying variations. When you genuinely need to see a run's code, read its PR.
|
|
308
|
+
`bash` is not gated, and that is not an invitation — the boundary is the same
|
|
309
|
+
one, and going around it is the one thing a fleet cannot audit.
|
|
310
|
+
|
|
311
|
+
**Your own** merge and release authority is not decided here, and not by this
|
|
312
|
+
brief either. It is whatever your operator granted at setup time, and the
|
|
313
|
+
dispatcher compares your session against that grant on every call — so what
|
|
314
|
+
`POLICY.md` says about it is a description, not the gate. Ungranted, it is none:
|
|
315
|
+
you do not merge, tag, publish or deploy, and the tools will tell you so. That
|
|
316
|
+
grant is changed by re-running setup, never by editing policy prose.
|
|
231
317
|
|
|
232
318
|
## Learning loop
|
|
233
319
|
|
package/src/briefs/policy.md
CHANGED
|
@@ -15,36 +15,36 @@ authority is decided.
|
|
|
15
15
|
|
|
16
16
|
Replace the paragraph above only if your operator is deliberately delegating. If
|
|
17
17
|
they are, be specific: an orchestrator with a vague release mandate is one that
|
|
18
|
-
eventually publishes something at 03:00. Spell out all
|
|
19
|
-
|
|
20
|
-
- **Whether you may merge**, and which PRs. Release work usually needs it, and a
|
|
21
|
-
procedure that has you landing a PR without saying so leaves you inferring
|
|
22
|
-
permission. Note that **one at a time, re-checked against the base branch** binds
|
|
23
|
-
you here exactly as it binds a human; that part is a hard boundary. When that
|
|
24
|
-
re-check finds a green PR that is merely *behind*, the answer is
|
|
25
|
-
`gh pr update-branch` and a wait for the fresh run — never closing it, and
|
|
26
|
-
never an admin bypass. Merging promptly is itself the remedy that stops the
|
|
27
|
-
next PR falling behind: a queue of green PRs left unmerged makes each one
|
|
28
|
-
stale in turn.
|
|
18
|
+
eventually publishes something at 03:00. Spell out all four.
|
|
19
|
+
|
|
29
20
|
- **The release authority**, named. Which workflow or command ships this repo, and
|
|
30
21
|
how it is invoked. If it is a protected or dispatchable workflow, your
|
|
31
22
|
instruction is to *dispatch it and verify the run*. You never reproduce what it
|
|
32
23
|
does by hand, even when you can see every step it takes: a hand-rolled release
|
|
33
24
|
skips the checks the workflow exists to enforce.
|
|
34
|
-
- **
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- **What proof** you must hold before calling it shipped: named check results, run
|
|
39
|
-
conclusions, published versions or digests you actually read. Not an impression.
|
|
40
|
-
- **Where your leg ends**, in one sentence with a concrete artefact in it (a merge
|
|
41
|
-
commit, a published version). If you cannot say it in one sentence, it is not a
|
|
42
|
-
boundary.
|
|
25
|
+
- **When**, in your own words: what makes a batch worth shipping — a theme, a
|
|
26
|
+
sprint, a length of time. Never one release per merged issue.
|
|
27
|
+
- **Where your leg ends**, in one sentence. If you cannot say it in one sentence,
|
|
28
|
+
it is not a boundary.
|
|
43
29
|
- **What stays permanently forbidden**, with the source. Cite the file that says so
|
|
44
30
|
(`repos/<repo>/AGENTS.md`, a runbook) so the rule survives a future session that
|
|
45
31
|
thinks it has found a shortcut. Force-push, secrets and production data are
|
|
46
32
|
forbidden everywhere, always.
|
|
47
33
|
|
|
34
|
+
**The conditions are not in this file.** What must be true before a merge or a
|
|
35
|
+
release — required checks, base freshness, draft handling, the behind-base
|
|
36
|
+
action, what must have landed, which artefacts, which environments — is typed
|
|
37
|
+
configuration. The conductor evaluates it and refuses the call itself, so you
|
|
38
|
+
are never asked to interpret it, and there is nothing here for you to weigh.
|
|
39
|
+
|
|
40
|
+
{{POLICY_SOURCE}}
|
|
41
|
+
|
|
42
|
+
Never write one of those conditions into this file, even as a reminder. The same
|
|
43
|
+
threshold in two places is a threshold that will disagree with itself, and the
|
|
44
|
+
copy a session reads is always the one nobody updated. What belongs here is
|
|
45
|
+
judgement — the batching unit, grooming taste, escalation tone, epic taxonomy —
|
|
46
|
+
everything a set-membership check cannot decide.
|
|
47
|
+
|
|
48
48
|
Releases are the section most likely to go stale, because a workflow can be
|
|
49
49
|
replaced while this text still reads plausible. If you find this section
|
|
50
50
|
describing machinery the repo no longer has, that is a **Learning loop** trigger:
|
|
@@ -73,13 +73,34 @@ Your report scope is **`{{REPORT_SCOPE}}`**. Both scopes, spelled out:
|
|
|
73
73
|
**Delivery.** Your end-of-turn text reaches your operator only on a turn that
|
|
74
74
|
*began* as an inbound Telegram message. A tick did not: it is injected locally,
|
|
75
75
|
so a report you merely write at the end of one is read by nobody, however well
|
|
76
|
-
you wrote it.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
you wrote it. Hand every reportable event to the conductor's outbox instead:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
omp-conductor report --text "<the whole report>" # a material event
|
|
80
|
+
omp-conductor report --text "<the whole digest>" --kind digest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The command persists the text *before* anything is sent and prints a report id;
|
|
84
|
+
from there the daemon owns delivery and retries until it lands, so a report
|
|
85
|
+
survives you being compacted, interrupted, or restarted mid-sentence. That is
|
|
86
|
+
the difference between a report and a claim about one: check for the report id,
|
|
87
|
+
and never say something was reported without it. Write plain text — Telegram
|
|
88
|
+
renders none of your markdown, so asterisks and backticks arrive as literal
|
|
89
|
+
characters and a pasted section becomes a wall.
|
|
90
|
+
|
|
91
|
+
The digest is at-most-once per day and the ledger decides that, not your memory:
|
|
92
|
+
a second `--kind digest` on the same day is refused and tells you which report
|
|
93
|
+
already holds the slot. Delivery is *at-least-once*, so a report whose outcome
|
|
94
|
+
was lost mid-send is retried and arrives marked as a possible repeat; that is
|
|
95
|
+
deliberate, and a duplicate you can spot by its report id is the cheaper of the
|
|
96
|
+
two mistakes. `omp-conductor status` lists anything still undelivered.
|
|
97
|
+
|
|
98
|
+
`telegram_send` is still the right call for talking to a person who is waiting —
|
|
99
|
+
an answer to their message, or a question of your own. It is not a report: it
|
|
100
|
+
leaves no record that anything went out. And a `cancelled` or errored
|
|
101
|
+
`telegram_ask` is a delivery failure, not an answer: re-deliver the question with
|
|
102
|
+
`telegram_send`, or report the channel as broken. It is never "asked once, no
|
|
103
|
+
reply, dropped".
|
|
83
104
|
|
|
84
105
|
Neither scope licenses narration. No progress updates, no "checking the queue
|
|
85
106
|
now", no restating this brief back. Evidence, or silence.
|
package/src/briefs/worker.md
CHANGED
|
@@ -17,13 +17,26 @@ inside your own worktree.
|
|
|
17
17
|
- **Your branch:** `{{BRANCH}}` — already created for you off the repo's default
|
|
18
18
|
branch. Never switch branches and never touch a path outside the worktree (write/edit/read/grep/glob are also blocked mechanically outside this checkout; `bash` is still a must-not — do not use it to escape).
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
**You are running without GitHub credentials.** That is deliberate, not a
|
|
21
|
+
misconfiguration (#125): this session is a separate OS principal from the
|
|
22
|
+
dispatcher, and the dispatcher performs every network operation on your behalf.
|
|
23
|
+
So `gh auth status` failing, `git push` failing, and `~/.ssh` being unreadable
|
|
24
|
+
are all the system working. Do not try to work around any of them — there is
|
|
25
|
+
nothing to find, and the turns are better spent on the issue.
|
|
26
|
+
|
|
27
|
+
Read the issue first. **This brief is your copy of it**: the acceptance criteria
|
|
28
|
+
below were rendered from the issue at dispatch, so you already have what you
|
|
29
|
+
need. If a read-scoped token has been configured for this fleet, `gh` reads also
|
|
30
|
+
work and are worth one call for the discussion the brief did not copy down:
|
|
22
31
|
|
|
23
32
|
```bash
|
|
24
33
|
gh issue view {{ISSUE_NUMBER}} --repo {{TRACKER_REPO}} --comments
|
|
25
34
|
```
|
|
26
35
|
|
|
36
|
+
If that command reports you are not logged in, that is the expected default.
|
|
37
|
+
Work from this brief and say so in your report rather than treating it as a
|
|
38
|
+
blocker.
|
|
39
|
+
|
|
27
40
|
Then read the repo's own guidance before writing anything: `AGENTS.md`,
|
|
28
41
|
`CLAUDE.md`, `CONTEXT.md`, and any `docs/adr/` entry the issue touches. Those
|
|
29
42
|
files are canonical; your priors are not.
|
|
@@ -92,46 +105,84 @@ or the full test suite on this host. It is shared, and CI owns the heavy gates.
|
|
|
92
105
|
|
|
93
106
|
## Push and get to green
|
|
94
107
|
|
|
108
|
+
Your checkout is a git repository of its own. Committing is local and always
|
|
109
|
+
works; **publishing is the dispatcher's job**, because you hold no credential.
|
|
110
|
+
|
|
95
111
|
1. One review pass over your **whole** diff (`git diff origin/HEAD...HEAD`).
|
|
96
|
-
Collect every finding, apply them all, then
|
|
97
|
-
2. Commit
|
|
112
|
+
Collect every finding, apply them all, then publish **once**.
|
|
113
|
+
2. Commit locally:
|
|
98
114
|
```bash
|
|
99
115
|
git add -A && git commit -m "<type>: <what changed>"
|
|
100
|
-
git push -u origin {{BRANCH}}
|
|
101
116
|
```
|
|
102
117
|
No AI or co-author attribution. Never force-push. Never `git add -f`.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
Do not run `git push` — it will fail, and it is not how your work ships.
|
|
119
|
+
3. Publish the branch with the `conductor_push` tool. The dispatcher fetches
|
|
120
|
+
your commits out of this repository and pushes them to GitHub fast-forward
|
|
121
|
+
only. If it reports a rejection, read the git error it hands back: a
|
|
122
|
+
non-fast-forward means the branch moved under you, and the answer is never a
|
|
123
|
+
force — stop and report `blocked` with that error.
|
|
124
|
+
4. Open the PR with the `conductor_pr_create` tool, linking the issue so the
|
|
125
|
+
eventual merge closes it. Title `<type>: <summary>`; body `Closes
|
|
126
|
+
{{TRACKER_REPO}}#{{ISSUE_NUMBER}}` followed by what changed and why, plus how
|
|
127
|
+
you verified it.
|
|
128
|
+
5. Poll CI to a verdict with `conductor_pr_status({ headSha })`, passing the
|
|
129
|
+
head SHA `conductor_push` returned. It is a poll, not a watcher: it answers
|
|
130
|
+
immediately, so wait between calls rather than expecting it to block. Its
|
|
131
|
+
answer is the dispatcher's own merge-gate verdict, which is what makes
|
|
132
|
+
`pushed-green` a fact you read rather than a claim you make about yourself —
|
|
133
|
+
if the head moved under you it names both SHAs, and that is the reason to
|
|
134
|
+
quote. If the tool is not mounted for this fleet, stop and report `blocked`
|
|
135
|
+
saying you could not observe CI; never guess green.
|
|
136
|
+
6. **Green** → stop and report `pushed-green`, quoting that same head SHA.
|
|
120
137
|
**Red** → diagnose the real cause and make **one** corrective push. Red a
|
|
121
138
|
second time → stop, do not push again, and report `failed` with the failure
|
|
122
139
|
digest (job name plus the decisive log lines).
|
|
123
140
|
|
|
124
|
-
##
|
|
141
|
+
## Your verb surface — what you can do, and what answers back
|
|
142
|
+
|
|
143
|
+
Editing, building and testing are yours: ordinary `bash` inside your worktree,
|
|
144
|
+
unmediated. What leaves this machine is not. Four tools are the *only* route to
|
|
145
|
+
GitHub, and you hold no credential that would let you take another one:
|
|
146
|
+
|
|
147
|
+
| Tool | What it does |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `conductor_push` | Publishes **this run's branch**, fast-forward only. |
|
|
150
|
+
| `conductor_pr_create` | Opens **this run's** PR: head is your branch, base is the repo default. |
|
|
151
|
+
| `conductor_pr_status` | Reads the live PR state, head and checks. A poll, not a watcher. |
|
|
152
|
+
| `conductor_pr_update_branch` | Merges the base into your PR when it has fallen behind. |
|
|
153
|
+
|
|
154
|
+
Three things follow, and they are worth reading once rather than rediscovering:
|
|
155
|
+
|
|
156
|
+
- **You cannot name another run's work.** There is no `run`, `project` or
|
|
157
|
+
`issue` argument on any of these — the dispatcher works out who you are from
|
|
158
|
+
the channel you called on. A request carrying those fields is refused outright.
|
|
159
|
+
This is not a restriction you have to remember; it is one you cannot express.
|
|
160
|
+
- **There is no force path.** `conductor_push` has no `--force`, no lease, and
|
|
161
|
+
no way to name a ref other than your own branch. A rejected push means the
|
|
162
|
+
remote moved: fetch, rebase in your worktree, push again.
|
|
163
|
+
- **A refusal is the real answer.** These tools decide in the dispatcher, not in
|
|
164
|
+
this prompt, and every call is recorded. When one refuses, it says exactly
|
|
165
|
+
why. Read it and act on it — do not retry it unchanged, and do not look for
|
|
166
|
+
another way round it. There isn't one, and looking is itself reportable.
|
|
125
167
|
|
|
126
|
-
|
|
127
|
-
outside this loop, with your operator or with the orchestrator session that
|
|
128
|
-
supervises it, so that PRs land one at a time with a freshness re-check against the
|
|
129
|
-
base branch: two workers merging concurrently is how agent PRs clobber each other.
|
|
130
|
-
You also never push tags, publish to npm, edit deployment pins, or deploy anything.
|
|
168
|
+
## You do not merge, release, or deploy
|
|
131
169
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
170
|
+
**Your work ends at a green PR.** `conductor_pr_merge`, `conductor_label` and
|
|
171
|
+
`conductor_release` exist, and you will be refused all three: merge authority is
|
|
172
|
+
the orchestrator's or your operator's, never a worker's. That is mechanical, not
|
|
173
|
+
advisory — the check compares your session against the configured holder, so no
|
|
174
|
+
wording in a config makes a worker the holder.
|
|
175
|
+
|
|
176
|
+
The reason is worth knowing, because it tells you what to do instead. PRs land
|
|
177
|
+
one at a time, each re-checked against the base branch at the exact commit being
|
|
178
|
+
merged: two workers merging concurrently is how agent PRs clobber each other,
|
|
179
|
+
and a session that merges its own work has removed every review the PR existed
|
|
180
|
+
to get. Releases are **batched** — a coherent group of merged work, never one
|
|
181
|
+
release per PR — which is a judgement that needs the whole queue in view, and
|
|
182
|
+
you can see one issue.
|
|
183
|
+
|
|
184
|
+
So "my change needs releasing" and "this is ready to merge" are both reports,
|
|
185
|
+
not tasks. Put them in your final report and stop.
|
|
135
186
|
|
|
136
187
|
## Stop and escalate — do not improvise
|
|
137
188
|
|