omp-conductor 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +253 -84
- package/package.json +1 -1
- package/src/availability.ts +165 -0
- package/src/board.ts +1 -1
- package/src/briefs/orchestrator.md +95 -28
- package/src/briefs/policy.md +44 -32
- package/src/cli.ts +226 -37
- package/src/config.ts +123 -7
- package/src/daemon.ts +1018 -145
- package/src/diff-flags.ts +77 -4
- package/src/digest-schedule.ts +92 -24
- package/src/escalate.ts +46 -19
- package/src/failure-class.ts +7 -5
- package/src/fleet.ts +39 -3
- package/src/omp.ts +8 -4
- package/src/orchestrator-tick.ts +471 -39
- package/src/orchestrator.ts +3 -2
- package/src/plugin.ts +166 -14
- package/src/release-policy.ts +66 -6
- package/src/reports.ts +202 -5
- package/src/session-host.ts +4 -3
- package/src/setup.ts +197 -35
- package/src/store.ts +785 -112
- package/src/tracker/github.ts +299 -56
- package/src/types.ts +289 -37
- package/src/verbs/actions.ts +245 -15
- package/src/verbs/protocol.ts +7 -6
- package/src/verbs/server.ts +94 -13
- package/src/worker.ts +51 -11
- package/src/worktree.ts +5 -0
package/README.md
CHANGED
|
@@ -75,34 +75,31 @@ Reporting is the one half of that the config also knows about, because the wizar
|
|
|
75
75
|
has to ask something in order to seed the brief, and it is the one half the
|
|
76
76
|
runtime acts on:
|
|
77
77
|
|
|
78
|
-
|
|
|
78
|
+
| Setup choice | What may interrupt | What waits |
|
|
79
79
|
| --- | --- | --- |
|
|
80
|
-
| `material`
|
|
81
|
-
| `escalations` |
|
|
82
|
-
| `decisions` | Tier-2 decisions and fleet-stopping conditions
|
|
80
|
+
| `material` | Every configured material event, when operator availability permits. | Non-bypass events outside configured hours wait for the next configured digest or opening. |
|
|
81
|
+
| `escalations` | Tier-2 escalations, when operator availability permits. | Everything else waits for the configured digest. |
|
|
82
|
+
| `decisions` (recommended) | Tier-2 decisions and fleet-stopping conditions, when operator availability permits. | Every other material event is recorded durably and ships with the configured digest as one message. |
|
|
83
|
+
| `quiet` | Tier-2 escalations, fleet stops, and confirmed failures, when operator availability permits. | Everything else waits for one daily rollup. |
|
|
83
84
|
|
|
84
85
|
**What the scope does:** the [orchestrator heartbeat](#orchestrator-tick) appends
|
|
85
|
-
|
|
86
|
-
prompt instead of only in a brief the session read hours ago.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
The
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Changing the key later does not rewrite an `ORCHESTRATOR.md` you already have:
|
|
104
|
-
the tick line changes, the brief does not. Edit its Reporting section too, or the
|
|
105
|
-
session is carrying two versions of your policy.
|
|
86
|
+
the current constraint to every tick it sends, so the reporting contract arrives
|
|
87
|
+
with the prompt instead of only in a brief the session read hours ago. Explicit
|
|
88
|
+
policies name their actual interrupt categories and digest cadence. The policy is
|
|
89
|
+
re-read from `~/.omp/conductor/config.json` on **every** tick, and escalation,
|
|
90
|
+
direct Telegram, and durable report paths apply it mechanically. Turning the
|
|
91
|
+
volume up or down — `/conductor setup` again, or an edit to the file — therefore
|
|
92
|
+
binds the next tick without restarting the session.
|
|
93
|
+
|
|
94
|
+
No config, an unreadable or invalid config, or several unnamed projects fall
|
|
95
|
+
back to the legacy `material` scope for the heartbeat and log the reason once.
|
|
96
|
+
An invalid live availability policy blocks autonomous Telegram fail-closed; it
|
|
97
|
+
does not guess that the operator is awake.
|
|
98
|
+
|
|
99
|
+
Changing the key later does not rewrite an `ORCHESTRATOR.md` you already have.
|
|
100
|
+
The generated `POLICY.md` describes every scope without pinning the current
|
|
101
|
+
choice; the tick constraint remains derived from live config. Keep any
|
|
102
|
+
operator-owned reporting additions in `ORCHESTRATOR.md` consistent with it.
|
|
106
103
|
|
|
107
104
|
## Where issues come from
|
|
108
105
|
|
|
@@ -808,6 +805,10 @@ every check succeeded or was skipped. Missing or nonterminal checks become
|
|
|
808
805
|
`pushed-pending` and are rechecked on later ticks; red or cancelled checks become
|
|
809
806
|
`failed` with a bounded job/log digest. Only verified evidence becomes
|
|
810
807
|
`pushed-green`.
|
|
808
|
+
If a failed report mentions PRs only in prose, the daemon retains the last URL
|
|
809
|
+
whose `owner/repo` matches the run's repository; links to other repositories are
|
|
810
|
+
ignored. This preserves the continuation target without trusting an unrelated
|
|
811
|
+
PR mentioned in the same report.
|
|
811
812
|
|
|
812
813
|
What happens after verification is a human's decision, taken minutes to days
|
|
813
814
|
later and never announced to the daemon — so every tick asks the tracker about
|
|
@@ -816,14 +817,15 @@ every pushed PR it is still holding:
|
|
|
816
817
|
| PR | Row becomes | Why |
|
|
817
818
|
| --- | --- | --- |
|
|
818
819
|
| merged | `merged` | The work landed. This is the state `merged` was reserved for. |
|
|
819
|
-
| closed without merging | `failed`, with the PR in `lastError` | A human read the work and
|
|
820
|
+
| closed without merging | `failed`, class `returned-for-revision`, with the PR in `lastError` | A human read the work and asked for another pass. Leaving it `pushed-green` strands the issue forever behind a PR nobody will merge, and calling it `merged` is a lie about code that is not on the base branch. `failed` is the honest row state; the class preserves the review decision and releases the issue so a re-queue can be attempted again. |
|
|
820
821
|
| still open | unchanged | The normal steady state. Its issue must stay occupied, or a second attempt lands on the live PR. |
|
|
821
822
|
| 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. |
|
|
822
823
|
|
|
823
|
-
Run history is untouched. A PR closed without merging
|
|
824
|
-
|
|
825
|
-
loses `agent:in-progress` from its issue: the row transition and
|
|
826
|
-
fact, and a terminal answer about the PR proves no worker
|
|
824
|
+
Run history is untouched. A PR closed without merging consumes one continuation,
|
|
825
|
+
not a failed implementation attempt; a merge spends neither budget. A settled
|
|
826
|
+
row also loses `agent:in-progress` from its issue: the row transition and label
|
|
827
|
+
removal are one fact, and a terminal answer about the PR proves no worker
|
|
828
|
+
process owns the issue,
|
|
827
829
|
so the duplicate-dispatch guard it exists for is spent. The removal is *enqueued
|
|
828
830
|
on the label projection outbox in the same breath as the terminal write* — a
|
|
829
831
|
durable local write that cannot fail on the tracker — so the row settles at once
|
|
@@ -848,6 +850,30 @@ issues carrying `agent:in-progress`, which eligibility reads as "a worker owns
|
|
|
848
850
|
this" — with the brief forbidding the orchestrator from editing a state label and
|
|
849
851
|
`unblock` declining to clear that one, neither issue could ever be claimed again.
|
|
850
852
|
|
|
853
|
+
### Base-branch health after merge
|
|
854
|
+
|
|
855
|
+
The daemon records two different facts after a merge:
|
|
856
|
+
|
|
857
|
+
- The **post-merge audit** attributes a regression to one merge. For up to 24
|
|
858
|
+
hours, it checks only push-triggered workflow runs for the exact merge SHA and
|
|
859
|
+
base branch. A new red result adds `base-branch-red` evidence and escalates.
|
|
860
|
+
- **Current health** drives `status` and release policy. On every sweep, the
|
|
861
|
+
daemon resolves the live head of each branch it merged into during the last
|
|
862
|
+
seven days, then reads only push-triggered runs for that head and branch. The
|
|
863
|
+
status row includes the head SHA and run count.
|
|
864
|
+
|
|
865
|
+
Current health is `green` only when every observed run completed successfully,
|
|
866
|
+
neutrally, or skipped. A failing conclusion is `red`; an in-progress or
|
|
867
|
+
unrecognised conclusion is `pending`; and a head with no push-triggered run is
|
|
868
|
+
`unknown`, never green. Pending and unknown heads are rechecked. Green and red
|
|
869
|
+
heads are read again when the branch moves, so an old verdict cannot describe a
|
|
870
|
+
new commit. If GitHub cannot return the head or its runs, the daemon keeps the
|
|
871
|
+
last honest row instead of replacing evidence with a network failure.
|
|
872
|
+
|
|
873
|
+
The `base-branch-green` release requirement reads this current live-head row for
|
|
874
|
+
the repository being released. Red, pending, unknown, and absent evidence all
|
|
875
|
+
refuse the release.
|
|
876
|
+
|
|
851
877
|
### The settlement audit
|
|
852
878
|
|
|
853
879
|
Verifying `state: pushed-green` left two lines of the same report still taken on
|
|
@@ -868,6 +894,7 @@ change a run's state, hold a merge, or spend an attempt.
|
|
|
868
894
|
| `undisclosed-file` | The PR touched a file the `changed:` line never named. Lockfiles are exempt — they are derived from a manifest the report did disclose. |
|
|
869
895
|
| `changed-line-missing` | The report had no usable `changed:` line at all. One flag, not one per file. |
|
|
870
896
|
| `unmatched-claim` | `changed:` named a path the PR never touched. The weaker direction, and reported as such. |
|
|
897
|
+
| `report-format-unparsed` | The same file appeared as both claimed-but-untouched and touched-but-unclaimed, so the `changed:` line's format defeated the parser. Read the diff directly; this is not a trust signal against the worker. |
|
|
871
898
|
| `test-file-deleted` | A test file left the tree with no rename to account for it. |
|
|
872
899
|
| `test-disabled` | A `.skip` / `.only` / `xit` / `@pytest.mark.skip` / `t.Skip` marker appears on a line the PR added. |
|
|
873
900
|
| `assertions-removed` | An assertion was commented out, or a test file lost more assertions than it gained. |
|
|
@@ -989,7 +1016,8 @@ rest. `0` is a real value (a hard stop), not "unset".
|
|
|
989
1016
|
| `maxConcurrentWorkersPerRepo` | `1` | Max live workers in the **same repo**. The mirror, branch-protection staleness and shared CI egress are all per-repo collision domains, so extra slots should land on other repos. Raise it only when a repo genuinely needs two workers at once. |
|
|
990
1017
|
| `dailySpendUsd` | `25` | Rolling-day spend ceiling in USD, or `null` for no spend gate. `0` is a hard stop. Metered from assistant `usage.cost.total`. |
|
|
991
1018
|
| `planUsage` | `null` (unmetered) | Subscription/plan allowance guard: `{ "windowId": "anthropic:7d", "maxUsedFraction": 0.85 }`, or `null` for no plan gate. Independent of `dailySpendUsd` — see [Plan allowance](#plan-allowance-planusage) below. |
|
|
992
|
-
| `workerMaxTurns` | `120` |
|
|
1019
|
+
| `workerMaxTurns` | `120` | Base ceiling for each new worker. Catches a session looping without converging; use `omp-conductor extend` to raise one live run or one issue's next attempt without changing this default. |
|
|
1020
|
+
| `workerMaxTurnsCeiling` | `240` (twice the effective `workerMaxTurns` when omitted) | Upper bound for per-issue turn extensions. Prevents the loopback control from granting an unbounded worker budget. |
|
|
993
1021
|
| `workerWallClockMs` | `5400000` (90 minutes) | Wall-clock ceiling for one worker. A session that is merely stuck spends no turns, so turns alone cannot detect it. |
|
|
994
1022
|
| `maxAttemptsPerIssue` | `2` | Failed implementation or CI attempts before escalation. Operational stops do not consume this budget, so salvage can continue without stealing the retry needed for a real failure. |
|
|
995
1023
|
| `maxContinuationsPerIssue` | `2` | Cap-kill, daemon-orphan and answered-block resumes before escalation. This independently bounds crash/resume loops. |
|
|
@@ -1005,19 +1033,31 @@ Work resumes only after `omp-conductor resume` (or `/conductor resume`).
|
|
|
1005
1033
|
`workerMaxTurns` and `workerWallClockMs` are enforced inside the session driver.
|
|
1006
1034
|
The daemon reads a live run's effective turn ceiling at every turn boundary. Use
|
|
1007
1035
|
`omp-conductor extend <issue> --turns N [--project NAME]` to raise it without
|
|
1008
|
-
restarting or reconstructing the session.
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1036
|
+
restarting or reconstructing the session. For a live worker, extension is
|
|
1037
|
+
monotonic: equal or lower values are refused. If the latest run is failed,
|
|
1038
|
+
killed, orphaned, or blocked, the command instead stores a one-shot ceiling for
|
|
1039
|
+
that issue's next attempt. A next-attempt ceiling must exceed the effective
|
|
1040
|
+
project base, and every extension must stay at or below
|
|
1041
|
+
`workerMaxTurnsCeiling`. `status` shows both active ceilings and pending
|
|
1042
|
+
next-attempt overrides. The store consumes an override atomically when it claims
|
|
1043
|
+
the next run, so later attempts return to the project base. Config edits change
|
|
1044
|
+
that base on the next tick but do not change workers already in flight. A cap
|
|
1045
|
+
that fires aborts the run, records it as `killed`, and names the ceiling in the
|
|
1046
|
+
escalation.
|
|
1014
1047
|
|
|
1015
1048
|
Pause one live worker cooperatively with
|
|
1016
1049
|
`omp-conductor worker pause <issue> [--project NAME]`. The daemon aborts the
|
|
1017
1050
|
active turn to an idle harness state, freezes the remaining wall-clock budget,
|
|
1018
1051
|
and keeps the run in the Running lane. `omp-conductor worker resume <issue>`
|
|
1019
1052
|
continues the same session with a prompt to re-check its last action before
|
|
1020
|
-
proceeding.
|
|
1053
|
+
proceeding. To end that run instead, use
|
|
1054
|
+
`omp-conductor worker stop <issue> --reason TEXT [--project NAME]`. Stop works
|
|
1055
|
+
from running or paused, salvages dirty work before removing the worktree, records
|
|
1056
|
+
the distinct terminal `stopped` state, and removes `agent:in-progress` through
|
|
1057
|
+
the label outbox. A salvage failure keeps the only copy in place and reports its
|
|
1058
|
+
path. Stopped runs consume neither implementation-failure nor continuation
|
|
1059
|
+
budget. Repeating stop reports the already-terminal state. These worker controls
|
|
1060
|
+
are separate from fleet-level `pause`, which stops new claims.
|
|
1021
1061
|
|
|
1022
1062
|
### Plan allowance (`planUsage`)
|
|
1023
1063
|
|
|
@@ -1285,20 +1325,72 @@ a report reached you only if the model remembered to call `telegram_send`. On
|
|
|
1285
1325
|
none of the three arrived, and nothing anywhere recorded that fact — an undelivered
|
|
1286
1326
|
report and a quiet tick look identical.
|
|
1287
1327
|
|
|
1328
|
+
### Material events survive the session
|
|
1329
|
+
|
|
1330
|
+
A deferred digest does not use the session transcript as its source of truth.
|
|
1331
|
+
Record each ordinary outcome when it happens:
|
|
1332
|
+
|
|
1333
|
+
```bash
|
|
1334
|
+
omp-conductor event record \
|
|
1335
|
+
--category merge \
|
|
1336
|
+
--summary "#42 merged" \
|
|
1337
|
+
--evidence "https://github.com/acme/api/pull/42"
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
`--category` is a short lowercase slug. `--summary` states the outcome, and
|
|
1341
|
+
`--evidence` names the issue, PR, release, run, commit, or URL that proves it.
|
|
1342
|
+
Use `--occurred-at <ISO timestamp>` when the event happened earlier; otherwise,
|
|
1343
|
+
the command uses the current time. The command writes one row to SQLite and
|
|
1344
|
+
sends nothing. The row survives later ticks, session compaction, session
|
|
1345
|
+
replacement, and daemon restarts.
|
|
1346
|
+
|
|
1347
|
+
When a digest is due, its tick prompt lists a bounded, oldest-first set of
|
|
1348
|
+
owed material events and deferred escalations. Each line includes its ledger id.
|
|
1349
|
+
The prompt gives the exact handoff shape:
|
|
1350
|
+
|
|
1351
|
+
```bash
|
|
1352
|
+
omp-conductor report \
|
|
1353
|
+
--kind digest \
|
|
1354
|
+
--events EVENT_ID_1,EVENT_ID_2 \
|
|
1355
|
+
--notices NOTICE_ID_1,NOTICE_ID_2 \
|
|
1356
|
+
--text "<the whole digest>"
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
Remove the id of any row you did not use. Omitted rows stay owed. The report row
|
|
1360
|
+
and the named ledger rows are associated in one SQLite transaction. If the
|
|
1361
|
+
handoff fails, no row is consumed. If a daily report deduplicates against a
|
|
1362
|
+
daily report already queued that day, newly named rows also stay owed. If
|
|
1363
|
+
delivery exhausts its retry budget and the report becomes `failed`, its rows
|
|
1364
|
+
return to the owed backlog, where a replacement digest can claim them.
|
|
1365
|
+
`omp-conductor status` always shows the
|
|
1366
|
+
material-event and held-escalation backlog counts, including the age of the
|
|
1367
|
+
oldest row when one exists.
|
|
1368
|
+
|
|
1369
|
+
This accumulator does not poll GitHub and does not infer outcomes from tracker
|
|
1370
|
+
state. The orchestrator still decides what is material and records the evidence.
|
|
1371
|
+
The mechanism only makes that decision durable until a non-failed digest owns it.
|
|
1372
|
+
|
|
1288
1373
|
Authorship still needs judgement the daemon does not have, so it stays with the
|
|
1289
1374
|
model. Delivery does not, so it moved:
|
|
1290
1375
|
|
|
1291
1376
|
```bash
|
|
1292
|
-
omp-conductor report --text "<the whole report>"
|
|
1293
|
-
omp-conductor report
|
|
1377
|
+
omp-conductor report --text "<the whole report>" # immediate report, when policy permits
|
|
1378
|
+
omp-conductor report \
|
|
1379
|
+
--text "<the whole digest>" --kind digest \
|
|
1380
|
+
--events EVENT_IDS --notices NOTICE_IDS # use row ids from its tick
|
|
1294
1381
|
```
|
|
1295
1382
|
|
|
1296
|
-
The command
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1383
|
+
The command persists the text before anything is sent and prints a durable
|
|
1384
|
+
handoff id. An immediate report admitted during quiet hours is stored as a held
|
|
1385
|
+
notice and prints that id; the daemon includes it in the next digest or in a
|
|
1386
|
+
catch-up report when the configured window opens. Otherwise it writes a
|
|
1387
|
+
`reports` row and prints its report id. Both survive the session being
|
|
1388
|
+
compacted, interrupted or restarted,
|
|
1389
|
+
and the daemon being restarted under it. The daemon delivers over the same bot
|
|
1390
|
+
token tier 2 uses, with bounded retries, and `omp-conductor status` lists
|
|
1391
|
+
anything it still owes. If availability closes after a material report was
|
|
1392
|
+
queued but before its first attempt, the outbox atomically converts that row to
|
|
1393
|
+
the same held-notice path instead of leaking the update through quiet hours.
|
|
1302
1394
|
|
|
1303
1395
|
### Delivery is at-least-once, and the docs will not pretend otherwise
|
|
1304
1396
|
|
|
@@ -1361,14 +1453,16 @@ then the only surface — a report has no tracker issue, so there is no issue
|
|
|
1361
1453
|
comment to fall back to. The page goes through the ordinary escalation ledger and
|
|
1362
1454
|
carries the report id, so one undeliverable report pages exactly once.
|
|
1363
1455
|
|
|
1364
|
-
###
|
|
1456
|
+
### Daily digests are deduplicated from the ledger
|
|
1365
1457
|
|
|
1366
|
-
`--kind digest` is accepted at most once per
|
|
1367
|
-
second hand-over on the same day is refused and
|
|
1368
|
-
the slot, including when that report has already
|
|
1369
|
-
from the `reports` table, not from the model's
|
|
1370
|
-
restarted or compacted session cannot send a second
|
|
1371
|
-
|
|
1458
|
+
With `digest.cadence: "daily"`, `--kind digest` is accepted at most once per
|
|
1459
|
+
**local** day, per project. The second hand-over on the same day is refused and
|
|
1460
|
+
told which report already holds the slot, including when that report has already
|
|
1461
|
+
been delivered. This is decided from the `reports` table, not from the model's
|
|
1462
|
+
memory of the last tick — a restarted or compacted session cannot send a second
|
|
1463
|
+
daily digest by forgetting the first. A `per-tick` digest carries no daily key,
|
|
1464
|
+
so later ticks can hand off newly accumulated rows. Material reports carry no
|
|
1465
|
+
dedupe key either: two events in a day are two events.
|
|
1372
1466
|
|
|
1373
1467
|
### What `status` shows
|
|
1374
1468
|
|
|
@@ -1464,6 +1558,7 @@ persists the class on the row, and performs the one recovery that class names.
|
|
|
1464
1558
|
| --- | --- | --- | --- |
|
|
1465
1559
|
| `env-start-failure` | turn 0 plus an explicit harness start error (`No model selected`, a rejected key) | escalate — the session never read the issue | none |
|
|
1466
1560
|
| `settlement-stuck` | a row carrying a PR that has since merged | settle: release the label, mark the row merged | none |
|
|
1561
|
+
| `returned-for-revision` | a `pushed-green` or `pushed-pending` PR was closed without merging | none — preserve the review decision for a human re-queue | continuation |
|
|
1467
1562
|
| `merge-conflict` | `pushed-green`, PR open, GitHub reports conflicting | requeue for a rebase continuation | continuation |
|
|
1468
1563
|
| `question` | the worker stopped to ask something (`blocked`) | escalate, carrying the worker's own report as evidence | none |
|
|
1469
1564
|
| `orphan-dirty` | orphaned with a failed salvage and no operator ack | hold — recorded only; the tree is the only copy | none |
|
|
@@ -1473,7 +1568,9 @@ persists the class on the row, and performs the one recovery that class names.
|
|
|
1473
1568
|
| `admin-kill` | killed *below* its own ceiling — a restart or a drain | requeue | none |
|
|
1474
1569
|
| `ci-infra` | PR open, every unresolved check cancelled / timed out / stale | re-run the failed jobs | none |
|
|
1475
1570
|
| `ci-deterministic` | PR open, a check genuinely reports `FAILURE` | escalate with the failing check names and links | failed attempt |
|
|
1571
|
+
| `dispatch-infra` | the conductor's own Git path failed before the worker's first turn | requeue, bounded by per-class strikes | none |
|
|
1476
1572
|
| `provider-credit` | the provider refused the run for credit (HTTP 402, or its own out-of-credit text read off the transcript) | pause the fleet and require `omp-conductor resume` once the provider has credit | none |
|
|
1573
|
+
| `provider-transient` | the provider aborted a request stream before the run produced a verdict | requeue, bounded by per-class strikes | none |
|
|
1477
1574
|
| `unknown` | anything unrecognised | escalate | as recorded |
|
|
1478
1575
|
|
|
1479
1576
|
**Unknown escalates; it never silently retries.** A shape this table does not
|
|
@@ -1483,13 +1580,14 @@ cause nobody has named — the behaviour this exists to end.
|
|
|
1483
1580
|
### The budgets follow the cause
|
|
1484
1581
|
|
|
1485
1582
|
`failuresFor` (implementation attempts) excludes `ci-infra`, `settlement-stuck`,
|
|
1486
|
-
`env-start-failure`
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1583
|
+
`env-start-failure`, `dispatch-infra`, `provider-credit`, `provider-transient`
|
|
1584
|
+
and `returned-for-revision`. `continuationsFor` excludes `admin-kill`,
|
|
1585
|
+
`settlement-stuck`, `env-start-failure`, `dispatch-infra`, `provider-credit`
|
|
1586
|
+
and `provider-transient`, but explicitly counts a failed
|
|
1587
|
+
`returned-for-revision` row. Environment, dispatch and provider faults charge
|
|
1588
|
+
neither budget because the issue did not receive a valid implementation
|
|
1589
|
+
attempt. A merge conflict and a returned review both charge a continuation:
|
|
1590
|
+
each asks for more work, but neither is a failed implementation attempt.
|
|
1493
1591
|
|
|
1494
1592
|
An **unclassified** row (every row written before 0.4.3) counts exactly as it
|
|
1495
1593
|
did before classification existed. Upgrading therefore changes no existing
|
|
@@ -1673,7 +1771,7 @@ Field notes:
|
|
|
1673
1771
|
| `escalation.orchestrator` | Optional; `"embedded"` (default) or `"external"`. `external` means an orchestrator session already runs elsewhere: the daemon starts none, and tier-1 escalations post as issue comments for that session to drain. Any other value is an error. |
|
|
1674
1772
|
| `authority` | Optional; `{ "merge": …, "release": … }`, each `"human"` (default) or `"orchestrator"`. It grants nothing to the daemon — it words the orchestrator's standing orders and the Releases paragraph of the rendered brief, so the config and the prompt cannot disagree about who holds the merge button. Unknown keys and any other value are errors, never folded to the default. |
|
|
1675
1773
|
| `releasePolicy` | Optional; `"none"` (default) or `"operator-brief"`. `none` installs a pre-tool-call tripwire in worker, embedded-orchestrator and external-orchestrator sessions. It blocks `git tag`, tag pushes, package publishing, GitHub release creation and recognised deploy commands before execution. `operator-brief` opens that gate only for the procedure in the operator-owned brief. Unknown values are errors. Every rejection is written to `release-policy-blocks.jsonl`; the heartbeat carries that day's count into the daily digest so configured intent and observed behaviour cannot drift silently. This is the mechanical gate; `authority.release` still says who owns the decision. |
|
|
1676
|
-
| `reporting` | Optional; a **legacy scope preset** (`reporting.scope` — `"material"` default, `"decisions"`, `"escalations"`) or the **explicit form** `{ "interruptOn": [...], "digest": { ... } }`. The preset writes which categories may page the operator (`interruptOn`) and when the rollup happens (`digest.cadence`); the explicit form sets both directly and
|
|
1774
|
+
| `reporting` | Optional; a **legacy scope preset** (`reporting.scope` — `"material"` default, `"decisions"`, `"escalations"`) or the **explicit form** `{ "interruptOn": [...], "digest": { ... }, "availability": { ... } }`. The preset writes which categories may page the operator (`interruptOn`) and when the rollup happens (`digest.cadence`); the explicit form sets both directly and may add a weekly operator-availability window. The two forms are mutually exclusive in one config. See [Reporting policy](#reporting-policy-reporting). |
|
|
1677
1775
|
| `orchestratorReadPaths` | **Retired in 0.4.3.** Still accepted in a config and ignored, so a fleet carrying it upgrades without an edit. It widened the orchestrator's file-tool allowlist; there is no allowlist any more — the orchestrator is [unconfined by design](#the-orchestrator-is-unconfined-deliberately). |
|
|
1678
1776
|
| `policy` | Optional; the gating conditions a merge or a release must satisfy, in two sections — `policy.merge` and `policy.release`. Any member may be omitted and the loader fills it from the strict default; an unknown key in either section, or a value outside its vocabulary, is an error naming the field, never a silent downgrade. See [Merge and release preconditions](#merge-and-release-preconditions-policy). |
|
|
1679
1777
|
| `workspaceRoot` / `mirrorRoot` | Optional; default to `worktrees/` and `mirrors/` under the state directory. `~` is expanded. |
|
|
@@ -1695,13 +1793,66 @@ spellings, mutually exclusive in one config (the loader rejects a `scope` next t
|
|
|
1695
1793
|
- **Explicit** — `reporting: { "interruptOn": ["tier2", "fleet-stopped", ...], "digest": { "cadence": "none" | "per-tick" | "daily" } }`.
|
|
1696
1794
|
`interruptOn` must be a non-empty array of known categories (`tier2`, `decision-needed`, `fleet-stopped`, `confirmed-failure`, `material`), each an escalation's tier-2 category. `daily` may add `at` (`HH:MM`, 24h) and `timezone` (a known IANA zone, defaulting to the host zone) — both only valid with `daily`.
|
|
1697
1795
|
|
|
1796
|
+
The explicit form may add a weekly local-time window:
|
|
1797
|
+
|
|
1798
|
+
```json
|
|
1799
|
+
{
|
|
1800
|
+
"reporting": {
|
|
1801
|
+
"interruptOn": ["tier2", "fleet-stopped"],
|
|
1802
|
+
"digest": { "cadence": "daily", "at": "17:00", "timezone": "Europe/London" },
|
|
1803
|
+
"availability": {
|
|
1804
|
+
"timezone": "Europe/London",
|
|
1805
|
+
"days": ["mon", "tue", "wed", "thu", "fri"],
|
|
1806
|
+
"start": "09:00",
|
|
1807
|
+
"end": "17:00",
|
|
1808
|
+
"bypass": ["fleet-stopped"]
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
```
|
|
1813
|
+
|
|
1814
|
+
`timezone` must be a known IANA zone. For a daily digest, its timezone defaults
|
|
1815
|
+
to this value and must match it when both are set.
|
|
1816
|
+
|
|
1817
|
+
`days` is a non-empty set of `mon` through `sun`; `start` is inclusive and
|
|
1818
|
+
`end` is exclusive. A start later than the end defines an overnight window on
|
|
1819
|
+
the day it opens. `bypass` is an explicit list of known interrupt categories
|
|
1820
|
+
that may still page outside the window; it may be empty. For ordinary notices,
|
|
1821
|
+
a bypass has no effect on a category omitted from `interruptOn`. Urgent recovery
|
|
1822
|
+
notices may bypass category batching when the digest loop itself is unavailable,
|
|
1823
|
+
but they still require the configured availability bypass outside the window.
|
|
1824
|
+
|
|
1825
|
+
The setup wizard offers this as **Weekly availability window** and asks for the
|
|
1826
|
+
zone, days, start/end, bypass categories, and digest schedule: every tick,
|
|
1827
|
+
model-timed daily, disabled, or a fixed daily `HH:MM`. Re-running setup or
|
|
1828
|
+
amending reporting preselects and preserves the configured `none`, `per-tick`,
|
|
1829
|
+
or `daily` cadence. Choosing **Continuous (24-hour interrupts)** is the explicit
|
|
1830
|
+
opt-out and preserves the behavior of every existing config; an absent
|
|
1831
|
+
`availability` key also means continuous operation.
|
|
1832
|
+
|
|
1833
|
+
Outside the window, an otherwise interruptible escalation is stored durably
|
|
1834
|
+
instead of sent. A daily digest may consume it first. Otherwise the daemon
|
|
1835
|
+
atomically queues one working-hours catch-up report when the window opens,
|
|
1836
|
+
including after downtime; associating the held rows before delivery prevents a
|
|
1837
|
+
later tick from authoring a duplicate. Each heartbeat prompt names the
|
|
1838
|
+
mechanically computed current mode and next transition. `status` shows the same
|
|
1839
|
+
state plus the next digest opportunity (`due now`, every tick, disabled, or its
|
|
1840
|
+
next operator-local timestamp). Config, escalation routing, and report transport
|
|
1841
|
+
are re-read at tick or send time, so changing the window or Telegram target
|
|
1842
|
+
does not require a daemon restart.
|
|
1843
|
+
|
|
1844
|
+
Attachment-bearing autonomous Telegram sends cannot be replayed by the text
|
|
1845
|
+
digest, so they are blocked with an explicit “nothing sent or held” error rather
|
|
1846
|
+
than silently dropping their files.
|
|
1847
|
+
|
|
1698
1848
|
A tier-2 escalation whose category is **not** in `interruptOn` is not dropped: it
|
|
1699
1849
|
is held (`held_notices`) and the next accepted digest is its delivery authority.
|
|
1700
|
-
|
|
1701
|
-
configured zone), which remains the delivery authority across restarts.
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1850
|
+
A `daily` digest is at-most-once per local day (`digest:<YYYY-MM-DD>` in the
|
|
1851
|
+
configured zone), which remains the delivery authority across restarts.
|
|
1852
|
+
`per-tick` digests are not daily-deduplicated, so a later tick can claim newly
|
|
1853
|
+
accumulated rows. A scheduled `daily` digest is only sent on a day it has not
|
|
1854
|
+
already run, once the local clock has passed `at`; a restart after `at` still
|
|
1855
|
+
sends today's (one catch-up), and a fully missed day is skipped, never sent late.
|
|
1705
1856
|
|
|
1706
1857
|
### Merge and release preconditions (`policy`)
|
|
1707
1858
|
|
|
@@ -1724,7 +1875,7 @@ policy instead of restating it — no threshold lives in two places.
|
|
|
1724
1875
|
|
|
1725
1876
|
| Field | Values | Default | Means |
|
|
1726
1877
|
| --- | --- | --- | --- |
|
|
1727
|
-
| `requires` | `runs-settled`, `no-open-prs`, `queue-drained`, `epic-children-closed` | `["runs-settled"]` | What must already have landed. Order and duplicates do not matter; the loader canonicalises. |
|
|
1878
|
+
| `requires` | `runs-settled`, `no-open-prs`, `queue-drained`, `base-branch-green`, `epic-children-closed` | `["runs-settled"]` | What must already have landed. `base-branch-green` requires the current live head's push-triggered workflow verdict for that routed repository to be green; pending, unknown, red, or no observation refuses release. Order and duplicates do not matter; the loader canonicalises. |
|
|
1728
1879
|
| `requiredChecks` | any check names | `[]` | Checks that must be green on the branch being released. Empty means every check it reports. |
|
|
1729
1880
|
| `artefacts` | any names | `[]` | The packages or images this project releases. **Empty denies**: nothing has been authorised to ship. |
|
|
1730
1881
|
| `environments` | any names | `[]` | Deploy targets. **Empty denies** every environment. |
|
|
@@ -1784,13 +1935,14 @@ writes this file for external orchestration. A manual configuration has this for
|
|
|
1784
1935
|
| `budgetSeconds` | no | `600` | Seconds a turn may run before the tick guard refuses its remaining tool calls (#189), and before a queued operator message preempts them. An integer ≥ 60; anything else degrades to the default. |
|
|
1785
1936
|
| `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`. |
|
|
1786
1937
|
| `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. |
|
|
1787
|
-
| `message` | no | `Tick <ISO timestamp>: re-read <workspaceRoot>/ORCHESTRATOR.md from disk, then run your standing loop from it.`, then the
|
|
1938
|
+
| `message` | no | `Tick <ISO timestamp>: re-read <workspaceRoot>/ORCHESTRATOR.md from disk, then run your standing loop from it.`, then the reporting-policy line, delivery rule, and mechanical availability state | When set, this text replaces the ordinary reporting-policy line and delivery rule, but the runtime-owned availability state is still appended: a custom prompt cannot infer whether the operator may be interrupted. 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. |
|
|
1788
1939
|
| `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. |
|
|
1789
1940
|
|
|
1790
|
-
A tick sends one message (`customType` `omp-conductor.tick`, attributed
|
|
1791
|
-
user): the standing-loop prompt, the
|
|
1792
|
-
|
|
1793
|
-
|
|
1941
|
+
A default tick sends one message (`customType` `omp-conductor.tick`, attributed
|
|
1942
|
+
to the user): the standing-loop prompt, the reporting-policy constraint re-read
|
|
1943
|
+
from conductor config on every tick, the delivery rule, and the mechanically
|
|
1944
|
+
computed operator-availability state. A configured `message` replaces the first
|
|
1945
|
+
three parts but not that clock state. The delivery rule is there
|
|
1794
1946
|
because end-of-turn text reaches the operator's Telegram only on a turn that
|
|
1795
1947
|
*began* as an inbound Telegram message: a tick is injected locally, so anything
|
|
1796
1948
|
the session merely writes at the end of one is read by nobody, and a reportable
|
|
@@ -1991,10 +2143,12 @@ omp-conductor tail <issue> [--project NAME]
|
|
|
1991
2143
|
omp-conductor extend <issue> --turns N [--project NAME]
|
|
1992
2144
|
omp-conductor worker pause <issue> [--project NAME]
|
|
1993
2145
|
omp-conductor worker resume <issue> [--project NAME]
|
|
2146
|
+
omp-conductor worker stop <issue> --reason TEXT [--project NAME]
|
|
1994
2147
|
omp-conductor unblock <issue> [--force] [--no-requeue] [--project NAME]
|
|
1995
2148
|
omp-conductor verb <conductor_*> [--project NAME] [--arg k=v ...]
|
|
1996
2149
|
omp-conductor friction <escalation-digest|report-noise|report-surprise> --detail TEXT [--issue N] [--project NAME]
|
|
1997
|
-
omp-conductor
|
|
2150
|
+
omp-conductor event record --category NAME --summary TEXT --evidence REF [--occurred-at ISO] [--project NAME]
|
|
2151
|
+
omp-conductor report --text TEXT [--kind material|digest] [--events IDS] [--notices IDS] [--project NAME]
|
|
1998
2152
|
omp-conductor decision open --question TEXT [--blocks TEXT] [--resolves-when COND] [--project NAME]
|
|
1999
2153
|
omp-conductor decision resolve <id> --answer TEXT [--project NAME]
|
|
2000
2154
|
omp-conductor decision withdraw <id> [--reason TEXT] [--project NAME]
|
|
@@ -2014,7 +2168,7 @@ omp-conductor help
|
|
|
2014
2168
|
| `restart [--now] [--timeout SECONDS] [--port N] [--project NAME]` | Drains the fleet by default: pause new claims, wait until live workers reach `0 / N` (bounded by `--timeout SECONDS`, default 1800 = 30 min), restart, then restore the prior dispatch state. 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. `--now` skips the drain and restarts immediately, orphaning any live runs (old behaviour). A drain that hits `--timeout` restarts nothing and leaves dispatch paused — `omp-conductor resume` lifts it, or re-run `restart` to keep waiting. 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). |
|
|
2015
2169
|
| `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. |
|
|
2016
2170
|
| `status [--project NAME]` | Layered fleet report first: `dispatch` / `ticks` / next scheduled tick / `pane` / `recovery` / `herdr` / `telegram` / `brief` / `decisions` / optional `failure classes` and `code graph` / `daemon`, then the project body. The project body includes the latest completed dispatch timestamp, ready/routed/admitted counts, bounded hold groups, and the GitHub API budget (`graphql` / `core` remaining and reset, in the caps block); 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. A `reports` block lists everything the outbox has not delivered, with its age, and prints `pending` (nobody has it) differently from `SENDING` (outcome unknown, it may already have arrived) — see [Report delivery](#report-delivery-the-outbox). The daemon block includes `rss` from `/healthz`; live workers add a busy-deploy warning. A `.conductor-stalled` marker adds an `orchestrator STALLED since …` line. |
|
|
2017
|
-
| `ledger [--issue N] [--limit N]` |
|
|
2171
|
+
| `ledger [--issue N] [--limit N]` | The action audit: every [mediated-verb](#the-mediated-verbs-126) mutation and every next-attempt turn budget. Verb entries include the arguments, decision, named refusal, and resulting SHA. Turn-budget entries remain after an override is replaced or consumed. Reads (`conductor_pr_status`) are absent so polling cannot bury the signal. `--issue` narrows both histories; `--limit` defaults to 50. Recent verb refusals and pending turn overrides also appear in `status`. |
|
|
2018
2172
|
| `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, the last 24 hours of Merged and Settled, and Parked (an issue the tracker has not confirmed closed — still open, or a label read that failed — so nothing dispatches it until a human labels it). 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. |
|
|
2019
2173
|
| `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). |
|
|
2020
2174
|
| `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. |
|
|
@@ -2022,12 +2176,13 @@ omp-conductor help
|
|
|
2022
2176
|
| `disarm [--project NAME]` | Remove the arm marker so ticks skip. Processes untouched. |
|
|
2023
2177
|
| `release-pane [--project NAME]` | Clear the `halt --pane` recovery pin so herdr-conductor may resume the fleet agent again. |
|
|
2024
2178
|
| `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>`. |
|
|
2025
|
-
| `extend <issue> --turns N [--project NAME]` |
|
|
2026
|
-
| `worker pause <issue>` / `worker resume <issue>` | Cooperatively park one live worker without changing its run state or lane. Pause aborts the active turn to harness idle and freezes the remaining wall-clock budget; resume continues the same session with a prompt to re-check its last action before repeating it. This is
|
|
2179
|
+
| `extend <issue> --turns N [--project NAME]` | Raise a live worker's effective turn ceiling through its owning daemon without restarting its session. If the latest run is failed, killed, orphaned, or blocked and has no live controller, store a one-shot ceiling for that issue's next claimed attempt instead. A next-attempt value must exceed the project base, every extension must stay at or below `workerMaxTurnsCeiling`, and live extensions remain monotonic. The pending value appears in `status`, is recorded in `ledger`, and is consumed atomically by one claim. |
|
|
2180
|
+
| `worker pause <issue>` / `worker resume <issue>` | Cooperatively park one live worker without changing its run state or lane. Pause aborts the active turn to harness idle and freezes the remaining wall-clock budget; resume continues the same session with a prompt to re-check its last action before repeating it. This is separate from fleet-level `pause`, which refuses new claims and work-starting mutations while allowing pre-pause completion work and releases. |
|
|
2181
|
+
| `worker stop <issue> --reason TEXT [--project NAME]` | Terminally end a running or cooperatively paused worker. The reason is required (1–500 characters) and persisted on the run. The command waits for settlement, records the distinct `stopped` state, salvages and publishes dirty work, removes `agent:in-progress` through the durable label outbox, and consumes neither failed-attempt nor continuation budget. If salvage fails, the tree holding the only copy stays in place and the command names it. Repeating stop is idempotent and reports the run's already-terminal state. |
|
|
2027
2182
|
| `unblock <issue> [--force] [--no-requeue]` | Remove that issue's `blocked` and `failed` labels so an answered escalation can be claimed again, and restore the project queue label by default so the dispatcher actually sees it. `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 the queue label stays off until that run settles), 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. The label changes go through the [label projection outbox](#the-tick): they are applied inline before the command returns, but **a tracker that refuses them (403, rate limit) no longer fails the verb** — it exits `0`, the intended label state is durable and the daemon retries it, and the output says `label sync queued (N pending) — the daemon retries` instead of claiming the labels were restored. Safety is preserved, but the issue is only claimable once the queue label itself lands: the queue read asks GitHub for issues carrying that label, so a refused queue-label add keeps the issue out of dispatch until projection succeeds. `--no-requeue` clears the state labels only, leaving the queue label untouched — the case where you are about to close the issue. **Refuses, clearing nothing and exiting `3`, when the newest attempt's work could not be committed and its worktree is the only copy** — re-claiming removes that tree. `--force` records the operator's acceptance on the run row and then clears; the salvage failure stays in history. Exits `2` when the issue number is missing or malformed. |
|
|
2028
2183
|
| `verb <conductor_*> [--arg k=v ...]` | Run one [mediated verb](#the-mediated-verbs-126) as the orchestrator, from the CLI — the external-orchestrator half of the verb surface. Every argument goes in as a `--arg k=v` string; an orchestrator can merge (`conductor_pr_merge`), label (`conductor_label`), release (`conductor_release`), update a branch (`conductor_pr_update_branch`) or title/body (`conductor_pr_update`), or read PR state (`conductor_pr_status`). The daemon applies the same checks and writes the same ledger rows a session's call would; a missing `--arg` is refused exactly as a missing tool argument is, worker-only verbs (`conductor_push`, `conductor_pr_create`) are refused with `role-not-allowed`, and a refusal exits `3`. An unknown verb exits `2`. |
|
|
2029
2184
|
| `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. |
|
|
2030
|
-
| `report --text TEXT [--kind material|digest]` | Hand a rendered report to the daemon's durable outbox. The
|
|
2185
|
+
| `report --text TEXT [--kind material|digest]` | Hand a rendered report to the daemon's durable outbox. The command persists the text **before** anything can send and prints a durable handoff id. A material report submitted during quiet hours becomes a held notice until the window opens; otherwise it becomes a report whose delivery the daemon owns, retries with bounded backoff, and records. Delivery is [at-least-once](#report-delivery-the-outbox), so a crash mid-send is retried as a possible repeat and `delivered` never proves exactly one message. `--kind digest` is accepted at most once per local day, decided from the ledger; an unknown `--kind` exits `2`. Anything still owed appears in `status` with its age. |
|
|
2031
2186
|
| `decision open --question TEXT [--blocks TEXT] [--resolves-when COND]` | Record a question the orchestrator has put to you, and print its id. A question that lives only in a session's context is lost at the next compaction — after which it is either asked twice or dropped silently. `--resolves-when` attaches a machine-checkable condition: `pr-merged:<https url>`, `pr-checks-green:<https url>`, `pr-mergeable:<https url>`, `issue-closed:<n>`, `npm-version:<pkg>@<version>`, or `rate-limit-reset:github`; anything else exits `2` listing the six forms. See [The decision ledger](#the-decision-ledger-resolves-when). |
|
|
2032
2187
|
| `decision resolve <id> --answer TEXT` | Record what you decided. Exits `1` naming the id when it is unknown or no longer open, so a second answer cannot overwrite the first. |
|
|
2033
2188
|
| `decision withdraw <id> [--reason TEXT]` | Close a question the session stopped needing, with why. Same guard as `resolve`. |
|
|
@@ -2036,7 +2191,7 @@ omp-conductor help
|
|
|
2036
2191
|
| `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. |
|
|
2037
2192
|
| `--port N` | Accepted by `start`, `restart` and `daemon`. Both `--port 9000` and `--port=9000` work; missing or out of range exits `2` rather than falling back to the default, because probing the wrong endpoint is worse than a hard failure. |
|
|
2038
2193
|
| `--project NAME` | Pick the project to service. One daemon process serves exactly one project; with several configured projects the name is required. |
|
|
2039
|
-
| `pause [--reason TEXT]` | Stop
|
|
2194
|
+
| `pause [--reason TEXT]` | Stop new claims and work-starting mutations only. The running daemon notices on its next tick; runs already in flight finish. The orchestrator may still merge, update, or label runs admitted before the pause, and may release when the release policy's own preconditions hold. The orchestrator heartbeat keeps ticking if armed — its gate is the arm marker, not this flag. Per-worker pause is separate. Prefer `hold` to silence both. `--reason TEXT` is recorded in the pause sentinel, which `status` shows as the pause provenance. |
|
|
2040
2195
|
| `resume` | Clear pause only — does **not** re-arm. Run `arm` after an inbound Telegram proof to resume ticks. |
|
|
2041
2196
|
| `--version`, `-V`, `version` | Print the installed `omp-conductor` package version and exit `0`. Works from the global binary and npm/plugin install because it reads the package metadata beside the shipped CLI. |
|
|
2042
2197
|
| `graph-setup` | Print how to set up the code-graph indexes workers query instead of grepping: a `git clone` for every index-only clone that does not exist yet, the one-shot index command per repo, and a `cbm-reindex.service` + `cbm-reindex.timer` pair generated from the project's own repos and branches. Reads only, so it is safe on a host where you are not root. Exits `1` when no repo in the project has [`graphProject`](#configuration) set, because the fix is a wizard answer rather than a flag. See [Code-graph discovery](#code-graph-discovery). |
|
|
@@ -2049,8 +2204,11 @@ omp-conductor help
|
|
|
2049
2204
|
| `help`, `--help`, `-h` | Print usage. An unknown or missing verb prints it too, and exits `2`. |
|
|
2050
2205
|
|
|
2051
2206
|
Pause is a flag file under the state directory, so it applies to every project and
|
|
2052
|
-
survives a daemon restart.
|
|
2053
|
-
|
|
2207
|
+
survives a daemon restart. It refuses new claims and work-starting mutations,
|
|
2208
|
+
allows completion verbs only for runs admitted before the pause, and leaves
|
|
2209
|
+
`conductor_release` to its normal authority, grant, and precondition checks.
|
|
2210
|
+
Per-worker pause is independent. Hold also removes the arm marker the heartbeat
|
|
2211
|
+
reads, so both brains go quiet without killing processes.
|
|
2054
2212
|
|
|
2055
2213
|
These are available in-session as `/conductor setup`, `/conductor status`,
|
|
2056
2214
|
`/conductor hold`, `/conductor halt [--pane]`, `/conductor arm`, `/conductor disarm`,
|
|
@@ -2210,7 +2368,7 @@ What holds the orchestrator instead:
|
|
|
2210
2368
|
| | |
|
|
2211
2369
|
| --- | --- |
|
|
2212
2370
|
| **The brief** | `ORCHESTRATOR.md`'s hard boundaries — never read or edit a worker's checkout or the mirror cache; when you need a run's code, read its PR. |
|
|
2213
|
-
| **The
|
|
2371
|
+
| **The action ledger** | Every `conductor_*` mutation and operator-selected next-attempt turn budget remains auditable. `omp-conductor ledger` shows both, including refused calls and consumed or replaced budget overrides. |
|
|
2214
2372
|
| **The dispatcher** | Merge, label and release authority are checked in the daemon against the operator's grant, across a process boundary, never in the prompt. |
|
|
2215
2373
|
|
|
2216
2374
|
Unconfined means auditable, not licensed. `orchestratorReadPaths` is retired: it
|
|
@@ -2243,6 +2401,14 @@ role must equal the configured holder.** `authority` has exactly two values, so
|
|
|
2243
2401
|
a `!== "human"` test would have let a *worker* release. A worker is refused
|
|
2244
2402
|
every release shape under the most permissive config there is.
|
|
2245
2403
|
|
|
2404
|
+
For Git-backed releases, `git-tag` is idempotent when the named tag exists
|
|
2405
|
+
locally but has not been pushed: it re-points the tag to the verified live
|
|
2406
|
+
default-branch head. `git-push-tags` performs the same re-point immediately
|
|
2407
|
+
before pushing if the default branch moved between the two calls. A tag already
|
|
2408
|
+
published on origin is immutable: an identical tag is accepted as already
|
|
2409
|
+
complete, while a different published target is refused and must use a new tag
|
|
2410
|
+
name.
|
|
2411
|
+
|
|
2246
2412
|
### The transport
|
|
2247
2413
|
|
|
2248
2414
|
Identity is never an argument. `project`, `run`, `issue` and the caller's role
|
|
@@ -2325,9 +2491,12 @@ no socket it fails closed and says so, rather than reaching for `git push`.
|
|
|
2325
2491
|
|
|
2326
2492
|
### The ledger
|
|
2327
2493
|
|
|
2328
|
-
Every mutating call is recorded with its arguments, the decision, the
|
|
2329
|
-
refusal reason and any resulting SHA. Reads are not: a status poll every
|
|
2330
|
-
seconds would bury the refusals the record exists to surface.
|
|
2494
|
+
Every mutating verb call is recorded with its arguments, the decision, the
|
|
2495
|
+
named refusal reason and any resulting SHA. Reads are not: a status poll every
|
|
2496
|
+
thirty seconds would bury the refusals the record exists to surface.
|
|
2497
|
+
|
|
2498
|
+
Every `extend` that sets a next-attempt budget also appends an audit entry.
|
|
2499
|
+
Replacing or consuming the pending override does not erase that history.
|
|
2331
2500
|
|
|
2332
2501
|
```console
|
|
2333
2502
|
$ omp-conductor ledger --issue 7
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|