Repository navigation
sonny acp: a task opens as the lead thread's child in T3 Code - #352
Conversation
Sonny run in a cmux tab now makes the project's cmux workspace its home, as it does a Herdr workspace or a Rex session: each task the lead starts gets a tab in the lead's pane running Sonny on it, titled with the bot's name; run_in_pane and show_agent open tabs and splits there; another project's lead gets a workspace of its own. Every call goes through `cmux rpc` (system.tree, workspace.create, surface.create, surface.split, surface.close, tab.action), and the workspace is kept in ~/.agent/sonny/places so the lookout's calls land in it. cmux already reads the OSC 7501 status and OSC 777 notifications Sonny writes, so its sidebar shows each task's state and rings the tab that needs the person. Each Sonny also gives its tab a resume command (`cmux surface resume set`, `Sonny: NAME`), so cmux reopens it after a relaunch once the person allows `sonny` commands; quitting clears it. Agents get a cmux-tabs skill like herdr-panes, and the daemon keeps a cmux tab's own place variables (CMUX_WORKSPACE_ID, CMUX_SURFACE_ID, CMUX_TAB_ID, CMUX_PANEL_ID) out of its tools' shells, as it does Herdr's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
T3 Code runs `sonny acp` as an ACP provider, a process per thread. A thread is its folder's lead, set up as `sonny` sets it up (rules, skills, tools, approval mode and sandbox, the lookout). Turns and calls become the thread's updates; a call waiting for the person is asked with session/request_permission; turns the lead takes on its own (the lookout waking it) show between prompts, as ACP v2 work the agent started. Each task the lead starts opens as a T3 thread of its own via T3's t3_thread_launch tool, and that thread binds to the task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
Agents' shells run in the daemon, which cmux did not start, so cmux's default "cmux processes only" mode refuses them. The skill and README now name the setting that lets them in, and say a workspace the person asks for is a project's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
… Claude's tool results - A call whose event carried only the start of its arguments is read from history, so a long start_task still opens the task's thread. - A thread that comes to a bot already waiting for the person is asked for those calls. - A task thread claims the task waiting for its message in its folder; the same brief twice in one folder is told apart by the task's name. - A resumed thread Sonny has no binding for is refused, not made a lead. - History items go through `responses`, so Anthropic-family bots' tool results show and their start_task opens threads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
…tream drops - A call listed with its arguments cut is shown whole, read from the node that planned it, before the person may allow it; when that read fails, the person may only deny it. - When the daemon lets a thread's stream go (follow_lagged, or the connection ends), the thread follows its bot again, answers a prompt whose turn ended meanwhile, and asks for calls waiting on the person; a prompt is failed rather than left waiting while the daemon is away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
…mes them, services on resume, no missed events, one follower - A cancel that comes before the prompt's turn is known is kept: the prompt is not sent, or its turn is interrupted once it is. - A task named with its project already (`calc.fix`) is found as `start_task` names it, not as `calc.calc.fix`. - A thread that comes back to a saved bot starts what it needs: the lookout, the lead's subscription, and the judge for an `auto` gate. - A thread whose stream dropped follows on from the last event it handled, so what happened meanwhile (a task started) is handled too. - Closing or replacing a thread stops its follower. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
…y approval page, answers on the live connection, steers by their own turn - Binding to a running bot keeps the running turn's started calls from the replay, so a start_task finishing after the thread came opens its thread. - A reply whose stream dropped partway sends the rest from the kept message instead of stopping at what streamed. - Approvals are listed page by page. - A permission answer goes through the thread's connection as it is when the person chooses; one not delivered is asked again on reattach. - A prompt that steers waits on the turn the daemon names for it, and on the turn that takes it in once steered; a turn finishing before then no longer answers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
The streamed prefix is settled only by the assistant's words, so a model that reasons before it answers no longer has its reply said twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
The claimed prompt is answered here only while it is still this thread's and waits on no turn; one the follower already answered is left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
T3 Code sends every prompt as its instructions around the person's words, then a note of its own (<runtime_info>, <pull_request_linking>) as a second text block. Joined, the prompt no longer ended in </user_request>, so nothing was unwrapped: a task's thread could not claim its task (its brief no longer matched) and opened a lead for the worktree instead, failing with project_exists; leads read T3 Code's instructions for tools they do not have. Each block is now unwrapped on its own and T3 Code's note is left out. The runtime tests send T3 Code's real two-block shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
…lead session/new and session/resume return a model option (category model) built from ~/.agent/models, with Default first. A pick before the first prompt is the model a new lead starts on; a later pick configures the bot when it is idle, or before its next turn. A bot's thread offers only its provider family, refuses another with -32602, and shows the bot's model again through config_option_update when another client changes it. A task's thread keeps the model its lead gave the task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
… next idle Model changes hold the session's switching lock, so overlapping picks cannot drop a configure that succeeded. A pick made while the bot works is applied when its turn finishes, not only before the person's next prompt, so the lookout's turns run it too. The thread refreshes the bot's model from inspect when a turn ends, rather than taking a turn's model, which may be that turn's own override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
While the person's prompt runs, the task a lead starts opens through T3 Code's delegate_task, as a child thread nested under the lead's, in the lead's folder (the task works in its own worktree). The prompt's answer waits until the child is open, so T3 Code's run is still on. A turn the lead takes on its own has no run to be a child of: delegate_task refuses, and the task opens with t3_thread_launch as before. T3 Code's message that a delegated task ended starts no turn; the lookout tells the lead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 084c19a701
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
|
The Generated by Claude Code |
Answers held while a task's thread opens are kept in a list, so a second prompt whose turn ends meanwhile cannot drop the first. The hold ends after 30 s whatever the opening does, fallback launch included; a thread still opening opens on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
… last The answer now waits for the task's thread, whose "Opened" update can come after the idle one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
George picked "tasks as children of the lead" for how Sonny's tasks show up in T3 Code. Until now, each task opened as its own top-level thread through
t3_thread_launch.What changes
delegate_taskwith{task: brief, title, clientRequestId: bot}. T3 makes it a child thread, nested under the lead's thread.parent_not_active. Sonny then falls back tot3_thread_launchin the task's worktree, as before. That covers any T3 that doesn't offerdelegate_tasktoo.end_turnwithout starting a lead turn. The lead already hears of its tasks from the lookout.Tests
test_a_thread_is_its_folders_lead_…:test_a_prompt_that_steers_…: whendelegate_taskrefuses, the task falls back tot3_thread_launchin the worktree.t3::is_completion.tests.test_acp, thesonnyunit tests, clippy and fmt all pass locally.No screenshot yet: the nesting shows in T3's sidebar, which I'll check on George's Mac once this is installed.
Generated by Claude Code