Metadata-Version: 2.4
Name: fulcrum-trust
Version: 0.3.0
Summary: Adaptive trust governance for the agent control plane
Project-URL: Homepage, https://github.com/Fulcrum-Governance/fulcrum-trust
Project-URL: Repository, https://github.com/Fulcrum-Governance/fulcrum-trust
Project-URL: Issues, https://github.com/Fulcrum-Governance/fulcrum-trust/issues
Author-email: Fulcrum <hello@fulcrumlayer.io>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,circuit-breaker,langgraph,multi-agent,trust
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: fakeredis>=2.23; extra == 'dev'
Requires-Dist: langgraph>=0.2.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff==0.15.18; extra == 'dev'
Provides-Extra: ipc
Requires-Dist: nats-py>=2.6; extra == 'ipc'
Requires-Dist: redis>=5.0; extra == 'ipc'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
Provides-Extra: numpy
Requires-Dist: numpy>=1.21; extra == 'numpy'
Description-Content-Type: text/markdown

# fulcrum-trust

Adaptive trust governance for the agent control plane. Bayesian Beta(α,β) trust evaluation, circuit breaking, and trust-aware routing for AI agent harnesses.

[![CI](https://github.com/Fulcrum-Governance/fulcrum-trust/actions/workflows/ci.yml/badge.svg)](https://github.com/Fulcrum-Governance/fulcrum-trust/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/fulcrum-trust)](https://pypi.org/project/fulcrum-trust/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)

## 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).

```python
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"](https://doi.org/10.5281/zenodo.19900714)
(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 bound `q(α_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`](https://github.com/Fulcrum-Governance/Fulcrum-Proofs) at the
`v0.2.0` tag (regime-conditioned docstring) — a **correspondence with the Lean
artifact, not a new proof**.

## Quick Start

```python
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:

```python
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`:

```python
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/events` REST endpoint is currently **DEFERRED** —
> fulcrum-io does not yet expose it. `FulcrumStore` writes 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, use `RedisIPCBridge` (`fulcrum_trust.ipc.redis_bridge`),
> which writes circuit state to Redis for O(1) reads by the Go Execution
> Envelope.

## Install

```bash
pip install fulcrum-trust

# Optional numpy fast path for decay math:
pip install "fulcrum-trust[numpy]"
```

## Documentation

- [API Reference](docs/api-reference.md) — all public classes and methods
- [Formal Validation](docs/formal-validation.md) — Lean 4 proof backing for the formal termination guarantee
- [Blog post](docs/blog-trust-circuit-breaker.md) — why agents need circuit breakers
- [RLM Python Prototype](docs/rlm-python-prototype.md) — Phase 5 prototype benchmark and architecture (public prototype — unstable API, not production-stable)

## Support

- Email: [agent@fulcrumlayer.io](mailto:agent@fulcrumlayer.io)
- GitHub Discussions: [Fulcrum-Governance/fulcrum-trust/discussions](https://github.com/Fulcrum-Governance/fulcrum-trust/discussions)

## Development

```bash
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](https://github.com/Fulcrum-Governance/fulcrum-io)** | Runtime control plane: gRPC/REST, MCP proxy, CLI, dashboard, SDKs | BSL 1.1 |
| **[Fulcrum-Boundary](https://github.com/Fulcrum-Governance/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](https://github.com/Fulcrum-Governance/Fulcrum-Proofs)** | Formal core: Lean 4 proofs, claim ledger, evidence artifacts | MIT |

Project docs: [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · [Changelog](CHANGELOG.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · [Citation](CITATION.cff)

`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](https://github.com/Fulcrum-Governance/fulcrum-io/blob/main/product/ADRs/003-three-repo-architecture.md) 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](LICENSE).
