Skip to main content

praxis-agent-sdk

Python SDK for the Praxis coding-agent harness. One agent API, every model and provider the harness supports — Anthropic, OpenAI (including Codex), Google Gemini, Vertex, AWS Bedrock, Z.AI, and anything else in its registry — with real tools, subagents, sessions, MCP, skills, and automatic context compaction.

# the SDK, straight from the repo
pip install "git+https://github.com/Facets-cloud/praxis-harness.git@master#subdirectory=python"
# plus the binary it drives (a git install does not carry one — see below)
GOPRIVATE=github.com/Facets-cloud go install github.com/Facets-cloud/praxis-harness/cmd/prx@latest

There is no Node.js requirement, no npm install -g, and no separate CLI to keep in version sync. Zero runtime dependencies, so it cannot conflict with your service's tree.

Interactive login

The SDK can drive the same local login flow as the TUI. Discovery exposes the currently selectable providers and the providers intentionally marked as coming soon; it never silently initiates a disabled provider.

from praxis_agent_sdk import PraxisClient, PraxisOptions

async with PraxisClient(PraxisOptions()) as agent:
    providers = await agent.get_login_providers()
    assert not next(p for p in providers if p.id == "anthropic").coming_soon

    challenge = await agent.login_challenge("anthropic")
    # Open challenge.auth_url, or collect the API key / redirect URL described
    # by challenge.prompt_message, then keep the opaque challenge unchanged.
    completion_value = input(challenge.prompt_message)
    result = await agent.complete_login(challenge, completion_value)

LoginChallenge.verifier and .state are OAuth continuation values. They are hidden from repr, but complete_login() needs the original object; do not reconstruct it from display fields. A coming-soon provider is returned by get_login_providers() with coming_soon=True and is rejected by both login methods.

Installing

From git — supported, and what to use today

The package lives in the python/ subdirectory of the harness repo, so every installer needs to be told that.

# pip
pip install "git+https://github.com/Facets-cloud/praxis-harness.git@master#subdirectory=python"
pip install "git+ssh://git@github.com/Facets-cloud/praxis-harness.git@master#subdirectory=python"

# uv
uv add praxis-agent-sdk --git https://github.com/Facets-cloud/praxis-harness --subdirectory python

# a local checkout, editable
pip install -e /path/to/praxis-harness/python
uv add --editable /path/to/praxis-harness/python

Pin a release rather than tracking master in anything you deploy — replace @master with a tag or commit, e.g. @python-v0.2.0.

A git or path install does NOT include the binary. Nothing compiles Go during a Python install, and _bin/ is gitignored on purpose so a platform-specific binary never enters the source tree. bundled_prx_version() returns None there, which is the honest answer. Supply prx by any one of:

# build it from the same checkout
cd /path/to/praxis-harness && make build && export PRAXIS_BIN=$PWD/bin/prx

# or install it with Homebrew (macOS, Linux)
brew install facets-cloud/tap/prx

# or install it with Go
GOPRIVATE=github.com/Facets-cloud go install github.com/Facets-cloud/praxis-harness/cmd/prx@latest

# or take a release archive, then put prx on PATH
# https://github.com/Facets-cloud/praxis-harness-releases/releases

# or point at it per session, no environment needed
python -c "from praxis_agent_sdk import PraxisOptions; PraxisOptions(executable='/path/to/prx')"

If prx cannot be found, the error names every one of these fixes rather than saying "not found".

From a release wheel — binary included, no PyPI needed

Every python-v* tag attaches platform wheels to its GitHub release, and a wheel bundles the matching prx. That is strictly better than a git install for a consumer, because nothing else has to be supplied:

pip install https://github.com/Facets-cloud/praxis-harness-releases/releases/download/python-v0.2.0/praxis_agent_sdk-0.2.0-py3-none-macosx_11_0_arm64.whl

Pick the wheel matching your platform from https://github.com/Facets-cloud/praxis-harness-releases/releases. bundled_prx_version() then reports the exact prx it carries.

From PyPI

uv add praxis-agent-sdk              # uv project
uv pip install praxis-agent-sdk      # uv, pip-style
pip install praxis-agent-sdk         # pip

