omp-conductor 0.3.21 → 0.3.23

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
@@ -165,6 +165,16 @@ Also required on the host:
165
165
  writes `access.json`. The fleet heartbeat also reads that file and refuses to
166
166
  tick unless exactly one owner is paired, on the grounds that unattended
167
167
  dispatch is only defensible while a tier-2 page can reach a person.
168
+ - **Approving a Learning-loop amendment from a heartbeat tick** needs one more
169
+ setting than pairing: a notify destination. `/telegram notify` writes
170
+ `notifyMode` and `notifyChat` into the same `access.json`; `/telegram topics`
171
+ does it with a forum topic instead. omp-telegram mounts its `telegram_ask`
172
+ tool only for a turn that resolves a notify target, and a locally injected
173
+ tick resolves one only through that setting — so without it the orchestrator
174
+ can page you but cannot put a yes/no question in front of you, which is the
175
+ one thing the Learning loop's approval step requires. `omp-conductor status`
176
+ reports this on the `telegram` row, and a tick that finds the tool missing
177
+ says so in its own prompt and falls back to `telegram_send`.
168
178
 
169
179
  With neither, tier 2 degrades to a comment on the issue. Nothing is broken in
170
180
  that configuration: it is supported, just slower to reach you.
@@ -462,22 +472,34 @@ Four control planes used to answer "stop" differently. The package verbs:
462
472
 
463
473
  `omp-conductor board [--project NAME]` opens the same facts as a live terminal
464
474
  kanban instead of a scrolling wall of status text. Its columns are Queue,
465
- Claimed, Running, Green, Blocked, Failed, and Merged. Queue cards are the
466
- bounded per-issue hold sample recorded by the latest dispatch; the Queue count
467
- is the authoritative ready count even when there are more ready issues than
468
- sampled cards. Run columns show the newest attempt for each issue. Merged keeps
469
- only the last 24 hours so the board stays operational rather than becoming an
470
- analytics archive.
475
+ Claimed, Running, Green, Blocked, Failed, Orphaned, Merged, and History.
476
+
477
+ Every column is a claim about an issue's state **now**, and each issue appears
478
+ in exactly one of them. Claimed, Running, and Green come from the store, because
479
+ a live worker or an open pull request is the most current fact there is. Every
480
+ stopped column comes from the labels the tracker carries right now: Queue is the
481
+ queue label plus the latest tick's admission holds, Failed is the failed state
482
+ label, Blocked is the blocked state label, and Orphaned is an issue the
483
+ in-progress label still claims with no worker running it — a protected orphan
484
+ whose worktree is kept because it may hold uncommitted work. So a requeued issue
485
+ is queue work even when its last attempt failed, and a closed issue is not
486
+ active work at all. Merged and History are the two backward-looking columns,
487
+ both bounded to the last 24 hours and neither one an action list: History holds
488
+ recently finished runs that no current label claims, so the board stays
489
+ operational rather than becoming an analytics archive.
471
490
 
472
491
  The board refreshes run, spend, turn, and dispatch values from SQLite every
473
- second. It refreshes the slower daemon, Herdr, Telegram, and code-graph health
474
- layer every ten seconds or immediately with `r`. Use arrow keys or `h/j/k/l` to
475
- select a card, `Enter` to inspect and follow its worker transcript, `u` to run
476
- the normal unblock workflow, `i` / `p` to open the issue / pull request, `?` for
477
- help, and `q`, `Esc`, or `Ctrl-C` to go back and quit. Narrow terminals show a
478
- sliding subset of columns around the selection; terminals below 50×20 get a
479
- single resize instruction instead of a broken layout. The board is read-only
480
- except for the explicit `u` action: it never claims work or changes stages.
492
+ second. It refreshes the slower daemon, Herdr, Telegram, code-graph, and tracker
493
+ label layer every ten seconds or immediately with `r`; the header reports
494
+ `tracker ok`, or how stale the label read is and why, so an empty Queue is never
495
+ mistaken for an empty queue. Use arrow keys or `h/j/k/l` to select a card,
496
+ `Enter` to inspect and follow its worker transcript, `u` to run the normal
497
+ unblock workflow on a Blocked, Failed, or Orphaned card, `i` / `p` to open the
498
+ issue / pull request, `?` for help, and `q`, `Esc`, or `Ctrl-C` to go back and
499
+ quit. Narrow terminals show a sliding subset of columns around the selection;
500
+ terminals below 50×20 get a single resize instruction instead of a broken
501
+ layout. The board is read-only except for the explicit `u` action: it never
502
+ claims work or changes stages.
481
503
 
