# nlai

**Evidence-gated claims, continuity anchors, and receipts for agent workflows.**

Language is a proposal, not an authority. When an AI agent says "tests pass" or
"the code is thread-safe," those are *claims* — not facts. `nlai` provides the
irreducible mechanism for tracking what was claimed, checking it against
constraints, and producing content-addressed receipts that prove the decision.

## 10-second demo

```python
from nlai import gate, Anchor

# Gate agent output — extract claims, produce receipt
result = gate("The tests definitely pass and the code is thread-safe.")
print(result.verdict)       # "pass" (no anchors to violate)
print(result.claims)        # [Claim("definitely", assertive), ...]
print(result.receipt.receipt_id)  # "sha256:..."

# Add constraints
anchors = [
    Anchor(id="no-thread-claims", description="Don't claim thread safety",
           forbidden=("thread-safe", "thread safe")),
]

result = gate("The code is thread-safe.", anchors=anchors)
print(result.verdict)       # "block"
print(result.violations)    # [Violation("no-thread-claims", ...)]
```

## Evidence and contradictions

Claims start as `unsupported`. Attach evidence to promote them.
Contradictions between claims are detected and tracked.

```python
from nlai import gate, Evidence, attach_evidence

# 1. Gate extracts unsupported claims (sentence-level text)
result = gate("The system definitely passes all tests.")
claim = result.claims[0]
print(claim.text)    # "The system definitely passes all tests"
print(claim.status)  # "unsupported"

# 2. Attach evidence — promotes to "supported"
evidence = Evidence(kind="test_result", reference="pytest exit 0")
claim = attach_evidence(claim, evidence)
print(claim.status)  # "supported"

# 3. Later, gate new text against the prior supported claim
result2 = gate(
    "The system definitely fails all tests.",
    prior_claims=[claim],
)
contested = result2.contested_claims
print(contested[0].status)          # "contested"
print(contested[0].conflicts_with)  # ("The system definitely passes all tests",)
# verdict is still "pass" — contradictions don't block, they contest
print(result2.verdict)              # "pass"
```

## Claim status semantics

| Status | Meaning |
|--------|---------|
| `unsupported` | No evidence attached. Default state for extracted claims. |
| `supported` | Evidence record(s) attached. **Not independently validated** — the kernel records, it doesn't verify. |
| `contested` | Conflicting claims detected. Sticky — only humans resolve. Evidence doesn't un-contest. |

Status transitions:
- `unsupported` + evidence → `supported`
- `supported` + conflict → `contested`
- `unsupported` + conflict → `contested`
- `contested` + evidence → `contested` (sticky)

## What this is

The kernel that [Agent Governor](https://github.com/unpingable/agent_gov) builds on.
Same law, smaller jurisdiction.

- Claims require evidence
- Decisions produce receipts
- Anchors enforce continuity
- Contradictions are tracked, not resolved
- Violations resolve deterministically

## Why not just a regex?

A regex hook can block dangerous strings. But the moment someone asks:

- **What exactly was checked?** The receipt records the subject hash.
- **What policy was violated?** The violation names the anchor, with evidence.
- **Can I verify the result later?** `verify_receipt()` works offline, anytime.
- **Can I trust the checker?** The gate is deterministic — same inputs, same receipt_id.

A regex detects patterns. `nlai` attests decisions. That's the difference.

```python
# Regex: "was it blocked?"
blocked = bool(re.search(r"rm -rf", agent_output))

# nlai: "what happened, provably?"
result = gate(agent_output, anchors=my_policy)
# result.verdict, result.receipt, result.violations, result.claims
# — all content-addressed, all verifiable, all auditable
```

## What this is NOT

- No daemon, no socket, no RPC
- No adaptive policy, no orchestration
- No external dependencies (stdlib only)
- No "lite mode" weaker enforcement

## Install

```bash
pip install nlai
```

Requires Python 3.10+. Zero dependencies.

## API

```python
# The gate function
gate(text, *, anchors=None, prior_claims=None) -> GateResult

# Data types
GateResult(verdict, receipt, claims, violations)
Receipt(receipt_id, schema_version, timestamp, gate, verdict, subject_hash, evidence_hash)
Anchor(id, description, required, forbidden, severity, constraint_class)
Claim(text, strength, status, span, evidence, conflicts_with)  # text = surrounding sentence
Violation(anchor_id, severity, description, evidence)
Evidence(kind, reference, source)

# Evidence operations
attach_evidence(claim, evidence) -> Claim    # Promote UNSUPPORTED → SUPPORTED
contest_claim(claim, conflicting_text) -> Claim  # Mark as CONTESTED
find_contradictions(claims, prior_claims) -> list[Claim]  # Opposing-pair detection

# Utilities
canonical_json(obj) -> bytes
content_hash(data: bytes) -> str
verify_receipt(receipt) -> bool
extract_claims(text) -> list[Claim]
assertiveness_score(text) -> float
```

## Verdicts

| Verdict | Meaning |
|---------|---------|
| `pass` | No violations |
| `warn` | Violations found, but only at `warn` severity |
| `block` | Violations at `correct` or `reject` severity |
| `observe` | Informational only (used by infrastructure) |

## Receipts

Every `gate()` call produces a content-addressed receipt:

```
receipt_id = H(schema_version + gate + subject_hash + evidence_hash)
```

Same inputs = same receipt_id. Timestamp is metadata, not identity.
Receipts can be verified offline with `verify_receipt()`.

## Relationship to Agent Governor

```
nlai (kernel)
    ^
agent_gov (runtime + policy + orchestration)
    ^
plugins / clerk / phosphor (distribution surfaces)
```

`nlai` is the foundation. Agent Governor adds regime detection, lane routing,
multi-agent coordination, and everything else needed for production governance.

## License

Apache-2.0
