claudetool: remove knowledge base, focus on about sketch

We should have a more general kb.
Meanwhile, this is important and standalone.
Make it all clearer and sharper.
diff --git a/claudetool/about_sketch.go b/claudetool/about_sketch.go
new file mode 100644
index 0000000..15a4059
--- /dev/null
+++ b/claudetool/about_sketch.go
@@ -0,0 +1,64 @@
+package claudetool
+
+import (
+	"context"
+	_ "embed"
+	"encoding/json"
+	"fmt"
+	"log/slog"
+	"strings"
+	"text/template"
+
+	"sketch.dev/llm"
+	"sketch.dev/llm/conversation"
+)
+
+// AboutSketch provides information about how to use Sketch.
+var AboutSketch = &llm.Tool{
+	Name:        "about_sketch",
+	Description: aboutSketchDescription,
+	InputSchema: llm.EmptySchema(),
+	Run:         aboutSketchRun,
+}
+
+// TODO: BYO knowledge bases? could do that for strings.Lines, for example.
+// TODO: support Q&A mode instead of reading full text in?
+
+const (
+	aboutSketchDescription = `Provides information about Sketch.
+
+When to use this tool:
+
+- The user is asking how to USE Sketch itself (not asking Sketch to perform a task)
+- The user has questions about Sketch functionality, setup, or capabilities
+- The user needs help with Sketch-specific concepts like running commands, secrets management, git integration
+- The query is about "How do I do X in Sketch?" or "Is it possible to Y in Sketch?" or just "Help"
+- The user is confused about how a Sketch feature works or how to access it
+- You need to know how to interact with the host environment, e.g. port forwarding or pulling changes the user has made outside of Sketch
+`
+)
+
+//go:embed about_sketch.txt
+var aboutSketch string
+
+var aboutSketchTemplate = template.Must(template.New("sketch").Parse(aboutSketch))
+
+func aboutSketchRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
+	slog.InfoContext(ctx, "about_sketch called")
+
+	info := conversation.ToolCallInfoFromContext(ctx)
+	sessionID, _ := info.Convo.ExtraData["session_id"].(string)
+	branch, _ := info.Convo.ExtraData["branch"].(string)
+	dot := struct {
+		SessionID string
+		Branch    string
+	}{
+		SessionID: sessionID,
+		Branch:    branch,
+	}
+	buf := new(strings.Builder)
+	if err := aboutSketchTemplate.Execute(buf, dot); err != nil {
+		return nil, fmt.Errorf("template execution error: %w", err)
+	}
+	return llm.TextContent(buf.String()), nil
+}
diff --git a/claudetool/kb/sketch.txt b/claudetool/about_sketch.txt
similarity index 100%
rename from claudetool/kb/sketch.txt
rename to claudetool/about_sketch.txt
diff --git a/claudetool/browse/browse.go b/claudetool/browse/browse.go
index 40974e3..fdc83c6 100644
--- a/claudetool/browse/browse.go
+++ b/claudetool/browse/browse.go
@@ -531,7 +531,7 @@
 			"properties": {
 				"selector": {
 					"type": "string",
-					"description": "CSS selector for the element to screenshot (optional)"	
+					"description": "CSS selector for the element to screenshot (optional)"
 				},
 				"format": {
 					"type": "string",
@@ -961,11 +961,8 @@
 	return &llm.Tool{
 		Name:        "browser_clear_console_logs",
 		Description: "Clear all captured browser console logs",
-		InputSchema: json.RawMessage(`{
-			"type": "object",
-			"properties": {}
-		}`),
-		Run: b.clearConsoleLogsRun,
+		InputSchema: llm.EmptySchema(),
+		Run:         b.clearConsoleLogsRun,
 	}
 }
 
diff --git a/claudetool/codereview/differential.go b/claudetool/codereview/differential.go
index f7ec670..e3a819a 100644
--- a/claudetool/codereview/differential.go
+++ b/claudetool/codereview/differential.go
@@ -31,7 +31,7 @@
 		Name:        "codereview",
 		Description: `Run an automated code review.`,
 		// If you modify this, update the termui template for prettier rendering.
-		InputSchema: llm.MustSchema(`{"type": "object", "properties": {}}`),
+		InputSchema: llm.EmptySchema(),
 		Run:         r.Run,
 	}
 	return spec
