feat: group chat privacy, channel-aware prompts, and safety hardening (#285)

Prevent personal memory (MEMORY.md) from leaking into group chat contexts
by adding system_prompt_for_context(is_group_chat) to the workspace. Add
channel-specific formatting hints (Discord, Telegram, Slack, WhatsApp),
runtime metadata injection, group chat behavioral guidance with NO_REPLY
silent token, safety rules in the system prompt, tool call style guidance,
wrap_external_content() for untrusted data, and improved workspace seed
files with richer identity/soul/agent templates and heartbeat checklist.

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-21 06:28:33 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 3124ab2b7f
commit 48b5323ec9
6 changed files with 284 additions and 29 deletions
+9 -1
View File
@@ -682,7 +682,15 @@ impl Agent {
// Convert SubmissionResult to response string
match result? {
SubmissionResult::Response { content } => Ok(Some(content)),
SubmissionResult::Response { content } => {
// Suppress silent replies (e.g. from group chat "nothing to say" responses)
if crate::llm::is_silent_reply(&content) {
tracing::debug!("Suppressing silent reply token");
Ok(None)
} else {
Ok(Some(content))
}
}
SubmissionResult::Ok { message } => Ok(message),
SubmissionResult::Error { message } => Ok(Some(format!("Error: {}", message))),
SubmissionResult::Interrupted => Ok(Some("Interrupted.".into())),
+13 -2
View File
@@ -40,9 +40,17 @@ impl Agent {
thread_id: Uuid,
initial_messages: Vec<ChatMessage>,
) -> Result<AgenticLoopResult, Error> {
// Detect group chat from channel metadata (needed before loading system prompt)
let is_group_chat = message
.metadata
.get("chat_type")
.and_then(|v| v.as_str())
.is_some_and(|t| t == "group" || t == "channel" || t == "supergroup");
// Load workspace system prompt (identity files: AGENTS.md, SOUL.md, etc.)
// In group chats, MEMORY.md is excluded to prevent leaking personal context.
let system_prompt = if let Some(ws) = self.workspace() {
match ws.system_prompt().await {
match ws.system_prompt_for_context(is_group_chat).await {
Ok(prompt) if !prompt.is_empty() => Some(prompt),
Ok(_) => None,
Err(e) => {
@@ -94,7 +102,10 @@ impl Agent {
None
};
let mut reasoning = Reasoning::new(self.llm().clone(), self.safety().clone());
let mut reasoning = Reasoning::new(self.llm().clone(), self.safety().clone())
.with_channel(message.channel.clone())
.with_model_name(self.llm().active_model_name())
.with_group_chat(is_group_chat);
if let Some(prompt) = system_prompt {
reasoning = reasoning.with_system_prompt(prompt);
}