Metadata-Version: 2.4
Name: lumyn
Version: 1.3.0
Summary: Decision Records for production AI.
Project-URL: Homepage, https://github.com/davidahmann/lumyn
Project-URL: Repository, https://github.com/davidahmann/lumyn
Author: Lumyn OSS Contributors
License: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.115
Requires-Dist: fastembed>=0.7.4
Requires-Dist: jsonschema>=4.23.0
Requires-Dist: lancedb>=0.25.3
Requires-Dist: pandas>=2.3.3
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: typer<0.20,>=0.12
Requires-Dist: ulid-py>=1.1.0
Provides-Extra: service
Requires-Dist: uvicorn>=0.30; extra == 'service'
Description-Content-Type: text/markdown

# Lumyn

**Decision Records for production AI.**

Lumyn is a deterministic `decide()` gateway for AI agents. It enforces strict policies, returns explicit verdicts (`ALLOW`, `DENY`, `ESCALATE`, `ABSTAIN`), and writes durable **Decision Records** for instant incident replay.

With **Lumyn Memory**, it learns from verified outcomes to block repeated failures ("Pre-Cognition") and fast-track proven successes ("Self-Healing").

> [!NOTE]
> **v1.0.0 Stable**: This documentation covers the v1 engine. For legacy v0 documentation, see [Legacy Specs](SPECS_SCHEMAS.md#legacy-specs-v0).

## When an AI incident happens

Support shares a screenshot.
Engineering tries to reconstruct what the model saw and what policy/risk rules fired.
Nobody can answer, precisely and repeatably: what happened, what changed, and why did we allow it?

Lumyn's unit of evidence is a `decision_id`. Paste it into the ticket, then:

- `lumyn show <decision_id>`
- `lumyn explain <decision_id> --markdown`
- `lumyn export <decision_id> --pack --out decision_pack.zip`
- `lumyn replay decision_pack.zip --markdown`

## Why teams adopt Lumyn

- **Write-path safety**: gates consequential actions with explicit policy and outcomes.
- **Replayable decisions**: stable digests (`policy.policy_hash`, `request.context.digest`, `determinism.inputs_digest`).
- **No bluffing**: uncertainty becomes `ABSTAIN` or `ESCALATE` with reason codes.
- **Compounding reliability**: labeled failures/successes feed Experience Memory similarity.
- **Drop-in**: works as a Python library and as an optional HTTP service.

## Operations & Safety

#### 📺 The War Room (`lumyn monitor`)
"Less drama. Fewer incidents."
A live, scrolling Matrix-style TUI showing decisions as they happen.
```bash
lumyn monitor --limit 50
```

#### 🛡️ Regression Testing (`lumyn diff`)
"Did my change block valid users?"
Run a candidate policy against a history of past records to catch regressions before deployment.
```bash
lumyn diff past_traffic.json --policy new_policy.v1.yml
```

## The primitive

You wrap a risky action with `decide()`:

1) you provide a `DecisionRequest` (subject, action, evidence, `schema_version: decision_request.v1`)
2) Lumyn evaluates deterministic `policy.v1` (strict stages + conditions)
3) Lumyn returns a `DecisionRecord` and persists it (append-only)

The Decision Record is the unit you export into incidents, tickets, and postmortems.

## How it works (one screen)

- You provide a `DecisionRequest` (no external fetches in v1; your app supplies `evidence`).
- Lumyn evaluations occur in 5 strict stages: `REQUIREMENTS` -> `HARD_BLOCKS` -> `ESCALATIONS` -> `ALLOW_PATHS` -> `DEFAULT`.
- Lumyn computes Experience Memory similarity from prior labeled outcomes.
- Lumyn persists the Decision Record to SQLite before returning (or returns ABSTAIN on storage failure).

## What a Decision Record looks like

