Runbook: operate praxis¶
The day-to-day operator loop. praxis defaults to stdio against SQLite with no external services.
Start¶
uv venv --python 3.12 .venv && uv pip install --python .venv -e .
PRAXIS_MODE=guarded PRAXIS_AUDIT_PATH=/var/lib/praxis/audit.jsonl \
.venv/bin/python -m praxis
Modes (ADR-0004): readonly (T0 only), guarded (T0-T2; T3 refused), open
(all tiers, each behind its gate). Start in guarded; raise to open only for a
deliberate T3 with a minted approval token and exactly one target.
Observe (read, T0)¶
ingest_observationparses captured telemetry (osquery/aide/probe/talos) into observed facts. It runs through the audited path (one audit record carrying the raw payload's SHA-256 and length, never the body) and arms the trifecta latch for the session (SEC-4, ADR-0016).query_facts/fact_historyread the bitemporal model, audited per call. A read that returns observed facts also arms the trifecta latch: collected data read back is as untrusted as live collection.drift_scandiffs observed facts against the known-good baseline, audited.
Actuate (DRY_RUN, then approve, then execute)¶
- Call
run_actionwithdry_run: trueto preview. A dry run needs no approval. Its response carries theaction_id; for a gated action the server mints a single-use approval token and prints it on ITS OWN console (stderr), out-of-band from the MCP channel (BL-072, ADR-0016). The token never appears in the tool response, so an autonomous caller cannot read and replay it. - Read the token from the server console, review the preview, then re-issue with
dry_run: falseand that token asapproval_token. The token is single-use, expires afterPRAXIS_APPROVAL_TTL_SECONDS(default 600), and is bound to the exact action, target, tier, and patterns version; for T3 exactly one target is allowed. A restart invalidates pending tokens: re-run the dry run. - Each call writes one audit record (allow, deny, or error). Output bodies are never logged, only their SHA-256 and length.
Trifecta note (SEC-4): once a session has taken in untrusted content (an ingest, or a read returning observed facts), ANY T1+ real run requires a minted approval, enforced inside the audited path itself. Free-form shell via ssh floors at T2 regardless (BL-073), so it always meets the gate.
Configuration this flow needs: PRAXIS_PLAYBOOK_ROOT and PRAXIS_RUNBOOK_ROOT
confine ansible and runbook actions; both adapters refuse outright until their
root is set (fail closed, BL-024/BL-081). Optional budget ceilings
(PRAXIS_MAX_ACTIONS, PRAXIS_MAX_WALL_SECONDS) deny, audited, once exhausted.
Stop everything¶
Call the emergency_stop tool (T0, audited, never gated): it trips the kill
switch immediately and, with PRAXIS_KILL_SWITCH_PATH set, writes a sentinel file
so the stop survives a restart. An operator can also engage the stop out-of-band
by creating the sentinel file (touch), with no tool call at all. Restoring
service is deliberately out-of-band: remove the sentinel file and restart (or call
kill_switch.reset() in-process). Setting PRAXIS_MODE=readonly and restarting
remains a coarser fallback. The credential broker's kill_all also trips the
shared switch (BL-049, BL-075).
Audit sinks and stamping¶
The append-only file at PRAXIS_AUDIT_PATH is the authoritative, tamper-evident sink.
Two opt-in additions are available, both off by default:
PRAXIS_AUDIT_SYSLOG_ADDRESSforwards each (already-redacted) audit line to syslog for SIEM/journald visibility: a Unix socket path (e.g./dev/log) orhost:portfor a remote UDP collector. It is best-effort and fanned out after the authoritative file write, so a down or oversized syslog endpoint never affects the file, the hash chain, orverify_audit.py(BL-100, ADR-0037). The destination is operator-trusted configuration; unlike the model-influenced egress paths it is not run through the SSRF filter, so a local SIEM on an RFC1918/Tailscale address works as intended.PRAXIS_TSA_URLplusPRAXIS_TSA_CERT(the TSA signing certificate, PEM) plus thetsaextra switch evidence checkpoints to a non-forgeable RFC 3161 timestamp authority instead of the keylessLocalStamper(BL-095, ADR-0030). Selection fails closed at startup if the URL is set without the certificate. Leave both unset to keep theLocalStamper, with OS append-only storage (chattr +a/WORM) as the required control.
Audit records carry optional request_id/client_id correlation fields (set per request
by the transport; BL-101, ADR-0038) so concurrent calls can be tied to their entries.
Retention and archival¶
The audit and evidence retention tiers are declared in config:
PRAXIS_AUDIT_RETENTION_DAYS and PRAXIS_EVIDENCE_RETENTION_DAYS (default 365;
0 is indefinite; the anchor follows the evidence tier). They are recorded in the
first session audit record for traceability (NIS2 Art. 23, ISO 27001 A.8.15).
praxis never deletes from the trail (it is append-only). Enforce a tier by archiving
whole files older than it, then rotating to a fresh PRAXIS_AUDIT_PATH: stop the
server, move audit.jsonl with its .evidence.jsonl and anchor sidecars to your
archive (WORM or a write-once bucket), and restart so a new chain begins with a fresh
session header. Do not truncate or copytruncate a live file: that breaks the hash
chain, the Merkle coverage, and the O_APPEND owner-only sink. Keep the three files
together so a retained window stays independently verifiable with
scripts/verify_audit.py.
Networked (HTTP) deployment¶
HTTP is opt-in and fails closed: it needs PRAXIS_HTTP_TOKEN and, for any
non-loopback bind, PRAXIS_HTTP_ALLOW_ANY=yes-i-understand-the-risk, plus the
SSRF egress filter on server-initiated requests. See deploy/ and ADR-0006.
Since ADR-0041 the HTTP serving loop is delivered (PRAXIS_TRANSPORT=http):
- Every request carries
Authorization: Bearer <PRAXIS_HTTP_TOKEN>; a missing or wrong token is 401. The token is never forwarded to any upstream. initializereturns anMcp-Session-Id; subsequent requests must send it (Mcp-Session-Id: <id>), or they are 404. Each session is isolated: its own trifecta taint latch, approval nonces, budget, and consent ceiling. Every action across all sessions still lands in the one audit hash chain, tagged with the request and session ids.- A session may pin a per-client tier ceiling at
initializewith aconsentCeilingparam (T0..T3); an action above it is refused. Absent, the session is gated byPRAXIS_MODElike stdio; a malformed value fails closed toT0(reads only). - Approval is unchanged over HTTP: a T2+ or trifecta-gated action needs a minted nonce, and the nonce is surfaced on the SERVER CONSOLE (never in the HTTP response), so the operator reads it out-of-band and supplies it on the real run.
- Serving is concurrent (ADR-0042, BL-110): a
ThreadingHTTPServerruns each request on its own thread over a store that serialises on a per-instance lock, so a slow actuation on one client no longer blocks the others while the append-only/bitemporal invariants hold. There is no SSE stream; the transport is JSON-RPC over POST.