Startup agent picker — fetch GET /agents when --new without --agent (TUI) #8

Closed
opened 2026-05-22 19:10:44 -07:00 by vh · 1 comment
Owner

Problem

Today, ratatoskr --new --agent <id> requires the --agent flag. If the
operator omits it, _parse_args raises UsageError("--agent is required when --new is passed"). That's the right behavior for --send mode
(non-interactive, can't prompt). It's the wrong behavior for the TUI: the
operator might not know which agents are available, doesn't want to look
them up out-of-band, and would prefer to pick from a list at startup.

The Worldtree spec exposes GET /agents (spec line 832; vendored at
docs/conversation-api-spec.md at SHA 55101e9...). It returns a JSON
array with at minimum agent_id, name, description per agent; some
carry version, capabilities, supported_models, persona_traits,
and ui_hints (icon + color_hint). Ratatoskr could fetch this list and
let the operator pick.

Solution

Minimal startup agent picker for the TUI when --new is passed without
--agent. Three pieces:

ratatoskr.sessions (issue #2 amendment): new list_agents() function.

FN list_agents(client: httpx.AsyncClient) -> list[AgentInfo]
BRIEF: GET /agents → list of available agents. No pagination, no filters.

AgentInfo is a new frozen dataclass with all fields the spec lists; the
non-required fields default to None / [] / {} when omitted from the
server response (mirrors the SessionInfo origin-conditional-defaults
pattern from INV-001/INV-002 of issue #2).

ratatoskr.cli (issue #3 amendment): --agent becomes conditionally
optional.

  • --agent requirement now depends on mode:
    • --send --new (non-interactive create): --agent STILL required;
      no way to prompt. UsageError preserved.
    • bare --new (TUI create): --agent OPTIONAL. When omitted, the TUI
      launches an agent-picker screen before the chat pane.
    • --session <id> (any mode): --agent STILL forbidden (existing
      INV-004 behavior; preserved).
  • _parse_args STEP 3 validates the mode-conditional requirement.

ratatoskr.tui (issue #4 amendment): new AgentPickerScreen.

Pushed onto the App's screen stack BEFORE on_mount's session-create
when args.new AND args.agent_id is None. Layout:

Header()
Static("Pick an agent for the new session:", id="picker-prompt")
ListView (id="agent-list")  ← one row per agent: "agent_id · name — description"
Footer()  ← bindings: Enter (pick) / Esc (exit)

On Enter, the picker pops itself off the screen stack and stores the
selection in self.agent_id; control returns to the chat-pane screen
which proceeds with create_session(client, agent_id=self.agent_id, end_user_id=self.args.end_user_id). (The end_user_id threading is
issue #5's amendment; this issue and #5 compose naturally.)

On Esc (or Ctrl-D / Ctrl-C from the picker): exit cleanly with code 0.
No session was created.

Out of scope (this issue)

  • Agent search / filter / sort. v1 picker is a flat list. If the
    installed-agent count grows large enough to need search, file a
    follow-up.
  • ui_hints rendering. icon and color_hint from the spec are
    declared in AgentInfo for future use but the picker just shows
    agent_id · name — description in v1. Pretty-print is a follow-up.
  • Tier 3 (consumer-defined) agents (spec §Tier 3, line 2576). They
    show up in GET /agents if the server has them; the picker shows
    them like any other; ratatoskr doesn't distinguish.
  • persona_traits / capabilities preview pane. Side detail when
    the operator highlights an agent. Future polish.
  • CLI agent picker for --send mode. --send is non-interactive
    by design (design-brief §8b). Operators using --send must know
    the agent_id; they can shell out to curl GET /agents | jq if they
    forget.
  • Caching agent list across launches. Each ratatoskr launch
    fetches fresh. v1 over-fetches by design — agent list is small,
    cheap, and rarely stale; cache-coherence is a real cost.

Benefits

  • Operator no longer needs to memorize agent_ids. ratatoskr --new
    Just Works for interactive use.
  • Discoverability: new agents the operator hasn't seen surface
    naturally on next launch.
  • Composes with #5 (--end-user-id): if both are needed, operator
    passes --end-user-id on the CLI and picks the agent in the TUI.
  • Small surface — one new function, one new dataclass, one new
    screen, one CLI conditional. Doesn't reshape the existing
    modules' invariants.

Acceptance

  • All amended contracts (#2, #3, #4) drift-check clean.
  • All tests pass (existing + new list_agents coverage + new TUI
    picker coverage).
  • uv run ruff check src/ tests/ clean.
  • Manual smoke (interactive):
    • ratatoskr --new (no --agent) → picker appears with all agents
      from personal Worldtree; pick mimir → proceeds to chat pane;
      type a message → response streams cleanly.
    • ratatoskr --new --agent mimir (with --agent) → picker skipped;
      behavior unchanged from today.
    • ratatoskr --send "hello" --new (no --agent) → UsageError; no
      TUI launch. (Regression check.)
    • ratatoskr --session s-1 → no picker; no agent fetch; attaches
      to the existing session. (Regression check.)

Dependencies

  • Issue #2 (ratatoskr.sessions) — landed on main; this issue adds
    list_agents() + AgentInfo dataclass.
  • Issue #3 (ratatoskr.cli) — landed on main; this issue amends
    _parse_args for conditional --agent requirement.
  • Issue #4 (ratatoskr.tui) — landed on main; this issue adds
    AgentPickerScreen and a startup branch.
  • Issue #5 (--end-user-id) — independent; composes naturally if both
    land but neither blocks the other.
## Problem Today, `ratatoskr --new --agent <id>` requires the `--agent` flag. If the operator omits it, `_parse_args` raises `UsageError("--agent is required when --new is passed")`. That's the right behavior for `--send` mode (non-interactive, can't prompt). It's the wrong behavior for the TUI: the operator might not know which agents are available, doesn't want to look them up out-of-band, and would prefer to pick from a list at startup. The Worldtree spec exposes `GET /agents` (spec line 832; vendored at `docs/conversation-api-spec.md` at SHA `55101e9...`). It returns a JSON array with at minimum `agent_id`, `name`, `description` per agent; some carry `version`, `capabilities`, `supported_models`, `persona_traits`, and `ui_hints` (icon + color_hint). Ratatoskr could fetch this list and let the operator pick. ## Solution Minimal startup agent picker for the TUI when `--new` is passed without `--agent`. Three pieces: **`ratatoskr.sessions` (issue #2 amendment): new `list_agents()` function.** ```contract FN list_agents(client: httpx.AsyncClient) -> list[AgentInfo] BRIEF: GET /agents → list of available agents. No pagination, no filters. ``` `AgentInfo` is a new frozen dataclass with all fields the spec lists; the non-required fields default to `None` / `[]` / `{}` when omitted from the server response (mirrors the `SessionInfo` origin-conditional-defaults pattern from INV-001/INV-002 of issue #2). **`ratatoskr.cli` (issue #3 amendment): `--agent` becomes conditionally optional.** - `--agent` requirement now depends on mode: - `--send --new` (non-interactive create): `--agent` STILL required; no way to prompt. `UsageError` preserved. - bare `--new` (TUI create): `--agent` OPTIONAL. When omitted, the TUI launches an agent-picker screen before the chat pane. - `--session <id>` (any mode): `--agent` STILL forbidden (existing INV-004 behavior; preserved). - `_parse_args` STEP 3 validates the mode-conditional requirement. **`ratatoskr.tui` (issue #4 amendment): new `AgentPickerScreen`.** Pushed onto the App's screen stack BEFORE `on_mount`'s session-create when `args.new` AND `args.agent_id is None`. Layout: ``` Header() Static("Pick an agent for the new session:", id="picker-prompt") ListView (id="agent-list") ← one row per agent: "agent_id · name — description" Footer() ← bindings: Enter (pick) / Esc (exit) ``` On Enter, the picker pops itself off the screen stack and stores the selection in `self.agent_id`; control returns to the chat-pane screen which proceeds with `create_session(client, agent_id=self.agent_id, end_user_id=self.args.end_user_id)`. (The `end_user_id` threading is issue #5's amendment; this issue and #5 compose naturally.) On Esc (or Ctrl-D / Ctrl-C from the picker): exit cleanly with code 0. No session was created. ## Out of scope (this issue) - **Agent search / filter / sort.** v1 picker is a flat list. If the installed-agent count grows large enough to need search, file a follow-up. - **`ui_hints` rendering.** `icon` and `color_hint` from the spec are declared in `AgentInfo` for future use but the picker just shows `agent_id · name — description` in v1. Pretty-print is a follow-up. - **Tier 3 (consumer-defined) agents** (spec §Tier 3, line 2576). They show up in `GET /agents` if the server has them; the picker shows them like any other; ratatoskr doesn't distinguish. - **`persona_traits` / `capabilities` preview pane.** Side detail when the operator highlights an agent. Future polish. - **CLI agent picker for `--send` mode.** `--send` is non-interactive by design (design-brief §8b). Operators using `--send` must know the agent_id; they can shell out to `curl GET /agents | jq` if they forget. - **Caching agent list across launches.** Each `ratatoskr` launch fetches fresh. v1 over-fetches by design — agent list is small, cheap, and rarely stale; cache-coherence is a real cost. ## Benefits - Operator no longer needs to memorize agent_ids. `ratatoskr --new` Just Works for interactive use. - Discoverability: new agents the operator hasn't seen surface naturally on next launch. - Composes with #5 (`--end-user-id`): if both are needed, operator passes `--end-user-id` on the CLI and picks the agent in the TUI. - Small surface — one new function, one new dataclass, one new screen, one CLI conditional. Doesn't reshape the existing modules' invariants. ## Acceptance - All amended contracts (#2, #3, #4) drift-check clean. - All tests pass (existing + new `list_agents` coverage + new TUI picker coverage). - `uv run ruff check src/ tests/` clean. - Manual smoke (interactive): - `ratatoskr --new` (no `--agent`) → picker appears with all agents from personal Worldtree; pick mimir → proceeds to chat pane; type a message → response streams cleanly. - `ratatoskr --new --agent mimir` (with `--agent`) → picker skipped; behavior unchanged from today. - `ratatoskr --send "hello" --new` (no `--agent`) → UsageError; no TUI launch. (Regression check.) - `ratatoskr --session s-1` → no picker; no agent fetch; attaches to the existing session. (Regression check.) ## Dependencies - Issue #2 (`ratatoskr.sessions`) — landed on main; this issue adds `list_agents()` + `AgentInfo` dataclass. - Issue #3 (`ratatoskr.cli`) — landed on main; this issue amends `_parse_args` for conditional `--agent` requirement. - Issue #4 (`ratatoskr.tui`) — landed on main; this issue adds `AgentPickerScreen` and a startup branch. - Issue #5 (`--end-user-id`) — independent; composes naturally if both land but neither blocks the other.
vh added the clituienhancementtask labels 2026-05-22 19:10:45 -07:00
vh closed this issue 2026-05-23 17:58:27 -07:00
Author
Owner

Shipped at d30be12 (v0.3.0).

  • ratatoskr.sessions: list_agents() + AgentInfo frozen dataclass.
  • ratatoskr.cli: --agent mode-conditional (required for --send --new; optional for bare --new; forbidden with --session).
  • ratatoskr.tui: new AgentPickerApp (separate Textual App, opens before RatatoskrApp so list_agents errors land on real stderr — preserves issue #6 INV-001). _resolve_then_run gains the pre-create picker branch.

Contract: docs/contracts/issues/8.contract.md (drift-check clean).

+18 tests; 227 total GREEN; ruff clean. Live smoke against personal Worldtree returned 12 agents; programmatic picker drive auto-picked lofn and created a real session with end_user_id="ratatoskr-tui". Interactive eyeball still pending — auto-pick covered the wiring.

Shipped at d30be12 (v0.3.0). - `ratatoskr.sessions`: `list_agents()` + `AgentInfo` frozen dataclass. - `ratatoskr.cli`: `--agent` mode-conditional (required for `--send --new`; optional for bare `--new`; forbidden with `--session`). - `ratatoskr.tui`: new `AgentPickerApp` (separate Textual App, opens before `RatatoskrApp` so `list_agents` errors land on real stderr — preserves issue #6 INV-001). `_resolve_then_run` gains the pre-create picker branch. Contract: `docs/contracts/issues/8.contract.md` (drift-check clean). +18 tests; 227 total GREEN; ruff clean. Live smoke against personal Worldtree returned 12 agents; programmatic picker drive auto-picked `lofn` and created a real session with `end_user_id="ratatoskr-tui"`. Interactive eyeball still pending — auto-pick covered the wiring.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vh/ratatoskr#8