omp-conductor 0.13.0 → 0.15.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.
Files changed (44) hide show
  1. package/README.md +549 -234
  2. package/package.json +8 -5
  3. package/schema/config.schema.json +609 -0
  4. package/src/availability.ts +165 -0
  5. package/src/board.ts +19 -32
  6. package/src/brief-upgrade.ts +1 -1
  7. package/src/briefs/orchestrator.md +72 -31
  8. package/src/briefs/policy.md +48 -36
  9. package/src/briefs/probes/gates.md +51 -0
  10. package/src/briefs/probes/project-context.md +59 -0
  11. package/src/briefs/probes/release-procedure.md +81 -0
  12. package/src/cli.ts +356 -212
  13. package/src/config-schema.ts +352 -0
  14. package/src/config.ts +1037 -679
  15. package/src/confinement.ts +54 -0
  16. package/src/daemon.ts +644 -390
  17. package/src/diff-flags.ts +73 -4
  18. package/src/digest-schedule.ts +92 -24
  19. package/src/escalate.ts +89 -22
  20. package/src/fleet.ts +351 -46
  21. package/src/generate-schema.ts +21 -0
  22. package/src/graph.ts +3 -3
  23. package/src/host.ts +16 -0
  24. package/src/omp.ts +21 -1
  25. package/src/orchestrator-tick.ts +732 -56
  26. package/src/privileged.ts +264 -0
  27. package/src/reports.ts +203 -6
  28. package/src/session-host.ts +3 -0
  29. package/src/setup-host.ts +209 -24
  30. package/src/setup-install.ts +320 -0
  31. package/src/setup-probe.ts +412 -0
  32. package/src/setup-wizard.ts +1946 -0
  33. package/src/setup.ts +457 -53
  34. package/src/store.ts +610 -98
  35. package/src/tracker/github.ts +43 -5
  36. package/src/types.ts +153 -14
  37. package/src/upgrade.ts +44 -10
  38. package/src/verbs/actions.ts +131 -13
  39. package/src/verbs/server.ts +40 -18
  40. package/src/wizard-ui.ts +249 -0
  41. package/src/worker.ts +24 -7
  42. package/skills/conductor-onboarding/SKILL.md +0 -748
  43. package/skills/conductor-update/SKILL.md +0 -51
  44. package/src/plugin.ts +0 -1495
