Repository navigation
Sonny: /goal WHAT asks the agent to set its own goal; /goal shows, /goal clear clears - #397
Conversation
The goal tool sets an agent, or a task it made, a goal: what done looks like and a shell command that exits 0 once it is. The lookout runs the check each time the agent's turn completes; while it fails, the agent gets a turn with its exit code and the end of its output, and its lead hears nothing. The goal ends once the check passes, the budget is spent, two turns it started call no tool, or the agent gives up with blocked, and the lead hears that last turn with how it ended. No judge, nothing in the daemon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
- A check's output is read as it prints, keeping only its end. - A turn's goal is read fresh as the turn ends, so one a lead set or cleared meanwhile counts. - One check at a time per agent; a turn that ends while one runs is plain news. - A task is the caller's by its creator's id, not its name. - A turn waiting for its check keeps the replay cursor before it, so a restarted lookout runs the check again. - The check runs without the provider keys in ~/.agent/env. - The macOS skip is back on the trigger test, not on note_for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Main does not build since #344 renamed Places::plist to Places::job while #318's restamp still called the old name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
- A goal left by an agent deleted while the lookout was away is dropped when another agent with that name ends a turn. - A goal's next turn is not news while it runs, as its end is. - A goal cleared or set again while its check ran is plain news, even when that check passed. - Listing goals shows a lead its tasks' goals, whoever set them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
# Conflicts: # sonny/src/lookout/mod.rs
…e its check ran ends so Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…lay runs again Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…turn is not counted twice Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
The reason an agent gives up for, and a check it set itself, are its own text; the lead reads them in the turn or with the goal tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
… turn waits on one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…ves a later turn's news Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…d a lookout that gives way mid-check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…-kxnbh9 # Conflicts: # sonny/src/served.rs
…-kxnbh9 # Conflicts: # sonny/src/lookout/mod.rs # sonny/src/lookout/updates.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…ched or not Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
… to tell Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…t runs `agent companion add/rm/ls` keeps a command in the store that every daemon on it starts once it listens, restarts with backoff and stops at shutdown. Sonny's lookout and approver now run as companions, so they share the daemon's life cycle whether or not a window is open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…, a removed one waited for, and Sonny's take store and socket from the daemon Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…-kxnbh9 # Conflicts: # src/store/db.rs
…nion's whole group is no client, and a log that is no file fails at once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
…s idle `/goal CHECK` keeps the agent the window shows working until CHECK exits 0 in its folder; `/goal` shows its goal and `/goal clear` clears it. With no turn running, the goal file carries the agent's last turn as `check_after` and the lookout runs the check on its next tick, as if that turn had just completed, so a failing check starts the agent's goal turn at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
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: 2704b1cdd7
ℹ️ 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".
| .ok() | ||
| .and_then(|turns| turns["turns"][0]["turn"].as_i64()), | ||
| false => None, |
There was a problem hiding this comment.
Start idle goals even when the agent has no previous turn
On a newly created agent, /goal CHECK cannot start any work: the turns response is empty, so last becomes None, which is also the value used for an agent whose turn is running. The goal is consequently written without check_after, and Goals::check_now skips it indefinitely until someone submits a separate message. Sonny's create path creates bots without submitting a turn, so this affects setting a goal as the first action in a new window. Represent an idle agent with no history separately from a busy agent and arrange for its initial check to run.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 30a928f. An idle agent with no turns now gets check_after: 0, so None only ever means a turn is running. A new runtime test sets a goal on a fresh agent and checks that it's met at turn 0.
Generated by Claude Code
| let Some(turn) = goal.check_after else { | ||
| continue; | ||
| }; | ||
| checked_now(&self.store, &goal); |
There was a problem hiding this comment.
Preserve pending idle checks until their outcome is durable
If the lookout restarts or reconnects while an immediate check is running, this clears the only durable indication that the check is pending before it finishes. Goals::reconnected aborts outstanding pursuits, and the immediate pursuit has no event cursor (self.pursue(..., None, ...)) to force replay of its historical turn. After reconnection, check_now therefore skips the goal, leaving the idle agent stuck without retrying the check or starting work. Retain a recoverable pending marker until the check completes or its continuation is durably submitted.
AGENTS.md reference: AGENTS.md:L32-L32
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 30a928f. check_now no longer clears check_after when the check starts. Goals::settled clears it once that turn's check has settled, so a lookout that reconnects or restarts mid-check runs the check again. A continuation submitted twice that way carries the same goal-BOT-TURN request id, and the daemon de-duplicates it.
Generated by Claude Code
…rk stays until its check says Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ 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".
| if let Some(goal) = read(&self.store, bot).filter(|goal| goal.check_after == Some(turn)) { | ||
| checked_now(&self.store, &goal); |
There was a problem hiding this comment.
Match settlement to the goal generation before clearing its marker
If /goal replaces an idle agent's goal while its check is running, check_now queues a Settled message for the aborted check and starts the replacement check against the same last turn. When that old message is consumed, settled matches only the bot and turn: it removes the replacement's tracking entry and clears the replacement's check_after, even though its check has not finished. A lookout restart during that replacement check then leaves the goal with neither a pending marker nor an event cursor to replay, so the idle agent never starts working. Carry the goal's set_ms through settlement and require it to match before modifying the active check or durable marker.
AGENTS.md reference: AGENTS.md:L32-L32
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 9e61b79. Settled now carries the checked goal's set_ms. Goals::settled removes a tracked check only when both turn and set_ms match, and clears check_after only on the goal with that set_ms. So the aborted check's settlement no longer touches the replacement's check or its marker.
Generated by Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
George picked the Codex shape: the person's words reach the agent as their message, and the agent sets the goal and its check with its goal tool, whose description now says so. /goal shows the goals of the agent and its tasks, and /goal clear clears its own, through the tool's own call. The person-set check and check-at-once machinery is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGETkBwAL5VmcD7kf5q
* Sonny: a register of every feature it has had, and why (sonny/FEATURES.md) Each Sonny feature added, changed or removed, with its pull requests, status (experimental, shipped, removed), the reason, and a Used? column for the person to fill in. AGENTS.md asks every pull request that touches a Sonny feature to update its row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGETkBwAL5VmcD7kf5q * FEATURES.md: the commons (#392) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGETkBwAL5VmcD7kf5q * FEATURES.md: /goal (#397) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGETkBwAL5VmcD7kf5q * FEATURES.md: #398 and #399 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGETkBwAL5VmcD7kf5q --------- Co-authored-by: Claude <noreply@anthropic.com>
What
A window's
/goal, in the shape Codex's/goalhas:/goal WHATgoes to the agent as the person typed it, a message like any other. The goal tool's description now ends with "A person's message that starts with /goal asks you to set one on yourself for what it says." So the agent sets the goal and picks its check with the tool, and the lookout follows it as it does any goal.sonny acpalready passes such words through as they are./goalshows the goals of the agent and its tasks:goals: lead: done when the tests pass; /goal clear clears this agent's, orno goal; /goal WHAT asks the agent to work until WHAT is done./goal clearclears the agent's own goal, through the tool's ownclearcall.The goal tool's
setstill goes through the approval gate like the agent's other calls; the person's/goalmessage is what the judge sees as their ask.An earlier version of this PR had the person set the check themselves and run it at once. That version is gone, so this PR no longer touches the lookout.
Tests
goal_words_go_to_the_agent_as_typed_and_goal_alone_shows_or_clears(sonny unit test):/goal WHATis submitted verbatim;/goaland/goal clearsubmit nothing; they show the goals and clear them.cargo test -p sonnypasses (470), and clippy is clean.🤖 Generated with Claude Code
https://claude.ai/code/session_013jkdGHCyGRHdT5WYDPKVFY