From f2ced826fac3b1fcfa1328d2d8485c221ffdf654 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 6 Jun 2026 05:51:30 +0000 Subject: [PATCH 1/2] Fix agent not using tools for personal queries Three issues: 1. Plan prompt didn't emphasise that personal queries MUST use tools. Added explicit instruction: "Never say you can't access something. You have tools for everything." Also injects user context into the plan so the LLM knows the user's state. 2. Synthesis prompt said "Answer using ONLY tool results" which made the LLM refuse when no tools were called. Changed to "Answer using tool results and user context" so it can use the injected context (unread mail count, etc.) directly. 3. Added shortcut mappings for common personal queries so they skip the LLM plan step entirely: "do i have mail", "check my email", "any new mail", "btc price", "today's news", etc. These go straight to the right tool. --- agent/agent.go | 18 ++++++++++++++++++ agent/run.go | 22 ++++++++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index ad47efaf..b7472f40 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1019,6 +1019,24 @@ func shortcutToolCalls(prompt string) []shortcutToolCall { "weather": {{Tool: "weather_forecast", Args: map[string]any{"lat": 51.5074, "lon": -0.1278}}}, "reminder": {{Tool: "reminder", Args: map[string]any{}}}, "apps": {{Tool: "apps_search", Args: map[string]any{}}}, + "mail": {{Tool: "mail_read", Args: map[string]any{}}}, + // Personal queries + "do i have mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "do i have unread mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "do i have email": {{Tool: "mail_read", Args: map[string]any{}}}, + "check my mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "check my email": {{Tool: "mail_read", Args: map[string]any{}}}, + "any new mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "any new email": {{Tool: "mail_read", Args: map[string]any{}}}, + "any mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "unread mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "my mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "btc price": {{Tool: "markets", Args: map[string]any{"category": "crypto"}}}, + "bitcoin price": {{Tool: "markets", Args: map[string]any{"category": "crypto"}}}, + "eth price": {{Tool: "markets", Args: map[string]any{"category": "crypto"}}}, + "what's happening": {{Tool: "news", Args: map[string]any{}}}, + "what's happening?": {{Tool: "news", Args: map[string]any{}}}, + "today's news": {{Tool: "news", Args: map[string]any{}}}, // Starter pill phrases "give me a summary of today's top news": {{Tool: "news", Args: map[string]any{}}}, "what's in the news?": {{Tool: "news", Args: map[string]any{}}}, diff --git a/agent/run.go b/agent/run.go index bd61f4d5..726273f1 100644 --- a/agent/run.go +++ b/agent/run.go @@ -133,10 +133,19 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { _ = acc // authenticated // Step 1: Plan + userCtx := "" + if UserContextFunc != nil { + userCtx = UserContextFunc(acc.ID) + } + planSystem := "You are an AI agent. Given a user question, output ONLY a JSON array of tool calls.\n\n" + + agentToolsDesc + + "\n\nOutput format: [{\"tool\":\"tool_name\",\"args\":{}}]\nUse at most 5 tool calls. Output [] if no tools needed." + + "\n\nIMPORTANT: For personal questions like 'do I have mail', 'what's the weather', 'news today', 'btc price' — ALWAYS use the appropriate tool. Never say you can't access something. You have tools for everything." + if userCtx != "" { + planSystem += "\n\nUser context:\n" + userCtx + } planResult, err := ai.Ask(&ai.Prompt{ - System: "You are an AI agent. Given a user question, output ONLY a JSON array of tool calls.\n\n" + - agentToolsDesc + - "\n\nOutput format: [{\"tool\":\"tool_name\",\"args\":{}}]\nUse at most 5 tool calls. Output [] if no tools needed.", + System: planSystem, Question: req.Prompt, Priority: ai.PriorityHigh, Provider: model.Provider, @@ -186,12 +195,9 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { // Step 3: Synthesise with user context. today := time.Now().UTC().Format("Monday, 2 January 2006 (UTC)") - userCtx := "" - if UserContextFunc != nil { - userCtx = UserContextFunc(acc.ID) - } synthSystem := "You are Micro, a personal AI assistant. Today is " + today + ". " + - "Answer concisely using the tool results below. Use markdown." + "Answer concisely using the tool results and user context below. Use markdown. " + + "If the user context already contains the answer (e.g. unread mail count), use it directly." if userCtx != "" { synthSystem += "\n\nUser context:\n" + userCtx } From 02b79c94e5b89f35ba6c2b3d1ee03cef02ae29c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 6 Jun 2026 05:53:50 +0000 Subject: [PATCH 2/2] =?UTF-8?q?Home=20prompt=20navigates=20to=20agent=20?= =?UTF-8?q?=E2=80=94=20no=20inline=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The home page is clean again: prompt + suggestions + cards. Submitting the prompt navigates to /agent?prompt=... which handles the full conversation with history, streaming, and proper UX. No more inline grey response box, no localStorage persistence, no consoleScript, no flip layout. The home page is a launcher for the agent, not a chat surface. Suggestion pills are now links to /agent?prompt=... instead of JS onclick handlers. Clicking "3 unread emails" takes you to the agent with that query pre-filled. Removed ~100 lines of consoleScript JS. Removed stream import from home package. Gear icon restored to date line. --- home/home.go | 131 +++++---------------------------------------------- 1 file changed, 13 insertions(+), 118 deletions(-) diff --git a/home/home.go b/home/home.go index 34eb4ea1..a22e917c 100644 --- a/home/home.go +++ b/home/home.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "sort" "strings" "sync" @@ -20,7 +21,6 @@ import ( "mu/mail" "mu/news" "mu/social" - "mu/stream" "mu/markets" "mu/reminder" "mu/video" @@ -389,7 +389,11 @@ func Handler(w http.ResponseWriter, r *http.Request) { } inviteHTML = fmt.Sprintf(`%s`, link, label) } - dateLine.WriteString(fmt.Sprintf(`
%s%s
`, now.Format("Monday, 2 January 2006"), inviteHTML)) + gearHTML := "" + if viewerAcc != nil { + gearHTML = ` ` + } + dateLine.WriteString(fmt.Sprintf(`
%s%s%s
`, now.Format("Monday, 2 January 2006"), inviteHTML, gearHTML)) // Inline weather: reads cached summary, and refreshes it in the // background if stale (>1 hour). This runs independently of the // weather card — even if the card is hidden, the date-line temp @@ -443,9 +447,8 @@ function fetchW(la,lo){ // ── Cards (always visible) ── b.WriteString(`
`) - // AI prompt — the primary interface. First thing on screen. + // AI prompt — submits to agent page. No inline response. if viewerID != "" { - // Build contextual suggestions based on user state. var suggestions []string if unread := mail.GetUnreadCount(viewerID); unread > 0 { suggestions = append(suggestions, fmt.Sprintf("%d unread email(s)", unread)) @@ -458,32 +461,21 @@ function fetchW(la,lo){ var suggestHTML string for _, s := range suggestions { - suggestHTML += fmt.Sprintf(``, htmlEsc(s)) + suggestHTML += fmt.Sprintf(`%s`, htmlEsc(url.QueryEscape(s)), htmlEsc(s)) } b.WriteString(fmt.Sprintf(`
-
-
%s
-
- -
- + +
-
`, suggestHTML, stream.MaxContentLength)) - b.WriteString(consoleScript) +
%s
+
`, suggestHTML)) } - // Date + browse section — cards are secondary to the AI prompt. + // Date + cards b.WriteString(dateHTML) - b.WriteString(`
`) - b.WriteString(`Browse`) - b.WriteString(`
`) - if viewerAcc != nil { - b.WriteString(` `) - } - b.WriteString(`
`) // Inline card preferences panel if viewerAcc != nil { @@ -732,103 +724,6 @@ func htmlEsc(s string) string { // suggestions, and typing indicator. // consoleScript — AI prompt with flip layout (input moves below response), // persistent last response, typing dots, suggestion pills. -const consoleScript = `` const statusCardScript = `