Summary
compose_inbound_wakeup_prompt in the host branches on adapter_type == "slack" and appends Slack-specific prompt text, reading two metadata keys (dmTarget, isActiveThread) that the Slack worker invents. This puts protocol-specific behavior in the host, coupled to the worker by untyped string keys across a language boundary.
The forthcoming architecture doc in #171 states the rule this breaks:
The host owns supervision, routing, durable message records, retries, and conversation wakeups. Protocol-specific behavior stays in each worker.
Where
Host side — crates/executor/src/adapter/runtime.rs:910-925:
if config.adapter_type == "slack" {
if let Some(dm_target) = metadata.get("dmTarget").and_then(|value| value.as_str()) {
prompt.push_str(&format!(
"\n\nSlack sender DM target: `{dm_target}`. Use this only for appropriate private follow-up; do not use DM to bypass safety policy.",
));
}
if metadata.get("isActiveThread").and_then(|value| value.as_bool()) == Some(true) {
prompt.push_str(
"\n\nThis Slack message is from an active thread, but it may be ambient conversation. Only call send_adapter_message if the message appears directed at Exo, ...",
);
}
}
Worker side — examples/exo/adapters/slack/worker.ts:771,775 emits isActiveThread and dmTarget into the event metadata.
Related instances of the same pattern (not the subject of this issue, but relevant if a general fix is preferred):
crates/executor/src/adapter/tools.rs:904 — Slack dm: target validation
Suggested fix
metadata is already free-form JSON fully controlled by the worker, so the guidance can travel with it. Have workers emit their own prompt notes and let the host append them generically:
Host side, replacing the adapter_type == "slack" block:
if let Some(notes) = metadata.get("promptNotes").and_then(|v| v.as_array()) {
for note in notes.iter().filter_map(|n| n.as_str()) {
prompt.push_str("\n\n");
prompt.push_str(note);
}
}
Summary
compose_inbound_wakeup_promptin the host branches onadapter_type == "slack"and appends Slack-specific prompt text, reading two metadata keys (dmTarget,isActiveThread) that the Slack worker invents. This puts protocol-specific behavior in the host, coupled to the worker by untyped string keys across a language boundary.The forthcoming architecture doc in #171 states the rule this breaks:
Where
Host side —
crates/executor/src/adapter/runtime.rs:910-925:Worker side —
examples/exo/adapters/slack/worker.ts:771,775emitsisActiveThreadanddmTargetinto the event metadata.Related instances of the same pattern (not the subject of this issue, but relevant if a general fix is preferred):
crates/executor/src/adapter/tools.rs:904— Slackdm:target validationSuggested fix
metadatais already free-form JSON fully controlled by the worker, so the guidance can travel with it. Have workers emit their own prompt notes and let the host append them generically:{ "type": "message", "target": "C123:1700000000.000000", "text": "...", "metadata": { "promptNotes": [ "This Slack message is from an active thread, but it may be ambient conversation. Only call send_adapter_message if ..." ] } }Host side, replacing the
adapter_type == "slack"block: