Metadata-Version: 2.4
Name: liminate-invariant
Version: 0.1.0
Summary: Deterministic claim-verification correction harness for AI agents, built on Liminate.
Project-URL: Homepage, https://github.com/rmichaelthomas/liminate-invariant
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: liminate<0.16,>=0.15.1
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# liminate-invariant

Invariant by Liminate. A deterministic claim-verification correction harness for AI agents, built on `liminate.run()`: it hosts an agent behind a callable, runs a Liminate contract against the agent's output each cycle, reads structured failure details off the result stream, feeds them back to the agent, and re-runs until claims verify, stall, or the ground proves unstable. Per-claim status, partial-pass acceptance, and honest escalation (a stall is not the same as flaky ground, and neither is a plain fail) are the point.

```python
import liminate_invariant as invariant

result = invariant.run(contract_text, agent, probes={"build-output": run_build})
```

`agent` is a callable of `AgentTask -> AgentResponse`: it receives the contract text, the current cycle number, and the failure details from the previous cycle, and returns the claims it produced this round. `probes` are optional callables that acquire ground truth directly (test output, a build status, a metric) — Invariant re-acquires and re-samples them each cycle to tell a genuinely unstable measurement (flaky) apart from a claim the agent keeps getting wrong (a stall).

`run()` never writes anything. It proposes a result; the caller decides what to do with it.

## Status vocabulary

Each claim resolves to one of:

- `verified` — passed on the first cycle.
- `corrected` — failed at least once, then passed after the agent revised its claim.
- `escalated` — did not resolve. `escalation_reason` is one of:
  - `"claim will not converge"` — the same failure recurs across consecutive cycles; re-eliciting from the agent isn't fixing it.
  - `"ground will not stabilize"` — the probe-acquired ground itself won't settle into a stable pass or fail.
- `pending` — reserved for a future prediction capability (condition-gated re-check). This build's loop never assigns it.

## Quick start with `simple()`

For a plain string-in/string-out function, skip the structured interface:

```python
import liminate_invariant as invariant

def call_my_agent(prompt: str) -> str:
    ...  # however you talk to your model of choice

agent = invariant.simple(call_my_agent, source="build-status")
result = invariant.run(contract_text, agent)
```

`simple()` serializes the contract and failure details into a single prompt string for you. It's the fast on-ramp — the structured `AgentTask` / `AgentResponse` interface is the recommended integration, since serializing to a prompt string discards machine-readable failure identity that a real integration can use directly.

## Generating governance

Pass `narratia=` to have repeated failures propose durable `inherited when` handlers instead of just failing the same way forever:

```python
import liminate_invariant as invariant

narratia = invariant.NarratiaReference(complete=lambda prompt: client(prompt))
result = invariant.run(contract_text, agent, narratia=narratia)
```

`complete` is any string-in/string-out completion callable wrapped in a lambda — there's zero LLM SDK dependency inside Invariant itself. Every proposal Narratia returns passes a deterministic gate before it can touch a cycle: it's parsed by the same engine that runs your contract, and checked for contradictions against the contract's obligations. Every rejection is recorded, not silently dropped.

Admitted handlers apply in memory for the rest of the run and come back as `InvariantResult.new_handlers` — canonical, attributed `inherited when ... from narratia` text. `run()` still never writes: committing those handlers to your actual contract is yours to do.
