A process execution kernel that refuses to call unverified work successful.
Praxis runs work: native programs, shell commands, agent adapters and remote
workers. It owns the rules around that work: authority, verification,
transactional publication and recovery. A process that exits 0 did not
necessarily succeed. Praxis keeps those two facts apart in the types, in the
stored record, and at the commit boundary. Thus a failed or an unverified result
cannot become a canonical artifact.
Praxis is a library and an ASGI service, not an application. Modulo owns the interface for people. Noesis owns knowledge. Adapters hold the code for each model; the kernel does not.
- Status: v1.2.0, with v1 wire contracts and SQLite layout 2. The qualification report gives the coverage and the deployment limits.
- Requires: Python 3.11 to 3.14. Local sandboxed execution needs Linux and Bubblewrap.
- Dependencies: none at runtime. The kernel, the API, the client and the adapters use only the standard library. Model SDKs and ASGI servers are host dependencies.
uv sync --locked # editable install with dev tooling
uv run python examples/demo.pyexamples/demo.py runs offline. It makes no model calls and it publishes
nothing. It tests local execution, verified commit, graphs, an adapter fixture,
the Noesis contract, control-plane inspection and recovery after a restart.
Run a program, require an artifact, and publish the artifact only if verification approves it.
import asyncio, sys
from pathlib import Path
from praxis.executors.local import LocalProcessExecutor
from praxis.kernel.authority import Authority
from praxis.kernel.capabilities import Resource
from praxis.kernel.runtime import Kernel
from praxis.kernel.spec import ProcessSpec
from praxis.storage.sqlite import SQLiteStore
from praxis.workspaces.local import LocalWorkspaces
from praxis.workspaces.transaction import CanonicalDirectory
async def main(root: Path) -> None:
workspaces = LocalWorkspaces(root / "workspaces")
authority = Authority(execution_defaults=frozenset({"local"}))
kernel = Kernel(SQLiteStore(root / "runtime.db"), workspaces,
{"local": LocalProcessExecutor(workspaces)}, authority=authority)
canonical = CanonicalDirectory(root / "canonical")
spec = ProcessSpec(
objective="write the answer",
executor="local",
inputs={"argv": [sys.executable, "-c",
"from pathlib import Path; Path('answer.txt').write_text('42')"]},
contract={"required_outputs": ["answer.txt"]},
)
process = kernel.create(spec, canonical=canonical)
# Authority is issued by the kernel, never carried in a spec.
authority.issue(process.process_id, Resource.FILESYSTEM,
frozenset({"read", "write"}), str(canonical.root))
authority.issue(process.process_id, Resource.WORKSPACE,
frozenset({"commit"}), process.process_id)
kernel.start(process.process_id)
await kernel.tasks[process.process_id]
result = kernel.result(process.process_id)
print(result.state.value, result.outcome.status.value, result.verification.approved)
print((canonical.path / "answer.txt").read_text())
asyncio.run(main(Path("./praxis-quickstart")))completed completed True
42
The process ran in a private sandboxed workspace. Praxis made a snapshot of the output and checked it against the contract. Only then did Praxis commit it to the canonical directory.
Now replace the program with one that writes nothing: "pass". Change nothing
else. The program still exits 0, so the executor still reports completed.
failed completed False
result.verification.missing_outputs is ('answer.txt',), and the canonical
directory is empty. The executor completed, but the process failed, because
verification did not approve it. Thus Praxis published nothing. This difference
between state and outcome.status is the core of the kernel. It holds after a
restart, a retry, a remote dispatch and a recovery.
| Concept | What it is |
|---|---|
ProcessSpec |
The versioned submission: objective, executor, inputs, environment, contract, budget, context and metadata. The schema is closed, thus an unknown field causes a rejection. |
Outcome |
What the executor did: completed, failed, cancelled, timed_out, partial, budget_exhausted or unavailable. |
ProcessResult |
What the kernel concluded: the terminal state, the outcome, an independent verification, artifacts, evidence, claims, effects and usage. |
Contract |
The acceptance rules that a machine can check: required outputs, invariants, validators and quorum. |
| Workspace | A private directory for staging. A snapshot identifies immutable bytes. Validators use disposable copies. |
CanonicalDirectory |
A managed store of versions. Its current pointer moves atomically, and only after the checks of verification, baseline and authority. |
Capability |
Authority with a type, issued by the kernel, over filesystem, network, executor, workspace, secret or effect scopes. A delegation can only make a scope smaller. |
| Effect | A write to the world outside the workspace, for example a git commit, a message or a publication. Praxis stages it and approves it apart from execution, and gives it an idempotency receipt. |
ProcessGraph |
Dependencies in a DAG, with requirements for success or for a terminal state, and policies of block, fail or continue. GraphStore keeps them. |
Read concepts to see how these parts fit together. Read architecture for the failure semantics.
Register a map of names to executors with the kernel. An executor advertises its features; the kernel does not assume them. A protocol version that agrees does not show support for cancellation or for checkpoints. If an adapter cannot cancel, the kernel refuses work that has a wall limit.
| Executor | Purpose | Advertised features |
|---|---|---|
LocalProcessExecutor |
Runs argv in a Bubblewrap sandbox |
cancel, isolation, signal, suspend (POSIX) |
ShellExecutor |
Runs a POSIX shell, after a check of explicit shell authority |
the same as local |
CodexExecutor |
Uses the Codex CLI through a JSON stream | cancel, streaming |
ClaudeExecutor |
Uses the Claude Agent SDK | none: no reliable cancel or checkpoint |
DeepSeekExecutor |
Uses the DeepSeek API | none: no reliable cancel or checkpoint |
RemoteExecutor |
Sends work to a registered worker that has a fence | streaming and the negotiated controls |
FakeExecutor |
Gives deterministic results for tests and smoke servers | cancel |
The three agent adapters need isolated_worker=True. This flag is an assertion
by the host that something else confines the worker. The flag does not make a
sandbox. See isolation.
Host the ASGI application. By default it denies anonymous access.
PRAXIS_API_TOKEN=$(openssl rand -hex 24) \
uv run --with uvicorn uvicorn examples.server:create_app --factory --host 127.0.0.1 --port 8000Submit work, stream events and control the process with the typed client:
from praxis.client import Client
from praxis.client.http import ClientHTTPTransport
from praxis.kernel.spec import ProcessSpec
client = Client(ClientHTTPTransport(base_url, headers={"Authorization": f"Bearer {token}"}))
receipt = await client.submit(ProcessSpec("example", "fake"), idempotency_key="example-1")
cursor = 0
async for stored in client.events(receipt.process_id, after=cursor):
cursor = stored.cursor # persist only after the consumer has processed the event
view = await client.inspect(receipt.process_id)The sample server has one operator and a fake executor, on purpose. Before you expose it, put TLS, rate limits, deadlines and a real authorization rule in front of it. Operations and API list what a host must supply.
For a real deployment alongside Modulo and Noesis, praxis.host supplies that host:
role-scoped client tokens with per-user delegation, mutual TLS in both directions,
Noesis context and a manual or event-driven publication trigger — see host.
python -m praxis.host token --client modulo # client digest for host.toml (start from examples/host.toml)
python -m praxis.host check --config host.toml
uv run --with uvicorn python -m praxis.host serve --config host.tomlThe index is in docs/.
| Document | Read it for |
|---|---|
| Concepts | The execution model, from end to end, with code you can run |
| Architecture | The boundaries of ownership and the failure semantics |
| Configuration | The TOML keys, the precedence and the event envelopes |
| Control-plane API | The HTTP routes, the status codes and the SSE cursors |
| Client SDK | The typed flows to submit, inspect, control and stream |
| Operations | How to install, serve, upgrade, back up and recover |
| Host | How to serve Praxis for Modulo and Noesis, with authentication, TLS and publication |
| Threat model | The trust boundaries TM-1 to TM-9, and what they exclude |
| Isolation | The sandbox, the mounts and the output limits |
| Secrets | How to integrate a provider, and the limits of injection |
| Redaction | What Praxis removes before bytes leave the controller |
| Metrics | The stable metric names, units and label allowlists |
| Versions | The version matrix for the wire and the storage, and the policy |
| Qualification | The v1.2.0 release evidence and the deployment limits |
| Acceptance | The opt-in live checks against real providers, and their records |
Start with concepts. Before you host native workloads, read the threat model and isolation.
uv run pytest # 441 tests
uv run pytest acceptance -rs # opt-in live checks; see docs/acceptance.md
uv run ruff check .
uv run mypy # strict, over src/The release gate is reproducible. It builds an sdist and then builds a wheel from it. It installs that wheel into a new virtual environment with locked dependencies. Then it makes sure that the imports come from that installation. Then it runs the test suite, the lint, the type checks, the examples and the CLI.
uv run python scripts/qualify.pyCI runs the source checks on Python 3.11 to 3.14. It runs the fresh-wheel gate on 3.12.
src/praxis/
kernel/ processes, authority, budgets, contracts, effects, graphs, runtime
executors/ adapter protocol, local/shell/remote, Codex/Claude/DeepSeek, isolation
workspaces/ private staging, snapshots, canonical transactions
storage/ SQLite store, journal, migrations, graph store, integrity
validators/ verification protocol, policy evaluation, command validator
evaluators/ candidate scoring and winner selection
api/ ASGI application, control-plane service, auth hooks
client/ typed v1 HTTP client
transport/ bounded HTTP transport shared by client and providers
remote/ worker registration, dispatch, placement, heartbeats, recovery
knowledge/ context providers, Noesis integration, publication policy
observability/ metrics, tracing, redaction, structured errors, health
host/ Modulo/Noesis deployment host: config, auth, TLS serving, publication
compatibility.py version matrix and negotiation
cli.py console entry point
docs/ reference documentation
examples/ offline demo, sample API server, host configuration
scripts/ release qualification gate
tests/ suites outside the package, run against the installed distribution
MIT. See LICENSE.