dos-kernel 0.22.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. dos/__init__.py +261 -0
  2. dos/_bin/dos-hook.exe +0 -0
  3. dos/_filelock.py +255 -0
  4. dos/_job_policy.py +97 -0
  5. dos/_tree.py +145 -0
  6. dos/admission.py +433 -0
  7. dos/answer_shape.py +299 -0
  8. dos/arbiter.py +859 -0
  9. dos/archive_lock.py +266 -0
  10. dos/arg_provenance.py +814 -0
  11. dos/attest.py +472 -0
  12. dos/breaker.py +311 -0
  13. dos/churn.py +226 -0
  14. dos/claim_extract.py +229 -0
  15. dos/claim_ttl.py +150 -0
  16. dos/cli.py +8721 -0
  17. dos/commit_audit.py +666 -0
  18. dos/completion.py +466 -0
  19. dos/concurrency_class.py +154 -0
  20. dos/config.py +1380 -0
  21. dos/config_lint.py +464 -0
  22. dos/cooldown.py +390 -0
  23. dos/coverage.py +387 -0
  24. dos/dangling_intent.py +287 -0
  25. dos/data_class.py +397 -0
  26. dos/decisions.py +1274 -0
  27. dos/decisions_tui.py +251 -0
  28. dos/dispatch_top.py +740 -0
  29. dos/dispatch_top_tui.py +116 -0
  30. dos/drivers/__init__.py +40 -0
  31. dos/drivers/ci_status.py +630 -0
  32. dos/drivers/citation_resolve.py +703 -0
  33. dos/drivers/decision_stop.py +98 -0
  34. dos/drivers/export_file.py +173 -0
  35. dos/drivers/export_otlp.py +275 -0
  36. dos/drivers/export_statsd.py +242 -0
  37. dos/drivers/hook_dialects.py +391 -0
  38. dos/drivers/job.py +47 -0
  39. dos/drivers/llm_judge.py +360 -0
  40. dos/drivers/memory_recall.py +1231 -0
  41. dos/drivers/notify_slack.py +373 -0
  42. dos/drivers/notify_webhook.py +251 -0
  43. dos/drivers/operator_judge.py +114 -0
  44. dos/drivers/os_acceptance.py +228 -0
  45. dos/drivers/paste_log.py +132 -0
  46. dos/drivers/plan_scope.py +133 -0
  47. dos/drivers/self_improve.py +375 -0
  48. dos/drivers/similarity_judge.py +249 -0
  49. dos/drivers/state_diff.py +274 -0
  50. dos/drivers/supervisor.py +347 -0
  51. dos/drivers/watchdog.py +363 -0
  52. dos/drivers/workshop.py +160 -0
  53. dos/durable_schema.py +344 -0
  54. dos/effect_witness.py +393 -0
  55. dos/efficiency.py +318 -0
  56. dos/enforce.py +414 -0
  57. dos/enumerate.py +776 -0
  58. dos/env_print.py +378 -0
  59. dos/event_severity.py +258 -0
  60. dos/evidence.py +692 -0
  61. dos/exec_capability.py +256 -0
  62. dos/export_cursor.py +143 -0
  63. dos/exporter.py +320 -0
  64. dos/firing_label.py +353 -0
  65. dos/fleet_roll.py +226 -0
  66. dos/gate_classify.py +827 -0
  67. dos/gh4_coverage.py +179 -0
  68. dos/git_delta.py +122 -0
  69. dos/guard.py +215 -0
  70. dos/health.py +552 -0
  71. dos/help_summary.py +519 -0
  72. dos/home.py +934 -0
  73. dos/hook_binary.py +194 -0
  74. dos/hook_dialect.py +271 -0
  75. dos/hook_exit.py +191 -0
  76. dos/hook_install.py +437 -0
  77. dos/id_alloc.py +304 -0
  78. dos/improve.py +499 -0
  79. dos/intent_ledger.py +635 -0
  80. dos/interpret.py +176 -0
  81. dos/intervention.py +769 -0
  82. dos/intervention_eval.py +371 -0
  83. dos/journal_delta.py +308 -0
  84. dos/judge_eval.py +328 -0
  85. dos/judges.py +366 -0
  86. dos/lane_infer.py +127 -0
  87. dos/lane_journal.py +1001 -0
  88. dos/lane_lease.py +952 -0
  89. dos/lane_overlap.py +228 -0
  90. dos/lease_health.py +282 -0
  91. dos/lifecycle.py +211 -0
  92. dos/liveness.py +352 -0
  93. dos/lock_modes.py +185 -0
  94. dos/log_source.py +395 -0
  95. dos/loop_decide.py +1746 -0
  96. dos/marker_gate.py +254 -0
  97. dos/marker_sensor.py +396 -0
  98. dos/noop_streak.py +280 -0
  99. dos/notify.py +479 -0
  100. dos/observe.py +175 -0
  101. dos/oracle.py +1661 -0
  102. dos/overlap_eval.py +214 -0
  103. dos/overlap_policy.py +342 -0
  104. dos/packet_sidecar.py +267 -0
  105. dos/phase_shipped.py +1985 -0
  106. dos/pick_priority.py +225 -0
  107. dos/pickable.py +369 -0
  108. dos/picker_oracle.py +1037 -0
  109. dos/plan_board.py +513 -0
  110. dos/plan_board_tui.py +113 -0
  111. dos/plan_source.py +455 -0
  112. dos/posttool_sensor.py +528 -0
  113. dos/precursor_gate.py +499 -0
  114. dos/precursor_gate_eval.py +239 -0
  115. dos/preflight.py +825 -0
  116. dos/pretool_sensor.py +490 -0
  117. dos/proc_delta.py +181 -0
  118. dos/productivity.py +296 -0
  119. dos/provider_limit.py +242 -0
  120. dos/py.typed +4 -0
  121. dos/reason_morphology.py +299 -0
  122. dos/reasons.py +449 -0
  123. dos/reconcile.py +173 -0
  124. dos/recurring_wedge.py +206 -0
  125. dos/render.py +393 -0
  126. dos/result_state.py +468 -0
  127. dos/resume.py +578 -0
  128. dos/resume_evidence.py +293 -0
  129. dos/retention.py +344 -0
  130. dos/reward.py +372 -0
  131. dos/rewind.py +587 -0
  132. dos/rewind_evidence.py +168 -0
  133. dos/rewind_tokens.py +252 -0
  134. dos/run_id.py +342 -0
  135. dos/scope.py +520 -0
  136. dos/scope_source.py +382 -0
  137. dos/scout.py +982 -0
  138. dos/self_modify.py +209 -0
  139. dos/sibling_scan.py +569 -0
  140. dos/skills/EXAMPLES.md +584 -0
  141. dos/skills/dos-class-cycle/SKILL.md +107 -0
  142. dos/skills/dos-dispatch/SKILL.md +177 -0
  143. dos/skills/dos-dispatch-loop/SKILL.md +254 -0
  144. dos/skills/dos-goal-gate/SKILL.md +269 -0
  145. dos/skills/dos-next-up/SKILL.md +231 -0
  146. dos/skills/dos-promote/SKILL.md +114 -0
  147. dos/skills/dos-replan/SKILL.md +159 -0
  148. dos/skills/dos-replan-loop/SKILL.md +114 -0
  149. dos/skills/dos-self-improve/SKILL.md +213 -0
  150. dos/skills/dos-supervise-loop/SKILL.md +180 -0
  151. dos/skills/dos-unstick/SKILL.md +108 -0
  152. dos/skills/dos-witness-claim/SKILL.md +251 -0
  153. dos/stamp.py +1002 -0
  154. dos/state_health.py +387 -0
  155. dos/status.py +114 -0
  156. dos/stop_policy.py +334 -0
  157. dos/supervise.py +1014 -0
  158. dos/testwitness.py +392 -0
  159. dos/timeline.py +1027 -0
  160. dos/tokens.py +485 -0
  161. dos/tool_stream.py +393 -0
  162. dos/tool_stream_eval.py +226 -0
  163. dos/trace.py +524 -0
  164. dos/verdict.py +140 -0
  165. dos/verdict_cli.py +189 -0
  166. dos/verdict_journal.py +497 -0
  167. dos/verdict_rollup.py +217 -0
  168. dos/verdicts.py +181 -0
  169. dos/wedge_reason.py +282 -0
  170. dos_kernel-0.22.0.dist-info/METADATA +859 -0
  171. dos_kernel-0.22.0.dist-info/RECORD +178 -0
  172. dos_kernel-0.22.0.dist-info/WHEEL +5 -0
  173. dos_kernel-0.22.0.dist-info/entry_points.txt +39 -0
  174. dos_kernel-0.22.0.dist-info/licenses/LICENSE +21 -0
  175. dos_kernel-0.22.0.dist-info/top_level.txt +2 -0
  176. dos_mcp/__init__.py +52 -0
  177. dos_mcp/py.typed +2 -0
  178. dos_mcp/server.py +779 -0
