Skip to content

Add local child agents to TUI#13776

Open
harryalbert wants to merge 3 commits into
harry/code-1822-tui-multi-sessionfrom
harry/code-1822-tui-local-children
Open

Add local child agents to TUI#13776
harryalbert wants to merge 3 commits into
harry/code-1822-tui-multi-sessionfrom
harry/code-1822-tui-local-children

Conversation

@harryalbert

@harryalbert harryalbert commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Adds TuiOrchestrationModel, the TUI coordinator that turns accepted local run_agents requests into native Oz child agents hosted by retained background terminal sessions. The parent session remains focused while each child gets a complete TuiTerminalSessionView and terminal manager, preserving the multi-session ownership model established in the PR below.

Each session view owns its StartAgentExecutor subscription and emits semantic launch/cleanup events with the parent working-directory snapshot. The coordinator subscribes to every session registered in TuiSessions, including background children, so descendants use the same path without exposing action/controller model handles through the view. Native launch creates the server task first, then creates the background session, inherits the parent execution profile/base model/current directory, applies any explicit run-wide model override, establishes conversation lineage, and sends the first prompt.

Introduces a shared local-Oz launch layer used by both GUI hidden panes and TUI background sessions. It owns task creation, agent-name normalization, setting inheritance, and explicit model overrides, while each frontend retains only its surface materialization. TUI model resolution now honors explicit per-surface child overrides before the file-backed agents.model default.

The coordinator also owns orchestration-event consumer lifetimes. Parent and child streams are registered only after successful materialization and are removed with their session.

The parent transcript now renders received child-message sender/subject lines and lifecycle-event counts, and suppresses the otherwise-empty WaitForEvents tool row. Rich child-message UI, remote/CLI-harness materialization, and child-session navigation remain in the PRs above.

Ownership and data flow

