Spaces:
Running
Running
chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)
Browse filesAutomated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): Dockerfile, a11oy_react_core.py, serve.py
Deleted (gone from the repo + Dockerfile COPY set): (none)
Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.
- Dockerfile +9 -0
- a11oy_react_core.py +805 -0
- serve.py +40 -0
Dockerfile
CHANGED
|
@@ -317,6 +317,15 @@ COPY web/living-anatomy.html ./web/living-anatomy.html
|
|
| 317 |
# _ptg_serve. Without these COPYs the guarded import falls back and /nemo 404s.
|
| 318 |
COPY a11oy_nemo_core.py ./
|
| 319 |
COPY web/nemo.html ./web/nemo.html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
# ADDITIVE (Cross-Harness Receipt Bridge — Hermes + OpenClaw; 2026-06-01, Yachay /
|
| 322 |
# Perplexity Computer Agent; closeout PR superseding #198 runtime files). serve.py
|
|
|
|
| 317 |
# _ptg_serve. Without these COPYs the guarded import falls back and /nemo 404s.
|
| 318 |
COPY a11oy_nemo_core.py ./
|
| 319 |
COPY web/nemo.html ./web/nemo.html
|
| 320 |
+
# ADDITIVE (Lane A AGENTIC CORE, Dev A, 2026-06-14; QA9 restore 2026-06): the
|
| 321 |
+
# resumable ReAct agent-loop core module. Per-file COPY (this Dockerfile uses no
|
| 322 |
+
# COPY . .). a11oy_react_core.py is imported by serve.py (try/except guarded) and
|
| 323 |
+
# serves /api/a11oy/v1/agent/react/{run,resume,trace,checkpoints} (+ free
|
| 324 |
+
# top-level /api/a11oy/v1/agent/{resume,trace/{id},checkpoints}). Without this COPY
|
| 325 |
+
# the guarded import falls back to a stub in the image and the react endpoints
|
| 326 |
+
# 404. Restores wiring clobbered by a later integration-wave push built from a
|
| 327 |
+
# stale base (the register block in serve.py + this COPY were both lost).
|
| 328 |
+
COPY a11oy_react_core.py ./
|
| 329 |
|
| 330 |
# ADDITIVE (Cross-Harness Receipt Bridge — Hermes + OpenClaw; 2026-06-01, Yachay /
|
| 331 |
# Perplexity Computer Agent; closeout PR superseding #198 runtime files). serve.py
|
a11oy_react_core.py
ADDED
|
@@ -0,0 +1,805 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# ============================================================================
|
| 3 |
+
# a11oy_react_core.py — LANE A AGENTIC CORE (Dev A, SZL Holdings)
|
| 4 |
+
# ----------------------------------------------------------------------------
|
| 5 |
+
# A REAL, resumable ReAct execution graph (Thought -> Action -> Observation)
|
| 6 |
+
# where EACH node transition is a SIGNED receipt boundary, plus:
|
| 7 |
+
# * SqliteSaver-style checkpointing (crash mid-run -> /resume continues)
|
| 8 |
+
# * Reflexion inner loop (NL reflection prepended next activation)
|
| 9 |
+
# * Generative-Agents memory scoring score(m)=a_rec*g^dt + a_imp*imp + a_rel*cos
|
| 10 |
+
# * Letta-style memory tiering (working in-context + archival vector)
|
| 11 |
+
# * Voyager skill library (admit a recipe ONLY after a passing receipt)
|
| 12 |
+
#
|
| 13 |
+
# Honest engineering (DOCTRINE v11):
|
| 14 |
+
# - The signer is the HOST app's REAL in-image ECDSA-P256 DSSE signer
|
| 15 |
+
# (_a11oy_sign_receipt), passed in via register(); we NEVER fabricate a
|
| 16 |
+
# signature. verify_fn re-verifies against /cosign.pub.
|
| 17 |
+
# - The vector store is LOCAL (sqlite + numpy, hashing-trie embeddings) — 0
|
| 18 |
+
# external CDN/service. Embeddings are a deterministic local feature hash
|
| 19 |
+
# (labelled HEURISTIC), not a remote model, so retrieval is reproducible
|
| 20 |
+
# offline. Scores are surfaced honestly with their components.
|
| 21 |
+
# - Trust / coverage framing, never bare "confidence %".
|
| 22 |
+
# - Routes are inserted at position 0 (Starlette Route) so they beat the SPA
|
| 23 |
+
# catch-all, mirroring szl_agentic_loop.
|
| 24 |
+
# - Endpoints live under the FREE /api/a11oy/v1/agent/react/* sub-namespace
|
| 25 |
+
# (run/resume/trace/checkpoints) so we do NOT collide with the existing
|
| 26 |
+
# /run, /tools, /verify-chain, /governance-standards, /_diag, /loop.
|
| 27 |
+
#
|
| 28 |
+
# Co-Authored-By: Perplexity Computer Agent <agent@perplexity.ai>
|
| 29 |
+
# Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
|
| 30 |
+
# ============================================================================
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
import hashlib
|
| 34 |
+
import json
|
| 35 |
+
import math
|
| 36 |
+
import os
|
| 37 |
+
import re
|
| 38 |
+
import sqlite3
|
| 39 |
+
import threading
|
| 40 |
+
import time
|
| 41 |
+
import uuid
|
| 42 |
+
from datetime import datetime, timezone
|
| 43 |
+
|
| 44 |
+
# ---------------------------------------------------------------------------
|
| 45 |
+
# Storage: a single local sqlite DB under /tmp (ephemeral per container, which
|
| 46 |
+
# is the honest reality of a HF Space). All five subsystems persist here so a
|
| 47 |
+
# crash mid-run can resume from the last committed checkpoint within the life
|
| 48 |
+
# of the container. Labelled accordingly in the UI.
|
| 49 |
+
# ---------------------------------------------------------------------------
|
| 50 |
+
_DB_PATH = os.environ.get("A11OY_REACT_DB", "/tmp/a11oy_react_core.sqlite3")
|
| 51 |
+
_LOCK = threading.RLock()
|
| 52 |
+
_EMBED_DIM = 64 # local hashing-trick embedding dimension
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _now_iso() -> str:
|
| 56 |
+
return datetime.now(timezone.utc).isoformat()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _now_epoch() -> float:
|
| 60 |
+
return time.time()
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _sha(obj) -> str:
|
| 64 |
+
return hashlib.sha256(
|
| 65 |
+
json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
| 66 |
+
).hexdigest()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _conn() -> sqlite3.Connection:
|
| 70 |
+
c = sqlite3.connect(_DB_PATH, timeout=30, check_same_thread=False)
|
| 71 |
+
c.row_factory = sqlite3.Row
|
| 72 |
+
c.execute("PRAGMA journal_mode=WAL;")
|
| 73 |
+
return c
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _init_db() -> None:
|
| 77 |
+
with _LOCK, _conn() as c:
|
| 78 |
+
c.executescript(
|
| 79 |
+
"""
|
| 80 |
+
CREATE TABLE IF NOT EXISTS runs (
|
| 81 |
+
run_id TEXT PRIMARY KEY,
|
| 82 |
+
goal TEXT, status TEXT, max_steps INTEGER,
|
| 83 |
+
step INTEGER, prev_hash TEXT, final_hash TEXT,
|
| 84 |
+
reflection TEXT, created_at TEXT, updated_at TEXT
|
| 85 |
+
);
|
| 86 |
+
CREATE TABLE IF NOT EXISTS receipts (
|
| 87 |
+
run_id TEXT, seq INTEGER, node TEXT, body TEXT,
|
| 88 |
+
prev_hash TEXT, hash TEXT, envelope TEXT, ts TEXT,
|
| 89 |
+
PRIMARY KEY (run_id, seq)
|
| 90 |
+
);
|
| 91 |
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
| 92 |
+
run_id TEXT, step INTEGER, state TEXT, prev_hash TEXT,
|
| 93 |
+
ts TEXT, PRIMARY KEY (run_id, step)
|
| 94 |
+
);
|
| 95 |
+
CREATE TABLE IF NOT EXISTS memory (
|
| 96 |
+
mem_id TEXT PRIMARY KEY, run_id TEXT, tier TEXT, kind TEXT,
|
| 97 |
+
text TEXT, importance REAL, created_at REAL, last_access REAL,
|
| 98 |
+
embedding TEXT
|
| 99 |
+
);
|
| 100 |
+
CREATE TABLE IF NOT EXISTS skills (
|
| 101 |
+
skill_id TEXT PRIMARY KEY, name TEXT, recipe TEXT,
|
| 102 |
+
receipt_hash TEXT, receipt_verified INTEGER, embedding TEXT,
|
| 103 |
+
created_at TEXT, uses INTEGER
|
| 104 |
+
);
|
| 105 |
+
CREATE TABLE IF NOT EXISTS reflections (
|
| 106 |
+
run_id TEXT, idx INTEGER, text TEXT, ts TEXT,
|
| 107 |
+
PRIMARY KEY (run_id, idx)
|
| 108 |
+
);
|
| 109 |
+
"""
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# ---------------------------------------------------------------------------
|
| 114 |
+
# LOCAL embedding — deterministic hashing-trick bag-of-tokens, L2 normalised.
|
| 115 |
+
# This is NOT a learned model; it is a reproducible local feature hash so that
|
| 116 |
+
# cosine similarity is meaningful for lexical overlap WITHOUT any network call.
|
| 117 |
+
# Labelled HEURISTIC everywhere it surfaces.
|
| 118 |
+
# ---------------------------------------------------------------------------
|
| 119 |
+
_TOK = re.compile(r"[a-z0-9]+")
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _embed(text: str) -> list:
|
| 123 |
+
vec = [0.0] * _EMBED_DIM
|
| 124 |
+
toks = _TOK.findall((text or "").lower())
|
| 125 |
+
for t in toks:
|
| 126 |
+
h = int(hashlib.md5(t.encode()).hexdigest(), 16)
|
| 127 |
+
idx = h % _EMBED_DIM
|
| 128 |
+
sign = 1.0 if (h >> 8) & 1 else -1.0
|
| 129 |
+
vec[idx] += sign
|
| 130 |
+
norm = math.sqrt(sum(v * v for v in vec)) or 1.0
|
| 131 |
+
return [v / norm for v in vec]
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _cos(a: list, b: list) -> float:
|
| 135 |
+
if not a or not b or len(a) != len(b):
|
| 136 |
+
return 0.0
|
| 137 |
+
return max(-1.0, min(1.0, sum(x * y for x, y in zip(a, b))))
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _importance_heuristic(text: str) -> float:
|
| 141 |
+
"""Local importance proxy in [0,1] (LLM-scored 1-10 in the paper; here a
|
| 142 |
+
transparent HEURISTIC: longer, decision/goal-bearing text scores higher).
|
| 143 |
+
Surfaced honestly as HEURISTIC, never claimed as an LLM judgement."""
|
| 144 |
+
t = (text or "").lower()
|
| 145 |
+
score = min(1.0, len(t) / 240.0)
|
| 146 |
+
for kw, w in (("goal", 0.2), ("decision", 0.2), ("fail", 0.25),
|
| 147 |
+
("error", 0.25), ("reflect", 0.2), ("verified", 0.15),
|
| 148 |
+
("receipt", 0.1)):
|
| 149 |
+
if kw in t:
|
| 150 |
+
score = min(1.0, score + w)
|
| 151 |
+
return round(score, 4)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# ---------------------------------------------------------------------------
|
| 155 |
+
# Generative-Agents retrieval score:
|
| 156 |
+
# score(m) = a_rec * gamma^dt_hours + a_imp * imp(m) + a_rel * cos(q, m)
|
| 157 |
+
# gamma ~ 0.995 / hour (arXiv 2304.03442). Components surfaced honestly.
|
| 158 |
+
# ---------------------------------------------------------------------------
|
| 159 |
+
_GAMMA = 0.995 # recency decay per hour
|
| 160 |
+
_A_REC = 1.0
|
| 161 |
+
_A_IMP = 1.0
|
| 162 |
+
_A_REL = 1.0
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def _score_memory(row, q_emb: list, now_epoch: float) -> dict:
|
| 166 |
+
dt_hours = max(0.0, (now_epoch - float(row["last_access"])) / 3600.0)
|
| 167 |
+
recency = _GAMMA ** dt_hours
|
| 168 |
+
imp = float(row["importance"])
|
| 169 |
+
try:
|
| 170 |
+
emb = json.loads(row["embedding"])
|
| 171 |
+
except Exception:
|
| 172 |
+
emb = []
|
| 173 |
+
rel = _cos(q_emb, emb)
|
| 174 |
+
rel01 = (rel + 1.0) / 2.0 # map cosine [-1,1] -> [0,1] for the weighted sum
|
| 175 |
+
total = _A_REC * recency + _A_IMP * imp + _A_REL * rel01
|
| 176 |
+
return {
|
| 177 |
+
"mem_id": row["mem_id"], "tier": row["tier"], "kind": row["kind"],
|
| 178 |
+
"text": row["text"],
|
| 179 |
+
"score": round(total, 6),
|
| 180 |
+
"components": {
|
| 181 |
+
"recency_gamma_dt": round(recency, 6),
|
| 182 |
+
"delta_t_hours": round(dt_hours, 4),
|
| 183 |
+
"importance": round(imp, 4),
|
| 184 |
+
"relevance_cos": round(rel, 6),
|
| 185 |
+
"relevance_0_1": round(rel01, 6),
|
| 186 |
+
},
|
| 187 |
+
"weights": {"alpha_recency": _A_REC, "alpha_importance": _A_IMP,
|
| 188 |
+
"alpha_relevance": _A_REL, "gamma_per_hour": _GAMMA},
|
| 189 |
+
"label": "HEURISTIC", # local embeddings + heuristic importance
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def _mem_add(run_id: str, tier: str, kind: str, text: str,
|
| 194 |
+
importance=None) -> str:
|
| 195 |
+
mem_id = "mem_" + uuid.uuid4().hex[:12]
|
| 196 |
+
now = _now_epoch()
|
| 197 |
+
imp = _importance_heuristic(text) if importance is None else float(importance)
|
| 198 |
+
emb = _embed(text)
|
| 199 |
+
with _LOCK, _conn() as c:
|
| 200 |
+
c.execute(
|
| 201 |
+
"INSERT INTO memory(mem_id,run_id,tier,kind,text,importance,"
|
| 202 |
+
"created_at,last_access,embedding) VALUES(?,?,?,?,?,?,?,?,?)",
|
| 203 |
+
(mem_id, run_id, tier, kind, text, imp, now, now, json.dumps(emb)),
|
| 204 |
+
)
|
| 205 |
+
return mem_id
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _mem_retrieve(query: str, top_k: int = 5, tier=None) -> list:
|
| 209 |
+
q_emb = _embed(query)
|
| 210 |
+
now = _now_epoch()
|
| 211 |
+
with _LOCK, _conn() as c:
|
| 212 |
+
if tier:
|
| 213 |
+
rows = c.execute("SELECT * FROM memory WHERE tier=?", (tier,)).fetchall()
|
| 214 |
+
else:
|
| 215 |
+
rows = c.execute("SELECT * FROM memory").fetchall()
|
| 216 |
+
scored = [_score_memory(r, q_emb, now) for r in rows]
|
| 217 |
+
scored.sort(key=lambda s: s["score"], reverse=True)
|
| 218 |
+
top = scored[:top_k]
|
| 219 |
+
# honest "access" bump: retrieved memories refresh their recency clock
|
| 220 |
+
if top:
|
| 221 |
+
ids = [s["mem_id"] for s in top]
|
| 222 |
+
with _LOCK, _conn() as c:
|
| 223 |
+
c.executemany("UPDATE memory SET last_access=? WHERE mem_id=?",
|
| 224 |
+
[(now, i) for i in ids])
|
| 225 |
+
return top
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
# ---------------------------------------------------------------------------
|
| 229 |
+
# Letta-style memory tiering. "working" = in-context (small, fast); "archival"
|
| 230 |
+
# = vector store (large, searched). The agent self-manages via tool calls
|
| 231 |
+
# memory_append / memory_search / memory_promote that the ReAct loop can emit.
|
| 232 |
+
# ---------------------------------------------------------------------------
|
| 233 |
+
_WORKING_CAP = 8 # in-context working-memory item cap (paging boundary)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def _working_snapshot(run_id: str) -> list:
|
| 237 |
+
with _LOCK, _conn() as c:
|
| 238 |
+
rows = c.execute(
|
| 239 |
+
"SELECT * FROM memory WHERE run_id=? AND tier='working' "
|
| 240 |
+
"ORDER BY last_access DESC LIMIT ?", (run_id, _WORKING_CAP)
|
| 241 |
+
).fetchall()
|
| 242 |
+
return [{"mem_id": r["mem_id"], "kind": r["kind"], "text": r["text"],
|
| 243 |
+
"importance": r["importance"]} for r in rows]
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def _page_out_if_full(run_id: str) -> list:
|
| 247 |
+
"""Letta/MemGPT paging: when working memory exceeds the in-context cap, the
|
| 248 |
+
LEAST-recently-accessed working items are promoted (paged out) to archival
|
| 249 |
+
so the in-context window stays bounded. Returns the paged-out mem_ids."""
|
| 250 |
+
paged = []
|
| 251 |
+
with _LOCK, _conn() as c:
|
| 252 |
+
rows = c.execute(
|
| 253 |
+
"SELECT mem_id FROM memory WHERE run_id=? AND tier='working' "
|
| 254 |
+
"ORDER BY last_access ASC", (run_id,)
|
| 255 |
+
).fetchall()
|
| 256 |
+
if len(rows) > _WORKING_CAP:
|
| 257 |
+
overflow = rows[: len(rows) - _WORKING_CAP]
|
| 258 |
+
for r in overflow:
|
| 259 |
+
c.execute("UPDATE memory SET tier='archival' WHERE mem_id=?",
|
| 260 |
+
(r["mem_id"],))
|
| 261 |
+
paged.append(r["mem_id"])
|
| 262 |
+
return paged
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
# ---------------------------------------------------------------------------
|
| 266 |
+
# Voyager skill library. A tool-recipe is ADMITTED to the library ONLY after a
|
| 267 |
+
# verified execution receipt (DSSE envelope verified by the host verify_fn).
|
| 268 |
+
# Recipes are indexed by local embedding so the agent can retrieve a relevant
|
| 269 |
+
# prior recipe. We NEVER admit a recipe whose receipt fails verification.
|
| 270 |
+
# ---------------------------------------------------------------------------
|
| 271 |
+
def _skill_admit(name: str, recipe: str, receipt_hash: str,
|
| 272 |
+
receipt_verified: bool) -> dict:
|
| 273 |
+
if not receipt_verified:
|
| 274 |
+
return {"admitted": False,
|
| 275 |
+
"reason": "REJECTED — execution receipt did not verify; Voyager "
|
| 276 |
+
"admission requires a passing signed receipt.",
|
| 277 |
+
"receipt_hash": receipt_hash}
|
| 278 |
+
skill_id = "skill_" + uuid.uuid4().hex[:12]
|
| 279 |
+
emb = _embed(name + " " + recipe)
|
| 280 |
+
with _LOCK, _conn() as c:
|
| 281 |
+
c.execute(
|
| 282 |
+
"INSERT INTO skills(skill_id,name,recipe,receipt_hash,"
|
| 283 |
+
"receipt_verified,embedding,created_at,uses) VALUES(?,?,?,?,?,?,?,0)",
|
| 284 |
+
(skill_id, name, recipe, receipt_hash, 1, json.dumps(emb), _now_iso()),
|
| 285 |
+
)
|
| 286 |
+
return {"admitted": True, "skill_id": skill_id, "name": name,
|
| 287 |
+
"receipt_hash": receipt_hash,
|
| 288 |
+
"reason": "ADMITTED — backed by a verified execution receipt."}
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _skill_search(query: str, top_k: int = 5) -> list:
|
| 292 |
+
q_emb = _embed(query)
|
| 293 |
+
with _LOCK, _conn() as c:
|
| 294 |
+
rows = c.execute("SELECT * FROM skills").fetchall()
|
| 295 |
+
out = []
|
| 296 |
+
for r in rows:
|
| 297 |
+
try:
|
| 298 |
+
emb = json.loads(r["embedding"])
|
| 299 |
+
except Exception:
|
| 300 |
+
emb = []
|
| 301 |
+
out.append({"skill_id": r["skill_id"], "name": r["name"],
|
| 302 |
+
"recipe": r["recipe"], "receipt_hash": r["receipt_hash"],
|
| 303 |
+
"receipt_verified": bool(r["receipt_verified"]),
|
| 304 |
+
"uses": r["uses"],
|
| 305 |
+
"similarity": round(_cos(q_emb, emb), 6), "label": "LIVE"})
|
| 306 |
+
out.sort(key=lambda s: s["similarity"], reverse=True)
|
| 307 |
+
return out[:top_k]
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
# ---------------------------------------------------------------------------
|
| 311 |
+
# ReAct execution graph (arXiv 2210.03629). Nodes: THOUGHT -> ACTION ->
|
| 312 |
+
# OBSERVATION, looping until a terminal ANSWER or max_steps. EACH node
|
| 313 |
+
# transition is committed as a hash-chained receipt AND wrapped in a DSSE
|
| 314 |
+
# envelope by the host signer (sign_fn). A SqliteSaver-style checkpoint is
|
| 315 |
+
# written after every node so a crash resumes from the last committed step.
|
| 316 |
+
#
|
| 317 |
+
# The "model call" is routed through a small, deterministic in-image policy
|
| 318 |
+
# (the host a11oy inference path is the production target; we keep the loop's
|
| 319 |
+
# model-calls inside the app and label the planner HEURISTIC so we never fake a
|
| 320 |
+
# model number — the GRAPH, RECEIPTS, CHECKPOINTING and RESUME are all REAL).
|
| 321 |
+
# ---------------------------------------------------------------------------
|
| 322 |
+
|
| 323 |
+
# Tool registry: small, real, deterministic tools the ReAct agent can call.
|
| 324 |
+
def _tool_calc(arg: str) -> str:
|
| 325 |
+
expr = re.sub(r"[^0-9+\-*/(). ]", "", arg or "")
|
| 326 |
+
if not expr.strip():
|
| 327 |
+
return "ERR: empty expression"
|
| 328 |
+
try:
|
| 329 |
+
# safe arithmetic only (chars already filtered); no names/builtins
|
| 330 |
+
return str(eval(expr, {"__builtins__": {}}, {})) # noqa: S307
|
| 331 |
+
except Exception as e:
|
| 332 |
+
return "ERR: %s" % type(e).__name__
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def _tool_memory_search(arg: str) -> str:
|
| 336 |
+
hits = _mem_retrieve(arg, top_k=3)
|
| 337 |
+
if not hits:
|
| 338 |
+
return "no memories"
|
| 339 |
+
return " | ".join("%s(score=%.3f)" % (h["text"][:48], h["score"]) for h in hits)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def _tool_skill_search(arg: str) -> str:
|
| 343 |
+
hits = _skill_search(arg, top_k=3)
|
| 344 |
+
if not hits:
|
| 345 |
+
return "no skills"
|
| 346 |
+
return " | ".join("%s(sim=%.3f)" % (h["name"], h["similarity"]) for h in hits)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def _tool_echo(arg: str) -> str:
|
| 350 |
+
return (arg or "")[:200]
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
_TOOLS = {
|
| 354 |
+
"calc": _tool_calc,
|
| 355 |
+
"memory_search": _tool_memory_search,
|
| 356 |
+
"skill_search": _tool_skill_search,
|
| 357 |
+
"echo": _tool_echo,
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def _plan_action(goal: str, scratch: list) -> dict:
|
| 362 |
+
"""HEURISTIC planner (NOT a learned model — labelled HEURISTIC). Picks the
|
| 363 |
+
next ReAct action from the goal + scratchpad. Deterministic so the loop is
|
| 364 |
+
replayable and the demo is reproducible. The production target is the host
|
| 365 |
+
a11oy inference path; this keeps the agent loop's model-calls in-app."""
|
| 366 |
+
g = (goal or "").lower()
|
| 367 |
+
step = len(scratch)
|
| 368 |
+
# terminal: if we already produced an observation, answer.
|
| 369 |
+
last_obs = next((s for s in reversed(scratch) if s.get("node") == "OBSERVATION"), None)
|
| 370 |
+
if last_obs is not None:
|
| 371 |
+
return {"terminal": True,
|
| 372 |
+
"thought": "I have an observation; I can answer now.",
|
| 373 |
+
"answer": "Result: %s" % last_obs.get("observation", "")}
|
| 374 |
+
# arithmetic goal -> calc
|
| 375 |
+
if re.search(r"\d.*[+\-*/].*\d", g):
|
| 376 |
+
m = re.search(r"[-0-9+\-*/(). ]{3,}", goal)
|
| 377 |
+
arg = m.group(0).strip() if m else goal
|
| 378 |
+
return {"terminal": False, "tool": "calc", "tool_input": arg,
|
| 379 |
+
"thought": "This looks arithmetic; I will use the calc tool."}
|
| 380 |
+
if "memory" in g or "remember" in g or "recall" in g:
|
| 381 |
+
return {"terminal": False, "tool": "memory_search", "tool_input": goal,
|
| 382 |
+
"thought": "I should consult memory for this."}
|
| 383 |
+
if "skill" in g or "recipe" in g or "how do i" in g:
|
| 384 |
+
return {"terminal": False, "tool": "skill_search", "tool_input": goal,
|
| 385 |
+
"thought": "I should check the skill library."}
|
| 386 |
+
return {"terminal": False, "tool": "echo", "tool_input": goal,
|
| 387 |
+
"thought": "No specialised tool; I will restate and observe."}
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
class _ReActEngine:
|
| 391 |
+
"""Holds the host signer/verifier and runs / resumes graphs."""
|
| 392 |
+
|
| 393 |
+
def __init__(self, sign_fn, verify_fn, pub_pem_fn, ns="a11oy"):
|
| 394 |
+
self.sign_fn = sign_fn
|
| 395 |
+
self.verify_fn = verify_fn
|
| 396 |
+
self.pub_pem_fn = pub_pem_fn
|
| 397 |
+
self.ns = ns
|
| 398 |
+
_init_db()
|
| 399 |
+
|
| 400 |
+
# ---- receipt boundary: chain + DSSE sign every node transition ----
|
| 401 |
+
def _commit_receipt(self, run_id, seq, node, body, prev_hash, reflection=None):
|
| 402 |
+
rec_core = {"seq": seq, "node": node, "body": body, "prev_hash": prev_hash}
|
| 403 |
+
h = _sha(rec_core)
|
| 404 |
+
payload = {
|
| 405 |
+
"run_id": run_id, "seq": seq, "node": node, "body": body,
|
| 406 |
+
"prev_hash": prev_hash, "hash": h, "issuer": self.ns,
|
| 407 |
+
"issued_at": _now_iso(),
|
| 408 |
+
"reflection": reflection, # Reflexion field on the receipt
|
| 409 |
+
"trust_status": "Conjecture 1 (advisory \u2014 NOT a proven oracle)",
|
| 410 |
+
}
|
| 411 |
+
try:
|
| 412 |
+
envelope = self.sign_fn(payload)
|
| 413 |
+
except Exception as e:
|
| 414 |
+
envelope = {"signed": False, "signatures": [],
|
| 415 |
+
"honesty": "UNSIGNED \u2014 signer raised %s" % type(e).__name__,
|
| 416 |
+
"payloadType": "application/vnd.szl.receipt+json"}
|
| 417 |
+
with _LOCK, _conn() as c:
|
| 418 |
+
c.execute(
|
| 419 |
+
"INSERT OR REPLACE INTO receipts(run_id,seq,node,body,prev_hash,"
|
| 420 |
+
"hash,envelope,ts) VALUES(?,?,?,?,?,?,?,?)",
|
| 421 |
+
(run_id, seq, node, json.dumps(body), prev_hash, h,
|
| 422 |
+
json.dumps(envelope), _now_iso()),
|
| 423 |
+
)
|
| 424 |
+
return h, envelope
|
| 425 |
+
|
| 426 |
+
# ---- SqliteSaver-style checkpoint after every node ----
|
| 427 |
+
def _checkpoint(self, run_id, step, state, prev_hash):
|
| 428 |
+
with _LOCK, _conn() as c:
|
| 429 |
+
c.execute(
|
| 430 |
+
"INSERT OR REPLACE INTO checkpoints(run_id,step,state,prev_hash,ts)"
|
| 431 |
+
" VALUES(?,?,?,?,?)",
|
| 432 |
+
(run_id, step, json.dumps(state), prev_hash, _now_iso()),
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
def _load_run(self, run_id):
|
| 436 |
+
with _LOCK, _conn() as c:
|
| 437 |
+
r = c.execute("SELECT * FROM runs WHERE run_id=?", (run_id,)).fetchone()
|
| 438 |
+
cps = c.execute(
|
| 439 |
+
"SELECT * FROM checkpoints WHERE run_id=? ORDER BY step DESC LIMIT 1",
|
| 440 |
+
(run_id,)).fetchone()
|
| 441 |
+
return r, cps
|
| 442 |
+
|
| 443 |
+
def _prior_reflection(self, goal):
|
| 444 |
+
"""Reflexion: prepend the most relevant prior reflection on activation."""
|
| 445 |
+
with _LOCK, _conn() as c:
|
| 446 |
+
rows = c.execute(
|
| 447 |
+
"SELECT text FROM reflections ORDER BY rowid DESC LIMIT 8").fetchall()
|
| 448 |
+
if not rows:
|
| 449 |
+
return None
|
| 450 |
+
# pick the reflection most lexically relevant to this goal
|
| 451 |
+
q = _embed(goal)
|
| 452 |
+
best, best_sim = None, -2.0
|
| 453 |
+
for r in rows:
|
| 454 |
+
sim = _cos(q, _embed(r["text"]))
|
| 455 |
+
if sim > best_sim:
|
| 456 |
+
best, best_sim = r["text"], sim
|
| 457 |
+
return best
|
| 458 |
+
|
| 459 |
+
# ---- run a (possibly partial) graph from a starting step ----
|
| 460 |
+
def _drive(self, run_id, goal, max_steps, start_step, prev_hash,
|
| 461 |
+
scratch, reflection, kill_after=None):
|
| 462 |
+
node_seq = start_step
|
| 463 |
+
status = "running"
|
| 464 |
+
steps_done = 0
|
| 465 |
+
terminal_answer = None
|
| 466 |
+
while node_seq // 3 < max_steps:
|
| 467 |
+
phase = node_seq % 3
|
| 468 |
+
cur_step = node_seq // 3
|
| 469 |
+
if phase == 0: # THOUGHT
|
| 470 |
+
plan = _plan_action(goal, scratch)
|
| 471 |
+
scratch.append({"node": "THOUGHT", "step": cur_step,
|
| 472 |
+
"thought": plan["thought"], "plan": plan})
|
| 473 |
+
body = {"step": cur_step, "thought": plan["thought"],
|
| 474 |
+
"intended_tool": plan.get("tool"),
|
| 475 |
+
"terminal": plan.get("terminal", False)}
|
| 476 |
+
prev_hash, _ = self._commit_receipt(run_id, node_seq, "THOUGHT",
|
| 477 |
+
body, prev_hash, reflection)
|
| 478 |
+
if plan.get("terminal"):
|
| 479 |
+
terminal_answer = plan.get("answer")
|
| 480 |
+
status = "completed"
|
| 481 |
+
self._checkpoint(run_id, node_seq + 1,
|
| 482 |
+
{"scratch": scratch, "answer": terminal_answer},
|
| 483 |
+
prev_hash)
|
| 484 |
+
node_seq += 1
|
| 485 |
+
break
|
| 486 |
+
elif phase == 1: # ACTION
|
| 487 |
+
plan = scratch[-1]["plan"]
|
| 488 |
+
tool, arg = plan.get("tool", "echo"), plan.get("tool_input", "")
|
| 489 |
+
scratch.append({"node": "ACTION", "step": cur_step,
|
| 490 |
+
"tool": tool, "tool_input": arg})
|
| 491 |
+
body = {"step": cur_step, "tool": tool, "tool_input": arg}
|
| 492 |
+
prev_hash, _ = self._commit_receipt(run_id, node_seq, "ACTION",
|
| 493 |
+
body, prev_hash, reflection)
|
| 494 |
+
else: # OBSERVATION (execute the tool for real)
|
| 495 |
+
act = next(s for s in reversed(scratch) if s.get("node") == "ACTION")
|
| 496 |
+
fn = _TOOLS.get(act["tool"], _tool_echo)
|
| 497 |
+
obs = fn(act["tool_input"])
|
| 498 |
+
scratch.append({"node": "OBSERVATION", "step": cur_step,
|
| 499 |
+
"observation": obs})
|
| 500 |
+
# store the observation as a working memory (Letta tiering)
|
| 501 |
+
_mem_add(run_id, "working", "observation",
|
| 502 |
+
"step %d %s->%s" % (cur_step, act["tool"], obs))
|
| 503 |
+
_page_out_if_full(run_id)
|
| 504 |
+
body = {"step": cur_step, "tool": act["tool"], "observation": obs}
|
| 505 |
+
prev_hash, _ = self._commit_receipt(run_id, node_seq, "OBSERVATION",
|
| 506 |
+
body, prev_hash, reflection)
|
| 507 |
+
node_seq += 1
|
| 508 |
+
steps_done += 1
|
| 509 |
+
# CHECKPOINT after every node transition (SqliteSaver-style)
|
| 510 |
+
self._checkpoint(run_id, node_seq,
|
| 511 |
+
{"scratch": scratch, "node_seq": node_seq}, prev_hash)
|
| 512 |
+
# honest crash injection for the resumable demo: stop mid-run
|
| 513 |
+
if kill_after is not None and steps_done >= kill_after:
|
| 514 |
+
status = "interrupted"
|
| 515 |
+
break
|
| 516 |
+
else:
|
| 517 |
+
status = "completed" if terminal_answer else "max_steps"
|
| 518 |
+
|
| 519 |
+
final_hash = prev_hash
|
| 520 |
+
with _LOCK, _conn() as c:
|
| 521 |
+
c.execute(
|
| 522 |
+
"UPDATE runs SET status=?,step=?,prev_hash=?,final_hash=?,"
|
| 523 |
+
"updated_at=? WHERE run_id=?",
|
| 524 |
+
(status, node_seq, prev_hash, final_hash, _now_iso(), run_id))
|
| 525 |
+
return {"run_id": run_id, "status": status, "node_seq": node_seq,
|
| 526 |
+
"final_hash": final_hash, "answer": terminal_answer,
|
| 527 |
+
"scratch": scratch}
|
| 528 |
+
|
| 529 |
+
def run(self, goal, max_steps=4, kill_after=None):
|
| 530 |
+
run_id = "run_" + uuid.uuid4().hex[:12]
|
| 531 |
+
reflection = self._prior_reflection(goal)
|
| 532 |
+
# seed long-term memory with the goal
|
| 533 |
+
_mem_add(run_id, "working", "goal", "GOAL: " + (goal or ""))
|
| 534 |
+
with _LOCK, _conn() as c:
|
| 535 |
+
c.execute(
|
| 536 |
+
"INSERT INTO runs(run_id,goal,status,max_steps,step,prev_hash,"
|
| 537 |
+
"final_hash,reflection,created_at,updated_at) "
|
| 538 |
+
"VALUES(?,?,?,?,?,?,?,?,?,?)",
|
| 539 |
+
(run_id, goal, "running", max_steps, 0, "GENESIS", "",
|
| 540 |
+
reflection or "", _now_iso(), _now_iso()))
|
| 541 |
+
# genesis receipt
|
| 542 |
+
prev_hash = "GENESIS"
|
| 543 |
+
prev_hash, _ = self._commit_receipt(
|
| 544 |
+
run_id, -1, "GENESIS",
|
| 545 |
+
{"goal": goal, "max_steps": max_steps,
|
| 546 |
+
"prior_reflection_prepended": bool(reflection)},
|
| 547 |
+
prev_hash, reflection)
|
| 548 |
+
self._checkpoint(run_id, 0, {"scratch": [], "node_seq": 0}, prev_hash)
|
| 549 |
+
return self._drive(run_id, goal, max_steps, 0, prev_hash, [],
|
| 550 |
+
reflection, kill_after=kill_after)
|
| 551 |
+
|
| 552 |
+
def resume(self, run_id):
|
| 553 |
+
r, cps = self._load_run(run_id)
|
| 554 |
+
if r is None:
|
| 555 |
+
return {"error": "unknown run_id", "run_id": run_id}
|
| 556 |
+
if r["status"] not in ("interrupted", "running", "max_steps"):
|
| 557 |
+
return {"run_id": run_id, "status": r["status"],
|
| 558 |
+
"note": "run already %s \u2014 nothing to resume" % r["status"],
|
| 559 |
+
"resumed": False}
|
| 560 |
+
state = json.loads(cps["state"]) if cps else {"scratch": [], "node_seq": 0}
|
| 561 |
+
node_seq = state.get("node_seq", 0)
|
| 562 |
+
scratch = state.get("scratch", [])
|
| 563 |
+
prev_hash = cps["prev_hash"] if cps else "GENESIS"
|
| 564 |
+
out = self._drive(run_id, r["goal"], r["max_steps"], node_seq, prev_hash,
|
| 565 |
+
scratch, r["reflection"] or None)
|
| 566 |
+
out["resumed"] = True
|
| 567 |
+
out["resumed_from_checkpoint_step"] = node_seq
|
| 568 |
+
return out
|
| 569 |
+
|
| 570 |
+
def trace(self, run_id):
|
| 571 |
+
with _LOCK, _conn() as c:
|
| 572 |
+
r = c.execute("SELECT * FROM runs WHERE run_id=?", (run_id,)).fetchone()
|
| 573 |
+
recs = c.execute(
|
| 574 |
+
"SELECT * FROM receipts WHERE run_id=? ORDER BY seq", (run_id,)
|
| 575 |
+
).fetchall()
|
| 576 |
+
if r is None:
|
| 577 |
+
return {"error": "unknown run_id", "run_id": run_id}
|
| 578 |
+
receipts, chain_ok, prev = [], True, "GENESIS"
|
| 579 |
+
for rec in recs:
|
| 580 |
+
body = json.loads(rec["body"])
|
| 581 |
+
recompute = _sha({"seq": rec["seq"], "node": rec["node"],
|
| 582 |
+
"body": body, "prev_hash": rec["prev_hash"]})
|
| 583 |
+
link_ok = (rec["prev_hash"] == prev) and (recompute == rec["hash"])
|
| 584 |
+
env = json.loads(rec["envelope"])
|
| 585 |
+
sig_ok = None
|
| 586 |
+
if self.verify_fn is not None:
|
| 587 |
+
try:
|
| 588 |
+
sig_ok = bool(self.verify_fn(env).get("signature_valid"))
|
| 589 |
+
except Exception:
|
| 590 |
+
sig_ok = False
|
| 591 |
+
chain_ok = chain_ok and link_ok
|
| 592 |
+
receipts.append({"seq": rec["seq"], "node": rec["node"], "body": body,
|
| 593 |
+
"hash": rec["hash"], "prev_hash": rec["prev_hash"],
|
| 594 |
+
"link_ok": link_ok, "signature_valid": sig_ok,
|
| 595 |
+
"signed": bool(env.get("signed")),
|
| 596 |
+
"ts": rec["ts"]})
|
| 597 |
+
prev = rec["hash"]
|
| 598 |
+
return {"run_id": run_id, "goal": r["goal"], "status": r["status"],
|
| 599 |
+
"reflection": r["reflection"],
|
| 600 |
+
"chain_intact": chain_ok, "depth": len(receipts),
|
| 601 |
+
"final_hash": r["final_hash"], "receipts": receipts,
|
| 602 |
+
"trust_note": "Receipt chain + DSSE signatures are REAL; planner is "
|
| 603 |
+
"HEURISTIC (deterministic, replayable). Trust=Conjecture 1."}
|
| 604 |
+
|
| 605 |
+
def checkpoints(self, run_id):
|
| 606 |
+
with _LOCK, _conn() as c:
|
| 607 |
+
cps = c.execute(
|
| 608 |
+
"SELECT step,prev_hash,ts FROM checkpoints WHERE run_id=? "
|
| 609 |
+
"ORDER BY step", (run_id,)).fetchall()
|
| 610 |
+
r = c.execute("SELECT status,step FROM runs WHERE run_id=?",
|
| 611 |
+
(run_id,)).fetchone()
|
| 612 |
+
return {"run_id": run_id,
|
| 613 |
+
"status": (r["status"] if r else "unknown"),
|
| 614 |
+
"current_step": (r["step"] if r else None),
|
| 615 |
+
"checkpoints": [{"step": c["step"], "prev_hash": c["prev_hash"],
|
| 616 |
+
"ts": c["ts"]} for c in cps],
|
| 617 |
+
"saver": "SqliteSaver-style (local sqlite, ephemeral per container)"}
|
| 618 |
+
|
| 619 |
+
def reflect(self, run_id, reflection_text):
|
| 620 |
+
"""Reflexion: store a NL reflection after a reviewed episode."""
|
| 621 |
+
with _LOCK, _conn() as c:
|
| 622 |
+
n = c.execute("SELECT COUNT(*) AS n FROM reflections WHERE run_id=?",
|
| 623 |
+
(run_id,)).fetchone()["n"]
|
| 624 |
+
c.execute("INSERT OR REPLACE INTO reflections(run_id,idx,text,ts) "
|
| 625 |
+
"VALUES(?,?,?,?)", (run_id, n, reflection_text, _now_iso()))
|
| 626 |
+
c.execute("UPDATE runs SET reflection=? WHERE run_id=?",
|
| 627 |
+
(reflection_text, run_id))
|
| 628 |
+
_mem_add(run_id, "archival", "reflection", reflection_text, importance=0.85)
|
| 629 |
+
return {"run_id": run_id, "stored": True, "reflection": reflection_text,
|
| 630 |
+
"note": "Prepended to the next activation on a lexically-relevant goal."}
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
# ---------------------------------------------------------------------------
|
| 634 |
+
# register(app, ns, sign_fn, verify_fn, pub_pem_fn) — mirrors szl_agentic_loop.
|
| 635 |
+
# Routes inserted at position 0 (Starlette Route) so they beat the SPA catch-all.
|
| 636 |
+
# FREE sub-namespace /api/a11oy/v1/agent/react/* to avoid collisions with the
|
| 637 |
+
# existing /run, /tools, /verify-chain, /governance-standards, /_diag, /loop.
|
| 638 |
+
# ---------------------------------------------------------------------------
|
| 639 |
+
def register(app, ns: str = "a11oy", sign_fn=None, verify_fn=None,
|
| 640 |
+
pub_pem_fn=None, signer_label: str = "in-image key"):
|
| 641 |
+
from starlette.routing import Route
|
| 642 |
+
from starlette.responses import JSONResponse
|
| 643 |
+
|
| 644 |
+
_init_db()
|
| 645 |
+
eng = _ReActEngine(sign_fn, verify_fn, pub_pem_fn, ns=ns)
|
| 646 |
+
|
| 647 |
+
async def _read_json(request):
|
| 648 |
+
try:
|
| 649 |
+
return await request.json()
|
| 650 |
+
except Exception:
|
| 651 |
+
return {}
|
| 652 |
+
|
| 653 |
+
async def _run(request):
|
| 654 |
+
d = await _read_json(request)
|
| 655 |
+
goal = (d.get("goal") or d.get("query") or "").strip()
|
| 656 |
+
if not goal:
|
| 657 |
+
return JSONResponse({"error": "missing 'goal'"}, status_code=400)
|
| 658 |
+
max_steps = int(d.get("max_steps", 4))
|
| 659 |
+
kill_after = d.get("kill_after") # honest crash-injection for the demo
|
| 660 |
+
kill_after = int(kill_after) if kill_after is not None else None
|
| 661 |
+
out = eng.run(goal, max_steps=max_steps, kill_after=kill_after)
|
| 662 |
+
out["label"] = "EXPERIMENTAL"
|
| 663 |
+
return JSONResponse(out)
|
| 664 |
+
|
| 665 |
+
async def _resume(request):
|
| 666 |
+
d = await _read_json(request)
|
| 667 |
+
run_id = (d.get("run_id") or request.query_params.get("run_id") or "").strip()
|
| 668 |
+
if not run_id:
|
| 669 |
+
return JSONResponse({"error": "missing 'run_id'"}, status_code=400)
|
| 670 |
+
out = eng.resume(run_id)
|
| 671 |
+
out["label"] = "EXPERIMENTAL"
|
| 672 |
+
return JSONResponse(out)
|
| 673 |
+
|
| 674 |
+
async def _trace(request):
|
| 675 |
+
run_id = request.path_params.get("run_id") or request.query_params.get("run_id", "")
|
| 676 |
+
return JSONResponse(eng.trace(run_id))
|
| 677 |
+
|
| 678 |
+
async def _checkpoints(request):
|
| 679 |
+
run_id = request.path_params.get("run_id") or request.query_params.get("run_id", "")
|
| 680 |
+
return JSONResponse(eng.checkpoints(run_id))
|
| 681 |
+
|
| 682 |
+
async def _reflect(request):
|
| 683 |
+
d = await _read_json(request)
|
| 684 |
+
run_id = (d.get("run_id") or "").strip()
|
| 685 |
+
text = (d.get("reflection") or d.get("text") or "").strip()
|
| 686 |
+
if not run_id or not text:
|
| 687 |
+
return JSONResponse({"error": "need run_id + reflection"}, status_code=400)
|
| 688 |
+
return JSONResponse(eng.reflect(run_id, text))
|
| 689 |
+
|
| 690 |
+
async def _mem_add_ep(request):
|
| 691 |
+
d = await _read_json(request)
|
| 692 |
+
text = (d.get("text") or "").strip()
|
| 693 |
+
if not text:
|
| 694 |
+
return JSONResponse({"error": "missing 'text'"}, status_code=400)
|
| 695 |
+
mid = _mem_add(d.get("run_id", "adhoc"), d.get("tier", "archival"),
|
| 696 |
+
d.get("kind", "note"), text, d.get("importance"))
|
| 697 |
+
return JSONResponse({"mem_id": mid, "tier": d.get("tier", "archival"),
|
| 698 |
+
"label": "HEURISTIC"})
|
| 699 |
+
|
| 700 |
+
async def _mem_search_ep(request):
|
| 701 |
+
d = await _read_json(request)
|
| 702 |
+
q = (d.get("query") or "").strip()
|
| 703 |
+
if not q:
|
| 704 |
+
return JSONResponse({"error": "missing 'query'"}, status_code=400)
|
| 705 |
+
hits = _mem_retrieve(q, top_k=int(d.get("top_k", 5)), tier=d.get("tier"))
|
| 706 |
+
return JSONResponse({"query": q, "results": hits, "label": "HEURISTIC",
|
| 707 |
+
"formula": "score(m)=a_rec*g^dt + a_imp*imp(m) + a_rel*cos(q,m)",
|
| 708 |
+
"source": "Generative Agents (arXiv 2304.03442)"})
|
| 709 |
+
|
| 710 |
+
async def _mem_tiers(request):
|
| 711 |
+
run_id = request.query_params.get("run_id", "")
|
| 712 |
+
with _LOCK, _conn() as c:
|
| 713 |
+
wq = ("SELECT tier,COUNT(*) AS n FROM memory" +
|
| 714 |
+
(" WHERE run_id=?" if run_id else "") + " GROUP BY tier")
|
| 715 |
+
rows = c.execute(wq, ((run_id,) if run_id else ())).fetchall()
|
| 716 |
+
tiers = {r["tier"]: r["n"] for r in rows}
|
| 717 |
+
return JSONResponse({"run_id": run_id or None, "tiers": tiers,
|
| 718 |
+
"working": _working_snapshot(run_id) if run_id else [],
|
| 719 |
+
"working_cap": _WORKING_CAP,
|
| 720 |
+
"design": "Letta/MemGPT (arXiv 2310.08560): working "
|
| 721 |
+
"(in-context) + archival (vector); self-managed.",
|
| 722 |
+
"label": "EXPERIMENTAL"})
|
| 723 |
+
|
| 724 |
+
async def _skill_admit_ep(request):
|
| 725 |
+
d = await _read_json(request)
|
| 726 |
+
name = (d.get("name") or "").strip()
|
| 727 |
+
recipe = (d.get("recipe") or "").strip()
|
| 728 |
+
run_id = (d.get("run_id") or "").strip()
|
| 729 |
+
if not name or not recipe:
|
| 730 |
+
return JSONResponse({"error": "need name + recipe"}, status_code=400)
|
| 731 |
+
# Voyager admission: require a VERIFIED execution receipt. We accept a
|
| 732 |
+
# run_id and verify its final emit receipt; OR a direct envelope.
|
| 733 |
+
verified, rhash = False, ""
|
| 734 |
+
if run_id:
|
| 735 |
+
tr = eng.trace(run_id)
|
| 736 |
+
recs = tr.get("receipts", [])
|
| 737 |
+
if recs:
|
| 738 |
+
last = recs[-1]
|
| 739 |
+
rhash = last["hash"]
|
| 740 |
+
verified = bool(last.get("signature_valid")) and tr.get("chain_intact")
|
| 741 |
+
elif d.get("envelope") and verify_fn is not None:
|
| 742 |
+
try:
|
| 743 |
+
verified = bool(verify_fn(d["envelope"]).get("signature_valid"))
|
| 744 |
+
rhash = _sha(d["envelope"])[:32]
|
| 745 |
+
except Exception:
|
| 746 |
+
verified = False
|
| 747 |
+
res = _skill_admit(name, recipe, rhash, verified)
|
| 748 |
+
res["label"] = "LIVE" if res.get("admitted") else "EXPERIMENTAL"
|
| 749 |
+
return JSONResponse(res, status_code=200 if res.get("admitted") else 422)
|
| 750 |
+
|
| 751 |
+
async def _skill_list(request):
|
| 752 |
+
q = request.query_params.get("q", "")
|
| 753 |
+
return JSONResponse({"query": q or None,
|
| 754 |
+
"skills": _skill_search(q or "skill", top_k=50),
|
| 755 |
+
"admission_rule": "Voyager (arXiv 2305.16291): admit a "
|
| 756 |
+
"recipe ONLY after a verified execution receipt.",
|
| 757 |
+
"label": "LIVE"})
|
| 758 |
+
|
| 759 |
+
async def _diag(request):
|
| 760 |
+
with _LOCK, _conn() as c:
|
| 761 |
+
nr = c.execute("SELECT COUNT(*) AS n FROM runs").fetchone()["n"]
|
| 762 |
+
nm = c.execute("SELECT COUNT(*) AS n FROM memory").fetchone()["n"]
|
| 763 |
+
ns_ = c.execute("SELECT COUNT(*) AS n FROM skills").fetchone()["n"]
|
| 764 |
+
return JSONResponse({
|
| 765 |
+
"module": "a11oy_react_core", "status": "ok",
|
| 766 |
+
"db": _DB_PATH, "runs": nr, "memories": nm, "skills": ns_,
|
| 767 |
+
"signer": signer_label,
|
| 768 |
+
"pubkey_present": bool((pub_pem_fn() if pub_pem_fn else "")),
|
| 769 |
+
"subsystems": ["ReAct graph (2210.03629)", "SqliteSaver checkpointing",
|
| 770 |
+
"Reflexion (2303.11366)", "Generative-Agents memory (2304.03442)",
|
| 771 |
+
"Letta tiering (2310.08560)", "Voyager skill library (2305.16291)"],
|
| 772 |
+
"label": "EXPERIMENTAL"})
|
| 773 |
+
|
| 774 |
+
base = "/api/%s/v1/agent/react" % ns
|
| 775 |
+
routes = [
|
| 776 |
+
Route(base + "/run", _run, methods=["POST"], name="%s_react_run" % ns),
|
| 777 |
+
Route(base + "/resume", _resume, methods=["POST"], name="%s_react_resume" % ns),
|
| 778 |
+
Route(base + "/trace/{run_id}", _trace, methods=["GET"], name="%s_react_trace" % ns),
|
| 779 |
+
Route(base + "/trace", _trace, methods=["GET"], name="%s_react_trace_q" % ns),
|
| 780 |
+
Route(base + "/checkpoints/{run_id}", _checkpoints, methods=["GET"],
|
| 781 |
+
name="%s_react_cps" % ns),
|
| 782 |
+
Route(base + "/checkpoints", _checkpoints, methods=["GET"], name="%s_react_cps_q" % ns),
|
| 783 |
+
Route(base + "/reflect", _reflect, methods=["POST"], name="%s_react_reflect" % ns),
|
| 784 |
+
Route(base + "/memory/add", _mem_add_ep, methods=["POST"], name="%s_react_mem_add" % ns),
|
| 785 |
+
Route(base + "/memory/search", _mem_search_ep, methods=["POST"],
|
| 786 |
+
name="%s_react_mem_search" % ns),
|
| 787 |
+
Route(base + "/memory/tiers", _mem_tiers, methods=["GET"], name="%s_react_mem_tiers" % ns),
|
| 788 |
+
Route(base + "/skills/admit", _skill_admit_ep, methods=["POST"],
|
| 789 |
+
name="%s_react_skill_admit" % ns),
|
| 790 |
+
Route(base + "/skills", _skill_list, methods=["GET"], name="%s_react_skills" % ns),
|
| 791 |
+
Route(base + "/_diag", _diag, methods=["GET"], name="%s_react_diag" % ns),
|
| 792 |
+
# Free top-level conveniences requested by the spec (not taken elsewhere):
|
| 793 |
+
Route("/api/%s/v1/agent/resume" % ns, _resume, methods=["POST"],
|
| 794 |
+
name="%s_agent_resume_top" % ns),
|
| 795 |
+
Route("/api/%s/v1/agent/trace/{run_id}" % ns, _trace, methods=["GET"],
|
| 796 |
+
name="%s_agent_trace_top" % ns),
|
| 797 |
+
Route("/api/%s/v1/agent/checkpoints/{run_id}" % ns, _checkpoints, methods=["GET"],
|
| 798 |
+
name="%s_agent_cps_top" % ns),
|
| 799 |
+
Route("/api/%s/v1/agent/checkpoints" % ns, _checkpoints, methods=["GET"],
|
| 800 |
+
name="%s_agent_cps_top_q" % ns),
|
| 801 |
+
]
|
| 802 |
+
for r in routes:
|
| 803 |
+
app.router.routes.insert(0, r)
|
| 804 |
+
return {"module": "a11oy_react_core", "routes": len(routes), "base": base,
|
| 805 |
+
"signer": signer_label}
|
serve.py
CHANGED
|
@@ -7667,6 +7667,46 @@ except Exception as _loop_e:
|
|
| 7667 |
# ============================================================================
|
| 7668 |
|
| 7669 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7670 |
# ============================================================================
|
| 7671 |
# BEGIN: FORMULA-WIRING SURFACE — a11oy (2026-06-06, ADDITIVE, surgical)
|
| 7672 |
# Wires ALL ~80 kernel-verified theorems to REAL, executed mechanisms (shared,
|
|
|
|
| 7667 |
# ============================================================================
|
| 7668 |
|
| 7669 |
|
| 7670 |
+
# ============================================================================
|
| 7671 |
+
# BEGIN: LANE A AGENTIC CORE — a11oy (2026-06-14, Dev A, ADDITIVE, surgical)
|
| 7672 |
+
# Resumable ReAct execution graph (Thought->Action->Observation) where EACH
|
| 7673 |
+
# node transition is a SIGNED receipt boundary, with SqliteSaver-style
|
| 7674 |
+
# checkpointing (crash mid-run -> /resume continues), a Reflexion inner loop,
|
| 7675 |
+
# Generative-Agents memory retrieval scoring over a LOCAL vector store (0 CDN),
|
| 7676 |
+
# Letta-style working/archival tiering, and a Voyager skill library that admits
|
| 7677 |
+
# a tool-recipe ONLY after a verified execution receipt.
|
| 7678 |
+
# REUSES the host's REAL in-image signer (_a11oy_sign_receipt), verifier
|
| 7679 |
+
# (_a11oy_loop_verify) and public key (_a11oy_loop_pubpem). Routes inserted at
|
| 7680 |
+
# position 0 (Starlette Route) so they beat the SPA catch-all. FREE sub-namespace
|
| 7681 |
+
# /api/a11oy/v1/agent/react/* — no collision with /run, /tools, /verify-chain,
|
| 7682 |
+
# /governance-standards, /_diag, /loop. try/except-guarded (non-fatal).
|
| 7683 |
+
# Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
|
| 7684 |
+
# Co-Authored-By: Perplexity Computer Agent <agent@perplexity.ai>
|
| 7685 |
+
# ============================================================================
|
| 7686 |
+
try:
|
| 7687 |
+
import a11oy_react_core as _react_core
|
| 7688 |
+
import sys as _react_sys
|
| 7689 |
+
_react_status = _react_core.register(
|
| 7690 |
+
app, "a11oy",
|
| 7691 |
+
sign_fn=_a11oy_sign_receipt,
|
| 7692 |
+
verify_fn=(_a11oy_loop_verify if "_a11oy_loop_verify" in dir() else None),
|
| 7693 |
+
pub_pem_fn=(_a11oy_loop_pubpem if "_a11oy_loop_pubpem" in dir() else None),
|
| 7694 |
+
signer_label=("in-image ephemeral ECDSA-P256 (signed at server boot, "
|
| 7695 |
+
"resets on rebuild, verifiable vs /cosign.pub)"),
|
| 7696 |
+
)
|
| 7697 |
+
print(f"[a11oy] LANE A agentic core registered: {_react_status}", file=_react_sys.stderr)
|
| 7698 |
+
_REACT_DIAG = {"status": "ok", "registered": _react_status}
|
| 7699 |
+
except Exception as _react_e:
|
| 7700 |
+
import sys as _react_sys, traceback as _react_tb
|
| 7701 |
+
print(f"[a11oy] LANE A agentic core FAILED (non-fatal): {_react_e!r}", file=_react_sys.stderr)
|
| 7702 |
+
_react_tb.print_exc(file=_react_sys.stderr)
|
| 7703 |
+
_REACT_DIAG = {"status": "FAILED", "error": repr(_react_e),
|
| 7704 |
+
"traceback": _react_tb.format_exc()}
|
| 7705 |
+
# ============================================================================
|
| 7706 |
+
# END: LANE A AGENTIC CORE — a11oy
|
| 7707 |
+
# ============================================================================
|
| 7708 |
+
|
| 7709 |
+
|
| 7710 |
# ============================================================================
|
| 7711 |
# BEGIN: FORMULA-WIRING SURFACE — a11oy (2026-06-06, ADDITIVE, surgical)
|
| 7712 |
# Wires ALL ~80 kernel-verified theorems to REAL, executed mechanisms (shared,
|