Metadata-Version: 2.4
Name: siphrix
Version: 1.8.1
Summary: Siphrix — AI Action Audit & Risk Monitor. Records every agent action, surfaces it in a dashboard, and raises risk-ranked warnings. Blocking exists as a frozen, opt-in enforcement mode.
Author: Siphrix Contributors
License: MIT License
        
        Copyright (c) 2026 Denis Ghengeaua
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://siphrix.com
Project-URL: Documentation, https://siphrix.com/docs
Project-URL: Install, https://siphrix.com/install
Project-URL: Privacy, https://siphrix.com/privacy
Project-URL: Issues, https://siphrix.com
Keywords: ai,ai-agents,ai-safety,ai-observability,policy-engine,policy-runtime,governance,audit,monitoring,risk-scoring
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=45.0.0
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: PyNaCl>=1.5.0
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Provides-Extra: build
Requires-Dist: build>=1.0; extra == "build"
Requires-Dist: twine>=5.0; extra == "build"
Provides-Extra: postgres
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
Dynamic: license-file

<p align="center">
  <img src="docs/assets/siphrix-banner.png" alt="Siphrix" width="600">
</p>

# Siphrix v1.8.1

**Siphrix — AI Action Audit & Risk Monitor.** The flight recorder for
your AI agents.

Siphrix writes down what the AI on your machine actually did — which
file it changed, which command it ran, which site it opened, in which
app — and raises the risky ones as warnings. It does not block. An
observer must not break the thing it observes; blocking survives only as
a frozen opt-in (`SIPHRIX_MODE=enforce`) for a future prevention tier.

The question it exists to answer is the one you cannot answer today:
*an agent worked on this machine for six hours — what did it do?*

**Evaluating Siphrix?** Start with
[docs/launch/QUICK_EVAL.md](docs/launch/QUICK_EVAL.md) — a 60-second
pitch and a short offline smoke block. After installing, the cheat sheet
is [docs/launch/PUBLIC_HANDOFF.md](docs/launch/PUBLIC_HANDOFF.md); the
five-minute live tour is
[docs/launch/DEMO_SCRIPT.md](docs/launch/DEMO_SCRIPT.md).

```bash
pip install siphrix
python -m siphrix demo        # block, allow, audit — offline, no keys
python -m siphrix doctor      # is this install actually ready?
```

> **The verdict is fail-closed, as data.** The focused examples under
> [examples/basic_agent/](examples/basic_agent/),
> [examples/tool_calling/](examples/tool_calling/) and
> [examples/universal_pattern/](examples/universal_pattern/) emit
> `BLOCK (policy_empty_allowlist)` when no policy is bound: the engine
> records a BLOCK, which shows up as a warning. Under the audit-first
> default **nothing is stopped**. That is expected, not a broken
> install — set `SIPHRIX_POLICY_FILE` to bind a real policy.

Under the hood there are four coordinated layers — a **policy decision**
layer (what is the right verdict?), a **runtime enforcement** layer
(frozen to observe-only by default), a **trust / audit / governance**
layer (make every decision defensible), and an **operator readiness**
surface (is this install actually ready?). Externally it is one product.

---

## Install

**Download, double-click, paste one code.** One code connects a
**computer**, and everything on that computer reports through it — the
coding agents, the editor, the browser.

### Windows

Run `Siphrix-Setup.exe`. It brings its own Python, installs the engine
offline from a bundled wheelhouse, and then wires up the machine:
enrols it, registers the agent hooks, installs the editor extension into
every editor it finds (VS Code, Cursor, Windsurf, VSCodium), registers
the browser bridge, and schedules the reporting task.

Per-user, no administrator, no service, no registry Run key, no firewall
hole. Uninstall removes all of it, including the bytecode Python wrote
on first run.

### Anywhere Python runs

```bash
pip install siphrix
siphrix set-up --code <your connection code>
```

`set-up` does the same work as the installer's second half. Nothing in
it can fail the install: a machine with no editors is a fine machine,
and every step reports what happened and moves on.

Without a code, Siphrix still records locally and can be connected
later:

```bash
pip install siphrix
siphrix demo        # the golden path: block, allow, audit
siphrix doctor      # is this install actually ready?
```

### The browser

Chrome forbids an installer adding extensions — the most abused malware
vector in the browser's history — so that step needs one click from a
person. Add **Siphrix — AI Action Monitor** from the Web Store.

You will not need a code for it. The extension asks the Siphrix already
running on the machine which computer it is on, so its records join that
computer rather than appearing beside it as a machine that does not
exist. On a machine with the engine installed, **the extension holds no
credential at all** — an extension is the easiest thing on a computer to
read, and a token that is not there cannot be taken.

---

## What gets recorded, and from where

| Surface | What it sees | How |
| --- | --- | --- |
| **Claude Code** | every governed tool call — the command, the file, the URL | `PreToolUse` hook, plus a `PostToolUse` pass that measures how much of the file actually changed (`+42 −8`) |
| **Codex** | proposed actions | advisory MCP tools it can consult |
| **Any MCP client, through any MCP server** | every tool call — which tool, on what | `siphrix mcp-wrap` sits between the two |
| **Any MCP client** | proposed actions it chooses to ask about | `siphrix.mcp.server` — stdio, verdict-only |
| **VS Code / Cursor / Windsurf / VSCodium** | text that arrived in a file without being typed; commands run in the integrated terminal | the editor extension. Neither identifies who caused it, so neither claims to: entries are attributed to `unattributed` and carry the circumstance — which AI extensions were active in the window at the time |
| **IntelliJ / PyCharm / WebStorm / GoLand / Rider** | same as above | the JetBrains plugin |
| **Chrome / Edge / Brave / Firefox** | 24 AI sites opened, messages sent, file uploads, secret-shaped pastes | the browser extension, over the local bridge |
| **Any framework emitting OpenTelemetry** | LLM calls — system, model, operation, tokens | `siphrix otlp`, an OTLP/JSON receiver |
| **Anything that talks to an LLM API** | provider, model, endpoint, token counts, timing | `siphrix llm-proxy` — 17 providers, including models running on this machine |
| **Models running locally** | Ollama, LM Studio, vLLM, llama.cpp, LocalAI | the same proxy — the one surface nothing else can see |
| **Anything else** | whatever you hand it | `ActionEvaluator` in Python, or `POST /v1/audit/ingest` |

Everything lands in one canonical record (`~/.siphrix/events.jsonl`),
which is what `siphrix analytics`, the console and the extensions all
read.

### What never reaches the record

- **File contents.** Siphrix records that a file was touched, never
  what is inside it. Where a file's identity matters, a one-way
  fingerprint is computed locally and only the fingerprint travels.
- **Message, prompt or conversation text.** The browser extension
  matches your rules against a prompt *in the page*; the text has no
  field to travel in.
- **Credentials.** Values shaped like tokens, API keys, passwords —
  including inside a command line, a URL query or an `Authorization`
  header — are masked before anything is written or synced.
- **Full URLs.** A site is reduced to a bare hostname, so a token
  sitting in a query string never reaches the log.

---

## What is *not* being recorded

The question underneath every other one, and the one an audit product
must never answer by omission:

```bash
siphrix coverage
```

```
Siphrix coverage: 4 of 5 AI surfaces on this machine are recorded.

Coding agents
  [recorded] Claude Code
      Hooked — every tool call is recorded by name.

JetBrains IDEs
  [NOT RECORDED] PyCharm
      No Siphrix plugin — install it from the JetBrains Marketplace.
```

It looks for what is actually on this computer — local model servers
answering on their ports, coding agents, editors, IDEs, the browser
bridge — and says for each whether anything is watching. Passive
throughout: it reads its own configuration, connects to a loopback port
and closes it without speaking, and looks at the PATH. It changes
nothing and never reaches the network.

A console showing four entries is indistinguishable from a quiet week
unless somebody can say what *should* have produced entries. A named gap
is worth more than a clean-looking console that silently omits a
surface.

### What cannot be reached at all

Written down rather than left to be rediscovered:

- **Word, Excel, PowerPoint, Outlook as installed applications.**
  Copilot runs inside the process, not in a page. No extension can see
  it, and the Office Add-in API would require reading the document
  itself — the one thing this product refuses to do. Office *on the web*
  is watched; the desktop is not.
- **Phone and tablet apps.** No extension surface exists there.
- **The ChatGPT and Claude desktop apps.** They call the API directly,
  so `siphrix llm-proxy` records them when their traffic is routed
  through it, and nothing does otherwise.


## Why anyone should believe the record

Two claims, proved by two different things. Keeping them apart is the
whole discipline.

