Siphrix v1.8.0
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 — setSIPHRIX_POLICY_FILEto 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
Authorizationheader — 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-proxyrecords 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. SetSIPHRIX_POLICY_FILEto 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:
- examples/basic_agent/ — one hardcoded action, one evaluation, allow/block.
- 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/ — the
canonical pipeline as a drop-in
process(prompt)function.
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_runtime—PolicyManager,PolicyDecision,PolicyInputsiphrix.policy_packs— shipped packs and resolutionsiphrix.agent—ActionEvaluator,ActionEvaluationResponsesiphrix.config—RuntimeConfig,load_runtime_configsiphrix.audit—AuditRecord,write_audit_recordsiphrix.explanation—DecisionExplanation,explain_decisionsiphrix.doctor—run_doctorsiphrix.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) orenforce(the frozen legacy blocking mode; explicit opt-in)SIPHRIX_HOME— base directory for all runtime stateSIPHRIX_LOG_PATH— absolute path forevents.jsonlSIPHRIX_STATE_PATH— absolute path forstate.jsonSIPHRIX_REMOTE_POLICY_CACHE— remote-policy cache pathSIPHRIX_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, andsiphrix doctorsays so plainly.hardened— requires a real secret viaSIPHRIX_TRUST_SEAL_SECRET(≥ 32 UTF-8 bytes). Fails closed with a stabletrust_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 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:
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:
- Explicit CLI flags —
--profile NAME,--policy-file PATH. - TOML config file —
--config PATHwith an[siphrix]table. - 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 -> executionpipeline as a drop-inprocess(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=ok→outcome=<value>policy_check+status=ok→verdict=<value> reason=<value>- any non-
okstatus →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.0
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 evaluateis a friendly alias ofsiphrix policy-check— same handler, exit codes, output and schema.siphrix policy-validatevalidates structure and schema only. It does not apply the policy, write audit, mutate environment, or contact the network.siphrix statusis strictly read-only.siphrix run --jsonnever surfaces raw user input — it emitsinput_lengthinstead 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:
- Create and activate a virtual environment.
- Install the package in editable mode with dev extras:
python -m pip install -e ".[dev]". - Run
python -m pytest -qto execute the repo-level test suite. - Run
python -m unittest discover -s siphrix/tests -t .for the package-internal public-API suite. - Run
python -m siphrix.tests_all_globalfor the canonical smoke runner. - Run
python examples/example_agent.py --list-scenariosto see the curated demo set. - Run
python examples/example_agent.py --scenario blocked_deleteandpython examples/example_agent.py --scenario allow_report_read. - If you have
OPENAI_API_KEYand the SDK installed, runpython examples/example_agent_openai.py --policy safe_defaults --input "read report". - Launch
python tools/integration_control_panel.pyfor interactive testing.
Architecture Map
High-level package map:
siphrix.orchestratorThe supported top-level pipeline used byrun_pipelineand the interactive demo.siphrix.foundationASPL parsing and typing, policy compilation, audit chains, proof bundles, proof-of-execution helpers, and signing primitives.siphrix.contractsTyped boundary contracts for all system interfaces including execution, planner, simulation, risk, and multi-agent surfaces.siphrix.trustPolicy signing, activation, policy anchors, remote sync, attestation, trust epochs, heartbeat validation, integrity ledgers, and trust seals.siphrix.runtimePolicy evaluation, execution interception, mediation, broker flows, sessions, capabilities, enforcement, evidence ledgers, and kernel syscall interception decisions.siphrix.governanceGovernance records, decision ledgers, explainability, delegated authority, exceptions, quorum flows, compliance export, replay, and retention workflows. Internally sub-packaged intoaudit/,compliance/,evidence/,incident/.siphrix.policy_versioningPolicy lifecycle management, versioning, replay, overlays, activation, distribution, evaluation, and explanation. Internally sub-packaged into 14 semantic domains.siphrix.analysisTrajectory analysis, threat graphs, formal checks, bounded exploration, cross-agent reasoning, escalation, simulation, determinism, and zero-trust style replay helpers.siphrix.resilienceTenant isolation, distributed enforcement concepts, temporal policy/state machinery, health monitoring, healing, adaptive guardrails, and failure containment.siphrix.riskRisk scoring, factor engine, thresholds, orchestration, and execution adapters.siphrix.simulationSimulation engine and audit for non-executing policy evaluation modeling.siphrix.integration_flowIntegration flow orchestration connecting context, planner, and executor adapters.siphrix.adaptersPluggable context, executor, framework, and planner adapters with a registration registry.siphrix.consoleOperator dashboard, workspaces, projections, and operator commands.siphrix.engineCanonical manifests, stack declarations, gap summaries, unified engine summaries, and the supported cross-layer runner surface.
The surfaces that reach the outside world:
siphrix.serviceThe 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.integrationsWhere 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_linkThe machine side of enrolment: outbound-only pull of rules and push of evidence, request-driven, with no background worker.siphrix.machineThis computer's stable identity, so everything installed on it joins one row in the console rather than appearing as three.siphrix.mcpThe verdict-only MCP server, stdio and stdlib only.siphrix.local_daemonThe loopback HTTP surface the extensions and editors talk to; bearer-authenticated, no public bind.siphrix.redaction/siphrix.sensitiveOne 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 modulessiphrix/policies/*.yamlcanonical policy assetssiphrix/policy_packs/*.yaml+roles/*.yamldeployment-oriented policy profilessiphrix/canon/*.yaml+canon_v1.mdcanon laws and conformance datasiphrix/integrations/canonical integration library (audit, layer_utils, snapshot)siphrix/examples/compatibility re-export shims (canonical paths are insiphrix.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 validationtests/fixtures/policies/compat/compatibility policy fixturessiphrix/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 withast.parseinmode="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 beforeshlex.splitruns. See tests/test_shell_executor_hardening.py. -
HTTP interception (
siphrix.exec_intercept.intercept): thehttp_getchoke point enforces anhttp/httpsscheme 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.governanceandsiphrix.policy_versioninglist 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 --userequivalent. - More of the ecosystem, without an adapter each.
siphrix mcp-wrapputs 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
vXXpackage 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.
- docs/ui/SHOWCASE.md — 5-minute operator tour covering version / doctor → demo → dashboard → release gate.
- docs/ui/README.md — UI documentation entry point, philosophy, and tech stance.
- 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 — implementation-level walkthrough (endpoints, safety limits, what each page binds to).
- docs/release/FINAL_RELEASE_STATUS.md
- docs/release/SIPHRIX_FULL_ABSORPTION_AUDIT.md
- docs/architecture/ARCHITECTURE_v1.md
- docs/architecture/policy_versioning.md
- CHANGELOG.md
- docs/integration/CONTROL_PANEL.md
- docs/integration/HOW_TO_INTEGRATE_SIPHRIX.md
- docs/integration/PLANNER_ADAPTERS.md
- docs/release/RELEASE_NOTES_v1.md
- docs/release/RELEASE_CONTENTS_v1.md
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file siphrix-1.8.0.tar.gz.
File metadata
- Download URL: siphrix-1.8.0.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e97b083de9672b023219a2123983fe0ca1a8f4d2963a543f1ccf1a7136e50f79
|
|
| MD5 |
861558c317a1b4abb5e461c799c43d26
|
|
| BLAKE2b-256 |
384411726f0926ecc6b6301b57b3191253628d932c038cf7c67c670c6bffe6ab
|
File details
Details for the file siphrix-1.8.0-py3-none-any.whl.
File metadata
- Download URL: siphrix-1.8.0-py3-none-any.whl
- Upload date:
- Size: 1.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d2d99534bf6f6ac594d85c9f27b97904f031a202c3516e64799e32e3ea80beb
|
|
| MD5 |
09f0382bc2aea175149a9c10a66dda31
|
|
| BLAKE2b-256 |
f574eecec8bc871ec314b86388b37b1cd8d25a73162386b85f9b888b97039c72
|