-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmemory.py
More file actions
132 lines (110 loc) · 6.16 KB
/
Copy pathmemory.py
File metadata and controls
132 lines (110 loc) · 6.16 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
"""Self-evolving memory & skills with safety guardrails (plan §5.4, §8).
When a repair succeeds, the lesson is worth keeping two ways: a **Reflexion lesson** (verbal — "an
invoice needs an existing customer; auto-create from party_id") and a **Voyager-style skill** (a
reusable macro). The known foot-gun (plan §8) is self-editing memory without versioning/audit —
catastrophic forgetting and poisoning. So this store is **append-only and content-addressed**: a
revision SUPERSEDES a prior entry by id (history is never destroyed, every state is reconstructable),
and feeding a lesson back into the manifest is a **proposal** the caller confirms — never a silent
write.
"""
from __future__ import annotations
import hashlib
import json
from collections.abc import Iterable
from pathlib import Path
from typing import Any
def compute_spec_hash(parts: Iterable[str]) -> str:
"""Deterministic content hash of the spec parts (schemas + RFC text), order-independent.
Sorting makes the anchor reproducible regardless of file-read order, so the same spec always
yields the same hash — and any byte change yields a different one (the drift signal)."""
digest = hashlib.sha256()
for part in sorted(parts):
digest.update(part.encode("utf-8"))
digest.update(b"\x00")
return digest.hexdigest()
class MemoryStore:
"""An append-only JSONL ledger of lessons and skills. Nothing is ever edited in place."""
def __init__(self, path: str | Path) -> None:
self.path = Path(path)
def _entries(self) -> list[dict[str, Any]]:
if not self.path.exists():
return []
return [json.loads(line) for line in self.path.read_text(encoding="utf-8").splitlines() if line.strip()]
def _append(self, kind: str, payload: dict[str, Any], supersedes: str | None) -> dict[str, Any]:
entries = self._entries()
canonical = json.dumps({"kind": kind, "payload": payload}, sort_keys=True, ensure_ascii=False)
# content-addressed id; seq disambiguates identical payloads recorded twice.
digest = hashlib.sha256(f"{len(entries)}:{canonical}".encode("utf-8")).hexdigest()[:16]
entry = {"seq": len(entries), "id": digest, "kind": kind, "payload": payload, "supersedes": supersedes}
self.path.parent.mkdir(parents=True, exist_ok=True)
with self.path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(entry, ensure_ascii=False) + "\n")
return entry
def add_lesson(self, text: str, *, verb: str | None = None, tags: list[str] | None = None) -> dict[str, Any]:
"""Record a Reflexion lesson. Returns the stored entry (with its version id)."""
return self._append("lesson", {"text": text, "verb": verb, "tags": tags or []}, supersedes=None)
def add_skill(self, name: str, steps: list[dict[str, Any]], *, supersedes: str | None = None) -> dict[str, Any]:
"""Add (or, via `supersedes`, revise) a Voyager-style reusable skill macro."""
return self._append("skill", {"name": name, "steps": steps}, supersedes=supersedes)
def record_reversal(
self,
*,
verb: str,
tier: str,
outcome: str,
compensation_token: str | None = None,
approver: str | None = None,
) -> dict[str, Any]:
"""Append an immutable reversal record (ROLLBACK). Every compensation is remembered:
which verb, its tier, who approved it, and how it resolved — never edited, never lost."""
return self._append(
"reversal",
{
"verb": verb,
"tier": tier,
"outcome": outcome,
"compensation_token": compensation_token,
"approver": approver,
},
supersedes=None,
)
def anchor_ratification(self, *, version: str, label: str, spec_hash: str) -> dict[str, Any]:
"""Write the ratification anchor: a content-hash of the frozen spec, bound to a version/label.
Because the ledger is append-only and content-addressed, this block can never be silently
altered — any later change to the spec changes its hash and appears as a NEW anchor, never an
in-place edit. That is what makes the lock permanent (plan Part 6, "lock it")."""
return self._append(
"anchor", {"version": version, "label": label, "spec_hash": spec_hash}, supersedes=None
)
def current_anchor(self) -> dict[str, Any] | None:
"""The most recently written ratification anchor, or None if the spec is unratified."""
anchors = [e for e in self._entries() if e["kind"] == "anchor"]
return anchors[-1] if anchors else None
def history(self, kind: str | None = None) -> list[dict[str, Any]]:
"""Every entry ever written (audit trail), optionally filtered by kind."""
return [e for e in self._entries() if kind is None or e["kind"] == kind]
def active(self, kind: str | None = None) -> list[dict[str, Any]]:
"""Current view: entries not superseded by a later revision. History is preserved on disk."""
entries = self._entries()
superseded = {e["supersedes"] for e in entries if e.get("supersedes")}
return [e for e in entries if e["id"] not in superseded and (kind is None or e["kind"] == kind)]
def propose_manifest_patch(lesson: dict[str, Any]) -> dict[str, Any] | None:
"""Turn a confirmed lesson into a manifest PATCH proposal (plan §5.4 — close the loop).
Returns a manifest fragment to be reviewed/merged, or None if the lesson has no structural
content. GUARDRAIL: this only *proposes* — it never writes a manifest. The caller (a human or a
tier-scoped policy) confirms before `manifest merge` applies it.
"""
payload = lesson.get("payload", lesson)
verb = payload.get("verb")
learned = payload.get("learned_requirement") # {"field":..., "kind":...}
if not verb or not isinstance(learned, dict) or not learned.get("field"):
return None
return {
"verbs": {
verb: {
"hidden_requirements": [
{"field": learned["field"], "kind": learned.get("kind", "required_scalar")}
]
}
}
}