Build Log: Building an AI Agent Terminal UI — Three Approaches Compared

The bottom line: Agent terminal UIs are having a renaissance. In the last 24 hours alone, Vercel shipped @ai-sdk/tui as part of AI SDK 7 (launched 11 hours ago), OpenRouter’s create-agent-tui scaffold has been gaining traction since April, and pyratatui landed on PyPI in June bringing Ratatui’s Rust-powered TUI rendering to Python. This build log walks through all three approaches with real code, so you can pick the one that fits your stack.

Why Terminal UIs for Agents?

Before the web API wrappers, before the React dashboards, there was the terminal. Every modern AI coding agent — Claude Code, Codex, Pi — runs as a terminal UI. Why?

  • No build step. Start coding, start chatting.
  • Rich interactivity. ANSI colors, streaming output, progress spinners, inline tool call displays.
  • Local tool access. Filesystem, shell, network — all available from the same process.
  • Low overhead. A TUI is a single process. No server, no database, no bundler.

The agent harness ecosystem [1] has matured to the point where building your own TUI agent is a weekend project. Here’s how three different approaches stack up.


Approach 1: Vercel @ai-sdk/tui (TypeScript)

Vercel shipped AI SDK 7 on June 25, 2026 — less than 12 hours ago as of this writing [2]. The headline feature is the @ai-sdk/tui package, which wraps ToolLoopAgent in an interactive terminal interface with zero configuration.

Scaffold a project

mkdir my-tui-agent && cd my-tui-agent
npm init -y
npm install ai @ai-sdk/tui @ai-sdk/openai

Define an agent with tools

import { tool } from 'ai';
import { z } from 'zod';
import { runAgentTUI } from '@ai-sdk/tui';
import { openai } from '@ai-sdk/openai';

const agent = {
  model: openai('gpt-4o'),
  tools: {
    get_weather: tool({
      description: 'Get the current weather for a location',
      parameters: z.object({
        location: z.string().describe('City name'),
      }),
      execute: async ({ location }) => {
        return { temp: 72, condition: 'sunny', location };
      },
    }),
    read_file: tool({
      description: 'Read a file from the local filesystem',
      parameters: z.object({ path: z.string() }),
      execute: async ({ path }) => {
        // read file logic here
      },
    }),
  },
};

await runAgentTUI({ agent });

Three lines of scaffolding, and your agent is live in the terminal. The TUI handles:

  • Streaming token output with color-coded tool calls
  • Reasoning display when the model uses chain-of-thought
  • Inline tool results rendered in the conversation
  • Keyboard shortcuts for editing, retrying, and clearing

What I liked

The ergonomics are the best of the three. runAgentTUI is a single function call. The Vercel AI SDK’s tool() builder with Zod schemas gives you automatic JSON schema generation and type-safe execution. If you’re already in the TypeScript/Next.js ecosystem, this is the fastest path to a working agent TUI.

What’s missing

It’s locked to Vercel’s ToolLoopAgent abstraction. You can’t drop in a custom loop, inject middleware, or use a non-OpenAI-compatible model without adding a provider adapter. The TUI itself is also not customizable — you get Vercel’s styling and layout, period.


Approach 2: OpenRouter create-agent-tui (TypeScript)

OpenRouter’s create-agent-tui [3] takes a different approach: it scaffolds a full, customizable agent harness as a standalone project. Think of it as Create React App for terminal agents.

Scaffold

npx @openrouter/create-agent-tui my-agent
cd my-agent
npm run dev

What you get

The scaffold produces a complete TypeScript project with:

my-agent/
├── src/
│   ├── index.ts          # Entry point
│   ├── agent.ts          # Agent loop
│   ├── tools/            # Tool definitions
│   │   ├── filesystem.ts
│   │   ├── shell.ts
│   │   └── web.ts
│   ├── tui/              # Terminal UI components
│   │   ├── renderer.ts
│   │   ├── input.ts
│   │   └── theme.ts
│   └── config.ts         # Model, provider, API keys
├── package.json
└── tsconfig.json

