Skip to main content

Persistent Python REPL for LLM CLI tools

Project description

pyreplab

Persistent Python REPL for LLM CLI tools.

LLM coding CLIs (Claude Code, Copilot CLI, etc.) can't maintain a persistent Python session — each bash command runs in a fresh process. For large datasets, reloading on every query is impractical. pyreplab fixes this.

How it works

A background Python process sits in memory with a persistent namespace. You write .py files with # %% cell blocks, then execute cells by reference. No ports, no sockets, no dependencies.

Quick start

Write a .py file with # %% cell blocks — in your editor, or let an LLM write it:

# analysis.py

# %% Load
import pandas as pd
df = pd.read_csv("data.csv")
print(df.shape)

# %% Explore
print(df.describe())

# %% Top rows
print(df.head(20))

Then run cells:

pyreplab start --workdir /path/to/project   # start (auto-detects .venv/)
pyreplab run analysis.py:0                  # Load data — stamps [0], [1], [2] into file
pyreplab run analysis.py:1                  # Explore (df still loaded)
pyreplab run analysis.py:2                  # Top rows (no reload)
pyreplab stop

After the first run, analysis.py is updated with cell indices:

# %% [0] Load        ← index added automatically
# %% [1] Explore
# %% [2] Top rows

CLI reference

pyreplab <command> [args]

  start [opts]        Start the REPL (opts: --workdir, --cwd, --venv, ...)
  run file.py         Run all cells (stamps [N] indices into file)
  run file.py:N       Run cell N from file (0-indexed)
  run 'code'          Run inline code
  run                 Read code from stdin
  cells file.py       List cells (stamps [N] indices into file)
  wait                Wait for a running command to finish
  dir                 Print session directory path
  stop                Stop the current session
  stop-all            Stop all active sessions
  ps                  List all active sessions with PID, uptime, memory
  status              Check if REPL is running (shows idle/executing)
  clean               Remove session files

Server options

python pyreplab.py [options]

  --session-dir DIR    Session directory (default: /tmp/pyreplab)
  --workdir DIR        Project root for session identity and .venv detection
  --cwd DIR            Working directory for the REPL (defaults to --workdir)
  --venv PATH          Path to virtualenv (auto-detects .venv/ in workdir)
  --conda [ENV]        Activate conda env (default: base)
  --no-conda           Disable conda auto-detection
  --timeout SECS       Per-command timeout (default: 30)
  --max-output CHARS   Hard cap on output size (default: 100000)
  --max-rows N         Pandas display rows (default: 50)
  --max-cols N         Pandas display columns (default: 20)
  --poll-interval SECS Poll interval (default: 0.05)

Working directory

By default, --workdir sets both the session identity (for .venv detection and session isolation) and the REPL's working directory. Use --cwd to override the REPL's working directory separately:

# .venv detected from project root, but REPL runs in data subdir
pyreplab start --workdir /project --cwd /project/data/experiment1
pyreplab run 'import pandas as pd; print(pd.read_csv("local_file.csv").shape)'

This is useful for data analysts who want to move between folders while keeping the same session and environment.

Async execution

Long-running commands return early instead of blocking. The client polls for up to PYREPLAB_TIMEOUT seconds (default: 115s, just under the typical 2-minute Bash tool timeout). If the command finishes in time, output is returned normally. If not:

export PYREPLAB_TIMEOUT=5
pyreplab run 'import time; time.sleep(30); print("done")'
# → pyreplab: still running (5s elapsed). Run `pyreplab wait` to check again.
# exit code 2

pyreplab wait
# → done
# exit code 0

If you try to run a new command while one is still executing:

pyreplab run 'print("hi")'
# → pyreplab: busy running previous command. Run `pyreplab wait` first.
# exit code 1

Short commands that finish within the timeout window work identically to before — no behavior change.

Environment detection

pyreplab automatically detects and activates Python environments so your project packages are available. Detection follows a priority order — the first match wins:

Priority Source How it's found
1 --venv PATH Explicit flag
2 .venv/ in workdir Auto-detected
3 --conda [ENV] Explicit flag
4 Conda base Auto-detected fallback

If a project has a .venv/, that always takes precedence over conda. If no .venv/ exists, pyreplab falls back to conda's base environment (giving you numpy, pandas, scipy, etc. out of the box). Use --no-conda to disable the fallback.

Virtual environments (venv, uv, virtualenv)

# Auto-detect .venv/ in workdir (most common)
pyreplab start --workdir /path/to/project

# Explicit path to any virtualenv
pyreplab start --venv /path/to/.venv

Works with uv venv, python -m venv, or any standard virtualenv.

Conda environments

# Auto-detect: if no .venv/, conda base is used automatically
pyreplab start --workdir /path/to/project

# Explicit: force conda base
pyreplab start --conda

# Named conda env
pyreplab start --conda myenv

# Disable conda fallback (bare Python only)
pyreplab start --no-conda

Conda base is found by checking, in order:

  1. $CONDA_PREFIX (set when a conda env is active)
  2. $CONDA_EXE (e.g. ~/miniconda3/bin/conda → derives ~/miniconda3)
  3. Common install paths: ~/miniconda3, ~/anaconda3, ~/miniforge3, ~/mambaforge, /opt/conda

Named envs resolve to <conda_base>/envs/<name>.

