Repository navigation
fix(agents): report a conversation wake that fails before its turn starts - #2192
Conversation
…arts A timer or watch that delivers to a conversation can fail before its turn reaches the harness: the agent's protection is unavailable, its start is refused, its root is not in the channel, or the handover times out. The bundled harnesses report failures only inside their own handoff, so this path reached only console.error, which the packaged app does not keep. The occurrence was spent with no trace. The runner now posts a⚠️ notice in that conversation that mentions the owner, once until one of the wake's turns starts again. It is not sent when the agent stopped, the wake is no longer current, or the agent is not a member there. A refused root is not replied to. The occurrence is still used, not retried. Co-authored-by: Opus 5.5 Max <d9fc2aa400ad81a2f289921a1720811e5d922c43e079f37241d4be458d8d92aa@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Riley Crane <[email protected]>
6e43b8f to
eb1d64f
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Looks good. I traced the new notice against the claude-code, goose and pi handoffs: none of them posts its own error and then rejects run, so this can't double up with a harness report. A protection failure still gets its notice, since publish doesn't go through the protection check. One nonblocking suggestion inline.
| private readonly failedSources = new WeakSet<AttentionObject>(); | ||
| /** Wakes whose failure to start was reported, until one of their turns | ||
| * starts again, so a failing repeat reports once. */ | ||
| private readonly reportedWakes = new Set<string>(); |
There was a problem hiding this comment.
🤖 Nonblocking: keying this on ${agent.key}\0${slug} lets the dedupe outlive the timer it was about. setAttention writes a new AttentionObject on every edit or rearm, but the slug stays the same. So this sequence stays silent: a one-shot timer fails and the owner gets a notice, the owner fixes its root or rearms it, and it fails again before any turn starts. That's the recovery the notice points people to, and it ends up losing the occurrence the same way main does. The set also never loses entries for timers that are removed while they're still failing.
If you key it on job.matched.object in a WeakSet<AttentionObject>, the same way failedSources works, a repeating timer still reports once, since its object stays the same across occurrences. Any edit or rearm resets it, and entries go away with the object. object.slug and object.value.type would also give you what/slug for the message, so you could drop the trigger.type narrowing in reported.
…when-ready * origin/main: (21 commits) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) perf: count member access once and scope find highlights to painting (#3464) ...
…n-on-github * origin/main: (21 commits) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) perf: count member access once and scope find highlights to painting (#3464) ...
* origin/main: (31 commits) Let plugin panels name their tab with panels.useTabTitle (#3251) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) fix(solo): keep navigation responsive during chat deletion (#3488) fix(channels): keep custom thread tab names until the tab closes (#3458) perf(relay): verify cached history in the native host (#3426) fix(settings): refine claude and typesafe key setup dialogs (#3391) Update bundled help for Bestie and agent settings (#3194) feat(profiles): share and show a local time zone (#3332) Revert "feat(link-previews): unfurl web links below messages by default" (#3480) Show disabled plugin chats in Solo (#3456) perf(messages): host one context menu per message list (#3465) test(claude): keep the quit-release test copy above the minimum version (#3477) fix(profiles): restore verified names from the device cache (#3382) fix(mentions): show addressed agents first in Solo audience picker (#2856) ...
…ve-client * origin/main: feat(solo): smooth chat recency reordering (#3502) Reject self-mentions that cannot wake another session (#3513) feat(status): offer don't clear for every status (#3515) Let plugin panels name their tab with panels.useTabTitle (#3251) fix(agents): report a conversation wake that fails before its turn starts (#2192) fix(usage): read Claude transcripts over 50 MB a page at a time (#3498) feat(agents): preview where interrupted agents were working before resume (#3494) perf(messages): close the editor and delete dialog on submit (#3493) feat(messages): quote selected message text into the composer (#2392) perf(toast): scope docked clearance to the toast host (#3489) fix(unread): undo exact captured ordinary reads conditionally (#3473) Signed-off-by: Philip Azar <[email protected]>
…y-unreads * origin/main: (24 commits) [1/2] feat(agents): add a recommended agent setup (core) (#3344) test(browser): survive the live-stream restart a page-clock jump causes (#3442) chore(renovate): hold rusqlite below 0.41 for mesh-llm (#3438) test(browser): serve the diffs and CLI reload fixtures outside the navigation budget (#3445) test(browser): play the draft clip at rate 0 instead of looping it before the Play click (#3446) fix(status): reopen a status whose preset deadlines coincide as Today (#3449) fix(messages): tolerate a cancelled reaction-name chunk load (#3450) fix(pairing): tolerate /proc/net/tcp rows printed twice or missed (#3451) test(browser): wait on the signal each test asserts in local-agent-activity and message-navigation (#3444) Expose native channel headings to Solo sources (#3523) test(browser-host): run the 100,000-channel broker scale check alone in the measurements lane (#3447) fix(solo): keep every contributed destination action (#3516) fix(identity): report an issuer that keeps rate-limiting an agent as a limit (#2365) fix(identity): let a stuck at-launch sign-in copy its link (#2453) fix(profiles): open avatar previews on hover in compact density (#3510) feat(solo): smooth chat recency reordering (#3502) Reject self-mentions that cannot wake another session (#3513) feat(status): offer don't clear for every status (#3515) Let plugin panels name their tab with panels.useTabTitle (#3251) fix(agents): report a conversation wake that fails before its turn starts (#2192) ... # Conflicts: # tests/browser/message-management.spec.mjs
Problem
A timer or watch that delivers to a conversation can fail before its turn ever reaches the harness. Causes include:
host.startrefusesThe bundled harnesses report failures only inside their own handoff (
report()in claude-code, goose and pi). This earlier path reaches onlyconsole.error("Agent run failed")inAgentRunners.start(), and the packaged app doesn't keep that.I hit this on a one-shot Sunday timer in Foundation 0.7.0-preview.218.1:
lastWokeAtrecorded,used: 1)The occurrence was lost with no trace anywhere. A 60s repro timer in the same DM ran end to end, so the cause was intermittent. I couldn't confirm it after the fact, which is the point of this change.
Change
AgentRunners.start()now handles a non-addressed wake (timer, watch, catch-up or source) with a conversation destination whosecompletionrejects. The agent posts this in that conversation, mentioning its owner:The notice is skipped when:
A root refused as "not in its channel" isn't replied to. The notice goes to the channel level instead.
docs/buzz-help.mdgets one sentence about the notice.Not changing
soloChathere, so I left that case alone.waitset. A rejectedhandedis only warned about, andrunresolves, so that failure stays silent. That's a gap inside the harness, not this path.Tests
src/features/agents/service.test.ts,describe("a timer turn that fails before it starts"), 4 cases:etagAll 4 fail on main. Removing just the dedupe fails the repeat case, and removing just the refused-root handling fails that case.
Validation on eb1d64f (rebased onto main
8aa3adf35):8aa3adf35pnpm check(biome, typecheck, design checks) is cleanvitest run: 21803 passed, 10 failedTwo of the failures also fail on clean main
8aa3adf35on this machine:browser-host/media-preparation.integration.test.mjs(tiled HEICdimensions) and
browser-host/vite-config.test.mjs(aspawnSyncerror).The other 8 are timeouts in
entity-navigation,DraftPreview,MessageComposerandstore-discovery, none of them under agents. Thatrun shared the machine with a full main run. Rerun alone on this branch,
those 4 files pass, 770 of 770.
How to test
just dev, enable Attention on an agent, and give it a channel you're both in.rootthat isn't in that channel. Ask the agent to setinterval_secs: 60androot: <any 64-hex id>withchannel: <the channel>.max_occurrences: 3, you should still see only one notice.🤖 Generated with Claude Code