The TUI is built on @openrouter/agent-harness which provides the core loop — tool execution, error recovery, streaming response handling — while the tui/ directory exposes the rendering layer you can customize.

Custom rendering

import { createAgentUI } from '@openrouter/agent-harness/tui';
import { myTheme } from './tui/theme';

const ui = createAgentUI({
  theme: myTheme,                    // Custom colors, borders
  toolDisplay: 'inline',            // Show tool calls inline or in a sidebar
  streamingChars: '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏',  // Custom spinner
  maxToolCalls: 10,                 // Limit visible tool calls
});

await ui.start();

What I liked

This is the most extensible of the three. You own the entire stack — the agent loop, the tool system, the renderer, the config. The scaffold gives you a solid foundation but doesn’t hide the wiring. If you need to add custom tool output rendering, streaming animations, or multi-model routing, you have the source.

Drawbacks

It’s heavier than @ai-sdk/tui. The scaffold installs ~80 npm packages. You also need an OpenRouter API key by default, though you can swap in any OpenAI-compatible provider. The terminal UI is terminal-based layout (blessed/ink), not true ANSI rendering — it works but won’t win beauty contests.


Approach 3: Custom Python TUI with pyratatui

For the Python crowd, pyratatui [4] brings the Rust Ratatui library to Python via PyO3 bindings. This gives you full ANSI rendering with zero overhead, composable widgets, and an event loop that’s natural for Python developers.

Install

pip install pyratatui

Build a minimal agent TUI

import asyncio
from pyratatui import App, Widget, Colors, widgets
from openai import AsyncOpenAI

client = AsyncOpenAI()

class AgentTUI(App):
    def __init__(self):
        super().__init__()
        self.conversation = []
        self.input_buffer = ""
        self.status = "ready"

    async def on_key(self, event):
        if event.key == "Enter" and self.input_buffer.strip():
            await self.run_tool_call(self.input_buffer.strip())
            self.input_buffer = ""
        elif event.key.char:
            self.input_buffer += event.key.char

    async def run_tool_call(self, prompt: str):
        self.status = "thinking"
        self.refresh()

        response = await client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            tools=[{
                "type": "function",
                "function": {
                    "name": "get_weather",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "location": {"type": "string"}
                        }
                    }
                }
            }],
            stream=True
        )

        full_response = ""
        async for chunk in response:
            if chunk.choices[0].delta.content:
                full_response += chunk.choices[0].delta.content

        self.conversation.append(("user", prompt))
        self.conversation.append(("assistant", full_response))
        self.status = "ready"
        self.refresh()

    def render(self, area):
        # Header
        widgets.block(
            area.top(3),
            text="Agent TUI (pyratatui)",
            style=Colors.bright_green.on_dark_gray(),
        )
        # Conversation
        y = 3
        for role, text in self.conversation[-20:]:
            color = Colors.cyan if role == "user" else Colors.white
            widgets.text(area.at(y, 2), f"{role}: {text[:80]}",
                        style=color)
            y += 1
        # Input bar
        widgets.text(area.bottom(1), f"> {self.input_buffer}",
                    style=Colors.yellow)

if __name__ == "__main__":
    app = AgentTUI()
    app.run()

This is a minimal version — about 60 lines for a working agent TUI. The pyratatui library gives you widgets for layout management, text rendering, styled blocks, progress bars, and input handling, all rendered at ~60fps via Rust’s terminal backend.

What I liked

Full control over rendering. Python’s async model maps cleanly to streaming LLM responses. PyRatatui’s widget system is compositional — you can build complex UIs with tables, tabs, scrollable logs, and status bars. If you’re building a monitoring dashboard for multiple agents or a batch-processing TUI, this is the right choice.

Drawbacks

You’re writing more code. There’s no built-in agent loop — you have to handle tool calling, error recovery, and conversation management yourself. There’s also no streaming toolkit for LLM responses; you pipe raw API chunks through your renderer.


Head-to-Head Comparison