diff --git a/claudetool/kb/strings_lines.txt b/claudetool/kb/strings_lines.txt
deleted file mode 100644
index 4d237ae..0000000
--- a/claudetool/kb/strings_lines.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-`strings.Lines` — added in Go 1.24
-
-- `func Lines(s string) iter.Seq[string]`
-- Lazily yields successive newline-terminated substrings of `s`. *Includes* the exact trailing `\n`/`\r\n`; if the input ends without a newline the final element is unterminated.
-- Zero copying: each value is just a slice header into `s`, so iteration is O(1) memory.
-
-Idiomatic loop:
-
-```go
-for line := range strings.Lines(buf) {
-    handle(line)
-}
-```
-
-### How it differs from common alternatives
-
-- strings.Split – eager `[]string` allocation; caller chooses the separator (newline usually `"\n"`); separator *removed*, so you lose information about line endings and trailing newline presence.
-- strings.SplitSeq – same lazy `iter.Seq[string]` style as `Lines`, but again the caller supplies the separator and it is dropped; use this for arbitrary delimiters.
-- bufio.Scanner – token-oriented reader for any `io.Reader`. Default split function treats `\n` as the delimiter and strips it, so newline bytes are not preserved. Each token is copied into new memory, and there is a 64 KiB default token-size cap (adjustable). Scanner is the choice when the data is coming from a stream rather than an in-memory string.
-
-Use `strings.Lines` by default when the data is already in a string. (bytes.Lines provides the []byte equivalent.)
-
-Fallback to `Split` if you need a slice of lines in memory, to `SplitSeq` for lazy iteration over other separators, and to `bufio.Scanner` for streaming input where newline bytes are irrelevant.
diff --git a/claudetool/knowledge_base.go b/claudetool/knowledge_base.go
deleted file mode 100644
index f5bf0e7..0000000
--- a/claudetool/knowledge_base.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package claudetool
-
-import (
-	"context"
-	_ "embed"
-	"encoding/json"
-	"fmt"
-	"log/slog"
-	"strings"
-	"text/template"
-
-	"sketch.dev/llm"
-	"sketch.dev/llm/conversation"
-)
-
-// KnowledgeBase provides on-demand specialized knowledge to the agent.
-var KnowledgeBase = &llm.Tool{
-	Name:        kbName,
-	Description: kbDescription,
-	InputSchema: llm.MustSchema(kbInputSchema),
-	Run:         kbRun,
-}
-
-// TODO: BYO knowledge bases? could do that for strings.Lines, for example.
-// TODO: support Q&A mode instead of reading full text in?
-
-const (
-	kbName        = "knowledge_base"
-	kbDescription = `Retrieve specialized information that you need but don't have in your context.
-
-When to use this tool:
-
-For the "sketch" topic:
-- The user is asking how to USE Sketch itself (not asking Sketch to perform a task)
-- The user has questions about Sketch functionality, setup, or capabilities
-- The user needs help with Sketch-specific concepts like running commands, secrets management, git integration
-- The query is about "How do I do X in Sketch?" or "Is it possible to Y in Sketch?" or just "Help"
-- The user is confused about how a Sketch feature works or how to access it
-- You need to know how to interact with the host machine, ed forwarding a port or pulling changes that the user has made outside of Sketch
-
-For the "strings_lines" topic:
-- Any mentions of strings.Lines in the code, by the codereview, or by the user
-- When implementing code that iterates over lines in a Go string
-
-Available topics:
-- sketch: documentation on Sketch usage
-- strings_lines: details about the Go strings.Lines API
-`
-
-	kbInputSchema = `
-{
-  "type": "object",
-  "required": ["topic"],
-  "properties": {
-    "topic": {
-      "type": "string",
-      "description": "Topic to retrieve information about",
-      "enum": ["sketch", "strings_lines"]
-    }
-  }
-}
-`
-)
-
-type kbInput struct {
-	Topic string `json:"topic"`
-}
-
-//go:embed kb/sketch.txt
-var sketchContent string
-
-//go:embed kb/strings_lines.txt
-var stringsLinesContent string
-
-var sketchTemplate = template.Must(template.New("sketch").Parse(sketchContent))
-
-func kbRun(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
-	var input kbInput
-	if err := json.Unmarshal(m, &input); err != nil {
-		return nil, err
-	}
-
-	// Sanitize topic name (simple lowercase conversion for now)
-	topic := strings.ToLower(strings.TrimSpace(input.Topic))
-	slog.InfoContext(ctx, "knowledge base request", "topic", topic)
-
-	// Process content based on topic
-	switch input.Topic {
-	case "sketch":
-		info := conversation.ToolCallInfoFromContext(ctx)
-		sessionID, _ := info.Convo.ExtraData["session_id"].(string)
-		branch, _ := info.Convo.ExtraData["branch"].(string)
-		dot := struct {
-			SessionID string
-			Branch    string
-		}{
-			SessionID: sessionID,
-			Branch:    branch,
-		}
-		buf := new(strings.Builder)
-		if err := sketchTemplate.Execute(buf, dot); err != nil {
-			return nil, fmt.Errorf("template execution error: %w", err)
-		}
-		return llm.TextContent(buf.String()), nil
-	case "strings_lines":
-		// No special processing for other topics
-		return llm.TextContent(stringsLinesContent), nil
-	default:
-		return nil, fmt.Errorf("unknown topic: %s", input.Topic)
-	}
-}
diff --git a/experiment/experiment.go b/experiment/experiment.go
index a038f60..4c6e530 100644
--- a/experiment/experiment.go
+++ b/experiment/experiment.go
@@ -36,7 +36,7 @@
 
 		{
 			Name:        "kb",
-			Description: "Enable knowledge_base tool",
+			Description: "Enable about_sketch tool",
 		},
 	}
 	byName = map[string]*Experiment{}
