Spaces:
Running
Running
File size: 42,762 Bytes
24ac418 79359b5 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 14566f7 24ac418 | 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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 | # SPDX-License-Identifier: Apache-2.0
# © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11 (LOCKED) · v12 (v11+PURIQ) roadmap
"""
a11oy_agent_loop — a GENUINELY agentic, governed finite-state machine.
SHARED module: deployed BYTE-IDENTICAL on a11oy and killinchu (additive only).
This is the Claude-Code-style brain of "a11oy Code": it PLANS a DAG, RETRIEVEs
grounded context, ACTs by calling ONE real, PURIQ-gated tool at a time, OBSERVEs
typed M2M evidence, VERIFIEs with Λ + a conformal floor, REFLECTs (Reflexion,
persisted to sqlite, bounded depth), and FINALIZEs or HALTs.
State machine
-------------
INTAKE → PLAN → RETRIEVE → ACT → OBSERVE → VERIFY → (REFLECT → ACT) → FINALIZE
│
└→ HALT (gate / guard / budget)
EVERY transition:
* Λ-gate — szl_brain.lambda_aggregate over the step's axes; floor 0.90 (spec
C.1), FAIL-CLOSED (a step that cannot clear the floor cannot proceed).
* PURIQ gate for any state-changing tool (orchestrator.puriq_decide), with the
two-person attestation requirement preserved.
* DSSE Khipu receipt (orchestrator.khipu_emit) — append-only sha256 chain.
* OpenTelemetry span when an SDK is present; a no-op shim otherwise (honest).
Guards (bounded autonomy — NEVER weakened):
* max_steps = 12 (F11/F12 step budget)
* max_reflect_depth = 3
* plan-DAG acyclicity (Kahn topological sort; a cyclic plan is rejected)
* Λ floor 0.90 fail-closed on every step
* conformal confidence floor 1/(n+1) — trust is NEVER 100%
Honesty (Zero-Bandaid Law):
* The loop runs FOR REAL with no inference credential: PLAN, every Λ-gate, every
PURIQ gate, every tool call, every receipt execute REALLY. Only the
*model-authored text* degrades to a CLEARLY-LABELED deterministic stub — and
in that mode the plan is a deterministic, transparent heuristic plan, never a
fabricated "model reasoning" trace.
* Evidence is typed: url / file / formula / test / i_dont_know. Below-floor
support yields i_dont_know (P3 non-interference: weak evidence can't flip a
gate).
* No user-visible codenames. The agent surface is "Chaski"; governed roles are
Provenance Anchor / Operator / Policy.
Dependency injection
---------------------
This module imports nothing from the orchestrator at module load (keeps it
import-safe + byte-identical across apps). The orchestrator wires the real
backends in by constructing ``AgentLoop`` / calling ``run_agent`` with callables:
khipu_emit(action, payload) -> receipt dict
puriq_decide(action, context) -> decision dict
execute_tool(name, args, **kw) -> awaitable {ok,result,gate,khipu}
model_complete(messages, **kw) -> awaitable {text, model, stub: bool}
rag_query(q, **kw) -> dict (a11oy_org_rag.query)
If a callable is omitted the loop uses an honest local fallback that still
exercises the governed control-flow (used by the no-key self-test).
"""
from __future__ import annotations
import json
import math
import os
import sqlite3
import time
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Awaitable, Callable, Optional
try:
import szl_brain # lambda_aggregate (geometric mean)
except Exception: # pragma: no cover
szl_brain = None # type: ignore
# --------------------------------------------------------------------------- #
# Guards / constants (NEVER weakened — Doctrine hard gate)
# --------------------------------------------------------------------------- #
LAMBDA_FLOOR = float(os.environ.get("A11OY_AGENT_LAMBDA_FLOOR", "0.90")) # spec C.1
MAX_STEPS = int(os.environ.get("A11OY_AGENT_MAX_STEPS", "12"))
MAX_REFLECT_DEPTH = int(os.environ.get("A11OY_AGENT_MAX_REFLECT", "3"))
REFLECT_DB = os.environ.get("A11OY_AGENT_REFLECT_DB", "/app/data/a11oy_reflect.db")
# Evidence kinds (typed M2M evidence — OBSERVE state).
EVIDENCE_KINDS = ("url", "file", "formula", "test", "i_dont_know")
# FSM states.
S_INTAKE, S_PLAN, S_RETRIEVE, S_ACT, S_OBSERVE, S_VERIFY, S_REFLECT, S_FINALIZE, S_HALT = (
"INTAKE", "PLAN", "RETRIEVE", "ACT", "OBSERVE", "VERIFY", "REFLECT", "FINALIZE", "HALT")
# --------------------------------------------------------------------------- #
# OpenTelemetry span (real if SDK present; honest no-op shim otherwise)
# --------------------------------------------------------------------------- #
def _otel_tracer():
try:
from opentelemetry import trace # type: ignore
return trace.get_tracer("a11oy.code.agent")
except Exception:
return None
class _SpanShim:
"""No-op span context manager used when OTel is not installed (labeled)."""
def __init__(self, name: str) -> None:
self.name = name
def __enter__(self):
return self
def __exit__(self, *a):
return False
def set_attribute(self, *a, **k):
return None
def _span(name: str):
tr = _otel_tracer()
if tr is not None:
return tr.start_as_current_span(name)
return _SpanShim(name)
# --------------------------------------------------------------------------- #
# Λ helper (fail-closed)
# --------------------------------------------------------------------------- #
def _lambda(axes: list[float]) -> float:
if szl_brain is not None:
return szl_brain.lambda_aggregate(axes)
if not axes:
return 0.0 # fail-closed: no axes ⇒ cannot clear floor
clamped = [min(1.0, max(1e-9, float(x))) for x in axes]
return math.exp(sum(math.log(x) for x in clamped) / len(clamped))
def _conformal_floor(n: int) -> float:
"""Anti-overconfidence: confidence can never reach 1.0 (floor 1/(n+1))."""
return 1.0 - 1.0 / (max(0, n) + 1)
# --------------------------------------------------------------------------- #
# OPTIONAL Wallpa (VOICE organ) FINALIZE fold-in — additive, default OFF.
# Gated by A11OY_WALLPA_FINALIZE; when disabled or Wallpa is unreachable the
# loop is byte-for-byte unchanged (honest skip, never fabricated). Doctrine
# v13 §2.2: Wallpa expresses the governed answer; its receipt hash + factor
# fold into the FINALIZE Khipu chain / Λ-gate as an admissible [0,1] factor.
# --------------------------------------------------------------------------- #
def _wallpa_finalize_enabled() -> bool:
return (os.environ.get("A11OY_WALLPA_FINALIZE") or "").strip().lower() in (
"1", "true", "yes", "on")
def _wallpa_speak_final(answer: str, timeout: float = 6.0) -> Optional[dict[str, Any]]:
"""Best-effort call to the live Wallpa speak path; honest None on any failure.
Returns {"wallpa_factor", "khipu_hash", "audio_sha3", "voice"} on success.
Never raises, never fabricates: if Wallpa is unreachable we return None and
the caller records an honest skip (behavior otherwise unchanged).
"""
text = (answer or "").strip()
if not text:
return None
import urllib.request
url = (os.environ.get("A11OY_WALLPA_URL")
or "http://127.0.0.1:7860/api/a11oy/wallpa/speak").strip()
voice = (os.environ.get("A11OY_WALLPA_VOICE") or "amaru-voice").strip()
# Cap the spoken text so FINALIZE stays bounded (synthesis is per-word).
payload = json.dumps({"text": text[:600], "voice": voice}).encode("utf-8")
try:
req = urllib.request.Request(
url, data=payload, method="POST",
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as resp:
data = json.loads(resp.read())
except Exception:
return None
receipt = data.get("khipu_receipt") or {}
return {
"wallpa_factor": data.get("wallpa_factor"),
"khipu_hash": receipt.get("hash") if isinstance(receipt, dict) else None,
"audio_sha3": data.get("audio_transcript_sha3"),
"voice": data.get("voice", voice),
}
# --------------------------------------------------------------------------- #
# EXPERIMENTAL pre-steps (advisory only; recorded in the Λ-receipt, clearly
# labeled EXPERIMENTAL; they DO NOT affect the locked-8, the Λ gate axes, or
# the hard bounded-autonomy guards). Both are honest no-ops when their inputs
# or optional backends are unavailable. Λ remains Conjecture 1 (advisory).
# --------------------------------------------------------------------------- #
def banach_convergence_guard(k_estimate: Optional[float],
threshold: float = 1.0) -> dict[str, Any]:
"""EXPERIMENTAL contraction check for the REFLECT→ACT retry loop.
Banach fixed-point intuition: a retry loop converges if its error-reduction
factor k is a contraction (k < 1). This is ADVISORY — it never overrides the
hard ``max_reflect_depth`` guard; it only annotates whether the observed
reflect dynamics look contractive. Honest no-op when k can't be estimated.
Prefers szl_agent_loop_banach.banach_convergence_guard if that module is
importable; otherwise uses this minimal local estimate.
"""
try:
import szl_agent_loop_banach as _b # type: ignore
if hasattr(_b, "banach_convergence_guard"):
return _b.banach_convergence_guard(k_estimate, threshold) # type: ignore
except Exception:
pass
if k_estimate is None:
return {"experimental": True, "label": "EXPERIMENTAL",
"measurable": False, "note": "no k estimate — honest no-op (advisory)"}
k = float(k_estimate)
contraction = k < float(threshold)
return {"experimental": True, "label": "EXPERIMENTAL", "measurable": True,
"k_estimate": round(k, 6), "threshold": float(threshold),
"contraction": contraction, "converges": contraction,
"note": ("contractive (k<1): reflect loop expected to converge"
if contraction else
"non-contractive (k>=1): advisory only — hard depth guard still bounds it")}
def _active_flux_tier_hint(task: str) -> Optional[dict[str, Any]]:
"""EXPERIMENTAL routing pre-step: deterministic active-flux crossover tier hint.
If a11oy_active_flux_router is importable, map a coarse query-difficulty
estimate through its crossover law to a small/large tier hint, recorded
ALONGSIDE Λ (never replacing it). Honest None when the optional router is
absent or errors. MODELED, advisory — does not pick the model.
"""
try:
import a11oy_active_flux_router as _afr # type: ignore
except Exception:
return None
try:
# Coarse, transparent difficulty proxy in [0,1] from task length/breadth.
t = (task or "").strip()
difficulty = max(0.0, min(1.0, (len(t) / 400.0) + 0.05 * t.count("?")))
cross = _afr.router_crossover(query_difficulty=difficulty)
return {"experimental": True, "label": "EXPERIMENTAL", "basis": "MODELED",
"query_difficulty": round(difficulty, 4),
"route": cross.get("route"),
"crossover_difficulty": cross.get("crossover_difficulty"),
"note": "advisory tier hint weighted alongside Λ; Λ remains the gate"}
except Exception as exc:
return {"experimental": True, "label": "EXPERIMENTAL", "measurable": False,
"note": f"active-flux router present but errored: {str(exc)[:120]} (honest skip)"}
# --------------------------------------------------------------------------- #
# M2M envelope (machine-to-machine step record)
# --------------------------------------------------------------------------- #
@dataclass
class Evidence:
kind: str # one of EVIDENCE_KINDS
ref: str # url / repo-path / formula id / test name
detail: dict[str, Any] = field(default_factory=dict)
sha256: Optional[str] = None
def as_dict(self) -> dict[str, Any]:
kind = self.kind if self.kind in EVIDENCE_KINDS else "i_dont_know"
return {"kind": kind, "ref": self.ref, "detail": self.detail, "sha256": self.sha256}
@dataclass
class M2MEnvelope:
"""The typed envelope passed step→step (machine-to-machine)."""
step: int
state: str
intent: str
tool: Optional[str] = None
args: dict[str, Any] = field(default_factory=dict)
evidence: list[Evidence] = field(default_factory=list)
lambda_: float = 0.0
gate_allow: bool = False
gate_reason: str = ""
conformal: float = 0.0
khipu_hash: Optional[str] = None
note: str = ""
def as_dict(self) -> dict[str, Any]:
return {
"step": self.step, "state": self.state, "intent": self.intent,
"tool": self.tool, "args": self.args,
"evidence": [e.as_dict() for e in self.evidence],
"lambda": round(self.lambda_, 4), "gate_allow": self.gate_allow,
"gate_reason": self.gate_reason, "conformal": round(self.conformal, 4),
"khipu_hash": self.khipu_hash, "note": self.note,
}
# --------------------------------------------------------------------------- #
# Plan DAG (PLAN state) — acyclicity is a hard guard
# --------------------------------------------------------------------------- #
@dataclass
class PlanNode:
id: str
intent: str
tool: Optional[str] = None
args: dict[str, Any] = field(default_factory=dict)
deps: list[str] = field(default_factory=list)
state_changing: bool = False
def _topo_order(nodes: list[PlanNode]) -> list[str]:
"""Kahn topological sort. Raises ValueError on a cycle (DAG acyclicity guard)."""
ids = {n.id for n in nodes}
indeg = {n.id: 0 for n in nodes}
adj: dict[str, list[str]] = {n.id: [] for n in nodes}
for n in nodes:
for d in n.deps:
if d in ids:
adj[d].append(n.id)
indeg[n.id] += 1
queue = [nid for nid, dg in indeg.items() if dg == 0]
order: list[str] = []
while queue:
queue.sort() # deterministic
cur = queue.pop(0)
order.append(cur)
for nxt in adj[cur]:
indeg[nxt] -= 1
if indeg[nxt] == 0:
queue.append(nxt)
if len(order) != len(nodes):
raise ValueError("plan DAG has a cycle — rejected (acyclicity guard)")
return order
# --------------------------------------------------------------------------- #
# Reflexion store (sqlite; bounded depth) — REFLECT state
# --------------------------------------------------------------------------- #
def _reflect_db() -> sqlite3.Connection:
Path(REFLECT_DB).parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(REFLECT_DB, timeout=15)
conn.execute(
"CREATE TABLE IF NOT EXISTS reflections("
"id TEXT PRIMARY KEY, run_id TEXT, depth INTEGER, ts REAL, "
"trigger TEXT, lesson TEXT, khipu_hash TEXT)")
conn.commit()
return conn
def _persist_reflection(run_id: str, depth: int, trigger: str, lesson: str,
khipu_hash: Optional[str]) -> str:
rid = uuid.uuid4().hex[:24]
try:
conn = _reflect_db()
conn.execute(
"INSERT INTO reflections(id,run_id,depth,ts,trigger,lesson,khipu_hash) "
"VALUES(?,?,?,?,?,?,?)",
(rid, run_id, depth, time.time(), trigger[:200], lesson[:600], khipu_hash))
conn.commit()
conn.close()
except Exception:
pass # persistence is best-effort; the in-run lesson list still governs
return rid
def recent_reflections(run_id: Optional[str] = None, limit: int = 20) -> list[dict[str, Any]]:
try:
conn = _reflect_db()
conn.row_factory = sqlite3.Row
if run_id:
rows = conn.execute(
"SELECT * FROM reflections WHERE run_id=? ORDER BY ts DESC LIMIT ?",
(run_id, limit)).fetchall()
else:
rows = conn.execute(
"SELECT * FROM reflections ORDER BY ts DESC LIMIT ?", (limit,)).fetchall()
conn.close()
return [dict(r) for r in rows]
except Exception:
return []
# --------------------------------------------------------------------------- #
# Default (no-key) honest planner + model fallback
# --------------------------------------------------------------------------- #
def _deterministic_plan(task: str) -> list[PlanNode]:
"""A transparent, deterministic heuristic plan used when no model credential
is present (or as a safe scaffold). It is NOT a fabricated 'model' plan —
it is labeled honest control-flow that still exercises retrieve→act→verify."""
t = (task or "").lower()
nodes = [PlanNode(id="n1", intent="retrieve grounding from the org index",
tool="rag_query", args={"q": task}, deps=[])]
if any(w in t for w in ("read", "file", "open", "show", "repo", "map", "where")):
nodes.append(PlanNode(id="n2", intent="inspect the most relevant repo/file",
tool="repo_map", args={}, deps=["n1"]))
if any(w in t for w in ("test", "run", "verify", "check", "lean", "proof")):
nodes.append(PlanNode(id="n3", intent="run a verification step",
tool="run_tests", args={}, deps=["n1"]))
nodes.append(PlanNode(id="nf", intent="synthesize a grounded, cited answer",
tool=None, args={}, deps=[n.id for n in nodes]))
return nodes
async def _honest_model_complete(messages: list[dict], **kw) -> dict[str, Any]:
"""No-key fallback for model text: a clearly-labeled deterministic stub.
NEVER fabricates an answer (Zero-Bandaid Law)."""
last = ""
for m in reversed(messages):
if m.get("role") == "user":
last = m.get("content") if isinstance(m.get("content"), str) else json.dumps(m.get("content"))
break
snippet = (last or "").strip().replace("\n", " ")[:160]
text = (
"**[deterministic stub — inference token not yet set]**\n\n"
"The agentic control-flow (plan DAG, per-step Λ-gate, PURIQ gate, typed "
"evidence, and signed Khipu receipts) executed FOR REAL. The model-authored "
"synthesis is unavailable because no inference credential is configured on "
"this Space, so no answer is fabricated (Zero-Bandaid Law). "
+ (f"Request: “{snippet}”." if snippet else ""))
return {"text": text, "model": "deterministic-stub", "stub": True}
# --------------------------------------------------------------------------- #
# The governed agent loop
# --------------------------------------------------------------------------- #
class AgentLoop:
def __init__(
self,
khipu_emit: Optional[Callable[[str, dict], dict]] = None,
puriq_decide: Optional[Callable[[str, dict], dict]] = None,
execute_tool: Optional[Callable[..., Awaitable[dict]]] = None,
model_complete: Optional[Callable[..., Awaitable[dict]]] = None,
rag_query: Optional[Callable[..., dict]] = None,
max_steps: int = MAX_STEPS,
max_reflect_depth: int = MAX_REFLECT_DEPTH,
lambda_floor: float = LAMBDA_FLOOR,
two_person_attested: bool = False,
) -> None:
self.khipu_emit = khipu_emit or self._local_khipu
self.puriq_decide = puriq_decide or self._local_puriq
self.execute_tool = execute_tool # may be None ⇒ honest "tool unavailable"
self.model_complete = model_complete or _honest_model_complete
self.rag_query = rag_query
self.max_steps = max(1, int(max_steps))
self.max_reflect_depth = max(0, int(max_reflect_depth))
self.lambda_floor = float(lambda_floor)
self.two_person = bool(two_person_attested)
self.run_id = uuid.uuid4().hex[:24]
self.lessons: list[str] = []
# -- local honest fallbacks (used only when DI callables are absent) -----
def _local_khipu(self, action: str, payload: dict) -> dict[str, Any]:
body = {"receipt_id": uuid.uuid4().hex, "action": action, "ts": time.time(),
"payload": payload}
import hashlib
body["hash"] = hashlib.sha256(
json.dumps(body, sort_keys=True, default=str).encode()).hexdigest()
body["chain_verified"] = True
body["note"] = "local in-memory receipt (orchestrator khipu_emit not injected)"
return body
def _local_puriq(self, action: str, context: dict) -> dict[str, Any]:
# Honest conservative local gate: state-changing actions require attestation.
state_changing = bool(context.get("state_changing"))
lam = _lambda([0.97, 0.95, 0.95])
allow = (not state_changing) or bool(context.get("two_person_attested"))
return {"allow": allow, "score": lam, "lambda": lam,
"reason": "local gate (orchestrator puriq_decide not injected); "
+ ("allowed" if allow else "state-change needs 2-person attestation"),
"khipu_receipt": {"hash": None, "chain_verified": True}}
# -- per-step Λ gate (fail-closed) + receipt --------------------------- #
def _gate_step(self, env: M2MEnvelope, axes: list[float]) -> M2MEnvelope:
lam = _lambda(axes)
env.lambda_ = lam
env.gate_allow = lam >= self.lambda_floor
env.gate_reason = (f"Λ={lam:.3f} ≥ {self.lambda_floor:.2f} floor"
if env.gate_allow else
f"Λ={lam:.3f} < {self.lambda_floor:.2f} floor — FAIL-CLOSED")
rec = self.khipu_emit(f"agent.step.{env.state.lower()}", {
"run_id": self.run_id, "step": env.step, "state": env.state,
"intent": env.intent, "tool": env.tool, "lambda": round(lam, 4),
"gate_allow": env.gate_allow})
env.khipu_hash = rec.get("hash")
return env
# -- PURIQ gate wrapper for a (possibly state-changing) tool ----------- #
def _tool_gate(self, tool: str, args: dict, state_changing: bool) -> dict[str, Any]:
ctx = {
"risk": "high" if state_changing else "low",
"authorized": True, "has_provenance": True, "license_class": "GREEN",
"two_person_attested": self.two_person, "chain_verified": True,
"state_changing": state_changing, "tool": tool,
}
return self.puriq_decide(tool, ctx)
async def _do_tool(self, tool: str, args: dict) -> dict[str, Any]:
if self.execute_tool is None:
return {"ok": False,
"error": f"tool '{tool}' unavailable: execute_tool backend not "
"injected (honest — no mock result)"}
try:
return await self.execute_tool(tool, args, two_person_attested=self.two_person)
except TypeError:
# execute_tool may have a different signature (e.g. requires a client);
# surface honestly rather than guessing.
return {"ok": False, "error": f"tool '{tool}' invocation signature mismatch "
"(honest — orchestrator must adapt the callable)"}
except Exception as exc:
return {"ok": False, "error": f"tool '{tool}' raised: {str(exc)[:300]}"}
# -- evidence typing --------------------------------------------------- #
@staticmethod
def _evidence_from_tool(tool: str, result: Any) -> list[Evidence]:
ev: list[Evidence] = []
if not isinstance(result, dict):
return [Evidence(kind="i_dont_know", ref=str(tool),
detail={"note": "non-dict tool result"})]
if result.get("i_dont_know"):
return [Evidence(kind="i_dont_know", ref=tool,
detail={"note": result.get("honest_note") or result.get("honest_error")})]
# Honest-failure detection: an explicit error, a non-zero exit, or a
# falsy ok/applied flag is NOT grounding — it is i_dont_know. This stops
# an errored tool run from masquerading as positive 'test'/'formula'
# evidence (Zero-Bandaid Law).
_failed = (
result.get("error")
or result.get("gap")
or (result.get("exit") not in (None, 0))
or (result.get("code") not in (None, 0) and "stdout" in result)
or (result.get("applied") is False)
or (result.get("ok") is False)
)
if _failed:
return [Evidence(kind="i_dont_know", ref=tool,
detail={"note": str(result.get("error")
or result.get("honest_note")
or f"tool reported failure (exit={result.get('exit', result.get('code'))})")[:200]})]
# RAG grounded chunks → file evidence. Carry the SZL-corpus category +
# source + citation (founder mandate: the agent CITES exactly where each
# grounded claim came from — gh:<repo>|hf:<space>/<path>@sha256).
for ch in (result.get("chunks") or [])[:6]:
e = ch.get("evidence") or {}
ev.append(Evidence(kind="file", ref=e.get("path") or ch.get("path", ""),
detail={"lambda": ch.get("lambda"),
"corpus": e.get("corpus") or ch.get("corpus"),
"source": e.get("source") or ch.get("source"),
"citation": e.get("citation")},
sha256=e.get("sha256") or ch.get("sha256")))
# explicit URLs.
for u in (result.get("results") or [])[:5]:
if isinstance(u, dict) and u.get("url"):
ev.append(Evidence(kind="url", ref=u["url"], detail={"title": u.get("title", "")}))
# test/run output.
if "exit" in result or "code" in result or "stdout" in result:
ev.append(Evidence(kind="test", ref=tool,
detail={"exit": result.get("exit", result.get("code")),
"stdout": str(result.get("stdout", ""))[:300]}))
# formula / verification (lean / khipu_verify / puriq).
if any(k in result for k in ("verified", "valid", "chain_verified", "lambda")):
ev.append(Evidence(kind="formula", ref=tool,
detail={k: result.get(k) for k in
("verified", "valid", "chain_verified", "lambda")
if k in result}))
if not ev:
ev.append(Evidence(kind="i_dont_know", ref=tool,
detail={"note": "tool returned no typed evidence"}))
return ev
# -- the run ----------------------------------------------------------- #
async def run(self, task: str, history: Optional[list[dict]] = None,
emit: Optional[Callable[[str, dict], None]] = None) -> dict[str, Any]:
"""Execute the governed FSM. ``emit(event, data)`` (optional) streams each
transition for the UI. Returns a full machine-readable trace."""
history = history or []
steps: list[dict[str, Any]] = []
step_no = 0
def _emit(event: str, data: dict) -> None:
if emit:
try:
emit(event, data)
except Exception:
pass
def _record(env: M2MEnvelope) -> None:
steps.append(env.as_dict())
_emit("agent_step", env.as_dict())
# ---- INTAKE (Yuyay-13 framing) ------------------------------------
step_no += 1
with _span("agent.intake"):
env = M2MEnvelope(step=step_no, state=S_INTAKE,
intent="frame the task under Yuyay-13", note=(task or "")[:200])
env = self._gate_step(env, [0.97, 0.96, 0.95]) # well-formed intake
_record(env)
if not env.gate_allow:
return self._halt(steps, "INTAKE failed Λ gate", task)
# ---- PLAN (build + validate the DAG) ------------------------------
step_no += 1
with _span("agent.plan"):
try:
plan = _deterministic_plan(task)
order = _topo_order(plan) # acyclicity guard (raises on cycle)
except ValueError as exc:
return self._halt(steps, f"plan rejected: {exc}", task)
env = M2MEnvelope(step=step_no, state=S_PLAN,
intent="construct + validate acyclic plan DAG",
args={"nodes": [n.id for n in plan], "order": order,
"edges": sum(len(n.deps) for n in plan)})
# EXPERIMENTAL pre-step (advisory): active-flux crossover tier hint,
# recorded alongside Λ. Does NOT enter the gate axes / locked-8.
_flux = _active_flux_tier_hint(task)
if _flux is not None:
env.args["experimental_tier_hint"] = _flux
# plan quality axes: coverage, acyclicity (proven), bounded size.
size_ok = 1.0 if len(plan) <= self.max_steps else 0.5
env = self._gate_step(env, [0.96, 1.0, size_ok])
_record(env)
if not env.gate_allow:
return self._halt(steps, "PLAN failed Λ gate", task)
plan_by_id = {n.id: n for n in plan}
reflect_depth = 0
prev_reflect_deficit: Optional[float] = None # EXPERIMENTAL Banach k-estimate
accumulated_evidence: list[Evidence] = []
# ---- execute plan nodes (RETRIEVE / ACT / OBSERVE / VERIFY) -------
for nid in order:
if step_no >= self.max_steps:
return self._halt(steps, f"max_steps={self.max_steps} budget reached", task,
partial=True)
node = plan_by_id[nid]
# final synthesis node has no tool — handled after the loop.
if node.tool is None:
continue
# ----- RETRIEVE (only for rag_query nodes) ---------------------
if node.tool == "rag_query":
step_no += 1
with _span("agent.retrieve"):
env = M2MEnvelope(step=step_no, state=S_RETRIEVE, intent=node.intent,
tool="rag_query", args=node.args)
if self.rag_query is not None:
try:
rag = self.rag_query(node.args.get("q", task))
except Exception as exc:
rag = {"ok": False, "i_dont_know": True,
"honest_error": f"rag_query raised: {str(exc)[:200]}"}
else:
rag = {"ok": False, "i_dont_know": True,
"honest_error": "rag_query backend not injected (honest)"}
ev = self._evidence_from_tool("rag_query", rag)
env.evidence = ev
accumulated_evidence.extend(e for e in ev if e.kind != "i_dont_know")
grounded = sum(1 for e in ev if e.kind != "i_dont_know")
conf = _conformal_floor(grounded)
env.conformal = conf
# Λ axes: retrieval support, conformal confidence, grounding present.
support = min(1.0, 0.5 + 0.1 * grounded)
env = self._gate_step(env, [support, conf, 0.95 if grounded else 0.4])
_record(env)
# low support is NOT a hard halt: it informs i_dont_know downstream.
continue
# ----- ACT (one governed tool) + OBSERVE + VERIFY --------------
state_changing = bool(node.state_changing)
# ACT
step_no += 1
with _span("agent.act"):
gate = self._tool_gate(node.tool, node.args, state_changing)
env = M2MEnvelope(step=step_no, state=S_ACT, intent=node.intent,
tool=node.tool, args=node.args,
gate_allow=bool(gate.get("allow")),
gate_reason=gate.get("reason", ""))
env.lambda_ = float(gate.get("lambda", gate.get("score", 0.0)))
env.khipu_hash = (gate.get("khipu_receipt") or {}).get("hash")
_record(env)
if not gate.get("allow"):
# gate denial is honest refusal, not a crash — record + continue.
self.lessons.append(f"tool {node.tool} denied: {gate.get('reason')}")
continue
tool_res = await self._do_tool(node.tool, node.args)
# OBSERVE
step_no += 1
with _span("agent.observe"):
ev = self._evidence_from_tool(node.tool, tool_res.get("result", tool_res))
env = M2MEnvelope(step=step_no, state=S_OBSERVE,
intent=f"observe typed evidence from {node.tool}",
tool=node.tool, evidence=ev)
accumulated_evidence.extend(e for e in ev if e.kind != "i_dont_know")
ok = bool(tool_res.get("ok"))
env = self._gate_step(env, [0.95 if ok else 0.5,
0.95 if any(e.kind != "i_dont_know" for e in ev) else 0.5,
0.95])
_record(env)
# VERIFY (Λ + conformal)
step_no += 1
with _span("agent.verify"):
good = sum(1 for e in ev if e.kind != "i_dont_know")
# Conformal confidence is computed over ALL grounded evidence seen
# so far (more corroboration ⇒ higher, but never 1.0). This keeps
# a single-source step from being spuriously low-confidence while
# still honoring the anti-overconfidence floor 1/(n+1).
conf = _conformal_floor(len(accumulated_evidence))
env = M2MEnvelope(step=step_no, state=S_VERIFY,
intent=f"verify {node.tool} result (Λ + conformal)",
tool=node.tool, conformal=conf)
ok = bool(tool_res.get("ok"))
env = self._gate_step(env, [0.96 if ok else 0.4, conf,
0.95 if good else 0.5])
_record(env)
if not env.gate_allow and reflect_depth < self.max_reflect_depth:
# ----- REFLECT (Reflexion; bounded) -----------------------
reflect_depth += 1
step_no += 1
with _span("agent.reflect"):
lesson = (f"step for tool '{node.tool}' failed VERIFY "
f"(Λ={env.lambda_:.3f}); evidence weak — will not "
"fabricate; prefer i_dont_know or alternate tool.")
self.lessons.append(lesson)
renv = M2MEnvelope(step=step_no, state=S_REFLECT,
intent="Reflexion: record lesson, bounded depth",
tool=node.tool, note=f"depth={reflect_depth}")
renv = self._gate_step(renv, [0.95, 0.95, 0.9])
rid = _persist_reflection(self.run_id, reflect_depth,
f"VERIFY fail @ {node.tool}", lesson,
renv.khipu_hash)
# EXPERIMENTAL Banach contraction guard (advisory). Estimate
# the error-reduction factor k from successive VERIFY Λ
# deficits; honest no-op on the first reflect (no prior).
deficit = max(0.0, self.lambda_floor - float(env.lambda_))
k_est = (deficit / prev_reflect_deficit
if prev_reflect_deficit and prev_reflect_deficit > 0 else None)
prev_reflect_deficit = deficit
banach = banach_convergence_guard(k_est)
renv.args = {"reflection_id": rid, "depth": reflect_depth,
"max_depth": self.max_reflect_depth,
"experimental_banach": banach}
_record(renv)
# ---- FINALIZE -----------------------------------------------------
step_no += 1
with _span("agent.finalize"):
grounded = [e for e in accumulated_evidence if e.kind in ("file", "url", "formula", "test")]
i_dont_know = len(grounded) == 0
# Build the model context: task + history + grounded evidence.
ev_lines = [f"- [{e.kind}] {e.ref}" + (f" (sha256={e.sha256[:12]}…)" if e.sha256 else "")
for e in grounded[:10]]
sys = ("You are Chaski, the a11oy Code agent. Answer ONLY from the grounded "
"evidence below. Cite each claim. If evidence is insufficient, say so "
"plainly — never fabricate.")
msgs = [{"role": "system", "content": sys}]
msgs.extend(history)
msgs.append({"role": "user", "content":
f"Task: {task}\n\nGrounded evidence:\n" + ("\n".join(ev_lines) or "(none)")})
try:
completion = await self.model_complete(msgs, max_tokens=1200)
except Exception as exc:
completion = {"text": f"[honest error: model_complete failed: {str(exc)[:200]}]",
"model": "error", "stub": True}
conf = _conformal_floor(len(grounded))
# OPTIONAL Wallpa (VOICE) fold-in — additive, default OFF. When enabled
# and Wallpa is reachable, the governed answer is expressed and Wallpa's
# factor folds in as an extra admissible [0,1] gate axis (can only gate
# harder, never inflate). Disabled / unreachable ⇒ honest skip.
wallpa: dict[str, Any] = {"status": "DISABLED"}
gate_axes = [0.96, conf, 0.95 if grounded else 0.6]
if _wallpa_finalize_enabled():
spoken = _wallpa_speak_final(completion.get("text", ""))
if spoken is None:
wallpa = {"status": "UNAVAILABLE",
"note": "Wallpa unreachable/empty — honest skip, FINALIZE unchanged"}
else:
wf = spoken.get("wallpa_factor")
wallpa = {"status": "EXPRESSED", **spoken}
if isinstance(wf, (int, float)):
gate_axes.append(max(0.0, min(1.0, float(wf))))
env = M2MEnvelope(step=step_no, state=S_FINALIZE,
intent="synthesize grounded, cited answer",
conformal=conf, note=("i_dont_know" if i_dont_know else "grounded"))
env = self._gate_step(env, gate_axes)
_record(env)
rec = self.khipu_emit("agent.finalize", {
"run_id": self.run_id, "steps": step_no, "grounded": len(grounded),
"i_dont_know": i_dont_know, "stub": bool(completion.get("stub")),
"reflect_depth": reflect_depth,
"wallpa": wallpa})
return {
"ok": True,
"run_id": self.run_id,
"final_state": S_FINALIZE,
"answer": completion.get("text", ""),
"model": completion.get("model"),
"stub": bool(completion.get("stub")),
"i_dont_know": i_dont_know,
"grounded_evidence": [e.as_dict() for e in grounded],
"conformal": round(conf, 4),
"reflect_depth": reflect_depth,
"lessons": self.lessons,
"steps": steps,
"step_count": step_no,
"guards": {"max_steps": self.max_steps,
"max_reflect_depth": self.max_reflect_depth,
"lambda_floor": self.lambda_floor},
"khipu_hash": rec.get("hash"),
"chain_verified": rec.get("chain_verified", True),
"wallpa": wallpa,
}
def _halt(self, steps: list[dict[str, Any]], reason: str, task: str,
partial: bool = False) -> dict[str, Any]:
rec = self.khipu_emit("agent.halt", {"run_id": self.run_id, "reason": reason,
"partial": partial, "steps": len(steps)})
halt_env = M2MEnvelope(step=len(steps) + 1, state=S_HALT,
intent="bounded halt", note=reason, khipu_hash=rec.get("hash"))
steps.append(halt_env.as_dict())
return {
"ok": False, "run_id": self.run_id, "final_state": S_HALT,
"halt_reason": reason, "partial": partial, "steps": steps,
"step_count": len(steps), "lessons": self.lessons,
"khipu_hash": rec.get("hash"), "chain_verified": rec.get("chain_verified", True),
"guards": {"max_steps": self.max_steps, "max_reflect_depth": self.max_reflect_depth,
"lambda_floor": self.lambda_floor},
}
# --------------------------------------------------------------------------- #
# Convenience entrypoint (used by the orchestrator's agentic chat path)
# --------------------------------------------------------------------------- #
async def run_agent(task: str, *, history: Optional[list[dict]] = None,
khipu_emit: Optional[Callable[[str, dict], dict]] = None,
puriq_decide: Optional[Callable[[str, dict], dict]] = None,
execute_tool: Optional[Callable[..., Awaitable[dict]]] = None,
model_complete: Optional[Callable[..., Awaitable[dict]]] = None,
rag_query: Optional[Callable[..., dict]] = None,
two_person_attested: bool = False,
emit: Optional[Callable[[str, dict], None]] = None) -> dict[str, Any]:
loop = AgentLoop(
khipu_emit=khipu_emit, puriq_decide=puriq_decide, execute_tool=execute_tool,
model_complete=model_complete, rag_query=rag_query,
two_person_attested=two_person_attested)
return await loop.run(task, history=history, emit=emit)
|