**Ordering** — nothing was removed from the middle, edited in place or
reordered. Every entry is hash-chained to the one before it, so an edit
breaks every hash after it. This is computed by the service, which means
it establishes that the service has been consistent with itself. To a
reader unwilling to assume the service is honest, that is worth nothing —
the same code that writes the rows writes the hashes over them.

**Origin** — this entry was produced by that machine, running that
software, under that policy. Each machine generates an Ed25519 key on
first use and the private half never leaves it, not to the console, not
to the hosted service, not into any backup. Entries are signed before
they are sent. The service can check the signature; it cannot produce
one.

```bash
siphrix verify-evidence acme-2026-08.json
```

```
  Records:         4128
  Verified:        4128

  Ordering chain:  intact
  Head hash:       f430c45fb16b56cd774abbdfd0ff99072be669fefd507f4659248b53d5d81c39
```

No network calls, no account, works with Siphrix switched off. Export
the pack from the console (**Evidence pack**) or `GET /v1/evidence`; it
carries the entries, the signatures and the public keys, and the person
who runs the verifier is usually the one who least wants to trust the
vendor.

The limits are stated at equal length in
[`docs/architecture/PROOF_MODEL.md`](docs/architecture/PROOF_MODEL.md):
this proves origin rather than truth, it never asserts that a *person*
did anything, a compromised machine signs whatever its attacker wants,
and integrity is not coverage.

## The console

```bash
siphrix console        # local: service + web UI, opens your browser
```

or sign in at your own deployment. One row per **machine**; click it to
see what runs on it and how closely each part is watched.

The console does not stop at a log. It answers the questions people
actually open it to ask:

- **Sessions** — 4,000 entries read as a handful of working stretches
  with a beginning and an end.
- **Baseline** — a log tells you an agent read forty files; a baseline
  tells you it normally reads four.
- **Hotspots** — what the AIs keep reaching for, with the paths a
  security reviewer cares about marked.
- **Patterns** — the record read as a *sequence*. Single entries are
  rarely alarming; their order often is.
- **Flows and blast radius** — who does what where, and how far each
  agent's reach extends.

The record is tamper-evident: a per-organisation hash chain, with an
anchor sealed before any retention pruning so the remaining chain still
verifies.

---

## Try it in 60 seconds

Fully offline. No API keys, no Docker, no account.

```bash
pip install siphrix

siphrix demo                 # the official walkthrough (block, allow, audit)
siphrix doctor               # human-readable launch-readiness report
python -m siphrix --help     # every subcommand
```

`siphrix demo` prints a BLOCK verdict for an unsafe action (recorded as
a warning — under the audit-first default nothing is stopped), an ALLOW
for a safe action under the shipped `safe_defaults` pack, and an
auditable decision-trail recap.

> **The verdict is fail-closed, as data.** With no policy bound, the
> engine records `BLOCK (policy_empty_allowlist)`, which shows up as a
> warning. Nothing is stopped. That is expected, not a broken install.
> Set `SIPHRIX_POLICY_FILE` to bind a real policy.

### The local audit flow, copy-paste

```bash
python -m siphrix doctor --json
python -m siphrix quickstart
python -m siphrix demo --json
python -m siphrix evaluate --context examples/actions/file_delete.json --json

# capture an audit record, then summarize it
python -m siphrix --audit-file ./siphrix-audit.jsonl \
  evaluate --context examples/actions/file_delete.json --json
python -m siphrix audit-summary --file ./siphrix-audit.jsonl
```

Under a default development install `siphrix doctor`'s
`launch_readiness.production_ready` is `false` with blocker
`trust_posture:not_production_ready`. That is expected locally.
Production trust posture is a deployment decision — opt in with
`SIPHRIX_TRUST_MODE=hardened` and `SIPHRIX_TRUST_SEAL_SECRET`.

For longer narration and per-step expected output, see
[docs/launch/QUICK_EVAL.md](docs/launch/QUICK_EVAL.md).

---

## Integrating your own agent

Siphrix is LLM-agnostic and framework-agnostic. It never calls a model.
What you hand it is a plain dict.

```python
from siphrix import ActionEvaluator

response = ActionEvaluator().evaluate({"action_name": "http_get"})
print(response.allowed, response.verdict, response.reason)
```

The canonical shape is three separated stages:

```
intent  ->  evaluation  ->  execution
```

- **intent** — your LLM / planner / rule engine yields a structured
  action dict.
- **evaluation** — one call to `ActionEvaluator.evaluate`. Siphrix
  touches your code only here.
- **execution** — your executor runs the side-effect. Under the
  audit-first default the verdict is recorded data; whether to gate on
  it is your choice.

Runnable, vendor-neutral examples — none of which import a provider SDK:

- [examples/basic_agent/](examples/basic_agent/) — one hardcoded action,
  one evaluation, allow/block.
- [examples/tool_calling/](examples/tool_calling/) — a fake LLM
  returning a structured intent dict; swaps 1:1 for OpenAI tool-calling,
  Anthropic tool use, Gemini function calling, or any local model.
- [examples/universal_pattern/](examples/universal_pattern/) — the
  canonical pipeline as a drop-in `process(prompt)` function.

Adoption docs: [QUICKSTART](docs/integration/QUICKSTART.md) ·
[ADVANCED](docs/integration/ADVANCED.md) ·
[index](docs/integration/README.md).

---

## Official Public API

The stable, product-facing surface is exposed from the top-level
`siphrix` package. Nine names are guaranteed; everything else lives
under an explicit subpackage path.

```python
from siphrix import (
    run_pipeline,                # main runtime execution entrypoint
    PipelineResult,              # runtime result type

    PolicyManager,               # policy evaluation entrypoint
    PolicyDecision,              # policy decision result type
    PolicyInput,                 # validated policy input contract
    PolicyInputValidationError,  # raised for invalid policy input

    ActionEvaluator,             # one-call adapter over PolicyManager
    ActionEvaluationResponse,    # stable scalar response

    __version__,
)
```

These are covered by the public-API contract test in
`tests/test_public_api_surface.py` and are intended to remain stable
across patch and minor releases. Additional product-facing surfaces live
at dedicated module paths (`siphrix.config`, `siphrix.audit`,
`siphrix.explanation`, `siphrix.doctor`, `siphrix.policy_packs`) and are
documented below.

### Canonical package surface

**Product-facing modules:**

- `siphrix` — top-level public API (above)
- `siphrix.policy_runtime` — `PolicyManager`, `PolicyDecision`, `PolicyInput`
- `siphrix.policy_packs` — shipped packs and resolution
- `siphrix.agent` — `ActionEvaluator`, `ActionEvaluationResponse`
- `siphrix.config` — `RuntimeConfig`, `load_runtime_config`
- `siphrix.audit` — `AuditRecord`, `write_audit_record`
- `siphrix.explanation` — `DecisionExplanation`, `explain_decision`
- `siphrix.doctor` — `run_doctor`
- `siphrix.readiness` — subsystem-health verdicts

**Deeper canonical domains** (stable subpackages with explicit `__all__`):
`orchestrator`, `contracts`, `foundation`, `trust`, `runtime`,
`governance`, `policy_versioning`, `analysis`, `resilience`, `risk`,
`simulation`, `integration_flow`, `adapters`, `console`, `engine`.

---

## What Siphrix does not include

Honesty about the edges, because a product that overclaims once is not
trusted the second time:

- a shipped native kernel driver;
- automatic installation of seccomp, eBPF, WFP or EndpointSecurity hooks
  on your machine;
- hardware-rooted attestation or TPM-backed key management;
- a signed Windows installer (the signing scaffold is built and opt-in;
  the certificate is an operator step).

The kernel syscall interception layer is a canonical model-level and
policy-level capability: it produces normalized interception plans,
decisions and evidence payloads. It is **not** a real OS kernel hook.

Editor witnessing is deliberately narrower than it looks: Copilot cannot
be hooked — it edits the buffer through the editor's own API, exactly as
a person typing does. There is no integration point to install and no
honest way to invent one, so those entries say `unattributed` and carry
the circumstance instead. The reviewer draws the conclusion. An observer
that guesses is an observer you cannot cite.

---

## Runtime environment

By default Siphrix writes logs, state, caches and the memory store to
`~/.siphrix/` (created owner-only on POSIX). Override with:

- `SIPHRIX_MODE` — `audit` (default: record and warn, never block) or
  `enforce` (the frozen legacy blocking mode; explicit opt-in)
