Persistent REPL for AI agents. Shared live terminal for humans. bash_tool is curl. k is a socket.
pip install agent-tty
Requires Python 3.8+ and tmux 3.0+
bash
# start a persistent bash session$ k new work bashOK work# run code, get JSON$ k run -j work "echo hello"{"cell_id": "a1b2c3", "status": "done", "output": "hello"}# state survives across cells$ k run -j work "cd /tmp && export MODE=warm"{"cell_id": "d4e5f6", "status": "done", "output": ""}$ k run -j work "pwd; echo $MODE"{"cell_id": "e7f8a9", "status": "done", "output": "/tmp\nwarm"}
Shared live TTY
The agent is not hidden inside a background task. It works in a real tmux session that a human can watch, interrupt, or take over without losing process state.
agent view
# agent sends a multi-command bash cell$ k fire work "cd app && pytest -q && tail -20 logs/server.log && export FIX_READY=1"{"cell_id": "a1b2c3", "status": "fired"}$ km work a1b2c3 -1{"status": "done", "ts": "..."}
human view
# k watch = filtered live view: cell markers, ✓ on done, frame noise hidden$ k watch workwatching work (ctrl-c to stop)
── a1b2c3d4 ──
$ cd app
$ pytest -q
..F
test_api.py::test_health failed
$ tail -20 logs/server.log
missing DATABASE_URL
$ export FIX_READY=1
── ✓ ──# k int interrupts from outside; tmux attach is native raw takeover$ k int workOK$ tmux attach -t work
Why agent-tty
👁️
Human-visible TTY
k watch streams the same live terminal. A human can see progress, interrupt, or attach directly.
🧠
State survives
Variables, cwd, imports, live connections, SSH sessions, and debugger state stay alive across agent turns.
🔔
Callback completion
km wakes the agent when a long cell finishes. No poll loop when the runtime can monitor stdout.
🔄
JSON cell API
run for inline cells, fire for long cells, poll as a fallback. All return structured JSON.
🎯
Any REPL
bash, python, gdb, redis-cli, ssh — anything with a readline prompt. Three frame detection modes adapt to each.
🛡️
Safe recovery
One cell at a time, timeout locks, k int Ctrl-C, and atomic result writes keep sessions recoverable.
How it works
k fire "echo hello"
|
+-- acquires lock (O_EXCL, rejected = zero side effects)
+-- sends code via paste-buffer (atomic)
+-- sends 5 frame enters (repeat mode only)
+-- starts background stream processor
|
stream processor tails log:
ECHOING: skip echo_count lines
OUTPUT: collect lines
DONE: 5 identical lines / prompt match / hook exit
|
writes result file (fsync + os.replace) -> exits
|
k poll
+-- checks result file (O(1))
+-- returns JSON
k run "echo hello"
|
+-- acquires lock -> sends code -> runs stream processor inline
+-- blocks until done or timeout
+-- returns JSON directly (no poll needed)
km — callback monitor
Persistent TTY state first; callback completion second.
Each stdout line is one JSON event for agent orchestration.
Designed for Claude Code's Monitor tool. Other frameworks: spawn km as a subprocess, read stdout.
agent orchestration
# poll is a fallback when the runtime cannot monitor stdout$ k poll work# → "running"... call again... "running"... again...# with km: fire a long stateful cell, then wait for its completion event$ k fire work "make build"{"cell_id": "a1b2c3", "status": "fired"}$ km work a1b2c3 -1{"cell_id": "a1b2c3", "session": "work", "status": "done", "ts": "..."}# continuous mode: watch all events across a session$ km work{"status": "fired", ...}{"status": "done", ...}{"status": "notify", "message": "deploy complete", ...}{"status": "fired", ...}{"status": "done", ...}