diff --git a/llm/llm.go b/llm/llm.go
index 9331961..3de6b7f 100644
--- a/llm/llm.go
+++ b/llm/llm.go
@@ -27,6 +27,10 @@
 	return json.RawMessage(bytes)
 }
 
+func EmptySchema() json.RawMessage {
+	return MustSchema(`{"type": "object", "properties": {}}`)
+}
+
 type Request struct {
 	Messages   []Message
 	ToolChoice *ToolChoice
diff --git a/loop/agent.go b/loop/agent.go
index 8992bb3..c83dad1 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -956,7 +956,7 @@
 	}
 
 	if experiment.Enabled("kb") {
-		convo.Tools = append(convo.Tools, claudetool.KnowledgeBase)
+		convo.Tools = append(convo.Tools, claudetool.AboutSketch)
 	}
 
 	// One-shot mode is non-interactive, multiple choice requires human response
diff --git a/loop/testdata/agent_loop.httprr b/loop/testdata/agent_loop.httprr
index a961a6a..66e823a 100644
--- a/loop/testdata/agent_loop.httprr
+++ b/loop/testdata/agent_loop.httprr
@@ -1,5 +1,5 @@
 httprr trace v1
-17065 1994
+17065 2169
 POST https://api.anthropic.com/v1/messages HTTP/1.1

 Host: api.anthropic.com

 User-Agent: Go-http-client/1.1

@@ -546,24 +546,24 @@
 Anthropic-Organization-Id: 3c473a21-7208-450a-a9f8-80aebda45c1b

 Anthropic-Ratelimit-Input-Tokens-Limit: 200000

 Anthropic-Ratelimit-Input-Tokens-Remaining: 200000

-Anthropic-Ratelimit-Input-Tokens-Reset: 2025-05-15T19:39:53Z