- `SIPHRIX_HOME` — base directory for all runtime state
- `SIPHRIX_LOG_PATH` — absolute path for `events.jsonl`
- `SIPHRIX_STATE_PATH` — absolute path for `state.json`
- `SIPHRIX_REMOTE_POLICY_CACHE` — remote-policy cache path
- `SIPHRIX_ARTIFACTS_DIR` — engine runner profile artefacts

Nothing is written to the installed package tree or the repository
working tree under default behaviour.

### Trust posture

`siphrix.trust.trust_seal` signs the in-process integrity chain with
HMAC-SHA256. Two postures:

- **`development`** *(default)* — reachable via the built-in development
  secret; preserves backward-compatible behaviour for local smoke tests.
  Not for production, and `siphrix doctor` says so plainly.
- **`hardened`** — requires a real secret via
  `SIPHRIX_TRUST_SEAL_SECRET` (≥ 32 UTF-8 bytes). Fails closed with a
  stable `trust_seal_secret_missing` / `trust_seal_secret_too_short`.

Set with `SIPHRIX_TRUST_MODE`.

The reference remote-policy server in
`siphrix.trust.remote_policy_server` is a **development / reference
surface only** — no authentication, no access control. Production
deployments terminate TLS and authenticate at a reverse proxy.

### From source

```bash
python -m venv .venv
. .venv/bin/activate          # Windows: .\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
```

### What ships vs. what's repo-only

| Surface | Ships with `pip install siphrix`? |
| --- | --- |
| `siphrix` Python package (public API) | **Yes** |
| `siphrix` console script + `siphrix demo` | **Yes** |
| Shipped policy packs + role overlays | **Yes** (package data) |
| Canon laws + baseline policy YAMLs | **Yes** (package data) |
| Web console / web app assets | **Yes** (package data) |
| `examples/`, `tests/`, `tools/`, `scripts/`, `docs/` | **No, repo-only** (in the sdist for auditability) |

---

## Shipped policy packs

Siphrix ships three canonical policy packs as package data. A policy
pack is a tool-level behavioural preset (pack-schema YAML) consumed by
pack-based integration flows; it is **not** an engine policy file.

### Canonical packs

| Pack | Description |
| --- | --- |
| ``safe_defaults`` | Conservative defaults for general AI assistants. |
| ``enterprise_defaults`` | Enterprise-oriented defaults with controlled internal execution. |
| ``dev_agent_defaults`` | Developer-oriented defaults for local testing and controlled experimentation. |

Role overlays ``admin`` / ``finance`` / ``support`` / ``developer`` are
also shipped and can be merged on top of a base pack via
``siphrix.policy_packs.resolver.resolve_policy(name, role)``.

### Discovery

From the command line:

```powershell
siphrix packs
# safe_defaults: Conservative defaults for general AI assistants.
# enterprise_defaults: Enterprise-oriented defaults with controlled internal execution.
# dev_agent_defaults: Developer-oriented defaults for local testing and controlled experimentation.
```

From Python:

```python
from siphrix.policy_packs import (
    SUPPORTED_BASE_POLICIES,
    list_policy_packs,
    resolve_policy_pack,
    ShippedPolicyPack,
    PolicyPackError,
)

for pack in list_policy_packs():
    print(pack.name, "->", pack.path)

try:
    pack: ShippedPolicyPack = resolve_policy_pack("safe_defaults")
except PolicyPackError as exc:
    ...
```

### Precedence: explicit policy file, runtime profile, shipped pack

Three distinct concepts. They are complementary, not competing. Ranked
by effect on the engine, highest first:

| # | Mechanism | Effect on the engine |
| --- | --- | --- |
| 1 | **Explicit policy file** — ``--policy-file PATH`` / ``RuntimeConfig.policy_file`` / ``SIPHRIX_POLICY_FILE`` | Loaded directly by ``PolicyManager`` as the active engine YAML. Always wins. |
| 2 | **Runtime profile** — ``--profile NAME`` / ``RuntimeConfig.profile`` | Product operating mode. Writes engine env vars where a direct mapping exists (today: ``safe_defaults`` sets ``SIPHRIX_NO_SIDE_EFFECTS=1``). Does **not** load a shipped pack into the engine. |
| 3 | **Shipped policy pack** — ``siphrix packs`` / ``resolve_policy_pack(name)`` | **No direct engine effect.** Packs are discoverable behavioural presets for pack-based integration flows (for example the example agent at ``examples/example_agent.py``). The engine consumes a different YAML schema. |

Three profile names (``safe_defaults``, ``enterprise_defaults``,
``dev_agent_defaults``) intentionally match shipped pack names, but the
two concepts are kept distinct: profile = product operating mode; pack
= shipped YAML asset. If a pack's rules should also govern engine
behaviour, translate the pack to the engine schema and supply it via
``--policy-file``; there is no silent auto-wiring.

### Pack-to-engine bridge

A shipped pack and an engine policy file use two different schemas.
The pack-to-engine bridge is an **explicit** translation step you run
by hand; it never auto-wires a pack into the engine.

From the command line:

```powershell
siphrix pack-export --name safe_defaults --output ./policy.yaml
# wrote engine policy for pack 'safe_defaults' to ./policy.yaml

siphrix --policy-file ./policy.yaml policy-check --context ./ctx.json
```

From Python:

```python
from siphrix.policy_packs import translate_policy_pack, export_policy_pack

data = translate_policy_pack("safe_defaults")     # -> engine-policy dict
export_policy_pack("safe_defaults", "./policy.yaml")
```

The generated YAML begins with a header comment listing what was
preserved and what was dropped. The translation subset is small and
honest:

| Pack concept | Translated? |
| --- | --- |
| ``mode: fail_closed`` | yes → engine ``default_action: BLOCK`` |
| ``tools.<name>: bool`` | yes → one engine rule per declared tool (``file_read`` / ``file_write`` / ``file_delete`` / ``email_send`` / ``shell_exec`` / ``network_access``). ``network_access`` maps to ``when: {category: network}``; the others to ``when: {action_name: <tool>}``. |
| ``llm.*`` | **no** — LLM orchestration; not engine-enforceable |
| ``files.*`` | **no** — engine ``when`` clauses do not support path-prefix matching |
| ``email.allow_external_recipients`` / ``allowed_domains`` | **no** — beyond the tool boolean |
| ``network.allowed_hosts`` | **no** — beyond the tool boolean |
| ``shell.allowed_commands`` / ``blocked_patterns`` | **no** — command substring matching unsupported |
| ``decision_defaults.*`` | **no** — engine has a single ``default_action`` |

If you need the unsupported semantics, edit the produced YAML by
hand. The bridge will not silently invent behaviour the engine
cannot enforce.

## Configuration

Siphrix is configured through a small, disciplined layer that sits in
front of the existing engine environment contract. Three inputs are
supported, in priority order:

1. **Explicit CLI flags** — ``--profile NAME``, ``--policy-file PATH``.
2. **TOML config file** — ``--config PATH`` with an ``[siphrix]`` table.
3. **Built-in defaults** — profile ``"default"``, no policy file.

### Config file shape

```toml
[siphrix]
profile      = "safe_defaults"
policy_file  = "/etc/siphrix/policy.yaml"
```

Both keys are optional. Unknown keys are rejected. Invalid values raise
a concise, traceback-free error and cause the CLI to exit with code
``3``.

### Supported profiles

Exactly four names, matching the shipped policy-pack layer where
applicable:

| Profile | Behavior |
| --- | --- |
| ``default`` | Zero-config baseline. No environment variables are written by the profile. |
| ``safe_defaults`` | Conservative. Sets ``SIPHRIX_NO_SIDE_EFFECTS=1`` so every action with ``effect_type = "SIDE_EFFECT"`` draws a BLOCK verdict from the engine's side-effect layer — a recorded warning in audit mode; enforced only under ``SIPHRIX_MODE=enforce``. |
| ``enterprise_defaults`` | Neutral profile for enterprise deployments. Expect a ``policy_file`` to be supplied alongside. |
| ``dev_agent_defaults`` | Most permissive shipped profile; for local development. |

``safe_defaults``, ``enterprise_defaults`` and ``dev_agent_defaults``
are the canonical names also used by
``siphrix.policy_packs.SUPPORTED_BASE_POLICIES`` and by
``examples/example_agent.py``.

### CLI examples

```powershell
# Run with a named profile
siphrix --profile safe_defaults run --input "hello"

# Point at an explicit policy file
siphrix --policy-file ./policy.yaml policy-check --context ./ctx.json

# Load everything from a TOML config
siphrix --config ./siphrix.toml run --input "hello"
```

