Adaptive trust governance for the agent control plane
Project description
fulcrum-trust
Adaptive trust governance for the agent control plane. Bayesian Beta(α,β) trust evaluation, circuit breaking, and trust-aware routing for AI agent harnesses.
What is this?
fulcrum-trust implements a Beta distribution trust model for agent-to-agent relationships. When an agent pair's trust score drops below a configurable threshold, the circuit breaks — terminating the interaction before runaway loops occur.
The math: every interaction updates a Beta(α, β) distribution. Trust score = α / (α + β). Starting at (1.0, 1.0) gives an uninformative prior of 0.5. Scores decay exponentially toward 0.5 over time (stale relationships revert to uncertainty).
Bounded detection latency (alpha_max)
By default α grows without bound, so a pair with a long clean history buys a
proportionally long runway of tolerated failures before the circuit opens.
Setting TrustConfig(alpha_max=K) clamps α at K after every update,
converting that runway into a constant: with threshold θ = p/q, the circuit
opens within ceil(α_max·(q−p)/p) consecutive failures regardless of prior
history (raw model; θ = 0.3 ⇒ ceil(α_max·7/3), e.g. α_max=20 ⇒ ≤ 47).
from fulcrum_trust import TrustManager, TrustConfig
tm = TrustManager(config=TrustConfig(alpha_max=20.0)) # detection ≤ 47 failures
The knob is a tradeoff surface: smaller α_max → tighter detection bound but
coarser trust resolution. Pick per deployment — there is no hardcoded value,
and alpha_max=None (the default) preserves the original unbounded behavior
exactly. Validation requires alpha_max >= alpha_prior > 0;
alpha_max == alpha_prior is a legal boundary that freezes success accrual
entirely — degenerate in practice.
Recovery under the cap. Bounded detection has a flip side: once the
circuit opens with β well past the cap (β > α_max·(q−p)/p), successes
alone can never re-cross θ — the score is pinned at α_max/(α_max+β)
because α cannot grow. Recovery flows through time decay (both parameters
contract toward the uninformative prior, restoring recoverability within a
fraction of a half-life), an explicit reset(), or operator action. This
asymmetry is intrinsic to capping success evidence; pair the cap with
recovery_cooldown_seconds for a governed re-entry probe once decay lifts
the score back above threshold.
Claims scope. The engine knob is Implemented (tested in
tests/test_evaluator.py::TestAlphaMaxCap). The worst-case bound itself is
Proved only for the discrete capped model in Lean — D4 Theorem 3.9
(capped_prior_strict_responsiveness), published in "A Bounded,
Machine-Checkable Governance Kernel for Trust-Gated Agent Execution"
(DOI 10.5281/zenodo.19900714), which proves threshold crossing within
q·(α_max+1) for the Laplace (α+1)/(α+β+2) estimator over Nat. The
deployed Python estimator is raw α/(α+β) over float; the two models agree
at the prior and diverge with counts, so their constants differ.
CORRESPONDENCE (carried verbatim from the sprint spec — mandatory). The Lean witness
q·(α+1)is sufficient, not minimal. The deployed Python raw estimator has a tighter minimal bound (β > α(q−p)/p). Document both constants and which model each belongs to. For θ=0.3 and α_max=20: Lean sufficient boundq(α_max+1)=210; raw-model minimal≈47. Do not present the Lean constant as the operational detection latency without this note.
Circuit state machine & recovery
Every agent pair carries a circuit in one of four states. evaluate() drives
transitions from the trust score; terminate() is the out-of-band kill switch.
| State | Meaning | IPC (CircuitState) |
|---|---|---|
CLOSED |
trust ≥ threshold — traffic flows | TRUSTED (0) |
OPEN |
trust < threshold — pair is isolated | ISOLATED (2) |
HALF_OPEN |
recovery probe admitted — one evaluation decides | EVALUATING (1) |
TERMINATED |
administratively killed — sticky, bypasses trust math | TERMINATED (3) |
Recovery from OPEN has two regimes, selected by
TrustConfig.recovery_cooldown_seconds.
Default (recovery_cooldown_seconds=None) — direct edge. The circuit closes
the moment an evaluation brings trust back to threshold θ; behavior is
unchanged from earlier releases.
CLOSED --(trust < θ)--> OPEN --(trust ≥ θ)--> CLOSED
Cooldown-gated (recovery_cooldown_seconds set) — observable probe. The pair
holds OPEN until the cooldown elapses since it opened, then the next evaluation
is admitted as a HALF_OPEN probe that resolves in a single step. Admission is
time-gated only — there is no direct OPEN → CLOSED edge in this regime.
CLOSED --(trust < θ)--------> OPEN # circuit opens (stamps opened_at)
OPEN --(cooldown elapsed)-> HALF_OPEN # probe admitted (time-gated)
HALF_OPEN --(trust ≥ θ)--------> CLOSED # probe succeeds → recovered
HALF_OPEN --(trust < θ)--------> OPEN # probe fails → cooldown restarts
The OPEN → HALF_OPEN edge publishes EVALUATING over the IPC bridge. Under a
capped prior (alpha_max), a pair whose β is deep past the cap cannot re-cross
θ on successes alone (see Recovery under the cap above) — the cooldown probe
becomes recoverable only once time decay lifts the score back over θ.
TERMINATED is reachable from any state via terminate() and never recovers
without an explicit reset().
Claims scope. The state machine is Implemented and Tested
(tests/test_manager_recovery_cooldown.py,
tests/test_manager_circuit_persistence.py). Its four-state transition relation
corresponds to the ValidTransition predicate formalized in
Fulcrum-Proofs at the
v0.2.0 tag (regime-conditioned docstring) — a correspondence with the Lean
artifact, not a new proof.
Quick Start
from fulcrum_trust import TrustManager, TrustOutcome
# Default: in-memory store, threshold=0.3, 24-hour decay half-life
tm = TrustManager()
# Record interaction outcomes
tm.evaluate("orchestrator", "code-agent", TrustOutcome.SUCCESS)
tm.evaluate("orchestrator", "code-agent", TrustOutcome.SUCCESS)
tm.evaluate("orchestrator", "code-agent", TrustOutcome.FAILURE)
# Check trust
print(tm.get_trust_score("orchestrator", "code-agent")) # 0.6
# Circuit break check — use this in your agent loop
if tm.should_terminate("orchestrator", "code-agent"):
raise RuntimeError("Circuit open — trust degraded below threshold")
Persist across sessions with FileStore:
from fulcrum_trust import TrustManager, TrustOutcome, TrustConfig
from fulcrum_trust.stores import FileStore
tm = TrustManager(
store=FileStore("trust_state.json"),
config=TrustConfig(threshold=0.3, half_life_seconds=3600), # 1-hour decay
)
Send trust events to Fulcrum backend with FulcrumStore:
from fulcrum_trust import FulcrumStore, TrustManager, TrustOutcome
tm = TrustManager(
store=FulcrumStore(
api_key="your-fulcrum-api-key",
base_url="https://api.fulcrumlayer.io", # best-effort REST event target
)
)
tm.evaluate("orchestrator", "code-agent", TrustOutcome.SUCCESS)
Note: The
/api/trust/eventsREST endpoint is currently DEFERRED — fulcrum-io does not yet expose it.FulcrumStorewrites locally first and best-effort ships events with a warning log on failure, so the agent keeps running and local trust state remains correct. For production cross-process integration today, useRedisIPCBridge(fulcrum_trust.ipc.redis_bridge), which writes circuit state to Redis for O(1) reads by the Go Execution Envelope.
Install
pip install fulcrum-trust
# Optional numpy fast path for decay math:
pip install "fulcrum-trust[numpy]"
Documentation
- API Reference — all public classes and methods
- Formal Validation — Lean 4 proof backing for the formal termination guarantee
- Blog post — why agents need circuit breakers
- RLM Python Prototype — Phase 5 prototype benchmark and architecture (public prototype — unstable API, not production-stable)
Support
- Email: agent@fulcrumlayer.io
- GitHub Discussions: Fulcrum-Governance/fulcrum-trust/discussions
Development
git clone https://github.com/Fulcrum-Governance/fulcrum-trust
cd fulcrum-trust
pip install -e ".[dev]"
pytest # Run tests (requires >=95% coverage)
mypy fulcrum_trust/ # Type check (strict mode)
ruff check . # Lint
ruff format . # Format
Part of the Fulcrum Architecture
fulcrum-trust is one of four repositories that make up the Fulcrum governance kernel — a portable, typed, pre-execution control plane that sits between intent and action:
| Repo | Role | License |
|---|---|---|
| fulcrum-io | Runtime control plane: gRPC/REST, MCP proxy, CLI, dashboard, SDKs | BSL 1.1 |
| Fulcrum-Boundary | Out-of-process enforcement boundary: transport adapters, 4-stage pipeline | Apache 2.0 |
| fulcrum-trust (this repo) | Trust engine: Beta(α,β) evaluator, circuit breaker, LangGraph adapter | Apache 2.0 |
| Fulcrum-Proofs | Formal core: Lean 4 proofs, claim ledger, evidence artifacts | MIT |
Project docs: Contributing · Security · Changelog · Code of Conduct · Citation
FulcrumStore bridges this package to the main Fulcrum backend with local-first persistence and best-effort REST event shipping. For production cross-process integration today, use RedisIPCBridge, which publishes circuit state for O(1) reads by the Go Execution Envelope. The Go backend has parity tests ensuring its trust implementation matches this Python package's behavior exactly.
See ADR-003 for the original repo-architecture rationale; the out-of-process enforcement boundary was added in April 2026 as GIL and now lives in the Fulcrum-Boundary repo.
Architecture
fulcrum_trust/
├── types.py — TrustOutcome enum, TrustState, TrustConfig, TrustCircuitOpen
├── evaluator.py — TrustEvaluator: Beta(α,β) scoring, pair_id generation
├── decay.py — Exponential decay toward uninformative prior
├── manager.py — TrustManager: orchestrates evaluator + store + decay
├── context.py — ContextVar isolation for concurrent evaluations
├── flusher.py — Background telemetry batching (non-blocking store writes)
├── rlm/ — Phase 5 prototype: public but unstable API, not production-stable
│ ├── context.py — 128k-bounded long-context externalization into symbolic handles
│ ├── runtime.py — Restricted `peek` + `llm_batch` navigation runtime
│ ├── prototype.py — Gratitude-loop analysis + lost-in-the-middle benchmark
│ └── fixtures.py — Deterministic 100K+ token synthetic session generator
└── stores/
├── base.py — TrustStore Protocol (structural subtyping)
├── memory.py — MemoryStore (default, in-process)
├── file.py — FileStore (JSON-backed, cross-session)
└── fulcrum.py — FulcrumStore (local-first + backend event shipping)
License
Apache 2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fulcrum_trust-0.3.0.tar.gz.
File metadata
- Download URL: fulcrum_trust-0.3.0.tar.gz
- Upload date:
- Size: 228.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07d8a95a621aaaa2a2a7357d7905932e2b964d0f8d272b83ded32cd40e310800
|
|
| MD5 |
fe223dcf7662df21f91dbd385c598047
|
|
| BLAKE2b-256 |
9cb4ba833cb01d519d1ba7c786cdee536ed8ee430d915047b4c93443c13aab9c
|
File details
Details for the file fulcrum_trust-0.3.0-py3-none-any.whl.
File metadata
- Download URL: fulcrum_trust-0.3.0-py3-none-any.whl
- Upload date:
- Size: 42.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
733fed3538b10ba1aaf6674da98c6348deadff5e92b09c849a818aa427c7cf43
|
|
| MD5 |
cbe2406232d1de8c9693abf4d9633517
|
|
| BLAKE2b-256 |
73f8fd21914cefd7d7b392dc0ae0b1de203d7f255bb3e0cdb70410ab9e14ddb2
|