agent-tty

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 bash OK 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 work watching 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 work OK $ 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", ...}

Events

fired {cell_id, session, status: "fired", ts} done {cell_id, session, status: "done", ts} notify {session, status: "notify", from, message, ts} closed {session, status: "closed", ts} error {session, status: "error", message, ts}