Skip to content

Commit b3cc26f

Browse files
committed
fix(slack): instruct gateway runtimes to reply over acp
1 parent a10aae8 commit b3cc26f

3 files changed

Lines changed: 26 additions & 4 deletions

File tree

docs/2026-04-26-channel-delivery-feedback-architecture.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,19 @@ protocol. In gateway-routed mode, it should not be given direct provider-channel
256256
send tools for Slack, Discord, Teams, or any future chat provider where Spritz
257257
owns delivery.
258258

259+
Gateway prompts should carry the same boundary as an explicit runtime
260+
instruction:
261+
262+
```text
263+
Spritz channel gateway will deliver your visible reply. Reply by returning
264+
normal assistant text over ACP. Do not call Slack, Discord, Teams, or other
265+
provider-channel send tools.
266+
```
267+
268+
This instruction is a guardrail, not the only enforcement layer. Runtime config
269+
must still hide or disable direct provider-channel send tools in gateway-routed
270+
deployments.
271+
259272
Spritz maps the typed runtime outcome to provider delivery behavior:
260273

261274
```json

integrations/slack-gateway/gateway_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,9 @@ func TestSlackEventRoutesToConversationAndReplies(t *testing.T) {
29112911
if !strings.Contains(text, "\"actor_user_id\":\"U_1\"") {
29122912
t.Fatalf("expected actor metadata in prompt text, got %q", text)
29132913
}
2914+
if !strings.Contains(text, "Reply by returning normal assistant text over ACP") {
2915+
t.Fatalf("expected ACP reply instruction in prompt text, got %q", text)
2916+
}
29142917
if !strings.HasSuffix(text, "\n\nhello") {
29152918
t.Fatalf("expected normalized prompt body after metadata block, got %q", text)
29162919
}
@@ -3752,6 +3755,9 @@ func TestBuildSlackPromptTextPrependsTrustedContext(t *testing.T) {
37523755
if payload["direct_message"] != false {
37533756
t.Fatalf("expected non-DM metadata, got %#v", payload["direct_message"])
37543757
}
3758+
if !strings.Contains(prompt, "Reply by returning normal assistant text over ACP") {
3759+
t.Fatalf("expected ACP reply instruction in prompt, got %q", prompt)
3760+
}
37553761
if !strings.HasSuffix(prompt, "\n\ncreate a zeno for me") {
37563762
t.Fatalf("expected normalized user text after metadata block, got %q", prompt)
37573763
}

integrations/slack-gateway/slack_events.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import (
1818
)
1919

2020
const (
21-
slackRecoveryStatusText = "Still waking up. I will continue here shortly."
22-
slackRecoveryFailureText = "I could not recover the channel runtime. Please try again."
23-
slackAckReactionTimeout = 2 * time.Second
21+
slackRecoveryStatusText = "Still waking up. I will continue here shortly."
22+
slackRecoveryFailureText = "I could not recover the channel runtime. Please try again."
23+
slackAckReactionTimeout = 2 * time.Second
24+
slackGatewayACPReplyInstruction = "Spritz channel gateway will deliver your visible reply. Reply by returning normal assistant text over ACP. Do not call Slack, Discord, Teams, or other provider-channel send tools."
2425
)
2526

2627
var slackMentionTokenPattern = regexp.MustCompile(`<@[^>]+>`)
@@ -1094,7 +1095,9 @@ func buildSlackPromptTextWithSynthetic(teamID string, event slackEventInner, bot
10941095
if err != nil {
10951096
return normalized
10961097
}
1097-
return "<spritz-channel-context>" + string(payload) + "</spritz-channel-context>\n\n" + normalized
1098+
return "<spritz-channel-context>" + string(payload) + "</spritz-channel-context>\n\n" +
1099+
"<spritz-channel-delivery-instructions>" + slackGatewayACPReplyInstruction + "</spritz-channel-delivery-instructions>\n\n" +
1100+
normalized
10981101
}
10991102

11001103
func slackReplyThreadTS(event slackEventInner) string {

0 commit comments

Comments
 (0)