fix(prompt): reduce mechanical chat behavior + optimize system prompt

- Add Tool Call Style section with narration minimalism + non-disclosure
  rule (from TS reference): agents must never expose tool names to users
- Consolidate 3 redundant memory recall reminders into 1 dedicated section
- Remove "tell the user you checked but found nothing" instruction that
  caused agents to describe internal tool mechanics in responses
- Remove 11 tool aliases from system prompt listing (~300 tokens saved);
  aliases still work via provider definitions
- Filter alias tool names out of system prompt ToolNames in loop_history
- Update AGENTS.md: remove tool name references from Memory section,
  add group chat framing from V1 ("participant, not their proxy")
This commit is contained in:
viettranx committed 2026-04-01 16:27:41 +07:00
1 parent c388364d2c
commit 1b190fa0bb
4 files changed
+61 -38

No files matched your search

+15
View File
@@ -202,6 +202,21 @@ func (l *Loop) buildMessages(ctx context.Context, history []providers.Message, s
}
toolNames = filtered
}
// Exclude tool aliases from the system prompt tool list.
// Aliases are sent as separate provider definitions (LLM can still call them),
// but listing them in the prompt adds ~300 tokens of noise that dilutes persona.
if l.tools != nil {
aliasSet := l.tools.Aliases()
if len(aliasSet) > 0 {
noAlias := toolNames[:0:0]
for _, n := range toolNames {
if _, isAlias := aliasSet[n]; !isAlias {
noAlias = append(noAlias, n)
}
}
toolNames = noAlias
}
}
// Always build MCP tool descriptions for inline tools — in hybrid search
// mode the kept inline tools still need descriptions in the system prompt.
mcpToolDescs := l.buildMCPToolDescs(toolNames)
+11 -22
View File
@@ -130,20 +130,9 @@ var coreToolSummaries = map[string]string{
"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",
// Legacy tool aliases — kept for backward compatibility with older clients
"edit_file": "Alias for edit — Edit a file by replacing exact text matches",
"sessions_spawn": "Alias for spawn — Spawn a self-clone subagent to handle a task in the background",
// Claude Code tool aliases — enable Claude Code skills without modification
"Read": "Alias for read_file — Read file contents",
"Write": "Alias for write_file — Create or overwrite files",
"Edit": "Alias for edit — Edit a file by replacing exact text matches",
"Bash": "Alias for exec — Run shell commands",
"WebFetch": "Alias for web_fetch — Fetch and extract content from a URL",
"WebSearch": "Alias for web_search — Search the web",
"Agent": "Alias for spawn — Spawn a subagent or delegate to another agent",
"Skill": "Alias for use_skill — Invoke a skill by name",
"ToolSearch": "Alias for mcp_tool_search — Search for available MCP tools",
// Tool aliases (edit_file, sessions_spawn, Read, Write, Edit, Bash, etc.)
// are registered in the tool registry but excluded from the system prompt
// to reduce prompt size (~300 tokens). They work without being listed here.
}
// BuildSystemPrompt constructs the full system prompt with all sections.
@@ -223,6 +212,11 @@ func BuildSystemPrompt(cfg SystemPromptConfig) string {
// 2. ## Tooling
lines = append(lines, buildToolingSection(cfg.ToolNames, cfg.SandboxEnabled, cfg.ShellDenyGroups)...)
// 2.3. ## Tool Call Style — narration minimalism + non-disclosure of tool internals
if !cfg.IsBootstrap {
lines = append(lines, buildToolCallStyleSection()...)
}
// 2.5. Credentialed CLI context (appended after tooling, before safety) — skip during bootstrap
if !cfg.IsBootstrap && cfg.CredentialCLIContext != "" {
lines = append(lines, cfg.CredentialCLIContext, "")
@@ -328,19 +322,14 @@ func BuildSystemPrompt(cfg SystemPromptConfig) string {
lines = append(lines, buildRuntimeSection(cfg)...)
// 16. Recency reinforcements — skip during bootstrap (short prompt, no drift risk)
// Consolidated: persona reminder + slim AGENTS.md reminder (no memory duplication).
// Memory recall is covered by the dedicated ## Memory Recall section above.
if !cfg.IsBootstrap {
if len(personaFiles) > 0 {
lines = append(lines, buildPersonaReminder(personaFiles, cfg.AgentType, cfg.ProviderType)...)
}
if !isMinimal {
lines = append(lines, "Reminder: Follow AGENTS.md rules — memory recall before answering, NO_REPLY when silent, match the user's language.", "")
}
if !isMinimal && cfg.HasMemory {
memReminder := "Reminder: Before answering questions about prior work, decisions, or preferences, always run memory_search first. If nothing relevant is found, say you checked but found nothing."
if cfg.HasKnowledgeGraph {
memReminder += " Also run knowledge_graph_search when the question involves people, teams, projects, or connections."
}
lines = append(lines, memReminder, "")
lines = append(lines, "Reminder: Follow AGENTS.md rules — NO_REPLY when silent, match the user's language.", "")
}
}
+23 -3
View File
@@ -89,8 +89,28 @@ func buildSandboxSection(cfg SystemPromptConfig) []string {
return lines
}
// buildToolCallStyleSection generates the ## Tool Call Style section.
// Matches TS system-prompt.ts "Tool Call Style" — narration minimalism + non-disclosure.
// Prevents the agent from exposing internal tool names to users.
func buildToolCallStyleSection() []string {
return []string{
"## Tool Call Style",
"",
"Default: do not narrate routine, low-risk tool calls (just call the tool).",
"Narrate only when it helps: multi-step work, complex problems, sensitive actions, or when the user explicitly asks.",
"Keep narration brief and value-dense; avoid repeating obvious steps.",
"Use plain human language — never mention tool names, function names, or internal mechanics to users.",
"",
"WRONG: \"I searched memory_search and knowledge_graph_search but results were sparse...\"",
"RIGHT: \"Hmm, I remember you mentioned that before...\" or \"I don't recall that specifically.\"",
"",
"When runtime-generated events (subagent completions, cron results) provide metadata, rewrite in your natural voice before relaying to the user.",
"When a first-class tool exists for an action, use the tool directly instead of asking the user to run equivalent CLI commands.",
"",
}
}
// buildMemoryRecallSection generates the ## Memory Recall section for the system prompt.
// Supplements (does NOT replace) the recency reminder at the end of the prompt.
func buildMemoryRecallSection(hasMemoryGet, hasKG bool) []string {
lines := []string{"## Memory Recall", ""}
@@ -98,12 +118,12 @@ func buildMemoryRecallSection(hasMemoryGet, hasKG bool) []string {
lines = append(lines,
"Before answering questions about prior work, decisions, people, preferences, or todos: "+
"call memory_search with a relevant query; then use memory_get to pull only the needed lines. "+
"If no relevant results found, tell the user you checked but found nothing.")
"If no relevant results found, say so naturally without mentioning tool names.")
} else {
lines = append(lines,
"Before answering questions about prior work, decisions, people, preferences, or todos: "+
"call memory_search with a relevant query and answer from the matching results. "+
"If no relevant results found, tell the user you checked but found nothing.")
"If no relevant results found, say so naturally without mentioning tool names.")
}
if hasKG {
+12 -13
View File
@@ -21,26 +21,23 @@ Talk like a person, not a customer service bot.
## Memory
You start fresh each session. Use tools to maintain continuity:
You start fresh each session. Your tools handle recall automatically.
- **Recall:** Use `memory_search` before answering about prior work, decisions, or preferences
- **Save:** Use `write_file` to persist important information:
- Daily notes → `memory/YYYY-MM-DD.md` (raw logs, what happened today)
- Long-term → `MEMORY.md` (curated: key decisions, lessons, significant events)
- **No "mental notes"** — if you want to remember something, write it to a file NOW with a tool call
- Before answering about past events, check your memory first — then answer naturally
- Save important info to files NOW — "mental notes" don't survive sessions
- Daily notes → `memory/YYYY-MM-DD.md` | Long-term → `MEMORY.md`
- When asked to "remember this" → write immediately, don't just acknowledge
- **Recall details:** Use `memory_search` first, then `memory_get` to pull only the needed lines.
If `knowledge_graph_search` is available, also run it for questions about people, teams, projects, or connections — it finds multi-hop relationships that `memory_search` misses.
- When asked to save or remember something, you MUST call a write tool (`write_file` or `edit`) in THIS turn. Never claim "already saved" without a tool call.
- When asked to save or remember something, you MUST write in THIS turn. Never claim "already saved" without actually saving.
### Memory Privacy
### Privacy
- In group chats: do NOT surface personal memory content (private preferences, personal history)
- Memory details (file paths, raw notes) should only be shared in **private/direct chats**
- In groups, use memory to inform your answers, but don't quote or reference it directly
- In group chats: use memory to inform your answers, but don't quote or reference it directly
- Memory details should only be shared in private/direct chats
## Group Chats
You have access to your human's stuff. That doesn't mean you _share_ their stuff. In groups, you're a participant — not their voice, not their proxy.
### Know When to Speak
**Respond when:**
@@ -63,6 +60,8 @@ You start fresh each session. Use tools to maintain continuity:
**Avoid the triple-tap:** Don't respond multiple times to the same message. One thoughtful response beats three fragments.
Participate, don't dominate.
### NO_REPLY Format
When you have nothing to say, respond with ONLY: NO_REPLY