omp-conductor 0.19.6 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/REFERENCE.md +27 -2
  2. package/agents/to-spec.md +76 -9
  3. package/package.json +1 -1
  4. package/schema/config.schema.json +4 -0
  5. package/src/arm-challenge.ts +204 -85
  6. package/src/ask.ts +130 -615
  7. package/src/board.ts +7 -1
  8. package/src/brief-upgrade.ts +24 -0
  9. package/src/briefs/console.md +253 -0
  10. package/src/briefs/correction.md +203 -0
  11. package/src/briefs/orchestrator.md +167 -97
  12. package/src/briefs/policy.md +19 -16
  13. package/src/briefs/to-spec.md +76 -9
  14. package/src/briefs/worker.md +50 -16
  15. package/src/cli.ts +4 -0
  16. package/src/command-manifest.ts +54 -8
  17. package/src/commands/arm.ts +113 -49
  18. package/src/commands/console.ts +70 -0
  19. package/src/commands/context.ts +2 -0
  20. package/src/commands/epic.ts +132 -0
  21. package/src/commands/extend.ts +9 -1
  22. package/src/commands/intake.ts +44 -14
  23. package/src/commands/stats.ts +19 -4
  24. package/src/commands/worker.ts +9 -1
  25. package/src/config-schema.ts +13 -0
  26. package/src/config.ts +27 -0
  27. package/src/daemon/ack.ts +159 -0
  28. package/src/daemon/admission-pass.ts +135 -0
  29. package/src/daemon/brief.ts +461 -0
  30. package/src/daemon/deps.ts +539 -0
  31. package/src/daemon/dispatch.ts +1779 -0
  32. package/src/daemon/drain.ts +185 -0
  33. package/src/daemon/groom-pass.ts +412 -0
  34. package/src/daemon/http.ts +417 -0
  35. package/src/daemon/integrity.ts +108 -0
  36. package/src/daemon/panes.ts +180 -0
  37. package/src/daemon/review.ts +1888 -0
  38. package/src/daemon/runtime.ts +736 -0
  39. package/src/daemon/settle-pass.ts +589 -0
  40. package/src/daemon/supervision.ts +438 -0
  41. package/src/daemon/tick.ts +968 -0
  42. package/src/daemon/views.ts +751 -0
  43. package/src/daemon.ts +105 -7832
  44. package/src/dashboard/app.js +58 -0
  45. package/src/dashboard/controls.ts +22 -3
  46. package/src/dashboard/server.ts +4 -0
  47. package/src/diff-flags.ts +24 -3
  48. package/src/doctor.ts +17 -12
  49. package/src/escalate.ts +39 -21
  50. package/src/failure-class.ts +75 -1
  51. package/src/fleet.ts +1218 -304
  52. package/src/groom.ts +461 -0
  53. package/src/http-token.ts +142 -0
  54. package/src/knowledge.ts +229 -0
  55. package/src/mining.ts +316 -0
  56. package/src/orchestrator-tick.ts +428 -1681
  57. package/src/ready-gate.ts +267 -0
  58. package/src/settlement.ts +72 -6
  59. package/src/setup-host.ts +32 -9
  60. package/src/setup-wizard.ts +55 -7
  61. package/src/setup.ts +229 -3
  62. package/src/stats.ts +257 -2
  63. package/src/status-render.ts +158 -7
  64. package/src/store.ts +646 -26
  65. package/src/to-spec.ts +194 -21
  66. package/src/tracker/github.ts +50 -0
  67. package/src/types.ts +435 -15
  68. package/src/verbs/protocol.ts +28 -0
  69. package/src/verbs/server.ts +384 -12
  70. package/src/wake.ts +19 -2
  71. package/src/worker.ts +456 -1
@@ -230,30 +230,45 @@ Keep the queue worth draining.
230
230
  the capability appears.
231
231
  - An issue that has exhausted its attempts is not a retry candidate. Diagnose it,
232
232
  split it, or hand it back to a human.
