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/noop_streak.py ADDED
@@ -0,0 +1,280 @@
1
+ """NOS — the no-op-streak verdict: *how many turns in a row produced zero ground-truth delta?*
2
+
3
+ docs/259 §Follow-up 1 — the **generalization of the wait-marker budget** off its one
4
+ special case ("markers emitted") onto the general one ("no-op turns since the last
5
+ forward delta"). `loop_decide.wait_marker_budget` already answers a count-vs-cap
6
+ question for ONE flavor of no-op turn: a `claude -p` keep-alive marker — a full
7
+ assistant turn that replays the whole context out of cache and produces nothing but
8
+ "still waiting." But a `ScheduleWakeup`-poll loop that re-reads a `.output` file in a
9
+ tight tick, or any loop that wakes, finds no change, and goes back to sleep, is the
10
+ *same* pathology: a turn that paid the cache-replay cost and moved no ground truth.
11
+ This module is the verdict that makes those one verdict — "the run has taken N no-op
12
+ turns since it last made a forward delta; has it spent its budget?"
13
+
14
+ This is `wait_marker_budget`'s **generalization**, and it sits in the temporal-verdict
15
+ family (`liveness` / `tool_stream` / `productivity` / **`noop_streak`**) — the same
16
+ pure-verdict shape, re-aimed once more:
17
+
18
+ liveness.classify (ProgressEvidence, policy) -> LivenessVerdict (did state move AT ALL?)
19
+ tool_stream.classify_stream (ToolStream, policy) -> StreamVerdict (is the tool stream repeating?)
20
+ productivity.classify (WorkHistory, policy) -> ProductivityVerdict (is the work-RATE fading?)
21
+ noop_streak.classify (NoOpHistory, policy) -> NoOpStreakVerdict (how many no-op turns since a forward delta?)
22
+ ^ THIS module
23
+
24
+ It is a COUNT-vs-cap verdict, not a TREND verdict — which is why it is `wait_marker_budget`'s
25
+ sibling and NOT a second mode of `productivity`. `productivity` reads a *vector* of
26
+ per-step work magnitudes and asks "is the rate falling" (`deltas[-1]`/`deltas[-2]`);
27
+ `noop_streak` reads a *single* monotone counter — the run-length of consecutive
28
+ no-op turns since the last forward delta — and asks "is that run-length past its
29
+ budget." Folding the streak into `productivity.WorkHistory` would overload its
30
+ well-defined "fading rate" semantics with a different question and collide its
31
+ "withhold the accusation / reject negatives" floor with this guard's opposite
32
+ conservative direction (below). So the streak gets its own small verdict, the way
33
+ `productivity` got its own rather than living inside `liveness`.
34
+
35
+ **The forward-delta reset is the load-bearing idea** (docs/259 §Follow-up 2). The
36
+ count is not "no-op turns ever" — it is "no-op turns *since the last forward delta*."
37
+ A forward delta (a commit, a real tool result, a host re-entering a fresh wait phase)
38
+ ZEROES the streak, the `tool_stream` ADVANCING analogue: progress earns the loop a
39
+ fresh budget. Without a reset the count is a strict lifetime monotone (what
40
+ `wait_marker_budget` is today); with one, a long-lived session that legitimately
41
+ makes progress and then re-enters a wait phase starts fresh instead of being refused
42
+ on a stale tally. The reset lives at the BOUNDARY (`marker_sensor.record_reset`
43
+ appends an `op:"RESET"` record; the replayed count is markers-after-the-last-reset);
44
+ this pure verdict just reads the resulting count.
45
+
46
+ **Byte-clean by construction.** A no-op turn is a turn the *runtime* observed to
47
+ produce zero ground-truth delta — it is counted by the durable accumulator
48
+ (`marker_sensor`), never threaded through the agent's own narration. So EXHAUSTED is
49
+ "the environment recorded N no-op turns since the last forward delta," never "the
50
+ agent says it has waited long enough" — a quantity, not a self-report (the docs/138
51
+ invariant `liveness`/`productivity`/`tool_stream` all keep).
52
+
53
+ **Advisory.** Like `wait_marker_budget` and `liveness.SPINNING`, EXHAUSTED REPORTS;
54
+ it never kills a process. A loop consults it and chooses to stop holding its turn
55
+ open (the marker hook is the first consumer — it blocks the Stop while the budget is
56
+ LIVE, allows the Stop once EXHAUSTED); nothing here enforces.
57
+
58
+ **The conservative direction is the OPPOSITE of `productivity`'s** — and that is why
59
+ it is a separate verdict. `productivity` withholds the DIMINISHING accusation when in
60
+ doubt (a missing delta is "still productive"). A *cost* guard must do the reverse:
61
+ when in doubt, count the no-op turn (so the guard refuses one *more* keep-alive turn,
62
+ never one *fewer*) — over-spending on a missed count is the failure to avoid. The
63
+ accumulator honors this by leaving a torn/unreadable RESET as "the reset didn't
64
+ happen" (the count stays HIGHER → EXHAUSTED sooner → refuse more); this verdict
65
+ honors it by treating `noop_turns >= max_streak` as EXHAUSTED (the `>=`, not `>`, so
66
+ the budget is spent the instant it is reached). Refusing one no-op turn too early
67
+ costs at most one missed poll; the real Bash `<task-notification>` (which fires on
68
+ the child's true exit regardless) is the safety net.
69
+
70
+ **No-telemetry / no-plan discipline** (the `test_verify_no_plan` sibling): NOS needs
71
+ *nothing* but the no-op-turn count the caller already replayed. No plan, no registry,
72
+ no clock — `classify()` makes no I/O at all (it is timeless, like `productivity`; it
73
+ reads a count, not an age). A caller with a count gets a verdict; a caller with 0 gets
74
+ the honest LIVE floor (a fresh wait phase has spent nothing yet).
75
+ """
76
+
77
+ from __future__ import annotations
78
+
79
+ import enum
80
+ from dataclasses import dataclass
81
+ from pathlib import Path
82
+
83
+
84
+ class NoOpStreak(str, enum.Enum):
85
+ """The typed no-op-streak verdict — two states, mutually exclusive.
86
+
87
+ `str`-valued so it round-trips through a CLI stdout token / exit-code map without
88
+ a lookup table (mirrors `liveness.Liveness` / `productivity.Productivity` /
89
+ `gate_classify.Verdict`).
90
+ """
91
+
92
+ LIVE = "LIVE" # the streak is under the cap — another no-op turn is permitted
93
+ EXHAUSTED = "EXHAUSTED" # the streak has reached the cap — refuse further no-op turns
94
+
95
+ def __str__(self) -> str: # pragma: no cover - trivial
96
+ return self.value
97
+
98
+
99
+ @dataclass(frozen=True)
100
+ class NoOpStreakPolicy:
101
+ """The cap that separates LIVE / EXHAUSTED — policy, not mechanism.
102
+
103
+ The same "mechanism is kernel, threshold is config" split as `productivity`'s
104
+ floor and `loop_decide`'s `max_iterations`. The default is GENERIC and equals
105
+ `wait_marker_budget`'s default (4) — so the generalized verdict refuses at the
106
+ same budget the shipped marker lever does (the marker case is the special case,
107
+ and it must not drift). A workspace declares its own in `dos.toml [noop_streak]`,
108
+ the closed-config-as-data pattern (`[tool_stream]` / `[productivity]` / `[stamp]`).
109
+
110
+ max_streak — the **no-op-turn budget since the last forward delta**: the most
111
+ consecutive zero-delta turns a loop may take before the verdict
112
+ refuses the next one. `wait_marker_budget`'s `max_markers`,
113
+ generalized off "markers" onto "no-op turns." A streak that REACHES
114
+ this cap (`>=`, the cost-guard direction) is EXHAUSTED.
115
+
116
+ Default: 4 — `wait_marker_budget`'s per-run cap, one below the `keepalive_poll`
117
+ telemetry flag (>=5), so the runtime refusal lands one turn before the post-hoc
118
+ alarm would fire.
119
+ """
120
+
121
+ max_streak: int = 4
122
+
123
+ def __post_init__(self) -> None:
124
+ if self.max_streak < 0:
125
+ raise ValueError("max_streak must be non-negative")
126
+
127
+
128
+ DEFAULT_POLICY = NoOpStreakPolicy()
129
+
130
+
131
+ @dataclass(frozen=True)
132
+ class NoOpHistory:
133
+ """The no-op-turn count `classify()` reads — gathered by the CALLER at the boundary.
134
+
135
+ No clock, no I/O inside the verdict — the arbiter rule, sharpened: NOS is
136
+ *timeless* (it reads a count, never an age). The caller's boundary
137
+ (`marker_sensor.marker_count`, which replays the session's `.dos/markers/<sid>.jsonl`
138
+ tally into markers-since-the-last-RESET) measures the streak and freezes it here.
139
+
140
+ noop_turns — the count of no-op turns SINCE the last forward-delta reset. A
141
+ no-op turn is a turn the runtime observed to produce zero ground-truth
142
+ delta (today: one keep-alive wait-marker; the generalization also
143
+ admits a poll-tick that found no change). 0 is "a fresh wait phase,
144
+ nothing spent yet" — the LIVE floor. Negative is rejected: a streak
145
+ length is a non-negative count.
146
+
147
+ The single load-bearing read is `noop_turns` vs the policy's `max_streak` — the
148
+ same count-vs-cap `wait_marker_budget(markers_emitted, max_markers)` makes. The
149
+ count is carried (not just a bool) so `--output json` can echo it and the verdict
150
+ can hand back the incremented value to carry into the next decision.
151
+ """
152
+
153
+ noop_turns: int = 0
154
+
155
+ def __post_init__(self) -> None:
156
+ if self.noop_turns < 0:
157
+ raise ValueError("noop_turns must be non-negative (a count of no-op turns)")
158
+
159
+ @classmethod
160
+ def of(cls, noop_turns: int) -> "NoOpHistory":
161
+ """Build a history from a replayed no-op-turn count."""
162
+ return cls(noop_turns)
163
+
164
+
165
+ @dataclass(frozen=True)
166
+ class NoOpStreakVerdict:
167
+ """The single verdict `classify()` returns, with the count echoed back.
168
+
169
+ `verdict` is the typed `NoOpStreak`. `allow` is the convenience bit the marker
170
+ hook keys on (`allow == (verdict is NoOpStreak.LIVE)`): True to permit one more
171
+ no-op turn, False to refuse it. `noop_turns` is the count to carry into the *next*
172
+ decision — incremented when allowed (this no-op turn now happened), unchanged when
173
+ refused (the refused turn did not happen) — byte-mirroring
174
+ `WaitMarkerDecision.markers_emitted`. `reason` is operator-facing. `to_dict` is the
175
+ json shape (the legible-distrust renderer seam: the operator sees not just
176
+ EXHAUSTED but the count and cap behind it).
177
+ """
178
+
179
+ verdict: NoOpStreak
180
+ allow: bool
181
+ noop_turns: int
182
+ reason: str
183
+
184
+ def to_dict(self) -> dict:
185
+ return {
186
+ "verdict": self.verdict.value,
187
+ "allow": self.allow,
188
+ "noop_turns": self.noop_turns,
189
+ "reason": self.reason,
190
+ }
191
+
192
+
193
+ def classify(
194
+ history: NoOpHistory, policy: NoOpStreakPolicy = DEFAULT_POLICY
195
+ ) -> NoOpStreakVerdict:
196
+ """Classify a run's no-op streak against its budget. PURE — no I/O.
197
+
198
+ The `wait_marker_budget` arithmetic, generalized: a count vs a cap, with the
199
+ cost-guard `>=` (the budget is spent the instant it is reached, not one past).
200
+
201
+ * EXHAUSTED (refuse) — `noop_turns >= max_streak`: the run has taken its whole
202
+ budget of no-op turns since the last forward delta. The next one would replay
203
+ full context out of cache for no work; refuse it (the loop ends its turn and
204
+ waits on the real completion signal). The count carried forward is UNCHANGED
205
+ (a refused turn did not happen).
206
+ * LIVE (allow) — `noop_turns < max_streak`: budget remains; permit one more no-op
207
+ turn and carry `noop_turns + 1` (this turn now happened) into the next decision.
208
+
209
+ A `max_streak == 0` policy refuses the FIRST no-op turn (`0 >= 0`) — the degenerate
210
+ `wait_marker_budget(0, 0)` preserves, and the honest reading of "no budget at all."
211
+ """
212
+ if history.noop_turns >= policy.max_streak:
213
+ return NoOpStreakVerdict(
214
+ verdict=NoOpStreak.EXHAUSTED,
215
+ allow=False,
216
+ noop_turns=history.noop_turns,
217
+ reason=(
218
+ f"no-op streak budget exhausted "
219
+ f"({history.noop_turns}/{policy.max_streak} no-op turns since the last "
220
+ f"forward delta) — each further turn replays full context out of cache "
221
+ f"for no work; wait on the real completion signal, a forward delta resets "
222
+ f"the streak"
223
+ ),
224
+ )
225
+ return NoOpStreakVerdict(
226
+ verdict=NoOpStreak.LIVE,
227
+ allow=True,
228
+ noop_turns=history.noop_turns + 1,
229
+ reason=(
230
+ f"no-op streak {history.noop_turns + 1}/{policy.max_streak} since the last "
231
+ f"forward delta — budget remains"
232
+ ),
233
+ )
234
+
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # The declarative on-ramp — read a policy out of dos.toml (mirror tool_stream/productivity).
238
+ # ---------------------------------------------------------------------------
239
+ def policy_from_table(table: dict) -> NoOpStreakPolicy:
240
+ """Turn a parsed `[noop_streak]` TOML table into a `NoOpStreakPolicy`. PURE (no I/O).
241
+
242
+ `table` is `{max_streak?}` — the shape `tomllib.load(...)["noop_streak"]` yields. A
243
+ missing key falls back to the generic default; a malformed value raises (via
244
+ `NoOpStreakPolicy.__post_init__`), so a bad declaration fails loudly at load (the
245
+ `tool_stream.policy_from_table` posture).
246
+ """
247
+ if not table:
248
+ return DEFAULT_POLICY
249
+ return NoOpStreakPolicy(
250
+ max_streak=int(table.get("max_streak", DEFAULT_POLICY.max_streak)),
251
+ )
252
+
253
+
254
+ def load_from_toml(
255
+ path: "Path | str", *, base: NoOpStreakPolicy = DEFAULT_POLICY
256
+ ) -> NoOpStreakPolicy:
257
+ """Build a `NoOpStreakPolicy` from a `dos.toml`'s `[noop_streak]` table.
258
+
259
+ Returns `base` unchanged when the file is absent, has no `[noop_streak]` table, or
260
+ `tomllib` is unavailable — the declarative path is purely additive, so a missing/empty
261
+ config degrades to the generic default, never an error (the `tool_stream.load_from_toml`
262
+ contract). A *present but malformed* table raises (`NoOpStreakPolicy.__post_init__`).
263
+ Reads with `utf-8-sig` to strip a PowerShell-written BOM (the
264
+ `reasons`/`intervention`/`tool_stream` `load_from_toml` fix).
265
+ """
266
+ p = Path(path)
267
+ if not p.exists():
268
+ return base
269
+ try:
270
+ import tomllib # py3.11+
271
+ except ModuleNotFoundError: # pragma: no cover - py<3.11 fallback
272
+ try:
273
+ import tomli as tomllib # type: ignore
274
+ except ModuleNotFoundError:
275
+ return base
276
+ data = tomllib.loads(p.read_text(encoding="utf-8-sig"))
277
+ table = data.get("noop_streak")
278
+ if not isinstance(table, dict) or not table:
279
+ return base
280
+ return policy_from_table(table)