Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions interface/src/components/ChannelEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -569,14 +569,17 @@ export function ChannelEditModal({platform, name, status, open, onOpenChange}: C
)}

{platform === "discord" && (
<div className="flex items-center gap-2">
<input
type="checkbox"
checked={bindingForm.require_mention}
onChange={(e) => setBindingForm({...bindingForm, require_mention: e.target.checked})}
className="h-4 w-4 rounded border-app-line bg-app-box"
/>
<label className="text-sm text-ink-dull">Require @mention or reply to bot</label>
<div>
<label className="flex items-center gap-2 text-sm text-ink-dull">
<input
type="checkbox"
checked={bindingForm.require_mention}
onChange={(e) => setBindingForm({...bindingForm, require_mention: e.target.checked})}
className="h-4 w-4 rounded border-app-line bg-app-box"
/>
Require @mention or reply to bot
</label>
<p className="mt-1.5 text-xs text-ink-faint">Blocks messages entirely — the agent won't see them at all. To let the agent read all messages but only respond to commands, mentions, or replies, use Mention Only mode in Channels settings (cog icon).</p>
</div>
)}

Expand Down
19 changes: 11 additions & 8 deletions interface/src/components/ChannelSettingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1171,14 +1171,17 @@ function BindingForm({
)}

{platform === "discord" && (
<div className="flex items-center gap-2">
<input
type="checkbox"
checked={bindingForm.require_mention}
onChange={(e) => setBindingForm({...bindingForm, require_mention: e.target.checked})}
className="h-4 w-4 rounded border-app-line bg-app-box"
/>
<label className="text-sm text-ink-dull">Require @mention or reply to bot</label>
<div>
<label className="flex items-center gap-2 text-sm text-ink-dull">
<input
type="checkbox"
checked={bindingForm.require_mention}
onChange={(e) => setBindingForm({...bindingForm, require_mention: e.target.checked})}
className="h-4 w-4 rounded border-app-line bg-app-box"
/>
Require @mention or reply to bot
</label>
<p className="mt-1.5 text-xs text-ink-faint">Blocks messages entirely — the agent won't see them at all. To let the agent read all messages but only respond to commands, mentions, or replies, use Mention Only mode in Channels settings (cog icon).</p>
</div>
)}

Expand Down
2 changes: 1 addition & 1 deletion interface/src/components/ConversationSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const RESPONSE_MODE_DESCRIPTIONS: Record<string, string> = {
quiet:
"Observes and learns from the conversation, but only responds when @mentioned, replied to, or given a command.",
mention_only:
"Only responds when explicitly @mentioned or replied to. No passive memory capture.",
"Messages are still visible to the agent for context, but it only responds when explicitly @mentioned, replied to, or given a command. To block messages entirely, use the binding-level require mention setting instead.",
};

const WORKER_HISTORY_OPTIONS = [
Expand Down
52 changes: 42 additions & 10 deletions src/agent/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,12 +1428,29 @@ impl Channel {
response_mode = ?self.resolved_settings.response_mode,
"suppressing unsolicited coalesced batch"
);
// In Quiet mode, keep passive memory capture.
// In MentionOnly mode, skip memory persistence.
if matches!(self.resolved_settings.response_mode, ResponseMode::Quiet) {
self.message_count += message_count;
self.check_memory_persistence().await;
// In MentionOnly mode, inject batch messages into in-memory history
// so the LLM retains channel context when eventually triggered.
if matches!(
self.resolved_settings.response_mode,
ResponseMode::MentionOnly
) {
{
let mut history = self.state.history.write().await;
for (formatted_text, _, _) in &pending_batch_entries {
history.push(rig::message::Message::User {
content: OneOrMany::one(UserContent::text(formatted_text)),
});
}
}
// Compaction guard: suppressed messages accumulate in history
// without agent turns, so check compaction to prevent unbounded growth.
if let Err(error) = self.compactor.check_and_compact().await {
tracing::warn!(channel_id = %self.id, %error, "compaction check failed");
}
}
// Both Quiet and MentionOnly keep passive memory capture.
self.message_count += message_count;
self.check_memory_persistence().await;
return Ok(());
}

Expand Down Expand Up @@ -1846,12 +1863,27 @@ impl Channel {
response_mode = ?self.resolved_settings.response_mode,
"suppressing unsolicited reply"
);
// In Quiet mode, keep passive memory capture.
// In MentionOnly mode, skip memory persistence entirely.
if matches!(self.resolved_settings.response_mode, ResponseMode::Quiet) {
self.message_count += 1;
self.check_memory_persistence().await;
// In MentionOnly mode, inject the message into in-memory history
// so the LLM retains channel context when eventually triggered.
if matches!(
self.resolved_settings.response_mode,
ResponseMode::MentionOnly
) {
{
let mut history = self.state.history.write().await;
history.push(rig::message::Message::User {
content: OneOrMany::one(UserContent::text(&user_text)),
});
}
// Compaction guard: suppressed messages accumulate in history
// without agent turns, so check compaction to prevent unbounded growth.
if let Err(error) = self.compactor.check_and_compact().await {
tracing::warn!(channel_id = %self.id, %error, "compaction check failed");
}
}
// Both Quiet and MentionOnly keep passive memory capture.
self.message_count += 1;
self.check_memory_persistence().await;
return Ok(());
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,11 @@ pub struct Binding {
/// Channel IDs this binding applies to. If empty, all channels in the guild/workspace are allowed.
pub channel_ids: Vec<String>,
/// Require explicit @mention (or reply-to-bot) for inbound messages.
/// Messages that don't match are blocked at the routing level and never
/// reach the channel — the agent cannot see them at all.
/// For context-aware mention filtering (agent sees messages but only
/// responds to mentions), use the channel-level `MentionOnly` response
/// mode instead.
pub require_mention: bool,
/// User IDs allowed to DM the bot through this binding.
pub dm_allowed_users: Vec<String>,
Expand Down
12 changes: 9 additions & 3 deletions src/conversation/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,15 @@ pub enum ResponseMode {
/// Observe and learn (history + memory persistence) but only respond
/// to @mentions, replies-to-bot, and slash commands.
Quiet,
/// Only respond when explicitly @mentioned or replied to.
/// Messages that don't pass the mention check are recorded in history
/// but receive no processing (no memory persistence, no LLM).
/// Only respond when explicitly @mentioned, replied to, or given a command.
/// Messages that don't pass the mention check are still ingested into
/// the in-memory context window (so the agent stays context-aware),
/// recorded in conversation history, and contribute to passive memory
/// capture — but do not trigger an LLM turn.
///
/// This differs from the binding-level `require_mention` flag, which
/// blocks message routing entirely — unmentioned messages never reach
/// the channel and are invisible to the agent.
MentionOnly,
}

Expand Down
Loading