Skip to main content

agent-tty

Persistent Python runtime for your AI agent.

Each session is a long-lived Python namespace in a child process. Agent cells enter that namespace through eval/exec, so imports, variables, open connections, servers, and in-memory decisions survive across agent turns.

bash_tool is curl: every call forks a process, runs, and dies. k is a socket: one process stays alive, and every call is a function invocation inside it. The agent sends source code through a structured pipe and receives captured output. Humans attach through a real terminal with readline, tab completion, colors, and Ctrl-C. Both channels share the same namespace.

Requires Python 3.10+.

Install the CLI:

python -m pip install agent-tty
k --version

After install, k is the complete CLI. Do not edit k.

From a source checkout, install editable once so k resolves to this tree:

python -m pip install -e .
k --version

The code help is the single source of truth:

k --help

Quick Start

Start the daemon in one terminal:

k daemon

Stop the daemon with k stop from a client terminal, or Ctrl-C in the daemon terminal. Daemon shutdown terminates owned sessions, closes the control socket, and removes TCP daemon.json metadata.

Use the client from another terminal:

k new work
k run work "x = 41"
k run work "x + 1"
# 42

Async cells:

k fire work "import time; time.sleep(2); y = x + 1"
# {"cell_id": "a1b2c3d4e5f6", "status": "fired"}

k poll work a1b2c3d4e5f6
# {"cell_id": "a1b2c3d4e5f6", "status": "done", "output": ""}

k run work "y"
# 42

Human attach:

k attach work

On PTY and WinPTY sessions, Ctrl-] detaches and leaves the session alive. exit() exits the session process. On socket-console sessions, ending stdin detaches.

Runtime Model

k daemon runs a foreground daemon. The daemon owns named session processes and a local control socket.

Each session is a Python process with:

  • one persistent namespace,
  • one execution lock,
  • one cell table for async fire results,
  • one human attach surface.

AI commands use a separate control path and share the same namespace as the human console.

Stateful First

Most command tools are intentionally stateless: fork, run, die. That is simple for humans, but wasteful for agents. Agents repeat imports, reopen connections, re-parse configs, and rebuild intermediate data because the process disappears after every call.

agent-tty flips that default. The process is the workspace. State is not a cleanup problem first; it is addressable memory.

Things that stay alive:

  • Python variables, imports, compiled regexes, parsed configs, DataFrames, and models,
  • database handles, HTTP sessions, WebSockets, TCP sockets, SSH tunnels, and browser/CDP sessions,
  • Flask apps, local servers, file watchers, monitors, and other daemon threads,
  • live control-plane decisions such as feature flags, rate limits, blocked IP sets, routing weights, and circuit breaker state.

Static config can become a Python variable. Patch one cell; the next request sees it. No restart is needed for logic that already lives inside the session.

Two Channels, One Namespace

The agent channel is structured: source code in, captured text/JSON out. It does not need to parse ANSI escape sequences, cursor movement, prompts, or screen redraws to know when a cell finished.

The human channel is interactive: k attach connects to the same process through PTY, WinPTY, or socket-console mode. A human can inspect variables, interrupt with Ctrl-C, or detach with Ctrl-] without discarding the namespace.

That split is the core design: pure data for agents, real terminal ergonomics for humans, one shared runtime underneath.

Session Modes

agent-tty uses the best local console surface available:

mode platform human attach
POSIX PTY Linux, macOS, WSL raw terminal, readline, tab, arrows, Ctrl-C
WinPTY Windows with pywinpty raw terminal through WinPTY
socket console fallback line-based InteractiveConsole over local TCP

PTY and WinPTY sessions support Ctrl-] to detach while the Python session keeps running. exit() exits the session process. Socket-console sessions detach when stdin ends.

Default Work Surface

Default to the live session for project work. Once a daemon/session exists, commands that affect the task should go through k run or k fire, even when the command is as simple as ls, pwd, or git status.

That keeps the work in one inspectable runtime: cwd changes, environment mutations, imports, open sockets, cached data, and command history all stay with the session the human can attach to.

Use the host shell as plumbing:

  • start or stop the daemon,
  • write larger Python cells to files before loading them,
  • inspect or repair the repository when no session is available.

Inside the session, call host commands through Python:

k run work "import subprocess; subprocess.run(['git', 'status'])"

Command Reference

k daemon [--show-token]   start daemon in foreground
k stop                    stop daemon gracefully
k new <name>              create a Python session
k int <name>              interrupt running async cells
k kill <name>             terminate session process and forget it
k run <name> "code"       sync Python eval/exec, print raw output
k fire <name> "code"      async queued eval/exec, print JSON cell_id
k poll <name> [cell_id]   print JSON cell result
k status <name>           print JSON session state
k vars <name>             print JSON list of public namespace names
k complete <name> "text"  print JSON Python completion candidates
k ls                      list sessions
k attach <name>           attach human REPL to the session
k --version|-V|version    print version

k new <name> creates a Python session. Put host commands inside Python cells with subprocess:

k run work "import subprocess; subprocess.run(['git', 'status'])"

For larger code, write a Python file and load it into the live session:

cat > /tmp/agent_tty_task.py << 'PY'
import subprocess
result = subprocess.run(["git", "status"], text=True, capture_output=True)
print(result.stdout)
PY

k run work "exec(open('/tmp/agent_tty_task.py').read())"

REPL Patterns

