Persistent terminal sessions for AI agents
Project description
agent-tty
Persistent terminal sessions for AI agents. Drives tmux, returns JSON.
The package is agent-tty. The CLI command is k, intentionally short to minimise token overhead in agent tool calls. km is the companion event monitor.
Requires tmux 3.0+ — k drives tmux for PTY multiplexing; it does not bundle or replace it.
Quick Start
k new work bash
k run -j work "echo hello"
# {"cell_id":"...","status":"done","output":"hello"}
k new py python3 -i # Python 3.12 and below
k new py "env PYTHON_BASIC_REPL=1 python3 -i" # Python 3.13+ (disables _pyrepl auto-indent)
k run -j py "print(42)"
Install
Requires: Python 3.8+, tmux 3.0+
pip install agent-tty # → k, km, agent-tty in PATH
Or without pip:
git clone <repo> && cd agent-tty
./scripts/k --help # works immediately (dev shim)
Or symlink into PATH:
ln -sf "$(pwd)/scripts/k" /usr/local/bin/k
ln -sf "$(pwd)/scripts/km" /usr/local/bin/km
Commands
k new <session> [cmd...] [--prompt="x"] spawn (default: bash)
k new <session> <cmd> --prompt=./hook hook mode
k fire [-t N] [session] <code> async fire (default 300s)
k poll [session] [cell_id] poll (O(1))
k run [-j] [-t N] [session] <code> sync (default 30s)
k await ... alias for run
k notify [session] <message> notification
k int [session] ctrl-c
k kill <session> kill + cleanup
k ls list sessions
k status [session] health check
k watch [session] live filtered view
k history [-n N] [session] last N×5 lines (default 5)
Frame Detection
Three modes via --prompt:
| --prompt= | mode | how |
|---|---|---|
| (not set) | repeat | 5 empty Enters → 5 identical lines → done |
"(gdb)" |
exact | match prompt string |
./hook.py |
hook | stdin lines → hook exit → done |
Hook protocol: k feeds ANSI-stripped lines to stdin. Hook exits = frame end. Hook paths must include a path separator (/, or \ on Windows). Path is canonicalised to absolute at k new time; hook must exist and be executable.
How It Works
k fire "echo hello"
|
+-- acquires lock (rejected fire = 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 -> exits
|
k poll
+-- checks result file (O(1))
+-- returns JSON
Safety
| invariant | mechanism |
|---|---|
| one cell per session | O_EXCL lock, acquired before send |
| timeout keeps lock | lock marked timed_out; subsequent polls say use k int or k kill |
| orphan recovery | bg PID in lock, poll checks os.kill(pid, 0) (POSIX) |
| no line-wrap skew | tmux width 10000 |
| atomic send | per-session named paste-buffer k_{session} |
| ctrl-c safe | kills watcher, writes {"status": "error", "output": "interrupted"}, re-sends frame enters (repeat only) |
| session name validation | [A-Za-z0-9_.-]+, no .., no path traversal |
| idempotent pipe restart | pipe-pane replaced on every fire/run |
| atomic result writes | tmp + fsync +os.replace — poll never reads partial JSON |
| no output classification | "done" = prompt appeared, not success |
JSON Schema (k)
fired: {"cell_id": "...", "status": "fired"}
running: {"cell_id": "...", "status": "running"}
done: {"cell_id": "...", "status": "done", "output": "..."}
timeout: {"cell_id": "...", "status": "timeout", "output": ""}
timeout(2+): {"cell_id": "...", "status": "timeout", "output": "use k int or k kill"}
error: {"status": "error", "output": "..."}
cell error: {"cell_id": "...", "status": "error", "output": "..."}
Errors without cell_id: no session 'x', active cell 'x', pipe failed: ..., send failed: ..., no active cell on 'x'.
Errors with cell_id: interrupted, unknown cell, watcher died, lock update failed; use k int or k kill, interrupt failed; use k kill.
km — event monitor
km <session> [cell_id] [-1]
Watches a session via pipe-pane. Each stdout line is one JSON event. -1 exits after first completion (one-shot .then()).
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": "..."}
Testing
python tests/test_contracts.py # static code contracts, no tmux
python tests/test_docs.py # README/SKILL drift, no tmux
bash tests/test.sh # 34 tests (32 without gdb), runtime smoke suite
python tests/test_regressions.py # targeted audit regressions
python tests/run_all.py # all suites
Files
src/agent_tty/cli.py k — main script
src/agent_tty/monitor.py km — event monitor
scripts/k, scripts/km dev shims (no pip install needed)
pyproject.toml pip install agent-tty → agent-tty, k, km in PATH
tests/test.sh runtime smoke suite
tests/*.py static, docs, and regression suites
SKILL.md agent reference
EXAMPLES.md patterns + philosophy
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_tty-0.1.0.tar.gz.
File metadata
- Download URL: agent_tty-0.1.0.tar.gz
- Upload date:
- Size: 33.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45079c9b36f90c4c51e6751c50dbd8b1523378ca815b272c3f95ca88ea014597
|
|
| MD5 |
5ef046ebe189afd2cefc36abcc97085b
|
|
| BLAKE2b-256 |
7984af7cfc80cc20ee89beef4e8eb34379295c38355725a720e0b5117d902111
|
Provenance
The following attestation bundles were made for agent_tty-0.1.0.tar.gz:
Publisher:
release.yml on rangersui/agent-tty
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_tty-0.1.0.tar.gz -
Subject digest:
45079c9b36f90c4c51e6751c50dbd8b1523378ca815b272c3f95ca88ea014597 - Sigstore transparency entry: 1870919416
- Sigstore integration time:
-
Permalink:
rangersui/agent-tty@fb3c705ba133cafb3013aa32854bd2fcaee73fad -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/rangersui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fb3c705ba133cafb3013aa32854bd2fcaee73fad -
Trigger Event:
release
-
Statement type:
File details
Details for the file agent_tty-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agent_tty-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f476afaadb4abe1389de32a160782808d0770783d74d684f3b36b14d627803af
|
|
| MD5 |
2705bc80716ed0daaee0e21c5d75b48a
|
|
| BLAKE2b-256 |
62edba6e081dad05375652e2d5f4dc250ea54acb2a18ad19f86f8bb250d5c00a
|
Provenance
The following attestation bundles were made for agent_tty-0.1.0-py3-none-any.whl:
Publisher:
release.yml on rangersui/agent-tty
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_tty-0.1.0-py3-none-any.whl -
Subject digest:
f476afaadb4abe1389de32a160782808d0770783d74d684f3b36b14d627803af - Sigstore transparency entry: 1870919522
- Sigstore integration time:
-
Permalink:
rangersui/agent-tty@fb3c705ba133cafb3013aa32854bd2fcaee73fad -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/rangersui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fb3c705ba133cafb3013aa32854bd2fcaee73fad -
Trigger Event:
release
-
Statement type: