Ingested from ActiveGraph (Nakajima, "The Log is the Agent",
arxiv 2605.21997, May 2026 — v1.0 May 2026, MIT/Apache). The
focused web search across 5 themes (event-sourced agent runtimes,
deterministic replay + content-addressed caching, multimodal data
primitives, context compression, durable workflows) confirmed
ActiveGraph is the uncontested 2026 SOTA at the event-sourced-
substrate layer. The other four themes have alternatives but
none with the same level of articulation; this wave ingests the
typed primitive JouleClaw's receipts + cascade had been pointing
at but didn't name.
1. jouleclaw-event-log (new crate) — typed substrate.
- EventKind { Goal | Object | Relation { from, to, edge_kind } |
LlmReq { request_hash, prompt_summary } | LlmResp {
request_hash, response_summary, tokens } | ToolReq { tool_id,
args_hash } | ToolResp { tool_id, args_hash, response_summary }
| Patch | Receipt }. The Relation variant uses `edge_kind`
(not `kind`) to avoid the silent serde-tag collision bug.
- Event { prev_hash, ts_ns, kind, event_hash } sealed at
append. EventLog::append computes prev_hash from the
previous event's hash (genesis = BLAKE3 of zero bytes for
the first event).
- EventLog::verify_chain catches tampering at the first
break with the exact index.
- replay() — deterministic fold producing a ProjectedGraph
(goal + typed objects + typed relations + receipts), each
element annotated with the event_hash that produced it.
- replay_strict() — compares a fresh fold to an expected
projection byte-for-byte under canonical JSON; returns
ReplayError::Divergence with the diverging content hashes.
A green strict replay is a proof the run is reproducible
(the published ActiveGraph claim).
- ContentAddressedCache — populates from the log's LlmResp +
ToolResp events, indexed by the documented 2026 SOTA
convention: LLM key = BLAKE3 over (system, user_messages,
model_id, tool_defs, output_schema); tool key = (tool_id,
args_hash). On replay, a behavior firing with a recorded
hash hits cache; no new model call.
- lineage_of(log, object_id) — given an artifact, return every
event in its causal chain (goal that motivated it, LlmReq/
LlmResp pair that produced it, ToolReq/ToolResp if any,
Object event itself, Relations referencing it after creation).
- 13 tests.
2. jouleclaw-jcr1::event_log — JCR-1 event-log/1 schema.
- EventLogReceipt { jc_receipt, id, closed_at, steward, log_id,
head_event_hash, chain_depth, counts: EventLogCounts,
total_tokens }.
- 2 conformance vectors with embedded receipt_hash.
- 3 jcr1 event_log tests.
3. Cross-language conformance — Rust + Python + Go verifiers all
shipped, byte-equal across the three implementations. The
three-impl pattern (matching JCR-1 v1) is preserved.
4. Demo binary — examples/wave54_event_log_replay.rs in
jouleclaw-stack. Builds a 9-event agent run (goal → LLM
exchange → tool exchange → object → relation → inline receipt),
demonstrates: (1) replay determinism; (2) strict-mode rejection
of a tampered projection with divergence pinned to a content
hash; (3) cache-served replay of the recorded LLM request (no
new model call); (4) lineage trace recovering 7 events in the
causal chain back to the goal; (5) event-log/1 JCR-1 receipt
sealed over the log snapshot.
The triangle (conservation + cone + oracle + elicitor) stays the
type system; wave-54 adds the event-sourced substrate that any
JouleClaw agent runtime can be built ON. The log IS the agent.
Workspace check clean.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
76 lines
2.1 KiB
Python
76 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
verify_event_log.py — independent Python verifier for the
|
|
JCR-1 `schema_id` schema.
|
|
|
|
Reads `event-log-vectors.json`, recanonicalises each receipt under
|
|
sorted-key JSON (UTF-8, no whitespace), BLAKE3-hashes the bytes,
|
|
and asserts the hex matches the vector's embedded `receipt_hash`.
|
|
|
|
Deps: blake3 (`pip install blake3`).
|
|
|
|
Usage:
|
|
python3 verify_event_log.py [event-log-vectors.json]
|
|
"""
|
|
import sys, json
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import blake3
|
|
except ImportError:
|
|
sys.stderr.write(
|
|
"this verifier requires the blake3 module — `pip install blake3`\n"
|
|
)
|
|
sys.exit(2)
|
|
|
|
|
|
def canonicalise(value):
|
|
if isinstance(value, dict):
|
|
return {k: canonicalise(value[k]) for k in sorted(value)}
|
|
if isinstance(value, list):
|
|
return [canonicalise(v) for v in value]
|
|
return value
|
|
|
|
|
|
def canonical_bytes(value) -> bytes:
|
|
return json.dumps(
|
|
canonicalise(value),
|
|
separators=(",", ":"),
|
|
ensure_ascii=False,
|
|
).encode("utf-8")
|
|
|
|
|
|
def main():
|
|
path = (
|
|
Path(sys.argv[1])
|
|
if len(sys.argv) > 1
|
|
else Path(__file__).parent / "event-log-vectors.json"
|
|
)
|
|
suite = json.loads(path.read_text())
|
|
expected_spec = "JCR-1 event-log v1"
|
|
if suite.get("spec") != expected_spec:
|
|
sys.stderr.write(f"spec mismatch: got {suite.get('spec')!r}, want {expected_spec!r}\n")
|
|
sys.exit(1)
|
|
failed = 0
|
|
for vec in suite["vectors"]:
|
|
n = vec["n"]
|
|
desc = vec["description"]
|
|
expected_hash = vec["receipt_hash"]
|
|
payload = vec["receipt"]
|
|
canon = canonical_bytes(payload)
|
|
computed = blake3.blake3(canon).hexdigest()
|
|
if computed == expected_hash:
|
|
print(f" ✓ vector {n}: {desc}")
|
|
else:
|
|
print(f" ✗ vector {n}: {desc}")
|
|
print(f" expected: {expected_hash}")
|
|
print(f" computed: {computed}")
|
|
failed += 1
|
|
if failed:
|
|
sys.stderr.write(f"\n{failed} vector(s) failed verification\n")
|
|
sys.exit(1)
|
|
print(f"\nverify_event_log.py — all {len(suite['vectors'])} vectors PASS")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|