fix(decisions): make dispatch_decision work (agent-by-id + channel inference)#207
Merged
Merged
Conversation
handle_create_decision looked up the agent with get_agent(name) while the
bridge addresses the callback as /internal/agent/{agent_id}/decisions with
the agent's UUID id (agents are keyed by id since #142). The id never
matched the name column, so every real agent's dispatch_decision 500'd with
"agent not found" — the resume path in the same file already used
get_agent_by_id. Switch the create path to get_agent_by_id.
The existing test masked it: its fixture seeds an agent whose name == id
("bot1"), so the by-name lookup happened to match. Added a regression test
with a real UUID id != name (verified it 404s without the fix).
Found by dogfooding the reviewer loop's escalation path (#206): a reviewer
agent correctly chose to escalate via dispatch_decision and hit this.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…race lookup
dispatch_decision inferred its inbox channel from `lifecycle.run_channel_id`,
backed by AgentTraceStore's in-memory agent-scoped channel. But `set_run_channel`
had no non-test caller, so the lookup always returned None and every escalation
400'd ("no active-run channel"). The Decision Inbox never worked in production;
tests passed only because the mock lifecycle set the channel by hand. Bug #1
(agent-by-name) failed earlier in the same handler and masked this.
The channel is display context (inbox label) only — delivery is agent-scoped
(resume_with_prompt), scoping is by workspace_id — so resolve it from durable
reads: claimed-task sub-channel, else current-run channel, else last chat
channel, else an honest 400. Task work drives status without a run-stamped
chat, so the claimed task's sub-channel is the reliable origin for a task
agent that escalates before chatting (the reviewer-loop case the dogfood hit).
Delete the dead set_run_channel / run_channel_id from the AgentLifecycle trait,
AgentManager, and the test mock — the trap that hid this. Add a regression test
(decision_channel_resolves_from_claimed_task) exercising the production path
with no mock channel; rework the existing decision tests off set_run_channel.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CHANGELOG: dispatch_decision now reaches the Decision Inbox (#207) - VERSION: 0.18.0 -> 0.18.1 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Makes
dispatch_decisionactually work end-to-end. Two distinct root causes, both found via live dogfood of the reviewer loop (#206), where a reviewer agent's escalation failed. They sit in the same handler and block the same goal, so they land together.Bug #1 — agent resolved by name, not id
handle_create_decisionlooked up the agent withget_agent(name), but the bridge addresses the callback as/internal/agent/{agent_id}/decisionswith the UUID id (agents are id-keyed since #142). Every real agent 404'd ("agent not found"); only test fixtures whose name equaled their id matched. Fixed toget_agent_by_id.Bug #2 — channel inferred from dead code
With #1 fixed, the next guard always failed: the decision's channel came from
lifecycle.run_channel_id, backed by an in-memory agent-scoped value set only byset_run_channel— which has no non-test caller. So in production it was alwaysNoneand every escalation 400'd. The Decision Inbox never worked; tests passed only because the mock lifecycle set the channel by hand.The channel is display context only (an inbox label) — delivery is agent-scoped (
resume_with_prompt), inbox scoping is byworkspace_id. So it's now resolved from durable reads, most-specific first:Deleted the dead
set_run_channel/run_channel_idfrom theAgentLifecycletrait,AgentManager, and the test mock — the trap that hid bug #2.Tests
decision_channel_resolves_from_claimed_task: a silent task agent (claims a task, never chats) escalates → decision attributed to the task's sub-channel. Exercises the production path with no mock channel.set_run_channelto use real channel sources.Verification
cargo test(lib 576, server_tests 89 incl. 7 decision tests) green ·cargo clippy --all-targets -- -D warningsclean ·cargo fmt --checkclean.🤖 Generated with Claude Code