Metadata-Version: 2.5
Name: kognita
Version: 0.5.0
Summary: Prove an AI answer was permitted — and evidence it. A governed decision, retrieval and evidence library with an AI gateway and MCP proxy.
Project-URL: Homepage, https://github.com/mze3e/kognita
Project-URL: Repository, https://github.com/mze3e/kognita
Project-URL: Issues, https://github.com/mze3e/kognita/issues
Project-URL: Changelog, https://github.com/mze3e/kognita/blob/main/CHANGELOG.md
Author: Ahmed Muzammil
License-Expression: MIT
License-File: LICENSE
Keywords: ai-gateway,audit,compliance,governance,mcp,policy-decision-point,rag
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Legal Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security
Requires-Python: >=3.12
Requires-Dist: numpy>=1.26.0
Requires-Dist: pydantic>=2.7.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: sqlmodel>=0.0.22
Provides-Extra: all
Requires-Dist: fastapi>=0.115.0; extra == 'all'
Requires-Dist: openai>=1.91.0; extra == 'all'
Requires-Dist: sentence-transformers>=3.0.0; extra == 'all'
Requires-Dist: sqlite-vec>=0.1.6; extra == 'all'
Requires-Dist: uvicorn>=0.30.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: import-linter>=2.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Provides-Extra: docling
Requires-Dist: docling-slim[format-docx,format-html,format-markdown]==2.140.0; extra == 'docling'
Provides-Extra: hermes-agent
Requires-Dist: hermes-agent>=0.19.0; extra == 'hermes-agent'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
Provides-Extra: local-embeddings
Requires-Dist: fastapi>=0.115.0; extra == 'local-embeddings'
Requires-Dist: sentence-transformers>=3.0.0; extra == 'local-embeddings'
Requires-Dist: uvicorn>=0.30.0; extra == 'local-embeddings'
Provides-Extra: openai
Requires-Dist: openai>=1.91.0; extra == 'openai'
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.2.0; extra == 'openai-agents'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.27.0; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.27.0; extra == 'otel'
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai>=0.1.0; extra == 'pydantic-ai'
Provides-Extra: vec
Requires-Dist: sqlite-vec>=0.1.6; extra == 'vec'
Description-Content-Type: text/markdown

# Kognita

