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/drivers/job.py ADDED
@@ -0,0 +1,47 @@
1
+ """dos.drivers.job — the reference userland app's policy pack (the reference driver).
2
+
3
+ This is the concrete *policy* the reference userland app supplies to the kernel
4
+ mechanism: a STRUCTURAL lane fallback (the exclusive `orchestration`/`global`
5
+ lanes) plus a factory that binds the workspace's authoritative taxonomy. The
6
+ authoritative work-lane taxonomy is NOT here — it lives in the consumer repo's
7
+ `dos.toml [lanes]` (read by `job_config` via `load_workspace_config`); this pack's
8
+ literal is only the domain-free fallback for a workspace with no `[lanes]`. It is
9
+ the reference example of what a host-repo driver looks like — the kernel ships
10
+ only the generic `main`/`global` default (`dos.config.default_config`) and reads
11
+ policy through `SubstrateConfig`; it hardcodes none of these names.
12
+
13
+ The structural-fallback literal lives in the `dos._job_policy` leaf (a near-leaf
14
+ that imports only `LaneTaxonomy` from `dos.config`). This module re-exports it as
15
+ the public host-policy surface: new code says `from dos.drivers.job import
16
+ JOB_LANE_TAXONOMY`. `job_config` is the binding factory — it still lives in
17
+ `dos.config` (it constructs the full `SubstrateConfig` from paths + facts, then
18
+ layers the workspace `dos.toml` over the fallback taxonomy) and is re-exported
19
+ here for the one-stop driver import.
20
+
21
+ Layering (resolves the 2026-06-01 audit's deferred relocation): `config` is
22
+ layer 2, `drivers` is layer 4, and the taxonomy leaf `_job_policy` is a near-leaf
23
+ (`_job_policy → config` for the `LaneTaxonomy` class only). So both layers import
24
+ the leaf without inverting the one-way arrow — the move the old audit said
25
+ "needs a third home BOTH layers may import (a `dos._job_policy` leaf, say)" is
26
+ now done. `dos.config` no longer DEFINES the domain taxonomy; it exposes
27
+ `JOB_LANE_TAXONOMY` only as a PEP-562 backward-compatible lazy attribute.
28
+
29
+ De-clustering note (2026-06-02): the taxonomy has `concurrent=()` / `autopick=()`
30
+ — there is no privileged concurrency/auto-pick set. Concurrency is gated by
31
+ tree-disjointness alone (the arbiter never reads `concurrent`), and bare auto-pick
32
+ is priority-first via the host's ladder.
33
+
34
+ Dynamic-claim-area note (2026-06-06, dos/119): the work-lane trees
35
+ (apply/tailor/discovery/recruiter/…) and their aliases were removed from the
36
+ fallback literal — they were userland policy and now live in the consumer's
37
+ `dos.toml`. A `--scope apply` request resolves to the narrow per-pick footprint
38
+ via the host's `_dynamic_claim_space`, not a curated `trees["apply"]`. The
39
+ fallback literal carries only the two exclusive lanes' structural trees.
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ from dos._job_policy import JOB_LANE_TAXONOMY
45
+ from dos.config import job_config
46
+
47
+ __all__ = ["JOB_LANE_TAXONOMY", "job_config"]
@@ -0,0 +1,360 @@
1
+ """dos.drivers.llm_judge — the OPTIONAL LLM adjudicator (outside the kernel line).
2
+
3
+ The kernel's judge (`dos judge` → `picker_oracle.classify`) is **deterministic by
4
+ construction**: it cross-checks a no-pick verdict's self-reported `reason_class`
5
+ against on-disk state and emits a provable `oracle_disagrees`. That is the right
6
+ default — the kernel's whole thesis is "the part that doesn't believe the agents,"
7
+ and it must never grow an LLM-provider branch (the DSP "Bulkhead" axiom).
8
+
9
+ But the deterministic oracle can only *abstain* on the `UNCLASSIFIED` rows — a
10
+ no-pick whose `reason_class` is a legacy / hand-authored token it has no
11
+ cross-check for. Those are exactly the rows an LLM adjudicator can usefully rule
12
+ on. So the LLM judge lives **here, in a driver** — outside the kernel boundary,
13
+ where provider surface is allowed (`drivers/__init__.py`: "they import the
14
+ kernel; the kernel never imports them"). The kernel CLI's `dos judge` points
15
+ here on an abstain; nothing in the kernel imports this module.
16
+
17
+ Contract (the honest part):
18
+ * **Deterministic-first.** This driver runs `picker_oracle` first and returns
19
+ its verdict verbatim whenever the oracle did NOT abstain. The LLM is consulted
20
+ ONLY for the `UNCLASSIFIED` residue — it never overrides a provable verdict.
21
+ * **Graceful when no provider.** The actual model call goes through one seam,
22
+ `_call_provider`. With no provider wired (no `DOS_LLM_JUDGE_CMD`, no SDK),
23
+ the seam returns an abstention and the driver reports "unadjudicated —
24
+ configure a provider," never a crash and never a hard dependency. The package
25
+ stays installable and the kernel stays pure.
26
+ * **A judge is advisory.** This driver emits a verdict; it does not mutate any
27
+ lease/registry/plan. Acting on its ruling is the operator's call (or a
28
+ separately-wired automation), exactly as the decision-queue actions are
29
+ emit-and-exit. The LLM never *believes itself into* a state change.
30
+
31
+ Usage:
32
+ python -m dos.drivers.llm_judge <run_ts> # adjudicate one run
33
+ python -m dos.drivers.llm_judge <run_ts> --json # machine-readable
34
+ DOS_LLM_JUDGE_CMD='claude -p --model claude-haiku-4-5' python -m dos.drivers.llm_judge <run_ts>
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import argparse
40
+ import dataclasses
41
+ import json
42
+ import os
43
+ import subprocess
44
+ from typing import Optional
45
+
46
+ # Imports the kernel — never the other way round (the driver rule).
47
+ from dos import config as _config
48
+ from dos import picker_oracle
49
+
50
+
51
+ # The env var naming the provider command. It must read a prompt on stdin and
52
+ # write the adjudication on stdout. `claude -p` is the canonical fit, but ANY
53
+ # command honoring that contract works — the driver is provider-agnostic at this
54
+ # seam, which is what keeps the kernel provider-invariant: the coupling lives in
55
+ # the operator's env, not in the code.
56
+ ENV_JUDGE_CMD = "DOS_LLM_JUDGE_CMD"
57
+
58
+
59
+ @dataclasses.dataclass(frozen=True)
60
+ class LlmVerdict:
61
+ """The driver's adjudication of one no-pick run.
62
+
63
+ `engine` is `"oracle"` when the deterministic judge ruled (the LLM was not
64
+ needed), `"llm"` when the model adjudicated the UNCLASSIFIED residue, or
65
+ `"unadjudicated"` when the oracle abstained AND no provider is wired.
66
+ `agrees_with_picker` mirrors the inverse of the oracle's `oracle_disagrees`
67
+ for the deterministic path; for the LLM path it is the model's own call.
68
+ """
69
+
70
+ run_ts: str
71
+ engine: str # "oracle" | "llm" | "unadjudicated"
72
+ cause: str # the no_pick_cause (oracle) or LLM-stated cause
73
+ agrees_with_picker: Optional[bool]
74
+ rationale: str
75
+ evidence: tuple[str, ...] = ()
76
+
77
+ def to_dict(self) -> dict:
78
+ return {
79
+ "run_ts": self.run_ts,
80
+ "engine": self.engine,
81
+ "cause": self.cause,
82
+ "agrees_with_picker": self.agrees_with_picker,
83
+ "rationale": self.rationale,
84
+ "evidence": list(self.evidence),
85
+ }
86
+
87
+
88
+ def _call_provider(prompt: str) -> str | None:
89
+ """The provider seam. Returns the model's raw stdout, or None if unavailable.
90
+
91
+ Honors `$DOS_LLM_JUDGE_CMD` (a shell command reading the prompt on stdin,
92
+ writing the verdict on stdout). Never raises — any failure (no command set,
93
+ command missing, timeout, non-zero exit) returns None so the caller degrades
94
+ to an "unadjudicated" verdict. This is the ONE place a provider is touched;
95
+ keeping it a single guarded seam is what lets the package ship with zero LLM
96
+ dependency while still allowing an operator to wire one in by env var.
97
+ """
98
+ cmd = os.environ.get(ENV_JUDGE_CMD)
99
+ if not cmd:
100
+ return None
101
+ try:
102
+ # shell=True so an operator can set a full `claude -p --model …` string.
103
+ p = subprocess.run(
104
+ cmd,
105
+ shell=True,
106
+ input=prompt.encode("utf-8"),
107
+ capture_output=True,
108
+ timeout=120,
109
+ )
110
+ except (OSError, subprocess.SubprocessError):
111
+ return None
112
+ if p.returncode != 0:
113
+ return None
114
+ out = (p.stdout or b"").decode("utf-8", errors="replace").strip()
115
+ return out or None
116
+
117
+
118
+ def _build_prompt(verdict: picker_oracle.PickerVerdict) -> str:
119
+ """The adjudication prompt for the UNCLASSIFIED residue.
120
+
121
+ Deliberately asks for a strict, skeptical ruling — the LLM stands in for the
122
+ deterministic cross-check the oracle could not perform, so it must DEFAULT to
123
+ distrusting the picker's self-report (the kernel's posture), not rubber-stamp it.
124
+ """
125
+ return (
126
+ "You are a strict adjudicator for a dispatch system. A picker reported a "
127
+ "NO-PICK (it found no dispatchable work) and gave a free-text reason, but "
128
+ "the deterministic oracle could not classify it (no known reason_class to "
129
+ "cross-check). Decide whether the picker's no-pick is BELIEVABLE given its "
130
+ "stated reason, or whether it looks like a picker bug (work likely existed). "
131
+ "Default to skepticism: if the reason is vague or unfalsifiable, lean "
132
+ "'disagree'. Answer in two lines:\n"
133
+ "VERDICT: <agree|disagree>\n"
134
+ "WHY: <one sentence>\n\n"
135
+ f"Run: {verdict.run_ts}\n"
136
+ f"Lane: {verdict.lane}\n"
137
+ f"Picker's stated reason: {verdict.picker_reason or '(none given)'}\n"
138
+ )
139
+
140
+
141
+ def _parse_llm_reply(reply: str) -> tuple[Optional[bool], str]:
142
+ """Parse the model's `VERDICT:`/`WHY:` reply into (agrees, rationale).
143
+
144
+ Tolerant: a reply that doesn't match the format yields (None, <raw reply>) so
145
+ an off-format model degrades to "ruled, but unparseable" rather than a crash.
146
+ """
147
+ agrees: Optional[bool] = None
148
+ why = reply.strip()
149
+ for line in reply.splitlines():
150
+ low = line.strip().lower()
151
+ if low.startswith("verdict:"):
152
+ val = low.split(":", 1)[1].strip()
153
+ if "agree" in val and "disagree" not in val:
154
+ agrees = True
155
+ elif "disagree" in val:
156
+ agrees = False
157
+ elif low.startswith("why:"):
158
+ why = line.split(":", 1)[1].strip()
159
+ return agrees, why
160
+
161
+
162
+ def adjudicate(run_ts: str, config: _config.SubstrateConfig | None = None) -> LlmVerdict:
163
+ """Adjudicate one no-pick run: deterministic-first, LLM for the residue.
164
+
165
+ Returns an `LlmVerdict`. Never raises on a provider problem — it degrades to
166
+ an `engine="unadjudicated"` verdict the caller can report honestly.
167
+ """
168
+ cfg = _config.ensure(config)
169
+ state = picker_oracle._load_yaml(cfg.paths.execution_state)
170
+ oracle_verdict = picker_oracle._classify_one(run_ts, state)
171
+ cause = (oracle_verdict.no_pick_cause.value
172
+ if oracle_verdict.no_pick_cause else "UNCLASSIFIED")
173
+
174
+ # Deterministic-first: if the oracle could classify it, the LLM is not needed.
175
+ if cause != "UNCLASSIFIED":
176
+ return LlmVerdict(
177
+ run_ts=run_ts,
178
+ engine="oracle",
179
+ cause=cause,
180
+ agrees_with_picker=not oracle_verdict.oracle_disagrees,
181
+ rationale=(
182
+ "deterministic oracle ruled — LLM not consulted "
183
+ f"(oracle_disagrees={oracle_verdict.oracle_disagrees})"
184
+ ),
185
+ evidence=oracle_verdict.evidence,
186
+ )
187
+
188
+ # UNCLASSIFIED residue — consult the LLM through the guarded seam.
189
+ reply = _call_provider(_build_prompt(oracle_verdict))
190
+ if reply is None:
191
+ return LlmVerdict(
192
+ run_ts=run_ts,
193
+ engine="unadjudicated",
194
+ cause="UNCLASSIFIED",
195
+ agrees_with_picker=None,
196
+ rationale=(
197
+ "oracle abstained (no reason_class to verify) and no LLM provider "
198
+ f"is wired — set ${ENV_JUDGE_CMD} to a `claude -p`-style command to "
199
+ "adjudicate the residue"
200
+ ),
201
+ evidence=oracle_verdict.evidence,
202
+ )
203
+ agrees, why = _parse_llm_reply(reply)
204
+ return LlmVerdict(
205
+ run_ts=run_ts,
206
+ engine="llm",
207
+ cause="UNCLASSIFIED",
208
+ agrees_with_picker=agrees,
209
+ rationale=why,
210
+ evidence=oracle_verdict.evidence + (f"llm: {reply[:200]}",),
211
+ )
212
+
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # The generic Judge-protocol occupant of the JUDGE rung (dos.judges, Axis 6).
216
+ # ---------------------------------------------------------------------------
217
+ #
218
+ # `adjudicate()` above is the original, picker-specific entry: it rules on a no-pick
219
+ # `run_ts`. `LlmJudge` is the *domain-neutral* face of the same machinery — it
220
+ # implements `dos.judges.Judge`, so it plugs into the trust ladder, the
221
+ # `dos judge-eval` instrument, and any consumer of the generic seam, ruling on an
222
+ # arbitrary `Claim` rather than only a no-pick row. It is the reference occupant of
223
+ # the JUDGE rung: a researcher's own judge implements the same `rule(claim, config)`
224
+ # shape, and `compose_deterministic_first` composes it under the same discipline.
225
+ #
226
+ # It reuses `_call_provider` verbatim (one provider seam, env-configured), so the
227
+ # kernel stays provider-invariant and the package ships with zero LLM dependency.
228
+ # The disciplines hold: advisory-only (it returns a `JudgeVerdict`, mutates nothing),
229
+ # and fail-to-abstain — with no provider wired, or on any provider error, `rule`
230
+ # ABSTAINS (never AGREE), so a missing model degrades to "ask a human," never to a
231
+ # rubber-stamp. (The `run_judge` wrapper would catch a raise too; `LlmJudge.rule`
232
+ # additionally abstains *gracefully* on the expected no-provider path so the verdict
233
+ # carries a useful "wire $DOS_LLM_JUDGE_CMD" message rather than a stack trace.)
234
+
235
+
236
+ def _build_claim_prompt(claim) -> str:
237
+ """A skeptical adjudication prompt for a generic `Claim`.
238
+
239
+ Same posture as `_build_prompt` (the picker version): the model stands in for a
240
+ cross-check the deterministic oracle could not perform, so it must DEFAULT to
241
+ distrust — the kernel's stance. It is shown the claim, the agent's narration, and
242
+ the forgery-resistant evidence, and asked whether the narration is supported.
243
+ """
244
+ ev = "\n".join(f" - {e}" for e in (claim.evidence or ())) or " (none provided)"
245
+ return (
246
+ "You are a strict adjudicator for an autonomous-agent substrate. An agent "
247
+ "asserted a claim and gave a justification (its NARRATION), and the system "
248
+ "gathered some forgery-resistant EVIDENCE (git lines, file state). The "
249
+ "deterministic oracle could not settle this claim, so it falls to you. Decide "
250
+ "whether the claim is BELIEVABLE GIVEN THE EVIDENCE — not given the narration, "
251
+ "which you should distrust. Default to skepticism: if the evidence does not "
252
+ "support the claim, answer 'disagree'; if you genuinely cannot tell from the "
253
+ "evidence, answer 'abstain' (do NOT guess 'agree'). Answer in two lines:\n"
254
+ "VERDICT: <agree|disagree|abstain>\n"
255
+ "WHY: <one sentence>\n\n"
256
+ f"CLAIM: {claim.claim_text}\n"
257
+ f"AGENT NARRATION: {claim.stated_reason or '(none given)'}\n"
258
+ f"EVIDENCE:\n{ev}\n"
259
+ )
260
+
261
+
262
+ def _parse_three_valued(reply: str):
263
+ """Parse a `VERDICT: agree|disagree|abstain` / `WHY:` reply into a JudgeVerdict.
264
+
265
+ Tolerant: an off-format reply (no recognizable VERDICT line) → ABSTAIN, never a
266
+ guessed AGREE — the conservative direction, consistent with the whole seam. A
267
+ 'disagree' substring is checked before 'agree' because "disagree" contains
268
+ "agree".
269
+ """
270
+ from dos.judges import JudgeVerdict
271
+ why = reply.strip()
272
+ stance = None
273
+ for line in reply.splitlines():
274
+ low = line.strip().lower()
275
+ if low.startswith("verdict:"):
276
+ val = low.split(":", 1)[1].strip()
277
+ if "abstain" in val:
278
+ stance = "abstain"
279
+ elif "disagree" in val:
280
+ stance = "disagree"
281
+ elif "agree" in val:
282
+ stance = "agree"
283
+ elif low.startswith("why:"):
284
+ why = line.split(":", 1)[1].strip()
285
+ if stance == "agree":
286
+ return JudgeVerdict.agree(why, evidence=(f"llm: {reply[:200]}",))
287
+ if stance == "disagree":
288
+ return JudgeVerdict.disagree(why, evidence=(f"llm: {reply[:200]}",))
289
+ return JudgeVerdict.abstain(
290
+ why if stance == "abstain" else
291
+ "LLM reply did not carry a parseable VERDICT — abstaining (never guess agree)",
292
+ evidence=(f"llm: {reply[:200]}",),
293
+ )
294
+
295
+
296
+ class LlmJudge:
297
+ """The reference LLM occupant of the JUDGE rung — a `dos.judges.Judge`.
298
+
299
+ Rules on a generic `Claim` via the `$DOS_LLM_JUDGE_CMD` provider seam, skeptical
300
+ by default, advisory-only, fail-to-abstain. With no provider wired it abstains
301
+ with a "configure a provider" message — so it is always safe to register and
302
+ eval, even on a box with no model. Register it under the `dos.judges` entry-point
303
+ group (or pass an instance directly to `judge_eval`):
304
+
305
+ [project.entry-points."dos.judges"]
306
+ llm = "dos.drivers.llm_judge:LlmJudge"
307
+ """
308
+
309
+ name = "llm"
310
+
311
+ def rule(self, claim, config):
312
+ from dos.judges import JudgeVerdict
313
+ reply = _call_provider(_build_claim_prompt(claim))
314
+ if reply is None:
315
+ return JudgeVerdict.abstain(
316
+ f"no LLM provider wired (set ${ENV_JUDGE_CMD} to a `claude -p`-style "
317
+ f"command) — abstaining, which routes this claim to a human."
318
+ )
319
+ return _parse_three_valued(reply)
320
+
321
+
322
+ def main(argv: list[str] | None = None) -> int:
323
+ ap = argparse.ArgumentParser(
324
+ prog="dos.drivers.llm_judge",
325
+ description=__doc__.splitlines()[0],
326
+ )
327
+ ap.add_argument("run_ts", help="the chained-run dir to adjudicate (e.g. 20260531T010000Z)")
328
+ ap.add_argument("--workspace", default=None,
329
+ help="workspace root (default: $DISPATCH_WORKSPACE or cwd)")
330
+ ap.add_argument("--job", action="store_true", help="use the job-repo taxonomy")
331
+ ap.add_argument("--json", action="store_true", help="machine-readable verdict")
332
+ args = ap.parse_args(argv)
333
+
334
+ if args.job:
335
+ _config.set_active(_config.job_config(args.workspace))
336
+ else:
337
+ _config.set_active(_config.default_config(args.workspace))
338
+
339
+ verdict = adjudicate(args.run_ts)
340
+ if args.json:
341
+ print(json.dumps(verdict.to_dict(), indent=2, default=str))
342
+ else:
343
+ print(f"RUN {verdict.run_ts}")
344
+ print(f"ENGINE {verdict.engine}")
345
+ print(f"CAUSE {verdict.cause}")
346
+ agree_s = (
347
+ "-" if verdict.agrees_with_picker is None
348
+ else ("agrees with picker" if verdict.agrees_with_picker
349
+ else "DISAGREES — likely picker bug")
350
+ )
351
+ print(f"VERDICT {agree_s}")
352
+ print(f"WHY {verdict.rationale}")
353
+ if verdict.evidence:
354
+ print("EVIDENCE " + "\n ".join(verdict.evidence))
355
+ # Exit code mirrors the kernel judge: 1 on a disagreement (a picker bug), else 0.
356
+ return 1 if verdict.agrees_with_picker is False else 0
357
+
358
+
359
+ if __name__ == "__main__":
360
+ raise SystemExit(main())