fix(slack): re-hydrate thread agent overrides after an inline reroute - #8340
fix(slack): re-hydrate thread agent overrides after an inline reroute#8340LuisBrel wants to merge 1 commit into
Conversation
The inline Slack handler hydrates the per-thread override maps under the entry session key. When the thread turns out to be owned by a linked dashboard session, `session_key` is reassigned to that owner and the agent re-resolution immediately after reads `_thread_agents` under a key that was never hydrated. It misses, falls through to the channel or default agent, and the turn runs as the generic default persona -- discarding a binding the session's own metadata already records correctly. Re-hydrate for the new owner in the same breath as the reroute, matching `transport_dispatch._resolve_thread_owner`, which already does exactly this on the transport path. The helper guards repeated I/O per session, so the extra call costs nothing when the key is already hydrated. Refs kirodotdev#2795 -- addresses only the agent-resolution defect on the inline handler. The effective-session context block, and the parallel privacy flag hydration at the same reroute, are deliberately untouched.
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of The fix and its claims verify cleanly against the base: entry hydration at Design-Verdict: CONCERNS Sound minimal fix mirroring the transport sibling, but it patches one of two Watch
Suggestions
[DESIGN-REVIEWED] c913797 |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All claims verified against the base. The fix site, the transport sibling it mirrors, and the entry hydration all read as described. I found one undeclared sibling the author didn't mention. Final review: First-Principles-Verdict: CONCERNS One reassign-then-read site is fixed; the pinned-asker reassignment two lines above has the identical shape and is neither fixed nor declared. What this change shipsIntent: make a Slack turn rerouted into a linked dashboard session run under that session's bound agent instead of the default — a FIX.
No new surface: no config key, flag, or export. The fix reuses the existing Watch
[FIRST-PRINCIPLES-REVIEWED] c913797 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings that block. FINDING — src/kiro_crew/slack/handler.py:3126 — the reroute rehydrates only [OPUS-REVIEWED] c913797 |
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Problem / Motivation
A Slack turn in a thread that is linked to a dashboard session runs under the generic default agent instead of the agent that session is bound to. The binding is persisted correctly in the session's own conversation-log metadata; the turn just never reads it, so the user's chosen agent is silently ignored and they get the default persona's answer.
Why it matters
An agent binding is the whole point of binding a session to an agent. Silently substituting the default persona is worse than an error: the reply looks plausible, so the user has no signal that their configured agent — its tools, its instructions, its model — was never used. Anyone driving a bound dashboard session from Slack is hit on the first reroute in a gateway process.
What changed (motivation → approach → change)
Symptom — a rerouted Slack turn resolves to the default agent.
Root cause — the per-thread override maps are keyed BY SESSION.
handle_messagehydrates them once at entry, under the pre-link session key derived from the Slack thread timestamp. When the thread turns out to be owned by a linked dashboard session, the reroute reassignssession_key = thread_owner_key, and the agent re-resolution on the very next lines reads_thread_agents.get(session_key)under a key that was never hydrated. It misses, and falls through tochannel_agent or _get_default_agent().Change — re-hydrate for the new owner in the same breath as the reroute, which is exactly what the sibling transport path already does:
transport_dispatch._resolve_thread_ownercalls_hydrate_thread_overrides(session_key, conversation_log)immediately after its ownsession_key = owner, with a comment explaining why it belongs inside the reroute rather than beside it. Rather than invent a second mechanism, this mirrors that one call._hydrate_thread_overridesguards repeated I/O per session, so it costs nothing when the key is already hydrated.Tests
test/test_slack_handler.py::TestPerThreadAgent::test_reroute_to_a_linked_session_keeps_its_agent— drives the realhandle_messagewith a thread index that resolves the thread to a dashboard session key, and a conversation log holding that session's persisted agent. It asserts the observable outcome, the agent the turn is acquired with, not the internals.Written first and confirmed failing for the right reason on the unfixed code — the reroute assertion passed, so the test was exercising the intended branch, while the agent came back
None:After the fix,
185 passedfor that file, and742 passedacross the 14 files covering this path (test_slack_handler.py,test_slack_thread_session_binding.py,test_slack_transport_dispatch.py, the three handler-coverage files,test_thread_override_persistence.py,test_slack_agent_passthrough.py,test_slack_dashboard_live_sync.py,test_close_guard_parity.py,test_slack_options_lifecycle.py,test_slack_mirror_unlink.py,test_chat_slack.py,test_slack_golden_transcript.py).scripts/check_black_formatting.py,isort --check-only,flake8andmypyall pass on the two changed files.Manual verification
N/A — unit coverage sufficient. The test drives the real
handle_messagethrough the real reroute branch, so the assertion sits on the same code path a live Slack reply takes; the only doubles are the session manager and the conversation log, which is how every existing test in this file exercises the handler.Why no screenshot: this is one inserted call inside the Slack inbound handler plus a regression test. There is no UI surface in the diff — the only observable difference is which agent a rerouted turn runs under, which is precisely what the test asserts.
Related Issues
Refs #2795 — deliberately not
Closes.That issue bundles two defects and this PR addresses only the second: the agent binding lost on an inline-handler reroute. Left out on purpose:
_hydrate_conv_flagsalso runs only under the entry key. The transport sibling re-runs both, but the inline path has its own flag-hydration ordering, and widening into it here would fold a second concern into a one-line fix.Pattern harvest
Rule candidate: review-prompt
Pattern: a cache hydrated once at entry, then read after the key it is indexed by has been reassigned — the lookup misses silently and falls through to a default instead of failing.
Checklist