Falln87 commited on
Commit
4f70a26
·
verified ·
1 Parent(s): 36958cf

CLERK core: slot schema, edit-op schema, deterministic reducer, serializers

Browse files
Files changed (1) hide show
  1. clerk/common.py +225 -0
clerk/common.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CLERK: Consolidated Ledger with Eviction and Rewrite Keys.
2
+
3
+ Core memory machinery shared by the data generator, the training harness and
4
+ the evaluators:
5
+
6
+ * ``Slot`` -- one memory slot (a typed fact plus lifecycle metadata)
7
+ * edit-op schema -- ADD / UPDATE / TOMBSTONE / EVICT / NOOP
8
+ * ``apply_ops`` -- the DETERMINISTIC reducer that applies a model-emitted
9
+ edit program to a ledger
10
+ * serializers -- compact JSON forms of ledgers and op programs
11
+
12
+ The reducer is the single source of truth: the generator emits gold op
13
+ programs, and every downstream consumer applies them through ``apply_ops``.
14
+ This guarantees that the training supervision, the simulated model rollouts
15
+ and the evaluation all execute the exact same state transition.
16
+
17
+ Author: Justin Wolcott (fallnai-research.org)
18
+ License: Apache-2.0
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ from dataclasses import dataclass, field
25
+ from typing import Any, Dict, List, Optional, Tuple
26
+
27
+ LEDGER_BUDGET_DEFAULT = 24
28
+ VALID_OPS = ("ADD", "UPDATE", "TOMBSTONE", "EVICT", "NOOP")
29
+
30
+
31
+ @dataclass
32
+ class Slot:
33
+ """A single ledger slot."""
34
+
35
+ id: int
36
+ subject: str = ""
37
+ predicate: str = ""
38
+ object: str = ""
39
+ t_last: int = -1 # session index when this slot was last written
40
+ n_uses: int = 0 # number of times the fact has been queried (internal)
41
+ valid: bool = True # False => tombstoned (fact no longer true)
42
+ occupied: bool = False # False => free (empty, or freed by EVICT)
43
+
44
+
45
+ def empty_ledger(budget: int = LEDGER_BUDGET_DEFAULT) -> List[Slot]:
46
+ return [Slot(id=i) for i in range(budget)]
47
+
48
+
49
+ def serialize_ledger(ledger: List[Slot]) -> str:
50
+ """Compact JSON view of a ledger, as seen by the model.
51
+
52
+ Tombstoned slots stay visible (with v=0) until they are evicted, so
53
+ temporal questions about superseded values remain answerable.
54
+ """
55
+ rows = []
56
+ for s in ledger:
57
+ if s.occupied:
58
+ rows.append(
59
+ {"i": s.id, "s": s.subject, "p": s.predicate,
60
+ "o": s.object, "t": s.t_last, "v": 1 if s.valid else 0}
61
+ )
62
+ return json.dumps(rows, separators=(",", ":"), ensure_ascii=False)
63
+
64
+
65
+ def parse_ledger(text: str, budget: int = LEDGER_BUDGET_DEFAULT) -> List[Slot]:
66
+ """Rebuild a ledger from its serialized form."""
67
+ ledger = empty_ledger(budget)
68
+ for row in json.loads(text):
69
+ s = ledger[row["i"]]
70
+ s.occupied = True
71
+ s.subject = row["s"]
72
+ s.predicate = row["p"]
73
+ s.object = row["o"]
74
+ s.t_last = row["t"]
75
+ s.valid = bool(row["v"])
76
+ return ledger
77
+
78
+
79
+ def parse_ops(text: str) -> Tuple[List[Dict[str, Any]], str]:
80
+ """Parse a model-emitted edit program.
81
+
82
+ Returns (ops, error). ``error`` is "" on success, otherwise a short
83
+ description of the first problem found. Malformed programs are surfaced
84
+ to the evaluation harness instead of being silently dropped.
85
+ """
86
+ text = text.strip()
87
+ if text.startswith("```"):
88
+ # strip a markdown fence if the model adds one
89
+ text = text.strip("`")
90
+ if text.startswith("json"):
91
+ text = text[4:]
92
+ try:
93
+ payload = json.loads(text)
94
+ except json.JSONDecodeError as e:
95
+ return [], f"json_decode_error: {e}"
96
+ if not isinstance(payload, dict) or "ops" not in payload:
97
+ return [], "missing_top_level_ops_key"
98
+ ops = payload["ops"]
99
+ if not isinstance(ops, list):
100
+ return [], "ops_not_a_list"
101
+ clean: List[Dict[str, Any]] = []
102
+ for op in ops:
103
+ if not isinstance(op, dict) or "op" not in op:
104
+ return clean, "op_missing_op_key"
105
+ if op["op"] not in VALID_OPS:
106
+ return clean, f"unknown_op:{op['op']}"
107
+ clean.append(op)
108
+ return clean, ""
109
+
110
+
111
+ def apply_ops(
112
+ ledger: List[Slot],
113
+ ops: List[Dict[str, Any]],
114
+ session_idx: int,
115
+ ) -> Tuple[List[Slot], Dict[str, int]]:
116
+ """Apply an edit program to a ledger. Deterministic; mutates a copy.
117
+
118
+ Rules:
119
+ ADD -- write into the lowest free slot; if the ledger has no free
120
+ slot the op is DROPPED and counted (a trained policy must
121
+ EVICT first; the gold generator always does).
122
+ UPDATE -- overwrite s/p/o of slot ``i`` and bump t_last. The slot
123
+ keeps its identity, so supersession is explicit.
124
+ TOMBSTONE -- mark slot ``i`` invalid (v=0); content stays until EVICT.
125
+ EVICT -- free slot ``i`` entirely.
126
+ NOOP -- nothing.
127
+ """
128
+ new_ledger = [Slot(**vars(s)) for s in ledger]
129
+ stats = {"ADD": 0, "UPDATE": 0, "TOMBSTONE": 0, "EVICT": 0,
130
+ "NOOP": 0, "dropped": 0}
131
+ for op in ops:
132
+ kind = op["op"]
133
+ stats[kind] += 1
134
+ if kind == "NOOP":
135
+ continue
136
+ if kind == "ADD":
137
+ target = next((s for s in new_ledger if not s.occupied), None)
138
+ if target is None:
139
+ stats["dropped"] += 1
140
+ continue
141
+ target.occupied = True
142
+ target.valid = True
143
+ target.t_last = session_idx
144
+ target.n_uses = 0
145
+ target.subject = str(op.get("s", ""))
146
+ target.predicate = str(op.get("p", ""))
147
+ target.object = str(op.get("o", ""))
148
+ elif kind == "UPDATE":
149
+ i = int(op.get("i", -1))
150
+ if not (0 <= i < len(new_ledger)) or not new_ledger[i].occupied:
151
+ stats["dropped"] += 1
152
+ continue
153
+ s = new_ledger[i]
154
+ if op.get("s") is not None:
155
+ s.subject = str(op["s"])
156
+ if op.get("p") is not None:
157
+ s.predicate = str(op["p"])
158
+ s.object = str(op.get("o", s.object))
159
+ s.t_last = session_idx
160
+ s.valid = True
161
+ elif kind == "TOMBSTONE":
162
+ i = int(op.get("i", -1))
163
+ if not (0 <= i < len(new_ledger)) or not new_ledger[i].occupied:
164
+ stats["dropped"] += 1
165
+ continue
166
+ new_ledger[i].valid = False
167
+ elif kind == "EVICT":
168
+ i = int(op.get("i", -1))
169
+ if not (0 <= i < len(new_ledger)) or not new_ledger[i].occupied:
170
+ stats["dropped"] += 1
171
+ continue
172
+ new_ledger[i] = Slot(id=i)
173
+ return new_ledger, stats
174
+
175
+
176
+ def salience(slot: Slot, now: int) -> float:
177
+ """Gold salience score used by the generator's eviction oracle.
178
+
179
+ Frequently-queried, currently-valid, recently-written facts are kept;
180
+ old, never-queried, tombstoned facts are evicted first. This is a
181
+ documented oracle, not a model component: the generator needs a
182
+ deterministic ground truth for eviction supervision.
183
+ """
184
+ age = max(0, now - slot.t_last)
185
+ return 4.0 * slot.n_uses + (3.0 if slot.valid else 0.0) + max(0.0, 4.0 - age)
186
+
187
+
188
+ def enforce_budget(
189
+ ledger: List[Slot], ops: List[Dict[str, Any]], now: int
190
+ ) -> Tuple[List[Slot], List[Dict[str, Any]]]:
191
+ """After applying gold ADD/UPDATE ops, evict lowest-salience occupied
192
+ slots until the active-slot count fits the budget. Appends the EVICT ops
193
+ to the gold program so the policy learns budget-aware behavior."""
194
+ ledger, _ = apply_ops(ledger, ops, now)
195
+ ops = list(ops)
196
+ occupied = [s for s in ledger if s.occupied]
197
+ overflow = len(occupied) - len(ledger)
198
+ for _ in range(max(0, overflow)):
199
+ victim = min(
200
+ (s for s in ledger if s.occupied), key=lambda s: salience(s, now)
201
+ )
202
+ ops.append({"op": "EVICT", "i": victim.id})
203
+ ledger[victim.id] = Slot(id=victim.id)
204
+ return ledger, ops
205
+
206
+
207
+ def facts_of(ledger: List[Slot], valid_only: bool = True) -> Dict[Tuple[str, str], str]:
208
+ """Current fact map {(subject, predicate): object}."""
209
+ out: Dict[Tuple[str, str], str] = {}
210
+ for s in ledger:
211
+ if s.occupied and (s.valid or not valid_only):
212
+ key = (s.subject, s.predicate)
213
+ if s.valid:
214
+ out[key] = s.object
215
+ return out
216
+
217
+
218
+ def ledger_stats(ledger: List[Slot]) -> Dict[str, int]:
219
+ occ = [s for s in ledger if s.occupied]
220
+ return {
221
+ "occupied": len(occ),
222
+ "valid": sum(1 for s in occ if s.valid),
223
+ "tombstoned": sum(1 for s in occ if not s.valid),
224
+ "budget": len(ledger),
225
+ }