```json
{
  "schema_version": "decision_record.v1",
  "decision_id": "01JZ1S7Y1NQ2A0D5JQK2Q2P3X4",
  "created_at": "2025-12-15T10:00:00Z",
  "request": {
    "schema_version": "decision_request.v1",
    "subject": { "type": "service", "id": "support-agent", "tenant_id": "acme" },
    "action": {
      "type": "support.refund",
      "intent": "Refund duplicate charge for order 82731",
      "amount": { "value": 201.0, "currency": "USD" }
    },
    "evidence": { "ticket_id": "ZD-1001", "order_id": "82731", "payment_instrument_risk": "low" },
    "context": { "mode": "digest_only", "digest": "sha256:aaaaaaaa..." }
  },
  "policy": {
    "policy_id": "lumyn-support",
    "policy_version": "1.0.0",
    "policy_hash": "sha256:bbbbbb...",
    "mode": "enforce"
  },
  "verdict": "ESCALATE",
  "reason_codes": ["REFUND_OVER_ESCALATION_LIMIT"],
  "matched_rules": [
    { "rule_id": "R008", "stage": "ESCALATIONS", "effect": "ESCALATE", "reason_codes": ["REFUND_OVER_ESCALATION_LIMIT"] }
  ],
  "risk_signals": {
    "uncertainty_score": 0.12,
    "failure_similarity": { "score": 0.07, "top_k": [] }
  },
  "determinism": {
    "engine_version": "1.0.0",
    "inputs_digest": "sha256:cccc..."
  }
}
```

## Quickstart (no keys, no Docker)

Install:
- `pip install lumyn`
- Service mode: `pip install lumyn[service]`

Fastest "aha" (compounding in seconds):

- `lumyn doctor --fix`
- `lumyn demo --story`

Common CLI workflows:
- `lumyn init` (creates local SQLite + starter policy)
- `lumyn monitor` (watch decisions live)

Key capabilities:
- **Policy-as-Code** (YAML + Jinja2)
- **Institutional Memory** (Learn from outcomes)
- **GitOps-native** workflow
- **Local & Fast** (Rust-powered internals, Python interface)

## Quickstart

### 1. Initialize
```bash
uv tool install lumyn
lumyn init
```

### 2. Make a Decision
```bash
lumyn decide request.json
```

### 3. Teach (Optional)
```bash
# If a decision turns out to be bad (e.g. fraud), teach Lumyn:
lumyn learn <decision_id> --outcome FAILURE
``` (prints a Decision Record)
- `lumyn show <decision_id>`, `lumyn explain <decision_id>`
- `lumyn export <decision_id> --pack --out decision_pack.zip`
- `lumyn replay decision_pack.zip` (validate pack + digests)
- `lumyn policy validate` (strict v1 validation)
- `lumyn migrate old_policy.v0.yml` (upgrade to v1)

## SDK (drop-in)

Lumyn does not call your model. You call Lumyn before (or around) a real write-path action.

```python
from lumyn import LumynConfig, decide_v1

cfg = LumynConfig(store_path=".lumyn/lumyn.db")  # loads policies/starter.v1.yml

record = decide_v1(
    {
        "schema_version": "decision_request.v1",
        "request_id": "req_123", # recommended for retries
        "subject": {"type": "service", "id": "support-agent", "tenant_id": "acme"},
        "action": {
            "type": "support.refund",
            "intent": "Refund duplicate charge",
            "amount": {"value": 20.0, "currency": "USD"},
        },
        "evidence": {
            "ticket_id": "ZD-1001",
            "payment_instrument_risk": "low",
        },
        "context": {"mode": "digest_only", "digest": "sha256:..."}
    },
    config=cfg,
)

if record["verdict"] == "ALLOW":
    pass  # perform the write-path action
elif record["verdict"] == "ESCALATE":
    pass  # route to human queue
else:
    pass  # block (ABSTAIN/DENY)
```

## Service mode (FastAPI)

Run:
- `lumyn serve`

Call:

`curl -sS -X POST http://127.0.0.1:8000/v1/decide -H 'content-type: application/json' --data-binary @request.json`

Endpoints:
- `POST /v1/decide` -> DecisionRecord (v1)
- `GET /v1/decisions/{decision_id}`
- `GET /v1/policy`

## Documentation

- [Feature Overview & Quickstart](docs/quickstart.md)
- [v1 Semantics Reference](docs/v1_semantics.md)
- [Lumyn Memory & Learning](docs/memory.md)
- [Architecture](docs/architecture.md)
- [Specs & Schemas](SPECS_SCHEMAS.md)
- [Integration Checklist](docs/integration_checklist.md)
- [Migration Guide](docs/migration_v0_to_v1.md)

## Design principles

- **Decision as an artifact**: every gate yields a record.
- **Policy + outcomes, not prompts**: rules tie to action classes and objective outcomes.
- **Telemetry ≠ truth**: OpenTelemetry is for visibility; the Decision Record is the system of record.
