System Design

Architecture

How Mithril's governance pipeline works — from incoming claim to admission decision. Every component is independent, testable, and contributes a distinct trust signal.

Governance Pipeline

Incoming Claim  (MCP tool · Slack export · file · API · dashboard)
     │
     ▼
┌──────────────────────────────────────────────────────────────┐
│  1. Credential Exfiltration Guard  (secrets.py)              │
│     Regex-based scanner (AWS keys, GitHub tokens, JWTs,      │
│     PEM blocks, DB URIs, Bearer tokens, key=value creds).    │
│     Redacts BEFORE anything else — Cognee, LLM, audit log    │
│     never see the raw value.                                 │
│     Credential planting penalizes source reputation.         │
└──────────────────────────────────────────────────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────────────┐
│  2. Adaptive Source Reputation  (reputation.py)              │
│     SQLite-backed, self-adjusting EWMA.                      │
│     Starts from configured priors (Security Policy: 0.98,    │
│     Slack: 0.60, Unknown Agent: 0.30, etc.).                 │
│     PENALTY_RATE = 0.22 (fast) · REWARD_RATE = 0.10 (slow)  │
│     Trust is easy to lose, hard to earn.                     │
│     Weight: source_rep × 0.40                                │
└──────────────────────────────────────────────────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────────────┐
│  3. Contradiction Detection  (memory_analysis.py)            │
│     cognee.recall(only_context=True) pulls verified context. │
│     LLM scores contradiction 0.0 → 1.0 (<score> tags).      │
│     Threshold: score > 0.3 = contradiction found.            │
│     Corroboration: extra aligned chunks = bonus (max 3).     │
│     Also scores standalone content danger before admission.   │
│     Weights: contradiction × -0.40, corroboration × 0.25     │
│              content_danger × -0.35                          │
└──────────────────────────────────────────────────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────────────┐
│  4. Trust Scorer  (scorer.py)                                │
│     raw = src×0.40 + corroboration×0.25 + freshness×0.05    │
│           - contradiction×0.40 - content_danger×0.35         │
│     final_score = raw / max_theoretical_score                │
│     Normalized to [0, 1] so thresholds apply cleanly.        │
└──────────────────────────────────────────────────────────────┘
     │
     ▼
┌──────────────────────────────────────────────────────────────┐
│  5. Admission Gate  (gate.py)                                │
│     Pure function: maps final_score → AdmissionStatus.       │
│     ≥ 0.85 → Accept   ≥ 0.60 → Warn    ≥ 0.40 → Review     │
│     ≥ 0.20 → Quarantine       < 0.20 → Reject               │
└──────────────────────────────────────────────────────────────┘
     │
     ├── ACCEPT / WARN  → cognee.remember(node_set=["verified"])
     ├── QUARANTINE / REVIEW / REJECT → SQLite quarantine store
     ├── Source reputation updated (EWMA) from the outcome
     └── All decisions → Audit log (SQLite)

Trust Score Formula

The trust score is a weighted sum of five independent signals, then normalized to [0, 1]:

scorer.py — weighted formula
raw = source_reputation × 0.40
    + corroboration    × 0.25
    + freshness        × 0.05
    - contradiction    × 0.40
    - content_danger   × 0.35

final_score = clamp(raw / max_theoretical_score, 0, 1)
👤

Source Reputation (×0.40)

Each source has a live reputation (0.0–1.0) stored in SQLite. Starts from configured priors (e.g. Security Policy = 0.98, Slack = 0.60). Adjusts with every decision via bounded EWMA. Penalty rate (0.22) is 2× the reward rate (0.10) — trust is asymmetric.
🤝

Corroboration (×0.25)

When Cognee retrieves multiple aligned verified chunks for a claim, each extra chunk (beyond the first) counts as independent corroboration. Capped at 3 chunks x 0.1 = max 0.30 raw bonus, then weighted by 0.25 in the final score.
⏱️

Freshness (×0.05)

Newer claims get a small bonus. Decays linearly from 0.05 to 0 over 90 days. Recent policy updates are slightly favored over stale facts.
⚔️

Contradiction (×-0.40)

LLM-scored 0.0–1.0 against existing verified memory. If contradiction score exceeds 0.3, the claim is flagged. The penalty weight is equal to source reputation — a single contradiction can wipe out even a high-trust source's advantage.
🚧

Content Danger (×-0.35)

The same analysis pass asks the configured LLM whether the claim is inherently dangerous, even when no verified memory exists yet. Scores above 0.5 are penalized as security anti-patterns or malicious instructions.

Credential Exfiltration Guard