Platform wheels for macOS (arm64, x86_64) and Linux (x86_64, aarch64) each bundle the matching prx, so pip/uv picks the right one and you need nothing else. Releases from 0.11.0 on are published to PyPI; for an older version use a release wheel or the git install above.

Resolution order: PraxisOptions.executable → $PRAXIS_BIN → the wheel's bundled binary → prx on $PATH. Bundled beats $PATH deliberately — a wheel pins a binary it was tested against, and silently preferring a stray prx makes version skew invisible.

Adding it to a uv service

# pyproject.toml
dependencies = ["praxis-agent-sdk>=0.2.0"]

# while unpublished, pin the source:
[tool.uv.sources]
praxis-agent-sdk = { git = "https://github.com/Facets-cloud/praxis-harness", subdirectory = "python" }
uv lock && uv sync

Then swap the Node layer for the binary in your image:

# was: RUN npm install -g @anthropic-ai/claude-code
COPY --from=praxis /usr/local/bin/prx /usr/local/bin/prx
ENV PRAXIS_BIN=/usr/local/bin/prx

The SDK's floor is Python 3.10 so it never forces a consumer's floor upward; uv lock/uv sync/uv run are verified against 3.14.

Quick start

import asyncio
from praxis_agent_sdk import PraxisOptions, query, AssistantMessage

async def main():
    async for message in query(
        "read README.md and summarise it in three bullets",
        PraxisOptions(model="claude-opus-5", cwd="/path/to/repo"),
    ):
        if isinstance(message, AssistantMessage):
            print(message.text)

asyncio.run(main())

Change one string to change provider — the model id resolves its own provider:

PraxisOptions(model="gpt-5.6-terra")     # OpenAI
PraxisOptions(model="gemini-2.5-pro")    # Google
PraxisOptions(model="claude-opus-5")     # Anthropic
PraxisOptions(model="glm-4.6")           # Z.AI

Credentials

Nothing to configure. If this machine is logged in — prx login, or the TUI's provider picker — the agent authenticates from that login, whichever kind it is: OAuth (a Claude or ChatGPT subscription) or an API key, resolved per provider from the model you asked for. Provider keys in the environment (ANTHROPIC_API_KEY, OPENAI_CODEX_API_KEY, …) keep working too; the child inherits your environment.

What the SDK does for you is keep that login reachable when your process gives the agent a different HOME:

# An isolated runtime: its own HOME, its own sessions, its own settings.
PraxisOptions(env={"HOME": str(runtime / "home")}, session_dir=str(runtime / "sessions"))

Praxis reads credentials from $HOME/.praxis/agent/auth.json, so a re-homed child would resolve an empty store and fail to start with no credential for provider — even though you are logged in. The SDK detects that the child would look somewhere else and pins PRAXIS_CODING_AGENT_AUTH_FILE at the store this process uses. Only the credential store is shared: sessions, skills, memories, and analytics stay under the HOME you chose.

What travels is the store, never a token the SDK copied out of it — that is what keeps OAuth working, since the harness still holds the refresh token, the account id, and the credential type that decides a subscription's context window.

# Authenticate from a specific store (a mounted secret, a service account's login).
PraxisOptions(credentials="/run/secrets/praxis-auth.json")

# Or opt out and let the child resolve its own credentials from the env you gave it.
PraxisOptions(env={"HOME": sandbox}, inherit_credentials=False)

A live session

query() is one prompt. PraxisClient is a conversation you can steer.

from praxis_agent_sdk import PraxisClient, PraxisOptions, AssistantMessage

async with PraxisClient(PraxisOptions(model="claude-opus-5", cwd=repo)) as agent:
    async for msg in agent.prompt("find the retry logic and explain it"):
        if isinstance(msg, AssistantMessage):
            print(msg.text)

    # Mid-turn course correction — the agent picks this up at its next tool
    # boundary instead of you interrupting and restating the whole task.
    await agent.steer("only look under internal/, skip the tests")

    await agent.set_model("gpt-5.6-terra")        # switch models, keep history
    usage, cost_usd = await agent.usage()
    print(f"{usage.billed} tokens, ${cost_usd:.4f}")

Exact plan approval