482
504
  `halt --pane` is **fail-closed**: it exits `0` only when the conductor agent is
483
505
  *proven* gone. It writes the recovery pin first, so a failed stop still cannot be
@@ -518,7 +540,8 @@ Per tick, for the daemon's project:
518
540
  the independent head/check verification; green → `pushed-green`, red →
519
541
  `failed`, and still pending stays occupied. For every verified
520
542
  `pushed-green` run, ask what became of its PR. Merged → `merged`; closed
521
- without merging → `failed`. Unknown answers leave the row unchanged. This
543
+ without merging → `failed`. Unknown answers leave the row unchanged. Every row
544
+ that settles also loses its `agent:in-progress` label. This
522
545
  maintenance runs even while dispatch is paused or workers are active, so
523
546
  status converges on the five-minute tick cadence. It also runs above admission
524
547
  so a row settled here frees its issue in the same tick. See
@@ -548,8 +571,12 @@ Per tick, for the daemon's project:
548
571
  survived step 8 — so at most one API call per free slot, never one per queued
549
572
  issue — the daemon asks whether an **open** PR already closes it. An open PR
550
573
  normally holds the issue. One narrow exception permits a routed continuation:
551
- the PR URL must match the latest terminal run's retained PR exactly. Drafts
552
- count because their branch can hold the only copy of the work.
574
+ the latest run must be terminal, and the open PR must be that run's retained
575
+ work either the PR URL it recorded or a PR opened on the branch it retained.
576
+ The branch half matters because a run can be cap-killed before its worker ever
577
+ opens a PR, leaving a retained branch and no recorded URL; a PR pushed to that
578
+ branch afterwards is still the continuation target. Drafts count because their
579
+ branch can hold the only copy of the work.
553
580
  The tracker also finds work missing from a new, moved, restored, or cleared
554
581
  store. If the check fails, the candidate is **held**, not admitted, and
555
582
  retried next tick: the cost of holding is five minutes, the cost of admitting
@@ -583,13 +610,14 @@ Then, per admitted issue:
583
610
  | Outcome | Labels | Worktree | Escalation |
584
611
  | --- | --- | --- | --- |
585
612
  | `pushed-pending` | `agent:in-progress` stays while the daemon rechecks GitHub | removed | none |
586
- | `pushed-green` | `agent:in-progress` stays until the merge closes the issue | removed | none |
613
+ | `pushed-green` | `agent:in-progress` stays while the PR is open | removed | none |
587
614
  | `blocked` | swapped to `agent:blocked` | removed | Tier 1 |
588
615
  | `failed` / `killed` | swapped to `agent:failed` | dirty tree committed to the branch, then retained until the PR or issue is terminal | Tier 1 |
589
616
  | unexpected error | swapped to `agent:failed` | same | Tier 1 |
590
617
 
591
618
  `pushed-pending` and `pushed-green` are not the end of the row: later ticks
