126 KiB
TUI
TUI Architecture Overview
!!! adr "Architecture Decision" The TUI architecture, framework selection (Textual >= 1.0), screen hierarchy, TuiMaterializer integration, and A2A event subscription model are defined in ADR-044: TUI Architecture and Framework.
The TUI (Terminal User Interface) is the second Presentation-layer surface for CleverAgents, built on Textual (>= 1.0). It provides real-time plan monitoring, multi-session management, interactive decision tree navigation, and rich conversation with actors — capabilities impractical in the stateless CLI.
The TUI communicates with the Application layer exclusively through A2A (ADR-026). It subscribes to A2A events for real-time updates and uses the existing Output Rendering Framework (ADR-021) via a TuiMaterializer that maps ElementHandle events to Textual widget operations — enabling all CLI command producers to render in the TUI without modification.
Key architectural principles:
- Direct-to-chat launch — no launcher screen; opens directly to the main chat interface
- Right-side collapsible sidebar — three states cycled by
shift+tab: hidden → visible → fullscreen - Multi-session tabs — independent sessions with separate personas, conversations, and A2A bindings
- Escape-cascading navigation —
escapealways moves toward the main screen from any state - Keyboard-first — every operation achievable without a mouse; mouse is supplementary
- Single UI codebase — the same Textual widget tree serves standalone TUI, Web (via Textual Web), and IDE plugin
MainScreen Layout — Sidebar Hidden
When the sidebar is hidden, the conversation takes the full terminal width:
┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │═══════════════════════════════════════════ ◆ ═══════════════════════════════════════════ │ │ Session 1 ┃ Session 2 ┃ Session 3 │ │ ┗━━━━━━━━━━━━┛ │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┃ You 14:01 │ │ ┃ Can you review the auth module in the │ │ ┃ API service? │ │ ┃ @project:api-service:src/auth/handler.py │ │ ┃ │ │ │ │ │ │ Actor 14:02 │ │ │ I'll review the authentication handler. │ │ │ Let me analyze the code structure and │ │ │ identify potential issues. │ │ │ │ │ │ 🔧 local/code-analysis ✔ ▶ │ │ │ │ │ │ Found 3 issues in the auth module: │ │ │ │ │ │ 1. **Missing rate limiting** │ │ │ 2. **JWT token** not validated │ │ │ 3. **Session cleanup** missing │ │ │ │ │ │ 🔧 local/file-read ✔ ▼ │ │ │ ┌─ src/auth/handler.py ─────────────┐ │ │ │ │ 45 │ def login(self, req): │ │ │ │ │ 46 │ creds = extract(req) │ │ │ │ │ 47 │ return auth(creds) │ │ │ │ └──────────────────────────────────┘ │ │ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ Agent connected │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ ┌─────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ ❯ What would you like to do? │ │ │ │ ▌@▐ refs ▌/▐ commands ▌!▐ shell │ │ │ └─────────────────────────────────────────────────────────────────────────────────────┘ │ │ feature-dev │ claude-4-sonnet │ think: high │ 2 projects │ $0.12 │ ├──────────────────────────────────────────────────────────────────────────────────────────────┤ │ F1 Help │ shift+tab Sidebar │ tab Persona │ ctrl+tab Preset │ ctrl+s Sessions │ ctrl+q Quit │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Key elements in this layout:
- Throbber (top edge): rainbow gradient bar, visible only when the actor is processing. Collapses to zero height when idle.
- Session Tabs (below throbber): tab bar with underline indicator on the active session. Hidden when only one session exists. Session labels show state icons:
⌛(actor working),❯(awaiting input), plain (idle). - Conversation (center): scrollable message stream with block cursor navigation.
- Prompt (bottom): PromptTextArea with mode-dependent symbol (❯ normal, $ shell, ☰ multi-line), overlays for @ references and / commands, and the PersonaBar.
- Footer: context-sensitive hotkey reference, always visible.
MainScreen Layout — Sidebar Visible
When the sidebar is visible (shift+tab from hidden), it docks to the right at 32-40 chars wide:
┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │═══════════════════════════════════════════ ◆ ═══════════════════════════════════════════ │ │ Session 1 ┃ Session 2 ┃ Session 3 │ │ ┗━━━━━━━━━━━━┛ │ ├─────────────────────────────────────────────────────────────┬────────────────────────────────┤ │ │ ▼ PLANS │ │ ┃ You 14:01 │ ┌────────────────────────────┐ │ │ ┃ Can you review the auth module in the │ │ ► fix-auth-bug │ │ │ ┃ API service? │ │ Phase: Execute ●●●○ │ │ │ ┃ @project:api-service:src/auth/handler.py │ │ Profile: trusted │ │ │ ┃ │ │ Actor: claude-4-sonnet │ │ │ │ │ │ Cost: $0.08 │ │ │ │ Actor 14:02 │ │ │ │ │ │ I'll review the authentication handler. │ │ ► refactor-models │ │ │ │ Let me analyze the code structure and │ │ Phase: Strategize ●○○○ │ │ │ │ identify potential issues. │ │ Profile: cautious │ │ │ │ │ │ Depth: 2 (3 subplans) │ │ │ │ 🔧 local/code-analysis ✔ ▶ │ │ Actor: gpt-4o │ │ │ │ │ │ ├─ users (execute) │ │ │ │ Found 3 issues in the auth module: │ │ ├─ orders (strategize) │ │ │ │ │ │ └─ auth (pending) │ │ │ │ 1. **Missing rate limiting** │ │ │ │ │ │ 2. **JWT token** not validated │ │ ◌ update-deps │ │ │ │ 3. **Session cleanup** missing │ │ Phase: Idle │ │ │ │ │ │ Profile: auto │ │ │ │ 🔧 local/file-read ✔ ▼ │ └────────────────────────────┘ │ │ │ ┌─ src/auth/handler.py ─────────────┐ │ │ │ │ │ 45 │ def login(self, req): │ │ ▼ PROJECTS │ │ │ │ 46 │ creds = extract(req) │ │ ┌────────────────────────────┐ │ │ │ │ 47 │ return auth(creds) │ │ │ ◆ cleveragents [2p] │ │ │ │ └───────────────────────────────────┘ │ │ ◆ api-service [1p] │ │ │ │ │ frontend-app [0p] │ │ │─────────────────────────────────────────────────────────────│ │ infra-terraform [0p] │ │ │ Agent connected │ └────────────────────────────┘ │ │─────────────────────────────────────────────────────────────│ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ │ ❯ What would you like to do? │ │ │ │ │ ▌@▐ refs ▌/▐ commands ▌!▐ shell │ │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ feature-dev │ claude-4-sonnet │ think: med │ 2 proj │ $0.12 │ │ ├─────────────────────────────────────────────────────────────┴────────────────────────────────┤ │ F1 Help │ shift+tab Sidebar │ tab Persona │ ctrl+tab Preset │ ctrl+s Sessions │ ctrl+q Quit │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Key elements in this layout:
- Throbber (top edge): rainbow gradient bar, visible only when the actor is processing. Collapses to zero height when idle.
- Session Tabs (below throbber): tab bar with underline indicator on the active session. Hidden when only one session exists. Session labels show state icons:
⌛(actor working),❯(awaiting input), plain (idle). - Conversation (center): scrollable message stream with block cursor navigation.
- Sidebar (right, 32-40 chars wide): Plans panel and Projects panel in collapsible containers.
- Prompt (bottom of left panel): same prompt components as hidden mode, constrained to the conversation column width.
- PersonaBar (below prompt): shows persona name, actor, preset, scope, and cost.
MainScreen Layout — Sidebar Fullscreen
Fullscreen mode (shift+tab from visible) covers the entire terminal for plan/project/persona management:
┌──────────────────────────────────────────────────────────────────────────────────────────────┐ │ PLANS & PROJECTS BROWSER │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ PLANS │ PROJECTS │ │ ┌───────────────────────────────────────────┐ │ ┌──────────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ [x] ► fix-auth-bug │ │ │ [x] cleveragents │ │ │ │ Phase: Execute ●●●○ │ │ │ Namespace: local/ │ │ │ │ State: in_progress │ │ │ Resources: 5 │ │ │ │ Profile: trusted │ │ │ Plans: 2 active │ │ │ │ Actor: anthropic/claude-4-sonnet │ │ │ Invariants: 3 │ │ │ │ Started: 2m ago Cost: $0.08 │ │ │ Validations: 2 │ │ │ │ Decisions: 6 (4 done, 2 pending) │ │ │ │ │ │ │ │ │ │ [ ] api-service │ │ │ │ [ ] refactor-models │ │ │ Namespace: local/ │ │ │ │ Phase: Strategize ●○○○ │ │ │ Resources: 3 │ │ │ │ State: in_progress │ │ │ Plans: 1 active │ │ │ │ Profile: cautious │ │ │ Invariants: 1 │ │ │ │ Actor: openai/gpt-4o │ │ │ Validations: 4 │ │ │ │ Started: 5m ago Cost: $0.03 │ │ │ │ │ │ │ Decisions: 3 (2 done, 1 active) │ │ │ [ ] frontend-app │ │ │ │ Subplans: 3 (depth 2) │ │ │ Namespace: local/ │ │ │ │ ├─ users Execute ●●●○ │ │ │ Resources: 2 │ │ │ │ ├─ orders Strategize ●○○○ │ │ │ Plans: 0 │ │ │ │ └─ auth Pending │ │ │ │ │ │ │ │ │ │ [ ] infra-terraform │ │ │ │ [ ] update-deps │ │ │ Namespace: local/ │ │ │ │ Phase: Idle │ │ │ Resources: 8 │ │ │ │ Profile: auto │ │ │ Plans: 0 │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────────┘ │ └──────────────────────────────────────────┘ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ PERSONA CYCLE LIST (tab order) │ │ ┌──────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ 1. feature-dev (claude-4-sonnet, 2 projects) │ │ │ │ 2. reviewer (gpt-4o, 1 project) │ │ │ │ 3. infra-admin (claude-4-opus, 1 project) │ │ │ │ [+] Add current selection as persona... │ │ │ └──────────────────────────────────────────────────────────────────────────────────────┘ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ Selected: 1 plan, 1 project │ │ space Select │ enter Details │ ctrl+p Create Persona │ / Search │ d Delete │ esc Back │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
In fullscreen mode:
- Selection mode:
spacetoggles selection on the highlighted plan or project. Selected items show[x], deselected show[ ]. - Persona creation:
ctrl+popens the PersonaEditorModal with the selected plans and projects pre-populated as the scope. - Detail inspection:
enteron a plan opens the PlanDetailModal;enteron a project opens the ProjectDetailModal. - Persona cycle list: The bottom panel shows the ordered list of personas that
tabcycles through. Users can reorder (ctrl+up/ctrl+down), remove (d), or add new personas from the current selection.
Sidebar State Transitions
| State | Layout | Input Focus | Content |
|---|---|---|---|
| Hidden | Sidebar display: none; conversation full width |
Prompt retains focus | No sidebar content visible |
| Visible | Sidebar docked right, 32-40 chars wide | Prompt retains focus; ctrl+b focuses sidebar |
Plans and Projects panels in collapsible containers |
| Fullscreen | Covers entire screen | Sidebar takes input focus | Extended details, selection mode, persona management |
State transitions:
Hidden ──shift+tab──► Visible ──shift+tab──► Fullscreen
▲ │
└──────────────── escape (×1-2) ───────────────┘
Persona System
!!! adr "Architecture Decision" The persona abstraction, argument preset cycling, scope behavior, and persona lifecycle are defined in ADR-045: TUI Persona System.
A persona is a TUI-only abstraction that bundles:
- Actor reference — a namespaced actor name (e.g.,
anthropic/claude-4-sonnet) - Base arguments — default arguments passed to the actor on every invocation
- Scoped projects — projects always included in the session's context
- Scoped plans — plans always included in the session's context
- Argument presets — named argument overrides cycled with
ctrl+tab - Display metadata — short name, accent color, description
Personas are stored as YAML files in ~/.config/cleveragents/personas/ and are strictly a Presentation-layer concept — they never appear in the domain model, A2A protocol, or database schema.
PersonaBar
The PersonaBar always shows the current state:
┌──────────────────────────────────────────────────────────────────────┐ │ ❯ What would you like to do? │ │ ▌@▐ refs ▌/▐ commands ▌!▐ shell │ └──────────────────────────────────────────────────────────────────────┘ feature-dev │ claude-4-sonnet │ think: high │ 2 projects │ $0.12 ─────────── ─ ─────────────── ─ ─────────── ─ ──────────────── ─ ───── persona actor preset scope cost
| PersonaBar Segment | Style | Updates When |
|---|---|---|
| Persona name | $text-primary on $primary 10% bg |
tab cycle |
| Actor name | $text-secondary |
tab cycle |
| Preset label | $text-warning (non-default) / $text-muted (default) |
ctrl+tab cycle |
| Scope indicator | $text-muted |
Persona change or /scope:add/remove |
| Session cost | $secondary 70%, right-aligned |
After each actor response |
Persona Cycling
tab cycles through personas in the configured cycle list. ctrl+tab cycles through the current persona's argument presets:
tab: persona_1 → persona_2 → persona_3 → persona_1 → ...
ctrl+tab: default → think: high → think: max → quick → default → ...
When cycling, the PersonaBar updates immediately. The actor binding for the session is updated — subsequent prompts use the new persona's actor and scope.
First-Run Experience
On first launch (no personas configured), a centered overlay guides actor selection:
┌──────────────────────────────────────────────────────────────────────┐ │ Welcome to CleverAgents │ │ │ │ Select an actor to get started: │ │ │ │ ❯ anthropic/claude-4-sonnet (recommended) │ │ anthropic/claude-4-opus │ │ openai/gpt-4o │ │ openai/o3 │ │ google/gemini-2 │ │ / to search... │ │ │ │ A default persona will be created with this actor. │ │ You can add more actors and personas later. │ │ │ │──────────────────────────────────────────────────────────────────────│ │ enter Select │ j/k Navigate │ / Search │ └──────────────────────────────────────────────────────────────────────┘
Selection creates a "default" persona with the chosen actor and auto-generated argument presets (thinking effort levels are auto-detected from the actor's argument schema). Subsequent launches restore the last active persona.
Reference and Command System
!!! adr "Architecture Decision" The @ reference notation grammar, fuzzy search algorithm, / command system, ! shell mode, and ACMS integration are defined in ADR-046: TUI Reference and Command System.
The TUI prompt supports three input modes, each activated by a distinct prefix:
| First Character | Mode | Prompt Symbol | Sent To | Overlay |
|---|---|---|---|---|
| (any other) | Normal | ❯ |
Actor via A2A | — |
@ (inline) |
Normal + Reference | ❯ |
Actor (with CRP directives) | ReferencePickerOverlay |
/ |
Command | / |
TUI command processor | SlashCommandOverlay |
! |
Shell | $ |
Host OS subprocess | — |
Reference Picker (@)
Typing @ anywhere in a normal-mode prompt activates the Reference Picker — a fuzzy-search overlay for projects, plans, and resources:
┌─ Reference Picker ───────────────────────────────────────────────────┐ │ @hand │ │ ──────────────────────────────────────────────────────────────────── │ │ PROJECT api-service:src/auth/handler.py │ │ local/api-service • Python • 245 lines │ │ │ │ PROJECT cleveragents:git_dir/src/cli/commands/handler.py │ │ local/cleveragents • Python • 189 lines │ │ │ │ PROJECT api-service:src/auth/middleware/handler_base.py │ │ local/api-service • Python • 67 lines │ │ │ │ PLAN fix-auth-handler (01HXM8C2...) │ │ Phase: Execute • Actor: claude-4-sonnet │ │ │ │ ──────────────────────────────────────────────────────────────────── │ │ enter Select │ tab Tree │ ctrl+p Projects │ ctrl+l Plans │ └──────────────────────────────────────────────────────────────────────┘
Resolved references use a canonical notation: @project:local/api-service:src/auth/handler.py. Fuzzy input like @handler.py auto-expands to the canonical form on selection.
Resolved @ references are translated into CRP (Context Request Protocol) directives before the prompt reaches the actor — they semantically direct the ACMS to prioritize referenced resources in context assembly.
Reference Picker — Tree Browser Mode
Pressing tab in the Reference Picker switches to tree browser mode for hierarchical navigation:
┌─ Reference Picker (Tree) ────────────────────────────────────────────┐ │ │ │ ▼ Projects │ │ ▼ local/cleveragents │ │ ▼ git_dir (git-checkout) │ │ ▸ src/ │ │ ▸ tests/ │ │ ▸ docs/ │ │ ─ pyproject.toml │ │ ─ README.md │ │ ▸ database (sqlite-db) │ │ ▸ local/api-service │ │ ▸ local/frontend-app │ │ ▼ Plans │ │ ▸ fix-auth-bug (01HXM8C2) │ │ ▸ refactor-models (01HXM9D3) │ │ │ │ enter Select │ tab Search │ space Expand │ / Filter │ └──────────────────────────────────────────────────────────────────────┘
Slash Command Overlay (/)
Typing / as the first character activates command mode. Commands are TUI operations executed locally — they are not sent to the actor.
Slash Command Overlay
┌─ Commands ─────────────────────────────────────────────────────────────┐ │ /se │ │ ───────────────────────────────────────────────────────────────────────│ │ /session:create Create a new session tab │ │ /session:list Show all sessions │ │ /session:show Show session details │ │ /session:switch Switch to session by ID │ │ /session:close Close current session │ │ /session:delete Delete a saved session │ │ /session:rename Rename current session │ │ /session:export Export session to file │ │ /session:import Import session from file │ │ /settings Open settings screen │ │ │ │ enter Execute │ tab Complete │ escape Dismiss │ └────────────────────────────────────────────────────────────────────────┘
TUI slash commands mirror CLI command patterns where applicable. The CLI uses agents <noun> <verb>; the TUI uses /<noun>:<verb>. Commands that exist in both CLI and TUI use the same verb names. TUI-only commands (persona, scope, TUI utilities) have no CLI equivalent.
Complete Command Reference
Session Commands — mirrors CLI agents session <verb>
| Command | Arguments | Description |
|---|---|---|
/session:create |
[--persona <name>] |
Create a new session tab |
/session:list |
— | Display all sessions |
/session:show <id> |
<id> |
Show session details |
/session:switch <id> |
<id> |
Switch to session by ID or tab index |
/session:close |
[--force] |
Close the current session tab |
/session:delete <id> |
<id>, [--yes/-y] |
Delete a saved session |
/session:rename <name> |
<name> |
Rename the current session |
/session:export [path] |
[path] |
Export session to JSON |
/session:import <path> |
<path> |
Import session from JSON |
Persona Commands — TUI-only
| Command | Arguments | Description |
|---|---|---|
/persona:list |
— | Display all personas |
/persona:set <name> |
<name> |
Switch to persona |
/persona:create |
— | Open PersonaEditorModal |
/persona:edit [name] |
[name] |
Edit persona |
/persona:delete <name> |
<name> |
Delete persona |
/persona:export <name> |
<name> |
Export persona YAML |
/persona:import <path> |
<path> |
Import persona YAML |
Scope Commands — TUI-only
| Command | Arguments | Description |
|---|---|---|
/scope:add <ref> |
<ref> |
Add project/plan to session scope |
/scope:remove <ref> |
<ref> |
Remove from session scope |
/scope:clear |
— | Clear session-level scope additions |
/scope:show |
— | Show effective scope |
Plan Commands — mirrors CLI agents plan <verb>
| Command | Arguments | Description |
|---|---|---|
/plan:use <action> |
<action>, [projects...], [--arg/-a <key=value>] |
Start a new plan |
/plan:list |
[--phase] [--state] [--project] [--action] |
List plans |
/plan:status [id] |
[id] |
Show plan status |
/plan:tree <id> |
<id>, [--show-superseded] [--depth] |
Show decision tree |
/plan:execute [id] |
[id] |
Execute a plan |
/plan:apply [id] |
[id] |
Apply a completed plan |
/plan:cancel <id> |
<id>, [--reason/-r] |
Cancel a plan |
/plan:diff <id> |
<id>, [--correction] |
Show plan diff |
/plan:correct <did> |
<did>, --mode, --guidance |
Correct a decision |
/plan:resume <id> |
<id>, [--dry-run] |
Resume a plan |
/plan:revert <id> |
<id>, [--to-phase] |
Revert a plan |
/plan:rollback <id> <cp> |
both required | Rollback to checkpoint |
/plan:explain <did> |
<did>, [--show-context] |
Explain a decision |
/plan:errors <id> |
<id> |
Show plan errors |
/plan:artifacts <id> |
<id> |
Show plan artifacts |
/plan:inspect [id] |
[id] |
Open PlanDetailModal |
Project Commands — mirrors CLI agents project <verb>
| Command | Arguments | Description |
|---|---|---|
/project:list |
[--namespace/-n] |
List all projects |
/project:create <name> |
<name>, [--description/-d] [--resource/-r] |
Create project |
/project:show <name> |
<name> |
Show project details |
/project:delete <name> |
<name>, [--yes/-y] |
Delete project |
/project:inspect <name> |
<name> |
Open ProjectDetailModal |
/project:context:show <name> |
<name> |
Show project context config |
Actor, Resource, Config, Tool, Skill, Invariant, Profile Commands — mirrors CLI
| Command | Description |
|---|---|
/actor:list, /actor:show, /actor:set-default |
Actor management |
/resource:list, /resource:show, /resource:tree, /resource:inspect |
Resource management |
/config:list, /config:get, /config:set |
Configuration management |
/tool:list, /tool:show |
Tool management |
/skill:list, /skill:show |
Skill management |
/invariant:list, /invariant:add, /invariant:remove |
Invariant management |
/profile:list, /profile:show |
Automation profile management |
/context:inspect, /context:set, /context:simulate |
Context management (inspect state, set policy, simulate assembly) |
TUI Utility Commands — TUI-only
| Command | Arguments | Description |
|---|---|---|
/clear |
— | Clear conversation display |
/theme [name] |
[name] |
Switch color theme |
/settings |
— | Open SettingsScreen |
/help [command] |
[command] |
Show help |
/about |
— | Show version and system info |
/debug |
— | Toggle debug mode |
Shell Mode (!)
Shell mode provides direct OS command execution from the TUI prompt. Typing ! as the first character changes the prompt symbol to $ and enables shell syntax highlighting, file/directory tab completion, and separate shell history.
┌────────────────────────────────────────────────────────────────────┐ │ $ git status │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ On branch main │ │ │ │ Your branch is up to date with 'origin/main'. │ │ │ │ │ │ │ │ Changes not staged for commit: │ │ │ │ modified: src/auth/handler.py │ │ │ │ modified: src/auth/middleware.py │ │ │ └────────────────────────────────────────────────────────────────┘ │ │ (exit code: 0) │ └────────────────────────────────────────────────────────────────────┘
Shell results appear as ShellResult blocks in the conversation stream with a $primary left border and $foreground 4% background. Long-running commands stream output in real-time; ctrl+c sends SIGINT.
Plan Detail Modal
The PlanDetailModal shows a plan's decision tree alongside decision details:
┌───────────────────────────────────── Plan: fix-auth-bug ─────────────────────────────────────┐ │ ID: 01HXM8C2ABCD Phase: Execute State: in_progress │ │ Profile: trusted Actor: claude-4-sonnet Started: 2m ago Cost: $0.08 │ │ Projects: local/api-service │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ DECISION TREE │ DECISION DETAIL │ │ ┌──────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ │ │ │ │ │ Decision: D3 │ │ │ │ ▼ D1 strategy_choice ✔ │ │ │ Type: implementation_choice │ │ │ │ ├─ D2 invariant_enforced ✔ │ │ │ Phase: Execute │ │ │ │ ├─▶D3 implementation_choice ⚡ │ │ │ │ │ │ │ │ ├─ D4 tool_invocation ✔ │ │ │ Question: │ │ │ │ │ ├─ D5 tool_invocation ⌛ │ │ │ How should rate limiting be │ │ │ │ │ └─ D6 tool_invocation ⌛ │ │ │ implemented on the login endpoint? │ │ │ │ ├─ D7 resource_selection ⌛ │ │ │ │ │ │ │ └─ D8 subplan_spawn ⌛ │ │ │ Chosen: Token bucket algorithm │ │ │ │ │ │ │ with Redis backing store │ │ │ │ / Search decisions... │ │ │ │ │ │ │ │ │ │ Alternatives: │ │ │ └──────────────────────────────────────┘ │ │ • Sliding window counter │ │ │ │ │ • Fixed window counter │ │ │ Legend: │ │ • IP-based blocking │ │ │ ✔ completed ⚡ active │ │ │ │ │ ⌛ pending ✗ failed │ │ Confidence: 0.85 │ │ │ ⊘ superseded │ │ Rationale: Token bucket provides │ │ │ │ │ fine-grained control with burst │ │ │ ─────────────────────────────────────── │ │ allowance and Redis enables │ │ │ Phase: ●●●○ │ │ distributed rate limiting... │ │ │ Decisions: 8 total │ │ │ │ │ 4 completed, 1 active, 3 pending │ │ Dependencies: │ │ │ │ │ ← D1 (strategy_choice) │ │ │ │ │ → D4, D5, D6 (tool_invocations) │ │ │ │ │ │ │ │ │ │ Context Snapshot: 2.3 KB │ │ │ │ └────────────────────────────────────────┘ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ j/k Nav │ enter Inspect │ c Correct │ d Diff │ h History │ t Toggle │ / Search │ │ s Superseded │ x Expand all │ z Collapse all │ escape Close │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Decision Tree Interaction
| Key | Action |
|---|---|
j / down |
Move to next decision |
k / up |
Move to previous decision |
enter |
Select decision → show detail in right panel |
space |
Expand/collapse subtree |
c |
Correct selected decision (opens correction dialog) |
d |
Show diff for selected decision |
h |
Show decision history / superseded chain |
t |
Toggle detail panel visibility |
x |
Expand all nodes |
z |
Collapse all nodes |
s |
Show superseded decisions (hidden by default) |
/ |
Search decisions by type, content, or ID |
escape |
Close modal |
Project Detail Modal
The ProjectDetailModal shows a project's resource DAG, invariants, and running plans:
┌───────────────────────────────── Project: local/api-service ─────────────────────────────────┐ │ Namespace: local/ Resources: 3 Plans: 1 active │ │ Invariants: 1 Validations: 4 Automation: trusted │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ RESOURCES │ DETAIL │ │ ┌──────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ │ │ │ │ │ Resource: api-repo │ │ │ │ ▼ api-repo (git-checkout) │ │ │ Type: git-checkout │ │ │ │ ├─ src/ (fs-directory) │ │ │ ID: 01HXYZ... │ │ │ │ │ ├─ auth/ │ │ │ │ │ │ │ │ │ ├─ handler.py │ │ │ URL: git@github.com:org/api.git │ │ │ │ │ │ ├─ middleware.py │ │ │ Branch: main │ │ │ │ │ │ └─ limiter.py │ │ │ Commit: a1b2c3d (2h ago) │ │ │ │ │ ├─ models/ │ │ │ │ │ │ │ │ └─ routes/ │ │ │ Sandbox: git_worktree │ │ │ │ ├─ tests/ (fs-directory) │ │ │ Checkpoint: enabled │ │ │ │ └─ pyproject.toml │ │ │ │ │ │ │ ─ api-db (container-instance) │ │ │ Children: 2 (src/, tests/) │ │ │ │ ─ api-cache (container-instance) │ │ │ Validations: 2 attached │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────┘ │ └────────────────────────────────────────┘ │ │ │ │ │ INVARIANTS │ RUNNING PLANS │ │ ┌──────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │ │ │ 1. All API endpoints must have │ │ │ fix-auth-bug Execute ●●●○ $0.08 │ │ │ │ rate limiting (project-level) │ │ │ │ │ │ └──────────────────────────────────────┘ │ └────────────────────────────────────────┘ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ j/k Nav │ enter Drill │ i Invariants │ v Validations │ p Plans │ / Search │ esc Close │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Persona Editor Modal
The PersonaEditorModal is used for creating and editing personas:
┌─────────────────────────────────────── Create Persona ───────────────────────────────────────┐ │ │ │ Name: ┌──────────────────────────────────────┐ │ │ │ feature-dev │ │ │ └──────────────────────────────────────┘ │ │ │ │ Description: ┌────────────────────────────────────────────────────────┐ │ │ │ Feature development with Claude on main projects │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ Actor: ┌──────────────────────────────────────────┐ │ │ │ ❯ anthropic/claude-4-sonnet │ ▼ │ │ └──────────────────────────────────────────┘ │ │ │ │ Color: ● $primary ○ $secondary ○ $success ○ $warning ○ $error │ │ │ │ ── Base Arguments (from actor schema) ────────────────────────────────────── │ │ thinking_effort: ┌──────────┐ temperature: ┌──────────┐ │ │ │ medium │ ▼ │ 0.7 │ │ │ └──────────┘ └──────────┘ │ │ max_tokens: ┌──────────┐ │ │ │ 16384 │ │ │ └──────────┘ │ │ │ │ ── Scoped Projects ───────────────────────────────────────────────────────── │ │ [x] local/cleveragents [x] local/api-service │ │ [ ] local/frontend-app [ ] local/infra-terraform │ │ │ │ ── Scoped Plans ──────────────────────────────────────────────────────────── │ │ [ ] fix-auth-bug (01HXM8C2) [ ] refactor-models (01HXM9D3) │ │ │ │ ── Argument Presets (ctrl+tab cycling) ───────────────────────────────────── │ │ ┌────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ 1. default │ (base arguments) │ │ │ │ 2. think: high │ thinking_effort=high │ │ │ │ 3. think: max │ thinking_effort=max, temperature=0.3 │ │ │ │ 4. quick │ thinking_effort=low, max_tokens=4096 │ │ │ │ [+] Add preset │ │ │ └────────────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ Cycle Order: ┌──────┐ (0 = not in tab cycle) │ │ │ 1 │ │ │ └──────┘ │ │ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ tab Next field │ enter Save │ escape Cancel │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
Permissions Screen
When a tool requests permission to modify resources, the PermissionsScreen shows the requested changes with a diff view:
┌───────────────────────────────────── Permission Request ─────────────────────────────────────┐ │ │ │ local/file-write wants to modify 3 files in local/api-service: │ │ │ │ ┌─────────────────────────────┐ ┌──────────────────────────────────────────────────────┐ │ │ │Files (3 changes): │ │src/auth/handler.py │ │ │ │ │ │──────────────────────────────────────────────────────│ │ │ │❯ src/auth/handler.py [M] │ │ @@ -45,6 +45,12 @@ │ │ │ │ src/auth/middleware.py [M] │ │ def login(self, request): │ │ │ │ src/auth/limiter.py [A] │ │ + @rate_limit(max_calls=5, period=60) │ │ │ │ │ │ credentials = self.extract(request) │ │ │ │ │ │ - return self.authenticate(credentials) │ │ │ │ │ │ + result = self.authenticate(credentials) │ │ │ │ │ │ + self.log_attempt(request, result) │ │ │ │ │ │ + return result │ │ │ │ │ │ │ │ │ │ │ │ @@ -78,3 +84,8 @@ │ │ │ │ │ │ def logout(self, request): │ │ │ │ │ │ + self.cleanup_session(request) │ │ │ │ │ │ return Response(status=200) │ │ │ │ │ │ │ │ │ └─────────────────────────────┘ └──────────────────────────────────────────────────────┘ │ │──────────────────────────────────────────────────────────────────────────────────────────────│ │ [M] Modified [A] Added [D] Deleted │ │ a Allow │ A Allow Always │ r Reject │ R Reject Always │ j/k Nav │ d Diff │ esc │ └──────────────────────────────────────────────────────────────────────────────────────────────┘
The diff view supports three display modes, toggled with d:
| Mode | Behavior |
|---|---|
| Unified | Standard unified diff with +/- lines (default) |
| Side-by-side | Two-column view with old content left, new content right |
| Context | Shows only changed lines with surrounding context (3 lines default) |
Additional UI Components
Throbber
A rainbow gradient animated bar spans the full width at the top, visible only when the actor is processing:
┌───────────────────────────────────────────────────────────────────────────┐ │ ════════════════════════════════════ ◆ ═══════════════════════════════════│ │ (actor processing...) │ │ │ │ (idle — throbber collapses to zero height) │ └───────────────────────────────────────────────────────────────────────────┘
The gradient cycles through 12 colors at 15fps:
#881177 → #aa3355 → #cc6666 → #ee9944 → #eedd00 → #99dd55 → #44dd88 → #22ccbb → #00bbcc → #0099cc → #3366bb → #663399
Session Tabs
┌────────────────────────────────────────────────────────────────────────────┐ │ Session 1 ┃ Session 2 ┃ Session 3 ┃ ⌛ Session 4 │ │ ┗━━━━━━━━━━━┛ │ │ │ │ ctrl+[ Previous │ ctrl+] Next │ ctrl+n New │ ctrl+w Close │ │ ctrl+s Sessions screen │ 1-9 Jump to tab │ │ │ │ Indicators: ⌛ actor working │ ❯ awaiting input │ (plain) idle │ │ ━━━ active tab underline │ └────────────────────────────────────────────────────────────────────────────┘
Notifications (Flash)
Notifications appear as a single-line Flash bar above the prompt, auto-dismissing after a configurable timeout (default 5s):
┌────────────────────────────────────────┐ │ ✔ Plan fix-auth-bug completed │ │ (auto-dismiss in 5s) │ │ │ │ ✗ Plan refactor-models failed │ └────────────────────────────────────────┘
Help Panel (F1)
F1 opens a context-sensitive help overlay showing available hotkeys for the current screen/widget:
┌─ Help: Main Screen ─────────────────────────────────────────────────────┐ │ │ │ Navigation │ │ shift+tab Cycle sidebar: hidden → visible → fullscreen │ │ tab Cycle to next persona │ │ ctrl+tab Cycle to next argument preset │ │ ctrl+[/] Switch session tab │ │ escape Close overlay / sidebar / return to prompt │ │ │ │ Prompt │ └─────────────────────────────────────────────────────────────────────────┘
Escape Cascade
The escape key always cascades toward the main screen:
┌───────────────────────────────────────────────────────────────────────┐ │ Modal (PlanDetail, ProjectDetail, PersonaEditor, ...) │ │ ↓ escape │ │ Overlay (ReferencePicker, SlashCommand) │ │ ↓ escape │ │ Sidebar Fullscreen │ │ ↓ escape │ │ Sidebar Visible │ └───────────────────────────────────────────────────────────────────────┘
Multiple escape presses from any state eventually reach the main chat prompt. No modal or screen may trap the user without an escape path.
Conversation Stream
The conversation displays a chronological stream of typed message blocks:
| Block Type | Visual Treatment | Source |
|---|---|---|
Welcome |
ASCII art + instructions in $text-success |
App startup (first message) |
UserInput |
Left border $secondary, 15% background tint, Markdown |
User prompt submission |
ActorResponse |
Streaming Markdown with syntax-highlighted code fences | session.message events |
ActorThought |
$primary-muted 20% bg, max 10 lines (expandable), italic |
Actor reasoning |
ToolCall |
Expandable: icon + status pill header, collapsible content | tool.invoked / tool.completed |
PlanProgress |
Grid layout with status icons per step | Plan phase changes |
DiffView |
Unified or side-by-side diff, syntax highlighting | Tool results with diffs |
TerminalEmbed |
Bordered terminal, $primary 50% border, green/red tint |
Shell or tool terminal output |
ShellResult |
Left border $primary, 4% foreground bg |
User shell command (!) output |
Note |
Semantic: info ($primary), warning ($warning), error ($error) |
System notifications |
The conversation uses a 2-column grid: a 1-character cursor column (left, navigable via alt+up/alt+down) and the content stream (right). The block cursor provides keyboard-driven message navigation — enter or space expands/collapses a block.
Theme and Styling
The TUI uses Textual's CSS (TCSS) system with semantic color tokens:
| Token | Usage |
|---|---|
$primary |
Active elements, sidebar borders, plan status |
$secondary |
User input highlights, prompt accent |
$success |
Completed states, passed validations |
$error |
Failed states, error notifications |
$warning |
Caution states, cost warnings |
$text, $text-muted |
Primary and secondary text |
Default theme is Dracula. All 17+ Textual built-in themes are supported and switchable via /theme <name>. The TCSS file hierarchy follows the screen/widget module structure.
Block Cursor and Context Menu
The conversation uses a 2-column grid: a 1-character-wide cursor column on the left and the content stream on the right. The block cursor (a ▌ gutter indicator) is navigated with alt+up/alt+down and provides keyboard-driven interaction with message blocks.
Cursor Navigation
| Key | Action |
|---|---|
alt+up |
Move cursor to previous block |
alt+down |
Move cursor to next block |
space |
Expand or collapse the focused block (if expandable) |
enter |
Open context menu for the focused block |
escape |
Clear cursor selection, return focus to prompt |
| Click on block | Position cursor on clicked block |
Blocks that implement the ExpandProtocol (ToolCall, ActorThought, DiffView) can be toggled with space. The ▶ indicator (collapsed) changes to ▼ (expanded) on toggle.
Context Menu
Pressing enter with the block cursor on any conversation block opens a floating context menu:
┌─ Block Actions ──────────────────────────────────────────────────────┐ │ │ │ c Copy to clipboard │ │ p Copy to prompt │ │ e Export as Markdown │ │ v Export as SVG (opens browser) │ │ m Maximize / restore block │ │ r Retry (ActorResponse only) │ │ d Show raw data │ │ │ │──────────────────────────────────────────────────────────────────────│ │ escape Dismiss │ └──────────────────────────────────────────────────────────────────────┘
| Action | Key | Applies To | Description |
|---|---|---|---|
| Copy to clipboard | c |
All blocks | Copies the block's text content (Markdown source for ActorResponse, plain text for others) to the system clipboard |
| Copy to prompt | p |
All blocks | Inserts the block's text content into the prompt TextArea at the cursor position |
| Export as Markdown | e |
All blocks | Exports the block's content to a .md file in the current working directory |
| Export as SVG | v |
All blocks | Renders the block to an SVG file and opens it in the default browser |
| Maximize / restore | m |
ActorThought, ToolCall, DiffView | Toggles between collapsed/default height and full-screen height for the block |
| Retry | r |
ActorResponse | Re-sends the preceding user prompt to the actor, replacing this response |
| Show raw data | d |
All blocks | Shows the raw A2A message data for debugging |
Conversation Block Details
Tool Call States
Tool calls progress through pending → completed/failed states. The expand behavior is controlled by the tools.expand setting:
┌────────────────────────────────────────────────────────────────────────┐ │ Pending: │ │ 🔧 local/code-analysis ⌛ ▶ │ │ │ │ Completed (collapsed): │ │ 🔧 local/code-analysis ✔ ▶ │ │ │ │ Completed (expanded): │ │ 🔧 local/code-analysis ✔ ▼ │ │ ┌─ src/auth/handler.py ────────────────────────────────────────────┐ │ │ │ 45 │ def login(self, req): │ │ │ │ 46 │ creds = extract(req) │ │ │ │ 47 │ return auth(creds) │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ Failed: │ │ 🔧 local/code-analysis ✗ failed ▶ │ └────────────────────────────────────────────────────────────────────────┘
tools.expand Setting |
Behavior |
|---|---|
never |
All tool calls start collapsed |
always |
All tool calls auto-expand on completion |
success |
Only successful tool calls auto-expand |
fail (default) |
Only failed tool calls auto-expand |
both |
Both successful and failed auto-expand |
Tool calls with kind: "read" (e.g., file reads, directory listings) never auto-expand regardless of setting, as they produce verbose output that would dominate the conversation.
Actor Thought Block
Actor thoughts (reasoning traces) display in a muted container with configurable visibility:
┌──────────────────────────────────────────────────────────────────────┐ │ Collapsed (default, max 10 lines): │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ I need to analyze the authentication flow. The handler │ │ │ │ uses a custom JWT implementation that may have... │ │ │ │ (8 more lines — space to expand) │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ Expanded (space or m to maximize): │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ I need to analyze the authentication flow. The handler │ │ │ │ uses a custom JWT implementation that may have timing │ │ │ │ vulnerabilities. Let me check the token validation... │ │ │ │ ...(full content shown)... │ │ │ └──────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────┘
| Property | Value |
|---|---|
| Default height | Max 10 lines (CSS max-height: 10) |
| Expanded height | Full content (CSS max-height: 100vh) |
| Background | $primary-muted 20% |
| Text style | Italic |
| Toggle visibility | agent.thoughts setting or ctrl+shift+t |
| Expand/collapse | space with block cursor, or m in context menu |
Permission Question Widget
When an actor requests permission for a single-file operation, a Question widget replaces the prompt area temporarily. For multi-file operations (diffs), the full PermissionsScreen is pushed instead.
┌──────────────────────────────────────────────────────────────────────┐ │ Permission Required │ │ │ │ The actor wants to write to: │ │ src/auth/handler.py │ │ │ │ ❯ a Allow once │ │ A Allow always (this session) │ │ r Reject once │ │ R Reject always (this session) │ │ │ │ For multi-file diffs, press v to open full PermissionsScreen │ └──────────────────────────────────────────────────────────────────────┘
| Key | Action |
|---|---|
a |
Allow this operation once |
A |
Allow all operations of this type for the remainder of the session |
r |
Reject this operation once |
R |
Reject all operations of this type for the remainder of the session |
v |
Open full PermissionsScreen with diff view (for file writes) |
up / down |
Navigate between options |
enter |
Select the highlighted option |
The Question widget uses a blinking caret animation on the selected option to draw attention. Multiple permission requests are queued and presented sequentially — the next question appears immediately after the current one is answered.
Prompt Architecture Detail
History Navigation
The prompt maintains two independent JSONL history files per project:
| History | File Location | Trigger |
|---|---|---|
| Prompt history | ~/.local/state/cleveragents/history/<project-hash>/prompt.jsonl |
up/down arrows when cursor is at first/last row of the TextArea |
| Shell history | ~/.local/state/cleveragents/history/<project-hash>/shell.jsonl |
up/down arrows in shell mode (!/$ prompt) |
History navigation rules:
upat row 0, column 0: replaces TextArea content with the previous history entrydownat the last row: replaces TextArea content with the next history entry (or clears to the original input if at the end)- History entries are stored as-is (including multi-line prompts)
- History files are capped at 10,000 entries; oldest entries are pruned on write
- Each prompt mode (normal, shell) has independent history and independent cursor position within that history
Key Passthrough
When the conversation Window widget has focus (e.g., during block cursor navigation), any printable character typed is forwarded to the prompt TextArea. This ensures the user can always start typing without explicitly focusing the prompt — the focus shifts automatically on the first printable keypress.
Multiline Detection
The prompt auto-detects multiline input and switches the prompt symbol to ☰:
| Condition | Result |
|---|---|
Input contains \n (newline character) |
Switch to multiline mode |
Input contains triple backticks (```) |
Switch to multiline mode |
shift+enter or ctrl+j pressed |
Insert newline, switch to multiline |
| All newlines removed | Revert to single-line mode |
In multiline mode, enter inserts a newline; ctrl+enter submits. In single-line mode, enter submits.
Shell Danger Detection
When shell mode is active (!/$ prefix), the prompt performs heuristic analysis of the command to detect potentially destructive operations. Dangerous commands are highlighted with $error styling and a warning indicator appears below the prompt:
| Pattern | Risk Level | Example |
|---|---|---|
rm -rf / rm -r |
High | rm -rf / |
chmod 777 |
Medium | chmod 777 /var/www |
> /dev/sda / dd if= |
High | dd if=/dev/zero of=/dev/sda |
:(){ :|:& };: (fork bomb) |
High | Fork bomb patterns |
mkfs / fdisk / parted |
High | Disk formatting tools |
kill -9 / killall |
Medium | Process termination |
sudo / su |
Low | Privilege escalation (warning only) |
Danger detection is controlled by the shell.warn_dangerous setting (default: true). The detection is advisory only — it never prevents command execution. The warning text reads: ⚠ Potentially destructive command detected.
Notification System
The TUI uses a multi-tier notification system to keep users informed across different attention contexts:
Flash Messages (In-App)
Flash messages are single-line notifications displayed between the conversation and the prompt. They auto-dismiss after the configured ui.flash_duration (default: 5s).
| Style | CSS Class | Use Case | Example |
|---|---|---|---|
| Default | -default |
Informational | Press ctrl+f to focus terminal |
| Success | -success |
Positive outcomes | ✔ Plan fix-auth-bug completed ($0.08) |
| Warning | -warning |
Caution | ⚠ Plan approaching cost limit ($4.50/$5.00) |
| Error | -error |
Failures | ✗ Plan refactor-models failed: validation error |
Desktop Notifications
Desktop notifications alert the user when the TUI is not in the foreground. They are delivered via the OS notification system (using notifypy on Linux/macOS, native win10toast on Windows).
| Setting Value | Behavior |
|---|---|
never |
Desktop notifications are disabled entirely |
on-blur (default) |
Notifications are sent only when the terminal window does not have focus |
always |
Notifications are sent regardless of focus state |
Events that trigger desktop notifications:
- Actor turn completed (response finished)
- Permission requested (actor wants to perform a gated action)
- Plan phase transition (idle → strategize, execute → completed, etc.)
- Plan error or failure
- Session cost threshold exceeded (configurable)
Each notification includes: the CleverAgents icon, a title (event type), and a body (session name + brief detail). Low-severity events (informational, routine phase transitions) are suppressed by default (notifications.hide_low_severity: true).
Terminal Title Management
The TUI updates the terminal window title via VT escape sequences (\033]0;...\007) to provide at-a-glance status:
| State | Title Format | Example |
|---|---|---|
| Idle | ◆ CleverAgents — <session_name> |
◆ CleverAgents — Session 2 |
| Actor working | ⌛ CleverAgents — <session_name> |
⌛ CleverAgents — Session 2 |
| Awaiting input | Alternates between ◆ and 👉 at 0.5s interval |
Blinking title draws attention |
| Permission requested | Alternates between ◆ and 🔐 at 0.5s interval |
Urgent attention for permission |
Title blinking is controlled by the notifications.blink_title setting (default: true). When disabled, the title updates but does not blink.
Sound Effects
Optional sound effects play on significant events (controlled by notifications.enable_sounds setting, default: true):
turn_complete.wav— Actor finished a responsepermission_required.wav— Permission requestedplan_complete.wav— Plan reached terminal stateerror.wav— Error or failure occurred
Sound files are bundled in src/cleveragents/tui/data/sounds/. The notification system gracefully degrades if audio playback is unavailable (e.g., headless server).
Clipboard Operations
The TUI provides clipboard integration for copying content from the conversation and other widgets:
| Operation | Trigger | Behavior |
|---|---|---|
| Auto-copy on selection | Mouse text selection | When ui.auto_copy is true (default), any text selected with the mouse is automatically copied to the system clipboard |
| Block copy | c in block context menu |
Copies the focused block's text content to the clipboard |
| Block copy to prompt | p in block context menu |
Inserts the focused block's text into the prompt TextArea at the cursor |
| Manual copy | OS-native selection + copy (e.g., ctrl+shift+c in most terminals) |
Standard terminal copy behavior |
Clipboard access uses pyperclip when available, with fallback to OSC 52 escape sequences for terminal-native clipboard access (works over SSH and in tmux/screen). If neither is available, copy operations display a flash message with the content for manual copying.
Session Persistence and Resume
TUI sessions are persisted to enable resumption across TUI restarts:
Storage
Sessions are stored in an SQLite database at ~/.local/state/cleveragents/tui.db:
| Column | Type | Description |
|---|---|---|
id |
TEXT (PK) | UUID matching the domain Session entity ID |
persona_name |
TEXT | Name of the persona active when the session was last used |
actor_identity |
TEXT | Namespaced actor identity (e.g., anthropic/claude-4-sonnet) |
title |
TEXT | User-assigned or auto-generated session title |
prompt_count |
INTEGER | Number of user prompts sent in this session |
total_cost |
REAL | Cumulative cost in USD for this session |
created_at |
TEXT (ISO 8601) | Session creation timestamp |
last_used |
TEXT (ISO 8601) | Last interaction timestamp |
project_path |
TEXT | Working directory at session creation (nullable) |
meta_json |
TEXT | JSON blob for extensible metadata (preset, sidebar state, scroll position) |
Resume Workflow
- User presses
ctrl+r(from SessionsScreen) or uses/session:import - The TUI reads the session record from SQLite
- A new A2A Task is initiated with the stored session ID — the Application layer recognizes this as a resume request and loads the existing Session entity
- The actor receives the session's conversation history via A2A's multi-turn interaction pattern
- The conversation stream is populated with historical messages (rendered from stored A2A message data)
- The persona is restored to the one stored in the session record
Not all actors support session resume — actors that do not support it will start a fresh conversation. A flash warning is shown: ⚠ This actor does not support session resume. Starting fresh conversation.
Sessions Screen
The Sessions screen (ctrl+s) is a modal overlay showing all active and saved sessions:
┌─ Sessions ─────────────────────────────────────────────────────────────────────────────┐ │ │ │ ❯ Session 2 • feature-dev • claude-4-sonnet • ❯ awaiting input 14:02 │ │ Last: Can you review the auth module... │ │ │ │ Session 1 • reviewer • gpt-4o • ⌛ working 13:58 │ │ Last: Analyze the test coverage for... │ │ │ │ Session 3 • infra-admin • claude-4-opus • idle 13:45 │ │ Last: Update the terraform modules... │ │ │ │────────────────────────────────────────────────────────────────────────────────────────│ │ Saved Sessions (from previous runs) │ │ │ │ debug-session • feature-dev • claude-4-sonnet • 23 prompts • Mar 10 09:15 │ │ refactor-v2 • reviewer • gpt-4o • 8 prompts • Mar 9 16:30 │ │ │ │────────────────────────────────────────────────────────────────────────────────────────│ │ enter Switch │ ctrl+r Resume saved │ ctrl+n New │ d Delete │ r Rename │ esc Back │ └────────────────────────────────────────────────────────────────────────────────────────┘
The screen is divided into two sections:
- Active Sessions (top): Currently open session tabs with their state indicators, persona, actor, and last prompt preview. The current session is highlighted with
❯. - Saved Sessions (bottom): Previously persisted sessions from the SQLite database, showing prompt count and last-used timestamp. These can be resumed with
ctrl+rorenter.
Time formatting follows human-readable relative time: just now, 2m ago, 1h ago, or full date for >24h.
Settings Screen
The Settings screen (F2 or ctrl+,) is a modal overlay with search-driven navigation through all TUI settings:
┌─ Settings ─────────────────────────────────────────────────────────────────────────┐ │ 🔍 Search: flash │ │────────────────────────────────────────────────────────────────────────────────────│ │ │ │ ▼ UI │ │ Theme [ dracula ] 17 themes available │ │ Column width [ 100 ] Fixed column width (40-300) │ │ Scrollbar [ normal ] normal / thin / hidden │ │ Flash duration [ 5.0 ] Seconds (0.5-30) │ │ Auto-copy on select [ ✔ ] Copy selected text to clipboard │ │ │ │ ▼ Notifications │ │ Desktop notifications [ on-blur ] never / on-blur / always │ │ Blink terminal title [ ✔ ] Blink when input needed │ │ Sound effects [ ✔ ] Play sounds on events │ │ │ │────────────────────────────────────────────────────────────────────────────────────│ │ enter Edit │ tab Next │ ctrl+s Focus search │ esc Back (auto-saves) │ └────────────────────────────────────────────────────────────────────────────────────┘
Settings are schema-driven: each setting has a type, default value, validation rules, and description. The schema is used to auto-generate the settings UI — new settings added to the schema automatically appear in the screen.
Settings are persisted as JSON in ~/.config/cleveragents/tui-settings.json and saved automatically when the settings screen is dismissed.
Complete Settings Schema
UI Settings (ui.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
ui.theme |
choices | dracula |
17+ Textual themes | Color theme for the entire TUI |
ui.column |
boolean | true |
— | Enable fixed column width for conversation content |
ui.column_width |
integer | 100 |
40–300 | Maximum column width in characters (when column is true) |
ui.scrollbar |
choices | normal |
normal / thin / hidden |
Scrollbar appearance for all scrollable widgets |
ui.compact_input |
boolean | false |
— | Remove prompt border and margin for minimal appearance |
ui.sessions_bar |
choices | multiple |
always / multiple / never |
When to show the session tab bar (multiple = only when ≥2 sessions) |
ui.footer |
boolean | true |
— | Show the hotkey footer bar at the bottom |
ui.info_bar |
boolean | true |
— | Show the info bar below the prompt (PersonaBar) |
ui.status_line |
boolean | true |
— | Show token count and cost in the info bar |
ui.flash_duration |
number | 5.0 |
0.5–30.0 | Seconds before flash messages auto-dismiss |
ui.auto_copy |
boolean | true |
— | Automatically copy mouse-selected text to clipboard |
ui.prune_low_mark |
integer | 1500 |
100–10000 | Target conversation line count after pruning |
ui.prune_excess |
integer | 1000 |
100–5000 | Lines over prune_low_mark that trigger pruning |
ui.throbber |
choices | rainbow |
rainbow / quotes |
Throbber style: animated gradient or rotating text quotes |
Notification Settings (notifications.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
notifications.desktop |
choices | on-blur |
never / on-blur / always |
When to send desktop notifications |
notifications.blink_title |
boolean | true |
— | Blink terminal window title when input is needed |
notifications.enable_sounds |
boolean | true |
— | Play sound effects on events |
notifications.turn_complete |
boolean | true |
— | Notify when actor finishes a response |
notifications.hide_low_severity |
boolean | true |
— | Suppress desktop notifications for low-severity events |
Sidebar Settings (sidebar.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
sidebar.auto_hide |
boolean | false |
— | Auto-hide sidebar when it loses focus (slides back on ctrl+b) |
sidebar.width |
integer | 36 |
24–60 | Sidebar width in characters when in visible state |
Agent Settings (agent.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
agent.thoughts |
boolean | true |
— | Show actor thinking/reasoning blocks in conversation |
Tool Settings (tools.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
tools.expand |
choices | fail |
never / always / success / fail / both |
When to auto-expand tool call results |
Shell Settings (shell.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
shell.command |
string | /bin/sh |
— | Shell executable for ! mode |
shell.startup_commands |
text | PS1="" |
— | Commands run at shell startup (newline-separated) |
shell.warn_dangerous |
boolean | true |
— | Highlight potentially dangerous commands |
shell.allow_commands |
text | git,ls,cat,... |
— | Commands recognized as shell (auto-switch to $ mode) |
shell.directory_commands |
text | cd,rmdir |
— | Commands whose arguments are directories (for tab completion) |
shell.file_commands |
text | cat,less,head |
— | Commands whose arguments are files (for tab completion) |
Diff Settings (diff.*)
| Setting | Type | Default | Range / Choices | Description |
|---|---|---|---|---|
diff.view |
choices | auto |
unified / split / auto |
Diff display mode (auto selects based on available width) |
Conversation Content Pruning
Long conversations accumulate widgets that consume memory and degrade rendering performance. The TUI implements automatic content pruning to maintain responsiveness:
| Parameter | Setting | Default | Description |
|---|---|---|---|
| Target line count | ui.prune_low_mark |
1500 | After pruning, the conversation is trimmed to approximately this many lines |
| Trigger threshold | ui.prune_excess |
1000 | Pruning activates when the conversation exceeds prune_low_mark + prune_excess lines |
Pruning behavior:
- When the conversation stream exceeds 2500 lines (1500 + 1000), pruning activates
- The oldest message blocks are removed from the DOM (not just hidden) until the line count drops to approximately
prune_low_mark - A
Noteblock is inserted at the top:(Earlier messages pruned — see session history for full conversation) - Pruning never removes the most recent 500 lines regardless of settings
- Pruned messages remain in the session history (SQLite) and can be viewed via
/session:export - Welcome blocks, system notes, and the current actor response are never pruned
Safety Behaviors
Double-Tap Quit
ctrl+c does not immediately quit the TUI. Instead:
- First press: If an actor is processing, sends an interrupt/cancel signal to the actor. If idle, shows a flash message:
Press ctrl+c again within 5s to quit - Second press within 5s: Quits the TUI (after saving session state)
- Second press after 5s: Resets — treated as a new first press
ctrl+q always quits immediately (after saving session state) without requiring a double-tap.
Double-Tap Escape for Terminal
When a terminal widget (TerminalEmbed or ShellTerminal) has focus, a single escape press is ambiguous — the terminal itself may use escape sequences. The TUI requires a double-tap within 400ms:
- First
escape: Starts a 400ms timer - Second
escapewithin 400ms: Exits terminal focus, returns focus to the prompt - No second
escapewithin 400ms: The escape keypress is forwarded to the terminal as a normal escape sequence
A flash hint appears on the first escape: Press escape again to exit terminal.
Loading States
The TUI supports two loading indicator styles, configurable via ui.throbber:
| Style | Setting Value | Description |
|---|---|---|
| Rainbow gradient | rainbow (default) |
Animated horizontal bar cycling through 12 colors at 15fps (see Throbber diagram) |
| Rotating quotes | quotes |
Displays shuffled quotes from a curated collection, rotating every 3 seconds |
The quotes collection contains ~200 entries drawn from science fiction (curated for relevance to AI/coding themes). Example quotes:
"I'm sorry, Dave. I'm afraid I can't do that." — HAL 9000"The only way to do great work is to love what you do." — Steve Jobs"Any sufficiently advanced technology is indistinguishable from magic." — Arthur C. Clarke
Both styles occupy 1 row at the top of the screen and collapse to zero height when idle.
Conversation Export
Conversations can be exported in multiple formats:
| Format | Command / Trigger | Output |
|---|---|---|
| JSON | /session:export [path] |
Full session data including A2A messages, metadata, timestamps |
| Markdown | /session:export --format md [path] |
Human-readable conversation transcript with Markdown formatting |
| SVG | Context menu → v (per block) |
Single block rendered as SVG image |
The JSON export format is the canonical format for /session:import — it contains all data needed to fully restore a session. The Markdown format is a lossy export intended for sharing and documentation.
Hotkey Reference
Hotkeys vary by the current screen and focused widget. The help panel (F1) always reflects the currently applicable hotkeys.
Global Hotkeys (Available Everywhere)
| Key | Action |
|---|---|
ctrl+q |
Quit TUI immediately (saves session state) |
ctrl+c |
Interrupt actor / double-tap to quit (5s window) |
F1 |
Toggle help panel |
F2 / ctrl+, |
Open settings |
ctrl+s |
Open Sessions screen |
ctrl+n |
Create new session tab |
ctrl+w |
Close current session tab |
ctrl+[ / ctrl+] |
Switch to previous / next session tab |
shift+tab |
Cycle sidebar: hidden → visible → fullscreen |
escape |
Close current overlay / modal / sidebar toward main screen |
MainScreen — Prompt Focused
| Key | Action |
|---|---|
enter |
Submit prompt (single-line mode) / insert newline (multi-line mode) |
ctrl+enter |
Submit prompt (multi-line mode) |
shift+enter / ctrl+j |
Insert newline (switch to multi-line mode) |
tab |
Cycle to next persona |
ctrl+tab |
Cycle to next argument preset |
up (at row 0) |
Previous prompt history entry |
down (at last row) |
Next prompt history entry |
@ |
Open Reference Picker overlay |
/ (at position 0) |
Open Slash Command overlay |
! or $ (at position 0) |
Activate shell mode |
backspace (at position 0 in shell mode) |
Deactivate shell mode, return to normal |
tab (in shell mode) |
Tab-complete file/directory path |
ctrl+b |
Focus sidebar (when visible) |
MainScreen — Conversation / Block Cursor
| Key | Action |
|---|---|
alt+up |
Move block cursor to previous block |
alt+down |
Move block cursor to next block |
space |
Expand / collapse focused block |
enter |
Open context menu for focused block |
escape |
Clear block cursor, return focus to prompt |
| Any printable character | Forward to prompt (key passthrough) |
MainScreen — Block Context Menu
| Key | Action |
|---|---|
c |
Copy block content to clipboard |
p |
Copy block content to prompt |
e |
Export block as Markdown file |
v |
Export block as SVG (opens browser) |
m |
Maximize / restore block height |
r |
Retry prompt (ActorResponse blocks only) |
d |
Show raw A2A message data |
escape |
Dismiss menu |
MainScreen — Terminal Focused
| Key | Action |
|---|---|
escape escape (within 400ms) |
Exit terminal focus, return to prompt |
ctrl+c |
Send SIGINT to terminal process |
| All other keys | Forwarded to terminal PTY |
Sidebar — Visible (Partial)
| Key | Action |
|---|---|
tab / shift+tab (within sidebar) |
Navigate between Plans and Projects panels |
enter |
Expand / collapse the highlighted collapsible section |
up / down |
Navigate items within a panel |
escape |
Dismiss sidebar, return focus to prompt |
shift+tab (global) |
Transition to fullscreen sidebar |
Sidebar — Fullscreen
| Key | Action |
|---|---|
space |
Toggle selection on highlighted plan/project |
enter |
Open detail modal (PlanDetailModal or ProjectDetailModal) |
up / down |
Navigate items |
left / right |
Switch between Plans and Projects columns |
ctrl+p |
Create persona from current selection |
ctrl+up / ctrl+down |
Reorder personas in the cycle list |
d |
Delete highlighted persona from cycle list |
/ |
Open search/filter within sidebar |
escape |
Return to visible sidebar state |
Reference Picker Overlay
| Key | Action |
|---|---|
| typing | Fuzzy search across projects, plans, and resources |
enter |
Insert selected reference into prompt |
up / down |
Navigate results |
tab |
Switch between fuzzy search and tree browser modes |
ctrl+p |
Filter to projects only |
ctrl+l |
Filter to plans only |
escape |
Dismiss picker, remove @ trigger character |
Reference Picker — Tree Browser Mode
| Key | Action |
|---|---|
enter |
Select highlighted item as reference |
space |
Expand / collapse tree node |
up / down |
Navigate tree |
/ |
Open inline filter |
tab |
Switch back to fuzzy search mode |
escape |
Dismiss picker |
Slash Command Overlay
| Key | Action |
|---|---|
| typing | Fuzzy search commands |
enter |
Execute selected command |
tab |
Auto-complete command name |
up / down |
Navigate command list |
escape |
Dismiss overlay |
Plan Detail Modal
| Key | Action |
|---|---|
up / down |
Navigate decision tree nodes |
enter |
Select decision node (show detail in right panel) |
space |
Expand / collapse subtree |
/ |
Search within decision tree |
c |
Correct the selected decision (opens correction dialog) |
a |
Approve pending decision |
r |
Reject pending decision |
d |
Show diff for the selected decision |
e |
Explain decision (show reasoning) |
x |
Cancel the plan |
escape |
Close modal |
Project Detail Modal
| Key | Action |
|---|---|
up / down |
Navigate resource tree |
enter |
View resource details (right panel) |
space |
Expand / collapse resource tree node |
/ |
Search within resource tree |
i |
Show invariants for selected resource |
v |
Show validations for selected resource |
escape |
Close modal |
Persona Editor Modal
| Key | Action |
|---|---|
tab / shift+tab |
Navigate form fields |
enter |
Edit the focused field / confirm selection |
ctrl+a |
Add a new argument preset |
ctrl+d |
Delete the selected argument preset |
ctrl+s |
Save persona and close editor |
escape |
Cancel and close editor (prompts to save if modified) |
Permissions Screen
| Key | Action |
|---|---|
j / down |
Next file diff |
k / up |
Previous file diff |
tab / shift+tab |
Cycle focus: file list → diff view → action buttons |
a |
Allow once |
A |
Allow always (this session) |
r |
Reject once |
R |
Reject always (this session) |
d |
Toggle diff view mode: unified → split → auto |
escape |
Reject and close (equivalent to r) |
Settings Screen
| Key | Action |
|---|---|
ctrl+s |
Focus search input |
tab / shift+tab |
Navigate settings fields |
enter |
Edit the focused setting |
escape |
Close settings (auto-saves) |
Sessions Screen
| Key | Action |
|---|---|
up / down |
Navigate session list |
enter |
Switch to highlighted active session / resume saved session |
ctrl+r |
Resume a saved (historical) session |
ctrl+n |
Create new session |
d |
Delete highlighted session |
r |
Rename highlighted session |
escape |
Close sessions screen |
Question / Permission Widget (Inline)
| Key | Action |
|---|---|
a |
Allow once |
A |
Allow always (this session) |
r |
Reject once |
R |
Reject always (this session) |
v |
Open full Permissions Screen (for file writes) |
up / down |
Navigate options |
enter |
Select highlighted option |
Help Panel (F1)
| Key | Action |
|---|---|
F1 |
Close help panel |
escape |
Close help panel |
up / down |
Scroll help content |