Because the session is a Python REPL, ordinary Python patterns become agent operations:

  • Prefix tax: import what you use once, then call shorter names in later cells.
  • Print tax: expression results display automatically; the last expression does not need print().
  • Hot reload: use exec(open("module.py").read()) or importlib.reload(m) to update code without losing process state.
  • Incremental execution: split a long script into cells. If step 3 fails, fix step 3; steps 1 and 2 still exist in memory.
  • Catch, fix, retry: read the traceback, patch a function, and run again in the same namespace.
  • Host commands: use subprocess.run(..., capture_output=True, text=True) from inside the session when you need the OS.

The REPL is Turing complete. File watchers, completion callbacks, local monitors, proxy servers, and control loops do not need to be built into agent-tty; the session can build them as Python code.

Output Formats

Each command has a fixed output style:

command format shape
k daemon process foreground daemon; startup line on stderr
k stop text OK stopping daemon or ERR ...
k new text OK <name> pid=<pid> ... or ERR ...
k int text OK interrupted <name> (N cells) or ERR ...
k kill text OK killed <name> or ERR ...
k ls text one line per session, or (no sessions)
k run raw text captured stdout/stderr from the cell
k fire JSON {"cell_id":"...","status":"fired"}
k poll JSON `{"cell_id":"...","status":"running
k status JSON `{"state":"idle
k vars JSON {"vars":["name", ...]}
k complete JSON {"matches":["os.path", ...]}
k attach stream interactive console
k --version text agent-tty 0.2.1; aliases: k -V, k version

k run prints expression results like a Python REPL: strings print as raw text; other values use repr.

Assignments usually produce no output:

k run work "x = 1"
# empty output

Expressions print:

k run work "x + 1"
# 2

Async Cells

fire starts a background cell and returns immediately. Cells inside one session execute serially under the session lock. Create multiple sessions for parallel work.

k new a
k new b
k fire a "import time; time.sleep(5); result = 'A'"
k fire b "import time; time.sleep(5); result = 'B'"

poll with a cell id returns that cell. poll without a cell id returns the most recent cell in the session, or {"status":"idle"} if no cells exist.

Transport

AF_UNIX mode uses K_SOCK, defaulting to /tmp/k.sock.

TCP mode uses 127.0.0.1:K_PORT (default 7399) and token authentication. Native Windows uses TCP mode. The daemon writes private daemon metadata for local clients:

k daemon pid=12345 127.0.0.1:7399 mode=winpty meta=...\daemon.json

The metadata file lets a new terminal run k ls without setting K_TOKEN. Shutdown removes the metadata file.

platform metadata path
Windows %LOCALAPPDATA%\agent-tty\daemon.json
POSIX TCP $XDG_RUNTIME_DIR/agent-tty/daemon.json
POSIX TCP fallback /tmp/agent-tty-$UID/daemon.json

Only one auto-discoverable TCP daemon can own daemon.json at a time. Starting a second TCP daemon while the metadata file points to a live daemon fails loud instead of replacing the first daemon's token.

K_TOKEN and K_PORT remain explicit overrides for debugging or unusual shells. Use k daemon --show-token only when you deliberately want shell setup text printed to stderr:

k daemon --show-token
set K_TOKEN=abc123...
export K_TOKEN=abc123...

Attach uses the same token lookup in TCP mode, so k attach works from another terminal after the daemon metadata file exists.

In a source checkout, k.py.template is an optional debug wrapper for storing a local daemon token:

cp k.py.template k.py
# paste K_TOKEN and K_PORT into k.py if you want a fixed-token wrapper
python k.py ls
python k.py new work

k.py is listed in .gitignore because it contains a live local daemon token.

On Windows, WinPTY mode requires pywinpty. With pywinpty available the daemon prints mode=winpty; otherwise it falls back to mode=socket.

Tests

Static syntax check:

python -B -m py_compile agent_tty.py k.py.template tests/test_pty_posix.py tests/test_tcp_windows.py

POSIX PTY regression suite:

python3 -B tests/test_pty_posix.py

The POSIX suite starts a daemon with a Unix socket, creates a PTY session, and checks new, ls, run, fire, poll, status, complete, and kill.

Windows TCP regression suite:

python -B tests/test_tcp_windows.py

The Windows suite starts a daemon on loopback TCP, verifies daemon.json token discovery without startup token leakage, then exercises the real CLI client path. It works with WinPTY when pywinpty is installed and with socket-console fallback otherwise.

Download files

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

Source Distribution

agent_tty-0.2.1.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

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

agent_tty-0.2.1-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file agent_tty-0.2.1.tar.gz.

File metadata

  • Download URL: agent_tty-0.2.1.tar.gz
  • Upload date:
  • Size: 31.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for agent_tty-0.2.1.tar.gz
Algorithm Hash digest
SHA256 9107fb68d623595dcb71d8623b6fb02aabd83ad221f9ec273a96e4b778cf3cf7
MD5 7589593c27db55904ed53f23641dd8ab
BLAKE2b-256 01284ad9863a6b817240f93a7225269920347c707788e11128baf9a9bd1e4f94

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_tty-0.2.1.tar.gz:

Publisher: release.yml on rangersui/agent-tty

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agent_tty-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: agent_tty-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for agent_tty-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d9b8ceedc585c7f23233a91a33a4d7d977716924c23c943d022578c50c741911
MD5 40dc1b7a5e078ca4e12fd46c159dd299
BLAKE2b-256 012df813b784a13889b94941268194feb3e3891725c5d95e648d9d0a87ac4929

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_tty-0.2.1-py3-none-any.whl:

Publisher: release.yml on rangersui/agent-tty

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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