Skip to main content

live lazy variable inspector for python

Project description

paar

paar is a live, lazy variable inspector for Python: a PyCharm-style variables pane for any running interpreter — Jupyter, a plain script, or a training run — served over HTTP from inside the process, with web, notebook, and terminal frontends.

  • Live: the view refreshes after every cell (IPython) or by poll+diff (any process).
  • Lazy: children are resolved one level at a time, on click — a million-row DataFrame costs nothing until you open it, and then only one page.
  • Everywhere: the same server feeds the inline Jupyter panel, the browser, paar-tui in a terminal, and — new — a restricted MCP surface for AI agents, so Claude Code or Codex can work inside your live session without being able to break it.

Installation

pip install paar          # from pypi
pip install paar[mcp]     # + the paar-mcp server for agent access (Claude Code, Codex, ...)
pip install git+https://github.com/vedicreader/paar.git   # latest from GitHub

Docs are hosted on this repo’s pages.

Live demo

from paar.fasthtml import inspector
inspector()          # panel renders inline; keep this cell's output visible
<script> document.body.addEventListener('htmx:configRequest', (event) => { if(event.detail.path.includes('://')) return; htmx.config.selfRequestsOnly=false; event.detail.path = `${location.protocol}//${location.hostname}:8000${event.detail.path}`; }); </script>

Open in new tab

<iframe src="http://localhost:8000/" style="width: 100%; height: 520px; border: none;" onload="" allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe>
import math, numpy as np, pandas as pd
data = {'a': 1, 'b': [1, 2, {'deep': [10, 20]}], 'c': 'hello', 'pi': math.pi}
arr = np.arange(12).reshape(3, 4)
df = pd.DataFrame({'x': range(5), 'y': list('abcde')})
data['d'] = list(range(1000))   # run this; the panel above updates without re-running inspector()

Viewing modes (all from the one in-kernel server):

  • Inline: the inspector() cell output above updates live after every cell — you do not re-run it.
  • Docked side panel (JupyterLab): right-click the inspector() output → Create New View for Cell Output → drag into a split pane.
  • Full window: open http://localhost:8000/ or click the open in browser link.

Click the ▸ toggle on any container row (dict / list / tuple / set / object) to load its children one level at a time. Nested containers keep their own toggles, so you can drill in as deep as the data goes — e.g. data'b'[2]'deep'.

arr (numpy) and df (pandas) show a ▦ grid toggle instead of a tree — click it to open a scrollable, paged table (use the row/col ◀ ▶ controls for data larger than one page). Plain containers still expand as a tree.

Exec, inline edit, and filtering:

  • Exec box: type an expression or statement and press Run — executes in the kernel; tick Isolated to evaluate side-effect-free (result shown without writing back to the namespace).
  • Inline edit: click any scalar value in the tree to edit it in place; the new value is written back into the kernel namespace immediately.
  • Filter bar: narrow the variable list by name substring or type (use the name field and the type dropdown above the table).

Across processes and environments

The inspector is not limited to Jupyter. Any plain Python program (in any package or virtualenv) can start a background paar server and expose its own live namespace:

import paar
paar.fasthtml.serve()   # inspects the caller's module globals; returns the URL, e.g. http://127.0.0.1:8000

serve() runs the server on a daemon thread and refreshes the view by polling the namespace, so no Jupyter kernel or manual nudging is needed. Each server registers itself (by repo/package name) in a small local registry, so multiple environments can run paar at once and any frontend can discover and switch between them.

From the command line

Run any script under a live inspector — the view updates while the script runs, and the server stays up afterwards so you can inspect the final state:

$ uv run paar-serve train.py            # or: uvx --from paar paar-serve train.py
paar inspector live at http://127.0.0.1:8000   (connect a terminal with: paar-tui)

Terminal frontend

paar-tui is a gruvbox terminal inspector (built with Textual) that connects to any running server — useful across environments since it only needs HTTP/WS:

$ uv run paar-tui                       # auto-discovers a running server from the local registry
$ uv run paar-tui --env train           # pick a specific env by name
$ uv run paar-tui --url http://host:8000

Inside the TUI: / filters variables, x opens a code-exec bar, 1/2/3 switch profile, g opens the grid, e switches between live environments, r refreshes, q quits.

Sharing your session with AI agents

