From de1342f7fedaf31a91c62262d9190f8a28e9ff82 Mon Sep 17 00:00:00 2001 From: Eric Jacksch Date: Fri, 20 Mar 2026 02:58:26 -0400 Subject: [PATCH 1/2] fix(cron): set peer on inbound message so channel bindings route correctly Cron jobs fired via ProcessDirectWithChannel had no Peer set on the InboundMessage, so the route resolver never matched peer-based bindings and always fell through to the default agent. Setting Peer{Kind: "channel", ID: chatID} when a real chatID is present means a cron job targeted at a specific Slack channel ID will now be routed to whichever agent has a matching peer binding for that channel, consistent with how live inbound messages are routed. Co-Authored-By: Claude Sonnet 4.6 --- pkg/agent/loop.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index c4479434f..b05f02c6e 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -679,6 +679,12 @@ func (al *AgentLoop) ProcessDirectWithChannel( Content: content, SessionKey: sessionKey, } + // Set peer so channel-based bindings (e.g. a specific Slack channel mapped + // to a named agent) are matched by the route resolver, exactly as they are + // for live inbound messages. + if chatID != "" && chatID != "direct" { + msg.Peer = bus.Peer{Kind: "channel", ID: chatID} + } return al.processMessage(ctx, msg) } From 9cff725226e34f67c271812f36e84b4f98c305df Mon Sep 17 00:00:00 2001 From: Eric Jacksch Date: Fri, 20 Mar 2026 10:46:12 -0400 Subject: [PATCH 2/2] fix(cron): publish agent response to bus after ProcessDirectWithChannel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a cron job with deliver=false runs, the agent response was silently discarded with a misleading comment "Will be sent by AgentLoop". The Run loop is never involved in cron execution — ProcessDirectWithChannel bypasses it entirely. As a result, Karen's response to scheduled tasks was computed but never delivered to Slack (or any other channel). Fix: explicitly publish the response via msgBus.PublishOutbound after ProcessDirectWithChannel returns, consistent with how command and deliver=true jobs already handle their output. Co-Authored-By: Claude Sonnet 4.6 --- pkg/tools/cron.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/tools/cron.go b/pkg/tools/cron.go index 648cc3c6c..1c6812f81 100644 --- a/pkg/tools/cron.go +++ b/pkg/tools/cron.go @@ -341,7 +341,14 @@ func (t *CronTool) ExecuteJob(ctx context.Context, job *cron.CronJob) string { return fmt.Sprintf("Error: %v", err) } - // Response is automatically sent via MessageBus by AgentLoop - _ = response // Will be sent by AgentLoop + if response != "" { + pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer pubCancel() + t.msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{ + Channel: channel, + ChatID: chatID, + Content: response, + }) + } return "ok" }