Memory poisoning has a mirror-image threat: credential planting. An agent or poisoned message writes an API key into shared memory. Once it's in the graph, every future agent with recall access can read it — a durable data leak.

Mithril treats this as an attack on the trust system, not just a privacy scrub:

🔒

Redact at Ingest

Credentials are stripped before anything else — Cognee, the LLM, and the audit log never see the raw value. On recall, answers are scrubbed again to catch memory that predates the firewall.
📉

Penalize the Source

A source caught planting credentials loses trust exactly like a source caught contradicting verified memory. The reputation hit is permanent and compounds.
📋

Record It

Every redacted secret type is logged in the audit trail as attempted exfiltration with the full decision provenance.

Detected Credential Types

KindPattern
Private Key PEM-----BEGIN ... PRIVATE KEY-----
AWS Access KeyAKIA / ASIA + 16 alphanumeric
GitHub Tokenghp_, gho_, ghu_, ghs_, ghr_ + 36+ chars
Slack Tokenxoxb-, xoxp-, xoxa- + 10+ chars
Google API KeyAIza + 35 chars
OpenAI / SK Keysk- (with or-, proj-, ant- prefix)
Stripe Keysk_live_, rk_test_ + 16+ chars
JSON Web TokeneyJ...header.payload.signature
DB URI Credentials://user:password@ in connection URIs
Bearer TokenBearer + 16+ char token
Credential Assignmentpassword=, api_key=, secret= values

Adaptive Source Reputation

Source reputation is not a static lookup table. It starts from configured priors and moves with every decision using a bounded exponential weighted moving average (EWMA):

reputation.py — update rules
# Trust earned per good outcome (slow to build)
REWARD_RATE  = 0.10
# Trust lost per bad outcome (fast to lose)
PENALTY_RATE = 0.22

# On a good decision (accept/warn, no contradiction):
new_rep = clamp(current + 0.10 × (1.0 - current))

# On a bad decision (quarantine/reject, or contradiction found):
new_rep = clamp(current + 0.22 × (0.0 - current))

# Floor: 0.05 — even the worst source can still submit
# Ceiling: 0.99 — perfection is never reached

This means a Slack channel at 0.60 reputation drops to ~0.47 after one blocked claim, and to ~0.36 after two. Recovering back to 0.60 requires many consecutive accepted claims. The asymmetry is intentional — it mirrors how trust works in the real world.

Cognee Integration

Mithril uses specific Cognee APIs to create a separation between verified and quarantined memory:

📝

cognee.remember()

Verified memories are stored with node_set=["verified", source_name] into the verified_memories dataset. Warned claims additionally get the low_confidence tag.
🔍

cognee.recall(only_context=True)

Used for contradiction detection — retrieves verified context chunks that the LLM then scores for contradiction. This is a read-only probe, not a query.
📖

cognee.recall(node_name=["verified"])

Used for verified recall — only returns memories that passed the trust gate. Quarantined data is invisible. Defense-in-depth: answers are scrubbed for secrets again on output.
🧠

cognee.improve()

Graph enrichment can be run on the verified dataset to build richer connections between accepted memories. Called via firewall.improve().

Three Entry Points, One Gate

No matter how a claim enters Mithril, it passes through the same governance pipeline:

SurfaceWhat it isEntry point
MCP ServerAgents call mithril_remember / mithril_recall over stdiomcp_server/server.py
Ingestion ConnectorParse Slack export or .txt/.jsonl file and run every message through the gatemithril/ingest.py
REST API + DashboardFastAPI backend + Next.js provenance UIapi/main.py

Project Layout

project structure
mithril/           Core package
  ├── firewall.py       Main Mithril class — chains the full pipeline
  ├── scorer.py         Weighted trust score formula
  ├── gate.py           Admission gate (score → status)
  ├── reputation.py     Adaptive source reputation (SQLite EWMA)
  ├── memory_analysis.py  Contradiction detection + corroboration
  ├── secrets.py        Credential exfiltration guard (11 patterns)
  ├── ingest.py         Slack export / file ingestion connectors
  ├── audit.py          SQLite audit log
  ├── quarantine.py     SQLite quarantine store
  ├── models.py         Shared dataclasses and enums
  ├── config.py         Weights, thresholds, source priors
  └── utils.py          Recall text extraction helpers

mcp_server/        MCP server — exposes 4 tools to agents
api/               FastAPI backend (9 endpoints)
benchmark/         Labeled memory-poisoning benchmark
demo/              Attack demo, vanilla comparison, ingestion demo
tests/             Unit + integration tests
ui/                Next.js landing page + provenance dashboard