A TUI primitive for coding agents — streaming markdown, tool calls, diffs, and more in linear scrollback with zero flicker.
Project description
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
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 agentic_tui-0.3.0.tar.gz.
File metadata
- Download URL: agentic_tui-0.3.0.tar.gz
- Upload date:
- Size: 5.9 MB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe9b469b55b10d944141978d09eaae2b3b854679ecf160d1a8dbadabade9931e
|
|
| MD5 |
4391e63eed0ffd9bf34b80613fae59db
|
|
| BLAKE2b-256 |
fd8e2fa771e8e5c36c3097859192cfddd89890e96ed1d4f7bc1da2199b3a671f
|
File details
Details for the file agentic_tui-0.3.0-py3-none-any.whl.
File metadata
- Download URL: agentic_tui-0.3.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cd028ffeff55bedb5080e0dbfca6cfa75ce6d0e0c2375696ad44ea0ec97fe32
|
|
| MD5 |
9e8f0001b803bd610432132659345bc0
|
|
| BLAKE2b-256 |
cd2436cefaaf7dcb43555ff22e8c7aacf77af3393ebfbd6732bdd7285de72cc9
|