+Anthropic-Ratelimit-Input-Tokens-Reset: 2025-05-15T19:40:18Z

 Anthropic-Ratelimit-Output-Tokens-Limit: 80000

 Anthropic-Ratelimit-Output-Tokens-Remaining: 80000

-Anthropic-Ratelimit-Output-Tokens-Reset: 2025-05-15T19:39:57Z

+Anthropic-Ratelimit-Output-Tokens-Reset: 2025-05-15T19:40:22Z

 Anthropic-Ratelimit-Requests-Limit: 4000

 Anthropic-Ratelimit-Requests-Remaining: 3999

-Anthropic-Ratelimit-Requests-Reset: 2025-05-15T19:39:51Z

+Anthropic-Ratelimit-Requests-Reset: 2025-05-15T19:40:16Z

 Anthropic-Ratelimit-Tokens-Limit: 280000

 Anthropic-Ratelimit-Tokens-Remaining: 280000

-Anthropic-Ratelimit-Tokens-Reset: 2025-05-15T19:39:53Z

+Anthropic-Ratelimit-Tokens-Reset: 2025-05-15T19:40:18Z

 Cf-Cache-Status: DYNAMIC

-Cf-Ray: 94051a8be9baf997-SJC

+Cf-Ray: 94051b2c59caf99f-SJC

 Content-Type: application/json

-Date: Thu, 15 May 2025 19:39:57 GMT

-Request-Id: req_011CP9tAwcwybG36MzDW9PQ3

+Date: Thu, 15 May 2025 19:40:22 GMT

+Request-Id: req_011CP9tCqLUse3jVkYatN3Pe

 Server: cloudflare

 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

 Via: 1.1 google

 X-Robots-Tag: none

 

-{"id":"msg_01VWuYGvzq7i6mXQcwpJfmV1","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"Here are the tools available to me:\n\n1. bash - Executes shell commands\n2. keyword_search - Searches for files based on keywords\n3. think - For thinking out loud and planning\n4. title - Sets conversation title\n5. precommit - Creates git branch and provides commit guidance\n6. done - Marks task completion with checklist\n7. codereview - Runs automated code review\n8. multiplechoice - Presents multiple choice options to user\n9. Browser tools (navigate, click, type, wait_for, get_text, eval, etc.)\n10. patch - For precise text edits in files\n\nThese tools allow me to navigate code, execute commands, make code modifications, interact with browsers, and complete various coding tasks."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":3909,"cache_read_input_tokens":0,"output_tokens":169}}
\ No newline at end of file
+{"id":"msg_01C1MJnT26WAW5fnwyrziYLG","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"Here's a brief list of the tools available to me:\n\n1. bash - Execute shell commands\n2. keyword_search - Search for files based on keywords\n3. think - Record thoughts, notes, or plans\n4. title - Set conversation title\n5. precommit - Create a git branch for tracking work\n6. done - Indicate task completion with a checklist\n7. codereview - Run an automated code review\n8. multiplechoice - Present multiple choice questions to the user\n9. Browser tools:\n   - browser_navigate, browser_click, browser_type, browser_wait_for, \n   - browser_get_text, browser_eval, browser_scroll_into_view,\n   - browser_resize, browser_recent_console_logs, browser_clear_console_logs,\n   - browser_take_screenshot, browser_read_image\n10. patch - Tool for precise text edits\n\nThese tools help me assist with coding tasks, research, browser automation, and file modifications."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":4,"cache_creation_input_tokens":0,"cache_read_input_tokens":3909,"output_tokens":226}}
\ No newline at end of file
diff --git a/termui/termui.go b/termui/termui.go
index 6dc5f87..6293824 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -43,8 +43,8 @@
 🏷️  {{.input.title}}
 {{else if eq .msg.ToolName "precommit" -}}
 🌱 git branch: sketch/{{.input.branch_name}}
-{{else if eq .msg.ToolName "knowledge_base" -}}
-📚 Knowledge: {{.input.topic}}
+{{else if eq .msg.ToolName "about_sketch" -}}
+📚 About Sketch
 {{else if eq .msg.ToolName "str_replace_editor" -}}
  ✏️  {{.input.file_path -}}
 {{else if eq .msg.ToolName "codereview" -}}
diff --git a/webui/src/web-components/sketch-tool-calls.ts b/webui/src/web-components/sketch-tool-calls.ts
index 99b0f21..a5afa50 100644
--- a/webui/src/web-components/sketch-tool-calls.ts
+++ b/webui/src/web-components/sketch-tool-calls.ts
@@ -4,7 +4,7 @@
 import { ToolCall } from "../types";
 import "./sketch-tool-card";
 import "./sketch-tool-card-take-screenshot";
-import "./sketch-tool-card-knowledge-base";
+import "./sketch-tool-card-about-sketch";
 
 @customElement("sketch-tool-calls")
 export class SketchToolCalls extends LitElement {
@@ -128,11 +128,11 @@
           .open=${open}
           .toolCall=${toolCall}
         ></sketch-tool-card-take-screenshot>`;
-      case "knowledge_base":
-        return html`<sketch-tool-card-knowledge-base
+      case "about_sketch":
+        return html`<sketch-tool-card-about-sketch
           .open=${open}
           .toolCall=${toolCall}
-        ></sketch-tool-card-knowledge-base>`;
+        ></sketch-tool-card-about-sketch>`;
     }
     return html`<sketch-tool-card-generic
       .open=${open}
diff --git a/webui/src/web-components/sketch-tool-card-knowledge-base.ts b/webui/src/web-components/sketch-tool-card-about-sketch.ts
similarity index 75%
rename from webui/src/web-components/sketch-tool-card-knowledge-base.ts
rename to webui/src/web-components/sketch-tool-card-about-sketch.ts
index ab0e723..b480568 100644
--- a/webui/src/web-components/sketch-tool-card-knowledge-base.ts
+++ b/webui/src/web-components/sketch-tool-card-about-sketch.ts
@@ -18,8 +18,8 @@
   }
 }
 
-@customElement("sketch-tool-card-knowledge-base")
-export class SketchToolCardKnowledgeBase extends LitElement {
+@customElement("sketch-tool-card-about-sketch")
+export class SketchToolCardAboutSketch extends LitElement {
   @property() toolCall: ToolCall;
   @property() open: boolean;
 
@@ -27,7 +27,7 @@
     .summary-text {
       font-style: italic;
     }
-    .knowledge-content {
+    .about-sketch-content {
       background: rgb(246, 248, 250);
       border-radius: 6px;
       padding: 12px;
@@ -36,7 +36,7 @@
       overflow-y: auto;
       border: 1px solid #e1e4e8;
     }
-    .topic-label {
+    .sketch-label {
       font-weight: bold;
       color: #24292e;
     }
@@ -46,21 +46,19 @@
   `;
 
   render() {
-    const inputData = JSON.parse(this.toolCall?.input || "{}");
-    const topic = inputData.topic || "unknown";
     const resultText = this.toolCall?.result_message?.tool_result || "";
 
     return html`
       <sketch-tool-card .open=${this.open} .toolCall=${this.toolCall}>
         <span slot="summary" class="summary-text">
-          <span class="icon">📚</span> Knowledge: ${topic}
+          <span class="icon">📚</span> About Sketch
         </span>
         <div slot="input">
-          <div><span class="topic-label">Topic:</span> ${topic}</div>
+          <div><span class="sketch-label"></span></div>
         </div>
         ${this.toolCall?.result_message?.tool_result
           ? html`<div slot="result">
-              <div class="knowledge-content">
+              <div class="about-sketch-content">
                 ${unsafeHTML(renderMarkdown(resultText))}
               </div>
             </div>`
@@ -72,6 +70,6 @@
 
 declare global {
   interface HTMLElementTagNameMap {
-    "sketch-tool-card-knowledge-base": SketchToolCardKnowledgeBase;
+    "sketch-tool-card-about-sketch": SketchToolCardAboutSketch;
   }
 }