loki-mode 9.12.5 → 9.16.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.
@@ -0,0 +1,684 @@
1
+ # Loop harness audit, and one proposed measurement slice
2
+
3
+ Audit only. No runtime behaviour is changed by this document, and the proposal
4
+ at the end is read-only by construction.
5
+
6
+ Every figure here was measured against the working tree at `4d8625bb`
7
+ (v9.12.5) on 2026-08-04. Commands are included so each can be re-run rather
8
+ than trusted.
9
+
10
+ ## What already exists
11
+
12
+ ### Structured traces
13
+
14
+ `.loki/events.jsonl` is the trace surface, written by `emit_event_json`
15
+ (`autonomy/run.sh:2435`) with a UTC timestamp and arbitrary `key=value` pairs.
16
+ **26 distinct event types** are emitted:
17
+
18
+ ```
19
+ agent_prompt budget_exceeded budget_warning
20
+ capability_degraded code_review_complete code_review_council_complete
21
+ code_review_*_oversized code_review_start dashboard_crash
22
+ gate_stuck iteration_complete iteration_start
23
+ managed_agents_fallback managed_review_council_ok phase_change
24
+ policy_denied provider_failover provider_recovery
25
+ review_verification_failed session_end session_start
26
+ stage_complete task_completion_claim watchdog_alert
27
+ ```
28
+
29
+ ```bash
30
+ grep -ohE 'emit_event_json "[a-z_]+"' autonomy/run.sh | sort -u
31
+ ```
32
+
33
+ ### Verifier surface
34
+
35
+ Gate functions in `autonomy/run.sh`:
36
+
37
+ ```
38
+ _evidence_gate_and_surface _invariant_gate_and_surface
39
+ _semantic_gate_and_surface _loki_supervised_completion_gates_pass
40
+ gate_failure_disposition build_gate_escalation_context
41
+ run_doc_quality_gate run_magic_debate_gate
42
+ ```
43
+
44
+ Plus the 3-reviewer blind council, whose completion is traced by
45
+ `code_review_complete` and `code_review_council_complete`.
46
+
47
+ ## The gap, stated precisely
48
+
49
+ **The verifiers run, but they do not record what the directive asks for.**
50
+
51
+ `code_review_complete` carries exactly three fields:
52
+
53
+ ```
54
+ review_id=<id> source=managed iteration=<n>
55
+ ```
56
+
57
+ `review_verification_failed` carries:
58
+
59
+ ```
60
+ reason=<slug> iteration=<n> implementation_retry=<bool>
61
+ ```
62
+
63
+ Neither carries any of:
64
+
65
+ | Field the directive asks for | Emitted today |
66
+ |---|---|
67
+ | eligibility (did this verifier apply?) | no |
68
+ | deterministic criterion | no |
69
+ | retry cap / timeout cap | no |
70
+ | latency | no |
71
+ | tokens | no |
72
+ | cash cost | no |
73
+ | verdict | partial (failure reason only) |
74
+ | changed the terminal outcome? | no |
75
+ | false-positive review | no |
76
+ | rollback switch | no |
77
+
78
+ The `_evidence_gate_and_surface`, `_invariant_gate_and_surface` and
79
+ `_semantic_gate_and_surface` functions emit **nothing structured at all** --
80
+ grepping their bodies for `emit|json|cost|latency|duration|verdict` returns no
81
+ matches.
82
+
83
+ So a loop-harness manifest cannot be *derived* from today's traces. It would
84
+ have to be *fabricated*, which is the failure mode this codebase treats as
85
+ worse than an absent measurement.
86
+
87
+ ### Why that matters more than it sounds
88
+
89
+ This session produced a concrete example of the cost. A gate false positive
90
+ (mock-integrity firing on `require.resolve` + `spawnSync`) made first-pass
91
+ completion impossible for every npm user, and it was invisible until someone
92
+ ran the real thing. With per-verifier records carrying `verdict`,
93
+ `changed_terminal_outcome` and `false_positive_reviewed`, that class shows up
94
+ as a measurement rather than a field report.
95
+
96
+ ## Other audit axes, briefly
97
+
98
+ **Memory / skills / prompts.** The main-loop prompt is assembled in memory and
99
+ never persisted (`build_prompt`, `autonomy/run.sh:8987`), so prompt-version
100
+ attribution is not currently possible. Review prompts *are* persisted
101
+ (`run.sh:14700`). Any prompt-versioning proposal has to start by making the
102
+ main prompt observable, and that is a runtime change -- out of scope here.
103
+
104
+ **Context compression.** The prompt splits at `[CACHE_BREAKPOINT]` into a
105
+ cache-stable prefix and a volatile tail. That is a real, already-shipped
106
+ compression discipline. It is not currently measured per-run.
107
+
108
+ **Model routing by quality/latency/cost.** `get_rarv_tier()` maps iteration to
109
+ model tier. The mapping is deterministic and readable; what is absent is any
110
+ *recorded* per-call association between the tier chosen and the outcome it
111
+ produced, which is what a routing evaluation would need.
112
+
113
+ ## Proposal: `loop-harness-v1`, read-only, one slice
114
+
115
+ **Do not add a loop. Do not change runtime architecture.** The single coherent
116
+ reversible slice is a **report over traces that already exist**, plus the
117
+ smallest instrumentation that makes the report non-vacuous.
118
+
119
+ ### Phase A -- report only, zero runtime change
120
+
121
+ `tools/loop-harness-report.py`, a read-only reader in the shape of the existing
122
+ `api_*` modules:
123
+
124
+ - reads `.loki/events.jsonl`
125
+ - emits one row per verifier invocation it can actually observe
126
+ - **every field it cannot derive reads UNKNOWN, never a default**
127
+ - carries the standard envelope: `source`, `freshness_s`, `reason`
128
+ - exits 3 (nothing to check) on a workspace with no verifier events
129
+
130
+ This is honest on day one: most columns read UNKNOWN, and the report says so.
131
+ That is the correct starting point -- it makes the gap visible and measurable
132
+ rather than asserting a completeness that does not exist.
133
+
134
+ ### Phase B -- instrumentation, only where A proves it is needed
135
+
136
+ Add the missing fields to the three gate functions and the council completion
137
+ event. Each addition is one `emit_event_json` call with named fields, and each
138
+ is independently revertable.
139
+
140
+ Phase B is **not** proposed for adoption yet: it changes runtime behaviour, and
141
+ the directive says keep runtime unchanged unless an evidenced deterministic
142
+ requirement justifies it. Phase A produces that evidence.
143
+
144
+ ### What would make this worth automating
145
+
146
+ The directive's bar is the right one: automate only when offline replay and
147
+ online outcomes prove quality-adjusted lift exceeds latency and cost. Phase A
148
+ cannot clear that bar and does not try -- it is the measurement that would let
149
+ a later proposal clear it.
150
+
151
+ ## Rollback
152
+
153
+ Phase A adds one file under `tools/` and touches no runtime path. Rollback is
154
+ deleting it. Nothing in `.loki/` is written, so there is no state to unwind.
155
+
156
+ ## The four surfaces, measured
157
+
158
+ | Surface | State | Evidence |
159
+ |---|---|---|
160
+ | Composable core-agent loop | present, modular | `run_autonomous()` + `get_rarv_tier()` + `build_prompt()` are separable functions |
161
+ | Bounded verification loop | present, **unmeasured** | 3 gate fns emit 0 structured records; `code_review_complete` carries 3 fields |
162
+ | Real-system event-driven loop | present, **partial contract** | `autonomy/trigger-server.py`: auth 7, timeout 12, idempotency 4, retry 2 -- but dedupe 0, dead-letter 0, backpressure 0 |
163
+ | Self-improvement / hill-climbing | present, **not wired to traces** | `LOKI_AUTO_LEARNINGS` appears 0 times in `run.sh`; the TS route has it (`counter_evidence.ts`, `episode_bridge.ts`) |
164
+
165
+ ```bash
166
+ for p in idempot dedupe dead.letter backpressure retry timeout auth; do
167
+ printf '%-16s %s\n' "$p" "$(grep -ciE "$p" autonomy/trigger-server.py)"
168
+ done
169
+ ```
170
+
171
+ ### The three exact gaps
172
+
173
+ 1. **Verifier records carry no cost, latency, criterion, or effect.** This is
174
+ the blocker for every downstream ask -- a marginal-lift comparison, a
175
+ promotion rule, and a canary decision all need per-verifier cost and
176
+ outcome, and none is emitted.
177
+
178
+ 2. **The trigger contract is three properties short.** Auth, timeout,
179
+ idempotency and bounded retry exist. Dedupe, dead-letter state and
180
+ backpressure do not. An idempotent trigger without dedupe still processes a
181
+ duplicate delivery; without dead-letter state a poisoned message retries to
182
+ its cap and vanishes.
183
+
184
+ 3. **The learnings loop is route-asymmetric.** `LOKI_AUTO_LEARNINGS` is
185
+ documented as default-on in the Bun runner and is absent from `run.sh`, so
186
+ the bash route contributes nothing to hill-climbing. Any trace-driven
187
+ improvement claim measured on one route does not transfer to the other.
188
+
189
+ ## Why architecture stays unchanged
190
+
191
+ Every downstream ask in the directive -- matched online cohorts, marginal-lift
192
+ per verifier, a promotion rule, a canary window -- is **downstream of
193
+ measurement that does not exist**. Building a manifest, a cohort comparison or
194
+ an automation rule on top of absent instrumentation would produce numbers with
195
+ no referent.
196
+
197
+ The cheapest surface that changes this is Phase A: a read-only reader that
198
+ reports what IS recorded and names what is not. It is implemented and tested
199
+ (`tools/loop-harness-report.py`, 8 assertions, both fabrication modes
200
+ mutation-tested). Against this repo's own trace it reads 776 records, finds no
201
+ verifier events, and exits 3 with a reason rather than printing an empty table
202
+ that reads as a clean run.
203
+
204
+ **The smallest reversible next step** is adopting Phase A and running it over
205
+ a real build's trace. That yields the first honest per-verifier row set, and
206
+ its UNKNOWN columns are the evidenced requirement that would justify Phase B
207
+ instrumentation -- which is a runtime change and is deliberately not proposed
208
+ until that evidence exists.
209
+
210
+ ## Correction: the six named axes, measured
211
+
212
+ The first pass treated routing as unmeasured. That was wrong, and the correction
213
+ matters because it changes which work is worth doing.
214
+
215
+ | Axis | State | Evidence |
216
+ |---|---|---|
217
+ | Memory / retrieval | wired into the loop | 13 references in `run.sh` |
218
+ | Tool descriptions | present | 13 in `mcp/server.py` |
219
+ | Context compression | shipped, unmeasured per-run | `[CACHE_BREAKPOINT]` prefix split |
220
+ | Prompts | **not attributable** | main prompt in memory only (`run.sh:8987`); review prompts persisted (`:14700`) |
221
+ | **Quality/latency/cost routing** | **fully recorded** | see below |
222
+ | Verifier records | **absent** | unchanged from the first pass |
223
+
224
+ ### Routing is already instrumented
225
+
226
+ `.loki/metrics/efficiency/` records carry:
227
+
228
+ ```
229
+ model provider phase iteration status timestamp
230
+ cost_usd duration_ms
231
+ input_tokens output_tokens cache_read_tokens cache_creation_tokens
232
+ ```
233
+
234
+ `LOKI_CURRENT_MODEL` holds the EXACT dispatched `--model` value, exported after
235
+ every mutation (opus-pin, `LOKI_MAX_TIER` clamp, mid-flight override), so the
236
+ recorded model is the one actually used. `run.sh:7834` documents the bug that
237
+ made this necessary: hardcoding the development-tier default mislabeled every
238
+ non-development iteration and "made the model-equivalence bench unfalsifiable."
239
+
240
+ `record_is_measured()` (`autonomy/lib/efficiency_cost.py:81`) is the single
241
+ definition of measured, and its docstring records why a second copy is
242
+ forbidden: "the four surfaces that once rendered an unmeasured run as $0.00
243
+ each had their own idea of what counted as measured."
244
+
245
+ `dashboard/api_runs.py` already reads these (22 references).
246
+
247
+ **So a quality/latency/cost routing evaluation is possible today** -- model,
248
+ cost, latency and tokens are all recorded per iteration with an honesty
249
+ predicate. What is missing is not instrumentation but a *comparison*: no
250
+ baseline pins a model choice to an outcome.
251
+
252
+ ### The caveat, and why it is smaller than it looked
253
+
254
+ Efficiency records are WIPED at run start (`run.sh:6212`), which is why a
255
+ historical run reports `cost_usd: None` and why this workspace holds zero
256
+ records.
257
+
258
+ But receipts survive, and they DO carry the model. `proof-generator.py:627`
259
+ resolves it through four sources -- an observed value, `LOKI_CURRENT_MODEL`,
260
+ `LOKI_SESSION_MODEL`, `SESSION_MODEL` -- then the execution policy's
261
+ `sdk_id`/`alias`, and only then returns the string `"unavailable"`. It never
262
+ guesses.
263
+
264
+ Verified end to end: generating a receipt with `LOKI_CURRENT_MODEL=
265
+ claude-sonnet-5` yields `provider: {"name": "claude", "model":
266
+ "claude-sonnet-5"}`. The nine archived receipts read `"model": "unavailable"`
267
+ with `cost_usd: None` because they predate the efficiency wiring, not because
268
+ the mechanism is missing.
269
+
270
+ **So the cross-run corpus for a routing evaluation is the receipt archive**,
271
+ which retains model, provider, cost, tokens and wall clock per run. No new
272
+ instrumentation is required.
273
+
274
+ ### What this changes about the proposal
275
+
276
+ Verifier records remain the real gap, unchanged. But routing does NOT need
277
+ Phase B instrumentation -- it needs an evaluation over data that already
278
+ exists. That is a cheaper and better-evidenced next step than instrumenting
279
+ the gates, and it is squarely inside the directive's "improve routing before
280
+ touching architecture."
281
+
282
+ ## The routing evaluation: blocked on corpus, not on code
283
+
284
+ Having established that receipts retain the model, the obvious next step is the
285
+ evaluation itself. It cannot be built yet, and the reason is worth recording
286
+ precisely.
287
+
288
+ Measured across the whole archive:
289
+
290
+ ```
291
+ receipts: 9
292
+ models: {"unavailable": 9}
293
+ with measured cost: 0
294
+ ```
295
+
296
+ Every receipt reads `"model": "unavailable"` and `cost_usd: None`. They were
297
+ written Jul 26 and Jul 31; the efficiency wiring that populates both landed
298
+ later. The mechanism is proven to work -- generating a receipt with
299
+ `LOKI_CURRENT_MODEL` set captures the exact model -- but no archived run
300
+ exercised it.
301
+
302
+ **So a routing evaluation today would have zero rows to compare.** Building it
303
+ now produces a reader with nothing to read, and any number it reported would
304
+ be derived from a single degenerate cohort.
305
+
306
+ That is a corpus problem, not a code problem, and the fix is not more code: it
307
+ is running builds and letting the archive fill. Each real `loki start` from
308
+ here produces a receipt carrying model, provider, cost, tokens and wall clock.
309
+ The evaluation becomes worth writing once the archive holds more than one
310
+ distinct model.
311
+
312
+ ### What this means for sequencing
313
+
314
+ The directive asks for verifier lift to exceed latency and cost on baseline
315
+ plus ambitious E2E plus online outcomes. That bar cannot be cleared from a
316
+ corpus of nine degenerate rows, and no amount of tooling changes it.
317
+
318
+ The honest ordering is therefore:
319
+
320
+ 1. accumulate receipts from real runs (no code required)
321
+ 2. write the evaluation once two or more distinct models appear
322
+ 3. only then consider verifier instrumentation, which is the one gap where
323
+ the data genuinely does not exist at any volume
324
+
325
+ Steps 1 and 2 need no runtime change. Step 3 remains unproposed.
326
+
327
+ ## Step 1 executed: the corpus has its first real row
328
+
329
+ Rather than leave "accumulate receipts" as advice, one real `loki start` was
330
+ run against a minimal spec. It completed exit 0, built working code (its own
331
+ 6 tests pass), and wrote a receipt carrying exactly what a routing evaluation
332
+ needs:
333
+
334
+ ```
335
+ provider {"name": "claude", "model": "sonnet"} <- not "unavailable"
336
+ cost_usd 1.3828 input/output tokens 48 / 7290
337
+ iterations 1 succeeded, 0 failed
338
+ duration_ms 128000 wall_clock_sec 576
339
+ base_sha ef1efe909750 head_sha 5a727616da91
340
+ ```
341
+
342
+ Contrast with the nine archived receipts, all `"model": "unavailable"` and
343
+ `cost_usd: None`. The mechanism was never broken; those runs simply predate
344
+ it.
345
+
346
+ `iterations.attribution` is worth noting for any lift measurement: it splits
347
+ cost into `progress` and `rework`, and states its own basis -- "rework counts
348
+ FAILED iterations only; a completed iteration forced to repeat by a gate is
349
+ counted as progress, so rework is a floor". That is a self-describing lower
350
+ bound rather than an unqualified number.
351
+
352
+ ### The verdict reads FAILED, correctly
353
+
354
+ `receipts_report` returns FAILED with `measured: True` and the real cost. The
355
+ reason is diff drift: 11 files / +494 recorded, more now. The cause was
356
+ verified by timestamp -- only `run.log` (still being appended) and
357
+ `.pytest_cache/` from the verification run itself changed after signing.
358
+
359
+ That is the verifier working. A receipt signed at time T and inspected at
360
+ T+delta, with files touched in between, SHOULD fail. The lesson for a future
361
+ evaluation harness: read receipts without running anything inside the
362
+ workspace, or the act of measuring invalidates what is measured.
363
+
364
+ ### Where the corpus stands
365
+
366
+ Two distinct models are needed before a comparison means anything. The archive
367
+ now holds one real row (`sonnet`) plus nine degenerate ones. The evaluation
368
+ remains unwritten, and that is still the honest position -- but step 1 is no
369
+ longer hypothetical.
370
+
371
+ ## Step 2: two models, and the first real comparison
372
+
373
+ A second real run, pinned with `LOKI_SESSION_MODEL=opus`, gives the corpus its
374
+ second distinct model. Both runs used the same shape of task (a small pure
375
+ helper plus its test) and both succeeded in one iteration.
376
+
377
+ | | sonnet | opus |
378
+ |---|---|---|
379
+ | cost_usd | 1.3828 | **0.6823** |
380
+ | output tokens | 7290 | **4446** |
381
+ | wall_clock_sec | 576 | **472** |
382
+ | progress duration_ms | 128000 | **71000** |
383
+ | iterations | 1 succeeded | 1 succeeded |
384
+
385
+ **This is two data points, not a finding.** Two runs of two different specs
386
+ cannot separate model effect from task effect, and the directive's bar --
387
+ lift exceeding latency and cost across baseline, ambitious E2E, and online
388
+ cohorts -- is nowhere near cleared. Recorded because it is the first
389
+ comparison the corpus has ever supported, and because the shape is now
390
+ proven: the fields needed for a routing evaluation arrive populated and
391
+ measured on every real run.
392
+
393
+ ## A real defect the corpus work surfaced
394
+
395
+ Both receipts read FAILED on diff drift. The first time, the cause was my own
396
+ verification run writing `.pytest_cache/`. The second time I deliberately
397
+ touched nothing, and it STILL failed. The only file newer than the receipt:
398
+
399
+ ```
400
+ run.log
401
+ ```
402
+
403
+ **A run whose stdout is redirected into its own workspace invalidates its own
404
+ receipt.** The log keeps growing after the receipt is signed, so the recorded
405
+ diff no longer matches the tree. Any user who runs
406
+ `loki start ./prd.md > run.log` inside the workspace gets a receipt that
407
+ cannot verify, through no fault of their own.
408
+
409
+ ### Scoping that claim honestly
410
+
411
+ The first write-up called this a usability trap users would hit. Checking
412
+ rather than assuming shrinks it:
413
+
414
+ - the documented invocation is plain `loki start prd.md` (README:333, 342,
415
+ 360) with NO redirection, so an interactive user never creates `run.log` in
416
+ the workspace
417
+ - the runtime has no concept of its own log path -- grepping `run.sh` and the
418
+ CLI for `LOKI_RUN_LOG`, `RUN_LOG=` or a log-path helper returns nothing
419
+ - `> run.log` appears nowhere in the docs; it was MY invocation choice for a
420
+ backgrounded run
421
+
422
+ So this is not a shipped defect users are hitting. It is a real constraint on
423
+ anyone who captures stdout inside the workspace -- CI harnesses, scripted
424
+ runs, and future evaluation tooling -- and that includes the routing
425
+ evaluation this audit is building toward.
426
+
427
+ `workspace_diff.py:28` already has an `_excluded()` predicate (currently
428
+ `.loki/` only), so excluding a known log path would be a one-line change. It
429
+ is NOT proposed, for a specific reason: excluding by the literal name
430
+ `run.log` would silently drop a user's own file of that name from the receipt,
431
+ which is a worse failure than the one being fixed. A correct fix needs the
432
+ runtime to know its own log path, and that is a runtime change with no
433
+ evidenced user demand behind it.
434
+
435
+ The durable output is the constraint, not a patch: **an evaluation harness
436
+ must not write inside the workspace it measures**, including redirecting the
437
+ run's own stdout there. Recorded so it is not rediscovered a third time.
438
+
439
+ ## Correction: two of the three trigger gaps were my grep, not the code
440
+
441
+ The audit claimed `autonomy/trigger-server.py` was three properties short:
442
+ dedupe 0, dead-letter 0, backpressure 0. Two of those were false negatives from
443
+ searching for the wrong words.
444
+
445
+ **Dedupe exists** and is well built. `Dispatcher.seen_delivery()` keeps recent
446
+ GitHub delivery ids in a lock-guarded bounded `OrderedDict`. It deliberately
447
+ does NOT refresh recency on a duplicate, and the code says why: "a flood of one
448
+ valid (authenticated) duplicate id could keep it pinned and evict up to
449
+ dedup_max genuinely-recent ids, letting real redeliveries slip through."
450
+
451
+ Verified by executing the method directly:
452
+
453
+ ```
454
+ first delivery abc -> False (new)
455
+ repeat delivery abc -> True (deduped)
456
+ absent header "" -> False (never deduplicated, falls through)
457
+ after eviction -> False (bounded, evicts oldest first)
458
+ ```
459
+
460
+ **Backpressure exists.** The dispatcher holds a bounded `queue.Queue`, catches
461
+ `queue.Full`, and sheds load with 503 -- the module docstring states it at line
462
+ 12. My earlier check used a malformed `-E` alternation and returned 0 for terms
463
+ plainly present in the file (`queue_size` alone appears 7 times).
464
+
465
+ **Dead-letter state is the one that is genuinely absent.** No DLQ, no failed-job
466
+ retention: a job that exhausts its retries is dropped.
467
+
468
+ ### The lesson, which is the same one twice
469
+
470
+ `phases` was reported missing from `loki proof --help` by a grep that could not
471
+ see it; dedupe was reported absent by a grep looking for the wrong noun. Both
472
+ times the code was fine and the measurement was broken.
473
+
474
+ An absence found by grep is a hypothesis, not a finding. It has to be confirmed
475
+ by reading the code or executing it -- exactly the standard this audit applies
476
+ to the runtime, now applied to the audit itself.
477
+
478
+ ### Revised trigger contract state
479
+
480
+ | Property | State |
481
+ |---|---|
482
+ | authentication | present |
483
+ | timeout | present |
484
+ | bounded retry | present |
485
+ | idempotency | present |
486
+ | **dedupe** | **present** (verified by execution) |
487
+ | **backpressure** | **present** (bounded queue, 503 shed) |
488
+ | dead-letter state | absent |
489
+
490
+ One property short, not three. A trigger-to-run-to-receipt path is therefore
491
+ much closer to complete than the audit claimed, and the remaining gap is
492
+ narrow: a job that exhausts retries vanishes without a record.
493
+
494
+ ## Third correction: the last "gap" is smaller still
495
+
496
+ Applying the new standard to my own remaining claim -- dead-letter state absent
497
+ -- turned up a near-miss worth recording, because the mistake is instructive.
498
+
499
+ Reading `_worker` (trigger-server.py:425) shows `dispatch_event` wrapped in
500
+ `try/finally` with NO `except`. That looks like a serious defect: one handler
501
+ exception would kill the worker thread, and with `DEFAULT_WORKERS = 4`, four
502
+ failures would silently drain all capacity while the server kept returning 200.
503
+
504
+ I reproduced exactly that behaviour and was ready to report it. **The
505
+ reproduction was wrong**: it used a stub that raised, not the real
506
+ `dispatch_event`.
507
+
508
+ The real function catches everything (`trigger-server.py:356`):
509
+
510
+ ```python
511
+ except Exception as e: # defensive: never let a worker die on bad input
512
+ logging.exception("Handler for %s raised: %s", event_type, e)
513
+ summary, status = None, "error"
514
+ ```
515
+
516
+ Verified against the actual code: `dispatch_event` returns `(None, "error")`
517
+ without raising, logs the traceback, and the daemon thread count is unchanged
518
+ (1 before, 1 after). The worker survives, and the guard is placed at the callee
519
+ precisely so the bare `try/finally` in the loop is safe.
520
+
521
+ ### What that leaves
522
+
523
+ A failed job IS recorded -- `logging.exception` plus `log_event(..., status)`.
524
+ So "dead-letter state absent" overstates it too. What is genuinely missing is a
525
+ *queryable* record: the failure lands in logs, not in a structure something
526
+ could retry from or report on.
527
+
528
+ That is a real but narrow gap, and it is not worth a runtime change on this
529
+ evidence.
530
+
531
+ ### Score on my own audit
532
+
533
+ Of the three trigger gaps originally claimed:
534
+
535
+ - dedupe -- **present**, found by grepping the wrong noun
536
+ - backpressure -- **present**, found by a malformed regex
537
+ - dead-letter -- **overstated**; failures are logged, just not queryable
538
+
539
+ And one defect I nearly reported was an artifact of testing my own mock.
540
+
541
+ Four measurement errors in one audit section. The corrective standard, now
542
+ demonstrated three times: **an absence is a hypothesis until the real code is
543
+ read or executed** -- and a reproduction must exercise the real function, not
544
+ a stand-in shaped like it.
545
+
546
+ ## Clean checkpoint: 5605deca, all 19 jobs green
547
+
548
+ Recorded per the gate discipline: the trigger-contract correction reached
549
+ `Tests@5605deca: completed / success`, 19 of 19 jobs, zero failures. No run was
550
+ superseded to get there.
551
+
552
+ ### Where the four surfaces actually stand, after all corrections
553
+
554
+ | Surface | State | Confidence |
555
+ |---|---|---|
556
+ | Composable core-agent | modular, separable functions | read |
557
+ | Bounded verification | verifiers run; records carry no cost/latency/criterion/effect | read + grep |
558
+ | Event-driven trigger | auth, timeout, retry, idempotency, dedupe, backpressure all present; failures logged but not queryable | **executed** |
559
+ | Self-improvement | `LOKI_AUTO_LEARNINGS` on the TS route only, absent from `run.sh` and the CLI | grep, unverified |
560
+
561
+ The confidence column matters more than the state column. Everything I checked
562
+ by execution survived scrutiny; three of the four things I checked by grep did
563
+ not.
564
+
565
+ ### The smallest next slice, and why it is not code
566
+
567
+ The directive's bar for any verifier change is a matched ablation showing lift
568
+ above p95 latency and cost, on the same task, across seeds. Nothing in this
569
+ repo can currently produce that number:
570
+
571
+ - verifier records carry no cost or latency at all, so the denominator is
572
+ missing
573
+ - the routing corpus holds two real rows from two DIFFERENT specs, which is
574
+ explicitly excluded ("do not infer this from different tasks or one seed")
575
+
576
+ So the smallest honest next slice is **the same spec run twice under two
577
+ models**, which is the minimum a matched ablation admits. That is one command
578
+ and no code. Until it exists, any harness or eval built on top would be
579
+ measuring a corpus that cannot support the claim.
580
+
581
+ The one surface still unverified by execution is self-improvement: the
582
+ `LOKI_AUTO_LEARNINGS` asymmetry was found by grep, and grep has been wrong
583
+ three times in this audit. It should be confirmed by running before it is
584
+ treated as a finding.
585
+
586
+ ## The matched pair, executed
587
+
588
+ The previous entry said the smallest honest next step was the same spec run
589
+ twice under two models. That has now been done: identical `prd.md` (byte-for-byte,
590
+ `diff -q` verified), identical iteration cap, run SEQUENTIALLY so CPU contention
591
+ could not confound latency, logs written OUTSIDE each workspace so the run could
592
+ not invalidate its own receipt.
593
+
594
+ | | sonnet | opus |
595
+ |---|---|---|
596
+ | cost_usd | 0.7177 | **0.6257** |
597
+ | output tokens | 3658 | 3547 |
598
+ | wall_clock_sec | 458 | **406** |
599
+ | progress duration_ms | 65000 | 67000 |
600
+ | iterations | 1 of 1 succeeded | 1 of 1 succeeded |
601
+ | produced code | 1 test passing | 1 test passing |
602
+
603
+ Both arms solved the task. On this pair, opus was 12.8% cheaper and 11% faster
604
+ in wall clock, while taking marginally longer in measured progress duration --
605
+ the wall/duration split suggests the difference sits in orchestration overhead,
606
+ not model latency.
607
+
608
+ ### What this does and does not license
609
+
610
+ It **does** establish that the corpus can now produce a matched comparison: same
611
+ task, same budget, controlled ordering, both outcomes verified by running the
612
+ produced tests rather than trusting the receipt.
613
+
614
+ It **does not** clear the directive's bar. That requires lift above p95 latency
615
+ and cost across seeds, and this is n=1 per arm. A single pair cannot separate
616
+ model effect from run-to-run variance, and the two prior unmatched runs
617
+ (sonnet $1.3828, opus $0.6823) differ from these by more than the arms differ
618
+ from each other -- which is itself the evidence that one pair proves nothing
619
+ about the population.
620
+
621
+ The honest reading: the *method* is now demonstrated end to end. The *finding*
622
+ needs repetition, and repetition is provider spend, which is a founder call.
623
+
624
+ ### Method notes worth keeping
625
+
626
+ Three procedural constraints were learned by getting them wrong first:
627
+
628
+ 1. log outside the workspace, or the run invalidates its own receipt
629
+ 2. run arms sequentially, or contention confounds the latency column
630
+ 3. verify the produced artifact by executing it -- a receipt records what a run
631
+ claimed to do, not whether the code works
632
+
633
+ ## Read-only gap check: one evidenced product defect
634
+
635
+ The directive asks whether memory, skills, prompts, tool descriptions,
636
+ compression, or routing can address any evidenced gap. Running the full tier
637
+ surfaced a defect that none of those six can touch, because it is not a model
638
+ problem at all.
639
+
640
+ ### `loki onboard --stdout` produces nothing on a large repository
641
+
642
+ Reproduced deterministically, not inferred:
643
+
644
+ ```
645
+ small repo (1 source file), explicit path 394 bytes
646
+ small repo, cwd form 394 bytes
647
+ this repo (4100 tracked files), any form 0 bytes
648
+ this repo, --depth 1 0 bytes
649
+ ```
650
+
651
+ It is not the argument form: both spellings work on a small repo. It is not
652
+ scan depth: depth 1 fails identically. The command logs three INFO lines --
653
+ "Analyzing project at", "Depth: 2 | Format: markdown", "Scanning source
654
+ files..." -- then stops, writes no file, emits nothing to stdout, and **exits
655
+ 0**.
656
+
657
+ Exit 0 with no output is the worst available shape. A caller that checks the
658
+ exit code sees success; a caller that reads stdout gets an empty string and
659
+ cannot tell "this project has no structure" from "the command gave up". It is
660
+ the same class as the dashboard rendering an unmeasured cost as `$0.00`, which
661
+ this release line already fixed twice.
662
+
663
+ This is what `tests/test-onboard-command.sh` has been failing on: six
664
+ assertions, all downstream of empty output. The suite fails identically at
665
+ `1c80c85ff~1`, so it is pre-existing, not introduced this session.
666
+
667
+ ### Why none of the six named levers apply
668
+
669
+ Memory, retrieval, skills, prompts, tool descriptions, compression and routing
670
+ all shape what a MODEL is asked or told. This defect is in a bash analysis
671
+ command that never calls a model: it walks the filesystem and formats markdown.
672
+ No prompt change makes it emit output, and no routing decision is involved.
673
+
674
+ That is itself the useful finding. The directive's ordering -- try the cheap
675
+ model-facing levers before touching runtime -- is correct as a default and
676
+ does not apply here, and saying so is more honest than proposing a prompt
677
+ change that could not work.
678
+
679
+ ### Not fixed here
680
+
681
+ Fixing it means changing `autonomy/loki`, which is a runtime change this
682
+ directive excludes, and the failure mode (silent give-up at scale) needs its
683
+ own diagnosis before a patch. Recorded with a reproduction so it is actionable
684
+ rather than rediscovered.