flowchart LR
  RunAgents["Accepted local run_agents"] --> Executor["StartAgentExecutor"]
  Executor --> SessionEvent["TuiTerminalSessionView event
request + parent cwd"]
  SessionEvent --> Coordinator["TuiOrchestrationModel"]
  Coordinator --> SharedLaunch["Shared local Oz launch
server task + settings"]
  SharedLaunch --> Background["Unfocused TuiSessions entry
view + terminal manager"]
  Background --> History["Child conversation + task id"]
  History --> Prompt["Send first child prompt"]
  Coordinator --> Streams["Parent + child event consumers"]
  Streams --> Transcript["Parent transcript messages/events"]
Loading

Linked Issue

CODE-1822 — Orchestration

Demo

Watch the local child-agent orchestration demo on Loom

Review guide

  • app/src/ai/blocklist/child_agent_launch.rs and app/src/pane_group/pane/terminal_pane.rs — shared native task preparation/settings and GUI reuse.
  • crates/warp_tui/src/orchestration_model.rs — session event routing, native launch phases, conversation/session ownership, streamer consumers, and failure cleanup.
  • crates/warp_tui/src/terminal_session_view.rs, session.rs, and session_registry.rs — executor-to-session events, semantic child startup, parent-cwd session creation, and retained background sessions.
  • app/src/ai/llms.rs and app/src/ai/blocklist/history_model.rs — explicit TUI child model overrides and topology cleanup on conversation removal.
  • crates/warp_tui/src/agent_block.rs — minimal received-message/lifecycle rendering and hidden WaitForEvents rows.
  • specs/code-1822-tui-local-children/TECH.md — as-built architecture, ownership boundaries, non-goals, and validation.

Testing

  • I have manually tested my changes locally with ./script/run

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

CHANGELOG-NONE

Co-Authored-By: Oz oz-agent@warp.dev

@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from 665b765 to 5511f60 Compare July 15, 2026 18:55
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch 2 times, most recently from a725a0d to 7fde057 Compare July 15, 2026 22:12
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch 2 times, most recently from 589ec22 to 5055c52 Compare July 16, 2026 02:44
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from 7fde057 to 289a5d1 Compare July 16, 2026 02:44
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from 289a5d1 to cbbd8d0 Compare July 16, 2026 11:58
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch 2 times, most recently from c4eb571 to e1b93b0 Compare July 16, 2026 12:44
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch 2 times, most recently from 6bb0618 to 6a04f77 Compare July 16, 2026 12:49
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch 2 times, most recently from 94f69eb to 626fa04 Compare July 16, 2026 12:53
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from 6a04f77 to 1e9b80f Compare July 16, 2026 12:53
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from 626fa04 to 90c65aa Compare July 16, 2026 14:00
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from 1e9b80f to 063d148 Compare July 16, 2026 14:00
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from 90c65aa to a548617 Compare July 16, 2026 14:17
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch 2 times, most recently from 97603f3 to b030f4f Compare July 16, 2026 14:19
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from a548617 to e38c4a2 Compare July 16, 2026 14:19
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from b030f4f to 6a48d86 Compare July 16, 2026 15:59
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from e38c4a2 to 1519f99 Compare July 16, 2026 15:59
@harryalbert harryalbert changed the title Add TuiOrchestrationModel + local native child agents in background sessions add native local child agents in background TUI sessions Jul 16, 2026
@harryalbert harryalbert changed the title add native local child agents in background TUI sessions Add local child agents to TUI Jul 16, 2026
@harryalbert harryalbert changed the title Add local child agents to TUI add native local child agents in background TUI sessions Jul 16, 2026
@harryalbert harryalbert changed the title add native local child agents in background TUI sessions Add local child agents to TUI Jul 16, 2026
@@ -0,0 +1,93 @@
//! Frontend-neutral preparation and settings propagation for local Oz children.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is mostly just code moved from GUI-specific places into shared helpers

| TodoOperation::MarkAsCompleted { .. } => {}
},

// TODO: add full status rendering for sub-agents.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is covered in a PR above this one in the stack, so don't index on the gross message UI in this PR

}

/// Creates and registers a full local terminal session.
pub(crate) fn create_local_terminal_session(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just a re-org so session creation can be done elsewhere using the same code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think session creation should be driven by the TuiSessionModel itself

@harryalbert
harryalbert marked this pull request as ready for review July 16, 2026 18:02
@harryalbert
harryalbert requested a review from kevinyang372 July 16, 2026 18:02
@oz-for-oss

oz-for-oss Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@harryalbert

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from a039404 to aa12618 Compare July 16, 2026 18:04

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR adds shared local Oz child-agent launch preparation and wires the TUI to materialize accepted local child agents as retained background sessions, with model override handling, event-consumer ownership, transcript rendering updates, and tests. The provided spec context contains no approved repository spec to validate against; the PR description includes a Loom demo for the user-facing TUI behavior.

Concerns

  • Background child session creation still focuses the newly created session view input, which can steal keyboard focus from the parent even though the session registry remains focused on the parent.
  • The TUI materializer listens to every StartAgentExecutor create event, so direct model-emitted StartAgent tool calls can launch local children without the accepted run_agents approval/config path described by the PR.
  • Removing a parent conversation still leaves its children_by_parent entry behind, so the topology index can return stale child IDs for a deleted parent.
  • Security/privacy: one new warning log includes the agent-controlled child name; avoid putting user/agent-controlled text into Info-and-above breadcrumbs.

Verdict

Found: 0 critical, 3 important, 1 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

) {
let sessions = TuiSessions::handle(ctx);
let (session_id, session_view) =
create_local_terminal_session(&sessions, false, working_directory, ctx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] focus=false only affects the session registry; constructing the child TuiTerminalSessionView still focuses its input view, so launching a background child can steal keyboard focus from the parent. Plumb the focus intent into view construction or restore parent focus after creating unfocused child sessions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check if this is true?

});
let start_agent_executor = action_model.as_ref(ctx).start_agent_executor(ctx);
ctx.subscribe_to_model(&start_agent_executor, |view, _, event, ctx| match event {
StartAgentExecutorEvent::CreateAgent(request) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] [SECURITY] This relays every StartAgentExecutor create event into the TUI materializer; direct model-emitted StartAgent tool calls autoexecute outside the accepted run_agents approval/config path, so carry provenance or otherwise gate this path to approved run_agents dispatches.

Comment on lines +2158 to +2161
self.children_by_parent.retain(|_, child_ids| {
child_ids.retain(|child_id| *child_id != conversation_id);
!child_ids.is_empty()
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This only removes the deleted conversation when it appears as a child, but leaves a stale entry when the deleted conversation is itself a parent; remove that parent key before pruning other parents so child_conversation_ids_of(&removed_parent) cannot return orphaned children.

Suggested change
self.children_by_parent.retain(|_, child_ids| {
child_ids.retain(|child_id| *child_id != conversation_id);
!child_ids.is_empty()
});
self.children_by_parent.remove(&conversation_id);
self.children_by_parent.retain(|_, child_ids| {
child_ids.retain(|child_id| *child_id != conversation_id);
!child_ids.is_empty()
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems valid?

) {
log::warn!(
"Failing TUI child agent request '{}': {message}",
request.name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 [SUGGESTION] [SECURITY] request.name is model/user-controlled, and warn-level logs can become Sentry breadcrumbs in crash-reporting builds; omit it, use a non-sensitive request identifier, or use a safe logging macro with redacted release output.

@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from 6a48d86 to d97af3b Compare July 16, 2026 22:12
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from aa12618 to 34e7925 Compare July 16, 2026 22:12
@harryalbert harryalbert mentioned this pull request Jul 16, 2026
4 tasks
Comment on lines +2158 to +2161
self.children_by_parent.retain(|_, child_ids| {
child_ids.retain(|child_id| *child_id != conversation_id);
!child_ids.is_empty()
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems valid?

Comment thread app/src/ai/llms.rs
/// execution profile. Orchestrated child runs use this because a requested
/// model equal to the profile default must still override the TUI's
/// file-backed model setting.
pub(crate) fn set_agent_mode_llm_override(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like emitting UpdatedActiveAgentModeLLM triggers a bunch of handlers downstream. ooc why do we need this specifically for TUI?

});
let model_for_sessions = model.clone();
ctx.subscribe_to_model(&sessions, move |sessions, event, ctx| match event {
TuiSessionsEvent::SessionAdded(session_id) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is SessionAdded the right trigger for registering entry in the orchestration model? A new session doesn't mean it is being orchestrated / should have child sessions yeah?

) {
let sessions = TuiSessions::handle(ctx);
let (session_id, session_view) =
create_local_terminal_session(&sessions, false, working_directory, ctx);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check if this is true?

history.delete_conversation(*conversation_id, terminal_surface_id, ctx);
});
if let Some(session_id) = self.child_session_by_conversation.remove(conversation_id) {
TuiSessions::handle(ctx).update(ctx, |sessions, ctx| {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the modeling dependency between TuiSessions and the orchestration model is a bit reversed imo

Right now the call graph is:

  1. Orchestration model subscribes to TuiSessions for updates
  2. On update handler result, the orchestration model calls directly into TuiSessions to create / clean up sessions

I think the correct flow should be:

  1. TuiSessions subscribe to Orchestration model for spawning / clean up sessions
  2. On orchestration attempt, TuiSessions call into the orchestration model to register parent / child orchestrations

}

/// Creates and registers a full local terminal session.
pub(crate) fn create_local_terminal_session(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think session creation should be driven by the TuiSessionModel itself

harryalbert and others added 3 commits July 16, 2026 19:09
…essions

The orchestration model subscribes to every session's StartAgentExecutor via
TuiSessions registration, materializes native (Oz) local children into
background sessions (create_agent_task -> shared create_local_terminal_session
helper -> child conversation linkage -> prompt dispatch), and resolves
CLI-harness/remote requests as clean per-child failures instead of spawn
timeouts. Exports the StartAgent executor surface through tui_export and adds
minimal transcript lines for inter-agent messages/lifecycle events
(TODO(code-1822) for richer rendering).

Co-Authored-By: Oz <oz-agent@warp.dev>
Co-Authored-By: Oz <oz-agent@warp.dev>
Co-Authored-By: Oz <oz-agent@warp.dev>
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-multi-session branch from d97af3b to eb8c75e Compare July 16, 2026 23:09
@harryalbert
harryalbert force-pushed the harry/code-1822-tui-local-children branch from 34e7925 to 8179fca Compare July 16, 2026 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants