agent-tty

A persistent TTY for your AI agent.
Shared live terminal for humans.
bash_tool is curl. k is a socket.

pip install agent-tty

Requires POSIX, Python 3.10+, and tmux 3.0+. Linux, macOS, and WSL are supported; native Windows fails fast.

v0.1.2 · MIT

bash
# agent creates a persistent session $ k new work bash OK work # agent runs code, gets JSON $ k run -j work "echo hello" {"cell_id": "a1b2c3d4e5f6", "status": "done", "output": "hello"} # agent writes complex code to a file, loads it through k $ cat > /tmp/task.sh << 'EOF' cd /tmp export MODE=warm printf 'mode=%s pwd=%s\n' "$MODE" "$PWD" EOF $ k run -j work "source /tmp/task.sh" {"cell_id": "d4e5f6a7b8c9", "status": "done", "output": "mode=warm pwd=/tmp"} # cwd/env persisted because source ran in the live session $ k run -j work 'pwd; echo $MODE' {"cell_id": "e7f8a9b0c1d2", "status": "done", "output": "/tmp\nwarm"}

Shared live TTY

The agent works in a real tmux session — not hidden inside a background task. You watch it live, interrupt with k int, or take over with tmux attach.

agent view
# agent fires a long cell via its shell tool $ k fire work "cd app && pytest -q && tail -20 logs/server.log && export FIX_READY=1" {"cell_id": "a1b2c3d4e5f6", "status": "fired"} $ km work a1b2c3d4e5f6 -1 {"cell_id": "a1b2c3d4e5f6", "session": "work", "status": "done", "ts": "..."}
human view
# you watch: filtered live view of what the agent is doing $ 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 ── ✓ ── # you interrupt the agent; tmux attach for native raw takeover $ k int work OK $ tmux attach -t work

Why give your agent a TTY

👁️

You can watch

You see everything the agent does. k watch for filtered live view, tmux attach for raw TTY takeover.

🧠

Agent state survives

Variables, cwd, imports, live connections, SSH sessions, and debugger state stay alive across agent turns.

🧵

Zero escaping

Write code to a file, load with source/exec. The heredoc is literal, the REPL command is one line, and live session state persists. No quoting layers to fight.

🔔

Callback completion

km wakes the agent when a long cell finishes. No poll loop when the runtime can monitor stdout.

🔄

JSON cell API

run -j for inline cells, fire/poll for long cells. Structured JSON with cell_id, status, and output.

🎯

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 recovery hints, k status next actions, and k int Ctrl-C — you or the agent can always recover.

Agent workflow

k new work bash +-- agent creates one persistent TTY +-- cwd, env, imports, sockets, ssh, debuggers stay alive k run -j work "echo hello" +-- short cell +-- returns JSON directly k fire work "pytest -q && tail -20 logs/server.log" +-- long cell +-- returns a cell_id immediately km work <cell_id> -1 +-- wait once +-- wake the agent on completion k status work +-- repair the log pipe if needed +-- print the next useful command k watch work you watch, filtered live view tmux attach -t work you take over, raw terminal k int work you interrupt the agent

km — callback monitor

Wakes your agent when a long cell finishes. No polling, no sleep loops.
Each stdout line is a JSON event. Any host with background-notification support can consume them directly.

agent orchestration
# poll is a fallback when the agent runtime cannot monitor stdout $ k poll work # → "running"... call again... "running"... again... # with km: agent fires a long cell, gets woken on completion $ k fire work "make build" {"cell_id": "a1b2c3d4e5f6", "status": "fired"} $ km work a1b2c3d4e5f6 -1 {"cell_id": "a1b2c3d4e5f6", "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} timeout {cell_id, session, status: "timeout", ts} interrupted {cell_id, session, status: "interrupted", ts} notify {session, status: "notify", from, message, ts} closed {session, status: "closed", ts} error {session, status: "error", message, ts}