SP/1.0: deterministic, reproducible verdicts for AI-agent decisions Sovereign Logic released SP/1.0, the first open-source runtime circuit breaker for AI agents, on June 25, 2026. The protocol provides deterministic, cryptographically audited verdicts on tool execution, addressing documented production failures such as agent infinite loops that consumed $6,000+ in API costs. SHACKLE operates as a sidecar daemon or in-process library, enforcing 9 mathematical invariants and Ed25519-signed append-only audit logs. Version: 1.0.0 Status: Published Date: 2026-06-25 Authors: Dante Bullock, Sovereign Logic License: Creative Commons Attribution 4.0 International CC-BY 4.0 Reference Implementation: https://github.com/Fame510/SHACKLE https://github.com/Fame510/SHACKLE First Public Commit: 2026-06-17 23:12 UTC Implementations of this specification are subject to the SHACKLE license terms.The reference implementation is dual-licensed: AGPLv3 open source and Commercial proprietary . Contact: docspoc101@gmail.com SHACKLE is a runtime circuit breaker for autonomous AI agents. It answers one question: Should this agent be allowed to execute this tool with these parameters at this moment? The protocol defines a deterministic, verifiable decision function backed by 9 mathematical invariants, a language-agnostic message schema, Ed25519-signed append-only audit logging, and a Redis-backed distributed state engine. It operates as a sidecar daemon with gRPC/Unix socket transport, or as an in-process library for single-agent deployments. SHACKLE is the first open-source runtime circuit breaker for AI agents with cryptographic audit chain-of-custody. This specification is the definitive reference for the SP/1.0 protocol. Autonomous AI agents execute tools — web search, file I/O, API calls, code execution — with no runtime oversight. The framework's recursion limit or token cap is the only guardrail. When an agent enters a retry loop same tool, same error, burning tokens each time , there is no mechanism to detect, intercept, and stop it before the wallet is empty. This is not hypothetical. Production deployments have documented: - Agent infinite loops consuming $6,000+ in API costs before the recursion limit fired - Duplicate tool calls repeating 50+ times with no variation - Spawned child processes hanging indefinitely while consuming tokens The industry consensus — independently reached by multiple teams in June 2026 — is that generation authority is not release authority. The model generates candidates. A separate mediation layer must authorize execution. SHACKLE is that mediation layer. | Principle | Meaning | |---|---| Deterministic core | decide state, call → Verdict is a pure function. Same inputs always produce same outputs. | Daemon as authority | The SHACKLE daemon is the sole source of truth for time, state, and verdicts. Agents are untrusted. | Append-only audit | Every decision is Ed25519-signed and written to an immutable audit log. Chain-of-custody is cryptographically verifiable. | Mathematically verified | 9 invariant properties hold under all inputs, proven by property-based testing Hypothesis, 500+ examples each . | Graceful degradation | Agents function in local/library mode without a daemon. Distributed state is an upgrade path. | Fail-closed | Network failure, daemon crash, or timeout → DENY. No execution without explicit authorization. | This specification covers: - The decision function and its 9 mathematical invariants §3 - Message schemas and semantics §4 - State model §5 - Transport bindings §6 - Audit and security §7 - Compliance framework §8 This specification does NOT cover: - Daemon persistence layer implementation detail - HITL console UI presentation concern - Pricing or commercial terms MODEL A — Library Mode In-Process ┌─────────────────────────┐ │ Agent Process │ │ ┌───────────────────┐ │ │ │ @Guard decorator │ │ │ │ Local state only │ │ │ └───────────────────┘ │ └─────────────────────────┘ MODEL B — Sidecar Daemon Production ┌─────────────────┐ Unix/gRPC ┌──────────────────────────┐ │ Agent Process │ ◄────────────────► │ SHACKLE Daemon │ │ ┌───────────┐ │ pre exec │ ┌────────────────────┐ │ │ │ Thin │ │ post exec │ │ Policy Engine │ │ │ │ Client │ │ register │ │ - Budgets │ │ │ │ Shim │ │ heartbeat │ │ - Counters │ │ │ └───────────┘ │ │ │ - Circuit Breakers │ │ └─────────────────┘ │ └────────────────────┘ │ │ ┌────────────────────┐ │ │ │ Audit Log │ │ │ │ Ed25519-signed │ │ │ │ Append-only │ │ │ │ Chain-linked │ │ │ └────────────────────┘ │ └──────────────────────────┘ MODEL C — Distributed Enterprise ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Agent A │ │ Agent B │ │ Agent C │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────────────┬─────────────┘ │ gRPC/TLS ┌────────┴────────┐ │ SHACKLE │ │ Daemon Cluster │ │ Redis state │ │ Postgres logs │ └─────────────────┘ ┌──────────────────────────────────┐ │ Policy Language future │ ← DSL for guard rules ├──────────────────────────────────┤ │ Decision Function │ ← decide state, call → Verdict ├──────────────────────────────────┤ │ Message Protocol │ ← This specification ├──────────────────────────────────┤ │ Transport Unix/gRPC/WS │ ← Binding layer └──────────────────────────────────┘ The decision function is the heart of SHACKLE. It is a pure function — no I/O, no side effects, no allocations in the hot path. It is human-auditable in under 10 minutes. It is under 200 lines of logic. function decide state: SessionState, call: ToolCall, config: GuardConfig, rng float: float → Verdict Layer 1: Circuit Breaker IF state.circuit tripped: → DENY CIRCUIT OPEN Layer 2: Nonce Validation Anti-Replay IF call.nonce IN state.seen nonces: → DENY POLICY VIOLATION Layer 3: Budget Guard IF config.budget usd 0: IF state.budget remaining usd <= 0: → DENY BUDGET EXHAUSTED IF hitl mode == ON THRESHOLD AND fraction <= threshold: → HITL "budget threshold reached" IF call.cost state.budget remaining: IF hitl mode IN ON DENY, ALWAYS : → HITL "cost exceeds remaining budget" → DENY BUDGET EXHAUSTED Layer 4: Repeat Call Guard IF config.max repeat calls 0: IF is repeat call, state.last call : limit = max repeat calls IF error amplification AND has error signal call : limit = max 1, limit - 1 IF repeat count = limit: → DENY MAX REPEAT EXCEEDED Layer 5: Time Window Guard IF config.window max calls 0: IF window count = window max calls: → DENY WINDOW EXCEEDED Layer 6: Global Call Limit IF config.max total calls 0 AND total calls = max total calls: → DENY GLOBAL LIMIT Layer 7: Probabilistic Denial Adversarial Hardening IF probabilistic deny AND budget ratio < 0.2: IF rng < deny jitter ratio 1.0 - budget ratio : → DENY BUDGET EXHAUSTED, probabilistic=true Layer 8: HITL Always IF hitl mode == ALWAYS: → HITL "HITL required for all calls" Default: → ALLOW These 9 properties are verified by property-based testing Hypothesis, Python with 500+ randomly generated inputs each. Properties P1-P9 are provably correct. | | Property | Formal Statement | |---|---|---| P1 | Budget monotonically non-increasing | ∀ calls: budget after ≤ budget before | P2 | Repeat counts non-decreasing | ∀ tool: repeat count never decreases | P3 | Once tripped, always tripped | circuit tripped ⇒ all subsequent verdicts = DENY | P4 | Budget never negative | budget remaining ≥ 0 | P5 | Repeat limit triggers DENY | repeat count ≥ max repeat calls ⇒ verdict = DENY | P6 | Fresh state ALLOWs first call | fresh SessionState + any ToolCall ⇒ ALLOW | P7 | Deterministic output | identical state, call, config, rng ⇒ identical Decision | P8 | HITL ALWAYS produces HITL | hitl mode = ALWAYS ∧ ¬circuit tripped ⇒ verdict = HITL | P9 | Nonce uniqueness enforced | duplicate nonce ⇒ DENY | | Verdict | Meaning | Agent Action | |---|---|---| ALLOW | Execute as requested | Proceed with tool call | DENY | Block execution | Abort, surface deny reason | HITL | Human decision required | Pause, await human verdict via HITL console | | Reason | Trigger | |---|---| CIRCUIT OPEN | Circuit breaker was previously tripped | BUDGET EXHAUSTED | Budget remaining ≤ 0 or cost remaining | MAX REPEAT EXCEEDED | Same tool + same params repeated too many times | WINDOW EXCEEDED | Too many calls in the current time window | GLOBAL LIMIT | Session-wide call limit reached | POLICY VIOLATION | Duplicate nonce replay attack | AUTH FAILED | Authentication failure | SHACKLE detects error signals in tool parameters WITHOUT regex no ReDoS attack surface . When error amplification is enabled, the repeat call threshold is reduced by 1 if the parameters contain known error signals: Error signals: 401, unauthorized, 403, forbidden, 500, 502, 503, 504, timeout, connection refused, connection reset, permission denied, rate limit, quota exceeded, invalid api key, token expired, model not found, resource exhausted, deadline exceeded This catches the "loop of death" — agent hits 401, retries, gets 401, retries — without waiting for the full repeat threshold. When probabilistic deny is enabled and the agent is below 20% budget, a random factor is introduced: probability = deny jitter ratio × 1.0 − budget ratio IF rng < probability: → DENY appears as BUDGET EXHAUSTED This prevents adversarial agents from probing the exact budget threshold and gaming it. The denial appears identical to a real budget exhaustion, making it impossible to distinguish signal from noise. Every SHACKLE message is wrapped in a Protocol Buffers envelope: message Envelope { string protocol version = 1; // "1.0.0" string message id = 2; // UUIDv7, client-generated string correlation id = 3; // Request/response pairing int64 client timestamp ns = 4; // Client clock informational int64 server timestamp ns = 5; // Set by daemon on receipt bytes hmac = 6; // HMAC-SHA256 over payload oneof payload { PreExecRequest pre exec = 10; PreExecResponse pre exec response = 11; PostExecNotification post exec = 12; RegisterRequest register = 13; RegisterResponse register response = 14; Heartbeat heartbeat = 15; HeartbeatAck heartbeat ack = 16; Error error = 17; } } message RegisterRequest { string agent id = 1; string agent version = 2; string framework = 3; // "crewai" | "autogen" | "langgraph" string session id = 4; // Optional: resume existing session string organization id = 5; string runtime = 6; map