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 +67 -27
- package/package.json +1 -1
- package/src/board.ts +292 -55
- package/src/briefs/orchestrator.md +24 -2
- package/src/daemon.ts +101 -15
- package/src/fleet.ts +65 -1
- package/src/orchestrator-tick.ts +93 -5
- package/src/tracker/github.ts +19 -6
- package/src/types.ts +25 -3
- package/src/unblock.ts +55 -17
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,
|
|
466
|
-
|
|
467
|
-
is
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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,
|
|
474
|
-
layer every ten seconds or immediately with `r
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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.
|
|
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
|
|
552
|
-
|
|
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
|
|
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
|
|
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
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
the
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.",
|