Your session is yours; an agent’s access to it should not be. serve() and inspector() also publish a restricted agent surface (/agent/api/*) built on two mechanisms in paar.agent:

  • An overlay namespace (AgentSession). Agent code reads your variables directly, but every name it binds lands in its own persistent layer — assigning to one of your names merely shadows it, and deleting one is impossible by construction. Your namespace is never written.
  • A restriction list (clikernel-compatible inspectors). Before a cell runs, its AST is checked: in-place mutation of your objects (.append, df.drop(inplace=True), x += ...), exec/eval-style escapes, and destructive file/shell calls are refused with an error that tells the agent what to do instead.

The net contract: agents can read anything and create anything new; they can never change or delete what you made. It works standalone too:

from paar.agent import AgentSession

yours = {'rows': [1, 2, 3], 'threshold': 2}          # stand-in for your live namespace
agent = AgentSession(owner=lambda: yours, rules=[])  # rules=[]: skip any local inspectors.py

agent.run('kept = [r for r in rows if r >= threshold]')   # reads yours, creates its own
agent.layer
for cell in ('rows.append(99)', 'del threshold', 'rows = []'):
    r = agent.run(cell)
    print(r.error or f'{cell!r} ran; owner still sees {yours}')

'rows = []' succeeded — as a shadow: the agent now has its own rows, while yours is untouched and still what you see. You can watch everything the agent has made via paar.fasthtml.agent_layer() (assign it to a variable and it shows up, expandable, in your own panel). Server modes: serve(agent='restricted') (default), 'readonly' (every agent exec is isolated — nothing persists), or 'off'. Add your own rules in ~/.config/paar/inspectors.py (or $PAAR_INSPECTORS) using clikernel’s file contract: define inspect(tree[, code]) and/or an inspectors list; raise paar.agent.RuleBlock to refuse a cell.

Owner endpoints (/exec, /set, /api/exec) are gated separately by a per-server owner token (<reg_dir>/token-<port>, mode 0600). Your web panel and paar-tui pick it up automatically; agents on /agent/api never see it.

Connecting Claude Code or Codex

paar-mcp is a stdio MCP server exposing the agent surface as tools — list_vars, expand, grid, execute, session_info — and finds your server through the local registry:

# your session (Jupyter or script):        # Claude Code:
from paar.fasthtml import inspector        claude mcp add my-session -- paar-mcp --env myrepo
inspector()                                # Codex (config.toml):
                                           # [mcp_servers.my-session]
                                           # command = "paar-mcp"
                                           # args = ["--env", "myrepo"]

--env picks a registry entry by name (defaults to the only live one); --url pins a base URL instead. The server resolves its target per call, so start order doesn’t matter. skills/paar-session/SKILL.md documents the contract for agents — copy it into ~/.claude/skills/ (or your agent’s skill directory) so the agent knows the etiquette before its first call.

Pairing with clikernel: watching the agent’s own session

The mirror-image setup: while paar-mcp lets an agent into your session, paar can also ride inside the agent’s clikernel worker — the persistent Python process Claude Code/Codex use as their workbench — so you can watch and steer their session live.

clikernel runs ~/.config/clikernel/startup.py inside the worker via %run -i, which is all paar needs:

# ~/.config/clikernel/startup.py — runs inside every clikernel worker
import os
import paar.fasthtml
paar.fasthtml.serve(port=8001, name=os.environ.get('PAAR_ENV_NAME', 'claude'))
  • Two sessions, two ports: your kernel on inspector(port=8000), the agent’s worker on 8001 (serve auto-bumps if taken). Both register in the local registry, so paar-tui’s e key or the web env dropdown flips between you and claude.
  • Naming: set PAAR_ENV_NAME=claude / PAAR_ENV_NAME=codex in each client’s MCP server config for clikernel, so the one startup.py labels every worker correctly.
  • Restarts survive: clikernel’s restart tool spawns a fresh worker, which re-runs startup.py — the inspector comes back by itself.
  • One rules file, two enforcers: paar’s inspectors use clikernel’s exact contract (inspect(tree[, code]), inspectors, RuleBlock), so the restriction list you write for the agent’s own kernel (~/.config/clikernel/inspectors.py) can be reused for its access to your session (~/.config/paar/inspectors.py).

Full circle: Claude works in its clikernel on 8001 while reaching into your 8000 session read-and-create-only via paar-mcp — and you see both, live, from one TUI.

Security model (honest edition)

These are guardrails for well-behaved agents, not a sandbox. Concretely:

  • The overlay gives a structural guarantee for name bindings: agent code cannot rebind or delete your variables no matter what it writes.
  • The AST restriction list guards in-place mutation and escapes, but static analysis can be evaded by sufficiently creative code (getattr chains, new mutator names, …).
  • The owner token keeps well-behaved tools off the owner endpoints, but any same-user local process with shell access could read the token file or the served page.

The realistic threat here is an agent accidentally clobbering your state — which this stops cold. A hard boundary against adversarial code would need a separate process on a copy of the data, which defeats the point of a live shared session.

Developer guide

paar is an nbdev project: the notebooks in nbs/ are the source of truth, paar/*.py is generated. To hack on it:

pip install -e .        # editable install
# edit notebooks under nbs/ ...
nbdev_prepare           # export modules, run tests, clean notebooks

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

paar-0.0.1.tar.gz (47.7 kB view details)

Uploaded Source

Built Distribution

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

paar-0.0.1-py3-none-any.whl (54.0 kB view details)

Uploaded Python 3

File details

Details for the file paar-0.0.1.tar.gz.

File metadata

  • Download URL: paar-0.0.1.tar.gz
  • Upload date:
  • Size: 47.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for paar-0.0.1.tar.gz
Algorithm Hash digest
SHA256 00cd8be2a8ec0437fe923ab04c399657d49c744b37dfabc5924c670c512edab7
MD5 c1b9a4d7d64e343bbc8a3f9190d64b7b
BLAKE2b-256 c8062526f6f29cbc7289e56056360290285c2dbda7f04e0e804ead3d1db9e3c3

See more details on using hashes here.

File details

Details for the file paar-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: paar-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 54.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for paar-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f0f2e5508fe631bf0634040bc16bcd6e16f8febb5f5bb9543455c6b102417f64
MD5 f24d010ceb38e55bde8b13315499a733
BLAKE2b-256 d96f66c52a919a339c0ba4bc7c8117ac6182bc8bca47b1b9d0397ab43581d419

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