Plan approval is bound to the proposal you reviewed. Enter plan mode, propose a plan, fetch that proposal by its opaque id, then pass the same id to approval. A proposal that changed, expired, or was never reviewed is rejected.

async with PraxisClient(PraxisOptions(permission_mode="plan")) as agent:
    await agent.set_plan_mode(True)
    proposal = await agent.propose_plan()
    proposal_id = proposal["proposalId"]
    content = await agent.get_plan_proposal(proposal_id)
    print(content)
    await agent.approve_plan("ship", proposal_id)

MCP server configuration

Use persist_mcp_server() to save a server for a later session without starting a process now. Use add_mcp_server() when the current conversation needs it immediately; it connects the server and persists the same configuration.

async with PraxisClient(PraxisOptions()) as agent:
    path = await agent.persist_mcp_server("docs", "npx", "-y", "docs-mcp-server")
    print(f"saved MCP configuration in {path}")

Tools that run in your process

from praxis_agent_sdk import PraxisOptions, PraxisClient, tool

@tool("get_deployment", "Look up a deployment by id", tier="read")
async def get_deployment(deployment_id: str) -> str:
    return await api.fetch(deployment_id)

@tool("scale_service", "Change a service's replica count", tier="write")
async def scale_service(service: str, replicas: int) -> str:
    await api.scale(service, replicas)
    return f"scaled {service} to {replicas}"

options = PraxisOptions(model="claude-opus-5", tools=[get_deployment, scale_service])

tier is load-bearing:

tier meaning approval
read no side effects auto-approved, never prompts
write mutates something needs a permission decision
exec runs commands needs a permission decision

A write/exec tool without a can_use_tool callback is denied, not stalled — the harness resolves the prompt locally rather than blocking a turn on an answer that is not coming.

Permissions

from praxis_agent_sdk import PermissionDecision, PraxisOptions

async def can_use_tool(request):
    if request.tool_name == "bash" and request.destructive:
        return PermissionDecision(allow=False, reason="no destructive shell in prod")
    return PermissionDecision(allow=True)

PraxisOptions(model="claude-opus-5", permission_mode="ask", can_use_tool=can_use_tool)

Permission modes: auto (default — classifier-gated, reads free), ask (everything non-read prompts), yolo (allow, minus deterministic red lines), plan (read-only planning).

Migrating from claude-agent-sdk

Claude Agent SDK Praxis SDK
ClaudeAgentOptions PraxisOptions
ClaudeSDKClient PraxisClient
query(prompt, options) query(prompt, options)
@tool(name, desc, schema) @tool(name, desc, schema, tier=...)
create_sdk_mcp_server(...) deleted — pass tools=[...]
options.mcp_servers={"x": server} tools=[...] for in-process; mcp_config= for real MCP servers
can_use_tool → PermissionResultAllow/Deny can_use_tool → PermissionDecision(allow=...)
options.model (Anthropic only) options.model (any provider)
options.max_turns options.max_turns
options.permission_mode options.permission_mode
options.setting_sources options.settings, no_rules, no_skills
ResultMessage.total_cost_usd ResultMessage, or await client.usage()
requires Node + @anthropic-ai/claude-code nothing — the binary ships in the wheel

The full port guide, including the wire protocol, is in docs/design/PYTHON-SDK.md.

Using a specific binary

Resolution order: PraxisOptions.executable → $PRAXIS_BIN → the wheel's bundled binary → prx on $PATH.

PRAXIS_BIN=/usr/local/bin/prx python app.py

Development

cd .. && make build        # do this FIRST: the contract tests pin ./bin/prx
cd python
uv venv && uv pip install -e '.[dev]'    # or python -m venv .venv && .venv/bin/pip install -e '.[dev]'
uv run pytest              # whole suite, ~16s (contract tests drive the real binary)
uv run pytest -v --durations=0   # per-test progress; use this if a run looks stuck
uv run ruff check . && uv run mypy src/praxis_agent_sdk
uv build                   # sdist + wheel

The test suite spawns a real subprocess speaking the real protocol, so framing and correlation bugs fail the tests rather than reaching production. tests/test_contract.py additionally drives the REAL binary, which is what stops the Go and Python halves of the protocol drifting.

