sshd gives you a shell. pythond gives you Python.
Persistent Python daemon with named sessions. Code in, result out. Variables, connections, and threads survive between calls. Agents send code through WebSocket. Humans attach with a real terminal. Same namespace.
pip install pythond
Python 3.10+. Three commands:
| Command | Role | Like |
|---|---|---|
pythond | daemon | sshd |
pysh | session client | ssh |
pyctl | daemon control | systemctl |
# terminal 1 pythond daemon # terminal 2 pysh new work pysh run work "x = 42" pysh run work "x + 1" # 43 pysh attach work # human REPL, Ctrl-] to detach
pysh run work "import sqlite3; db = sqlite3.connect('app.db')"
# ... 100 turns later ...
pysh run work "db.execute('SELECT count(*) FROM users').fetchone()"
# (42,)
The connection never closed. The namespace is the workspace.
# fire: thread, shares namespace
pysh fire work "model = train(X, y)"
pysh poll work abc123
# {"cell_id":"abc123", "status":"done", "output":"..."}
pysh run work "model.score(X_test)" # model is there
# fork: child process (POSIX only), killable, pickles vars back
pysh fork work "results = expensive_search(params)"
pysh int work # SIGKILL the fork (POSIX)
AI agents can't hold SSH sessions open. The local daemon holds the connection the agent can't hold.
# server pip install pythond pyctl start --listen 0.0.0.0:7399 --tls --show-token # prints token and cert fingerprint # client: self-signed server cert must be pinned before connecting # copy server ~/.pythond/tls/cert.pem to client as ~/server_cert.pem pyctl pin ~/server_cert.pem pythond daemon pyctl connect work 10.0.0.5:7399 <token> --tls pysh run work "import platform; platform.node()" # remote-host-01
# client: generate client cert pyctl cert # copy client ~/.pythond/tls/cert.pem to server as ~/client_cert.pem # server: generate server cert, then trust client cert pyctl cert # copy server ~/.pythond/tls/cert.pem to client as ~/server_cert.pem pyctl trust ~/client_cert.pem pyctl start --listen 0.0.0.0:7399 --tls --show-token # cert is required and token is still required # client: pin server cert, then connect (client cert sent automatically) pyctl pin ~/server_cert.pem pythond daemon pyctl connect work 10.0.0.5:7399 <token> --tls pysh run work "x"
Complex code with quotes, f-strings, or SQL? Write a file, load it. No escaping.
cat > /tmp/task.py << 'EOF'
import pandas as pd
df = pd.read_csv("data.csv")
print(f"rows: {len(df)}, cols: {list(df.columns)}")
EOF
pysh run work "exec(open('/tmp/task.py').read())"
Agent channel (pysh run): Human channel (pysh attach):
code in, text out real PTY, readline, colors
no ANSI, no parsing tab completion, Ctrl-C
WebSocket protocol Ctrl-] to detach
\ /
same Python namespace
pysh new <name> create session pysh run <name> "code" sync exec, raw output pysh fire <name> "code" async thread, shares namespace (can't kill C code) pysh fork <name> "code" async process (POSIX), killable, pickles vars back pysh poll <name> [cell_id] check async result pysh attach <name> human REPL pysh int <name> interrupt (fire=best effort, fork=kill) pysh kill <name> terminate session pysh ls list sessions pysh status <name> JSON health pysh vars <name> JSON namespace names pysh complete <name> "text" JSON completion candidates pyctl start [--listen H:P] [--tls] [--show-token] start daemon pyctl stop stop daemon pyctl status daemon endpoint metadata and liveness pyctl connect <name> <h:p> <tok> [--tls] proxy to remote pyctl disconnect <name> drop remote pyctl cert show/generate TLS cert pyctl trust <cert.pem> authorize client pyctl pin <cert.pem> verify server
pyctl cert prints the certificate path, key path,
fingerprint, and role hints: copy this cert to the server for
pyctl trust when this machine is a client, or copy it to the
client for pyctl pin when this machine is a server.
WebSocket text frames. First line = command. After newline = code body. Python code is never JSON-escaped.
ws.send("run work\nprint('hello')") # "hello"
ws.send("fire work\ntrain(epochs=50)") # {"cell_id":"..."}
ws.send("ls") # " work: alive"
The security model mirrors SSH:
| pythond | SSH equivalent |
|---|---|
token in daemon.json | private key in ~/.ssh/ |
pyctl trust cert.pem | adding a line to authorized_keys |
pyctl pin cert.pem | adding a line to known_hosts |
| authenticated client | logged-in user |
Once authenticated, a client has full access to all sessions — no per-session permission isolation. Same as SSH: once you log in, you are that user with all their permissions.
Not a sandbox: code runs with the daemon user's OS permissions.
| Mode | Auth |
|---|---|
| Local POSIX | AF_UNIX socket, mode 0o600 |
| Local Windows | TCP + token, OWNER RIGHTS DACL (owner-level isolation, comparable to Unix chmod 700) |
| Remote | pinned self-signed TLS cert + token; optional mTLS client cert |
Access logs are written as ACCESS lines to runtime access.log
and mirrored to daemon stderr for supervisors. They include conn_id, peer,
cmd, session, status, and body_bytes; they do not include tokens
or Python code bodies.
Interactive pysh run/fire/fork also echoes submitted code, errors,
and raw run output to the client terminal's stderr. That is operator
feedback, not daemon access logging.
Session names are lowercase only: a-z, 0-9,
underscore, and hyphen, up to 80 characters. Windows reserved device names such
as CON, NUL, COM1, and LPT1
are rejected.
Like shell history and environment variables under SSH, pythond session
history, logs, and live namespaces can expose secrets. history.py
and session.log may contain executed Python source and captured
output. Do not paste API keys, passwords, tokens, or other secrets into cells
unless you are willing for them to persist in that session and its local files.
| Platform | PTY | Transport |
|---|---|---|
| Linux / macOS / WSL | pty.openpty() | AF_UNIX WebSocket |
| Windows | pywinpty | TCP WebSocket |
| Package | Role |
|---|---|
| websockets | protocol transport |
| wsproto | raw WSS client framing |
| cryptography | TLS cert generation and pinning |
| pywinpty | Windows PTY (Windows only) |