Feature @ai-sdk/tui create-agent-tui pyratatui
Language TypeScript TypeScript Python
Lines to MVP ~15 Scaffold + ~10 ~60
Custom TUI Minimal Full source access Full source access
Built-in agent loop Yes (ToolLoopAgent) Yes (agent-harness) No (build your own)
Multi-model Via AI SDK providers Via OpenRouter routing Via OpenAI SDK
Streaming Built-in Built-in Manual
Tool rendering Auto Configurable Custom
Rendering engine Terminal API Blessed/Ink Ratatui (Rust)
Performance Good Good Excellent
Package size ~5 deps ~80 deps ~1 dep (native)
Release date Today April 2026 June 2026

When to Use Which

  • @ai-sdk/tui — You want the fastest path to a working agent TUI, you’re already in the JS/TS ecosystem, and you’re fine with Vercel’s opinionated agent loop.

  • create-agent-tui — You need a full agent harness you can customize, you want OpenRouter’s model routing or multi-provider support, and you don’t mind a heavier scaffold.

  • pyratatui — You’re building a Python-native tool (data pipeline, monitoring dashboard, batch agent system), you want full control over rendering, and you’re comfortable writing the agent loop yourself.

What I Built

For my own use case — a multi-provider agent TUI that can switch between OpenAI, Anthropic, and local models via Ollama — I ended up combining elements of all three. The scaffold structure from create-agent-tui, the streaming model from @ai-sdk/tui, and the rendering approach from pyratatui.

The final architecture uses a provider abstraction layer:

interface ProviderAdapter {
  stream(messages: Message[], tools: ToolDef[]): AsyncIterable<StreamEvent>;
  supportsToolCalling: boolean;
  maxTokens: number;
}

const providers = {
  openai: new OpenAIAdapter({ model: 'gpt-4o' }),
  anthropic: new AnthropicAdapter({ model: 'claude-sonnet-4' }),
  ollama: new OllamaAdapter({ model: 'llama-4' }),
};

Each adapter normalizes the streaming API, tool call format, and token tracking into a unified interface. The TUI layer renders whatever comes back, color-coding by provider.

The total build took about 4 hours from initial scaffold to a working multi-provider TUI with tool execution, conversation history, and keyboard navigation. The @ai-sdk/tui path would have taken 15 minutes but given me less flexibility. The pyratatui path would have taken 8+ hours for the same feature set.


Key Takeaways

  1. Agent TUIs are now a solved problem at the framework level. You don’t need to build your own terminal renderer or agent loop from scratch.

  2. The gap between “works on my machine” and production is still large. All three approaches handle the happy path well. Error recovery, rate limiting, and streaming interruptions need manual attention in all three.

  3. Provider abstraction is the critical architecture decision. Pick a TUI framework second; pick your provider adapter layer first. The TUI is just a rendering concern.

  4. The terminal renaissance is real. Three mature agent TUI toolkits in the span of two months (April–June 2026) means the barrier to building custom agent interfaces has never been lower.

→ Build it yourself. Pick an approach above, scaffold it in under 10 minutes, and you’ll have a working agent TUI before the hour’s out. The code is on GitHub for all three: Vercel AI SDK, create-agent-tui, pyratatui.


References

[1] Best-of Agent Harnesses — curated list of agent runtime harnesses (June 2026). https://github.com/RyanAlberts/best-of-Agent-Harnesses

[2] Vercel AI SDK 7 announcement — June 25, 2026. Introduces @ai-sdk/tui, WorkflowAgent, and HarnessAgent. https://vercel.com/blog/ai-sdk-7

[3] OpenRouter — Build Your Own Agent TUI cookbook. Scaffolds a full TypeScript agent harness with create-agent-tui. https://openrouter.ai/docs/cookbook/building-agents/create-agent-harness-tui

[4] PyRatatui — Rust-powered terminal UI for Python (PyPI, June 2026). Exposes the full Ratatui widget library via PyO3. https://github.com/pyratatui/pyratatui

[5] AI SDK 7 Terminal UI docs — @ai-sdk/tui reference for runAgentTUI. https://ai-sdk.dev/v7/docs/agents/terminal-ui

← Back to all posts