Three things about running it:

  • The contract tests use ../bin/prx and nothing else. They deliberately do NOT fall back to a prx on $PATH: a stale binary answers a newly added command with unknown command: X, which reads like a protocol bug rather than a missing make build. Without the built binary they skip and say so. PRAXIS_CONTRACT_BIN overrides the path for CI layouts that build elsewhere.
  • No test may hang. pytest-timeout caps every test at 60s and dumps the stack of all threads, because a hang blocks the whole suite and -q prints its summary only at the end — so a slow suite is indistinguishable from a stuck one.
  • PRAXIS_TEST_BUILD=1 opts into the packaging build test, which provisions an isolated environment and needs the network. It is skipped by default so the suite stays hermetic.

Runnable examples for every capability are in examples/.

Metadata

Release files for praxis-agent-sdk 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for praxis-agent-sdk 0.11.0
File Size Uploaded Provenance Actions
praxis_agent_sdk-0.11.0.tar.gz 106.9 kB Without provenance Details for praxis_agent_sdk-0.11.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for praxis-agent-sdk 0.11.0
File Actions
praxis_agent_sdk-0.11.0-py3-none-manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for praxis_agent_sdk-0.11.0-py3-none-manylinux2014_x86_64.whl
praxis_agent_sdk-0.11.0-py3-none-manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for praxis_agent_sdk-0.11.0-py3-none-manylinux2014_aarch64.whl
praxis_agent_sdk-0.11.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for praxis_agent_sdk-0.11.0-py3-none-macosx_11_0_arm64.whl
praxis_agent_sdk-0.11.0-py3-none-macosx_10_15_x86_64.whl Python 3 none macOS 10.15+ x86-64 Details for praxis_agent_sdk-0.11.0-py3-none-macosx_10_15_x86_64.whl

Total release size: 157.5 MB

Release files / praxis_agent_sdk-0.11.0.tar.gz

Download URL praxis_agent_sdk-0.11.0.tar.gz
Size 106.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4f154f9c61106034ec3a2a85cc945b309ae66ffe34aca50842814a53af3fe3bf
BLAKE2b-256 checksum
How to use checksums
983c0222d4200e20013baf48a2492b2ac30a1a24c581a3e2661f14858fe34ec5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / praxis_agent_sdk-0.11.0-py3-none-manylinux2014_x86_64.whl

Download URL praxis_agent_sdk-0.11.0-py3-none-manylinux2014_x86_64.whl
Size 40.5 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
1979c821dae982f59b8eb5c292e7e2f8fdfef6d96c17291be8c941afd8693f66
BLAKE2b-256 checksum
How to use checksums
2420a336d099d023940f5951d61776eb798f648f3f3144b0d7201551a5885839
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / praxis_agent_sdk-0.11.0-py3-none-manylinux2014_aarch64.whl

Download URL praxis_agent_sdk-0.11.0-py3-none-manylinux2014_aarch64.whl
Size 36.9 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
0329a3f40b590ed894732b91e643609c5e405386ae02a677b069932b13e4f7fa
BLAKE2b-256 checksum
How to use checksums
579fb112e3844fdaa94e09d41e1d2b9cddf3d0ad8ea933930b7f3e575feb7db8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / praxis_agent_sdk-0.11.0-py3-none-macosx_11_0_arm64.whl

Download URL praxis_agent_sdk-0.11.0-py3-none-macosx_11_0_arm64.whl
Size 38.7 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
761008666400a135b463418bbeb5cdb4badda5435309583639e6a51da75d5c51
BLAKE2b-256 checksum
How to use checksums
b5991a4882a5e42814d86850113eacee209ca53abed65deb3e4a2490f888a4d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / praxis_agent_sdk-0.11.0-py3-none-macosx_10_15_x86_64.whl

Download URL praxis_agent_sdk-0.11.0-py3-none-macosx_10_15_x86_64.whl
Size 41.4 MB
Tags Python 3 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
747c51ef7aed15574540972ae31427bdd6a9cbad099c4f759ffa3d6f417f525d
BLAKE2b-256 checksum
How to use checksums
40eea96aa961a1b75a0dc1cb94e3f3346d6d4b81aab631d077d4787f496c044e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for praxis-agent-sdk 0.11.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.11.0 This release

5 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page