CLI flags always override config-file values. Configuration does not
introduce new environment variables — ``RuntimeConfig.apply()`` only
writes to the existing ``SIPHRIX_POLICY_FILE`` and
``SIPHRIX_NO_SIDE_EFFECTS`` names that the engine already reads.

## Product Contracts

The product surface is defined by three stable contracts. Behaviour of
the underlying engine is unchanged; these contracts are the validated
boundary callers integrate against.

### Runtime result — ``PipelineResult``

``run_pipeline(text)`` returns a ``PipelineResult`` with the following
fields:

| Field | Type | Allowed values |
| --- | --- | --- |
| ``outcome`` | str | ``"proceed"``, ``"block"``, ``"ask_confirm"``, ``"clarify"`` |
| ``final_text`` | str | generated text when ``outcome == "proceed"``; otherwise empty |
| ``law_id`` | Optional[str] | canonical law identifier when the outcome was driven by Canon |
| ``reason_code`` | Optional[str] | stable reason code when the outcome was driven by Canon |

### Policy decision — ``PolicyDecision``

``PolicyManager().decide(...)`` returns a ``PolicyDecision`` with the
following fields:

| Field | Type | Allowed values |
| --- | --- | --- |
| ``decision_id`` | str | per-evaluation UUID |
| ``verdict`` | str | ``"ALLOW"``, ``"BLOCK"``, ``"ABORT"`` |
| ``reason`` | str | stable reason code |
| ``policy_id`` | str | identifier of the policy that produced the decision |
| ``matched_rule_id`` | Optional[str] | id of the rule that matched, if any |
| ``decision_graph`` | Optional[list] | per-layer evaluation trace (present when emitted by the engine) |
| ``explain`` | Optional[dict] | structured explanation (present when emitted by the engine) |

### Policy input — ``PolicyInput``

``PolicyInput`` is the validated input contract for
``PolicyManager().decide(...)`` and for ``siphrix policy-check``. The
JSON shape is:

```json
{
  "action_name": "http_get",

  "resource": "https://example.com/api/data",
  "resource.scheme": "https",
  "resource.domain": "example.com",
  "resource.path": "/api/data",
  "resource.raw": "https://example.com/api/data",

  "origin": "user",
  "category": "network",
  "effect_type": "READ_ONLY",
  "risk_level": "LOW",
  "trust_boundary": "api.external",
  "intent": "NORMAL",
  "agent_id": "agent.local",
  "tool_id": "tool.http",
  "source": "user",

  "params": { "timeout": 10 }
}
```

Only ``action_name`` is required and must be a non-empty string. All
other fields are optional. Enum fields are closed:

- ``effect_type`` ∈ ``{"READ_ONLY", "SIDE_EFFECT"}``
- ``risk_level`` ∈ ``{"LOW", "MEDIUM", "HIGH", "CRITICAL"}``
- ``intent`` ∈ ``{"NORMAL", "SENSITIVE"}``

``params`` must be a JSON object. Unknown top-level keys are rejected.
Invalid input raises ``PolicyInputValidationError`` from Python and
causes ``siphrix policy-check`` to exit with code ``3`` and a
concise, traceback-free error.

Minimal valid input:

```json
{ "action_name": "file_read" }
```

## Python integration (agent / tool-calling)

The official v1 integration surface for AI agents and tool-calling
runtimes is :class:`siphrix.ActionEvaluator`. It composes the existing
product contracts — :class:`PolicyInput`, :class:`PolicyManager`,
:class:`PolicyDecision` — into one small callable.

```python
from siphrix import ActionEvaluator

evaluator = ActionEvaluator()

response = evaluator.evaluate({
    "action_name": "http_get",
    "resource.domain": "example.com",
    "effect_type": "READ_ONLY",
    "risk_level": "LOW",
})

if response.allowed:
    perform_action()
else:
    handle_block(response.reason, response.verdict, response.decision_id)
```

``evaluate()`` accepts either a raw mapping — validated through
``PolicyInput.from_dict`` — or a pre-built :class:`PolicyInput`.
Invalid input raises :class:`PolicyInputValidationError`.

### ``ActionEvaluationResponse``

Stable scalar projection of :class:`PolicyDecision`. The unstable
``decision_graph`` and ``explain`` are intentionally not exposed; call
``PolicyManager().decide(...)`` directly if you need them.

| Field | Type | Notes |
| --- | --- | --- |
| ``allowed`` | bool | ``True`` iff ``verdict == "ALLOW"`` |
| ``verdict`` | str | ``"ALLOW"`` / ``"BLOCK"`` / ``"ABORT"`` |
| ``reason`` | str | stable reason code |
| ``decision_id`` | str | per-evaluation UUID |
| ``policy_id`` | str | identifier of the policy that produced the decision |
| ``matched_rule_id`` | Optional[str] | id of the rule that matched, if any |

### Optional audit hook

Pass an ``audit_file=Path(...)`` to emit one compact JSONL record per
evaluation, using the same shape the CLI writes:

```python
evaluator = ActionEvaluator(audit_file="agent-audit.jsonl", audit_profile="safe_defaults")
```

Audit writes inside the adapter are non-fatal — if the file cannot be
written, the evaluation still returns its response. Callers that
require fail-closed audit behaviour should write records themselves
via ``siphrix.audit.write_audit_record``.

## Integration pattern (LLM-agnostic)

Siphrix is provider-independent. It has no SDK dependency on
OpenAI, Anthropic, Gemini, or any other model — it never calls an
LLM. What you hand to ``ActionEvaluator.evaluate`` is a plain Python
dict that conforms to the :class:`PolicyInput` contract. Any
producer of such a dict — tool-calling, function-calling, rule
engines, hand-written planners — integrates the same way.

The canonical shape is three clearly separated stages:

```
intent  ->  evaluation  ->  execution
```

- **intent** — your LLM / planner / rule engine yields a structured
  action dict.
- **evaluation** — one call to
  ``ActionEvaluator.evaluate(intent) -> ActionEvaluationResponse``.
  Siphrix touches your code only here.
- **execution** — your executor runs the side-effect. The examples
  gate on ``response.allowed``; under the audit-first default the
  verdict is recorded data — whether to gate on it is your choice.

Runnable, vendor-neutral examples live under [examples/](examples/):

- [examples/basic_agent/](examples/basic_agent/) — the minimum
  viable integration: one hardcoded action, one evaluation,
  allow/block.
- [examples/tool_calling/](examples/tool_calling/) — a fake LLM
  that returns a structured intent dict; trivially swappable for a
  real model.
- [examples/universal_pattern/](examples/universal_pattern/) — the
  canonical ``intent -> evaluation -> execution`` pipeline as a
  drop-in ``process(prompt)`` function.

None of the examples import a provider SDK. Replace the intent
producer with your own model integration and Siphrix stays
unchanged in the middle.

## Diagnostics (doctor)

``siphrix doctor`` runs a small set of product-facing checks and
prints a compact report. It reuses the canonical health dataclasses
from :mod:`siphrix.readiness`, so every check result conforms to the
same :class:`SubsystemHealth` / :class:`SystemHealthReport` shape used
elsewhere in the codebase.

### Checks (canonical order)

| Check | Purpose |
| --- | --- |
| ``core_package`` | Top-level public API imports cleanly (``run_pipeline``, ``PolicyManager``, ``ActionEvaluator``, ``PolicyInput``, ``__version__``). |
| ``shipped_policy_packs`` | Every entry in ``SUPPORTED_BASE_POLICIES`` resolves and its YAML file exists on disk. |
| ``config_file`` | The TOML at ``--config PATH`` loads via ``load_runtime_config``. ``SKIPPED`` if no ``--config``. |
| ``policy_file`` | The file at ``--policy-file PATH`` exists on disk. ``SKIPPED`` if no ``--policy-file``. |

### Exit codes

| Verdict | Exit | Meaning |
| --- | --- | --- |
| ``READY`` | ``0`` | All applicable checks healthy. |
| ``DEGRADED`` | ``0`` | Some checks degraded but none unhealthy — usable. |
| ``NOT_READY`` | ``1`` | At least one check is unhealthy — the installation or the supplied input is broken. |

### CLI example

```powershell
siphrix doctor
# siphrix doctor: READY: 2/4 healthy
#   [HEALTHY] core_package: Siphrix core package importable.
#   [HEALTHY] shipped_policy_packs: All 3 shipped policy packs present on disk.
#   [SKIPPED] config_file: No --config provided.
#   [SKIPPED] policy_file: No --policy-file provided.

siphrix --config ./siphrix.toml --policy-file ./policy.yaml doctor
```