592
- verify outstanding checks and settle the PR once it resolves. See
619
+ verify outstanding checks and settle the PR once it resolves, and a row that
620
+ settles gives up its `agent:in-progress` label. See
593
621
  [what settles a green PR](#what-settles-a-green-pr).
594
622
 
595
623
  Label swaps add the new label before removing the old one: the reverse order
@@ -683,11 +711,15 @@ every pushed PR it is still holding:
683
711
  | could not be determined | unchanged | A flaky network, a revoked token, a deleted PR. An unknown answer never settles a row; the next tick asks again for free. |
684
712
 
685
713
  Run history is untouched. A PR closed without merging becomes a concrete failed
686
- attempt; a merge does not spend failure or continuation budget. A merge normally
687
- closes the issue, and a human who closed a PR is already looking at it, so what
688
- an issue's labels should say next remains the orchestrator's drain-duty
689
- judgement. One unreachable PR costs its own row and nothing else; the rest of
690
- the sweep still settles.
714
+ attempt; a merge does not spend failure or continuation budget. A settled row also
715
+ loses `agent:in-progress` from its issue: the row transition and the label are one
716
+ fact, and a terminal answer about the PR proves no worker process owns the issue,
717
+ so the duplicate-dispatch guard it exists for is spent. Rows that did not settle
718
+ keep the label, and a tracker that refuses the removal is logged rather than
719
+ allowed to abort the sweep — `omp-conductor unblock` clears it by hand in that
720
+ rare case. Anything beyond that one release — a re-queue, a `blocked` marker —
721
+ is still the orchestrator's drain-duty judgement. One unreachable PR costs its own
722
+ row and nothing else; the rest of the sweep still settles.
691
723
 
692
724
  Until this existed, nothing ever revisited a `pushed-green` row: the startup
693
725
  reconciler only settles rows that held a process, and `merged` went unwritten. On
@@ -696,6 +728,12 @@ merged and whose issues were all closed, through two daemon restarts — and bec
696
728
  the active set *is* the busy set, those three issues were permanently unclaimable.
697
729
  A status page that has stopped being evidence is worse than no status page.
698
730
 
731
+ The label half of that outlived the row half by two days. On 2026-08-09 a merged
732
+ PR and a closed-unmerged one both settled their rows correctly and both left their
733
+ issues carrying `agent:in-progress`, which eligibility reads as "a worker owns
734
+ this" — with the brief forbidding the orchestrator from editing a state label and
735
+ `unblock` declining to clear that one, neither issue could ever be claimed again.
736
+
699
737
  ### Continuation runs
700
738
 
701
739
  When a worktree is provisioned onto a branch that already exists in the mirror
@@ -960,7 +998,9 @@ label on the issue, and eligibility reads any state label as disqualifying, so a
960
998
  answered issue that keeps one is never re-claimed and the answer is inert — nothing
961
999
  fails, the issue just stops existing as far as dispatch is concerned.
962
1000
  [`omp-conductor unblock <issue>`](#cli-reference) is the way back: it clears the
963
- label through the same tracker the dispatcher writes with. The brief tells the
1001
+ label through the same tracker the dispatcher writes with, including
1002
+ `agent:in-progress` when the newest recorded run is terminal, since a terminal row
1003
+ is proof the worker process is gone. The brief tells the
964
1004
  orchestrator to run that verb rather than edit the label itself, and that is not a
965
1005
  formality — orphan detection works by comparing `agent:in-progress` labels against
966
1006
  live runs, and it is only trustworthy while every state label on the tracker was
@@ -1350,7 +1390,7 @@ omp-conductor help
1350
1390
  | `restart` | Prefer `systemctl restart` when the unit owns the live pid so the replacement stays supervised; otherwise `stop` then `start`, inheriting the running daemon's port and project unless a flag overrides them. The new process **salvages dirty live worktrees before orphaning** those rows — see [Deploying a new package onto a busy fleet](#deploying-a-new-package-onto-a-busy-fleet). |
1351
1391
  | `upgrade [--to VERSION] [--project NAME]` | Deterministically update the Bun-global CLI, omp plugin, Herdr recovery plugin, and managed brief as one release. Resolves the npm version and exact `gitHead`, pauses only new claims, drains active workers, installs all surfaces, reloads Herdr and the daemon, waits for pane recovery, verifies identities and fleet health twice, then restores the original dispatch state. A no-op when already current. Failure leaves dispatch paused. Must run outside a Herdr-managed session. |
1352
1392
  | `status [--project NAME]` | Layered fleet report first: `dispatch` / `ticks` / next scheduled tick / `pane` / `recovery` / `herdr` / `telegram` / optional `code graph` / `daemon`, then the project body. The project body includes the latest completed dispatch timestamp, ready/routed/admitted counts, and bounded hold groups; API failures are marked `DEGRADED` so queue starvation cannot look idle. The next tick comes from the live heartbeat process, not a guess from log timestamps. Telegram health uses `getMe` to prove API authentication without sending a message and reports inbound bridge configuration separately. Configured graphs report prerequisites, indexed repos, timer state, and refresh freshness without blocking dispatch. The daemon block includes `rss` from `/healthz`; live workers add a busy-deploy warning. A `.conductor-stalled` marker adds an `orchestrator STALLED since …` line. |
1353
- | `board [--project NAME]` | Live keyboard-driven kanban over the same SQLite and `/healthz` truth as `status`: Queue, Claimed, Running, Green, Blocked, Failed, and the last 24 hours of Merged. Refreshes run/spend/turn values every second and slower health every ten seconds. `Enter` follows the selected transcript in place; `u` invokes the existing unblock workflow; `i` / `p` open the issue / PR; `r` refreshes health; `?` shows all keys. Requires an interactive terminal of at least 50×20. |
1393
+ | `board [--project NAME]` | Live keyboard-driven kanban over the same SQLite and `/healthz` truth as `status`, plus the tracker's current labels: Queue, Claimed, Running, Green, Blocked, Failed, Orphaned, and the last 24 hours of Merged and History. Columns are mutually exclusive and describe current state, not the newest run row, so a requeued issue is queued rather than failed and a closed issue is neither. Refreshes run/spend/turn values every second, and health plus the label read every ten seconds. `Enter` follows the selected transcript in place; `u` invokes the existing unblock workflow on a Blocked, Failed, or Orphaned card; `i` / `p` open the issue / PR; `r` refreshes health; `?` shows all keys. Requires an interactive terminal of at least 50×20. |
1354
1394
  | `hold [--project NAME]` | Soft stop: pause claiming **and** disarm ticks. Daemon and pane stay up. Prefer this over `pause` when the intent is "stop the conductor" without killing processes. See [Stop the conductor](#stop-the-conductor-hold--halt). |
1355
1395
  | `halt [--pane] [--project NAME]` | `hold`, then stop the dispatch daemon (systemctl-aware). Pane stays up unless `--pane` is passed. `halt --pane` also pins herdr-conductor recovery off for the conductor agent only — it does **not** stop `herdr-fleet.service` or any other herdr session. Fail-closed: exits nonzero unless the agent is proven gone. |
1356
1396
  | `arm [--project NAME]` | Proof-gated: send a Telegram challenge and write the arm marker only after your reply appears as a user turn in the orchestrator transcript. Never auto-armed by `resume` / `hold`. |
@@ -1358,7 +1398,7 @@ omp-conductor help
1358
1398
  | `release-pane [--project NAME]` | Clear the `halt --pane` recovery pin so herdr-conductor may resume the fleet agent again. |
1359
1399
  | `tail <issue>` | Follow the newest run for that issue: the worker's assistant text as `assistant: …` and each tool it calls as `tool: <name>`, printed as they land. Workers are omp sessions inside the daemon rather than terminals, so this is the only way to watch one live — a herdr pane running it becomes an observation window. Starts from the top of the transcript, not the end, so attaching to a run that is already ten turns in shows those ten turns. Exits `1` with `no run recorded for #N` when the issue has never been dispatched, or `no transcript yet (state: …)` when the attempt has not opened one. Otherwise it runs until `Ctrl-C`, or until the run has finished and its transcript has been silent for five seconds, and prints `run ended: <state>`. |
1360
1400
  | `extend <issue> --turns N [--project NAME]` | Monotonically raise that live worker's effective turn ceiling through its owning daemon. The current omp session keeps running; no restart or continuation is created. The daemon persists the new ceiling for `status` and rejects missing, settled, cap-killed, equal, or lower requests instead of implying that an immutable session changed. |
1361
- | `unblock <issue>` | Remove that issue's `blocked` and `failed` labels so an answered escalation can be claimed again. `agent:in-progress` is never touched. Run history remains intact: blocks consume the independent continuation budget, not failed implementation attempts. The output reports both budgets and warns when either will make the next tick escalate instead of dispatch. Exits `2` when the issue number is missing or malformed. |
1401
+ | `unblock <issue>` | Remove that issue's `blocked` and `failed` labels so an answered escalation can be claimed again. `agent:in-progress` comes off too, but only when the newest recorded run is terminal — that row is the proof no worker still owns the issue, so a live run keeps the label, and so does an issue with no run row at all. Run history remains intact: blocks consume the independent continuation budget, not failed implementation attempts. The output reports both budgets and warns when either will make the next tick escalate instead of dispatch. Exits `2` when the issue number is missing or malformed. |
1362
1402
  | `friction <kind> --detail TEXT [--issue N]` | Record one bounded judgment the daemon cannot infer: an escalation belonged in a digest, or a tick report was noise/surprising. The detail is limited to 160 characters. One event never changes policy; three observations inside seven days make the aggregate eligible for one Learning-loop prompt, followed by a seven-day cooldown. |
1363
1403
  | `daemon` | Run the loop in the **foreground**, ticking every 5 minutes and serving `/healthz`. Admitted workers run in a tracked background pool, so settlement and capacity checks remain periodic while they work; shutdown drains the pool before closing the store. This is what `start` launches and what a systemd unit should call. |
1364
1404
  | `daemon --once` | Run a single tick, wait for workers admitted by that tick, and exit. No HTTP server or pidfile — a drill must not register itself as the daemon, or the next reader believes it and the real daemon's in-flight runs get reconciled as orphans. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-conductor",
3
- "version": "0.3.21",
3
+ "version": "0.3.23",
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.",