feat(agent): inject <current_reply_target> into system prompt

Give the LLM an unambiguous reference for the currently bound chat so
it can compare against any target it plans to pass. Block lists channel,
chat_id and kind (direct/group) with a one-line policy: omit target to
reply here, only set target when forwarding per explicit user request.

Threads ChatID through SystemPromptConfig, IdentityData,
BridgePromptBuilder, and buildMessages so the pipeline populates the
block from pipeline.RunInput.ChatID.
This commit is contained in:
viettranx committed 2026-04-18 17:45:14 +07:00
1 parent 4658014ac8
commit 3715d260a5
5 files changed
+94 -74

No files matched your search

+2 -1
View File
@@ -17,7 +17,7 @@ import (
// buildMessages constructs the full message list for an LLM request.
// Returns the messages and whether BOOTSTRAP.md was present in context files
// (used by the caller for auto-cleanup without an extra DB roundtrip).
func (l *Loop) buildMessages(ctx context.Context, history []providers.Message, summary, userMessage, extraSystemPrompt, sessionKey, channel, channelType, chatTitle, peerKind, userID string, historyLimit int, skillFilter []string, lightContext bool) ([]providers.Message, bool) {
func (l *Loop) buildMessages(ctx context.Context, history []providers.Message, summary, userMessage, extraSystemPrompt, sessionKey, channel, channelType, chatTitle, chatID, peerKind, userID string, historyLimit int, skillFilter []string, lightContext bool) ([]providers.Message, bool) {
var messages []providers.Message
// Build system prompt — 3-layer mode resolution: runtime > auto-detect > config
@@ -204,6 +204,7 @@ func (l *Loop) buildMessages(ctx context.Context, history []providers.Message, s
Workspace: promptWorkspace,
Channel: channel,
ChannelType: channelType,
ChatID: chatID,
ChatTitle: chatTitle,
PeerKind: peerKind,
OwnerIDs: l.ownerIDs,
+1 -2
View File
@@ -129,7 +129,7 @@ func (l *Loop) makeBuildMessages() func(ctx context.Context, input *pipeline.Run
msgs, _ := l.buildMessages(ctx, history, summary,
input.Message, input.ExtraSystemPrompt,
input.SessionKey, input.Channel, input.ChannelType,
input.ChatTitle, input.PeerKind, input.UserID,
input.ChatTitle, input.ChatID, input.PeerKind, input.UserID,
input.HistoryLimit, input.SkillFilter, input.LightContext)
return msgs, nil
}
@@ -428,4 +428,3 @@ func (l *Loop) makeBootstrapCleanup() func(ctx context.Context, state *pipeline.
return l.bootstrapCleanup(ctx, l.agentUUID, state.Input.UserID)
}
}
+1
View File
@@ -35,6 +35,7 @@ func (b *BridgePromptBuilder) Build(cfg PromptConfig) (string, error) {
spc.AgentID = cfg.IdentityData.AgentName
spc.Model = cfg.IdentityData.Model
spc.Channel = cfg.IdentityData.Channel
spc.ChatID = cfg.IdentityData.ChatID
spc.ChatTitle = cfg.IdentityData.ChatTitle
spc.PeerKind = cfg.IdentityData.PeerKind
}
+8 -7
View File
@@ -6,13 +6,13 @@ import "github.com/nextlevelbuilder/goclaw/internal/memory"
// Each bool field maps to a named template block.
type PromptConfig struct {
// Section toggles
Identity bool
Persona bool // SOUL.md content
Instructions bool // AGENTS.md content
Tools bool
Skills bool
Team bool
Workspace bool
Identity bool
Persona bool // SOUL.md content
Instructions bool // AGENTS.md content
Tools bool
Skills bool
Team bool
Workspace bool
Memory bool // auto-inject L0 section
Sandbox bool
Orchestration bool // v3 orchestration delegation targets
@@ -43,6 +43,7 @@ type IdentityData struct {
Emoji string
Model string
Channel string
ChatID string // current reply target chat id
ChatTitle string
PeerKind string // "direct" or "group"
}
+82 -64
View File
@@ -97,35 +97,36 @@ type SystemPromptConfig struct {
DisplayName string // human-readable agent display name
Model string
Workspace string
Channel string // runtime channel instance name (e.g. "my-telegram-bot")
ChannelType string // platform type (e.g. "zalo_personal", "telegram")
ChatTitle string // group chat display name (shown in identity line)
PeerKind string // "direct" or "group"
OwnerIDs []string // owner sender IDs
Mode PromptMode // full or minimal
ToolNames []string // registered tool names
SkillsSummary string // XML from skills.Loader.BuildSummary()
HasMemory bool // memory_search/memory_get available?
HasSpawn bool // spawn tool available?
IsTeamContext bool // inject team sections (leader inbound OR team dispatch)
TeamWorkspace string // absolute path to team shared workspace (empty if not in team)
TeamMembers []store.TeamMemberData // team member roster for task assignment
TeamGuidance string // edition-specific guidance from TeamActionPolicy.MemberGuidance()
Channel string // runtime channel instance name (e.g. "my-telegram-bot")
ChannelType string // platform type (e.g. "zalo_personal", "telegram")
ChatID string // current reply target chat id (drives <current_reply_target>)
ChatTitle string // group chat display name (shown in identity line)
PeerKind string // "direct" or "group"
OwnerIDs []string // owner sender IDs
Mode PromptMode // full or minimal
ToolNames []string // registered tool names
SkillsSummary string // XML from skills.Loader.BuildSummary()
HasMemory bool // memory_search/memory_get available?
HasSpawn bool // spawn tool available?
IsTeamContext bool // inject team sections (leader inbound OR team dispatch)
TeamWorkspace string // absolute path to team shared workspace (empty if not in team)
TeamMembers []store.TeamMemberData // team member roster for task assignment
TeamGuidance string // edition-specific guidance from TeamActionPolicy.MemberGuidance()
ContextFiles []bootstrap.ContextFile // bootstrap files for # Project Context
ExtraPrompt string // extra system prompt (subagent context, etc.)
AgentType string // "open" or "predefined" — affects context file framing
ExtraPrompt string // extra system prompt (subagent context, etc.)
AgentType string // "open" or "predefined" — affects context file framing
HasSkillSearch bool // skill_search tool registered? (for search-mode prompt)
HasSkillManage bool // skill_manage tool registered + skill_evolve enabled for this agent
PinnedSkillsSummary string // XML summary of pinned skills only (hybrid mode)
HasMCPToolSearch bool // mcp_tool_search tool registered? (MCP search mode)
HasKnowledgeGraph bool // knowledge_graph_search tool registered?
HasMemoryExpand bool // memory_expand tool registered? (v3 episodic deep retrieval)
MCPToolDescs map[string]string // MCP tool name → description (inline mode only)
HasMCPToolSearch bool // mcp_tool_search tool registered? (MCP search mode)
HasKnowledgeGraph bool // knowledge_graph_search tool registered?
HasMemoryExpand bool // memory_expand tool registered? (v3 episodic deep retrieval)
MCPToolDescs map[string]string // MCP tool name → description (inline mode only)
// Sandbox info — matching TS sandboxInfo in system-prompt.ts
SandboxEnabled bool // exec tool runs inside Docker sandbox?
SandboxContainerDir string // container-side workdir (e.g. "/workspace")
SandboxEnabled bool // exec tool runs inside Docker sandbox?
SandboxContainerDir string // container-side workdir (e.g. "/workspace")
SandboxWorkspaceAccess string // "none", "ro", "rw"
// ProviderType identifies the LLM provider (e.g. "openai", "anthropic", "codex").
@@ -173,46 +174,46 @@ func (cfg SystemPromptConfig) sectionContent(id string, defaultFn func() []strin
// coreToolSummaries maps tool names to one-line descriptions.
// Shown in the ## Tooling section of the system prompt.
var coreToolSummaries = map[string]string{
"read_file": "Read file contents — only accesses your agent workspace. For docs returned by vault_search (shared/personal/team vault), use vault_read instead",
"write_file": "Create or overwrite files",
"list_files": "List directory contents",
"exec": "Run shell commands",
"memory_search": "Search indexed memory files (MEMORY.md + memory/*.md)",
"memory_get": "Read specific sections of memory files",
"spawn": "Spawn a self-clone subagent to handle a task in the background",
"web_search": "Search the web",
"web_fetch": "Fetch and extract content from a URL",
"datetime": "Get current date/time with timezone — use before creating cron jobs",
"cron": "Manage scheduled jobs and reminders (e.g. 'remind me at 9am', 'check every morning')",
"heartbeat": "Periodic background monitoring with HEARTBEAT.md. Unlike cron, auto-suppresses 'all OK' via HEARTBEAT_OK",
"skill_search": "Search available skills by keyword (weather, translate, github, etc.)",
"skill_manage": "Create, patch, or delete skills from conversation experience",
"publish_skill": "Register a skill directory in the system database, making it discoverable",
"use_skill": "Invoke a skill by name and follow its instructions",
"mcp_tool_search": "Search for available MCP external integration tools by keyword",
"browser": "Browse web pages interactively",
"tts": "Convert text to speech audio",
"edit": "Edit a file by replacing exact text matches",
"message": "Send a PROACTIVE message to another channel/chat — do NOT use this to reply to the user, just respond directly",
"sessions_list": "List sessions for this agent",
"session_status": "Show session status (model, tokens, compaction count)",
"sessions_history": "Fetch message history for a session",
"sessions_send": "Send a message into another session",
"read_image": "Analyze images — call with path from <media:image> tags",
"read_audio": "Analyze audio — call with media_id from <media:audio> tags",
"read_video": "Analyze video — call with media_id from <media:video> tags",
"create_video": "Generate videos from text descriptions using AI",
"read_document": "Analyze documents (PDF, DOCX) from <media:document> tags. If fails, use a skill instead. Path is directly accessible",
"create_image": "Generate images from text descriptions using AI",
"create_audio": "Generate music or sound effects from text descriptions using AI",
"knowledge_graph_search": "Find people, projects, and their connections — use for relationship questions (who works with whom, project dependencies) that memory_search may miss",
"team_tasks": "Team task board — track progress, manage dependencies (spawn auto-creates delegation tasks)",
"list_group_members": "List all members of the current group chat (Feishu/Lark only)",
"create_forum_topic": "Create a forum topic in a Telegram supergroup",
"delegate": "Delegate a task to a linked agent (requires agent_links). See ## Delegation Targets for available agents",
"memory_expand": "Retrieve full session details from episodic memory results — use after memory_search returns episodic hits",
"vault_search": "Search documents in the knowledge vault (hybrid keyword + semantic). Pass the returned doc_id to vault_read for full content",
"vault_read": "Read full content of a vault document by doc_id (from vault_search). Use for shared/personal/team vault docs that read_file cannot reach",
"read_file": "Read file contents — only accesses your agent workspace. For docs returned by vault_search (shared/personal/team vault), use vault_read instead",
"write_file": "Create or overwrite files",
"list_files": "List directory contents",
"exec": "Run shell commands",
"memory_search": "Search indexed memory files (MEMORY.md + memory/*.md)",
"memory_get": "Read specific sections of memory files",
"spawn": "Spawn a self-clone subagent to handle a task in the background",
"web_search": "Search the web",
"web_fetch": "Fetch and extract content from a URL",
"datetime": "Get current date/time with timezone — use before creating cron jobs",
"cron": "Manage scheduled jobs and reminders (e.g. 'remind me at 9am', 'check every morning')",
"heartbeat": "Periodic background monitoring with HEARTBEAT.md. Unlike cron, auto-suppresses 'all OK' via HEARTBEAT_OK",
"skill_search": "Search available skills by keyword (weather, translate, github, etc.)",
"skill_manage": "Create, patch, or delete skills from conversation experience",
"publish_skill": "Register a skill directory in the system database, making it discoverable",
"use_skill": "Invoke a skill by name and follow its instructions",
"mcp_tool_search": "Search for available MCP external integration tools by keyword",
"browser": "Browse web pages interactively",
"tts": "Convert text to speech audio",
"edit": "Edit a file by replacing exact text matches",
"message": "Send a PROACTIVE message to another channel/chat — do NOT use this to reply to the user, just respond directly",
"sessions_list": "List sessions for this agent",
"session_status": "Show session status (model, tokens, compaction count)",
"sessions_history": "Fetch message history for a session",
"sessions_send": "Send a message into another session",
"read_image": "Analyze images — call with path from <media:image> tags",
"read_audio": "Analyze audio — call with media_id from <media:audio> tags",
"read_video": "Analyze video — call with media_id from <media:video> tags",
"create_video": "Generate videos from text descriptions using AI",
"read_document": "Analyze documents (PDF, DOCX) from <media:document> tags. If fails, use a skill instead. Path is directly accessible",
"create_image": "Generate images from text descriptions using AI",
"create_audio": "Generate music or sound effects from text descriptions using AI",
"knowledge_graph_search": "Find people, projects, and their connections — use for relationship questions (who works with whom, project dependencies) that memory_search may miss",
"team_tasks": "Team task board — track progress, manage dependencies (spawn auto-creates delegation tasks)",
"list_group_members": "List all members of the current group chat (Feishu/Lark only)",
"create_forum_topic": "Create a forum topic in a Telegram supergroup",
"delegate": "Delegate a task to a linked agent (requires agent_links). See ## Delegation Targets for available agents",
"memory_expand": "Retrieve full session details from episodic memory results — use after memory_search returns episodic hits",
"vault_search": "Search documents in the knowledge vault (hybrid keyword + semantic). Pass the returned doc_id to vault_read for full content",
"vault_read": "Read full content of a vault document by doc_id (from vault_search). Use for shared/personal/team vault docs that read_file cannot reach",
// Tool aliases (edit_file, sessions_spawn, Read, Write, Edit, Bash, etc.)
// are registered in the tool registry but excluded from the system prompt
@@ -251,6 +252,25 @@ func BuildSystemPrompt(cfg SystemPromptConfig) string {
}
lines = append(lines, fmt.Sprintf("You are a personal assistant running in %s (%s).", channelLabel, chatType))
lines = append(lines, "")
// Inject explicit reply-target block so the LLM has a copy-paste-ready
// value to compare against when deciding to forward. Pairs with the
// MessageTool cross-target guard.
if cfg.ChatID != "" {
kind := "direct"
if cfg.PeerKind == "group" {
kind = "group"
}
lines = append(lines,
"<current_reply_target>",
fmt.Sprintf(" channel: %s", channelLabel),
fmt.Sprintf(" chat_id: %s", cfg.ChatID),
fmt.Sprintf(" kind: %s", kind),
"</current_reply_target>",
"When using the message tool, omit `target` to reply here. Set `target` only when forwarding to a different chat per explicit user request (also requires `forward=true` + `forward_reason`).",
"",
)
}
}
// 1.5. First-run bootstrap override (must be early so model sees it first)
@@ -685,5 +705,3 @@ func buildWorkspaceSection(workspace string, sandboxEnabled bool, containerDir s
"",
}
}