dos/improve.py ADDED
@@ -0,0 +1,499 @@
1
+ """improve — the self-improving-loop admission verdict: may this loop KEEP this candidate? (docs/280)
2
+
3
+ The kernel leaf of the **first self-improving work loop for DOS**. A
4
+ self-improvement loop is a `propose -> verify -> measure -> keep-or-revert`
5
+ cycle: an agent proposes a change, the change is checked, its effect is measured,
6
+ and it is kept only if the measurement says it helped. The shape is universal;
7
+ its one fatal failure mode is the one DOS exists to refuse —
8
+
9
+ the loop grades its own homework: the agent that WROTE the change is the one
10
+ that decides "yes, this is better," so it learns to NARRATE improvement
11
+ instead of making it.
12
+
13
+ This module is the witness-gated keep-gate that closes that hole. It is
14
+ `reward.admit` (docs/230/234 — "may a fine-tune TRAIN on this trajectory?")
15
+ re-aimed at a development loop ("may this loop KEEP this commit?"), conjoined with
16
+ the green-suite floor of the apply gate (docs/126) and bounded by the circuit
17
+ breaker (docs/223). It is the `loop_decide.decide` (docs/258) of the **outer**
18
+ loop, whose iterations are candidate self-improvements rather than dispatch
19
+ packets:
20
+
21
+ loop_decide.decide (LoopState, IterationOutcome) -> LoopDecision (inner: one dispatch iteration)
22
+ improve.classify (CandidateEvidence, policy) -> CandidateVerdict (outer: one self-improvement)
23
+ ^ THIS module
24
+
25
+ THE NON-FORGEABLE KEEP-BIT — why this makes recursive self-improvement honest
26
+ =============================================================================
27
+
28
+ The Anthropic recursive-self-improvement research names verification as the
29
+ gating constraint on the whole enterprise ("requiring verification regimes
30
+ enabling labs to confirm"; "human judgment on which problems matter remains the
31
+ acknowledged bottleneck"). The keep-bit here is a pure function of four facts,
32
+ **every one authored by the environment, none by the loop**:
33
+
34
+ * `suite_passed` — the test runner's exit status on the candidate-only tree.
35
+ * `truth_clean` — `dos verify` / `dos commit-audit` over git ancestry.
36
+ * `work` — the env-measured improvement metric, AFTER the candidate.
37
+ * `baseline_work` — the same metric, BEFORE the candidate.
38
+
39
+ There is no input the loop authors that can move REVERT -> KEEP. The candidate's
40
+ commit message, its code comments, its self-assessment — none are read by
41
+ `classify` (a `narrated` string is carried for the operator surface and parsed
42
+ for NOTHING, exactly as in `reward.admit`). This is the docs/234 theorem at loop
43
+ scale: *the policy cannot write its way into the kept set.* A loop that learns to
44
+ write "this is a great improvement" in every commit gains exactly zero
45
+ keep-probability, because the claim is not in the decision. The only path to KEEP
46
+ is to actually make the suite greener / the truth syscall clean / the metric move
47
+ — i.e. **to actually do the work.** That is the docs/138 invariant (the bytes
48
+ that decide are bytes the claimant did not author), and it is why DOS — the
49
+ substrate the loop runs ON and that refuses to believe it — is the right place
50
+ for a self-improving loop.
51
+
52
+ **Advisory, like every verdict leaf.** `classify` REPORTS KEEP/REVERT/ESCALATE;
53
+ it executes no merge, no `git checkout`, no escalation. The driver
54
+ (`dos.drivers.self_improve`) gathers the four facts at its I/O boundary and
55
+ carries out the verdict — the same mechanism/policy/actuation split as
56
+ `liveness`/`efficiency`/`breaker`.
57
+
58
+ **Mechanism is the kernel; which-metric is policy.** The kernel does not know
59
+ whether "improvement" means a newly-passing property test, a removed line of dead
60
+ policy `dos lint` reports zero of, a faster `dos verify` wall-clock, or a smaller
61
+ cold-start. The host names the metric in `dos.toml [improve]` and gathers it; the
62
+ kernel only compares the two magnitudes and reads the two boolean witnesses — the
63
+ `productivity`/`efficiency` work-unit split, applied to a candidate diff.
64
+
65
+ No I/O, no clock, names no host: a pure `classify(evidence, policy)` leaf.
66
+ """
67
+
68
+ from __future__ import annotations
69
+
70
+ import enum
71
+ from dataclasses import dataclass
72
+ from typing import Optional
73
+
74
+ from dos import breaker, efficiency
75
+
76
+
77
+ class Candidate(str, enum.Enum):
78
+ """The verdict for one candidate self-improvement.
79
+
80
+ `str`-valued so it round-trips through a CLI stdout token / exit-code map
81
+ without a lookup table (the `liveness.Liveness` / `breaker.BreakerState`
82
+ idiom). Three outcomes — the whole decision space of "what do I do with this
83
+ candidate diff?":
84
+
85
+ KEEP — a WITNESSED improvement: the suite is green on the candidate
86
+ tree, the truth syscall is clean, the env-measured metric strictly
87
+ improved, and the spend was not WASTEFUL. Merge it; reset the
88
+ breaker. This is the loop's `reward.admit` ACCEPT.
89
+ REVERT — discard the candidate and keep the tree as it was. Either it
90
+ REGRESSED (suite red or truth dirty — the non-negotiable floor) or
91
+ it was a NO-OP (safe but the metric did not move). The loop's
92
+ default action on an unwitnessed candidate is UNDO, not keep — the
93
+ abstention-first discipline (docs/87) at loop scale.
94
+ ESCALATE — the breaker is OPEN: too many candidates in a row that nothing
95
+ accepted. Stop proposing and surface to a human — the RSI
96
+ research's irreducible "human judgment on which problems matter"
97
+ seed (docs/223 Escalation.HUMAN).
98
+ """
99
+
100
+ KEEP = "KEEP"
101
+ REVERT = "REVERT"
102
+ ESCALATE = "ESCALATE"
103
+
104
+ def __str__(self) -> str: # pragma: no cover - trivial
105
+ return self.value
106
+
107
+
108
+ class RevertCause(str, enum.Enum):
109
+ """Why a REVERT — the floor failure vs the no-op miss, kept distinct.
110
+
111
+ A REVERT verdict is two genuinely different situations and an operator routes
112
+ them differently, so the kernel names which one fired (the `liveness`/`breaker`
113
+ echo-the-evidence / `tripped_on` discipline):
114
+
115
+ REGRESSED — the candidate broke the suite or failed the truth syscall. A
116
+ FAULT: the change actively made the kernel worse. The
117
+ non-negotiable floor — undone before any improvement is even
118
+ considered.
119
+ NO_IMPROVEMENT — the candidate is safe (suite green, truth clean) but the
120
+ env-measured metric did not strictly beat the baseline. A MISS,
121
+ not a fault: the change cost effort and moved nothing the metric
122
+ can see. Undone to keep the tree minimal.
123
+ WASTEFUL — the candidate improved the metric AND kept the suite green, but
124
+ `efficiency.classify` ruled the spend WASTEFUL/COSTLY for that
125
+ gain (a host armed an efficiency floor and the candidate fell
126
+ under it). A real-but-overpriced improvement the host chose to
127
+ refuse. Only reachable when the host arms an efficiency floor;
128
+ with the default disabled floor this cause never fires.
129
+ """
130
+
131
+ REGRESSED = "regressed"
132
+ NO_IMPROVEMENT = "no-improvement"
133
+ WASTEFUL = "wasteful"
134
+
135
+ def __str__(self) -> str: # pragma: no cover - trivial
136
+ return self.value
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class ImprovePolicy:
141
+ """The thresholds that gate a KEEP — policy, not mechanism.
142
+
143
+ The same "mechanism is kernel, thresholds are config" split as `breaker`'s
144
+ maxima and `efficiency`'s floor. The defaults are generic; a workspace declares
145
+ its own in `dos.toml [improve]` (closed-config-as-data, like `[breaker]`).
146
+
147
+ max_consecutive_reverts — trip the ESCALATE breaker after this many candidates
148
+ IN A ROW that did not KEEP (each REVERT bumps the carried count;
149
+ a KEEP resets it). The RSI research's bottleneck made
150
+ operational: when the loop cannot find a witnessed improvement
151
+ after N tries, hand the "which problem matters next" judgment
152
+ back to a human rather than burn budget on candidates nothing
153
+ accepts. The `breaker.BreakerPolicy.max_consecutive` rung.
154
+ efficiency_floor — the work-per-token floor handed to `efficiency.classify`
155
+ when judging whether a metric-improving candidate was worth its
156
+ spend. Default 0.0 = DISABLED (the `efficiency` default): every
157
+ improving candidate with green suite + clean truth is KEEP
158
+ regardless of token cost, and the WASTEFUL revert-cause never
159
+ fires. A host arms this only when it has a token budget that
160
+ means something for its improvement unit. (WASTEFUL-for-zero-work
161
+ never arises here because a zero-`work` candidate is already a
162
+ NO_IMPROVEMENT revert — the efficiency rung only ever sees a
163
+ candidate whose metric already beat the baseline.)
164
+ min_tokens_for_efficiency — the `efficiency.classify` min-spend floor below
165
+ which the efficiency rung withholds its accusation (a candidate
166
+ that improved the metric on a handful of tokens is not WASTEFUL,
167
+ it is cheap). Passed straight through to `EfficiencyPolicy`.
168
+
169
+ `max_consecutive_reverts` must be >= 1 (a breaker that escalates on zero
170
+ reverts would escalate before the loop ever ran).
171
+ """
172
+
173
+ max_consecutive_reverts: int = 3
174
+ efficiency_floor: float = 0.0
175
+ min_tokens_for_efficiency: int = 1000
176
+
177
+ def __post_init__(self) -> None:
178
+ if self.max_consecutive_reverts < 1:
179
+ raise ValueError(
180
+ "max_consecutive_reverts must be >= 1 (a breaker that escalates "
181
+ "before the loop runs is a config error)"
182
+ )
183
+ if self.efficiency_floor < 0:
184
+ raise ValueError("efficiency_floor must be non-negative")
185
+ if self.min_tokens_for_efficiency < 0:
186
+ raise ValueError("min_tokens_for_efficiency must be non-negative")
187
+
188
+
189
+ DEFAULT_POLICY = ImprovePolicy()
190
+
191
+
192
+ @dataclass(frozen=True)
193
+ class CandidateEvidence:
194
+ """The facts `classify()` reads — gathered by the CALLER at its I/O boundary.
195
+
196
+ No subprocess, no git, no clock inside the verdict (the arbiter rule): the
197
+ driver runs the suite, runs the truth syscall, measures the metric, and counts
198
+ the tokens, then freezes the RESULTS here. Every field is env-authored — the
199
+ docs/138 invariant that makes the keep-bit non-forgeable:
200
+
201
+ suite_passed — the test runner's exit status on a tree carrying the
202
+ candidate's diff AND NOTHING ELSE (the candidate-only tree —
203
+ a worktree, so a concurrent edit cannot contaminate the
204
+ witness). True iff `python -m pytest -q` (or the host's
205
+ suite) exited 0. The runner authored it.
206
+ truth_clean — True iff the truth witnesses are clean: `dos verify` for any
207
+ phase the candidate claims, and/or `dos commit-audit` over
208
+ the candidate commit (the subject's claim matches its diff).
209
+ The git machinery + the oracle authored it; the loop did not.
210
+ work — the env-measured improvement metric AFTER the candidate (the
211
+ host's unit: passing-property-test count, 1000 minus the
212
+ `dos lint` finding count, a wall-clock budget, …). The same
213
+ work unit `productivity`/`efficiency` count. Non-negative.
214
+ baseline_work — the SAME metric BEFORE the candidate (measured on the green
215
+ baseline tree at the start of the cycle). Non-negative. KEEP
216
+ requires `work > baseline_work` — a STRICT, env-measured gain.
217
+ tokens — the tokens the candidate's proposing agent spent (the provider
218
+ usage record), the env-authored price for the efficiency rung.
219
+ Non-negative.
220
+ consecutive_reverts — the CARRIED breaker count: how many prior candidates in
221
+ a row did not KEEP. Threaded by the driver through the cycle
222
+ (the `breaker.BreakerCounts.consecutive` / `LoopState`
223
+ counter). Non-negative.
224
+
225
+ `narrated` is the candidate's own description of what it did — carried for the
226
+ operator surface (`dos improve --json` echoes it) and **parsed for nothing**.
227
+ It is the agent-authored, forgeable channel; by construction it touches no part
228
+ of the verdict (the `reward.admit` / `effect_witness` `narrated`-is-not-read
229
+ discipline). It exists so the operator can read *why the loop thought* a
230
+ reverted candidate would help — never so the loop can argue its way to a KEEP.
231
+ """
232
+
233
+ suite_passed: bool
234
+ truth_clean: bool
235
+ work: int = 0
236
+ baseline_work: int = 0
237
+ tokens: int = 0
238
+ consecutive_reverts: int = 0
239
+ narrated: str = ""
240
+
241
+ def __post_init__(self) -> None:
242
+ if self.work < 0:
243
+ raise ValueError("work must be non-negative (a count of the metric)")
244
+ if self.baseline_work < 0:
245
+ raise ValueError("baseline_work must be non-negative")
246
+ if self.tokens < 0:
247
+ raise ValueError("tokens must be non-negative (a count of tokens spent)")
248
+ if self.consecutive_reverts < 0:
249
+ raise ValueError("consecutive_reverts must be non-negative")
250
+
251
+ @property
252
+ def improved(self) -> bool:
253
+ """True iff the env-measured metric STRICTLY beat the baseline — the gain
254
+ the environment witnessed, never the agent's claim of one."""
255
+ return self.work > self.baseline_work
256
+
257
+ @property
258
+ def delta(self) -> int:
259
+ """The signed metric change (after minus before). Positive iff improved."""
260
+ return self.work - self.baseline_work
261
+
262
+
263
+ @dataclass(frozen=True)
264
+ class CandidateVerdict:
265
+ """The single verdict `classify()` returns, with the facts echoed back.
266
+
267
+ `verdict` is the typed `Candidate`. `revert_cause` is set ONLY on a REVERT
268
+ (which of the two/three revert situations fired — REGRESSED / NO_IMPROVEMENT /
269
+ WASTEFUL), None otherwise. `next_consecutive_reverts` is the breaker count the
270
+ driver carries into the NEXT cycle — reset to 0 on KEEP, bumped on REVERT, and
271
+ on ESCALATE held at the tripping value (the loop is stopping, the count is
272
+ historical). `escalation` is the trust-ladder rung an ESCALATE names (HUMAN by
273
+ construction — a stuck improver is a human-seed, never a deterministic
274
+ re-check); NONE otherwise. `reason` is the one-line operator-facing summary.
275
+ `evidence` is echoed so `dos improve --json` emits the verdict AND the facts
276
+ behind it in one object (the legible-distrust renderer seam — the operator sees
277
+ not just REVERT but *why*: suite red, or metric flat at 42).
278
+ """
279
+
280
+ verdict: Candidate
281
+ next_consecutive_reverts: int
282
+ reason: str
283
+ evidence: CandidateEvidence
284
+ revert_cause: Optional[RevertCause] = None
285
+ escalation: breaker.Escalation = breaker.Escalation.NONE
286
+
287
+ @property
288
+ def is_keep(self) -> bool:
289
+ return self.verdict is Candidate.KEEP
290
+
291
+ def to_dict(self) -> dict:
292
+ e = self.evidence
293
+ return {
294
+ "verdict": self.verdict.value,
295
+ "revert_cause": self.revert_cause.value if self.revert_cause else None,
296
+ "escalation": self.escalation.value,
297
+ "next_consecutive_reverts": self.next_consecutive_reverts,
298
+ "reason": self.reason,
299
+ "evidence": {
300
+ "suite_passed": e.suite_passed,
301
+ "truth_clean": e.truth_clean,
302
+ "work": e.work,
303
+ "baseline_work": e.baseline_work,
304
+ "delta": e.delta,
305
+ "improved": e.improved,
306
+ "tokens": e.tokens,
307
+ "consecutive_reverts": e.consecutive_reverts,
308
+ "narrated": e.narrated,
309
+ },
310
+ }
311
+
312
+
313
+ def _efficiency_policy(policy: ImprovePolicy) -> efficiency.EfficiencyPolicy:
314
+ """Build the `EfficiencyPolicy` for the WASTEFUL rung from the improve policy."""
315
+ return efficiency.EfficiencyPolicy(
316
+ min_tokens=policy.min_tokens_for_efficiency, floor=policy.efficiency_floor
317
+ )
318
+
319
+
320
+ def classify(
321
+ evidence: CandidateEvidence, policy: ImprovePolicy = DEFAULT_POLICY
322
+ ) -> CandidateVerdict:
323
+ """Decide KEEP / REVERT / ESCALATE for one candidate self-improvement. PURE — no I/O.
324
+
325
+ Reads the ladder top to bottom (this function IS the answer to "when does the
326
+ loop keep a candidate?"):
327
+
328
+ 1. REVERT (regressed) — the suite is red OR the truth syscall is dirty. The
329
+ non-negotiable floor: a self-improvement that breaks the kernel is not an
330
+ improvement, full stop. Checked FIRST, before any gain is considered, and
331
+ before the breaker — a regression is undone whether or not the breaker
332
+ would also escalate (the operator wants "it broke the suite," and the
333
+ bumped count rides along in `next_consecutive_reverts` for the next cycle).
334
+ The conjunctive floor (docs/113 `admissible_under_floor`): KEEP requires
335
+ clearing this floor AND the improvement policy admitting — the floor alone
336
+ can never be overridden by a "but it's better" claim.
337
+
338
+ 2. ESCALATE (breaker open) — the candidate did NOT clear the floor as an
339
+ improvement (it is heading for a REVERT) AND bumping the carried
340
+ `consecutive_reverts` reaches `max_consecutive_reverts`. Stop proposing and
341
+ surface to a human (docs/223 Escalation.HUMAN) — the RSI bottleneck: N
342
+ candidates in a row that nothing accepted means the loop should hand the
343
+ "what matters next" judgment back, not propose an (N+1)th. Checked AFTER
344
+ the regression floor so the operator still learns a regression *as* a
345
+ regression even on the escalating candidate — the ESCALATE carries the
346
+ revert_cause that tipped it.
347
+
348
+ 3. KEEP (witnessed improvement) — suite green AND truth clean AND the metric
349
+ STRICTLY improved (`work > baseline_work`, an env-measured gain) AND the
350
+ spend was not WASTEFUL/COSTLY by `efficiency.classify`. The improvement is
351
+ witnessed by bytes the loop did not author, so keep it and RESET the
352
+ breaker (`next_consecutive_reverts = 0`). This is the loop's `reward.admit`
353
+ ACCEPT — the gain is a count the environment authored, never the claim.
354
+
355
+ 4. REVERT (no improvement / wasteful) — suite green and truth clean, but
356
+ either the metric did not beat the baseline (NO_IMPROVEMENT — a safe miss)
357
+ or it did but the spend was WASTEFUL/COSTLY under an armed efficiency floor
358
+ (WASTEFUL — a real-but-overpriced gain the host refuses). Undo it to keep
359
+ the tree minimal; bump the breaker (a no-op still counts toward escalation,
360
+ so a loop that only ever proposes no-ops eventually stops).
361
+
362
+ The breaker arithmetic is `breaker.record_failure` / `record_success`
363
+ specialized to the carried `consecutive_reverts` field — the same primitive
364
+ `loop_decide` routes its streaks through (docs/258). A REVERT is a `failure`
365
+ (bump); a KEEP is a `success` (reset).
366
+ """
367
+ brk_policy = breaker.BreakerPolicy(
368
+ max_consecutive=policy.max_consecutive_reverts,
369
+ max_total=0, # consecutive-only — a no-op streak escalates; cumulative is not the signal
370
+ on_trip=breaker.Escalation.HUMAN,
371
+ )
372
+
373
+ # 1. REVERT (regressed) — the non-negotiable floor. A red suite or a dirty truth
374
+ # syscall is undone before any improvement is weighed. We still compute the
375
+ # breaker transition so the carried count rides forward (and an escalating
376
+ # regression surfaces as a regression, below).
377
+ if not evidence.suite_passed or not evidence.truth_clean:
378
+ trip = breaker.record_failure(
379
+ breaker.BreakerCounts(consecutive=evidence.consecutive_reverts),
380
+ brk_policy,
381
+ )
382
+ why = (
383
+ "the test suite is RED"
384
+ if not evidence.suite_passed
385
+ else "the truth syscall is DIRTY (dos verify / commit-audit refused)"
386
+ )
387
+ if trip.verdict.is_open:
388
+ return CandidateVerdict(
389
+ verdict=Candidate.ESCALATE,
390
+ next_consecutive_reverts=trip.counts.consecutive,
391
+ reason=(
392
+ f"candidate REGRESSED ({why}) AND that is "
393
+ f"{trip.counts.consecutive} reverted candidates in a row "
394
+ f"(>= {policy.max_consecutive_reverts}) — revert and ESCALATE "
395
+ f"to a human (the loop can't find a witnessed improvement)"
396
+ ),
397
+ evidence=evidence,
398
+ revert_cause=RevertCause.REGRESSED,
399
+ escalation=trip.verdict.escalation,
400
+ )
401
+ return CandidateVerdict(
402
+ verdict=Candidate.REVERT,
403
+ next_consecutive_reverts=trip.counts.consecutive,
404
+ reason=(
405
+ f"candidate REGRESSED ({why}) — revert; it broke the kernel, so it "
406
+ f"is not an improvement no matter what it claims "
407
+ f"({trip.counts.consecutive}/{policy.max_consecutive_reverts} "
408
+ f"consecutive reverts)"
409
+ ),
410
+ evidence=evidence,
411
+ revert_cause=RevertCause.REGRESSED,
412
+ )
413
+
414
+ # The candidate cleared the non-negotiable floor (suite green, truth clean).
415
+ # Now: did it actually IMPROVE the env-measured metric?
416
+ if evidence.improved:
417
+ # It improved. Was the spend worth it? Ask `efficiency` — but only the
418
+ # WASTEFUL/COSTLY rung can refuse here (a zero-work candidate never reaches
419
+ # this branch — it is a NO_IMPROVEMENT revert below). With the default
420
+ # disabled floor, `efficiency` always returns EFFICIENT for nonzero work, so
421
+ # this rung is a no-op until a host arms a floor.
422
+ eff = efficiency.classify(
423
+ efficiency.EfficiencyEvidence.of(work=evidence.delta, tokens=evidence.tokens),
424
+ _efficiency_policy(policy),
425
+ )
426
+ if eff.verdict is efficiency.Efficiency.EFFICIENT:
427
+ # KEEP — a witnessed, well-priced improvement. Reset the breaker.
428
+ healed = breaker.record_success(
429
+ breaker.BreakerCounts(consecutive=evidence.consecutive_reverts),
430
+ brk_policy,
431
+ )
432
+ return CandidateVerdict(
433
+ verdict=Candidate.KEEP,
434
+ next_consecutive_reverts=healed.counts.consecutive, # 0
435
+ reason=(
436
+ f"candidate KEPT — suite green, truth clean, metric improved "
437
+ f"{evidence.baseline_work} -> {evidence.work} (+{evidence.delta}); "
438
+ f"witnessed by bytes the loop did not author, so the gain is real"
439
+ ),
440
+ evidence=evidence,
441
+ )
442
+ # Improved, but the host's efficiency floor refused the price. A REVERT, not
443
+ # a KEEP — bump the breaker like any other non-keep.
444
+ trip = breaker.record_failure(
445
+ breaker.BreakerCounts(consecutive=evidence.consecutive_reverts),
446
+ brk_policy,
447
+ )
448
+ verdict = Candidate.ESCALATE if trip.verdict.is_open else Candidate.REVERT
449
+ return CandidateVerdict(
450
+ verdict=verdict,
451
+ next_consecutive_reverts=trip.counts.consecutive,
452
+ reason=(
453
+ f"candidate improved the metric (+{evidence.delta}) but the spend "
454
+ f"was {eff.verdict} for that gain ({eff.reason}) — revert the "
455
+ f"overpriced improvement"
456
+ + (
457
+ f" AND ESCALATE ({trip.counts.consecutive} reverts in a row)"
458
+ if trip.verdict.is_open
459
+ else ""
460
+ )
461
+ ),
462
+ evidence=evidence,
463
+ revert_cause=RevertCause.WASTEFUL,
464
+ escalation=trip.verdict.escalation,
465
+ )
466
+
467
+ # 4. REVERT (no improvement) — safe but the metric did not move. A miss, not a
468
+ # fault. Undo to keep the tree minimal; bump the breaker so an all-no-op loop
469
+ # eventually escalates.
470
+ trip = breaker.record_failure(
471
+ breaker.BreakerCounts(consecutive=evidence.consecutive_reverts),
472
+ brk_policy,
473
+ )
474
+ if trip.verdict.is_open:
475
+ return CandidateVerdict(
476
+ verdict=Candidate.ESCALATE,
477
+ next_consecutive_reverts=trip.counts.consecutive,
478
+ reason=(
479
+ f"candidate did not improve the metric (flat at {evidence.work}) AND "
480
+ f"that is {trip.counts.consecutive} non-improving candidates in a row "
481
+ f"(>= {policy.max_consecutive_reverts}) — revert and ESCALATE to a "
482
+ f"human (the loop has run dry of witnessed improvements)"
483
+ ),
484
+ evidence=evidence,
485
+ revert_cause=RevertCause.NO_IMPROVEMENT,
486
+ escalation=trip.verdict.escalation,
487
+ )
488
+ return CandidateVerdict(
489
+ verdict=Candidate.REVERT,
490
+ next_consecutive_reverts=trip.counts.consecutive,
491
+ reason=(
492
+ f"candidate is safe (suite green, truth clean) but did not improve the "
493
+ f"metric (flat at {evidence.work}) — revert; a safe no-op is not kept "
494
+ f"({trip.counts.consecutive}/{policy.max_consecutive_reverts} consecutive "
495
+ f"reverts)"
496
+ ),
497
+ evidence=evidence,
498
+ revert_cause=RevertCause.NO_IMPROVEMENT,
499
+ )