233
- - **Grooming is throughput-bound, so delegate the finding.** N workers drain the
234
- queue in parallel while you refill it alone: on a three-worker fleet grooming
235
- has to produce roughly three well-specced issues in the time one worker takes
236
- to finish one. Auditing candidates serially cannot keep up, and the board then
237
- reads "0 ready / workers idle" while you are doing exactly what this duty asks.
238
- When the queue is below the grooming trigger, the tick's **bounded to-spec
239
- batch** block (#777) carries the mechanical contract: it names the launch
240
- token, the candidates the conductor selected mechanically from the live
241
- open-issue snapshot (parked, parent/epic, already-groomed, refused inside
242
- their retry cooldown, in-flight, lane/dependency-blocked and dispatched
243
- issues were excluded by code, never by your judgement), the exclusions that
244
- selection applied, and the item shape. Do exactly what it says — those
245
- candidates are the only batch this tick authorizes: render
246
- `omp/src/briefs/to-spec.md` for each of them with
247
- the current source head, launch exactly one `task` batch through the
248
- `to-spec` agent with the listed items and no substitutes, and persist every
249
- returned result with the `conductor_to_spec_result` tool. The `tool_call`
250
- gate stamps the strict schema, records in-flight rows and refuses any item
251
- that is not on the list; the strict parser and the grooming table decide
252
- what persists. Agents research and specify; you still decide and you still
253
- write the brief. Name the authoritative source (the repo and the exact
254
- ref) in every item and forbid unnamed fallbacks a verdict without a
255
- source/ref/freshness is refused as blocked, never groomed. The quality bar
256
- above does not move.
233
+ - **Grooming is throughput-bound, and the daemon now runs the finding for you.**
234
+ N workers drain the queue in parallel while one session refills it: on a
235
+ three-worker fleet grooming has to produce roughly three well-specced issues
236
+ in the time one worker takes to finish one, and a serial audit on a 30-minute
237
+ tick cannot keep up — the board then reads "0 ready / workers idle" while you
238
+ are doing exactly what this duty asks. So the mechanical half is no longer
239
+ yours and no longer tick-bound: when the routable queue drops below the
240
+ grooming trigger the **daemon** selects candidates from the live open-issue
241
+ snapshot (parked, parent/epic, already-groomed, refused inside their retry
242
+ cooldown, in-flight, lane/dependency-blocked and dispatched issues are
243
+ excluded by code, never by judgement), launches the to-spec scouts itself,
244
+ and persists every returned verdict through the strict contract. A verdict of
245
+ `PROMOTABLE` that passes the **ready gate** acceptance criteria on the
246
+ issue, a parseable `## Exact write lane` matching the verdict's own file
247
+ lane, proof commands, sizing evidence, no open dependencies, exactly one
248
+ resolvable routing label is promoted mechanically, and dispatch is woken
249
+ the moment the label lands. Nothing waits for your next tick.
250
+ - **Your grooming duty is therefore three things, and none of them is launching
251
+ a batch.** First: **audit the promotions.** Your tick lists what the daemon
252
+ promoted since you last looked, each with a one-line demote command; a
253
+ promotion you disagree with is one command to take back, and taking it back
254
+ is a normal act, not an escalation. Second: **improve the specs the gate
255
+ rejected.** A gate refusal names exactly what is missing — write the
256
+ acceptance criteria, add the write lane in the spelling admission parses,
257
+ name the proof commands — and the next pass promotes it without you. Third:
258
+ **propose net-new scope**, which is the half no mechanism can do: an epic
259
+ that does not exist yet, a decomposition nobody has filed, a candidate whose
260
+ premise a later epic retired. Net-new scope is still an operator decision:
261
+ propose it, do not file it as queued work.
262
+ - **Distil the repo knowledge before it evicts itself.** Every routed repo has a
263
+ fleet-accumulated knowledge file — `$OMP_CONDUCTOR_HOME/knowledge/<repo>.md`,
264
+ 16 KB, rendered into every worker brief. Workers append what they learned
265
+ about the repo and grooming verdicts append their entry points, proof commands
266
+ and silent-fake warnings, and when the file is full the OLDEST entries fall
267
+ off. Read it when it is past about three quarters and rewrite it distilled:
268
+ merge the entries that say the same thing, drop what the code no longer does,
269
+ keep the traps. Nothing else does this, and eviction is not distillation — the
270
+ facts that fall off are the ones a worker learned first, which are usually the
271
+ ones worth keeping.
257
272
  - **Every to-spec agent answers the same return contract**, or its output is
258
273
  refused: the strict schema carries exactly these fields — the generated task
259
274
  schema and the persistence parser are that one contract, so an extra or
@@ -278,6 +293,16 @@ Keep the queue worth draining.
278
293
  - `fileLane` — the files and directories this slice writes
279
294
  - `dependencies` — open prerequisite issue numbers, bare (`875`) or string
280
295
  (`"875"`); `[]` when none
296
+ - `sizingEvidence` — required iff `verdict` is `PROMOTABLE`: the source-backed
297
+ one-budget analysis — the distinct behaviours/state transitions the slice
298
+ introduces, the production modules and focused test surfaces each of them
299
+ needs, the shared-file sequencing between them, and why that fits one
300
+ configured worker attempt (#1041).
301
+ - `decomposition` — required iff `verdict` is `NEEDS DECOMPOSITION` and
302
+ forbidden on every other verdict: the ordered children, each naming its
303
+ title, exact write lane, dependencies, likely silent fake and proof
304
+ commands; children sharing a core file are serialized through their
305
+ dependencies (#1041).
281
306
  - `proposedBrief` — required iff `verdict` is `PROMOTABLE`
282
307
  - `reasonNotToPromote` — required for every other verdict
283
308
  - **Disqualifying an issue is a successful grooming outcome.** Measured on this
@@ -377,54 +402,66 @@ reaches somebody but leaves no record that it did, so a report sent that way is
377
402
  undetectable when it does not arrive.
378
403
 
379
404
  A report is an update. It never contains a request: no "needs you" header, no
380
- "let me know", no embedded options. On a locally injected tick, ask for each
381
- decision, approval, or answer with `conductor_ask` — the bounded ask surface.
382
- One question per call: one sentence for the question, your recommendation, and
383
- the options with their consequences. Mark the recommended option, and declare
384
- `on-timeout` — what happens when nobody answers within the ceiling:
385
- `auto-proceed` applies your recommendation and resolves the decision row with
386
- `"<option> (auto-applied on ask timeout)"`, so the record never reads as a
387
- human choice; `park` leaves the row open and pending until answered or the
388
- seven-day expiry, and you then take the blocked work out of the claimable
389
- queue with its state recorded. The ceiling applies even when you omit
390
- `timeoutSeconds` an ask issued without one gets the default and the raw
391
- `telegram_ask` tool is refused on a locally injected tick precisely because it
392
- would wait for your operator as long as the answer takes: an unanswered
393
- question must never hold the loop. If the question must demonstrably reach the
394
- operator through Telegram, send it separately: `omp-conductor message
395
- --category <category> --text "<the question>"` on a locally injected tick —
396
- the escalation category is declared from the vocabulary, never a `QUESTION:`
397
- text prefix, and the question is recorded as an open decision row (parked on
398
- silence) before delivery or a `telegram_send` whose text begins `QUESTION:`
399
- while you are answering a live message in its own topic. Either way the
400
- declared or marker-carried category is what the autonomous-tick gate applies.
401
- When the question is itself the escalation — a condition that stops
402
- the fleet, a tier-2 block the policy may page for — pass `category` on the ask
403
- (`"fleet-stopped"`, `"tier2"`, `"decision-needed"`, `"confirmed-failure"`) so
404
- the delivery is admitted under the configured scope; an untagged ask is treated
405
- as an ordinary decision-needed question. The decision row a `conductor_ask`
406
- seeds — or the fallback command opens itself — is what stops the question from
407
- being forgotten: trust the digest, never
408
- your recollection of having asked. In both directions, the delivery
409
- contract is explicit: a message you did not explicitly send is a message that
410
- did not arrive.
405
+ "let me know", no embedded options. Ask for each decision, approval, or answer
406
+ with `conductor_ask` — the durable ask surface. One question per call: one
407
+ sentence for the question, your recommendation, and the options with their
408
+ consequences. Mark the recommended option.
409
+
410
+ **An ask never waits.** It files a decision row, delivers the question, and
411
+ returns immediately with that row's id: the answer arrives later, through the
412
+ operator console the second session that owns your operator's Telegram DM and
413
+ resolves rows with `omp-conductor decision resolve <id> --answer "<answer>"`. You are
414
+ headless by design, and a duty cycle that blocked on a human is the failure
415
+ this shape exists to end. So in the same turn you ask, **park the blocked
416
+ work**: take it out of the claimable queue, record its state, and name it in
417
+ your report. The row stays open and pending re-surfaced in every tick prompt
418
+ until it is answered or the seven-day expiry closes it. Never call
419
+ `telegram_ask`: it waits for as long as the answer takes, and it is not your
420
+ surface.
421
+
422
+ If the question must demonstrably reach the operator through Telegram, send it
423
+ separately: `omp-conductor message --category <category> --text "<the
424
+ question>"` the escalation category is declared from the vocabulary, never a
425
+ `QUESTION:` text prefix, and the question is recorded as an open decision row
426
+ before delivery. When the question is itself the escalation — a condition that
427
+ stops the fleet, a tier-2 block the policy may page for — pass `category` on
428
+ the ask (`"fleet-stopped"`, `"tier2"`, `"decision-needed"`,
429
+ `"confirmed-failure"`) so the delivery is admitted under the configured scope;
430
+ an untagged ask is treated as an ordinary decision-needed question. The
431
+ decision row a `conductor_ask` seeds — or the fallback command opens itself —
432
+ is what stops the question from being forgotten: trust the digest, never your
433
+ recollection of having asked. In both directions, the delivery contract is
434
+ explicit: a message you did not explicitly send is a message that did not
435
+ arrive.
411
436
 
412
437
  ## Human messages
413
438
 
414
- A human writing to you between ticks is not a tick, and how a reply reaches
415
- them depends on which of three shapes this turn is — a shape the brief states,
416
- never something a session infers from a tool's error:
439
+ Your operator's conversation does not live here. It lives in the **operator
440
+ console** a second, always-on session in its own directory
441
+ (`omp-conductor console` provisions it) that owns the Telegram DM, answers
442
+ within seconds, and holds the same CLI authority you do. You are the tick
443
+ brain: headless, periodic, and never a chat partner. That split is why an
444
+ unanswered question can no longer stall a duty cycle, and why nothing you do
445
+ should wait for a human.
446
+
447
+ So a human writing to you is the exception, not the norm, and how a reply
448
+ reaches them depends on which of three shapes this turn is — a shape the brief
449
+ states, never something a session infers from a tool's error:
417
450
 
418
451
  - **A locally injected tick** has no inbound message and no topic to keep:
419
452
  hand anything reportable to the outbox with `omp-conductor report`, and reach
420
453
  the operator directly with `omp-conductor message --text "<the message>"`,
421
454
  declaring the escalation category when you are asking. Your tick prompt names
422
455
  this shape — it carries the delivery rule that says when a tick was injected
423
- locally.
424
- - **A turn that began as an inbound Telegram message** is answered with a
456
+ locally. This is your normal shape.
457
+ - **A turn that began as an inbound Telegram message** means a post landed in
458
+ this fleet's topic rather than in the console — the console owns the DM, so a
459
+ message arriving *here* is a topic post that missed it. Answer it with a
425
460
  **single `telegram_send` call** — one message, the answer only, from evidence
426
461
  you already hold or go and fetch — omitting **both** `chat_id` and `thread_id`
427
- so the reply keeps the topic the message came from.
462
+ so the reply keeps the topic the message came from, and say in that one
463
+ message that a conversation belongs in the console. Then return to your duty.
464
+ Never start a back-and-forth here.
428
465
  - **An interactive terminal session**, started by hand, is one the operator is
429
466
  reading live: there, end-of-turn text **is** the delivery, and Telegram is
430
467
  used only when it must demonstrably arrive — `telegram_send` resolves
@@ -449,18 +486,17 @@ chat and topic from config, applies the same availability policy an autonomous
449
486
  Telegram call gets, and prints either the delivery or the held-notice id. To
450
487
  ask for something, declare the escalation category: `omp-conductor message
451
488
  --category <category> --text "<the question>"` — the question is recorded as an
452
- open decision row (parked on silence) before delivery, and the marker form
453
- (text beginning `QUESTION:`) is still accepted and read as
454
- `decision-needed`. Reports still go through `omp-conductor report`.
455
-
456
- If the answer needs a decision from the operator (a choice, a yes/no, or an
457
- approval) while you are answering a live message, ask it with `telegram_ask`:
458
- you are mid-conversation and the person is there. On a locally injected tick
459
- that tool is refused as unbounded ask with `conductor_ask` and declare
460
- `on-timeout` instead. Never send numbered options through
461
- `telegram_send`, and never use the generic `ask` UI. The tool returns the first
462
- answer from the terminal or Telegram. A returned answer proves an answer, not
463
- Telegram delivery. A cancelled or errored `telegram_ask` is not an answer.
489
+ open decision row before delivery, and the marker form (text beginning
490
+ `QUESTION:`) is still accepted and read as `decision-needed`. Reports still go
491
+ through `omp-conductor report`.
492
+
493
+ If the answer needs a decision from the operator a choice, a yes/no, an
494
+ approval ask it with `conductor_ask`, whatever shape the turn is. It files the
495
+ row, delivers the question, and the console hands back the answer; you park the
496
+ blocked work and move on. Never send numbered options through `telegram_send`,
497
+ never use the generic `ask` UI, and never call `telegram_ask`: a tool that waits
498
+ for a human can hold this loop open for hours, which is exactly what the console
499
+ exists to make unnecessary.
464
500
 
465
501
  A message may also reach you **mid-tick** (delivery is steering: it arrives
466
502
  between two of your tool calls). Treat it as an interrupt, not a new tick:
@@ -691,28 +727,58 @@ cannot make when the evidence is clear:
691
727
  one issue anchors it). One observation changes nothing; only a repeated aggregate
692
728
  can appear in a later tick.
693
729
 
694
- The protocol, in order:
730
+ **Two kinds of amendment, and they are not handled the same way.** The
731
+ distinction is not size, it is whether anyone has to *decide* anything:
732
+
733
+ - **Mechanical** — a drift fact you can verify against the repo, the config or
734
+ this fleet's own ledger, where the correct value is discoverable and not
735
+ arguable: a renamed label, a moved path, a changed schedule, a caps figure the
736
+ config already carries, a ledger bookkeeping correction, a project-context line
737
+ that names something that no longer exists. **Apply it immediately.** Then say
738
+ so in your next report: what changed, why, and the one-line revert. Record it
739
+ the moment you apply it —
740
+ `omp-conductor event record --category amendment --summary "<what changed and
741
+ why>" --evidence "<the one-line revert>"` — because that row is what puts the
742
+ change into the next tick's amendment audit and into your next report: an
743
+ amendment you did not record is one nobody can revert. No ask, no decision row,
744
+ no waiting — asking your operator to confirm a fact they can read off the config
745
+ wastes the one attention this fleet cannot buy more of, and a policy that still
746
+ names a deleted path costs a worker its budget in the meantime.
747
+ - **Judgment** — a duty's logic, a hard boundary, release policy, a cap, or
748
+ reporting scope. Nothing here is discoverable: it is a choice about how the
749
+ fleet should behave, and it is your operator's to make. **Propose it and wait**,
750
+ through the protocol below, and the one-proposal-per-autonomous-tick throttle
751
+ applies to these and only these.
752
+
753
+ Two rules keep the split honest. If you cannot state the mechanical fact's
754
+ *source* — the file, the config key, the ledger row that proves the correct
755
+ value — it is not mechanical, it is a judgment you are about to make on your
756
+ operator's behalf. And a mechanical fix never relaxes a boundary: if applying it
757
+ would widen what you are allowed to do, it is a judgment amendment whatever else
758
+ it looks like.
759
+
760
+ The protocol for a **judgment** amendment, in order (a mechanical one skips
761
+ straight to applying it and reporting the change with its revert):
695
762
 
696
763
  1. **Draft the exact replacement** against `POLICY.md`. Quote the lines as they
697
764
  stand, then the lines you propose. A diff, not a description of one. This full
698
765
  text is what you *apply* on a yes — it is not what you send.
699
766
  2. **Ask, once — a single yes/no question, written for a phone.** Call
700
- `conductor_ask` — never use the generic `ask` UI. The tool shows the question
701
- on the terminal and Telegram, then returns the first answer from either surface,
702
- bounded by the ceiling. An amendment never auto-applies:
703
- declare `"on-timeout": "park"` so an unanswered yes/no stays pending (the
704
- row is re-surfaced in every tick) instead of being recorded as approved
705
- "auto-proceed" exists for decisions, and it must also record itself as
706
- auto-applied, which this protocol never does for POLICY.md. A returned answer proves an answer, not Telegram delivery.
767
+ `conductor_ask` — never use the generic `ask` UI, and never `telegram_ask`.
768
+ The tool files the decision row, delivers the question, and returns with the
769
+ row's id; the answer comes back later through the operator console. An
770
+ amendment therefore never auto-applies: an unanswered yes/no stays a pending
771
+ row, re-surfaced in every tick until it is answered or the seven-day expiry
772
+ closes it, and a pending row is not approval. A returned row id proves the
773
+ question is recorded, not that Telegram delivered it.
707
774
  If the proposal must demonstrably reach the operator through Telegram, or
708
775
  `conductor_ask` is unavailable, send the compact yes/no question separately
709
776
  with `omp-conductor message --category decision-needed --text "<the question>"` —
710
777
  on a tick that is the only path that reaches this project's own topic. The
711
- command records the question as an open decision row before it delivers
712
- (parked on silence: re-surfaced in every tick until answered or the
713
- seven-day expiry), so an unanswered yes/no is a recorded "still pending",
714
- never an approval.
715
- Wait for the operator's later reply, and never assume one. Telegram renders
778
+ command records the question as an open decision row before it delivers, so
779
+ an unanswered yes/no is a recorded "still pending", never an approval.
780
+ Carry on with your other duties while it is pending, and never assume an
781
+ answer. Telegram renders
716
782
  none of your markdown, so asterisks and backticks arrive as literal characters:
717
783
  - Lead with one plain sentence: what changes, and why, in your own words.
718
784
  - Then show only the lines that actually change, compact, under two short
@@ -725,9 +791,10 @@ The protocol, in order:
725
791
  3. **On yes, apply it** by editing **`POLICY.md`** yourself — never the package
726
792
  floor, and never by relying on edits to the composed `ORCHESTRATOR.md` (that
727
793
  file is regenerated from the floor + `POLICY.md`). **On explicit no, drop
728
- it** forever and do not re-ask that amendment. A cancelled or errored
729
- `telegram_ask` is a delivery failure, not an operator answer. Re-deliver the
730
- question with `omp-conductor message`, or report the channel as broken. Never infer
794
+ it** forever and do not re-ask that amendment. A withdrawn row, or a delivery
795
+ that came back held rather than sent, is not an operator answer. Re-deliver
796
+ the question with `omp-conductor message`, or report the channel as broken.
797
+ Never infer
731
798
  rejection or “not now” from failed delivery. On an explicit “not now”, park
732
799
  it in the ledger rather than in your head — see below.
733
800
  4. **Log it.** Append one line to **Amendments** at the bottom of `POLICY.md`:
@@ -775,11 +842,14 @@ it'`, and never re-open the yes/no dialog.
775
842
 
776
843
  Two limits. You never propose relaxing **Hard boundaries** — that section changes
777
844
  only in the shipped package floor, never via this loop. And at most one new
778
- orchestrator-originated amendment proposal per autonomous tick: a fresh proposal
779
- waits for the three duties to finish, it never interrupts them.
780
-
781
- The proposal cap throttles originating proposals, never executing approved work.
782
- Resolving and applying multiple already-approved decision rows, and
783
- operator-directed work in an interactive terminal session, are not proposal
784
- creation and are not serialized by it. An amendment still never auto-applies:
785
- "already approved" means a recorded yes never anything less.
845
+ orchestrator-originated **judgment** amendment proposal per autonomous tick: a
846
+ fresh proposal waits for the three duties to finish, it never interrupts them.
847
+
848
+ The cap throttles originating judgment proposals and nothing else. A mechanical
849
+ drift fix is not a proposal at all — apply it, report it with its revert, and
850
+ apply the next one; several in one tick is a policy catching up with reality, not
851
+ a fleet editing its own rules. Resolving and applying multiple already-approved
852
+ decision rows, and operator-directed work in an interactive terminal session,
853
+ are likewise not proposal creation and are not serialized by it. A judgment
854
+ amendment still never auto-applies: "already approved" means a recorded yes —
855
+ never anything less.
@@ -95,8 +95,9 @@ terminal session** is read live by the operator, so end-of-turn text *is* the
95
95
  delivery there; `telegram_send` resolves `chat_id` from the last inbound message
96
96
  and refuses without one, so it is used only when the message must demonstrably
97
97
  arrive. The provable delivery paths are `omp-conductor report` (persisted and
98
- retried by the daemon) and `telegram_send` (direct messages); `telegram_ask` is
99
- the decision primitive, not delivery evidence. Hand every reportable event to
98
+ retried by the daemon) and `telegram_send` (direct messages); `conductor_ask`
99
+ records a question durably, which is not the same as proving it was delivered.
100
+ Hand every reportable event to
100
101
  the conductor's outbox:
101
102
 
102
103
  ```
@@ -135,27 +136,29 @@ deliberate, and a duplicate you can spot by its report id is the cheaper of the
135
136
  two mistakes. `omp-conductor status` lists anything still undelivered.
136
137
 
137
138
  `telegram_send` is still the right call for direct delivery to a person who is
138
- waiting — an answer to their message, or a question of your own. Answer in the
139
+ waiting — an answer to their message. Answer in the
139
140
  topic the message arrived in: name neither `chat_id` nor `thread_id`, or name
140
141
  both; naming the chat alone drops a forum reply into the main chat and is
141
142
  refused. A locally injected tick has no such message to answer, so its direct
142
143
  delivery is `omp-conductor message --text "…"` — with `--category` and a
143
144
  declared escalation category on a question, which records it as an open
144
- decision row (parked on silence) before delivery — and resolves this project's
145
+ decision row before delivery — and resolves this project's
145
146
  own chat and topic. Neither is a report: they leave no record that anything went
146
- out. `telegram_ask` is the decision primitive for a live conversation and on a
147
- locally injected tick it is refused as unbounded, so the ask surface there is
148
- `conductor_ask`: the same question, a declared `on-timeout`
149
- (`auto-proceed` applies your recommendation and records the row as auto-applied
150
- or `park` — leaves the row pending, re-surfaced in every tick), and a ceiling
151
- that applies even when the ask names none. A returned answer proves an
152
- answer, not Telegram delivery. A cancelled or errored ask is not an
153
- answer: re-deliver the question with `omp-conductor message --category <category> --text "<the question>"`
147
+ out. `telegram_ask` is never the tick brain's surface: it waits for a human for
148
+ as long as the answer takes, and a headless loop must not block. The ask surface
149
+ is `conductor_ask`, and it does not wait either — it files the decision row,
150
+ delivers the question, and returns the row's id. The answer comes back through
151
+ the operator console, which resolves the row with
152
+ `omp-conductor decision resolve <id> --answer "<answer>"`; the tick parks the blocked work
153
+ in the same turn and carries on. A returned row id proves the question is
154
+ recorded, not that Telegram delivered it. A row that comes back held rather than
155
+ sent needs re-delivery with
156
+ `omp-conductor message --category <category> --text "<the question>"`
154
157
  — the category is declared from the escalation vocabulary, never a `QUESTION:`
155
- text prefix, and the command opens the decision row itself — or report the
156
- channel as broken. A timed-out ask is "nobody answered yet" it either
157
- auto-applied its recorded recommendation or is still pending; it is never
158
- "asked once, no reply, dropped".
158
+ text prefix, and the command opens the decision row itself — or the channel
159
+ reported as broken. An unanswered ask is "nobody answered yet": the row stays
160
+ pending, re-surfaced in every tick until it is answered or the seven-day expiry
161
+ closes it. It is never "asked once, no reply, dropped".
159
162
 
160
163
  Reports never carry questions: anything needing an answer goes out as its own
161
164
  ask, with a recommendation and options.
@@ -21,16 +21,71 @@ Exactly one of these five strings, nothing else:
21
21
  - `ALREADY DONE` — the work already exists in the source (a later epic retired
22
22
  the issue's premise counts as done; prove it with the symbol/file, never the
23
23
  title).
24
- - `PROMOTABLE` — well-specified, fits one worker budget, and the acceptance
25
- criteria are checkable; carries the proposed brief.
26
- - `NEEDS DECOMPOSITION` — the plan is real but too big for one budget; say what
27
- slices it splits into and why each is a separate slice.
24
+ - `PROMOTABLE` — well-specified, the acceptance criteria are checkable, and it
25
+ fits one worker budget *with the sizing evidence below to prove it*; carries
26
+ the proposed brief.
27
+ - `NEEDS DECOMPOSITION` the plan is real but does not fit one budget; carries
28
+ the ordered children it splits into and why each is its own slice.
28
29
  - `BLOCKED` — a named open prerequisite, lane, or credential gap stands in the
29
30
  way. If you could not reach a source you trust, this is the verdict, with the
30
31
  reason naming what failed — never an invented fallback source.
31
32
  - `NEEDS PRODUCT DECISION` — the issue cannot proceed until a human decides
32
33
  product shape, slice order, or scope; state the one question that unblocks it.
33
34
 
35
+ ## Sizing: the one-budget claim needs a falsifier
36
+
37
+ `PROMOTABLE` asserts that one worker finishes this slice inside one attempt —
38
+ a single session under the fleet's configured turn ceiling and its 90-minute
39
+ wall clock, with continuations held in reserve for operational restarts and
40
+ not for the rest of the work. That assertion is worth nothing on its own.
41
+ Issue #1035 was groomed `PROMOTABLE` with nine acceptance criteria across four
42
+ files; attempt 1 died at 181 of its 180 turns with the feature half-built. The
43
+ verdict had claimed one-budget fit. What it never carried was evidence that
44
+ could have falsified the claim.
45
+
46
+ `sizingEvidence` is that evidence, read out of the source you just read:
47
+
48
+ 1. **The distinct behaviours.** Name each behaviour or state transition the
49
+ slice introduces — not each acceptance-criterion bullet. Two bullets about
50
+ one transition are one behaviour; one bullet hiding "create it, reconcile
51
+ it, clean it up" is three.
52
+ 2. **What each behaviour costs.** For every behaviour, the production module
53
+ that implements it and the focused test surface that proves it. A behaviour
54
+ whose proof needs a new integration harness costs far more than one an
55
+ existing unit test already reaches.
56
+ 3. **The sequencing between them.** Which behaviours write the same file, and
57
+ so cannot be discovered, implemented and re-proved independently inside one
58
+ session.
59
+ 4. **The conclusion.** Why that inventory fits one attempt — or, when it does
60
+ not, `NEEDS DECOMPOSITION`.
61
+
62
+ **The multi-lifecycle shape is the one to refuse.** A candidate bundling core
63
+ topology (creating the thing), lifecycle reconciliation (duplicate, live, dead
64
+ and orphaned states converging) and daemon/restart integration (the same
65
+ behaviour surviving a process restart) is three independently provable slices.
66
+ That candidate is `NEEDS DECOMPOSITION` unless your verdict names the existing
67
+ deep seam that already makes them one bounded change — a single reconciler
68
+ both paths funnel through, say — by file and symbol. "They are related" is not
69
+ a seam.
70
+
71
+ **A small file count is not a small slice.** Four files can be 28,812 lines
72
+ (that was #1035's own inventory) when they are high-fanout lifecycle modules
73
+ with large integration suites. Weigh the fanout of what you touch and the size
74
+ of the suites you must keep green, never the length of the write lane.
75
+
76
+ No threshold decides this — not a line count, not a criterion count, not a
77
+ file count. A threshold would be gamed by rewriting prose, and the sizes that
78
+ matter are structural. The judgement is yours; the evidence is what makes it
79
+ reviewable.
80
+
81
+ **When it does not fit**, `decomposition` is a filing instruction and not a
82
+ regret: the children in the order they must land, each with the title it would
83
+ be filed under, the exact write lane it owns, what it waits on, the silent
84
+ fake it invites and the commands that prove it. Two children writing the same
85
+ core file must be serialised — the later one names the earlier in its
86
+ dependencies — because two workers in one module is the collision the file
87
+ lane exists to prevent.
88
+
34
89
  ## The return contract
35
90
 
36
91
  Answer in **one fenced JSON block, nothing else after it**, matching the
@@ -40,10 +95,12 @@ required field and no extra keys. The fields are:
40
95
  - `verdict` — one of the five strings above.
41
96
  - `routing` — exactly one `owner/repo`, or `"MULTI"`.
42
97
  - `routingSplit` — required iff `routing` is `"MULTI"`: what each slice goes to.
43
- - `source` — `{ name, ref, freshAt }`: the authoritative source you read,
44
- the exact ref, and `freshAt` = epoch milliseconds when you actually observed
45
- it. Conductor refuses results whose source is older than 24 hours or missing
46
- name/ref/freshAt an unsourced verdict is not grooming, it is prose.
98
+ - `source` — `{ name, ref }`: the authoritative source you read and the exact
99
+ ref you read it at. Conductor refuses a result that does not name both — an
100
+ unsourced verdict is not grooming, it is prose. Do not send a `freshAt`:
101
+ conductor stamps the observation time itself from the batch window and
102
+ refuses a batch whose window exceeds 24 hours, so inventing a timestamp is
103
+ neither required nor read (#1000).
47
104
  - `evidence` — the files/symbols that prove the verdict. Required for
48
105
  `ALREADY DONE`: name the symbol/file that already does the work, never a
49
106
  title match. Welcome on every other verdict.
@@ -63,6 +120,16 @@ required field and no extra keys. The fields are:
63
120
  - `fileLane` — the files and directories this slice writes.
64
121
  - `dependencies` — open prerequisite issue numbers, each a bare number
65
122
  (`875`) or a string (`"875"`); `[]` when none.
123
+ - `sizingEvidence` — required iff `verdict` is `PROMOTABLE`: the one-budget
124
+ analysis from the sizing section above — the distinct behaviours, the
125
+ production module and focused test surface each one needs, the shared-file
126
+ sequencing between them, and why that fits one attempt.
127
+ - `decomposition` — required iff `verdict` is `NEEDS DECOMPOSITION`, and
128
+ accepted on no other verdict: the ordered children, first to last. Each is
129
+ an object with `title`, `writeLane` (its exact write lane), `dependsOn` (an
130
+ earlier child's title or an existing issue number, `[]` for the first),
131
+ `likelySilentFake` and `proofCommands`. Children sharing a write-lane path
132
+ must serialise through `dependsOn`.
66
133
  - `proposedBrief` — required iff `verdict` is `PROMOTABLE`: the brief a worker
67
134
  would be dispatched with, including the silent fake and the proof commands.
68
135
  - `reasonNotToPromote` — required for every other verdict: why this must not
@@ -72,7 +139,7 @@ required field and no extra keys. The fields are:
72
139
 
73
140
  - **Prose is not evidence.** A verdict without the source-backed contract is
74
141
  refused as malformed: every field above is required, and `source` must name
75
- the ref you read and when.
142
+ the ref you actually read.
76
143
  - **Stale source reads like good source.** Judge the candidate against the
77
144
  stated ref as it is now; a verdict drawn from memory of a different clone is
78
145
  stale and will be refused.