The summary counts HEALTHY checks against the total number of checks;
SKIPPED checks are counted in the denominator but not the numerator.
``2/4 healthy`` with the other two SKIPPED is the steady-state when no
optional inputs are supplied.

When ``--config`` or ``--policy-file`` points at something broken the
doctor reports it as a structured check (not a pre-dispatch error):

```powershell
siphrix --policy-file /does/not/exist.yaml doctor
# siphrix doctor: NOT_READY: 2/4 healthy, 1 unhealthy
#   [HEALTHY]   core_package: Siphrix core package importable.
#   [HEALTHY]   shipped_policy_packs: All 3 shipped policy packs present on disk.
#   [SKIPPED]   config_file: No --config provided.
#   [UNHEALTHY] policy_file: Policy file not found: /does/not/exist.yaml
# (exit code: 1)
```

### Python example

```python
from siphrix.doctor import run_doctor

report = run_doctor()
print(report.summary)
for check in report.subsystems:
    print(check.subsystem_id, check.status, check.message)
```

## Decision explanation

For logs, tests, and user-facing messaging, Siphrix provides a
compact, stable projection of a policy decision. It lives under
:mod:`siphrix.explanation`.

```python
from siphrix import ActionEvaluator
from siphrix.explanation import explain_action_evaluation

response = ActionEvaluator().evaluate({
    "action_name": "http_get",
    "resource.domain": "example.com",
})
explanation = explain_action_evaluation(response)

print(explanation.summary)
# BLOCK (policy_empty_allowlist) — policy=policy_v0 rule=-

print(explanation.to_dict())
# {"allowed": False, "verdict": "BLOCK", "reason": "policy_empty_allowlist",
#  "policy_id": "policy_v0", "matched_rule_id": None,
#  "decision_id": "...", "summary": "BLOCK (policy_empty_allowlist) — ..."}
```

Callers holding a raw :class:`PolicyDecision` use :func:`explain_decision`
instead; both factories produce identical :class:`DecisionExplanation`
objects for the same underlying decision.

### Contract

| Field | Type | Source |
| --- | --- | --- |
| ``allowed`` | bool | ``True`` iff ``verdict == "ALLOW"`` |
| ``verdict`` | str | ``"ALLOW"`` / ``"BLOCK"`` / ``"ABORT"`` |
| ``reason`` | str | stable reason code |
| ``policy_id`` | str | identifier of the policy that produced the decision |
| ``matched_rule_id`` | Optional[str] | id of the rule that matched, if any |
| ``decision_id`` | str | per-evaluation UUID |
| ``summary`` | str | ``"{verdict} ({reason}) — policy={policy_id} rule={matched_rule_id or '-'}"`` |

The engine's internal ``decision_graph`` and ``explain`` payloads are
**not** part of this surface — they vary by engine code path and are
not considered stable product API. Callers that need them should
continue to use ``PolicyManager().decide(...)`` and read
``PolicyDecision.decision_graph`` / ``PolicyDecision.explain``
directly.

## Audit

``siphrix run`` and ``siphrix policy-check`` can emit one compact
audit record per invocation. Audit is **opt-in**: without
``--audit-file PATH``, nothing is written.

```powershell
siphrix --audit-file ./siphrix-audit.jsonl run --input "hello"
```

### Output format

One JSON line per event, using the same envelope the rest of the
codebase writes through ``siphrix.logs.logger.log_event``:

```json
{"ts":"2026-04-19T12:34:56.789+00:00","event_type":"runtime_run","payload":{"profile":"default","status":"ok","input_summary":{"input_length":5,"input_preview":"hello"},"result_summary":{"outcome":"block","law_id":"CANON-022","reason_code":"COHERENCE_NO_DECISION","final_text_length":0},"outcome_id":null,"error_message":null}}
```

### Record shape

Payload fields (stable):

| Field | Type | Notes |
| --- | --- | --- |
| ``profile`` | str | ``"default"`` / ``"safe_defaults"`` / ``"enterprise_defaults"`` / ``"dev_agent_defaults"`` |
| ``status`` | str | ``"ok"`` / ``"failed"`` / ``"invalid_input"`` |
| ``input_summary`` | object | Compact; see below |
| ``result_summary`` | object | Compact; see below |
| ``outcome_id`` | string or null | ``decision.decision_id`` for ``policy_check``; ``null`` for ``runtime_run`` |
| ``error_message`` | string or null | Present only when ``status != "ok"`` |

Envelope fields: ``ts`` (ISO-8601 UTC), ``event_type`` (``"runtime_run"`` or ``"policy_check"``).

``input_summary`` for ``runtime_run``: ``{"input_length": int, "input_preview": str}`` — the preview is truncated to 128 characters.

``input_summary`` for ``policy_check``: a subset of ``action_name``,
``resource.domain``, ``origin``, ``category``, ``effect_type``,
``risk_level``, ``trust_boundary``, ``intent`` — keys are present only
when the corresponding field was supplied. ``params``,
``resource`` (single-string form), ``resource.path``, ``resource.raw``,
``resource.scheme``, ``agent_id``, ``tool_id`` and ``source`` are **not**
emitted, because they can carry secrets, IDs, or high-cardinality
values that do not belong in a compact audit record.

``result_summary`` for ``runtime_run``:
``{"outcome", "law_id", "reason_code", "final_text_length"}``. The
runtime text itself is not included.

``result_summary`` for ``policy_check``:
``{"verdict", "reason", "policy_id", "matched_rule_id"}``.

### Behavior on audit write failure

Audit writing is **non-fatal by design**. If the audit file cannot be
opened or appended to (permission denied, disk full, bad parent
directory), the CLI prints a single warning line to stderr —

```
siphrix: audit write failed: <reason>
```

— and the primary operation's exit code is unchanged. Operators who
require fail-closed audit behaviour should wrap the CLI in a
supervisor that inspects this stderr line.

### Inspecting an audit file

Two read-side subcommands consume the JSONL shape above.

``siphrix audit-tail`` prints the most recent N records (default
10) as compact one-line summaries:

```powershell
siphrix audit-tail --file ./siphrix-audit.jsonl --tail 3
# 2026-04-19T20:29:44.134957+00:00  policy_check   ok             verdict=BLOCK reason=policy_empty_allowlist
# 2026-04-19T20:29:44.135151+00:00  policy_check   ok             verdict=BLOCK reason=policy_empty_allowlist
# 2026-04-19T20:29:44.135320+00:00  policy_check   invalid_input  error=action_name missing
```

Each line shows the timestamp, ``event_type``, ``status``, and a
single compact result indicator that depends on the event:

- ``runtime_run`` + ``status=ok`` → ``outcome=<value>``
- ``policy_check`` + ``status=ok`` → ``verdict=<value> reason=<value>``
- any non-``ok`` status → ``error=<error_message>``

``siphrix audit-summary`` walks the whole file once and prints
deterministic counts:

```powershell
siphrix audit-summary --file ./siphrix-audit.jsonl
# audit summary for ./siphrix-audit.jsonl
#   total records:      6
#   malformed lines:    0
#
#   by event_type:
#     policy_check:  3
#     runtime_run:   3
#   by status:
#     ok:             5
#     invalid_input:  1
#   top verdicts (policy_check):
#     BLOCK:  2
#   top reasons:
#     policy_empty_allowlist:  2
```

Counts are sorted by count descending, ties broken by key ascending —
identical runs produce byte-identical output.

**Malformed-line behavior.** Lines that are not valid JSON, are not
a JSON object, or are missing the ``ts`` / ``event_type`` / ``payload``
envelope keys are **counted and skipped**, not fatal. ``audit-summary``
reports the count in its ``malformed lines`` field; ``audit-tail``
surfaces a non-zero count on stderr as:

```
siphrix audit-tail: skipped N malformed line(s)
```

Blank lines are ignored and do not count as malformed. Only a
missing or unreadable file causes the commands to exit with code
``3``.

## Command-line interface

Installing the package registers a single console script, ``siphrix``.
``siphrix --help`` lists every subcommand; the ones worth knowing by
name are grouped here.

**Setting a machine up**

| Subcommand | Purpose |
| --- | --- |
| ``set-up`` | Connect this machine and wire up everything on it. |
| ``agent-setup`` | Register the coding-agent hooks and export a policy. |
| ``bridge`` | Register / list / revoke the browser bridge. |
| ``cloud-connect`` · ``cloud-status`` · ``cloud-disconnect`` | Manage the console link. |
| ``cloud-sync`` | Sync now (``--watch`` keeps reporting on its own). |

**Reading the record**

