Files
cleveragents-core/docs/adr/ADR-045-tui-persona-system.md
freemo 1fd4918be8
CI / push-validation (pull_request) Successful in 34s
CI / helm (pull_request) Successful in 45s
CI / build (pull_request) Successful in 49s
CI / lint (pull_request) Successful in 1m6s
CI / quality (pull_request) Successful in 1m8s
CI / typecheck (pull_request) Successful in 1m21s
CI / security (pull_request) Successful in 1m33s
CI / unit_tests (pull_request) Successful in 6m3s
CI / docker (pull_request) Successful in 1m24s
CI / coverage (pull_request) Successful in 12m56s
CI / integration_tests (pull_request) Successful in 24m23s
CI / status-check (pull_request) Successful in 3s
docs(spec): clarify TUI session export formats and persona schema fields
Accept implementation extensions as spec updates based on UAT findings:

1. `/session:export` now officially supports three formats:
   - `json` (default, canonical, importable)
   - `md` (Markdown transcript, lossy)
   - `txt` (plain-text transcript, lossy)
   Updated ADR-046 command table and spec Conversation Export section.

2. Persona data model now includes two optional display fields:
   - `icon`: emoji or single character for tab labels and lists
   - `greeting`: message shown when persona is activated
   Updated ADR-045 data model YAML example and Decision section.