@@ -1,748 +0,0 @@
1
- ---
2
- name: conductor-onboarding
3
- description: Interview-driven onboarding for omp-conductor. Use when the user wants to set up conductor, onboard a new fleet or project, configure the fleet, asks for conductor setup help, asks what belongs in POLICY.md / ORCHESTRATOR.md, or wants an agent's release and merge authority scoped and written down. Interviews the operator on release policy, escalation taste and reporting scope, reads each routing repo's CI to propose the real pre-push gates, learns the product and roadmap the fleet will groom, scaffolds the release procedure from the repo's own release workflows rather than from the operator's memory, tailors POLICY.md (and the composed ORCHESTRATOR.md view) from the shipped templates, verifies the worker brief's assumptions against the actual repos, finishes through the deterministic /conductor setup wizard, then builds the code-graph indexes workers query instead of grepping.
4
- ---
5
-
6
- # Onboarding a conductor fleet
7
-
8
- Onboarding has two layers, and they are not the same job.
9
-
10
- - **`/conductor setup` is the mechanical layer.** It is deterministic, tested, and
11
- the **only** thing that writes `config.json`. It asks closed questions, plans
12
- labels, prints a dry run, and mutates nothing until the operator confirms.
13
- - **You are the judgement layer.** The wizard cannot ask "where should this fleet
14
- stop?" and get a useful answer from a text prompt, and it cannot read a repo to
15
- find out what CI actually runs. That is the part that decides whether an
16
- unattended fleet is safe, and it is yours.
17
-
18
- So: **you interview and investigate; the wizard writes.** Never hand-edit
19
- `config.json`, never construct it and ask for a blessing, never skip the wizard's
20
- dry run. Its consent gate is the safety property; going around it removes the one
21
- step where the operator sees what is about to change.
22
-
23
- Two more ground rules before you start.
24
-
25
- **Do not recite this file.** Nothing here is a script to read aloud. Every
26
- question below exists to extract one decision; ask it in your own words, in the
27
- operator's vocabulary, and skip the ones the repo already answers. An interview
28
- where you ask and they answer is worth more than a checklist you both step
29
- through.
30
-
31
- **Investigate before you ask.** Anything discoverable — default branches, CI
32
- jobs, gate commands, whether a repo has a `Makefile` — you read, then propose.
33
- Asking an operator to recite their own CI config is how gates end up wrong: they
34
- tell you what they *think* runs, and the fleet pushes on it at 03:00.
35
-
36
- ---
37
-
38
- ## Step 0 — orient
39
-
40
- Before the first question, find out where you are:
41
-
42
- ```bash
43
- gh auth status # repo + project scopes, and who the token is
44
- cat ~/.omp/conductor/config.json # honours $OMP_CONDUCTOR_HOME
45
- ```
46
-
47
- - **Config already there?** This is a re-run or a second project. The wizard
48
- pre-fills from the existing project and replaces it in place, keeping its
49
- neighbours — so the interview is now about *what changed*, not everything.
50
- Read the existing project out loud and ask what is wrong with it.
51
- - **`gh` missing `repo` or `project`?** Say so now. The wizard warns at the
52
- confirm, but a token that cannot label issues means every claim fails, and it
53
- is cheaper to fix before the interview than after.
54
- - **A `POLICY.md` or `ORCHESTRATOR.md` already exists** under `<workspaceRoot>/`
55
- (default `~/.omp/conductor/worktrees/`)? Read `POLICY.md` first when present —
56
- that is the editable fleet policy. Otherwise read `ORCHESTRATOR.md`. It is the
57
- operator's accumulated policy, and it outranks the shipped template. Your job
58
- becomes amending it, and you must warn that a wizard re-run offers to overwrite
59
- it.
60
-
61
- ---
62
-
63
- ## Step 1 — the interview
64
-
65
- Ask for decisions, not for values you could look up. Each question below carries
66
- the reason it matters; when you ask it, lead with that reason. An operator who
67
- understands why a question is being asked gives a usable answer.
68
-
69
- ### Project and tracker
70
-
71
- **"What is this fleet called, and which one repo holds the queue?"**
72
-
73
- *Why:* the project name is the handle for everything afterwards
74
- (`/conductor status <name>`, `--project <name>`), and the tracker is the **one**
75
- `owner/repo` whose open-issue list *is* the queue. Nothing else is ever read.
76
-
77
- Say the part that surprises people: **the tracker repo does not have to contain
78
- any code.** A planning repo whose only content is issues is the normal shape, and
79
- it is often the better one — the queue then has its own label namespace and its
80
- own permissions, separate from the code it dispatches into.
81
-
82
- Then: **"What label means a human has signed this off?"** Default
83
- `ready-for-agent`. *Why:* this label is the entire consent mechanism for
84
- unattended work. The conductor never adds it. Everything the fleet ever does
85
- starts with a human putting that label on an issue, so it should read like a
86
- decision, not like a status.
87
-
88
- The three state labels (`agent:in-progress`, `agent:blocked`, `agent:failed`) are
89
- written *by* the conductor so the tracker alone shows live state. Only ask about
90
- renaming them if the repo already has a colliding label convention — the
91
- namespaced defaults are right almost always, and the wizard asks with one confirm.
92
-
93
- ### Routing — what lands where
94
-
95
- **"Which code repos does this queue dispatch into, and how would you tell them
96
- apart on an issue?"**
97
-
98
- *Why:* an issue must carry **exactly one** `repo:<name>` label naming a repo in
99
- `routing.repos`. Zero or two is reported unroutable and skipped — never guessed.
100
- Routing is the fan-out: one tracker, any number of code repos.
101
-
102
- For each repo you need the clone URL and the default branch, but **look those up**
103
- rather than asking (Step 2). What you actually need from the operator is the
104
- routing *key* — the word that goes after the prefix — and it should be the word
105
- they already use in conversation about that repo, because they are the one who
106
- will be typing it onto issues at 2am.
107
-
108
- Prefer an SSH clone URL, or an https URL backed by a credential helper. A URL
109
- with a credential in it gets persisted into the mirror's git config exactly as it
110
- would for a hand-run clone; say that out loud if you see one.
111
-
112
- ### Release policy — the question that actually matters
113
-
114
- This is the decision the shipped brief exists to protect, so give it room.
115
-
116
- First, settle who is even a candidate, because operators assume it is the workers
117
- and it never is. A worker is scoped to one issue: it cannot judge whether a release
118
- is worth cutting, and it stops at a green PR permanently. **The only two candidates
119
- are the orchestrator and a human.** The orchestrator is the right agent for it if
120
- any agent is: it is long-lived, it can see everything that merged since the last
121
- release, and batching is exactly the judgement a per-issue session cannot make.
122
-
123
- So frame it as **three** options, in this order, and name the default. Each one
124
- maps to two independent wizard answers:
125
-
126
- - `authority.release` says **who owns the release decision**;
127
- - `releasePolicy` is the mechanical tool gate that says whether release-shaped
128
- commands may run at all.
129
-
130
- The interview decides both answers, and `/conductor setup` records them. Never
131
- write either value into the brief or hand-edit `config.json`: config is the
132
- single source, and the wizard's dry run is where the operator sees the combined
133
- grant.
134
-
135
- 1. **Humans release.** *(default, and what the package ships)* Work ends at a
136
- green PR. Merging is a separate decision; releasing is a separate human
137
- action. Neither a worker nor the orchestrator tags, pins, publishes, or
138
- deploys. "This needs releasing" becomes something the orchestrator reports.
139
- → `authority.release=human`, `releasePolicy=none`; answer **no** to both
140
- release confirms.
141
- 2. **The orchestrator releases up to a named boundary.** Delegate release
142
- authority only when its leg includes the first release action (for example,
143
- creating a tag or dispatching a release workflow). → usually
144
- `authority.release=orchestrator`, `releasePolicy=operator-brief`, with the
145
- boundary in the procedure rather than in the grant. If its leg ends before
146
- that action, use `authority.release=human`, `releasePolicy=none`.
147
- 3. **The orchestrator releases fully.** →
148
- `authority.release=orchestrator`, `releasePolicy=operator-brief`.
149
-
150
- Merge authority is its own confirm in every option. Also say what opening the
151
- tool gate means: it is project-wide and is passed to worker sessions as well as
152
- the orchestrator. It does not grant workers release authority — their brief
153
- still stops them at a green PR — but it removes the mechanical block. Never open
154
- it until the operator has reviewed the release procedure and stopping boundary.
155
-
156
- Then ask the question that makes option 2 real:
157
-
158
- > **"Where does the orchestrator's leg END?"**
159
-
160
- Not "can it release" — *where does its leg stop*. A boundary you cannot state in
161
- one sentence is not a boundary, and an orchestrator with a vague release mandate
162
- is one that eventually publishes something at 03:00.
163
-
164
- Give them a worked answer so they can calibrate. One real deployment's answer:
165
- **the agent's leg ends at the merged version pin.** It may open and land the PR that
166
- bumps the module image tags, because that artefact is reviewable, reversible by a
167
- revert, and its correctness is checkable by a named CI check. Deploying that pin
168
- to prod is **operator territory** — it needs a person who can watch it, and who
169
- owns the rollback. That is a leg with an end: a merge commit.
170
-
171
- If they choose option 2, capture all five of these, because the brief needs them
172
- and a missing one is a hole:
173
-
174
- - **what** may be released — which packages or images, from which branch;
175
- - **when** — batched how, after which *named* checks are green;
176
- - **what proof** must be held first — check results actually read, not an
177
- impression;
178
- - **what must still be asked, every time**;
179
- - **what stays permanently forbidden** — force-push, secrets, production data.
180
-
181
- If they choose option 3, do not just write it down. Warn, concretely:
182
-
183
- - **Credentials.** Full release means the session holds publish tokens, registry
184
- credentials, or deploy keys. Those live in the session's environment, exposed to
185
- every turn, including a turn that went wrong. Ask whether they are willing to
186
- put that credential in a process that runs unattended for weeks.
187
- - **Rollback ownership.** An agent that can release owns the 03:00 rollback too,
188
- and a rollback is a judgement call under time pressure with partial
189
- information — the exact thing agents are worst at. If the answer to "who rolls
190
- this back" is a person, then that person is already the release owner, and
191
- option 2 with a named boundary is the honest configuration.
192
-
193
- Push back once if the answer is option 3 and the reasoning is "it'll be fine".
194
- Then record what they decide. It is their fleet.
195
-
196
- **Then stop, and do not write the section yet.** What you have is the operator's
197
- *intent*: how much they want to delegate, and where they want the line. The
198
- *steps* on their side of that line come from the repo's own release machinery, not
199
- from this conversation. Step 4 reads that machinery and turns the intent into a
200
- procedure. Writing release steps from an interview answer is how a brief ends up
201
- prescribing a hand-rolled release the repo's own policy forbids.
202
-
203
- ### Escalation taste
204
-
205
- **"What should wake you up, and what can wait for the digest?"**
206
-
207
- *Why:* there are two tiers, and the split is not adjustable — tier 1 is a worker
208
- that stopped and asked a question (the orchestrator answers it), tier 2 is
209
- "nobody can proceed without a human". What *is* adjustable is how tier 2 reaches
210
- them and how much else comes along with it. An operator who gets paged for
211
- everything stops reading the pages, which is functionally the same as having no
212
- escalation channel at all.
213
-
214
- Concretely, decide:
215
-
216
- - **Channel.** If `omp-telegram` is installed and paired, the wizard offers that
217
- chat. If not, tier 2 degrades to an issue comment — a documented fallback, not
218
- an error, but say plainly that a comment on an issue nobody is watching is a
219
- page that lands nowhere.
220
- - **Belt and braces.** Also comment on the issue when a run escalates?
221
- Recommended yes: a chat message you miss is a run nobody sees, and the comment
222
- is the durable copy.
223
- - **Attempts before escalating** (`maxAttemptsPerIssue`, default 3). *Why:* an
224
- issue that failed three times is not a retry candidate — it is a diagnosis
225
- task. Setting this high converts a bad issue into a spend line.
226
-
227
- ### Reporting scope
228
-
229
- **"Do you want to hear about progress, or only about problems?"**
230
-
231
- *Why:* this is the one half of the brief that the *config* also knows about,
232
- because every orchestrator tick appends the matching constraint line to its
233
- prompt, re-read from the config each tick. It maps onto exactly two values:
234
-
235
- | Answer | `reporting.scope` | What that means |
236
- | --- | --- | --- |
237
- | "Tell me when things happen." | `material` *(default)* | Escalations, plus every material event as it happens: a run reaching a green PR (with link), a run that failed twice, an issue pulled off the queue, a cap that stopped the fleet. |
238
- | "Only bother me when I'm needed." | `escalations` | Every tier-2 escalation immediately, plus one daily digest. Every other tick silent. |
239
-
240
- Two honesty notes to pass on:
241
-
242
- - **Neither scope is an outbound filter.** Nothing inspects the orchestrator's
243
- messages and drops the ones the scope did not ask for. It is a constraint handed
244
- to the model each turn, not a gate it is held to.
245
- - **Changing the scope later does not rewrite an existing `POLICY.md`.** The
246
- tick line changes; the brief does not. Whoever changes it must edit the brief's
247
- Reporting section too, or the session is carrying two versions of the policy.
248
-
249
- ---
250
-
251
- ## Step 2 — read the repos, then propose the gates
252
-
253
- **Do this before you ask anything about gates.** The wizard's gate prompt is a
254
- free-text field; whatever goes in it is what an unattended worker runs before
255
- every push. It has to match what CI actually runs, and the operator is not the
256
- best source for that. Their CI config is.
257
-
258
- For each routing repo, mirror it read-only if you do not have it locally, then
259
- read:
260
-
261
- ```bash
262
- gh api repos/<owner>/<repo> --jq '.default_branch'
263
- gh api repos/<owner>/<repo>/contents/.github/workflows --jq '.[].name'
264
- ```
265
-
266
- and then, in the checkout:
267
-
268
- - `.github/workflows/*.yml` — the PR-triggered jobs and their exact `run:` lines,
269
- including their `working-directory`;
270
- - `package.json` — the `scripts` block (`lint`, `check`, `typecheck`, `test`);
271
- - `Makefile` / `justfile` / `Taskfile.yml` — many repos put the real gate here and
272
- have CI call `make check`;
273
- - `pyproject.toml`, `Cargo.toml`, `go.mod` — for the same reason, in other
274
- ecosystems;
275
- - any `CONTRIBUTING.md` / `AGENTS.md` line that names the pre-push command.
276
-
277
- Then **propose**, do not ask. Show your reading and the exact commands, with the
278
- `cwd` each runs from, in the format the wizard takes (`cmd`, or `cmd @ cwd`,
279
- comma-separated; `cwd` defaults to `.`):
280
-
281
- ```text
282
- repo:api — from .github/workflows/ci.yml (on: pull_request), jobs lint + test:
283
- bun run lint @ . # CI: `bun run lint` at repo root, whole tree
284
- bun run check @ . # CI: tsc --noEmit
285
- bun test @ . # CI: bun test
286
-
287
- Is that the set, and is anything missing that CI would catch?
288
- ```
289
-
290
- Three warnings to carry, every time:
291
-
292
- 1. **Whole tree, not the subdirectory.** CI lints everything. A gate that lints
293
- only `src/` is how an error in a migration, a config file, a script or a test
294
- fixture reaches the runners. If CI runs `bun run lint` at the root, the gate is
295
- `bun run lint` at the root — not `bun run lint src`.
296
- 2. **Cheap gates only.** Do not put docker builds, image builds, production
297
- builds, browser/e2e suites, or a full integration suite in here. The host is
298
- shared; CI owns the heavy gates. If a repo's only meaningful check is heavy,
299
- say so — that repo's workers will lean on CI, and that is a known cost, not
300
- something to paper over with a fake gate.
301
- 3. **No gates at all is a real answer, and a loud one.** The wizard warns. Repeat
302
- the warning in plain terms: a repo with no gates means every unattended push
303
- is an experiment run on the shared runners.
304
-
305
- ---
306
-
307
- ## Step 3 — learn the product
308
-
309
- The duties assume an orchestrator that understands what it is grooming. Duty 2
310
- asks it to judge whether acceptance criteria are readable and whether an issue is
311
- worth claiming at all; neither is answerable by a session that knows only the repo
312
- names. So learn the product now, and write what you learn into the brief.
313
-
314
- Read, per routing repo:
315
-
316
- - the `README` — what the thing is for, and who uses it;
317
- - whatever top-level architecture doc exists (`docs/`, `ARCHITECTURE.md`, an ADR
318
- directory), enough to say which repo owns which concern;
319
- - `AGENTS.md` / `CONTRIBUTING.md` for the repo's own rules, which outrank the
320
- brief.
321
-
322
- Then read the tracker as a roadmap rather than as a queue:
323
-
324
- ```bash
325
- gh issue list --repo <tracker> --state open --limit 100 --json number,title,labels,milestone
326
- gh api repos/<tracker>/milestones --jq '.[] | "\(.title) — \(.open_issues) open"'
327
- gh label list --repo <tracker>
328
- ```
329
-
330
- Milestones and the label taxonomy are what separate a theme from a one-off. Epics
331
- usually surface as one or the other.
332
-
333
- Then ask the operator exactly one question, because it is the one thing none of
334
- that reading answers:
335
-
336
- > **"Where does the roadmap live, and what is the current priority?"**
337
-
338
- *Why:* a tracker shows what is open, never what matters. An orchestrator that
339
- cannot rank work grooms by recency, and that is how a stale issue outranks the
340
- thing the operator is actually shipping this month.
341
-
342
- **Write the findings into the brief** as a `## Project context` section, in the
343
- editable half beside Releases — the template ships a stub for it. Keep it under 40
344
- lines: it is read on every tick, and a brief nobody finishes reading is a brief
345
- that gets skimmed. It needs four things.
346
-
347
- 1. **The product in one paragraph.** What it does, for whom. Not a feature list.
348
- 2. **A repo map** — one line per routing key, naming what that repo owns in the
349
- operator's vocabulary. This is what turns a routing label into a judgement.
350
- 3. **Grooming guidance for Duty 2.** Which repos ship together, so a change in one
351
- is known to need a matching PR in the other. And which kinds of issue touch the
352
- same files: those must not be queued concurrently, because two workers editing
353
- one file produce two PRs that cannot both merge.
354
- 4. **Where the roadmap lives, and how to judge priority against it** — the pointer
355
- the operator just gave you, in one line.
356
-
357
- Same order of operations as Step 5: draft it now, and apply it to the brief after
358
- the wizard has written the file.
359
-
360
- ---
361
-
362
- ## Step 4 — scaffold the release process from the repo, not from memory
363
-
364
- The interview gave you a boundary. This step turns it into steps the operator's
365
- repos will actually accept, for the same reason Step 2 reads CI instead of asking
366
- about gates: an operator describes the release they *remember*, and a release is
367
- the one procedure where being approximately right is worst.
368
-
369
- Skip this step only for option 1 (humans release). There is nothing to scaffold:
370
- the rendered paragraph already says humans hold both, and there is no procedure
371
- under it to write.
372
-
373
- ### Find the release authority
374
-
375
- Per repo that can be released, read:
376
-
377
- ```bash
378
- gh api repos/<owner>/<repo>/contents/.github/workflows --jq '.[].name'
379
- gh release list --repo <owner>/<repo> --limit 10
380
- git tag --list --sort=-v:refname | head
381
- ```
382
-
383
- and then, in the checkout:
384
-
385
- - **every workflow that publishes anything** — a release, a tag, an image, a
386
- package, a deployment. For each, the **trigger** is the fact that matters:
387
- - `workflow_dispatch` → the release is *dispatched*. That workflow is the
388
- authority, and the correct instruction is "dispatch it with the planned
389
- version", never "do what it does".
390
- - `on: push: tags:` → a pushed tag is the trigger, so tagging *is* releasing.
391
- - `on: release: published` → the GitHub Release is the trigger.
392
- - **what the workflow enforces.** Many reject a tag unless several version files
393
- agree. That constraint belongs in the brief, quoted, because it is the failure
394
- the agent will otherwise hit at 03:00.
395
- - **`AGENTS.md`, `CONTRIBUTING.md`, and any release runbook.** An explicit policy
396
- outranks anything you infer from a workflow, and it is usually where the
397
- *forbidden* paths are named.
398
- - **whether a release needs a human by construction.** An `npm publish` behind
399
- interactive 2FA cannot be delegated to an unattended session at all, whatever the
400
- operator would like. Say so rather than writing a step that cannot run.
401
-
402
- ### Then write three things, and one of them is the forbidden list
403
-
404
- Present this back as a proposal, in the repo's own commands, before it goes in the
405
- brief:
406
-
407
- 1. **The authority, named.** One sentence: which workflow or command ships this
408
- repo, and how it is invoked. If it is a protected workflow, the brief says
409
- *dispatch it and verify the run* and stops there.
410
- 2. **The steps on the agent's side of the boundary**, as commands, in order, each
411
- with the check that proves it worked. A step whose success cannot be read from
412
- a named check is not a step the agent can own.
413
- 3. **What is forbidden, and why, with the citation.** This is the part that decays
414
- silently, so it is the part to write down hardest. Cite the file and the line:
415
- `never mutate the deployment directly or reimplement the release by hand
416
- (repos/<repo>/AGENTS.md)`. A forbidden path with a source attached survives a
417
- future session's improvisation; "be careful with releases" does not.
418
-
419
- ### Ask two things the repo cannot tell you
420
-
421
- - **"What is a release worth cutting?"** The batching unit, in the repo's own
422
- vocabulary: a sprint, an epic's children all closed, N merged issues waiting, N
423
- days elapsed, or urgency. Without this the orchestrator either releases per merge
424
- (a stream of meaningless versions burning shared runners) or never releases at
425
- all.
426
- - **"Who owns the rollback?"** If the answer is a person, that person owns the
427
- release, and the boundary belongs before the irreversible step regardless of
428
- what option 2 sounded like in the interview.
429
-
430
- ### And make it self-correcting
431
-
432
- A release process is the section most likely to go stale: workflows get replaced,
433
- and a brief describing the old one still reads plausible. So tell the operator
434
- plainly, and make sure the brief's own **Learning loop** covers it: when the
435
- release workflow changes, the brief contradicts repo reality, which is exactly an
436
- amendment trigger. The session proposes the corrected steps and they approve with
437
- a yes.
438
-
439
- ---
440
-
441
- ## Step 5 — tailor the brief, and show the diff first
442
-
443
- `src/briefs/orchestrator.md` in this package is the **floor**, not the deliverable.
444
- It ships deliberately conservative so that an operator who never edits it still
445
- has a safe fleet. Your job is to raise it to *this* fleet.
446
-
447
- The file has a hard line in it — an HTML comment banner reading
448
- `YOURS TO EDIT`. Respect it in both directions:
449
-
450
- **Above the banner — leave it alone.** The three duties — drain (unstick what is
451
- stuck), groom (keep the queue worth draining), report — the
452
- escalation-tier table, and the hard boundaries describe how the package already
453
- behaves. Rewriting them makes the brief disagree with the code, and the code wins.
454
- The evidence rule in particular is not negotiable: *every claim cites evidence — a
455
- PR URL, an issue number, or a named check actually read.* "Should be fine",
456
- "looks green" and "probably passing" are not evidence.
457
-
458
- Know what is *not* up there, though, because operators expect it to be: the
459
- orchestrator's merge and release authority is **not** a hard boundary. It comes
460
- from the two `authority` answers in config. Release execution has a second,
461
- independent config control: `releasePolicy=none` mechanically blocks tags,
462
- publishing, releases, and deploy commands even when
463
- `authority.release=orchestrator`; `operator-brief` opens that gate for the
464
- reviewed procedure. What is fixed above the banner is that a *worker* never
465
- merges or releases, and that PRs land one at a time with a freshness re-check.
466
- Changing either release control needs no negotiation with the shipped half, but
467
- both must agree with the procedure below the banner.
468
-
469
- **Below the banner — rewrite from the interview.**
470
-
471
- - **Releases.** The section's opening paragraph is rendered from the `authority`
472
- answers, and Duty 1's "the PR is green" branch is rendered from the same
473
- place. Leave both alone: they are the config speaking, and re-running
474
- `/conductor setup` is how they change. What you write is everything under that
475
- paragraph — the procedure you scaffolded in Step 4. If humans-release, there is
476
- nothing to scaffold and you stop. If they chose a boundary, write the boundary
477
- as a sentence with an end — *"your leg ends at the merged pin PR; you never
478
- deploy it"* — then the scaffolded steps, then the forbidden list with its
479
- citations. Do not restate the merge grant in your own words: a second spelling
480
- of it is exactly the disagreement the rendered paragraph exists to prevent.
481
- - **Reporting.** Rewrite it as the one scope they chose, in the second person,
482
- concretely. Delete the description of the scope they did not choose: it is
483
- useful in a template and noise in a live prompt. Keep the closing constraint
484
- verbatim in substance — no narration, no progress updates, no restating the
485
- brief back. Evidence, or silence.
486
-
487
- **Then add what the template cannot know: this fleet's own hard boundaries.** Ask
488
- for them directly:
489
-
490
- > **"Is there anywhere in these repos an agent must never go, even when an issue
491
- > says to?"**
492
-
493
- Typical answers, worth prompting for by name: infrastructure directories
494
- (`terraform/`, `puppet/`, `ansible/`, `charts/`, `.github/workflows/`), anything
495
- holding secrets or environment files, migration directories, a vendored or
496
- generated tree, and **whole repos** that are in the org but off-limits. Write each
497
- one as a path or repo name, not as a category — "no infra changes" is advice, and
498
- `never edit terraform/ or .github/workflows/; escalate instead` is a boundary.
499
-
500
- **Show the diff before writing anything.** Present the tailored sections against
501
- the shipped template — the sections you rewrote, the boundaries you added, and an
502
- explicit statement that everything above the banner is untouched. Get an
503
- acknowledgement. This file becomes the standing prompt for a session that runs
504
- unattended for weeks; the operator reading it once, now, is the cheapest review it
505
- will ever get.
506
-
507
- **Order of operations matters here.** The wizard writes the *template* (with
508
- coordinates substituted) at the path it owns. So: draft and agree the tailored
509
- sections now, let the wizard write the rendered floor in Step 7, and apply the
510
- agreed edits to that file immediately afterwards. Do not pre-write the file to a
511
- path you guessed, and do not skip the wizard's brief-writing step — you would
512
- lose the substituted coordinates and the overwrite confirmation.
513
-
514
- ---
515
-
516
- ## Step 6 — check the worker brief's assumptions against reality
517
-
518
- The worker brief makes concrete claims to a session that has no other context. If
519
- a claim is wrong, the worker cannot tell — it just fails in a confusing way. Check
520
- each one against the actual repos, and report what you found.
521
-
522
- 1. **Default branches.** The brief says the worktree is cut from the repo's
523
- default branch and the PR targets it. Confirm per repo with
524
- `gh api repos/<owner>/<repo> --jq .default_branch`. A repo on `master`, `develop`
525
- or `trunk` configured as `main` produces a run that fails at worktree creation.
526
- 2. **Branch naming.** The conductor cuts `<type>/<slug>`, where type is `fix` when
527
- an issue label's last segment is `bug` and `feat` otherwise. Check that against
528
- the repo's convention and its branch protection: a ruleset that only permits
529
- `feature/*`, or requires a ticket prefix, will reject every push the fleet
530
- makes. `gh api repos/<owner>/<repo>/rulesets` and the branch-protection settings
531
- are the place to look.
532
- 3. **The gates actually run, and exit 0 on a clean checkout.** This is the one
533
- worth spending real time on. In a clean mirror or worktree, run each proposed
534
- gate read-only and record the exit code:
535
- - Safe to run: lint, typecheck, unit tests, formatter `--check` modes.
536
- - Do **not** run: anything that writes to the working tree (a formatter without
537
- `--check`), anything that hits the network beyond a package install, docker
538
- builds, deploys, or a suite that needs live services. If a gate cannot be
539
- verified safely, say which one and why, rather than asserting it works.
540
- - A gate that fails on a *clean* checkout is a fleet that can never push. That
541
- is a finding to report before arming, not after — the operator either fixes
542
- the repo or drops the gate, and both are their call.
543
- 4. **Does `pull_request` actually fire?** The whole loop ends with a worker
544
- watching `gh pr checks --watch` to a verdict. A workflow triggered only on
545
- `push` to a branch pattern, or gated behind `if: github.actor != ...`, or one
546
- that requires approval for a first-time contributor, gives a PR with **no
547
- checks** — and a worker that waits forever on a verdict that never comes. Read
548
- the `on:` block of each workflow and confirm `pull_request` is there and not
549
- path-filtered away from the paths this fleet will touch.
550
-
551
- Report these as findings with evidence, not as reassurance. "I read
552
- `.github/workflows/ci.yml`; `on: pull_request` is present with no path filter" is a
553
- finding. "CI should trigger" is not.
554
-
555
- ---
556
-
557
- ## Step 7 — finish through the wizard
558
-
559
- Now hand the collected answers to the deterministic path:
560
-
561
- ```text
562
- /conductor setup
563
- ```
564
-
565
- You have the answers ready, so this is fast — and it stays the wizard's decision
566
- to write, not yours. It asks, in this order: project name; tracker repo; queue
567
- label; whether to rename the state labels; routing label prefix; then per repo the
568
- routing key, clone URL, default branch and **pre-push gates** (your Step 2
569
- proposal, in `cmd @ cwd` form); whether to add another repo; whether to set up
570
- **code-graph discovery** and the root its clones live under (Step 8); caps; the
571
- merge and release authority confirms; the independent **Release tool gate**
572
- confirm (`none` or `operator-brief`); the worker model; the Telegram chat id for
573
- tier 2; the escalation fallback; whether an orchestrator session already runs
574
- elsewhere; the report scope; and finally whether to write `ORCHESTRATOR.md` +
575
- `POLICY.md`.
576
-
577
- The end of the wizard is load-bearing:
578
-
579
- - **The dry run is the point.** Before consent, setup reads the tracker with the
580
- daemon's routing code. It shows each routable and unroutable issue. Walk the
581
- operator through that output. An unroutable issue means that the labels and
582
- routing configuration disagree.
583
- - **Nothing changes before consent.** Setup creates no label, file, database, or
584
- arm marker before the operator agrees. Never answer that consent prompt for
585
- the operator.
586
- - **Review authority and gate together.** If the procedure assigns tags,
587
- publishing, release creation, or deploy commands to the orchestrator, the dry
588
- run must show both `release=orchestrator` and
589
- `releasePolicy=operator-brief`. `release=orchestrator` with
590
- `releasePolicy=none` assigns the job while mechanically preventing it. A
591
- human-owned release should normally remain `releasePolicy=none`.
592
- - **The host plan is part of consent.** For an external orchestrator, setup names
593
- the heartbeat file and both safety gates. It also names the staged systemd
594
- unit. An invalid existing heartbeat stops setup before any write.
595
- - **The smoke is automatic and paused.** After consent, setup holds dispatch,
596
- runs one daemon tick, proves `/healthz`, reads stored status, and stops the
597
- temporary daemon. A running daemon is health-checked and safely restarted only
598
- when no worker is live.
599
- - **External arming proves the real channel.** Every setup run sends the inbound
600
- Telegram challenge, even when an arm marker exists. A failed proof leaves
601
- dispatch paused and prints the recovery commands.
602
-
603
- Ask the operator to approve the `ORCHESTRATOR.md` and `POLICY.md` write. After
604
- the wizard completes, apply the edits from Steps 3, 4, and 5 to `POLICY.md`.
605
- Tell the operator its path. A later wizard run asks before it replaces these files.
606
-
607
- ### Then explain hold, pause, and disarm
608
-
609
- These controls have different effects:
610
-
611
- - **Setup readiness** means that the paused smoke passed. For external
612
- orchestration, the Telegram arm proof also passed. Setup then clears the
613
- dispatch pause. It does not install the staged systemd unit. Run the printed
614
- install commands on systemd, or run `omp-conductor start` on another host.
615
- - **Hold** (`omp-conductor hold`) pauses claims and disarms ticks. The daemon and
616
- pane stay active. Use this command for maintenance or an overnight stop.
617
- - **Pause** (`omp-conductor pause`) stops new claims only. Active workers finish,
618
- and an armed heartbeat continues its triage and reporting duties.
619
- - **Disarm** (`omp-conductor disarm`) removes the marker named by
620
- `.conductor-tick.json`. The heartbeat stops, but the daemon can still claim
621
- work. Use `hold`, not `disarm`, when no orchestrator can supervise dispatch.
622
- - **The channel gate can stop ticks.** Every fleet heartbeat config names the
623
- Telegram `access.json`. Each tick requires `enabled: true` and exactly one
624
- paired owner. Missing, invalid, disabled, empty, or ambiguous access stops the
625
- heartbeat.
626
-
627
- ---
628
-
629
- ## Step 8 — build the code graph, if they said yes to it
630
-
631
- Only if the wizard's code-graph question was answered yes. It is optional, and a
632
- fleet without it works exactly as it did before — but it is the cheapest single
633
- improvement to how far a worker gets, so lead with the number: **workers spend
634
- most of a run finding code, not changing it.** Measured on the reference fleet, a
635
- run typically spends 30–62 `read` and 32–69 `bash` calls against 9–24 edits, and
636
- the runs that hit the turns cap hit it with the work unfinished. A graph answers
637
- "who calls this" in one call instead of twenty greps.
638
-
639
- Say the thing operators get wrong before you run anything: **the indexed
640
- directories are conductor's, not theirs.** Three candidates and only one works.
641
-
642
- - A worker's **worktree** cannot be indexed usefully — an index is keyed by the
643
- realpath it was built from, so a throwaway `worktrees/<issue>` path is always an
644
- empty project. That is why the brief hands the worker an absolute path instead.
645
- - Their **own checkout** must not be indexed. Refreshing means resetting to the
646
- default branch, which in a directory they work in either destroys uncommitted
647
- work or indexes the feature branch they left checked out.
648
- - Conductor's **mirrors** are bare. No working tree, nothing to index.
649
-
650
- So each `graphProject` is a fourth thing: a disposable clone that exists only to
651
- be indexed, pinned to the repo's default branch, never edited by a human. Say that
652
- out loud, because an operator who points it at `~/projects/<repo>` to "save disk"
653
- has armed something that will one day `git reset --hard` over their work.
654
-
655
- Two host prerequisites come before any of that, and neither is conductor's to
656
- install. `graph-setup` reports both as step 0, so run it first and read that
657
- block before running anything else.
658
-
659
- - **The indexer must be on PATH.** `codebase-memory-mcp` is a separate project
660
- ([source](https://github.com/DeusData/codebase-memory-mcp)); the package never
661
- installs, spawns or depends on it. A host without it gets command-not-found
662
- partway down the plan.
663
- - **It must be mounted as an MCP server for sessions**, in `~/.omp/agent/mcp.json`
664
- on the account the daemon runs as. This is the one that bites, because it fails
665
- *silently*: indexing succeeds, the databases are real and correct, and worker
666
- sessions have no graph tools at all — so every worker quietly greps and the
667
- whole thing looks like it simply did not help. `graph-setup` prints the exact
668
- entry to paste, pointed at the binary it found.
669
-
670
- Check the mount on the daemon's account, not yours — a per-user config that is
671
- present for the operator and absent for the service account looks fine from the
672
- shell they are typing in.
673
-
674
- Then, on the host that runs the daemon:
675
-
676
- ```bash
677
- omp-conductor graph-setup # read-only: prints the whole plan
678
- ```
679
-
680
- Walk them through what it printed rather than pasting it silently. It has three
681
- parts, and each one is a decision they can still refuse: a `git clone` per missing
682
- clone, an index command per repo (minutes each — run them now, or the first worker
683
- queries an empty graph), and a `cbm-reindex.service` + `cbm-reindex.timer` pair
684
- derived from their own repos and branches. Then:
685
-
686
- ```bash
687
- omp-conductor graph-setup --write # stages the script and the two units (no root)
688
- ```
689
-
690
- **Have them run this as the account the fleet runs as, not under `sudo`.** The
691
- command refuses sudo outright, and that refusal is the whole point: config path,
692
- state directory, `~/.cache` and the unit's own `User=` all resolve per-account,
693
- so a root run stages a timer that goes green while writing indexes into
694
- `/root/.cache` where no worker session looks. It is silent, and it looks exactly
695
- like the feature not helping.
696
-
697
- `--write` stages all three files in the state directory and prints the two `sudo`
698
- lines that install and enable them — installing units is the only privileged
699
- step, and it never runs `systemctl` itself. Have them start the service once by
700
- hand and read the result: a first real run is where a wrong branch or a missing
701
- clone shows up, and the unit is written to fail loudly rather than index a stale
702
- tree.
703
-
704
- Two things to leave them with:
705
-
706
- - **A timer, not the server's own watcher.** That watcher lives inside a connected
707
- MCP session and dies with it, so nothing a worker mounts keeps anything fresh.
708
- If the timer is not enabled, the graph decays and no one is told.
709
- - **The graph is a snapshot, and the brief says so.** Workers are told to orient
710
- with it and then read the real file before editing, because the index is the
711
- default branch at the last reindex — not their branch, and not their edits.
712
-
713
- Verify before moving on: `codebase-memory-mcp cli list_projects` must show one
714
- entry per repo whose `root_path` is exactly the configured `graphProject`. That
715
- match is the whole contract — the worker brief tells the session to find its
716
- project by that path, so a mismatch means a silent fallback to grep.
717
-
718
- ---
719
-
720
- ## Step 9 — hand over the learning loop
721
-
722
- Finish by telling the operator the truth about what they just wrote:
723
-
724
- **A brief converges from operation, not from an interview.** What you produced
725
- today is a good first draft, and it is wrong in ways neither of you can see yet.
726
- Real deployments diverge from the template exactly where the operator learns
727
- something: a reporting scope that turned out too loud, a release boundary drawn in
728
- the wrong place, an escalation that should have been a digest line, a hard
729
- boundary nobody thought to name until an agent walked into it.
730
-
731
- The brief closes that gap itself now. Point them at its **Learning loop** section
732
- and say what it means in practice:
733
-
734
- - **Their corrections are the trigger.** When they tell the orchestrator to work
735
- differently — mid-flight, in a reply to an escalation, anywhere — it drafts the
736
- matching edit to its own brief instead of just complying once.
737
- - **Approval is a Telegram yes/no.** The proposal arrives as one question carrying
738
- the exact diff: the current lines, then the replacement. Yes applies it. No, or
739
- no answer at all, drops it, and it does not raise that amendment again.
740
- - **Two things it will never do:** propose relaxing **Hard boundaries** (that
741
- section changes only when they hand-edit it), or interrupt a tick's duties to
742
- ask.
743
- - **Every applied amendment is logged** as one line under **Amendments** at the
744
- bottom of the brief — date, trigger, summary. That list is the honest record of
745
- where this interview was wrong, and it accumulates without anyone scheduling it.
746
-
747
- That is the whole handover. Convergence is now the loop's job rather than a habit
748
- the operator has to keep.