instar 1.3.1313 → 1.3.1315

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.1313",
3
+ "version": "1.3.1315",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-10-02T01:51:41.995Z",
5
- "instarVersion": "1.3.1313",
4
+ "generatedAt": "2026-10-02T10:55:24.335Z",
5
+ "instarVersion": "1.3.1315",
6
6
  "entryCount": 203,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -1539,7 +1539,7 @@
1539
1539
  "type": "subsystem",
1540
1540
  "domain": "server",
1541
1541
  "sourcePath": "src/server/AgentServer.ts",
1542
- "contentHash": "1f54e9359f0c4604ad4b9d5e8420f7be0a33907c000ae5e1ac957c82d4ef9434",
1542
+ "contentHash": "015d93053813a521d0f06087f9bea72a07bd76cc7ca424d584ffd9ea06e76508",
1543
1543
  "since": "2025-01-01"
1544
1544
  },
1545
1545
  "subsystem:session-manager": {
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "35448db940d659661ebdfb09761439033f93a15157540ee2f6eecf825488e1e6",
5
- "packageVersion": "1.3.1313",
5
+ "packageVersion": "1.3.1315",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "8ce42dd4c6abeb23b4d5187b6de490f3f39b33ca878b15d820aa4f1822315c16",
2
+ "sha256": "81bd9fa3c78c88ad9c840e55c57dda4d4c4566f3fea67f8defd03cf97099e00f",
3
3
  "registrySha256": "35448db940d659661ebdfb09761439033f93a15157540ee2f6eecf825488e1e6",
4
- "packageVersion": "1.3.1313"
4
+ "packageVersion": "1.3.1315"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "35448db940d659661ebdfb09761439033f93a15157540ee2f6eecf825488e1e6",
3
3
  "articleCount": 92,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1313"
5
+ "packageVersion": "1.3.1315"
6
6
  }
@@ -3,7 +3,7 @@ name: Feedback-Factory Operating Drain
3
3
  description: "Cadenced end-to-end feedback drain. POST /feedback-factory/drain/tick clusters canonical input, runs registered frontier-model readiness judgment inside deterministic floors, enqueues one durable outbox row per readiness epoch, and—after separate consumer promotion—creates and reads back one Initiative task. Development-agent processing/drain is live; fleet remains dark; consumer ships simulation-first. Spec: docs/specs/feedback-factory-operating-drain.md."
4
4
  schedule: "*/30 * * * *"
5
5
  priority: low
6
- expectedDurationMinutes: 2
6
+ expectedDurationMinutes: 5
7
7
  model: haiku
8
8
  supervision: tier1
9
9
  enabled: true