These changes accept positive implementation extensions discovered by
UAT testing (issues #4871, #4858) rather than reverting them.

Refs: #4940
2026-05-31 08:35:06 -04:00

19 KiB

adr_number, title, status_history, tier, authors, superseded_by, related_adrs, acceptance
adr_number title status_history tier authors superseded_by related_adrs acceptance
45 TUI Persona System
2026-03-10
Proposed
Jeffrey Phillips Freeman
2026-03-10
Accepted
Jeffrey Phillips Freeman
4
Jeffrey Phillips Freeman
null
number title relationship
10 Actor and Agent Architecture Personas reference actors defined by the actor architecture
number title relationship
17 Automation Profiles Persona argument presets may override automation profile selection
number title relationship
20 Session Model Each TUI session binds to a persona which configures the session's actor and scope
number title relationship
44 TUI Architecture and Framework Personas are a TUI-specific concept within the TUI architecture
number title relationship
9 Project Model Personas scope to projects defined by the project model
number title relationship
6 Plan Lifecycle Personas scope to plans that follow the plan lifecycle
votes_for votes_against abstentions
voter comment
Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com> The persona abstraction elegantly bundles the TUI-specific combination of actor selection, argument configuration, project/plan scoping, and argument presets into a single switchable unit — solving the multi-project workflow ergonomics problem without polluting the domain model

Context

In the CLI, each invocation specifies its configuration explicitly: agents plan use --actor anthropic/claude-4 --project local/api-service --automation-profile trusted .... This is appropriate for stateless command execution but creates friction in the TUI, where users conduct long-running interactive sessions and frequently switch between different actor configurations, project scopes, and reasoning modes.

Consider a developer managing three projects simultaneously. In the CLI, they would run separate commands with different flags for each project. In the TUI, they need a way to quickly switch their entire working context — the actor, its configuration arguments, and the set of projects and plans the actor should focus on — without re-typing configuration every time.

Additionally, the CleverAgents system supports actors with configurable arguments (e.g., thinking_effort, temperature, max_tokens). Power users frequently want to toggle between reasoning modes (e.g., "quick answer" vs. "deep analysis") within the same session. This requires a mechanism to define named argument overlays that can be cycled through rapidly.

None of these concerns belong in the domain model: they are purely Presentation-layer ergonomics for the TUI. The domain layer knows about actors, sessions, projects, and plans — but not about the TUI-specific bundling of these into a switchable unit.

Decision Drivers

  • Users working with multiple projects need to switch their entire working context (actor, arguments, project scope, plan scope) as a single atomic operation, not as individual flag changes
  • Argument presets (e.g., cycling through thinking effort levels) are a common workflow pattern that should be achievable with a single keypress, not by editing configuration
  • The persona concept must remain a TUI-only abstraction — it must not leak into the domain model, A2A protocol, or database schema
  • Default behavior must be sensible without any persona configuration: first-run users should get a working persona automatically based on their actor selection
  • The persona system must integrate cleanly with the existing actor argument system — presets overlay arguments, they do not replace them
  • Personas must be portable (exportable/importable) for team sharing and machine migration

Decision

CleverAgents introduces the Persona concept as a TUI-only abstraction that bundles:

  1. Actor reference — a namespaced actor name (e.g., anthropic/claude-4-sonnet)
  2. Base arguments — the default arguments passed to the actor on every invocation
  3. Scoped projects — a list of project references that are always included in the session's context
  4. Scoped plans — a list of plan references that are always included in the session's context
  5. Argument presets — named sets of argument overrides that can be cycled with ctrl+tab
  6. Display metadata — a short name, optional accent color, optional icon (emoji/character), optional greeting message, and description

Personas are:

  • Stored locally in ~/.config/cleveragents/personas/ as individual YAML files
  • Cyclable via tab in the main chat screen, cycling through a user-configured ordered list
  • Session-bound — each TUI session has a current persona; changing the persona mid-session updates the actor and scope for subsequent prompts (previous conversation history is retained)
  • Auto-generated on first run from the actor selection, with thinking-level presets auto-detected from the actor's argument schema

Design

Persona Data Model

# ~/.config/cleveragents/personas/feature-dev.yaml
name: "feature-dev"
description: "Feature development with Claude on main projects"
actor: "anthropic/claude-4-sonnet"
color: "$primary"                    # Textual CSS color token or hex
icon: "🚀"                           # Optional emoji or single character for display in tab labels and lists
greeting: "Ready to build features!" # Optional greeting shown when this persona is activated

base_arguments:
  thinking_effort: "medium"
  temperature: 0.7
  max_tokens: 16384

scoped_projects:
  - "local/cleveragents"
  - "local/api-service"

scoped_plans: []                     # Usually empty; populated ad-hoc

argument_presets:
  - name: "default"                  # Always present, always first
    display: "default"               # Short label for PersonaBar
    overrides: {}                    # No overrides — uses base_arguments as-is

  - name: "think-more"
    display: "think: high"
    overrides:
      thinking_effort: "high"

  - name: "think-max"
    display: "think: max"
    overrides:
      thinking_effort: "max"
      temperature: 0.3               # Lower temperature for deep reasoning

  - name: "quick"
    display: "quick"
    overrides:
      thinking_effort: "low"
      max_tokens: 4096

cycle_order: 1                       # Position in tab-cycle list (1-indexed, 0 = not in cycle)

Argument Preset Resolution

When the user selects a preset (via ctrl+tab), the effective arguments are computed as:

effective_arguments = merge(base_arguments, active_preset.overrides)

Where merge is a shallow dictionary merge — preset keys override base keys, base keys not in the preset are preserved. This is the same semantics as CLI flag overrides of YAML-configured defaults.

The "default" preset always has empty overrides ({}), meaning it produces exactly base_arguments. This ensures the user can always return to their configured baseline.

Auto-Generated Presets

When a persona is created (either automatically on first run or manually via the persona editor), the system inspects the actor's argument schema to auto-generate useful presets:

  1. Thinking effort detection — If the actor schema includes a thinking_effort argument (as all built-in LLM actors do), three presets are auto-generated:

    • "default"{} (uses base thinking_effort)
    • "think: high"{ thinking_effort: "high" }
    • "think: max"{ thinking_effort: "max" }
  2. Temperature detection — If the actor schema includes a temperature argument, presets for "precise" ({ temperature: 0.1 }) and "creative" ({ temperature: 0.9 }) are appended.

  3. No matching arguments — If the actor has no recognized argument patterns, only the "default" preset is created.

The user can modify, delete, or add presets at any time via the PersonaEditorModal.

Tab Cycling Behavior

The tab key in the main chat prompt cycles through personas in the user's configured cycle list:

tab:       persona_1 → persona_2 → persona_3 → persona_1 → ...
shift+tab: (used for sidebar cycling, NOT persona reverse-cycling)

The cycle list is the set of personas with cycle_order > 0, sorted by cycle_order. Personas with cycle_order: 0 exist but are not part of the tab cycle — they can only be selected explicitly via /persona:set <name> or through the fullscreen sidebar.

When cycling, the PersonaBar updates immediately to show the new persona's name, actor, and current preset. The actor binding for the session is updated — subsequent prompts use the new persona's actor and scope. Previous conversation messages remain visible (they are part of the session history, not the persona).

Ctrl+Tab Preset Cycling

ctrl+tab cycles through the current persona's argument presets:

ctrl+tab: default → think: high → think: max → quick → default → ...

The active preset's display name appears in the PersonaBar. The effective arguments change immediately for the next prompt.

Persona Lifecycle

┌───────────┐     ┌──────────────┐     ┌───────────────┐     ┌──────────┐
│ First Run │────►│ Auto-create  │────►│ Active in     │────►│ Modified │
│ (no       │     │ "default"    │     │ session       │     │ by user  │
│  personas)│     │ persona from │     │               │     │          │
└───────────┘     │ actor select │     └───────────────┘     └──────────┘
                  └──────────────┘            ▲                     │
                                              │                     │
                                              └─── Saved to YAML ───┘
  1. First run: No personas exist. Actor selection overlay creates a "default" persona with auto-generated presets.
  2. Normal startup: Last active persona is restored from ~/.config/cleveragents/tui-state.yaml.
  3. Manual creation: User creates personas via PersonaEditorModal (from fullscreen sidebar or /persona:create command). The editor provides:
    • Actor selection (searchable list from Actor Registry)
    • Base argument editor (form fields derived from actor's argument schema)
    • Project/plan scope selection (checkboxes from Project/Plan registries)
    • Preset editor (add/remove/edit named argument overlays)
    • Cycle order assignment
  4. Modification: User edits an existing persona; changes are saved to the YAML file immediately.
  5. Deletion: User deletes a persona; the YAML file is removed. If the deleted persona was active, the next persona in the cycle list becomes active (or "default" if none remain).

Scope Behavior

When a persona has scoped projects or plans:

  1. Every prompt submitted in a session using that persona implicitly includes those project/plan references in the context. This is equivalent to the user typing @project:<name> for each scoped project in every prompt.
  2. The @ reference system can add additional projects/plans for a single prompt — these are additive to the persona's scope.
  3. The sidebar highlights scoped projects/plans with a distinct indicator (e.g., a pin icon or accent color) to distinguish them from unscoped items.
  4. Context assembly (ACMS) receives the combined scope (persona scope + prompt @ references) and prioritizes those resources in the CRP for the resulting plan.

PersonaBar Display

The PersonaBar sits directly below the prompt input and is always visible:

┌─────────────────────────────────────────────────────────────────────┐
│ ❯ What would you like to do?                                        │
│   ▌@▐ refs  ▌/▐ commands  ▌!▐ shell                                 │
└─────────────────────────────────────────────────────────────────────┘
  feature-dev │ anthropic/claude-4-sonnet │ think: high │ 2 projects │ $0.12
Segment Content Style Updates When
Persona name feature-dev $text-primary on $primary 10% background tab cycle
Actor name anthropic/claude-4-sonnet $text-secondary tab cycle
Preset label think: high $text-warning (differs from default), $text-muted (default) ctrl+tab cycle
Scope indicator 2 projects / 1 plan, 3 projects $text-muted Persona change or /scope:add/remove
Session cost $0.12 $secondary 70%, right-aligned After each actor response

Persona Storage Format

Personas are stored as individual YAML files in ~/.config/cleveragents/personas/:

~/.config/cleveragents/
├── config.toml              # Global config (existing)
├── tui-state.yaml           # TUI state (last persona, window sizes, etc.)
└── personas/
    ├── default.yaml
    ├── feature-dev.yaml
    ├── reviewer.yaml
    └── infra-admin.yaml

The TUI state file tracks transient state:

# ~/.config/cleveragents/tui-state.yaml
last_persona: "feature-dev"
last_preset: "think: high"
last_session_id: "01HXM8C2..."
sidebar_state: "visible"            # hidden | visible | fullscreen
theme_override: null                # null = use config.toml setting
window_width: 180
window_height: 50

Persona Export and Import

Personas can be shared between users or machines:

/persona:export feature-dev          # Writes feature-dev.yaml to current directory
/persona:import ./feature-dev.yaml   # Copies to personas/ directory

Exported files are the same YAML format as stored files — no transformation needed. This enables teams to share persona configurations via version control or messaging.

Constraints

  • Personas must never appear in the domain model, A2A protocol, database schema, or any non-TUI code path — they are a pure Presentation-layer concept
  • Every persona must have exactly one "default" argument preset with empty overrides; this preset cannot be deleted or renamed
  • The cycle_order field must be unique across all personas with cycle_order > 0; validation occurs on save
  • Actor references in personas must resolve to registered actors at session start; if an actor is unregistered, the persona is marked as invalid with a visible warning
  • Base arguments must conform to the referenced actor's argument schema; invalid arguments are rejected at persona save time with a validation error
  • Scoped projects and plans must reference existing entities at session start; stale references produce warnings (not errors) since projects/plans may be created or removed between TUI sessions

Consequences

Positive

  • Users can switch their entire working context (actor, arguments, project scope) with a single tab keypress, dramatically reducing friction in multi-project workflows
  • Argument presets provide instant access to different reasoning modes (quick vs. deep thinking) without editing configuration — a single ctrl+tab changes the actor's behavior
  • Auto-generated presets mean first-run users get useful preset cycling without any configuration — the system discovers the actor's capabilities and creates sensible defaults
  • Persona portability (YAML export/import) enables team standardization — a lead developer can define personas for common workflows and share them with the team
  • The strict separation from the domain model means personas can evolve freely without database migrations, A2A version bumps, or backward compatibility concerns

Negative

  • The persona concept introduces a TUI-specific abstraction that has no CLI equivalent — CLI users must specify actor, arguments, and project scope explicitly on each command (this is by design, but creates a feature gap between interfaces)
  • Auto-generated presets may not match user expectations for all actor types — custom actors with unusual argument schemas may produce unhelpful or no auto-generated presets
  • Persona state is local to the machine; users on multiple machines must manually sync persona files (mitigated by export/import)

Risks

  • If the actor argument schema changes (e.g., a new version of an actor adds or removes arguments), existing persona base_arguments and preset overrides may become invalid — the system should validate on load and warn rather than fail
  • The cycle_order uniqueness constraint may frustrate users who want to reorder personas frequently — consider a drag-and-drop interface in the fullscreen sidebar to simplify reordering
  • Stale project/plan references in scoped_projects/scoped_plans could silently degrade context quality if the user doesn't notice the warning — consider making stale references more prominent (e.g., PersonaBar turns warning color)

Alternatives Considered

Store personas in the database via A2A — This would make personas available across CLI, TUI, and Web. Rejected because it adds domain complexity for a Presentation-layer concept. Different frontends have different ergonomic needs; the CLI's stateless model doesn't benefit from personas. If cross-frontend persona sharing is needed later, it can be added as a new A2A operation without changing the persona model.

Use environment variables or shell aliases instead of personas — Users could define shell aliases like alias ca-dev="agents --actor anthropic/claude-4 --project local/api-service". Rejected because this doesn't work within a TUI session (can't change the alias mid-session), doesn't support argument presets, and provides no discoverability or management UI.

Actor profiles (actor-side configuration) — Define different configurations as separate actors (e.g., local/claude-quick, local/claude-deep). Rejected because this pollutes the actor registry with configuration variants rather than distinct actors, and doesn't address project scoping.

Session-level configuration only — Allow changing actor and scope per-session via commands but without the persona bundling. Rejected because it requires multiple commands to switch context (change actor, change scope, change arguments) rather than a single tab press.

Compliance

  • Unit tests: Persona YAML serialization/deserialization round-trip tests for all fields
  • Validation tests: Verify that invalid argument schemas, missing actors, duplicate cycle orders, and missing default presets are caught at save time
  • Integration tests: Pilot tests verify that tab cycling updates the PersonaBar and subsequent prompts use the new persona's actor
  • Preset tests: Verify that ctrl+tab cycling computes effective arguments correctly (merge semantics)
  • Auto-generation tests: Verify that actors with thinking_effort arguments produce the expected presets, and actors without recognized arguments produce only the default preset
  • State persistence tests: Verify that tui-state.yaml correctly saves and restores last persona, preset, and sidebar state across TUI restarts
  • Export/import tests: Verify that exported YAML files are valid and importable on a different machine (no absolute paths or machine-specific references)