| Subcommand | Purpose |
| --- | --- |
| ``analytics`` | Real metrics from the local audit log. |
| ``audit-tail`` · ``audit-summary`` | Recent records; counts by type, status, verdict, reason. |
| ``audit-explorer`` | Local audit explorer + evidence bundle. Never uploads. |
| ``verify-evidence`` | Check an exported evidence pack offline — no network, no account. |
| ``fingerprint`` | Fingerprint a file so it can be matched against the record. |
| ``console`` | Launch the app (service + web UI) and open it. |

**Evaluating and diagnosing**

| Subcommand | Purpose |
| --- | --- |
| ``demo`` | The official four-flow product demo. |
| ``doctor`` · ``diagnostics`` | Readiness report; sanitized support bundle. |
| ``evaluate`` / ``policy-check`` | Evaluate a JSON action context. |
| ``run`` | Execute the runtime pipeline on one input. |
| ``packs`` · ``pack-export`` · ``policy-validate`` | Shipped packs; pack→engine YAML; validate a policy. |
| ``serve`` | Run the loopback local-console daemon. |
| ``version`` · ``status`` | Installed version; read-only local summary. |

Top-level flags (apply to every subcommand): ``--config PATH``,
``--profile NAME``, ``--policy-file PATH``, ``--audit-file PATH``.

```powershell
siphrix version
# 1.8.1

siphrix run --input "read the quarterly report"
# outcome: proceed
# law_id: -
# reason_code: -
# final_text: [local-llm] received: read the quarterly report

siphrix policy-check --context ./ctx.json
# verdict: BLOCK
# decision_id: 8f2e...-...
# reason: policy_empty_allowlist
# policy_id: policy_v0
# matched_rule_id: -

siphrix packs
# safe_defaults: Conservative defaults for general AI assistants.
# enterprise_defaults: Enterprise-oriented defaults with controlled internal execution.
# dev_agent_defaults: Developer-oriented defaults for local testing and controlled experimentation.

siphrix doctor
# siphrix doctor: READY: 2/4 healthy
#   [HEALTHY] core_package: Siphrix core package importable.
#   [HEALTHY] shipped_policy_packs: All 3 shipped policy packs present on disk.
#   [SKIPPED] config_file: No --config provided.
#   [SKIPPED] policy_file: No --policy-file provided.
```

``siphrix run`` without ``--input`` prompts once on stdin. The two
"outcome-bearing" subcommands (``run`` and ``policy-check``) report
the policy outcome in their **output**, not in the exit code.

Exit codes:

| Code | Meaning |
| --- | --- |
| ``0`` | Success. For ``run`` / ``policy-check``, the outcome is in the output. For ``doctor``, the verdict is ``READY`` or ``DEGRADED``. |
| ``1`` | Unexpected internal failure during ``run`` / ``policy-check``, or ``doctor`` reported ``NOT_READY``. |
| ``2`` | Argparse usage error. |
| ``3`` | User input error: invalid ``--config`` / ``--profile`` / ``--policy-file``, missing or malformed ``--context`` JSON, or a policy-input shape violation. |

## Integration surfaces

Siphrix is local-first. The engine runs on your machine, the record is
written on your machine, and nothing is sent anywhere until you connect
it to a console you chose.

### A. SDK — direct Python integration

```python
from siphrix import ActionEvaluator

result = ActionEvaluator().evaluate({
    "action_name": "file_read",
    "effect_type": "READ_ONLY",
    "risk_level": "LOW",
})
```

`result` is an `ActionEvaluationResponse`:

| Field | Meaning |
| --- | --- |
| `result.allowed` | Boolean — true only when the engine returns `ALLOW`. |
| `result.verdict` | `ALLOW` / `BLOCK` / `ABORT`. |
| `result.reason` | Stable short reason code. |
| `result.decision_id` | UUID for the audit trail. |
| `result.policy_id` | Identifier of the policy that produced the decision. |
| `result.matched_rule_id` | Rule that matched, or `None`. |
| `result.risk_score` / `result.risk_band` | Optional risk envelope. |

Invalid intent dicts raise `PolicyInputValidationError` with a short,
stable message — no path leak, no traceback in user-facing output.

### B. Agent hooks

```bash
siphrix agent-setup            # exports a policy, registers the hooks
```

Registers the Claude Code `PreToolUse` and `PostToolUse` hooks and
configures Codex's advisory MCP tools where present. The operator's
console rules govern these hooks: flip a rule in the console and the
very next tool call obeys it. Block-only — the overlay can add blocks
but never unblocks an engine BLOCK.

`siphrix set-up --code <code>` does this and everything else for the
machine in one command.

### C. MCP — two ways round

**`siphrix mcp-wrap` — the whole ecosystem, one adapter.**

```bash
siphrix mcp-wrap -- npx -y @modelcontextprotocol/server-filesystem /repo
```

Point your MCP client at that instead of the server. Siphrix launches
the server exactly as the client would have, passes every byte through
in both directions, and writes down each tool call as it goes — which
tool, on what, at what risk. The agent sees the server it asked for; the
server sees the agent it expected.

This is the cheapest coverage in the product. Every other integration is
one adapter for one agent; an agent that speaks MCP describes every tool
call in one wire format, so standing between the two covers every server
anybody writes without Siphrix knowing what any of them do.

In your client's config, that is one line changed:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "siphrix",
      "args": ["mcp-wrap", "--", "npx", "-y",
               "@modelcontextprotocol/server-filesystem", "/repo"]
    }
  }
}
```

Arguments are read for *what the call was about* — a path, a command, a
URL — from a closed list of argument names, secret-masked. Everything
else is counted, not copied: a tool argument can just as easily hold a
document's whole text, and an audit log that is a copy of the data it
describes is not an audit log.

Audit-first: the verdict is recorded and the call proceeds. Only
`SIPHRIX_MODE=enforce` turns a BLOCK into a refusal returned in the
server's place — as a protocol-correct error, never a dropped message
that would leave the agent waiting.

**`siphrix.mcp.server` — Siphrix as a tool the agent can consult.**

```bash
python -m siphrix.mcp.server
```

Verdict-only, stdio-only, stdlib-only. It binds no socket and opens no
network listener. Three tools: `siphrix_evaluate_action`,
`siphrix_explain_action`, `siphrix_list_policy_packs`. Contract:
[docs/architecture/MCP_SERVER_BOUNDARY.md](docs/architecture/MCP_SERVER_BOUNDARY.md).

### D. Two surfaces for software that will not cooperate

Every integration above needs something to meet it halfway. Plenty of
software offers none — a closed desktop app, a vendor CLI, a script
somebody wrote two years ago — and two things are still true of almost
all of it.

**It talks to an LLM over HTTP.**

```bash
siphrix llm-proxy
export OPENAI_BASE_URL=http://127.0.0.1:8785/openai/v1
export ANTHROPIC_BASE_URL=http://127.0.0.1:8785/anthropic
```

A loopback reverse proxy in front of eleven providers. Every call is
recorded — provider, model, endpoint, token counts, timing — then
forwarded unchanged.

A reverse proxy, not an interceptor, and the distinction is the design:
no certificate to install, nothing decrypted that was not addressed
here, no traffic captured that was not deliberately routed. A tool
becomes visible because somebody pointed it here, never because this was
running. The provider map is closed — an open forwarder on a well-known
local port is an exfiltration tool waiting to be found.

The prompt and the completion pass through and are not kept. The API key
is a header on the way through and is written nowhere.

**Or it already emits OpenTelemetry.**

```bash
siphrix otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:8787
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
```

LangChain, the OpenAI Agents SDK, LlamaIndex and Semantic Kernel already
emit spans following the GenAI semantic conventions. One receiver covers
what would otherwise be five adapters against five APIs producing the
same span. Only spans that say they are GenAI spans are taken; prompt
and completion attributes travel in OTLP and are never read.

### E. Browser bridge

```bash
siphrix bridge                 # register it (set-up does this for you)
siphrix bridge --list          # who may open it, and who is installed
siphrix bridge --revoke <id>   # take one extension's access away
```

Three messages, and no more: `hello`, `enrolment`, `record`. The
extension asks *whether* the machine is connected, never *with what*;
`record` only appends, cannot read anything, cannot change a setting and
cannot reach the network. Every field is re-derived from a closed set,
because an audit trail that prints whatever it is handed is not an audit
trail.

### F. CLI — human-readable and machine-readable

```bash
siphrix demo                                  # official walkthrough
siphrix status                                # read-only local summary
siphrix analytics                             # real metrics from the log
siphrix evaluate --context ./ctx.json         # evaluate an action context
siphrix run --input "read the report"         # runtime pipeline on one input
siphrix policy-validate --file ./policy.yaml  # validate, do not apply
siphrix cloud-status                          # server + last sync, never the token
```

Every command above carries `--json`, emitting a stable payload with no
banners and no stderr noise on success:

```bash
siphrix demo --json
siphrix status --json
siphrix policy-check --context ./ctx.json --json
siphrix evaluate --context ./ctx.json --json
siphrix run --input "read the report" --json
siphrix policy-validate --file ./policy.yaml --json
```

| Command | Schema identifier |
| --- | --- |
| `siphrix demo --json` | `official_demo_v1` |
| `siphrix status --json` | `status_result_v1` |
| `siphrix policy-check --json` / `evaluate --json` | `policy_check_result_v1` |
| `siphrix run --json` | `pipeline_result_v1` |
| `siphrix policy-validate --json` | `policy_validate_result_v1` |

Notes:

- `siphrix evaluate` is a friendly alias of `siphrix policy-check` —
  same handler, exit codes, output and schema.
- `siphrix policy-validate` validates structure and schema only. It does
  not apply the policy, write audit, mutate environment, or contact the
  network.
- `siphrix status` is strictly read-only.
- `siphrix run --json` never surfaces raw user input — it emits
  `input_length` instead of the prompt text.
- Every user-configurable path is surfaced in JSON as the scrubbed
  `file:<basename>` form, so absolute or temp paths cannot leak.

### G. Hosted service tier

`siphrix.service` is a real, persistent, multi-tenant service: orgs,
API keys, RBAC, decision API, audit ingestion with a tamper-evident hash
chain, approvals, device enrolment, policy versions, SSO (OIDC with PKCE
and browser-bound state), SCIM, OData feeds, billing plans and retention.

SQLite by default; Postgres via `pip install "siphrix[postgres]"`.
Deployment: [deploy/](deploy/) (Docker, Caddy, Fly.io) and
[deploy/FLY.md](deploy/FLY.md).

Devices push evidence and pull rules over one authenticated heartbeat —
outbound only, no listener, no background worker. A device token can
append audit stamped with its own identity and read its own policy;
it cannot write policy. Visibility flows up, authority flows down.


## Run The Demo

Supported interactive demo entrypoint:

```powershell
python examples/app.py
```

Canonical smoke runner (ships in the wheel; requires repo fixtures to run from
a git checkout):

```powershell
python -m siphrix.tests_all_global
```

Practical integration examples:

```powershell
python examples/example_agent.py --list-scenarios
python examples/example_agent.py --scenario blocked_delete
python examples/example_agent.py --scenario review_quarterly_report
python examples/example_agent.py --scenario allow_report_read
python examples/example_agent.py --scenario allow_developer_shell
python examples/example_agent_openai.py --policy safe_defaults --input "read report"
python tools/integration_control_panel.py
```

What is real versus simulated in these examples:

- real
  policy loading, role-aware resolution, planner normalization, Siphrix pipeline calls, policy decisions, and local audit logging
- simulated
  file changes, email sending, shell execution, and other real-world side effects

## Run Tests

```powershell
# Compile check (import-time validation)
python -m compileall -q siphrix

