Repository navigation
Sonny: fix main's build; restamp edits a systemd trigger too - #351
Merged
Merged
Conversation
#318's restamp still read the definition through Places::plist, which #344 renamed to Places::job, so main did not compile. The store id it rewrites now has a unit form too, and restamp runs at start on Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGF2ShRt6WjfWgiPdZF
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. |
lydakis
pushed a commit
that referenced
this pull request
Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d
lydakis
added a commit
that referenced
this pull request
Oct 29, 2026
…309) Bots take `unsandboxed`, command prefixes (`mcpx`, `docker compose`) that run outside their sandbox without asking, when a plain script runs nothing else. Sonny reads `[sonny] unsandboxed` from ~/.agent/config.toml for its leads and tasks, and in `full` mode tasks run unconfined, as their lead does. Carries #351's fix for main's build (restamp uses Places::job). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHN4UHiWFxnggwKHoV
lydakis
added a commit
that referenced
this pull request
Oct 29, 2026
* Lay Sonny's work out in cmux 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 * Leave a Sonny in a Herdr pane to Herdr when cmux hosts Herdr Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * Sonny: `sonny acp`, Sonny as a T3 Code provider 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 * cmux-tabs: point agents at cmux's Automation mode when it refuses them 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 * sonny acp: whole call arguments, waiting approvals, claims by folder, 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 * sonny acp: approve only what was shown whole; follow again when the stream 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 * sonny acp: stop before the turn is known, task names as start_task names 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 * sonny acp: calls in flight on bind, the rest of a dropped reply, every 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 * sonny acp: a reasoning item leaves the streamed reply as it is 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 * sonny acp: a task thread that came as its turn ended answers once 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 * sonny acp: read the person's words out of each block T3 Code sends 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 * sonny acp: T3 Code's model picker lists your models and switches the 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 * sonny acp: one model change at a time, and a pick waits for the bot's 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 * sonny acp: a task opens as the lead thread's child in T3 Code 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 * Port #351's fix for main's build (trigger restamp) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * sonny acp: every held prompt is answered, and none waits past 30 s 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 * test_acp: the steered prompt's idle comes before its held answer, not 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 --------- Co-authored-by: Claude <noreply@anthropic.com>
lydakis
added a commit
that referenced
this pull request
Oct 29, 2026
* Lay Sonny's work out in cmux 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 * Leave a Sonny in a Herdr pane to Herdr when cmux hosts Herdr Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * Sonny: `sonny acp`, Sonny as a T3 Code provider 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 * cmux-tabs: point agents at cmux's Automation mode when it refuses them 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 * sonny acp: whole call arguments, waiting approvals, claims by folder, 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 * sonny acp: approve only what was shown whole; follow again when the stream 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 * sonny acp: stop before the turn is known, task names as start_task names 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 * sonny acp: calls in flight on bind, the rest of a dropped reply, every 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 * sonny acp: a reasoning item leaves the streamed reply as it is 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 * sonny acp: a task thread that came as its turn ended answers once 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 * sonny acp: read the person's words out of each block T3 Code sends 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 * sonny acp: T3 Code's model picker lists your models and switches the 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 * sonny acp: one model change at a time, and a pick waits for the bot's 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 * sonny acp: a task opens as the lead thread's child in T3 Code 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 * Port #351's fix for main's build (trigger restamp) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * sonny acp: every held prompt is answered, and none waits past 30 s 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 * test_acp: the steered prompt's idle comes before its held answer, not 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 * T3 Code opens Sonny threads again: the model option carries `id` T3 Code finds the model option by `id` even on protocol version 2, whose schema names it `configId`. Since the model picker landed, Sonny sent only `configId` on version 2, so T3 threw opening every session: no prompt reached Sonny, and the picker probe failed to "Default". The option now carries both names, and a pick may name it by either. A failed daemon connection is no longer kept for the process's life, so a daemon restarted under a running T3 is reached on the next thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d --------- Co-authored-by: Claude <noreply@anthropic.com>
lydakis
added a commit
that referenced
this pull request
Oct 29, 2026
…ead leaves the lead's model alone (#363) * Lay Sonny's work out in cmux 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 * Leave a Sonny in a Herdr pane to Herdr when cmux hosts Herdr Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * Sonny: `sonny acp`, Sonny as a T3 Code provider 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 * cmux-tabs: point agents at cmux's Automation mode when it refuses them 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 * sonny acp: whole call arguments, waiting approvals, claims by folder, 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 * sonny acp: approve only what was shown whole; follow again when the stream 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 * sonny acp: stop before the turn is known, task names as start_task names 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 * sonny acp: calls in flight on bind, the rest of a dropped reply, every 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 * sonny acp: a reasoning item leaves the streamed reply as it is 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 * sonny acp: a task thread that came as its turn ended answers once 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 * sonny acp: read the person's words out of each block T3 Code sends 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 * sonny acp: T3 Code's model picker lists your models and switches the 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 * sonny acp: one model change at a time, and a pick waits for the bot's 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 * sonny acp: a task opens as the lead thread's child in T3 Code 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 * Port #351's fix for main's build (trigger restamp) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * sonny acp: every held prompt is answered, and none waits past 30 s 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 * test_acp: the steered prompt's idle comes before its held answer, not 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 * T3 Code opens Sonny threads again: the model option carries `id` T3 Code finds the model option by `id` even on protocol version 2, whose schema names it `configId`. Since the model picker landed, Sonny sent only `configId` on version 2, so T3 threw opening every session: no prompt reached Sonny, and the picker probe failed to "Default". The option now carries both names, and a pick may name it by either. A failed daemon connection is no longer kept for the process's life, so a daemon restarted under a running T3 is reached on the next thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * T3 Code: models as /model lists them, an effort picker, and a new thread leaves the lead's model alone - The model picker lists what the providers list now, as Sonny's /model does, with ~/.agent/models' notes and the models only it names; a listing that takes over five seconds leaves the file's. A file written before a model came out no longer hides it. - An Effort option (category thought_level) beside the model: the bot's family's efforts, taken at once when idle and with the next turn when not, as a model is. - A pick made before a thread has a bot is for a lead Sonny makes. T3 Code puts its last pick to every thread it opens, so a new thread in a folder whose lead was there already moved that lead, every other thread's too, to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d --------- Co-authored-by: Claude <noreply@anthropic.com>
lydakis
added a commit
that referenced
this pull request
Oct 29, 2026
* Lay Sonny's work out in cmux 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 * Leave a Sonny in a Herdr pane to Herdr when cmux hosts Herdr Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * Sonny: `sonny acp`, Sonny as a T3 Code provider 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 * cmux-tabs: point agents at cmux's Automation mode when it refuses them 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 * sonny acp: whole call arguments, waiting approvals, claims by folder, 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 * sonny acp: approve only what was shown whole; follow again when the stream 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 * sonny acp: stop before the turn is known, task names as start_task names 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 * sonny acp: calls in flight on bind, the rest of a dropped reply, every 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 * sonny acp: a reasoning item leaves the streamed reply as it is 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 * sonny acp: a task thread that came as its turn ended answers once 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 * sonny acp: read the person's words out of each block T3 Code sends 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 * sonny acp: T3 Code's model picker lists your models and switches the 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 * sonny acp: one model change at a time, and a pick waits for the bot's 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 * sonny acp: a task opens as the lead thread's child in T3 Code 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 * Port #351's fix for main's build (trigger restamp) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * sonny acp: every held prompt is answered, and none waits past 30 s 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 * test_acp: the steered prompt's idle comes before its held answer, not 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 * T3 Code opens Sonny threads again: the model option carries `id` T3 Code finds the model option by `id` even on protocol version 2, whose schema names it `configId`. Since the model picker landed, Sonny sent only `configId` on version 2, so T3 threw opening every session: no prompt reached Sonny, and the picker probe failed to "Default". The option now carries both names, and a pick may name it by either. A failed daemon connection is no longer kept for the process's life, so a daemon restarted under a running T3 is reached on the next thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * T3 Code: models as /model lists them, an effort picker, and a new thread leaves the lead's model alone - The model picker lists what the providers list now, as Sonny's /model does, with ~/.agent/models' notes and the models only it names; a listing that takes over five seconds leaves the file's. A file written before a model came out no longer hides it. - An Effort option (category thought_level) beside the model: the bot's family's efforts, taken at once when idle and with the next turn when not, as a model is. - A pick made before a thread has a bot is for a lead Sonny makes. T3 Code puts its last pick to every thread it opens, so a new thread in a folder whose lead was there already moved that lead, every other thread's too, to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d * sonny README: T3 Code's pickers as #363 left them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGFMvxuKK3qzEb4j48d --------- Co-authored-by: Claude <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Main has not compiled since #318 and #344 landed together.
restamp(from #318) read the definition throughPlaces::plist, and #344 renamed that toPlaces::job(E0599).restampnow usesplaces.job(...)."--store-id" "ID"pair inExecStart=. The plist's<string>ID</string>edit does not match unit text. On a plist the edit is unchanged.restamp_at_startno longer returns early off macOS, and it loads throughtrigger::schedule. This matchesrefreshafter Sonny: triggers on Linux, kept by systemd's user manager #344.cargo test -p sonny trigger(39 pass),cargo clippy -p sonny --all-targets -D warningsandcargo fmt --checkare clean.Generated by Claude Code