pyvivo
pyvivo is an experimental, REPL-style development tool for inspecting and
modifying a running CPython process. It uses Python 3.14's PEP 768
sys.remote_exec() API to bootstrap a small in-process agent, then
communicates through a mode-0600 Unix-domain socket.
The name is short for in vivo — Latin for "within the living", the life sciences' term for an experiment done in a living organism, as opposed to in vitro ("in glass"): in a test tube, on dead or isolated material. pyvivo works in vivo: it inspects and modifies the live, running interpreter.
The workflow is borrowed from the Lisp REPL tradition, where development means
working on a live image — evaluating expressions and redefining functions in a
program that keeps running — rather than the edit–restart cycle.
pyvivo's architecture includes an agent inside the process, a controller outside,
a socket between them. It can inject the agent into an already-running process
that never planned for it, and redefine a function with a tracked patch
of one function's __code__, with rollback.
This is a research prototype, not a production debugger. Attaching grants arbitrary code execution inside the target process.
Requirements
- CPython 3.14 or newer, with remote debugging enabled.
- Unix-domain sockets. The current prototype targets Linux/macOS-style systems.
- Permission to trace the target process. On Linux this is governed by ptrace,
Yama, process ancestry, and capabilities such as
CAP_SYS_PTRACE. On macOS, taking the target's Mach task port generally requires running the controller as root (sudo), even when the target is the controller's own child.
Install
pyvivo is a standard Python package with no runtime dependencies, so any
PEP 517 installer works. It ships a pyvivo console script, but every command
in this README is written as python -m pyvivo so it runs the same regardless
of how (or whether) you install it — from an active virtual environment they
are interchangeable.
With uv:
uv venv --python 3.14
uv sync
With pip:
python -m venv .venv && . .venv/bin/activate
pip install -e .
Running the tests
The suite is standard-library only:
python -m unittest discover -s tests
Examples
These drive pyvivo against the sample programs in example/.
Attach to the example process
Terminal 1:
python example/target.py
Terminal 2 (PID is printed by the target, sudo may be required):
python -m pyvivo remote PID attach
python -m pyvivo remote PID eval 'counter'
python -m pyvivo remote PID patch __main__:compute example/patch_compute.py
python -m pyvivo remote PID rollback PATCH_ID
python -m pyvivo remote PID detach
Every command except attach automatically attaches if necessary. detach
does not attach merely to report that the process is already detached.
On macOS, attaching needs the target's Mach task port, which the OS grants only
to root — parent/child ancestry does not help either — so prefix these commands, and
the programmatic demos below, with sudo:
sudo python -m pyvivo remote PID attach
Keep using sudo for every follow-up command on the same target: the socket
path is derived from the controller's uid and temporary directory, so mixing
sudo and non-sudo invocations computes different paths.
Process trees
pyvivo can recursively discover a root process and all its descendants, then run
one command against the whole tree and aggregate the per-process results. To try
it against the sample tree in example/:
Terminal 1 (a root plus two child workers; it prints the root pid):
python example/tree_target.py --children 2
# tree root pid=ROOT children=[...]
Terminal 2 (sudo may be required):
python -m pyvivo tree ROOT list # discover the whole tree
python -m pyvivo tree ROOT --descendants-only stack
python -m pyvivo tree ROOT --descendants-only eval 'compute(3)'
python -m pyvivo tree ROOT --descendants-only patch __main__:compute example/patch_compute.py
python -m pyvivo tree ROOT --descendants-only rollback SHARED_PATCH_ID # id printed by the patch above
python -m pyvivo tree ROOT --descendants-only detach --rollback
--descendants-only excludes the root; drop it to include the root too — the
sample tree is homogeneous, so every process (root included) exposes
__main__:compute. The same commands work for any local multi-process Python
application, not just this sample.
Tree discovery is framework-neutral: it uses OS parent/child relationships and
does not interpret application-specific environment variables. Discovery reads
/proc on Linux and falls back to ps on macOS/BSD. Each process has a
separate CPython interpreter, so the controller bootstraps one agent per PID
and communicates with each agent over its own Unix socket
pyvivo-UID-PID.sock in the controller's temporary directory. The command
aggregates their independent responses into one JSON result.
After a tree attach, each process remains independently addressable:
python -m pyvivo remote CHILD_PID stack
python -m pyvivo remote CHILD_PID eval 'some_process_specific_expression'
A tree patch generates one patch ID in the controller and supplies that same ID to every process where the patch succeeds. The top-level tree response includes the ID, so it can be used for a later tree-wide rollback:
python -m pyvivo tree ROOT_PID --descendants-only patch package:function patch.py
python -m pyvivo tree ROOT_PID --descendants-only rollback SHARED_PATCH_ID
Tree operations do not assume homogeneous programs. Each request is evaluated
against each process's own modules, threads, and frames. If one child runs a
different script, lacks a target function, has a shallower stack, or cannot
evaluate an expression, that child returns ok: false while other children may
return ok: true. Use python -m pyvivo remote CHILD_PID ... for
process-specific frame selection or probes.
Programmatic demo
The example/ directory has runnable demos that drive pyvivo's client API end
to end — spawn a target, attach, inspect, hot-patch, roll back, and detach:
python example/demo.py # single process
python example/tree_demo.py --children 2 # a process tree
demo.py starts one target as its child, attaches, lists threads and stack
frames, evaluates target state, replaces a function's __code__, rolls it back,
and detaches. tree_demo.py runs the same cycle across a whole process tree,
patching every process under one shared patch id and aggregating their results.
Both scripts read as a worked example of using pyvivo.client directly.
Command reference
Usage lives in the CLI help, so it stays in sync with the code:
python -m pyvivo --help # modes: remote, tree
python -m pyvivo remote --help # ping, threads, stack, locals, eval, exec, patch, rollback, patches, detach
python -m pyvivo remote PID eval --help
python -m pyvivo tree --help
The eval/exec, patch, and detach help text documents the semantics
worth knowing before use — frame and name resolution, the call-shape and
free-variable requirements for a patch, and the detach policies.
Important limitations
- Foreign-frame inspection is a concurrent snapshot; the selected thread is not paused.
- A CPython safe evaluation point is not necessarily an application safe point.
repr()and arbitrary eval/exec may have side effects or significant cost.- The socket has restrictive filesystem permissions but no complete peer authorization, audit, or production threat model.
- Patch history is memory-only and is not durable across process exit or
detach --keep-patches.
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 pyvivo-0.1.0.tar.gz.
File metadata
- Download URL: pyvivo-0.1.0.tar.gz
- Upload date:
- Size: 22.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
720fcc62ca5bf9a6172d3b5ae225ce01700e17ceca825b98d56200934274c2e7
|
|
| MD5 |
3a9fd81d75c94ae072fb4bd3d7aa584a
|
|
| BLAKE2b-256 |
d416c895880193dc4cc2cf52f38e7574c5c6836ee277fd9a8fd37e33f2b94467
|
File details
Details for the file pyvivo-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyvivo-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f918deb9084670afe7d06f6a1df980a1f877e128a9a9ed4fc328ed36bc932bf2
|
|
| MD5 |
d6851a285f150c8eb12c82cb189f406b
|
|
| BLAKE2b-256 |
22b94640fd54cdf1f964e6f11caafbdffe2a4d05e313483af9b6096d5634a88f
|