-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlearn_null.py
More file actions
162 lines (137 loc) · 7.18 KB
/
Copy pathlearn_null.py
File metadata and controls
162 lines (137 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""PHASE 2 — FIRST VERTICAL SLICE: learn an identified null.
The runtime loop (Phase 1) IDs nulls: abstains land in field_log.jsonl. This closes the
circle for the BEHAVIOR-abstain class whose cause is EVIDENCE ROUTING — the knowledge exists
in the corpus but under a different owner than the claim named (e.g. the SaveInventory
debounce doc lives on URiftPersistenceInterface, claim said URiftInventoryComponent).
The loop: DETECT (abstained BEHAVIOR facts in the field log) -> ACQUIRE (relaxed-owner search:
every doc/implementation for that member under ANY owner) -> VERIFY (the judge, with the found
evidence, trinary) -> WRITE BACK (learned_facts.jsonl: statement + verdict + evidence source +
provenance). JudgeChecker loads learned facts at startup and injects them as evidence, so what
abstained before passes after — the system has LEARNED it, and the learned store is cartridge
content that survives model swaps.
UNSURE stays null (parked, honest) — learning is not forcing a verdict.
Run: .venv/Scripts/python.exe learn_null.py # process all abstained BEHAVIOR facts
"""
from __future__ import annotations
import json
import re
from datetime import datetime, timezone
from pathlib import Path
from checker import JudgeChecker, _ollama_generate
HERE = Path(__file__).resolve().parent
FIELD_LOG = HERE / "field_log.jsonl"
LEARNED = HERE / "learned_facts.jsonl"
FACT_RE = re.compile(r"BEHAVIOR:\s*([\w?]+)::(\w+)\s*=\s*(.+)$")
def abstained_behaviors() -> list[tuple[str, str, str, str]]:
"""Unique abstained BEHAVIOR facts from the field log: (fact, cls, member, value)."""
out, seen = [], set()
if not FIELD_LOG.exists():
return out
for line in FIELD_LOG.read_text(encoding="utf-8").splitlines():
for r in json.loads(line)["results"]:
m = FACT_RE.match(r["fact"])
if m and r["verdict"] == "abstain" and r["fact"] not in seen:
seen.add(r["fact"])
out.append((r["fact"], *m.groups()))
return out
def already_learned() -> set:
if not LEARNED.exists():
return set()
return {(e["cls"], e["member"], e["statement"])
for e in map(json.loads, LEARNED.read_text(encoding="utf-8").splitlines())}
def acquire(jc: JudgeChecker, member: str) -> list[tuple[str, str]]:
"""Relaxed-owner search: every docstring and implementation for this member, any owner."""
ev = [(f"{o}::{m}", f"DOC for {o}::{m}: {doc}")
for (o, m), doc in jc.docs.items() if m == member]
ev += [(f"{o}::{m} (impl)", f"IMPLEMENTATION of {o}::{m}:\n{body}")
for (o, m), body in jc.defs.items() if m == member]
return ev
def verify(jc: JudgeChecker, cls: str, member: str, value: str, ev: list) -> str:
evidence = "\n---\n".join(t for _, t in ev)
prompt = (
"You are checking a claim against real C++ from the RiftSuite codebase. The IMPLEMENTATION "
"shows what the method actually does.\n"
f"EVIDENCE:\n{evidence}\n\n"
f'CLAIM: "{cls}::{member}: {value}"\n\n'
"Answer with exactly one word:\n"
"- CONTRADICTED if the evidence shows the claim is false.\n"
"- CONSISTENT if the evidence confirms the claim.\n"
"- UNSURE only if the evidence genuinely does not show enough to decide."
)
u = _ollama_generate(jc.model, prompt).upper()
if "CONTRADICT" in u:
return "refuted"
if "CONSISTENT" in u:
return "verified"
return "unsure"
def learn_all(jc: JudgeChecker) -> dict:
"""Process every abstained BEHAVIOR fact in the field log. Returns a summary dict.
Callable from the CLI below or the MCP server (learn_from_log tool)."""
known = already_learned()
summary = {"learned": [], "parked": [], "skipped": 0}
for fact, cls, member, value in abstained_behaviors():
if (cls, member, value) in known:
summary["skipped"] += 1
continue
# ownership/location claims are inherently null (design decisions) — learn_null's direct
# qwen call bypasses JudgeChecker's guard, so guard here too or a doc'd design INTENT
# could get "verified" into the learned store as a false constraint
if JudgeChecker.OWNERSHIP_RE.search(value):
summary["parked"].append({"fact": fact, "why": "inherently null — ownership/location is a design decision"})
continue
ev = acquire(jc, member)
if not ev:
summary["parked"].append({"fact": fact, "why": "no evidence anywhere in corpus"})
continue
verdict = verify(jc, cls, member, value, ev)
if verdict == "unsure":
summary["parked"].append({"fact": fact, "why": f"judge unsure with {len(ev)} evidence item(s)"})
continue
entry = {"ts": datetime.now(timezone.utc).isoformat(), "cls": cls, "member": member,
"statement": value, "verdict": verdict,
"evidence_source": "; ".join(src for src, _ in ev)}
with LEARNED.open("a", encoding="utf-8") as f:
f.write(json.dumps(entry) + "\n")
summary["learned"].append({"fact": fact, "verdict": verdict})
return summary
def reverify_all(jc: JudgeChecker) -> dict:
"""Staleness defense: re-run every ACTIVE learned fact against the CURRENT corpus.
A fact that no longer verifies is RETIRED (kept in the file with retirement metadata —
a retired fact is itself knowledge), never silently served stale. UNSURE also retires:
evidence that stopped being conclusive stops being served."""
if not LEARNED.exists():
return {"reverified": 0, "retired": []}
entries = [json.loads(l) for l in LEARNED.read_text(encoding="utf-8").splitlines()]
summary = {"reverified": 0, "retired": []}
for e in entries:
if e.get("retired"):
continue
ev = acquire(jc, e["member"])
verdict = verify(jc, e["cls"], e["member"], e["statement"], ev) if ev else "unsure"
summary["reverified"] += 1
if verdict != e["verdict"]:
e["retired"] = {"ts": datetime.now(timezone.utc).isoformat(),
"was": e["verdict"], "now": verdict,
"why": "no evidence in current corpus" if not ev else "verdict changed on re-verification"}
summary["retired"].append({"fact": f"{e['cls']}::{e['member']}: {e['statement'][:60]}",
"was": e["verdict"], "now": verdict})
LEARNED.write_text("\n".join(json.dumps(e) for e in entries) + "\n", encoding="utf-8")
return summary
def main() -> int:
import sys
jc = JudgeChecker()
if "--reverify" in sys.argv:
s = reverify_all(jc)
print(f"re-verified {s['reverified']} active learned fact(s), retired {len(s['retired'])}")
for r in s["retired"]:
print(f" RETIRED ({r['was']} -> {r['now']}): {r['fact']}")
return 0
s = learn_all(jc)
for item in s["learned"]:
print(f" LEARNED ({item['verdict']}): {item['fact'][:80]}")
for item in s["parked"]:
print(f" PARK ({item['why']}): {item['fact'][:80]}")
print(f"\n{len(s['learned'])} learned | {len(s['parked'])} parked | {s['skipped']} already known")
return 0
if __name__ == "__main__":
raise SystemExit(main())