Skip to content

feat(tui): render hierarchical agent tree path (root/agent/sub-agent) instead of raw thread UUIDs (#23588)#356

Merged
andrei-hasna merged 2 commits into
mainfrom
feat/tui-agent-tree-names
Jul 22, 2026
Merged

feat(tui): render hierarchical agent tree path (root/agent/sub-agent) instead of raw thread UUIDs (#23588)#356
andrei-hasna merged 2 commits into
mainfrom
feat/tui-agent-tree-names

Conversation

@andrei-hasna

Copy link
Copy Markdown
Contributor

What

The Codewith TUI never rendered the canonical AgentPath (/root/agent/sub_agent) the fork already composes server-side. Collab transcript rows fell back to the raw 36-character thread UUID whenever an agent had no nickname, e.g. Waiting for 019f8894-89dc-70f2-ad8e-d74deba8ed9b.

Addresses upstream openai/codex#23588.

The single choke point

multi_agents.rs::agent_label_spans() feeds all seven collab rows (Waiting for / Spawned / Sent input to / Resuming / Resumed / Closed / wait-complete). The no-nickname branch printed thread_id.to_string() (full UUID). It now renders a precomputed hierarchical tree_path, and as a last resort an 8-character short id — never a full UUID.

How

  • AgentMetadata / AgentLabel gain a tree_path; the no-nickname branch prefers it, then a short id.
  • AgentNavigationState gains parents and paths maps so ancestry survives thread switches (which rebuild the ChatWidget).
  • New App::agent_tree_path resolves in order:
    1. the server-composed authoritative AgentPath (leading / stripped, leaf swapped for the thread's nickname when known);
    2. else a parent-chain walk to root, labeling each node by nickname → [role] → 8-char short id, with a visited-set cycle guard and an …/ prefix when the chain to root cannot be confirmed;
    3. else thread_label (short id, never a full UUID).
  • parent_thread_id + source.get_agent_path() are captured at the ThreadStarted, thread/read liveness, loaded-subagent backfill, and local/external spawn upsert sites.

Edge cases handled

  • Depth cap (4 segments): root/…/<parent>/<leaf>, with per-segment grapheme truncation (truncate_text).
  • Cycle / self-parent: visited-set guard terminates; result is …/-prefixed.
  • Orphaned parent (edge → uncached thread): terminates; result is …/<leaf>.
  • Non-sub-agent threads (/side forks, primary): no agent_path, no parent → thread_label, unchanged.
  • A full UUID is never emitted anywhere.

Tests

  • agent_tree_path unit tests (thread_routing.rs): authoritative-path formatting + nickname leaf swap; parent-walk nickname→role→short-id; depth-cap collapse; cycle guard; orphaned parent; primary → thread_label; unknown thread → short id. Each asserts the output never contains a 36-char UUID.
  • multi_agents.rs rendering tests: no-nickname row renders the tree path (and, without one, an 8-char short id) instead of the receiver UUID.
  • Existing collab_agent_transcript / collab_resume_interrupted snapshots are unchanged (they use nicknames → first branch).

Second raw-UUID surface fixed

The live "Waiting for N agents" status indicator (collab_wait_status.rs) also rendered the raw thread UUID for nickname-less agents. It now uses the same nickname → tree_path → 8-char short id fallback.

Verification (Blacksmith, codex-tui)

3447 tests run: 3447 passed, 4 skipped (exit 0)

New tests, all passing:

  • agent_tree_path_* (8): authoritative-path + nickname leaf swap, without-nickname, parent-walk nickname→role→short-id, deep-path collapse, cycle guard, orphaned parent, primary→thread_label, unknown→short id (each asserts no 36-char UUID in output).
  • multi_agents::nickname_less_row_renders_tree_path_instead_of_uuid and ..._renders_short_id_not_uuid.
  • loaded_threads::captures_authoritative_agent_path_and_parent_for_loaded_subagent.

Two snapshots updated — both flip a full UUID to an 8-char short id (the intended fix, reviewed):

  • app_server_collab_spawn_completed_renders_requested_model_and_effort: Spawned 019cff70-…-b91781b41a8eSpawned 019cff70.
  • collab_wait_status::multiple_wait_status: • 019cff70-…-b96db334332d — Working• 019cff70 — Working.

TUI-only rendering change; does NOT modify server-side path composition.

…UUIDs

Collab transcript rows (Waiting for / Spawned / Sent input to / Resuming /
Resumed / Closed / wait-complete) previously printed the full 36-character
thread UUID whenever an agent had no nickname, e.g.
`Waiting for 019f8894-89dc-70f2-ad8e-d74deba8ed9b`.

Fix the single choke point `agent_label_spans` so the no-nickname branch
renders a readable hierarchical path (`root/agent/sub_agent`) and, as a last
resort, an 8-character short id — never a full UUID.

- `AgentMetadata`/`AgentLabel` gain a precomputed `tree_path`; the fallback
  branch prefers it, then a short id.
- `AgentNavigationState` gains `parents` and `paths` maps so ancestry survives
  thread switches (which rebuild the ChatWidget).
- New `App::agent_tree_path` resolves, in order: (a) the server-composed
  authoritative `AgentPath` (leading `/` stripped, leaf swapped for the
  thread's nickname); (b) a parent-chain walk to root labeling each node by
  nickname -> `[role]` -> short id, with a visited-set cycle guard and an
  ellipsis prefix for unconfirmed chains; (c) `thread_label` (short id).
- Depth is capped at 4 segments (`root/…/<parent>/<leaf>`) with per-segment
  grapheme truncation. Cycles, self-parents, and orphaned parent edges
  terminate safely with a `…/` prefix.
- `parent_thread_id` + `source.get_agent_path()` are captured at the
  ThreadStarted, thread/read liveness, loaded-subagent backfill, and local /
  external spawn upsert sites.

Addresses upstream openai/codex#23588 (TUI never rendered the AgentPath the
fork already composes server-side).
@andrei-hasna
andrei-hasna merged commit 7cc23bd into main Jul 22, 2026
25 of 27 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant