Skip to main content

Siphrix

Siphrix v1.8.2

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 — a 60-second pitch and a short offline smoke block. After installing, the cheat sheet is docs/launch/PUBLIC_HANDOFF.md; the five-minute live tour is docs/launch/DEMO_SCRIPT.md.

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/tool_calling/ and 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

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:

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:

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.

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: 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

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.

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

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.


Integrating your own agent

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

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:

Adoption docs: QUICKSTART · ADVANCED · index.


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.

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_runtimePolicyManager, PolicyDecision, PolicyInput
  • siphrix.policy_packs — shipped packs and resolution
  • siphrix.agentActionEvaluator, ActionEvaluationResponse
  • siphrix.configRuntimeConfig, load_runtime_config
  • siphrix.auditAuditRecord, write_audit_record
  • siphrix.explanationDecisionExplanation, explain_decision
  • siphrix.doctorrun_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_MODEaudit (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

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:

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:

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 packsiphrix 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:

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:

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

[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

# 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:

{
  "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:

{ "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.

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:

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/basic_agent/ — the minimum viable integration: one hardcoded action, one evaluation, allow/block.
  • examples/tool_calling/ — a fake LLM that returns a structured intent dict; trivially swappable for a real model.
  • 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

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):

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

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.

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.

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:

{"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:

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=okoutcome=<value>
  • policy_check + status=okverdict=<value> reason=<value>
  • any non-ok status → error=<error_message>

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

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.

siphrix version
# 1.8.2

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

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

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.

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:

{
  "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.

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.

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.

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.

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

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

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:

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/ (Docker, Caddy, Fly.io) and 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:

python examples/app.py

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

python -m siphrix.tests_all_global

Practical integration examples:

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

# 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.

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.

  • 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.

  • 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.

  • 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.

Security and contribution

  • Vulnerability reports and the detailed threat model live in SECURITY.md.
  • Development workflow, test commands, and rules for adding canon laws or public API symbols live in CONTRIBUTING.md.
  • A cross-platform 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 — 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/ 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.

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.

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 — a relative link would be dead for anyone reading this on PyPI.

Author

Denis Ghengeaua

Siphrix was originally designed and implemented by Denis Ghengeaua.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

siphrix-1.8.2.tar.gz (2.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

siphrix-1.8.2-py3-none-any.whl (1.3 MB view details)

Uploaded Python 3

File details

Details for the file siphrix-1.8.2.tar.gz.

File metadata

  • Download URL: siphrix-1.8.2.tar.gz
  • Upload date:
  • Size: 2.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for siphrix-1.8.2.tar.gz
Algorithm Hash digest
SHA256 3c372c8ec3f8135041f5d8b43d0293b2bfe462e437b9c5f8b80fdcb66de875ac
MD5 ad75800fea71843c3a0e95e066823ca0
BLAKE2b-256 000cbaa58891454ef054c9169b788f202779af622ce72ede64c11412bd65e262

See more details on using hashes here.

File details

Details for the file siphrix-1.8.2-py3-none-any.whl.

File metadata

  • Download URL: siphrix-1.8.2-py3-none-any.whl
  • Upload date:
  • Size: 1.3 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for siphrix-1.8.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d6be439b35950e94474675f5871a67493456736746c04744756f34ac632f18c5
MD5 4a6cceae3e3f270a155f123edb278631
BLAKE2b-256 f4c607ffbeff8f5e29b846e8dab7649936cbb56ecbed018ac08376641d3e64b2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page