# Repo-level test suite (uses pytest with pythonpath = . examples tools)
python -m pytest -q

# Or via unittest discover (identical coverage)
python -m unittest discover -s tests -q

# Package-internal public-API suite (repo-only; not shipped in the wheel)
python -m unittest discover -s siphrix/tests -t .

# Canonical smoke runner
python -m siphrix.tests_all_global
```

In a normal developer environment, `python -m pytest -q` resolves to the real
external `pytest` package from the dev dependencies. The repository also keeps
a repo-local fallback module under `siphrix/devtools/pytest_fallback/` for
self-contained offline validation; it is internal-only and is excluded from
the distributed wheel.

## Getting Started

For a new developer, the shortest useful path is:

1. Create and activate a virtual environment.
2. Install the package in editable mode with dev extras: `python -m pip install -e ".[dev]"`.
3. Run `python -m pytest -q` to execute the repo-level test suite.
4. Run `python -m unittest discover -s siphrix/tests -t .` for the package-internal public-API suite.
5. Run `python -m siphrix.tests_all_global` for the canonical smoke runner.
6. Run `python examples/example_agent.py --list-scenarios` to see the curated demo set.
7. Run `python examples/example_agent.py --scenario blocked_delete` and `python examples/example_agent.py --scenario allow_report_read`.
8. If you have `OPENAI_API_KEY` and the SDK installed, run `python examples/example_agent_openai.py --policy safe_defaults --input "read report"`.
9. Launch `python tools/integration_control_panel.py` for interactive testing.

## Architecture Map

High-level package map:

- `siphrix.orchestrator`
  The supported top-level pipeline used by `run_pipeline` and the interactive demo.
- `siphrix.foundation`
  ASPL parsing and typing, policy compilation, audit chains, proof bundles, proof-of-execution helpers, and signing primitives.
- `siphrix.contracts`
  Typed boundary contracts for all system interfaces including execution, planner, simulation, risk, and multi-agent surfaces.
- `siphrix.trust`
  Policy signing, activation, policy anchors, remote sync, attestation, trust epochs, heartbeat validation, integrity ledgers, and trust seals.
- `siphrix.runtime`
  Policy evaluation, execution interception, mediation, broker flows, sessions, capabilities, enforcement, evidence ledgers, and kernel syscall interception decisions.
- `siphrix.governance`
  Governance records, decision ledgers, explainability, delegated authority, exceptions, quorum flows, compliance export, replay, and retention workflows. Internally sub-packaged into `audit/`, `compliance/`, `evidence/`, `incident/`.
- `siphrix.policy_versioning`
  Policy lifecycle management, versioning, replay, overlays, activation, distribution, evaluation, and explanation. Internally sub-packaged into 14 semantic domains.
- `siphrix.analysis`
  Trajectory analysis, threat graphs, formal checks, bounded exploration, cross-agent reasoning, escalation, simulation, determinism, and zero-trust style replay helpers.
- `siphrix.resilience`
  Tenant isolation, distributed enforcement concepts, temporal policy/state machinery, health monitoring, healing, adaptive guardrails, and failure containment.
- `siphrix.risk`
  Risk scoring, factor engine, thresholds, orchestration, and execution adapters.
- `siphrix.simulation`
  Simulation engine and audit for non-executing policy evaluation modeling.
- `siphrix.integration_flow`
  Integration flow orchestration connecting context, planner, and executor adapters.
- `siphrix.adapters`
  Pluggable context, executor, framework, and planner adapters with a registration registry.
- `siphrix.console`
  Operator dashboard, workspaces, projections, and operator commands.
- `siphrix.engine`
  Canonical manifests, stack declarations, gap summaries, unified engine summaries, and the supported cross-layer runner surface.

The surfaces that reach the outside world:

- `siphrix.service`
  The hosted tier: orgs, API keys, RBAC, decision API, audit ingestion with the tamper-evident hash chain, approvals, device enrolment, policy versions, SSO/SCIM, OData, billing, retention.
- `siphrix.integrations`
  Where Siphrix meets the things it observes: the Claude Code hook, the browser bridge (`native_host`), the editor witness layer, machine setup, file watching and fingerprinting.
- `siphrix.cloud_link`
  The machine side of enrolment: outbound-only pull of rules and push of evidence, request-driven, with no background worker.
- `siphrix.machine`
  This computer's stable identity, so everything installed on it joins one row in the console rather than appearing as three.
- `siphrix.mcp`
  The verdict-only MCP server, stdio and stdlib only.
- `siphrix.local_daemon`
  The loopback HTTP surface the extensions and editors talk to; bearer-authenticated, no public bind.
- `siphrix.redaction` / `siphrix.sensitive`
  One masker and one sensitive-path classifier, shared by every writer — two copies of "mask the secrets" is how a leak happens.

For a short architecture reference, see [docs/architecture/ARCHITECTURE_v1.md](docs/architecture/ARCHITECTURE_v1.md).

## Repository Layout

Installable Python package (ships in the wheel):

- `siphrix/` canonical product code and runtime modules
- `siphrix/policies/*.yaml` canonical policy assets
- `siphrix/policy_packs/*.yaml` + `roles/*.yaml` deployment-oriented policy profiles
- `siphrix/canon/*.yaml` + `canon_v1.md` canon laws and conformance data
- `siphrix/integrations/` canonical integration library (audit, layer_utils, snapshot)
- `siphrix/examples/` compatibility re-export shims (canonical paths are in `siphrix.integrations`)

Repository-only (not installed by the wheel):

- `tests/` repo-level unit and compatibility coverage (shipped in sdist for auditability)
- `tests/fixtures/keys/` committed example/dev crypto fixtures used by local validation
- `tests/fixtures/policies/compat/` compatibility policy fixtures
- `siphrix/tests/` package-internal public-API test suite (repo-only, not shipped)
- `siphrix/research/` internal research notes (repo-only, not shipped)
- `siphrix/devtools/pytest_fallback/` offline pytest fallback (repo-only, not shipped)
- `docs/` release, architecture, and integration documentation (shipped in sdist)
- `examples/` runnable demo agents (repo-only, not packaged)
- `tools/` supported operator scripts (repo-only, not packaged)
- `scripts/` dev scripts (repo-only, not packaged)

Planner adapters live in `siphrix.adapters.planners`. Planner contracts live
in `siphrix.contracts.planner`.

Only example or smoke-safe assets should live in tracked paths. Real private
keys, operator secrets, tenant-specific ledgers, and environment-specific trust
material must not be committed. The `.gitignore` already excludes the standard
runtime output paths (logs, state, caches, `.venv`, `__pycache__`, generated
artefacts).

## Key Hygiene

- committed key-like files in this repo are example/dev fixtures only
- local smoke and tests use example fixtures under `tests/fixtures/keys`
- real signing keys should live outside the repository or in untracked local secret storage
- do not place operator or production private keys in `artifacts/` or any tracked path

## Internal Hardening Model

Siphrix enforces defence-in-depth at several internal boundaries.  Each
limit is small, explicit, and covered by dedicated tests so a future
change cannot silently weaken the guarantee.

- **Canon DSL evaluator** (`siphrix.canon.canon_runtime`): trigger
  conditions in Canon YAML are parsed with ``ast.parse`` in ``mode="eval"``
  and walked through a strict node whitelist before any value is read.
  No Python builtins, function calls, subscripts, comprehensions,
  arithmetic, or attribute calls can reach the evaluator.  Any
  disallowed construct is rejected up front.  See
  [tests/test_canon_runtime_hardening.py](tests/test_canon_runtime_hardening.py).

- **Shell executor** (`siphrix.adapters.executors.shell_executor`): only
  the hostname/whoami allowlist is executable.  Commands are bounded
  in length (512 chars), argument count (16), and wall-clock time
  (10 s subprocess timeout).  Null bytes, newlines, and shell
  metacharacters are rejected before ``shlex.split`` runs.  See
  [tests/test_shell_executor_hardening.py](tests/test_shell_executor_hardening.py).

- **HTTP interception** (`siphrix.exec_intercept.intercept`): the
  ``http_get`` choke point enforces an ``http``/``https`` scheme
  allowlist before any policy or network work runs, and caps response
  bodies at 16 MiB to keep callers memory-bounded.  See
  [tests/test_intercept_http_hardening.py](tests/test_intercept_http_hardening.py).

- **Public API surface**: every canonical package publishes an explicit
  ``__all__``.  ``siphrix.governance`` and
  ``siphrix.policy_versioning`` list every re-exported symbol by name
  instead of using wildcard re-exports.  See
  [tests/test_public_api_surface.py](tests/test_public_api_surface.py).

## Security and contribution

- Vulnerability reports and the detailed threat model live in
  [SECURITY.md](SECURITY.md).
- Development workflow, test commands, and rules for adding canon laws
  or public API symbols live in [CONTRIBUTING.md](CONTRIBUTING.md).
- A cross-platform [Makefile](Makefile) exposes the common developer
  tasks (``make smoke``, ``make test``, ``make coverage``, ``make clean``).

## What is next

Nearest first.

- **A signed installer.** Without Authenticode, SmartScreen shows every
  first-time installer the blue "Windows protected your PC" screen. The
  signing path is built and opt-in (`build-installer.ps1 -Sign`); the
  certificate is a procurement step, not a code one.
- **macOS and Linux parity for setup.** The engine, the CLI, the hooks
  and the MCP server already run everywhere Python does. The browser
  bridge registers on all three platforms; the scheduled reporting task
  is Windows-only and wants a `launchd` / `systemd --user` equivalent.
- **More of the ecosystem, without an adapter each.**
  `siphrix mcp-wrap` puts the record between any MCP client and any MCP
  server; an OTLP receiver would do the same for anything already
  emitting GenAI spans. Both beat writing one integration per framework.
- **A JavaScript SDK on npm**, so a Node agent has the same one-call
  surface Python has.

Further out, and honest about being further out: a concrete Linux
enforcement backend behind the existing decision model (seccomp/eBPF),
hardware-rooted attestation instead of software-only local attestations,
and real multi-node policy distribution.

## Development Rules

- extend canonical non-versioned modules
- do not add new `vXX` package paths
- keep backward compatibility unless correctness requires a change
- treat generated logs, caches, ledgers, and smoke outputs as disposable runtime material

## Release Documents

- [docs/release/RELEASE_RUNBOOK.md](docs/release/RELEASE_RUNBOOK.md) — the canonical live release runbook (three-gate model, preflight checklist, one release-gate command, publish + rollback steps).
- `.github/workflows/release-gate.yml` — CI release gate (runs tools/release_check.py)

## UI / Dashboard

The local operator dashboard is implemented and framework-free. It
ships as [`tools/siphrix_dashboard/`](tools/siphrix_dashboard/)
in the repository (it is not part of the installable wheel) and is
served by a stdlib-only dev server on `127.0.0.1:8765`. No npm, no
bundler, no external frontend dependencies.

```powershell
python tools/siphrix_dashboard/dev_server.py
# then open http://127.0.0.1:8765/
```

Eight pages are wired against existing product surfaces —
**Overview**, **Readiness**, **Policy Packs**, **Release State**,
**Audit**, **Policy Check**, **Demo**, **Runtime**. The first four
are read-only projections of the doctor / packs / release-check
JSON; the latter four are narrow ephemeral endpoints over the
canonical `PolicyManager.decide()` / `run_pipeline()` / `siphrix
demo` product calls. No write path, no terminal emulator, no
filesystem browser.

- [docs/ui/SHOWCASE.md](docs/ui/SHOWCASE.md) — 5-minute operator
  tour covering version / doctor → demo → dashboard → release gate.
- [docs/ui/README.md](docs/ui/README.md) — UI documentation entry
  point, philosophy, and tech stance.
- [docs/ui/DASHBOARD.md](docs/ui/DASHBOARD.md) — canonical
  dashboard spec: page map, component list, data contracts, UI
  states, explicit out-of-scope list.
- [tools/siphrix_dashboard/README.md](tools/siphrix_dashboard/README.md)
  — implementation-level walkthrough (endpoints, safety limits,
  what each page binds to).
- [docs/release/FINAL_RELEASE_STATUS.md](docs/release/FINAL_RELEASE_STATUS.md)
- [docs/release/SIPHRIX_FULL_ABSORPTION_AUDIT.md](docs/release/SIPHRIX_FULL_ABSORPTION_AUDIT.md)
- [docs/architecture/ARCHITECTURE_v1.md](docs/architecture/ARCHITECTURE_v1.md)
- [docs/architecture/policy_versioning.md](docs/architecture/policy_versioning.md)
- [CHANGELOG.md](CHANGELOG.md)
- [docs/integration/CONTROL_PANEL.md](docs/integration/CONTROL_PANEL.md)
- [docs/integration/HOW_TO_INTEGRATE_SIPHRIX.md](docs/integration/HOW_TO_INTEGRATE_SIPHRIX.md)
- [docs/integration/PLANNER_ADAPTERS.md](docs/integration/PLANNER_ADAPTERS.md)
- [docs/release/RELEASE_NOTES_v1.md](docs/release/RELEASE_NOTES_v1.md)
- [docs/release/RELEASE_CONTENTS_v1.md](docs/release/RELEASE_CONTENTS_v1.md)

## What is free, what is paid

**The code is MIT. All of it** — engine, bridges, console and service
tier. `pip install siphrix` gives you the whole product on your own
machine, forever, with no seat count and no phone-home. There is no
withheld enterprise edition.

**What is paid is the hosted service at siphrix.com** — us running it,
keeping it up, holding the record, and answering for it. Plans limit a
hosted tenancy, not the software; nothing in the code could enforce one
off our servers.

Stated in full, including the claims we refuse to make, in
`LICENSING.md` (shipped in the source distribution) and on
[siphrix.com](https://siphrix.com) — a relative link would be dead
for anyone reading this on PyPI.

## Author

Denis Ghengeaua

Siphrix was originally designed and implemented by Denis Ghengeaua.

