omp-conductor 0.3.5 → 0.3.6

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
@@ -38,7 +38,7 @@ The package ships three deployables, plus one skill:
38
38
  | --- | --- | --- |
39
39
  | omp plugin | `/conductor` slash command | Inspect and arm the conductor from inside an omp session: dry-run the queue, read status, pause, resume. |
40
40
  | Standalone daemon | `omp-conductor` binary | The dispatch loop, managed as a background process (`start` / `stop` / `restart`) with a `/healthz` endpoint for a supervisor. |
41
- | Orchestrator heartbeat | omp extension, activated by `.conductor-tick.json` | Prompts a 24/7 orchestrator session on a fixed interval so its standing loop actually runs, and marks the session stalled when its prompts stop being consumed. Inert in every other session. See [Orchestrator tick](#orchestrator-tick). |
41
+ | Orchestrator heartbeat | omp extension, activated by `.conductor-tick.json` | Prompts a 24/7 orchestrator session on a fixed interval so its standing loop actually runs, and marks the session stalled when its prompts stop being consumed. Inert in every other session — including a second session opened in the fleet's own directory. See [Orchestrator tick](#orchestrator-tick). |
42
42
  | Onboarding skill | `skill://conductor-onboarding` | Directs an omp session to interview you, read your repos for real CI gates, and tailor `ORCHESTRATOR.md` — then finish through the wizard. Discovered automatically once the plugin is installed. See [Onboarding](#onboarding). |
43
43
 
44
44
  The first two are thin wrappers over the same `daemon.ts`, so the plugin and the
@@ -170,7 +170,7 @@ Onboarding this package has two layers, and installing it gives you both.
170
170
 
171
171
  | Layer | What it is | What it owns |
172
172
  | --- | --- | --- |
173
- | **`/conductor setup`** | The deterministic wizard. Closed questions, a label plan, a dry run, one confirm. | **Mechanical config.** It is the only thing that writes `config.json`, and it mutates nothing before you confirm. |
173
+ | **`/conductor setup`** | The deterministic wizard. Closed questions, a label plan, a dry run, one confirm. On a project it already knows, it offers to amend one area instead of re-asking everything — see [Changing one setting](#changing-one-setting). | **Mechanical config.** It is the only thing that writes `config.json`, and it mutates nothing before you confirm. |
174
174
  | **`skill://conductor-onboarding`** | A skill bundled in this package (`skills/conductor-onboarding/SKILL.md`), discovered automatically by any omp session once the plugin is installed. | **Brief authoring.** The judgement the wizard cannot prompt for. |
175
175
 
176
176
  The split exists because the two halves fail differently. A wrong config value is
@@ -215,6 +215,59 @@ turned on you can also invoke it directly:
215
215
  Nothing about the wizard changes: `/conductor setup` on its own remains a
216
216
  complete, supported path, and the brief it renders is safe unedited.
217
217
 
218
+ ### Changing one setting
219
+
220
+ `config.json` is wizard-written, so changing a value means running the wizard —
221
+ and a wizard that re-asks twenty questions to add one key is a wizard people edit
222
+ the file behind instead. So a re-run against a project that is already configured
223
+ opens with one question:
224
+
225
+ ```text
226
+ "veltro" is already configured — what would you like to do?
227
+ > Change one area
228
+ asks one area's questions; every other answer is carried through from the saved config
229
+ Walk every question again
230
+ the full interview, every prompt pre-filled with what is configured now
231
+ ```
232
+
233
+ Amending is the default. Pick it and the eight areas are listed with what each one
234
+ says right now, so the row you want is the row you can see:
235
+
236
+ ```text
237
+ Which area? Each row shows what it says now
238
+ tracker & repos — veltrosecurity/veltro, queue "ready-for-agent", "repo:" → veltro, chad, warden, vectorflow
239
+ gates — veltro: none; chad: ruff check . @ backend, pnpm lint @ frontend; warden: ruff check . @ backen…
240
+ caps & worker model — 2 workers, 120 turns, 90m, $25/day, 2 attempts (all defaults) — harness default model
241
+ code graph — not configured — workers grep
242
+ authority — merge=orchestrator, release=orchestrator
243
+ escalation & triage — tier 2 pages Telegram 8236653927, comments too, triage external
244
+ reporting scope — material — escalations, plus green PRs, second failures, and anything that stops the fleet
245
+ orchestrator brief — none at /root/.omp/conductor/worktrees/ORCHESTRATOR.md
246
+ ```
247
+
248
+ Only that area's questions are asked. Every other answer is read back out of
249
+ `config.json` and written again unchanged — the same answers, the same builder,
250
+ the same single confirm, so there is still exactly one thing in this package that
251
+ writes a config, and it still writes nothing before you agree. The consent screen
252
+ leads with the delta and then shows the whole project as it would be written:
253
+
254
+ ```text
255
+ amending code graph — project veltro
256
+ was not configured — workers grep
257
+ now /root/.cache/conductor-graph/veltrosecurity — 4 clone(s): veltro, chad, warden, vectorflow
258
+ carried over tracker & repos, gates, caps & worker model, authority, escalation & triage, reporting scope, orchestrator brief
259
+ read back from /root/.omp/conductor/config.json and rewritten unchanged
260
+ ```
261
+
262
+ A first run, or a project name this config has never seen, never sees either
263
+ question: there is nothing to amend, so it is the full interview exactly as
264
+ before. Choosing *Walk every question again* is also unchanged — every prompt
265
+ pre-filled with what is configured, Enter to keep it — with one wrinkle worth
266
+ knowing: the two authority confirms and the orchestrator-session confirm cannot
267
+ start on "yes", so Entering through the full interview **revokes** a delegation
268
+ rather than renewing it. Amending the `authority` area names the current grant in
269
+ the question, which is the safer way to leave one alone.
270
+
218
271
  ### Keeping a brief current
219
272
 
220
273
  Upgrading the package does not upgrade a brief you are already running, and it is
@@ -552,7 +605,10 @@ Say yes and the wizard asks for one root, then derives one clone per routed repo
552
605
  underneath it (default `~/.cache/conductor-graph/<org>/<repo>`) and writes it to
553
606
  each repo's [`graphProject`](#configuration). Nothing else changes: this package
554
607
  never runs an indexer, never imports one, and behaves identically with the graph
555
- server absent — dispatch, caps and escalation do not know it exists.
608
+ server absent — dispatch, caps and escalation do not know it exists. On a fleet
609
+ that was configured before this key existed, `/conductor setup` and the `code
610
+ graph` area add it in two prompts — see
611
+ [Changing one setting](#changing-one-setting).
556
612
 
557
613
  ### Why the clone, and not your checkout or the worktree
558
614
 
@@ -686,6 +742,10 @@ The file is validated on every read. A malformed config produces one readable er
686
742
  listing every fault, and the daemon refuses to start rather than running with half
687
743
  a project.
688
744
 
745
+ `/conductor setup` is the only thing here that writes this file, and on a project
746
+ it already knows it can rewrite one area of it without re-asking the rest — see
747
+ [Changing one setting](#changing-one-setting).
748
+
689
749
  `version` is `2`. A `version: 1` file still loads: caps it names that this build no
690
750
  longer enforces are dropped rather than treated as typos, and the next save writes
691
751
  it back as `2`. In a `version: 2` file an unrecognised cap key **is** an error,
@@ -811,7 +871,8 @@ in the orchestrator's working directory:
811
871
  | `intervalSeconds` | yes | — | Whole seconds between ticks, minimum `60`. A tick costs a full turn of a frontier model, so a sub-minute period is refused rather than obeyed. |
812
872
  | `armedFile` | no | none — the gate passes | Path to the arm marker. A tick does nothing while the file is missing. Relative paths resolve against the session cwd, so `state/armed` means `<cwd>/state/armed`. |
813
873
  | `accessFile` | no | none — the gate passes | Path to the Telegram bridge's `access.json`. Every tick re-reads it and requires `enabled: true` with exactly one entry in `allowFrom`. Relative paths resolve against the session cwd. **Configure this on any fleet deploy** — see below. |
814
- | `message` | no | `Tick <ISO timestamp>: re-read <workspaceRoot>/ORCHESTRATOR.md from disk, then run your standing loop from it.`, then the `reporting.scope` line, then the delivery rule | Sent verbatim when set — and then it owns the whole contract: neither the scope line nor the delivery rule is appended to a prompt you wrote yourself. Re-read from disk on **every** tick, so rewording it binds the next heartbeat instead of waiting for a session restart; a re-read that fails — caught mid-edit, removed, or invalid — keeps the value read at session start rather than stopping the heartbeat. `intervalSeconds` is *not* re-read: rescheduling a live timer still needs a restart. The default *orders* the session to re-read its brief, naming the path resolved from the conductor config a 24/7 session otherwise acts on the copy loaded at its start (resume included), and an amendment added between ticks never binds — and carries the timestamp, which makes two consecutive ticks distinguishable in the session log. |
874
+ | `message` | no | `Tick <ISO timestamp>: re-read <workspaceRoot>/ORCHESTRATOR.md from disk, then run your standing loop from it.`, then the `reporting.scope` line, then the delivery rule | Sent verbatim when set — and then it owns the whole contract: neither the scope line nor the delivery rule is appended to a prompt you wrote yourself. Re-read from disk on **every** tick, so rewording it binds the next heartbeat instead of waiting for a session restart; a re-read that fails — caught mid-edit, removed, or invalid — keeps the value read at session start rather than stopping the heartbeat. `intervalSeconds` is *not* re-read: rescheduling a live timer still needs a restart. The default *orders* the session to re-read its brief, naming the path resolved from the project's `workspaceRoot`, because a standing prompt drifts out of a long-lived session's context while the file on disk does not. |
875
+ | `agentName` | no | `fleet` | The herdr agent name the orchestrator's pane is registered under. Under herdr this is the whole of the identity check below, and the default matches `AGENT_NAME=${AGENT_NAME:-fleet}` in the recovery plugin's `recover.sh`, so both halves key on one name. Rename the agent and set this to match. |
815
876
 
816
877
  A tick sends one message (`customType` `omp-conductor.tick`, attributed to the
817
878
  user): the standing-loop prompt, the one constraint line the project's
@@ -825,8 +886,6 @@ watched succeed. The tick starts a turn if the session is idle; while a turn is
825
886
  streaming it is queued as a follow-up and consumed when that turn ends.
826
887
  It sends **nothing** when:
827
888
 
828
- - `/conductor pause` (or `omp-conductor pause`) holds the pause flag, the same
829
- flag the dispatch loop reads, so pausing the fleet pauses its heartbeat;
830
889
  - `armedFile` is configured and missing;
831
890
  - `accessFile` is configured and the escalation channel is not verifiably up;
832
891
  - an earlier tick is still queued. Ticks coalesce rather than stack, so a slow
@@ -834,6 +893,53 @@ It sends **nothing** when:
834
893
  in a row are the signal that the session is not slow but wedged, which is
835
894
  what the [stall marker](#a-wedged-session-and-the-marker-that-notices) is for.
836
895
 
896
+ ### One session per directory ticks, and it says which
897
+
898
+ Activation is a property of the *directory*, so before it arms anything the
899
+ heartbeat asks whether this session is the orchestrator or merely a session
900
+ standing in its directory. It has to: opening a second omp session in the fleet's
901
+ cwd — a shell to read state, say — used to arm a second heartbeat that prompted
902
+ *that* session with the standing loop, and with
903
+ [`authority`](#configuration) delegated it would consider itself entitled to
904
+ merge PRs and cut releases. Two brains, one queue, and nothing in the log to tell
905
+ them apart.
906
+
907
+ **Under herdr** (`HERDR_ENV=1` with a `HERDR_PANE_ID`), the answer is the pane's
908
+ registered agent name: the heartbeat asks `herdr agent list` for the entry whose
909
+ `pane_id` is this pane's and ticks only when its `name` equals `agentName`. Fleetness
910
+ is the *session* — every pane in it shares `HERDR_SESSION` and the cwd — and
911
+ herdr's `agent` field is the *runtime*, `omp` for the orchestrator and for the
912
+ shell beside it, so neither can tell them apart. The registered name can, it is
913
+ what `herdr agent start fleet --kind omp --pane <id>` sets, and it is the same
914
+ identity the recovery plugin keys on. A pane with a different name, or no name at
915
+ all, stays inert.
916
+
917
+ **Without herdr**, the session claims the directory in a sibling
918
+ `.conductor-tick-owner.json` (pid, session file, claim time) and ticks only while
919
+ it is the live claimant. Liveness is a **pid check, never a timestamp**: a crashed
920
+ orchestrator's claim is reclaimed by the next session rather than wedging the
921
+ fleet until somebody deletes a file, and a slow-but-running orchestrator never
922
+ loses its claim to a lease that expired.
923
+
924
+ Declining is logged once, at session start, naming the holder — which is the whole
925
+ point, because the original failure was that the second ticker was
926
+ indistinguishable from the first:
927
+
928
+ ```text
929
+ [omp-conductor] orchestrator tick inactive: pane w1:p1 (agent "fleet") owns the fleet tick here — this session will not tick
930
+ [omp-conductor] orchestrator tick inactive: this pane is agent "scratch", not the fleet agent "fleet" — this session will not tick
931
+ [omp-conductor] orchestrator tick inactive: pid 4147344 (claimed 2026-08-07T07:55:36.001Z, session …/fleet.jsonl) owns the fleet tick in /root/.omp/conductor — this session will not tick
932
+ ```
933
+
934
+ A `herdr agent list` that does not answer also declines, for the same reason the
935
+ escalation channel fails closed: under herdr this session is one pane of several
936
+ in that directory, and an unproven identity is exactly the case the check exists
937
+ for. That includes `herdr` not being on the session's `PATH` — worth checking on a
938
+ fleet host, where the orchestrator's environment comes from a unit file rather
939
+ than a login shell — and `HERDR_BIN_PATH` names the binary when it is not, the
940
+ same escape hatch the recovery plugin's `recover.sh` has. On a host with no herdr
941
+ and no prior claimant — the ordinary single-session case — nothing changes.
942
+
837
943
  ### The escalation channel is a gate, and it fails closed
838
944
 
839
945
  Unattended dispatch is only defensible while a tier-2 escalation can reach a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-conductor",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
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.",
@@ -176,12 +176,23 @@ Not yours to relax:
176
176
  it, never you. A fleet that patches its own dispatcher is a fleet whose
177
177
  behavior nobody can reproduce, and the next install silently reverts the
178
178
  patch, which is worse than never having made it.
179
+ - **A worker's branch is theirs; the PR is yours to steer.** Never `git checkout`,
180
+ commit or push inside a worker's worktree, never cut a branch from one, never
181
+ force-push or rewrite history anywhere, and never author a commit under an
182
+ invented identity. But `gh pr update-branch` **is** yours to run and is the
183
+ sanctioned remedy for a green PR that has fallen behind: it is a server-side
184
+ merge of the base into the head, it destroys nothing, it rewrites nothing, and
185
+ under a ruleset that requires branches to be up to date it is the only way a
186
+ correct PR ever merges. Closing a green PR to make a fresh worker redo the
187
+ merge costs a whole attempt to buy what one command does in minutes — do not.
188
+ Bypassing branch protection with admin rights is still forbidden; updating the
189
+ branch is how you satisfy it, not how you dodge it.
179
190
 
180
191
  **Your own** merge and release authority is not decided here. It is whatever your
181
192
  operator granted at setup time, stated in the first paragraph of **Releases**
182
193
  below; ungranted, it is none — you do not merge, tag, publish or deploy either.
183
194
  That grant is a deliberate operator decision, changed by re-running setup rather
184
- than by editing this file. The four boundaries above are not.
195
+ than by editing this file. The five boundaries above are not.
185
196
 
186
197
  ## Learning loop
187
198
 
@@ -248,7 +259,12 @@ eventually publishes something at 03:00. Spell out all seven.
248
259
  - **Whether you may merge**, and which PRs. Release work usually needs it, and a
249
260
  procedure that has you landing a PR without saying so leaves you inferring
250
261
  permission. Note that **one at a time, re-checked against the base branch** binds
251
- you here exactly as it binds a human; that part is a hard boundary.
262
+ you here exactly as it binds a human; that part is a hard boundary. When that
263
+ re-check finds a green PR that is merely *behind*, the answer is
264
+ `gh pr update-branch` and a wait for the fresh run — never closing it, and
265
+ never an admin bypass. Merging promptly is itself the remedy that stops the
266
+ next PR falling behind: a queue of green PRs left unmerged makes each one
267
+ stale in turn.
252
268
  - **The release authority**, named. Which workflow or command ships this repo, and
253
269
  how it is invoked. If it is a protected or dispatchable workflow, your
254
270
  instruction is to *dispatch it and verify the run*. You never reproduce what it