[![PyPI version](https://img.shields.io/pypi/v/kognita.svg)](https://pypi.org/project/kognita/)
[![Python Versions](https://img.shields.io/pypi/pyversions/kognita.svg)](https://pypi.org/project/kognita/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/mze3e/kognita/actions/workflows/ci.yml/badge.svg)](https://github.com/mze3e/kognita/actions/workflows/ci.yml)
[![Downloads](https://static.pepy.tech/badge/kognita)](https://pepy.tech/project/kognita)

![Kognita Logo](docs/kognita-logo.png)

**Prove an AI action was permitted, and evidence it.**

Content guardrails check what an AI *says*. Kognita decides whether the agent was *allowed to ask*, before any data is retrieved, and writes a tamper-evident record of the decision and the rule behind it.

Your agent pulls a client's portfolio, summarises it, and sends it to a model API. Was it allowed to? With Kognita, that question has an answer you can show a regulator: which rule permitted it, which rules would have stopped it, and proof that the record hasn't been edited since.

```python
from datetime import datetime, timezone
from sqlmodel import Session

from kognita import Envelope, Policy, decide, load_snapshot
from kognita.db import create_all, make_engine

engine = make_engine()
create_all(engine)
start = datetime(2026, 1, 1, tzinfo=timezone.utc)

with Session(engine) as session:
    session.add_all([
        Policy(regime="BOOKING_CENTRE", rule_type="ATTRIBUTE_ALLOWLIST",
               rule={"allow": {"actor_location": ["SG"]}},
               citation="Cross-border manual s3.2: SG-booked clients served from SG",
               effective_from=start),
        Policy(regime="INVESTOR_STATUS", rule_type="REQUIRES_FLAG",
               rule={"flags": ["accredited_investor"]},
               citation="Product governance policy s7: complex products",
               effective_from=start),
    ])
    session.commit()

    evaluation = decide(
        Envelope(principal="rm@bank.example", purpose="PRODUCT_DISCUSSION",
                 tool="discuss_product", actor_location="HK",
                 subject_type="client", subject_id="123"),
        load_snapshot(session),
        attributes={"actor_location": "HK", "accredited_investor": False},
        purposes=["PRODUCT_DISCUSSION"],
    )

print(evaluation.outcome.value)
for check in evaluation.basis():
    print(f"{check.regime:16} {check.citation}")
```

```console
DENY
BOOKING_CENTRE   Cross-border manual s3.2: SG-booked clients served from SG
INVESTOR_STATUS  Product governance policy s7: complex products
```

Two independent rules refused. Neither masked the other, each names its source, and nothing was retrieved. The policy names and citations above are illustrative; in a real deployment they come from your own policy set.

## Install

```bash
pip install kognita
```

The core depends on four packages (`pydantic`, `sqlmodel`, `numpy`, `python-dotenv`) and runs with no network and no API key. Deciding whether a request is permitted should not require the machinery that answers it. `import-linter` contracts and a no-extras install test keep it that way.

Optional extras add provider-backed embedders (`kognita[openai]`), a SQLite vector index (`kognita[vec]`), local embeddings (`kognita[local-embeddings]`), structured document ingestion (`kognita[docling]`), and OpenTelemetry export (`kognita[otel]`). `kognita[docling]` parses markdown, HTML and DOCX. PDF is refused until section headings can be read without torch. One extra per framework adapter: `kognita[hermes-agent]`, `kognita[openai-agents]`, `kognita[langgraph]` and `kognita[pydantic-ai]`. The core does not import those frameworks or OpenTelemetry.

## What it does today

**Authorise before discovery.** An agent's intent is described as an envelope (who, for what purpose, with which tool, about which subject, from where) and evaluated before anything is fetched. A denial returns no data, not filtered data. Retrieval filters on zones and classification before scoring. An item with no zones is not visible in any zone.

**Fail closed.** Outcomes resolve as `DENY > ESCALATE > HUMAN_APPROVAL > ALLOW`. One failing check among a hundred passes still denies, so a policy set cannot be widened by adding permissive rules. A policy whose rule type has no evaluator escalates rather than being skipped. A missing or empty purpose list fails the purpose check.

**Use-case register.** `run_governed`, `ask`, the MCP proxy and the AI gateway always decide against the register. An empty register denies, and the denial cites the register rule. A passing check cites that use case's accountable owner. `decide` still uses the free-text purpose list when the caller does not pass a register; that check cites the agent's accountable owner, or the principal when the call names no agent. No owner fails closed. An approved model is a model card, and a call that uses one links that card. An unmediated entry records its assurance gap and compensating control; the gateway flags a call to an AI endpoint no use case names, and the flag does not decide the call. Retiring a use case stops agents registered only to it and applies its retention period. Tier floors and review periods are constants in `kognita.usecases`:

| Tier | Approval gate | Sampling | Autonomy ceiling | Explanation | Review |
| --- | --- | --- | --- | --- | --- |
| 1 | none | 5% | L2 | citation | 365 days |
| 2 | human review | 10% | L3 | sources | 180 days |
| 3 | human approval | 50% | L4 | suitability | 90 days |
| 4 | two signatures | 100% | L4 | full | 90 days |

Monitoring is required at every tier. On the review date the use case is denied. Activating it again waits until the designated control function approves the materiality rating at or after that date. A change of purpose or category clears activation, as does a change of autonomy, jurisdiction, client population, or intended use.

**Every decision cites its rule.** Each check carries the regime and citation it came from. A check without a citation is an assertion, not a decision, and the conformance kit enforces it.

**Agents are registered, and can be stopped.** An agent that is not in the registry is denied. Each registered agent carries a version, an accountable owner and a materiality tier, and has a kill switch that denies its next request:

```console
DENY  Kill switch engaged — accountable owner: Head of Wealth Advisory
DENY  Agent inventory — 'AGENT-UNKNOWN' is not registered
```

Through `kognita serve`, a call with no agent name is denied. An agent name is accepted only when the bound client configuration lists it. An approved system trigger is admitted, and it is not treated as a human.

**Decisions are pure and replayable.** `decide()` writes nothing and takes the instant as a parameter. Policies are effective-dated rows, so *"what would this have decided in March?"* has an answer:

```python
decide(envelope, load_snapshot(session, as_of=march), as_of=march, ...)
```

**Evidence is tamper-evident.** Each event carries the previous event's hash, so altering any payload breaks every hash after it:

```console
$ kognita evidence verify --db store.db
BROKEN: evidence chain broken at sequence 2: payload does not match its hash

$ kognita evidence export --db store.db -o audit.json   # portable, self-verifying

$ kognita evidence reconstruct <interaction_id> --db store.db -o report
# writes report.json and report.txt
```

`kognita evidence affected --model <version> | --policy <id> | --usecase <id> --from --to` lists every client who received model output or released tool output in that window, with the interaction. `kognita revoke` stops one agent, model, provider, tool, source, client, use case, or action. `kognita reinstate` closes that revocation when an accountable owner cites the rule. A provider stays revoked until every accountable owner of every use case that lists it has cited a reinstatement. A registered circuit-breaker threshold inserts a prohibiting policy when the flags in its window reach it. Nothing trips until a threshold is registered. `kognita drill run` records a kill-switch drill for a high-materiality use case. A high-materiality pilot or live use case with no drill scheduled, or a drill past its due date, is a denial. A payload rewritten behind the store's back, with its hashes left as they were, is reported by `kognita evidence verify`.

Payloads hold hashes and references by default, because an append-only log full of personal data collides with erasure rights.

**Pinned evidence and replay.** Each policy check records the hash of the policy row as evaluated. `replay_decision()` fails if a pinned policy row, retrieved item, or retained prompt or response no longer matches. An in-place edit of an effective policy is refused; a change is a new row via `supersede_policy()`. Retrieval records a content hash and the embedding model. A model call records the provider, the model name and version reported by the provider, the prompt template version, and hashes of the prompt as sent and the response as received. Tool and egress events record a hash of the response. Prompts, responses, and source snapshots live in a retention store keyed by those hashes. Erasure deletes the bytes and appends an `ERASURE` event; the chain keeps the hash. `kognita evidence reconstruct` answers the ten reconstruction-test questions from that record. It checks the chain and every pinned hash. Origination, RM review, and client communication are answered when those events were recorded on the interaction. A journey with no client send still marks that question "not recorded".

**Humans approve what they actually reviewed.** Through `run_governed()`, a `HUMAN_APPROVAL` decision holds the tool until approval is granted. The hold checkpoints the run. `continue_run(run_id, approvals_resolved={approval_id: True})` resumes a single-signature hold; a denied approval does not execute. A two-signature hold is `mark()`, then `continue_run` confirms the second signature. Pass `confirmer` for that signature; when it is omitted, `approver_name` is the confirming party. A missing confirmer, or the same identity as the marker, does not confirm and does not resume the run. A true resolution before `mark()` does not grant the approval and does not resume the run. Approvals bind to a hash of the envelope, attributes and checks, so an approval for one request cannot be replayed for a different one. `mark` and `confirm` are exported from `kognita`.

**Egress is guarded, not merely refused.** The egress guard decides per classification and destination whether content may leave, must be redacted, or may not leave at all:

```python
from kognita import Classification
from kognita.egress import EgressGuard, PatternRedactor

guard = EgressGuard(redactor=PatternRedactor(extra_terms=["Jane Tan"]))
result = guard.send("Jane Tan (jane.tan@example.com) holds account SG-PB-004417",
                    call_the_model, classification=Classification.C2,
                    destination="api.openai.com", destination_is_local=False)

result.decision   # REDACT
# the provider saw:  [TERM_1] ([EMAIL_1]) holds account [ACCOUNT_1]
# the caller got the real values back; evidence records the redaction
# manifest hash, never the content
```

> `PatternRedactor` is a floor, not a guarantee. It catches the patterns it knows (emails, IBANs, card numbers, phone numbers and similar) plus terms you list, and it misses names in prose that you didn't list. Deployments handling real personal data should supply an NER-based `Redactor`. The tests cover the plumbing, that nothing unredacted escapes the guard, not detection recall.

**Governed tools and questions.** `run_governed()` is the only path to a registered tool: decide, record, and only then execute, with tool-call and egress evidence. `ask()` answers a question from entitled, cited sources only, and when it refuses, the basis for refusing is the answer.

**Run budgets.** A `Run` on `run_governed()` and `ask()` caps call count, wall clock, classification ceiling, and cost when the caller already knows it. Token spend is recorded when the call site supplies it. The AI gateway adds the provider's token totals and cost to that budget. Exceeding a budget is a `DENY` that cites that budget, and the consumption is written to the evidence chain.

**Classifier-derived envelopes.** `ask()` classifies the question, and `run_governed()` classifies free-text arguments, with the pattern classifier in core. A typed attribute wins. Identity, purpose, and subject are not taken from the text. The recorded model, version, label, calibrated confidence, and input hash are what `decide()` replays, so replay does not run the classifier. Below a `CLASSIFIER_CONFIDENCE` threshold the outcome is `ESCALATE`, never `ALLOW`. A citation that acted on a classifier label names both the policy rule and that label.

**The AI gateway.** `kognita serve` fronts an OpenAI-compatible provider. Agents point `base_url` at the gateway. It decides before any byte is forwarded, redacts through the egress guard, restores redacted spans, and classifies the response. `MODEL_CALL` and `EGRESS` evidence record hashes and references, not the prompt or the response. If the evidence store cannot record the call, the default `--failure-mode FAIL_CLOSED` refuses it and does not call the provider. `DEGRADED` may proceed only for a local model and content below classification C2 (C2 is client-identifying), and writes the decision and the model evidence once the store accepts writes. The gateway overhead benchmark drops its warm-up calls, then fails unless the 95th percentile of the remaining sample is under 50 ms and the mean is under 25 ms. Classifier inference and the upstream stand-in are excluded. A single slow call is printed and is not the gate.

```console
$ kognita serve --provider openai-compatible --upstream https://api.openai.com \
    --purpose COLLABORATION --purposes COLLABORATION --agent dossier-agent
```

`--provider` accepts `openai-compatible` only, and `--upstream` is the provider origin. `--purposes` is the allowlist; with none, every call is denied. `--purpose` is the purpose claimed when a request has none. The process listens on `127.0.0.1:8080`. Clients set `base_url` to `http://127.0.0.1:8080/v1`.

**The MCP proxy.** `kognita serve --mcp --root-config config.json` fronts the MCP servers named in that file. The file also names the policy pack, the evidence database, and the default actor context. Every call becomes an envelope and is authorised with `run_governed()`. The backend is contacted only when the call is released: an allow, or a human approval that already has a live grant. A denial, an escalation, and an ungranted human approval return the outcome and the citations. If the evidence store cannot record the call, the proxy refuses it and does not forward. It has no degraded mode.

**Flagship demo.** `kognita scaffold --template governed-agent` creates a small app and a SQLite policy and evidence store.

**Conformance.** The [conformance kit](#conformance) asserts that a domain pack's rules are decided fail-closed, cited, and evidenced.

## Continuous Learning and Drift

The [roadmap](docs/ROADMAP.md) schedules this loop after 0.3:

**Outcome metrics per use case (0.4).** Each use case names one primary metric — cycle time, decision quality, or reliability — with a baseline and a target. A Knowledge Lead owns the quality standard. The three metrics are computed from the interaction record. Cycle time is mean seconds from the interaction opening to the final decision. A named primary metric with no measurement is a breach. Autonomy gates that consume those metrics are still 0.5.

**Flight recorder (0.4).** `kognita recorder` lists recent runs from the evidence chain, shows one run's decisions and citations, and exports a run as self-verifying JSON. Alerts cover a budget breach, repeated denials from one actor, a chain break, and classifier confidence drift. RM acceptance, edit, rejection, and wrong-client rates are counted from evidence. A missing measurement is unmeasured. `kognita serve --otel` exports decisions, denials, latency, token use, and tool failures when `kognita[otel]` is installed and `OTEL_EXPORTER_OTLP_ENDPOINT` is set. Injection attempts and entitlement blocks are unmeasured until an event records them. An export failure is reported and does not change the decision.

**Autonomy earns its expansion (0.5).** Raising an agent's autonomy level requires thresholds on those metrics. The evidence is necessary, not sufficient: the accountable owner still approves. If the metrics fall below a lower threshold, the agent's effective level steps down one step.

**Institutional memory (0.6).** A pattern in RM corrections or outcomes becomes a proposed change. The Knowledge Lead approves it as a new dated version. Learning is versioned, never edited in place.

## Domain packs

The core is domain-blind. A pack supplies what it cannot know: what a request's *attributes* are, and how to load the *subjects* it refers to.

```python
class MyPack:
    name = "my-domain"
    def load_subjects(self, envelope, session): ...
    def resolve_attributes(self, envelope, subjects): ...
    def rules(self): return build_registry(MY_EVALUATORS)
```

Policies are data: effective-dated rows with a JSON payload, interpreted by the evaluator registered for their `rule_type`. The core ships seven primitives (allowlist, denylist, required flag, required human approval, two-signature approval, classifier confidence (`CLASSIFIER_CONFIDENCE`), prohibited); a pack registers whatever its regimes need beyond them.

### Conformance

The conformance kit is a set of assertions every domain pack must satisfy: whatever a pack's rules say, they are decided fail-closed, cited and evidenced.

```bash
pytest --pyargs kognita.testing.conformance      # the kit against its bundled pack
```

```python
from kognita.testing import ConformanceCase, Harness

class TestMyPack(ConformanceCase):
    @pytest.fixture(autouse=True)
    def _bind(self):
        self.harness = Harness(pack=MyPack(), purposes=PURPOSES, seed=seed)
        self.allow_envelope = Envelope(...)
        self.deny_envelope = Envelope(...)
```

The invariants are importable, so external packs run them in their own repositories.

## Known limitations

Kognita is alpha. These are known gaps in v0.5.0. The [changelog](CHANGELOG.md) has the full list.

- **Several 0.5 definition-of-done items are partial.** Agent identity does not yet require every field. Blast-radius quota admission is not atomic under concurrency, and there is no daily transaction or allowed-products limit. Containment and step-down alerts are evidence events with no delivery. Provider-credential detection scans declared metadata only. A timed-out or killed MCP call's result is withheld, but the backend call keeps running.
- **Gateway model calls do not apply every run control.** Live-run interventions (pause, override, restrict, recover), the tool isolation checks and human-approval continuation apply on governed tool calls, not on AI gateway model calls.
- **The framework adapters cannot pass an agent credential or delegation id yet.** Registered-agent calls through the hermes-agent, openai-agents, langgraph and pydantic-ai adapters are denied (fail closed).
- **Approved system triggers are denied.** A trigger with no agent credential fails the credential check on the gateway and the MCP proxy.
- **The gateway does not place a response on the review queue.** Scoring a response against the use case's risk criteria happens when the interaction is screened. A response that was never screened is counted as unscreened in the coverage report.
- **Degraded-mode evidence is held in the gateway process.** A `DEGRADED` call that proceeds while the evidence store is down keeps the decision and the model evidence in memory until the store accepts writes. Restarting the process before then drops them. `FAIL_CLOSED` is the default, and it refuses the call. The MCP proxy has no degraded mode.
- **`create_all` does not migrate an existing SQLite file beyond the additive agent columns.** Tables already in the file are otherwise left as they are. A domain pack's models must be imported first, or its tables are skipped.

## Where it's heading

The direction is **supervisory examinability**: a regulator should be able to pick one AI-assisted client interaction and reconstruct it from evidence alone. Why this client, why this product, what data the AI used, which model produced it, what the agent was authorised to do, which controls ran, what the human saw and changed, who decided, what the client was told, and whether all of that can be reproduced later.

The test every release is measured against:

> **Can the bank explain, control, stop and reconstruct every material AI action that affects a client?**

**0.3 "Gateways, the Run and Replay" is shipped** in v0.3.0: the AI gateway, the MCP proxy, classifier-derived envelopes, run budgets, suspend and resume, pinned evidence, `kognita evidence reconstruct`, degraded gateway mode, and the Tier 0 defect closures.

**0.4 "Ingestion, Policy Language and the Client Lifecycle" is shipped** in v0.4.1: structured ingestion with passage-level citations, the policy language, the use-case register, the client interaction record, outcomes and the review queue, communication and intervention controls, the circuit breaker and revocation, starter packs, the flight recorder, and the hermes-agent, openai-agents, langgraph and pydantic-ai adapters.

**0.5 "Agents, Authority and Fleets" is released as an alpha** in v0.5.0: authenticated agent identity and workload credentials, delegated authority with dual checks, autonomy levels with evidence-gated promotion and step-down, blast-radius limits, taint isolation, anomaly containment, agent delegation and fleet controls, reversibility, and a threat model. Some definition-of-done items are still partial; see Known limitations.

Planned, not yet built:

- **0.6: Claims and institutional memory.** Typed, sourced, current claims checked before an RM relies on them; approved-source grounding; supervised memory that turns lessons into shared standards; governed business definitions.
- **0.7: Trust and resilience.** Signed evidence, external verification, provider and dependency registers.

The full plan is in [docs/ROADMAP.md](docs/ROADMAP.md). How it maps to bank control frameworks for agentic AI is in [docs/control-frameworks.md](docs/control-frameworks.md), which consolidates a 40-domain bank-grade framework and a 70-control wealth management framework, plus the EVOLVE framework's learning loop and institutional memory.

The design rule behind all of it: **never rely on the LLM to enforce a control that can be enforced outside the LLM.**

## What Kognita does not hold

Kognita governs policies, guidelines and governance records: the rules, the registers and the evidence. It does not host client data, document stores, agent memory or a knowledge graph, and it does not ship connectors to file stores. Those stay in the bank's systems, and Kognita decides and evidences access to them.

> **Removed after 0.3.0.** The Graphiti and Kuzu graph engine (`kognita.graph`, the `[graph]` extra, and the `[anthropic]`, `[groq]` and `[gemini]` extras that only installed it) is no longer part of the package. Pin `kognita[graph]==0.3.0` if you still need it. Details: [docs/decisions/0008-remove-the-graph-engine.md](docs/decisions/0008-remove-the-graph-engine.md).

## Layout

```
kognita            the decision engine: decisions, evidence, approvals,
                   retrieval, egress, tools, the AI gateway and the MCP proxy.
                   Four dependencies, no network.
kognita.testing    the conformance kit
kognita.adapters   provider-backed embedders                 [openai]
                   framework adapters                         [hermes-agent]
                                                              [openai-agents]
                                                              [langgraph]
                                                              [pydantic-ai]
```

`import kognita` never loads a graph database or a provider SDK, and `tests/test_packaging.py` asserts it.

> **Moved in 0.2.** `kognita.core.*` → `kognita.*`. Details: [docs/decisions/0003-the-top-level-namespace.md](docs/decisions/0003-the-top-level-namespace.md).

## Status

**Alpha.** v0.5.0 is on PyPI. See the [changelog](CHANGELOG.md) for what changed and [CONTRIBUTING.md](CONTRIBUTING.md) to get involved.

MIT licensed.
