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.
- package/README.md +81 -101
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/intent.sh +414 -0
- package/autonomy/issue-providers.sh +21 -0
- package/autonomy/lib/agent_readiness.py +202 -0
- package/autonomy/lib/claim_grounding.py +171 -0
- package/autonomy/lib/config-map.sh +10 -6
- package/autonomy/lib/decision_record.py +198 -0
- package/autonomy/lib/failure_memory.py +199 -0
- package/autonomy/lib/outcome_ledger.py +498 -0
- package/autonomy/lib/preedit_snapshot.py +216 -0
- package/autonomy/lib/verdict.py +204 -0
- package/autonomy/loki +378 -22
- package/autonomy/provider-offer.sh +25 -1
- package/autonomy/run.sh +516 -11
- package/autonomy/telemetry.sh +8 -1
- package/autonomy/verify.sh +10 -1
- package/completions/_loki +4 -0
- package/completions/loki.bash +2 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_operator.py +15 -2
- package/dashboard/api_v2.py +6 -1
- package/dashboard/control.py +62 -10
- package/dashboard/run.py +13 -2
- package/dashboard/scim.py +221 -0
- package/dashboard/server.py +48 -5
- package/docs/GATE-FAILURE-TRIAGE.md +254 -0
- package/docs/LOOP-CANDIDATE-PROPOSAL-v1.md +167 -0
- package/docs/LOOP-HARNESS-AUDIT.md +684 -0
- package/docs/VERIFICATION-COST.md +103 -0
- package/docs/WANG-PRINCIPLES-PLAN.md +1 -1
- package/loki-ts/dist/loki.js +402 -398
- package/mcp/__init__.py +1 -1
- package/mcp/_sdk_loader.py +25 -0
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/loop-harness-report.py +216 -0
|
@@ -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.
|