Session isolation

Each --workdir gets its own isolated session — separate process, namespace, and files. No clashing between projects.

# Two projects, two sessions
pyreplab start --workdir ~/projects/project-a
pyreplab start --workdir ~/projects/project-b

# See what's running
pyreplab ps
# SESSION                      PID     UPTIME   MEM    DIR
# project-a_a1b2c3d4           12345   5m30s    57MB   /tmp/pyreplab/project-a_a1b2c3d4
# project-b_e5f6g7h8           12346   2m15s    43MB   /tmp/pyreplab/project-b_e5f6g7h8

# Commands auto-resolve to the right session based on cwd
cd ~/projects/project-a && pyreplab run analysis.py:0
cd ~/projects/project-b && pyreplab run analysis.py:0

# Stop everything
pyreplab stop-all

Display limits

Output is automatically truncated for LLM-friendly sizes:

Library Setting Default
pandas max_rows 50
pandas max_columns 20
pandas max_colwidth 80 chars
numpy threshold 100 elements

Override with --max-rows and --max-cols. The --max-output flag is a hard character cap that truncates at line boundaries, keeping both head and tail.

Cell markers and stamping

Cells are delimited by # %% comments (the percent format, compatible with VS Code, Spyder, PyCharm, and Jupytext). Both # %% and #%% are accepted.

When you run or list cells, pyreplab stamps [N] indices into the cell markers in your file:

# Before:                       # After first run/cells:
# %% Load                       # %% [0] Load
import pandas as pd              import pandas as pd
# %%                             # %% [1]
# Clean the data                 # Clean the data
df = df.dropna()                 df = df.dropna()
  • Idempotent — running again doesn't double-stamp; indices update if cells are reordered
  • #%% normalizes to # %% — the PEP 8 / linter-friendly form (avoids flake8 E265)
  • PYREPLAB_STAMP=0 — disables file modification entirely
  • Inline code and stdin — no stamping (no file to modify)

The cells command also reads the first comment line below an unlabeled # %% marker as its description:

$ pyreplab cells analysis.py
  0: # %% Load
  1: # %% Clean the data       ← peeked from comment below "# %% [1]"

Session history

Every execution is logged to history.md in the session directory. This is useful for context recovery — if an LLM conversation gets compressed or a session is resumed, the agent can read the history to see what was already run and what's in the namespace.

cat "$(pyreplab dir)/history.md"

The history resets on each new session start.

Protocol

cmd.py (client writes):

# %% id: unique-id
import pandas as pd
df = pd.read_csv("big.csv")
print(df.shape)

The first line is a # %% cell header with a command ID. The rest is plain Python — no escaping, no JSON encoding.

output.json (pyreplab writes):

{"stdout": "(1000, 5)\n", "stderr": "", "error": null, "id": "unique-id"}

Files are written atomically (write .tmp, then os.rename). The id field prevents reading stale output.

Install

git clone https://github.com/anthropics/pyreplab.git
cd pyreplab

Make pyreplab available on your PATH (pick one):

# Option 1: symlink (recommended)
ln -s "$(pwd)/pyreplab" /usr/local/bin/pyreplab

# Option 2: add directory to PATH
echo 'export PATH="'$(pwd)':$PATH"' >> ~/.zshrc
source ~/.zshrc

Verify:

pyreplab start --workdir .
pyreplab run 'print("hello")'
pyreplab stop

Using with Claude Code

Append the agent instructions to Claude Code's system prompt:

claude --append-system-prompt-file /path/to/pyreplab/AGENT_PROMPT.md

Or add them to your project's CLAUDE.md so they're loaded automatically in every session.

Tests

bash test_pyreplab.sh    # 14 tests: basic execution, persistence, errors, display limits, cells, stdin
bash test_agent.sh     # 10-step agent walkthrough: loads data, analyzes, reaches a conclusion

Requirements

Python 3.9+. Zero dependencies — stdlib only.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyreplab-0.3.6.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

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

pyreplab-0.3.6-py3-none-any.whl (16.4 kB view details)

Uploaded Python 3

File details

Details for the file pyreplab-0.3.6.tar.gz.

File metadata

  • Download URL: pyreplab-0.3.6.tar.gz
  • Upload date:
  • Size: 19.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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 pyreplab-0.3.6.tar.gz
Algorithm Hash digest
SHA256 1d209a02758770c09ad21b96208b947c33217d116f191b0fb036d2c83f3ac1b3
MD5 6ccdd12232110bb570995dd022a27a4e
BLAKE2b-256 72ad52934f8a412f5da92a574d8ff564c4af945dae2bdde91d1222b85320353a

See more details on using hashes here.

File details

Details for the file pyreplab-0.3.6-py3-none-any.whl.

File metadata

  • Download URL: pyreplab-0.3.6-py3-none-any.whl
  • Upload date:
  • Size: 16.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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 pyreplab-0.3.6-py3-none-any.whl
Algorithm Hash digest
SHA256 412adee1b26eaa01856d1c77e23cf96c936bc2bb57007e02cd8be1367d1aa6ed
MD5 2c4ed7020c59ec7652d1031b9c1b6ba6
BLAKE2b-256 a5bfc4ca4d07e56b3b451a6113c892da7c08d18d4e70c3b1cbda1314de875fd9

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