Skip to content

processkit — documentation

processkit is a child-process toolkit for Python — the asyncio-native, kernel-backed, no-orphan binding to the processkit Rust crate. It is organized in two layers:

┌─────────────────────────────────────────────────────────────────┐
│  Runner layer (sync + asyncio)                                  │
│  Command · RunningProcess · Pipeline · Supervisor · CliClient   │
│  capture / streaming / interactive stdin / readiness probes     │
│  testing seam: Runner / Scripted / RecordReplay / Recording     │
├─────────────────────────────────────────────────────────────────┤
│  Group layer (kill-on-exit containment)                         │
│  ProcessGroup: start / signal / suspend / members /             │
│  stats / limits / shutdown                                      │
├─────────────────────────────────────────────────────────────────┤
│  OS mechanisms (in the Rust crate)                              │
│  Windows Job Object · Linux cgroup v2 · POSIX process group     │
└─────────────────────────────────────────────────────────────────┘

Every Command run gets containment for free: the one-shot verbs spawn into a fresh private group that dies with the run, so a returning, raising, or cancelled caller never leaks a process tree. The layers are also usable independently — a raw ProcessGroup contains children you start into it, and the testing doubles never touch the OS at all.

Both surfaces are first-class: a synchronous one (plain method names) and an asyncio one (the same names with an a prefix). They share the same types and the same no-orphan guarantee.

Guides

New here? Start with the Cookbook — short task-to-snippet recipes for everything the package does — then read Running commands end to end (it's the vocabulary every other guide builds on). Coming from the standard library? Coming from subprocess maps your existing subprocess / asyncio.subprocess patterns onto their processkit equivalents. Reach for the rest as the need arises, and keep Platform support handy before you ship: it collects every per-OS caveat in one place.

Guide Covers
Cookbook "I want to …" → working snippet, for every capability; the fastest way in
Coming from subprocess Side-by-side translation of subprocess / asyncio.subprocess patterns, the exception mapping, and the whole-tree containment the stdlib can't give
Running commands The Command builder end to end — args, env/sandboxing, stdin, stdout/stderr redirection, encodings, output caps, timeouts, privileges — and every consuming verb (output, run, probe, …) with its error semantics
Process groups Kill-on-drop containment: creating groups, spawning, teardown, whole-tree signals, suspend/resume, member listing, resource limits, stats
Sandboxing untrusted tools The agent/LLM-tool recipe: locked-down env → bounded output → group resource limits → timeout → teardown, a checklist, and an honest threat model (what this does and does not protect against)
Streaming & interactive I/O astart() and the live RunningProcess: line streaming, interactive stdin, readiness probes (wait_for_line / wait_for_port / wait_until), per-run profiling
Pipelines Shell-free command pipelines — chain with .pipe() or the pipe operator: wiring, pipefail attribution, chain timeouts, binary tails
Timeouts & cancellation How a deadline is captured vs when it raises, interrupting a blocked sync call (Ctrl+C), and asyncio cancellation that reaps the whole tree
Supervision Keeping a child alive: restart policies, backoff & jitter, stop conditions, outcomes
Testing your code The ProcessRunner seam — ScriptedRunner (incl. scripted streaming start()), record/replay cassettes, the RecordingRunner spy, the CliClient wrapper, and the autoloaded pytest plugin (ready-made fixtures + a no-real-spawn guard)
Command-line usage python -m processkit run -- ...: containment and resource limits for a shell command with no Python to write, flags, exit codes
Async runtimes & event loops Which event loops the asyncio-native surface runs on — asyncio and uvloop (yes), anyio-on-asyncio (yes), native trio / anyio-on-trio / curio (no) — and why
Platform support The containment mechanisms, every per-feature support matrix in one place, and the caveats worth knowing before you ship

Packaging

Unlike the Rust crate's compile-time feature flags, the Python wheel ships one surface with everything enabled — resource limits, signals/stats, record/replay, and opt-in logging are all present in every published wheel. There is nothing to opt into at install time:

pip install processkit-py   # import name: processkit

On a platform without a prebuilt wheel, build from source (uv run maturin develop) — see the README.

Distributed as abi3 wheels for CPython 3.10+ (one wheel per OS/arch runs on every supported minor version, 3.14 included), plus a version-specific free-threaded wheel for CPython 3.14t (PEP 703). See Platform support for the wheel matrix and the free-threaded note.

The 60-second tour

import asyncio
from processkit import Command, ProcessGroup

# One-shot, sync: capture everything. A non-zero exit is data, not an exception.
head = Command("git", ["rev-parse", "HEAD"]).output()
print("HEAD =", head.stdout.strip(), head.code)

# Success-checking: a non-zero exit / timeout / signal-kill becomes a typed exception.
version = Command("python", ["--version"]).run()

async def main():
    # Asyncio: the same verbs with an `a` prefix; cancelling reaps the whole tree.
    result = await Command("git", ["status", "--short"]).aoutput()

    # Stream a child's stdout; the context manager reaps the tree on exit.
    async with await Command("my-build", ["--watch"]).astart() as proc:
        async for line in proc.stdout_lines():
            print(line)

    # Containment: anything started in the group dies with it (grandchildren too).
    async with ProcessGroup() as group:
        await group.astart(Command("dev-server"))
    # async-with exit reaps the whole tree

asyncio.run(main())

API reference

The API reference is the complete, per-symbol index of the public surface — every class, function, protocol, type alias, and exception, plus the processkit.testing submodule. It is generated straight from the type stubs and docstrings (the same source your IDE and mypy read), so it never drifts from the real API. These guides are the narrative layer on top — they explain how the pieces compose, with the platform fine print collected in Platform support. The underlying algorithms (the OS containment mechanisms, race-free spawn) live in the processkit Rust crate.