Skip to main content

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

pyvivo-0.1.0.tar.gz (22.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyvivo-0.1.0-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

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

Hashes for pyvivo-0.1.0.tar.gz
Algorithm Hash digest
SHA256 720fcc62ca5bf9a6172d3b5ae225ce01700e17ceca825b98d56200934274c2e7
MD5 3a9fd81d75c94ae072fb4bd3d7aa584a
BLAKE2b-256 d416c895880193dc4cc2cf52f38e7574c5c6836ee277fd9a8fd37e33f2b94467

See more details on using hashes here.

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

Hashes for pyvivo-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f918deb9084670afe7d06f6a1df980a1f877e128a9a9ed4fc328ed36bc932bf2
MD5 d6851a285f150c8eb12c82cb189f406b
BLAKE2b-256 22b94640fd54cdf1f964e6f11caafbdffe2a4d05e313483af9b6096d5634a88f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page