agentui
A small, opinionated Python library for building coding-agent terminal UIs.
Streaming markdown, tool-call blocks, unified diffs, confirmation prompts, slash commands, and an @-file picker — all in linear scrollback with zero flicker.
Why
Every coding agent — Claude Code, Codex, Aider, Toad — converges on the same terminal UX: linear scrollback, streaming markdown that updates in place without flicker, collapsible tool calls, unified diffs, slash commands, an @-file picker. But in Python, no library delivers this shape as a coherent primitive.
The options today are bad:
- Textual is a full TUI framework optimized for multi-pane applications. It owns the alt-buffer, breaks native terminal selection, and forces a CSS/widget mental model that's overkill for a chat-shaped agent loop.
- prompt_toolkit + Rich in REPL mode preserves scrollback and copy-paste, but offers no agent-specific primitives. Every project reinvents streaming markdown, tool-call panels, diff rendering, confirmation prompts, and slash commands from scratch.
Other ecosystems have closed this gap — pi-tui in TypeScript (powering Mastra Code), claude-code-kit (extracted Claude Code components), Bubble Tea + Lip Gloss in Go, Ratatui in Rust. Python had no equivalent.
agentui exists because building a polished agent UI shouldn't require 2000 lines of rendering plumbing. It should take 30.
Install
pip install agentui
For .gitignore-aware @-file completion:
uv pip install agentui[gitignore]
Quick Start
import asyncio
from agentui import Session
async def main():
async with Session() as ui:
while True:
user_input = await ui.prompt("> ")
if user_input.startswith("/"):
await ui.handle_command(user_input)
continue
async with ui.assistant_turn() as turn:
async for chunk in your_llm.stream(user_input):
await turn.append_markdown(chunk.text)
asyncio.run(main())
That's a working agent chat UI in 14 lines.
Features
Streaming Markdown
Accepts an async iterator of string chunks. Updates in place using synchronized output (CSI ?2026h/l) — zero flicker on modern terminals, graceful degradation elsewhere. When the turn completes, content commits to permanent scrollback.
async with ui.assistant_turn() as turn:
async for chunk in stream:
await turn.append_markdown(chunk)
Tool Call Blocks
Structured rendering with name, JSON-pretty arguments, status badge (pending/running/done/error), and result. Collapses automatically after completion.
async with turn.tool_call("search", {"query": "test", "limit": 5}) as tc:
result = await execute_search(...)
await tc.complete(result)
Unified Diffs
Colored diff rendering with line numbers, green additions, red deletions. Supports an inline confirmation prompt.
block = turn.diff("src/main.py", unified_diff_string)
approved = await block.confirm() # prompts user inline
Confirmation Prompts
Yes/no, multi-choice, and free-text — all keyboard-driven, rendered in scrollback.
if await ui.confirm("Apply changes?"):
apply()
choice = await ui.choose("Pick a model:", ["gpt-4", "claude", "local"])
name = await ui.input("Project name:", default="my-project")
Slash Commands
Register handlers and get tab-completion for free. Built-ins: /help, /quit, /clear.
@ui.command("model", help_text="Switch the active model")
async def cmd_model(session, args):
session.print(f"Switched to {args}")
@-File Picker
Type @ in the prompt to fuzzy-match files in the current directory. Respects .gitignore (uses pathspec if installed, falls back to basic glob matching).
Interrupt Handling
Ctrl-C during streaming cancels the in-flight turn. Partial content commits cleanly to scrollback — terminal state is never corrupted.
async with ui.assistant_turn() as turn:
async for chunk in stream:
if turn.cancelled:
break
await turn.append_markdown(chunk)
Architecture
agentui is a thin orchestration layer over two battle-tested libraries:
- Rich — rendering (markdown, syntax highlighting, panels, tables)
- prompt_toolkit — input (history, key bindings, completion)
The core innovation is a scrollback-aware live region manager. Only the currently-streaming turn uses a managed region wrapped in synchronized output sequences. When the turn completes, the content becomes ordinary terminal scrollback. Native copy-paste, tmux scrollback, and Cmd-F search keep working.
User application (agent loop)
|
| render calls + input prompts
v
agentui
┌────────────┐ ┌───────────────┐
│ Live region │ │ Input session │
│ manager │ │ (slash cmds, │
│ (sync out) │ │ file picker) │
└──────┬──────┘ └───────┬───────┘
│ │
┌──────▼──────┐ ┌───────▼───────┐
│ Rich │ │ prompt_toolkit │
│ Console │ │ PromptSession │
└─────────────┘ └───────────────┘
API Reference
Session
The main entry point. Async context manager.
| Method | Description |
|---|---|
prompt(message) |
Async user input with slash command and @-file completion |
assistant_turn() |
Returns a Turn async context manager |
print(*args) |
Print to scrollback (delegates to Rich Console) |
confirm(message, default) |
Yes/no prompt, returns bool |
choose(message, options, default) |
Multi-choice, returns int index |
input(message, default) |
Free-text input, returns str |
handle_command(command) |
Dispatch a /command to its handler |
command(name, help_text) |
Decorator to register a slash command |
register_command(name, handler) |
Non-decorator command registration |
Turn
A single assistant response. Async context manager opened via session.assistant_turn().
| Method | Description |
|---|---|
append_markdown(text) |
Stream a markdown chunk |
tool_call(name, args) |
Returns a ToolCallContext async context manager |
diff(path, patch) |
Render a unified diff block, returns DiffBlock |
cancelled |
bool — whether Ctrl-C was pressed |
ToolCallContext
Async context manager for tool execution within a turn.
| Method | Description |
|---|---|
complete(result) |
Mark done with optional result string |
error(message) |
Mark as errored |
DiffBlock
Rendered diff with optional inline confirmation.
| Method | Description |
|---|---|
confirm() |
Async — prompts user to accept/reject, returns bool |
approved |
`bool |
Non-Goals
- Not a general TUI framework. Use Textual for multi-pane dashboards.
- Not an agent framework. No LLM client, no tool dispatcher, no memory store. The library renders; you drive.
- Not alt-buffer / full-screen. The whole point is to preserve native scrollback.
Requirements
- Python 3.13+
rich >= 13.0prompt-toolkit >= 3.0
License
MIT
Release files for agentic-tui 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agentic_tui-0.3.0.tar.gz | 5.9 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentic_tui-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 5.9 MB
Release files / agentic_tui-0.3.0.tar.gz
| Download URL | agentic_tui-0.3.0.tar.gz |
|---|---|
| Size | 5.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fe9b469b55b10d944141978d09eaae2b3b854679ecf160d1a8dbadabade9931e
|
|
BLAKE2b-256 checksum How to use checksums |
fd8e2fa771e8e5c36c3097859192cfddd89890e96ed1d4f7bc1da2199b3a671f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / agentic_tui-0.3.0-py3-none-any.whl
| Download URL | agentic_tui-0.3.0-py3-none-any.whl |
|---|---|
| Size | 20.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0cd028ffeff55bedb5080e0dbfca6cfa75ce6d0e0c2375696ad44ea0ec97fe32
|
|
BLAKE2b-256 checksum How to use checksums |
cd2436cefaaf7dcb43555ff22e8c7aacf77af3393ebfbd6732bdd7285de72cc9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|