@@ -36,7 +36,8 @@ TOKEN = os.environ.get('INSTAR_AUTH_TOKEN') or (CONFIG.get('authToken') if isins
36
36
  AGENT_ID = os.environ.get('INSTAR_AGENT_ID') or str(CONFIG.get('projectName') or '')
37
37
  BASE = 'http://localhost:' + str(os.environ.get('INSTAR_PORT') or CONFIG.get('port') or 4042)
38
38
  DEVELOPMENT_AGENT = CONFIG.get('developmentAgent') is True
39
- POLL_SECONDS = float(os.environ.get('FEEDBACK_DRAIN_POLL_SECONDS') or 60)
39
+ # A run is capped at 115 s of wall clock; poll past that so a slow run's outcome is still read here.
40
+ POLL_SECONDS = float(os.environ.get('FEEDBACK_DRAIN_POLL_SECONDS') or 180)
40
41
 
41
42
  def fail(reason):
42
43
  path = os.environ.get('INSTAR_JOB_FAILURE_FILE')
@@ -103,7 +104,9 @@ run_id = tick['runId']
103
104
  if tick.get('proxied'):
104
105
  done('proxied to the owner machine, run ' + run_id + '; the owner records its outcome')
105
106
 
106
- # 3. Poll this run to a terminal state (60 s). Still running at the limit is not a failure.
107
+ # 3. Poll this run to a terminal state. Still running past the drain's wall clock is a failed run:
108
+ # no later cadence reads this run's outcome (live 2026-10-01: two degraded 70 s runs were recorded
109
+ # as successes by a 60 s poll).
107
110
  deadline = time.time() + POLL_SECONDS
108
111
  last = None
109
112
  after = None
@@ -114,7 +117,8 @@ while time.time() < deadline:
114
117
  break
115
118
  time.sleep(3)
116
119
  else:
117
- done('run ' + run_id + ' still in flight; the next cadence observes it')
120
+ seen = (str(last.get('runId')) + ' ' + str(last.get('state'))) if isinstance(last, dict) else 'no lastRun (HTTP ' + str(code) + ')'
121
+ fail('drain run ' + run_id + ' still in flight after ' + str(int(POLL_SECONDS)) + ' s, past the drain wall clock (last seen: ' + seen + ')')
118
122
 
119
123
  # 4. Tier-1 supervision of the terminal run.
120
124
  run_state = last.get('state')
@@ -131,7 +135,7 @@ The script prints one `FEEDBACK_DRAIN_RESULT` line. It records a failed run itse
131
135
  - Posture `dark` → silent exit. `unavailable` → failed run. Unreadable status → failed run on a development agent, silent exit on the fleet.
132
136
  - A refused tick (any HTTP code other than 202, e.g. `current registered readiness agent required` after the authority was demoted) → failed run.
133
137
  - A tick proxied to the owner machine → success here; its outcome lives in the owner's run history and status.
134
- - Terminal `succeeded` / `no-op` → success. Terminal `degraded`, `failed` or `abandoned` → failed run carrying the drain's reason.
138
+ - Terminal `succeeded` / `no-op` → success (a `succeeded` run may carry an informational reason such as `readiness-time-exhausted-rest-due`). Still in flight after 180 s → failed run. Terminal `degraded`, `failed` or `abandoned` → failed run carrying the drain's reason.
135
139
  - In simulation, claimed/completed work counts must not advance; if they do → failed run.
136
140
 
137
141
  Never retry in the same run; the durable queue and the next cadence own retry. Then exit silently. Do NOT relay anything to Telegram and do NOT summarize. A failed run reaches the operator through the scheduler's consecutive-failure alert.
@@ -0,0 +1,52 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The feedback drain's readiness stage sent all due clusters, up to the authority's
9
+ `maxBatch`, to the registered model in one call. Live, at about 3-4 s per candidate, a
10
+ 50-candidate call hit the 60 s call budget every time, and the run degraded with
11
+ `readiness-authority-failed`.
12
+
13
+ - A tick now reviews its due clusters in sequential calls of at most
14
+ `feedbackFactory.drain.readinessChunkSize` (default 10). The total per tick is still capped
15
+ at the authority's `maxBatch`.
16
+ - Later calls are sized from the pace measured earlier in the same tick, so the last call
17
+ still fits the tick's wall clock. 20 s is kept for the stages after readiness. Rows not
18
+ reached stay due for the next tick.
19
+ - Each call's decisions apply as it returns. A later failure no longer discards them.
20
+ - Spend is reserved per call, against the same daily cap.
21
+ - A contract violation still demotes the authority at once. A tick with any successful call
22
+ resets the transient-failure count. A tick in which every call failed counts once (3 in a
23
+ row demote).
24
+ - `feedbackFactory.drain.maxWallClockMs` is now honoured. It was declared (90 s, as the spec
25
+ says) but ignored, so the service ran on 115 s.
26
+
27
+ Prompt and schema versions are unchanged, so the registered authority stays valid.
28
+
29
+ ## What to Tell Your User
30
+
31
+ The feedback sorter was asking its model about too many feedback groups at once, and the
32
+ model ran out of time every time. It now asks about ten at a time and keeps going while time
33
+ allows. Earlier answers are kept even if a later question fails. Nothing for you to do.
34
+
35
+ ## Summary of New Capabilities
36
+
37
+ - The feedback drain reviews readiness in time-bounded chunks (`feedbackFactory.drain.readinessChunkSize`, default 10).
38
+
39
+ ## Evidence
40
+
41
+ From the Mac Studio's live store (a read-only copy):
42
+ - Runs 2b4bebb3 and 0e7b229a: 50 candidates, 60.1 s each, both timeouts.
43
+ - Run 20c4fac7: 10 candidates, 32.6 s, succeeded.
44
+
45
+ Tests replay the recorded gpt-6-astra replies with that latency shape:
46
+ - At a 90 s tick, the calls are 10, 10 and 2 (22 clusters decided).
47
+ - A timeout on the second call keeps the first call's 5 `ready` decisions and resets the
48
+ failure count.
49
+ - The old single 50-candidate call times out.
50
+
51
+ Unit (11), integration (2) and e2e (1) cover this. Of the original 11, 10 fail on the pre-fix
52
+ source; the 11th guards the unchanged demotion rule.
@@ -0,0 +1,51 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Two live faults on 2026-10-01 (Mac Studio, v1.3.1314, authority gen 4 with `maxBatch` 50):
9
+
10
+ - **A good tick was labelled degraded.** Runs ef0f6cbf and 10674e04 each made two good
11
+ readiness calls (about 20 and 17 decisions applied, zero provider errors). Then each started
12
+ a one-candidate call with under 8 s left. A call also pays about 9 s of start-up that the
13
+ per-candidate pace does not show, so the drain's own tick clock cut it at 70.0 s, and the
14
+ run ended `degraded` / `readiness-authority-failed`.
15
+ - A call after the first now needs at least 25 s left (`MIN_LATER_READINESS_CALL_MS`).
16
+ - If the tick clock (not the provider) cuts a later call short, its rows stay due,
17
+ untouched.
18
+ - Either way, a tick that already reviewed some candidates ends `succeeded`. It carries
19
+ the note `readiness-time-exhausted-rest-due` in the run's reason.
20
+ - Real failures stay degraded: a provider timeout or error, a contract violation, the
21
+ spend brake, or a first call that cannot finish.
22
+ - **The job recorded those degraded runs as successes.** The `feedback-factory-process` body
23
+ polled for 60 s, the runs took 70 s, and it exited "still in flight; the next cadence
24
+ observes it". No later cadence ever reads an earlier run. It now polls for 180 s, which is
25
+ above the drain's 115 s wall-clock ceiling. A run still in flight after that fails the job.
26
+ `expectedDurationMinutes` goes from 2 to 5. Existing agents get the new body and manifest
27
+ through `installBuiltinJobs` on update. The scheduler's declared-failure file was already
28
+ wired correctly.
29
+
30
+ ## What to Tell Your User
31
+
32
+ The feedback sorter sometimes ran out of time near the end of a run and wrongly called the
33
+ whole run a failure, even though every answer it got was fine. It now just stops and leaves
34
+ the rest for the next run. Its scheduled job also waits long enough to see how a slower run
35
+ ended, so real failures show up as failures. Nothing for you to do.
36
+
37
+ ## Summary of New Capabilities
38
+
39
+ - Drain runs that stop for lack of tick time end `succeeded`, with the note `readiness-time-exhausted-rest-due`.
40
+
41
+ ## Evidence
42
+
43
+ - Live store (read-only `.backup` copy): both runs' readiness rows were stamped at about 32 s,
44
+ 62 s and 70.0 s. The single 70.0 s row in each run was marked `readiness-authority-failed`.
45
+ - The job transcripts read `FEEDBACK_DRAIN_RESULT run run:ef0f6cbf… still in flight` and
46
+ `… run:10674e04… still in flight`.
47
+ - Unit, integration and e2e tests use the live latency shape (a fixed start-up cost plus a
48
+ per-candidate pace). The e2e runs the shipped job script against the production
49
+ AgentServer. On the pre-fix source the calls are `[5, 4, 1]` and the run is degraded. After
50
+ the fix the calls are `[5, 4]`, the run succeeded with the note, the rest stays due, and the
51
+ job records success.
@@ -0,0 +1,35 @@
1
+ # ELI16 — the feedback sorter asks about ten groups at a time
2
+
3
+ ## The problem
4
+
5
+ The feedback sorter ("drain") groups similar bug reports. Then it asks a model which groups
6
+ are ready to become a task. It used to put up to 50 groups into one question.
7
+
8
+ The model takes about 3 to 4 seconds per group. Fifty groups need about three minutes, but
9
+ each question is allowed one minute. So the big question ran out of time every time. The
10
+ run failed and no group got a verdict. With ten groups per question, the answer came back
11
+ in about half a minute.
12
+
13
+ ## The fix
14
+
15
+ The sorter now asks about at most ten groups per question, one question after another. It
16
+ keeps going until it reaches the run's group limit or runs low on time. Each answer is saved
17
+ as soon as it arrives. If a later question fails, the earlier answers still count.
18
+
19
+ Before each question it checks the clock. If the next question would not finish in time, it
20
+ stops and leaves the remaining groups for the next run. That run starts with them.
21
+
22
+ The rule for "the model is broken, stop trusting it" counts whole runs:
23
+ - A run where at least one question worked shows the model is fine, and resets the count.
24
+ - A run where every question failed counts once.
25
+ - Three such runs in a row switch the model off.
26
+ - A wrong-shaped answer still switches it off at once.
27
+
28
+ A setting for the run's time limit (90 seconds) existed but was never used. The sorter
29
+ quietly used 115 seconds. It now follows the setting.
30
+
31
+ ## What does not change
32
+
33
+ The question wording, the answer format, the daily spending cap and the approved group
34
+ limit are the same. The spending cap is now checked before each question rather than once
35
+ per run.
@@ -0,0 +1,36 @@
1
+ # ELI16 — "ran out of time" is not "broken", and the job now waits for the answer
2
+
3
+ ## The problem
4
+
5
+ The feedback sorter ("drain") asks a model which groups of bug reports are ready to become a
6
+ task. It asks about up to ten groups per question, one question after another, until its
7
+ run's time is nearly used up.
8
+
9
+ It guessed how long the next question would take from how long earlier questions took per
10
+ group. But every question also has a fixed start-up cost of about 9 seconds, however few
11
+ groups it holds. Near the end of a run, the sorter had about 8 seconds left. It decided one
12
+ group would fit and asked. The question could never finish in 8 seconds, so its own timer cut
13
+ it off. The run was then labelled "degraded, the model failed", even though the model never
14
+ failed once. Two real runs on 2026-10-01 went exactly like this.
15
+
16
+ A second fault hid it. The scheduled job that starts each run waited only 60 seconds for the
17
+ result. These runs took 70 seconds, so the job gave up, said "still running, the next run will
18
+ check", and recorded a success. No later run ever checks an earlier one.
19
+
20
+ ## The fix
21
+
22
+ - The sorter does not start another question unless at least 25 seconds are left.
23
+ - If its own timer still cuts a later question short, that is "out of time", not "the model
24
+ failed". Those groups simply wait for the next run.
25
+ - A run that got some answers and then ran out of time is now "succeeded". It carries a short
26
+ note saying the rest is still waiting.
27
+ - Real problems still mark the run degraded: the model timing out on its own, an error, a
28
+ wrong-shaped answer, the spending cap, or a first question that cannot finish.
29
+ - The job now waits up to 3 minutes. That is longer than any run may last (at most 115
30
+ seconds). If a run is somehow still going after that, the job counts it as a failure
31
+ instead of quietly passing.
32
+
33
+ ## What does not change
34
+
35
+ The question wording, the answer format, the spending cap, the approved group limit and the
36
+ rules for switching the model off all stay the same.
@@ -0,0 +1,224 @@
1
+ # Side-Effects Review — feedback readiness reviewed in chunks
2
+
3
+ **Version / slug:** `feedback-arbiter-chunking`
4
+ **Date:** `2026-10-01`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `independent reviewer subagent (see below)`
7
+
8
+ ## Summary of the change
9
+
10
+ The operated feedback drain asks the registered readiness authority (gpt-6-astra via
11
+ codex-cli) which clusters are ready to become work. It sent every due candidate, up to the
12
+ authority's `maxBatch` (50), in ONE model call. Live on 2026-10-01 the model took about 3-4 s
13
+ per candidate: 10 candidates finished in ~31 s (run 20c4fac7, 32.6 s end to end) and 50 hit
14
+ the 60 s call budget twice in a row (runs 2b4bebb3 and 0e7b229a, 60.1 s each, both
15
+ `readiness-authority-failed`). A 50-candidate call can never fit the call budget.
16
+
17
+ `FeedbackDrainService` now reviews a tick's due candidates (still capped at the authority's
18
+ `maxBatch`) in sequential calls of at most `readinessChunkSize` (default 10). Before each call
19
+ it checks the time left for readiness (the tick's wall clock minus one 20 s stage budget kept
20
+ for the stages after readiness). The first call is a full chunk; later calls are sized from
21
+ the pace observed so far in that tick, with a 25% margin. Each call's budget is also capped by
22
+ the time left. When the next call would not fit, the loop stops cleanly: rows not reached stay
23
+ due, untouched, for the next tick. Each call's decisions are applied as soon as it returns. A
24
+ failed call stops the tick, and its own rows are marked `readiness-authority-failed`
25
+ (re-review in 15 min). Spend is reserved per call: one `estimatedReadinessBatchUsd` per call,
26
+ plus that call's decision count. Daily usage is still counted per decision.
27
+
28
+ Failure accounting: a contract violation (wrong model, schema, ids) still demotes the authority
29
+ at once in any call. A transient failure (timeout, provider error) counts ONE toward the
30
+ consecutive-failure limit only if no call in that tick succeeded. A tick with at least one
31
+ successful call resets the counter.
32
+
33
+ Also: `feedbackFactory.drain.maxWallClockMs` (90 s in config defaults, and 90 s in the approved
34
+ spec §203) was declared but never passed to the service. The service ran on its own 115 s
35
+ default. It is wired now, together with the new optional `readinessChunkSize`.
36
+
37
+ Files: `src/feedback-factory/drain/FeedbackDrainService.ts`, `src/server/AgentServer.ts`
38
+ (wiring), `src/core/types.ts` (config type). Prompt id, schema id, parser and
39
+ `FeedbackReadinessArbiter` are unchanged.
40
+
41
+ ## Decision-point inventory
42
+
43
+ - `FeedbackReadinessArbiter.decideBatch` (`feedback-cluster-readiness`): pass-through. The
44
+ same prompt and schema now receive packets of at most 10 candidates instead of up to 50.
45
+ Every packet stays inside the registered `maxBatch` envelope.
46
+ - Authority transient-failure demotion (`FeedbackDrainService`): modify. The counter is now
47
+ per tick (any successful call resets it; an all-failed tick counts once) instead of per
48
+ single call.
49
+ - Spend brake (`reserveAuthoritySpend`): modify (granularity only). It is reserved per call
50
+ instead of per tick. The same cap applies, and a brake mid-tick keeps the earlier calls'
51
+ decisions.
52
+
53
+ ---
54
+
55
+ ## 1. Over-block
56
+
57
+ No block/allow surface on user content. The one gating effect is on throughput: a tick can
58
+ now review fewer candidates than `maxBatch` when the wall clock runs short. Those rows are
59
+ not rejected; they stay due and the next tick reviews them first (the due order is
60
+ `next_review_at, cluster_id`).
61
+
62
+ ---
63
+
64
+ ## 2. Under-block
65
+
66
+ - A chunk that keeps timing out after one healthy chunk in every tick never reaches the
67
+ demotion limit. This is intentional: the tick is making progress. Each such tick still ends
68
+ `degraded` with `readiness-authority-failed`, and the job reports it as failed, so the
69
+ pattern stays visible.
70
+ - The per-candidate pace comes from earlier calls in the same tick. A sudden slowdown on a
71
+ later call can still overrun its slot. That call's budget is capped at the time left, so it
72
+ fails as a transient failure rather than blowing the tick's wall clock.
73
+
74
+ ---
75
+
76
+ ## 3. Level-of-abstraction fit
77
+
78
+ This is the right layer. Batching is a drain-service concern: the service owns the tick
79
+ budget, the spend reservation and the failure accounting. The arbiter stays a single-call
80
+ authority adapter with its unchanged envelope check (`candidates.length <= maxBatch`). No
81
+ parallel gate is introduced.
82
+
83
+ ---
84
+
85
+ ## 4. Signal vs authority compliance
86
+
87
+ - [x] No — this change has no block/allow surface.
88
+
89
+ The readiness judgment stays with the registered LLM authority. This change only decides how
90
+ many candidates go into each call and when to stop for the tick's time budget. Those are
91
+ resource limits, not judgments about the content.
92
+
93
+ ---
94
+
95
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
96
+
97
+ No new static heuristic at a competing-signals decision point. Chunk sizing is a resource
98
+ bound over a measured pace (time left / observed per-candidate latency). The readiness
99
+ decision itself remains the LLM authority's.
100
+
101
+ ---
102
+
103
+ ## 5. Interactions
104
+
105
+ - Cancellation and ownership: `stopIfCancelled()` (owner fence, wall-clock assert, run-lease
106
+ heartbeat) runs after every call. A long tick therefore heartbeats its 120 s run lease after
107
+ each call, not only once.
108
+ - Later stages: readiness ends at least one 20 s stage budget before the tick's wall clock.
109
+ This guarantees each later stage its own per-stage budget, but not the whole tail (claims
110
+ allow up to maxClaims x 20 s, plus compaction). Before this change the unwired 115 s tick
111
+ left about 50 s after a 65 s call. Now, with the spec's 90 s, readiness can fill to 70 s
112
+ and leave 20 s. With the consumer in dry-run (live today), the tail is milliseconds. A
113
+ live consumer with slow claims could trip the wall-clock assert and fail the run. That is
114
+ named here; a later tick retries.
115
+ - Ownership loss, cancellation or the wall-clock assert after a call ends the run (failed or
116
+ cancelled), exactly like any later stage. It is not counted as a provider failure, and the
117
+ undecided chunk is left untouched.
118
+ - Job body (`feedback-factory-process`): unchanged. A tick that stops on the wall clock is
119
+ `succeeded` (or `no-op`). A failed call still yields `degraded` /
120
+ `readiness-authority-failed`, which the job reports.
121
+ - New reason `readiness-wall-clock-exhausted`. It appears only when earlier stages used the
122
+ whole readiness window and no call was made. The run is degraded and no transient failure
123
+ is counted.
124
+
125
+ ---
126
+
127
+ ## 6. External surfaces
128
+
129
+ - Model spend: about 2-3 calls per tick instead of one. At the default $0.01 per call and 48
130
+ ticks a day, that is about $1.44/day, under the authority's $5/day cap. The cap is still
131
+ enforced, now before each call.
132
+ - Config: new optional `feedbackFactory.drain.readinessChunkSize`. There is no default in
133
+ ConfigDefaults (the code default is 10), so no migration is needed.
134
+ `maxWallClockMs` now takes effect: agents with the shipped 90 s default go from 115 s to
135
+ 90 s per tick, which matches spec §203.
136
+ - No change to prompts, the schema, routes, the dashboard, messaging or the CLAUDE.md
137
+ template. The template's "three timeouts or provider errors in a row" wording still
138
+ describes the behaviour.
139
+
140
+ ---
141
+
142
+ ## 6b. Operator-surface quality
143
+
144
+ Not applicable. No operator surface is touched.
145
+
146
+ ---
147
+
148
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
149
+
150
+ Unchanged. The drain runs only on the canonical owner machine (owner fence + epoch). Chunking
151
+ is internal to one owner's tick. Proxied ticks behave as before.
152
+
153
+ ---
154
+
155
+ ## 8. Rollback cost
156
+
157
+ Low. Revert the commit. Setting `readinessChunkSize` to 50 gives back one call per tick
158
+ (which times out live), with no revert needed. No data shape changes: readiness rows, daily
159
+ usage rows and authority records are written with the same columns and meanings.
160
+
161
+ ---
162
+
163
+ ## Conclusion
164
+
165
+ The change makes the operated readiness stage able to finish on real latency. It respects the
166
+ approved authority envelope (prompt, schema, `maxBatch`, spend cap). Earlier decisions are no
167
+ longer thrown away when a later call fails, and timeouts are not over-counted toward
168
+ demotion. It also brings the tick's wall clock back to the spec's 90 s.
169
+
170
+ Throughput, measured at ~3.1 s per candidate:
171
+ - About 22 candidates per tick at 90 s, about 30 at 115 s.
172
+ - Every tick is still capped by the authority's `maxBatch`. The live gen 3 authority is
173
+ narrowed to 10, so the drain stays at 10 per tick (480 a day at the 30-minute cadence)
174
+ until a generation with a larger `maxBatch` is registered.
175
+
176
+ ## Second-pass review (if required)
177
+
178
+ **Reviewer:** independent reviewer subagent
179
+ **Independent read of the artifact: concur**
180
+
181
+ "Concur with the review". The reviewer ran the three new test files (8/8, 2/2, 1/1). The
182
+ non-blocking notes and what was done:
183
+ - Non-numeric `readinessChunkSize` / `maxWallClockMs` from config produced NaN, which stalls
184
+ or demotes. Fixed: they fall back to 10 and 115 s (unit test).
185
+ - A store error while applying decisions still counted as a success, and re-marked
186
+ already-approved rows. Fixed: success is counted after apply, and only rows still
187
+ `collecting` are marked failed (unit test).
188
+ - Ownership loss or the wall-clock assert inside the call's try was counted as a provider
189
+ failure. Fixed: `stopIfCancelled()` now runs outside it, so the run fails or cancels
190
+ (unit test).
191
+ - The §5 later-stage wording was inaccurate. Corrected above (named exposure for a live
192
+ consumer).
193
+ - Left as is (named):
194
+ - The first call is not sized to the time left (rare; bounded; one transient failure).
195
+ - When the time left is under 60 s, the service timer fires before the arbiter's own
196
+ timeout and the codex child keeps running. This is unchanged from before, and 30-minute
197
+ ticks never overlap.
198
+
199
+ ## Evidence pointers
200
+
201
+ - Live latency shapes: the Mac Studio store's `drain_runs` (a read-only `.backup` copy). Runs
202
+ 2b4bebb3 and 0e7b229a: 60.1 s, degraded `readiness-authority-failed` (50 candidates). Run
203
+ 20c4fac7: 32.6 s, succeeded (10 candidates). The authority records show gen 3 with
204
+ `max_batch` 10.
205
+ - `tests/unit/feedback-factory/drain-readiness-chunking.test.ts` (11): fake clock at 3.1 s
206
+ per candidate, with a 60 s `CodexExecJsonTimeoutError`. Recorded gpt-6-astra decisions are
207
+ replayed through the unchanged parser. Of the first 8, 7 fail on the pre-fix source; the
208
+ 8th guards the unchanged all-failed demotion rule. The last 3 cover the review fixes.
209
+ - `tests/integration/feedback-drain-readiness-chunking.test.ts` (2): AgentServer wiring of
210
+ both config fields and HTTP ticks. Both fail on the pre-fix source.
211
+ - `tests/e2e/feedback-drain-readiness-chunking.test.ts` (1): production wiring, the recorded
212
+ live reports and recorded replies. The second call times out, the first chunk survives, and
213
+ the next tick finishes the pass. It fails on the pre-fix source.
214
+
215
+ ## Class-Closure Declaration (display-only mirror)
216
+
217
+ No agent-authored-artifact defect: the defect is in the drain's TypeScript batching. The
218
+ change modifies a bounded retry path inside a scheduled controller, so its convergence is
219
+ stated here:
220
+ - Control-loop edge: one tick makes at most `ceil(maxBatch / readinessChunkSize)` calls
221
+ (5 at most), and stops earlier on the wall clock.
222
+ - Steady-state bound: any failed call ends the tick's readiness stage.
223
+ - Settling brake: the authority demotes to proposal-only after 3 consecutive ticks with no
224
+ successful call, after any contract violation, or at the daily spend cap.