Detailed changes
@@ -13,7 +13,7 @@ import (
)
//go:embed templates/agent_tool.md
-var agentToolDescription []byte
+var agentToolDescription string
type AgentParams struct {
Prompt string `json:"prompt" description:"The task for the agent to perform"`
@@ -39,7 +39,7 @@ func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error)
}
return fantasy.NewParallelAgentTool(
AgentToolName,
- tools.FirstLineDescription(agentToolDescription),
+ agentToolDescription,
func(ctx context.Context, params AgentParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Prompt == "" {
return fantasy.NewTextErrorResponse("prompt is required"), nil
@@ -17,7 +17,7 @@ import (
)
//go:embed templates/agentic_fetch.md
-var agenticFetchToolDescription []byte
+var agenticFetchToolDescription string
// agenticFetchValidationResult holds the validated parameters from the tool call context.
type agenticFetchValidationResult struct {
@@ -65,7 +65,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
return fantasy.NewParallelAgentTool(
tools.AgenticFetchToolName,
- tools.FirstLineDescription(agenticFetchToolDescription),
+ agenticFetchToolDescription,
func(ctx context.Context, params tools.AgenticFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
validationResult, err := validateAgenticFetchParams(ctx, params)
if err != nil {
@@ -1,15 +1 @@
Launch a new agent that has access to the following tools: glob, grep, ls, view. When you are searching for a keyword or file and are not confident that you will find the right match on the first try, use the agent tool to perform the search for you.
-
-<usage>
-- If you are searching for a keyword like "config" or "logger", or for questions like "which file does X?", the Agent tool is strongly recommended
-- If you want to read a specific file path, use the View or GlobTool tool instead of the Agent tool, to find the match more quickly
-- If you are searching for a specific class definition like "class Foo", use the GlobTool tool instead, to find the match more quickly
-</usage>
-
-<usage_notes>
-1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
-2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
-3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
-4. The agent's outputs should generally be trusted
-5. IMPORTANT: The agent can not use Bash, Replace, Edit, so can not modify files. If you want to use these tools, use them directly instead of going through the agent.
-</usage_notes>
@@ -1,64 +1 @@
Fetch a URL or search the web using an AI sub-agent that can extract, summarize, and answer questions. Slower and costlier than fetch; use fetch for raw content or API responses.
-
-<when_to_use>
-Use this tool when you need to:
-- Search the web for information (omit the url parameter)
-- Extract specific information from a webpage (provide a url)
-- Answer questions about web content
-- Summarize or analyze web pages
-- Research topics by searching and following links
-
-DO NOT use this tool when:
-- You just need raw content without analysis (use fetch instead - faster and cheaper)
-- You want direct access to API responses or JSON (use fetch instead)
-- You don't need the content processed or interpreted (use fetch instead)
-</when_to_use>
-
-<usage>
-- Provide a prompt describing what information you want to find or extract (required)
-- Optionally provide a URL to fetch and analyze specific content
-- If no URL is provided, the agent will search the web to find relevant information
-- The tool spawns a sub-agent with web_search, web_fetch, and analysis tools
-- Returns the agent's response about the content
-</usage>
-
-<parameters>
-- prompt: What information you want to find or extract (required)
-- url: The URL to fetch content from (optional - if not provided, agent will search the web)
-</parameters>
-
-<usage_notes>
-- IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions. All MCP-provided tools start with "mcp_".
-- When using URL mode: The URL must be a fully-formed valid URL. HTTP URLs will be automatically upgraded to HTTPS.
-- When searching: Just provide the prompt describing what you want to find - the agent will search and fetch relevant pages.
-- The sub-agent can perform multiple searches and fetch multiple pages to gather comprehensive information.
-- This tool is read-only and does not modify any files.
-- Results will be summarized if the content is very large.
-- This tool uses AI processing and costs more tokens than the simple fetch tool.
-</usage_notes>
-
-<limitations>
-- Max response size: 5MB per page
-- Only supports HTTP and HTTPS protocols
-- Cannot handle authentication or cookies
-- Some websites may block automated requests
-- Uses additional tokens for AI processing
-- Search results depend on DuckDuckGo availability
-</limitations>
-
-<tips>
-- Be specific in your prompt about what information you want
-- For research tasks, omit the URL and let the agent search and follow relevant links
-- For complex pages, ask the agent to focus on specific sections
-- The agent has access to web_search, web_fetch, grep, and view tools
-- If you just need raw content, use the fetch tool instead to save tokens
-</tips>
-
-<examples>
-Search for information:
-- prompt: "What are the main new features in the latest Python release?"
-
-Fetch and analyze a URL:
-- url: "https://docs.python.org/3/whatsnew/3.12.html"
-- prompt: "Summarize the key changes in Python 3.12"
-</examples>
@@ -17,7 +17,7 @@ import (
const CrushInfoToolName = "crush_info"
//go:embed crush_info.md
-var crushInfoDescription []byte
+var crushInfoDescription string
type CrushInfoParams struct{}
@@ -30,7 +30,7 @@ func NewCrushInfoTool(
) fantasy.AgentTool {
return fantasy.NewAgentTool(
CrushInfoToolName,
- string(crushInfoDescription),
+ crushInfoDescription,
func(ctx context.Context, _ CrushInfoParams, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
return fantasy.NewTextResponse(buildCrushInfo(cfg, lspManager, allSkills, activeSkills, skillTracker)), nil
})
@@ -19,7 +19,7 @@ import (
const CrushLogsToolName = "crush_logs"
//go:embed crush_logs.md
-var crushLogsDescription []byte
+var crushLogsDescription string
// Max line size to prevent memory issues with very long log lines (1 MB).
const maxLogLineSize = 1024 * 1024
@@ -58,7 +58,7 @@ type CrushLogsParams struct {
func NewCrushLogsTool(logFile string) fantasy.AgentTool {
return fantasy.NewAgentTool(
CrushLogsToolName,
- string(crushLogsDescription),
+ crushLogsDescription,
func(ctx context.Context, params CrushLogsParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
result := runCrushLogs(logFile, params)
return fantasy.NewTextResponse(result), nil
@@ -22,12 +22,12 @@ type DiagnosticsParams struct {
const DiagnosticsToolName = "lsp_diagnostics"
//go:embed diagnostics.md
-var diagnosticsDescription []byte
+var diagnosticsDescription string
func NewDiagnosticsTool(lspManager *lsp.Manager) fantasy.AgentTool {
return fantasy.NewAgentTool(
DiagnosticsToolName,
- FirstLineDescription(diagnosticsDescription),
+ diagnosticsDescription,
func(ctx context.Context, params DiagnosticsParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if lspManager.Clients().Len() == 0 {
return fantasy.NewTextErrorResponse("no LSP clients available"), nil
@@ -1,24 +1 @@
-Get LSP errors, warnings, and hints for a file or the whole project.
-
-<usage>
-- Provide file path to get diagnostics for that file
-- Leave path empty to get diagnostics for entire project
-- Results displayed in structured format with severity levels
-</usage>
-
-<features>
-- Displays errors, warnings, and hints
-- Groups diagnostics by severity
-- Provides detailed information about each diagnostic
-</features>
-
-<limitations>
-- Results limited to diagnostics provided by LSP clients
-- May not cover all possible code issues
-- Does not provide suggestions for fixing issues
-</limitations>
-
-<tips>
-- Use with other tools for comprehensive code review
-- Combine with LSP client for real-time diagnostics
-</tips>
+Get LSP errors, warnings, and hints for a file or the whole project.
@@ -32,7 +32,7 @@ type DownloadPermissionsParams struct {
const DownloadToolName = "download"
//go:embed download.md
-var downloadDescription []byte
+var downloadDescription string
func NewDownloadTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
if client == nil {
@@ -48,7 +48,7 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
}
return fantasy.NewParallelAgentTool(
DownloadToolName,
- FirstLineDescription(downloadDescription),
+ downloadDescription,
func(ctx context.Context, params DownloadParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.URL == "" {
return fantasy.NewTextErrorResponse("URL parameter is required"), nil
@@ -1,28 +1 @@
-Download a URL directly to a local file (binary-safe, streaming, max 100MB); overwrites without warning. For reading content into context use fetch.
-
-<usage>
-- Provide URL to download from
-- Specify local file path where content should be saved
-- Optional timeout for request
-</usage>
-
-<features>
-- Downloads any file type (binary or text)
-- Auto-creates parent directories if missing
-- Handles large files efficiently with streaming
-- Sets reasonable timeouts to prevent hanging
-- Validates input parameters before requests
-</features>
-
-<limitations>
-- Max file size: 100MB
-- Only supports HTTP and HTTPS protocols
-- Cannot handle authentication or cookies
-- Some websites may block automated requests
-- Will overwrite existing files without warning
-</limitations>
-
-<tips>
-- Use absolute paths or paths relative to working directory
-- Set appropriate timeouts for large files or slow connections
-</tips>
+Download a URL directly to a local file (binary-safe, streaming, max 100MB); overwrites without warning. For reading content into context use fetch.
@@ -49,7 +49,7 @@ var (
)
//go:embed edit.md
-var editDescription []byte
+var editDescription string
type editContext struct {
ctx context.Context
@@ -68,7 +68,7 @@ func NewEditTool(
) fantasy.AgentTool {
return fantasy.NewAgentTool(
EditToolName,
- FirstLineDescription(editDescription),
+ editDescription,
func(ctx context.Context, params EditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.FilePath == "" {
return fantasy.NewTextErrorResponse("file_path is required"), nil
@@ -1,147 +1 @@
-Edit a file by exact find-and-replace; can also create or delete content. For renames/moves use bash. For large edits use write.
-
-<prerequisites>
-1. Use View tool to understand file contents and context
-2. For new files: Use LS tool to verify parent directory exists
-3. **CRITICAL**: Note exact whitespace, indentation, and formatting from View output
-</prerequisites>
-
-<parameters>
-1. file_path: Absolute path to file (required)
-2. old_string: Text to replace (must match exactly including whitespace/indentation)
-3. new_string: Replacement text
-4. replace_all: Replace all occurrences (default false)
-</parameters>
-
-<special_cases>
-
-- Create file: provide file_path + new_string, leave old_string empty
-- Delete content: provide file_path + old_string, leave new_string empty
- </special_cases>
-
-<critical_requirements>
-EXACT MATCHING: The tool is extremely literal. Text must match **EXACTLY**
-
-- Every space and tab character
-- Every blank line
-- Every newline character
-- Indentation level (count the spaces/tabs)
-- Comment spacing (`// comment` vs `//comment`)
-- Brace positioning (`func() {` vs `func(){`)
-
-Common failures:
-
-```
-Expected: " func foo() {" (4 spaces)
-Provided: " func foo() {" (2 spaces) β FAILS
-
-Expected: "}\n\nfunc bar() {" (2 newlines)
-Provided: "}\nfunc bar() {" (1 newline) β FAILS
-
-Expected: "// Comment" (space after //)
-Provided: "//Comment" (no space) β FAILS
-```
-
-UNIQUENESS (when replace_all=false): old_string MUST uniquely identify target instance
-
-- Include 3-5 lines context BEFORE and AFTER change point
-- Include exact whitespace, indentation, surrounding code
-- If text appears multiple times, add more context to make it unique
-
-SINGLE INSTANCE: Tool changes ONE instance when replace_all=false
-
-- For multiple instances: set replace_all=true OR make separate calls with unique context
-- Plan calls carefully to avoid conflicts
-
-VERIFICATION BEFORE USING: Before every edit
-
-1. View the file and locate exact target location
-2. Check how many instances of target text exist
-3. Copy the EXACT text including all whitespace
-4. Verify you have enough context for unique identification
-5. Double-check indentation matches (count spaces/tabs)
-6. Plan separate calls or use replace_all for multiple changes
- </critical_requirements>
-
-<warnings>
-Tool fails if:
-- old_string matches multiple locations and replace_all=false
-- old_string doesn't match exactly (including whitespace)
-- Insufficient context causes wrong instance change
-- Indentation is off by even one space
-- Missing or extra blank lines
-- Wrong tabs vs spaces
-</warnings>
-
-<recovery_steps>
-If you get "old_string not found in file":
-
-1. **View the file again** at the specific location
-2. **Copy more context** - include entire function if needed
-3. **Check whitespace**:
- - Count indentation spaces/tabs
- - Look for blank lines
- - Check for trailing spaces
-4. **Verify character-by-character** that your old_string matches
-5. **Never guess** - always View the file to get exact text
- </recovery_steps>
-
-<best_practices>
-
-- Ensure edits result in correct, idiomatic code
-- Don't leave code in broken state
-- Use absolute file paths (starting with /)
-- Use forward slashes (/) for cross-platform compatibility
-- Multiple edits to same file: send all in single message with multiple tool calls
-- **When in doubt, include MORE context rather than less**
-- Match the existing code style exactly (spaces, tabs, blank lines)
- </best_practices>
-
-<whitespace_checklist>
-Before submitting an edit, verify:
-
-- [ ] Viewed the file first
-- [ ] Counted indentation spaces/tabs
-- [ ] Included blank lines if they exist
-- [ ] Matched brace/bracket positioning
-- [ ] Included 3-5 lines of surrounding context
-- [ ] Verified text appears exactly once (or using replace_all)
-- [ ] Copied text character-for-character, not approximated
- </whitespace_checklist>
-
-<examples>
-β
Correct: Exact match with context
-
-```
-old_string: "func ProcessData(input string) error {\n if input == \"\" {\n return errors.New(\"empty input\")\n }\n return nil\n}"
-
-new_string: "func ProcessData(input string) error {\n if input == \"\" {\n return errors.New(\"empty input\")\n }\n // New validation\n if len(input) > 1000 {\n return errors.New(\"input too long\")\n }\n return nil\n}"
-```
-
-β Incorrect: Not enough context
-
-```
-old_string: "return nil" // Appears many times!
-```
-
-β Incorrect: Wrong indentation
-
-```
-old_string: " if input == \"\" {" // 2 spaces
-// But file actually has: " if input == \"\" {" // 4 spaces
-```
-
-β
Correct: Including context to make unique
-
-```
-old_string: "func ProcessData(input string) error {\n if input == \"\" {\n return errors.New(\"empty input\")\n }\n return nil"
-```
-
-</examples>
-
-<windows_notes>
-
-- Forward slashes work throughout (C:/path/file)
-- File permissions handled automatically
-- Line endings converted automatically (\n β \r\n)
- </windows_notes>
+Edit a file by exact find-and-replace; can also create or delete content. For renames/moves use bash. For large edits use write.
@@ -22,7 +22,7 @@ const (
)
//go:embed fetch.md
-var fetchDescription []byte
+var fetchDescription string
func NewFetchTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
if client == nil {
@@ -39,7 +39,7 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
return fantasy.NewParallelAgentTool(
FetchToolName,
- FirstLineDescription(fetchDescription),
+ fetchDescription,
func(ctx context.Context, params FetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.URL == "" {
return fantasy.NewTextErrorResponse("URL parameter is required"), nil
@@ -1,45 +1 @@
-Fetch raw content from a URL as text, markdown, or html (max 100KB); no AI processing. For analysis or extraction use agentic_fetch.
-
-<when_to_use>
-Use this tool when you need:
-- Raw, unprocessed content from a URL
-- Direct access to API responses or JSON data
-- HTML/text/markdown content without interpretation
-- Simple, fast content retrieval without analysis
-- To save tokens by avoiding AI processing
-
-DO NOT use this tool when you need to:
-- Extract specific information from a webpage (use agentic_fetch instead)
-- Answer questions about web content (use agentic_fetch instead)
-- Analyze or summarize web pages (use agentic_fetch instead)
-</when_to_use>
-
-<usage>
-- Provide URL to fetch content from
-- Specify desired output format (text, markdown, or html)
-- Optional timeout for request
-</usage>
-
-<features>
-- Supports three output formats: text, markdown, html
-- Auto-handles HTTP redirects
-- Fast and lightweight - no AI processing
-- Sets reasonable timeouts to prevent hanging
-- Validates input parameters before requests
-</features>
-
-<limitations>
-- Max response size: 100KB
-- Only supports HTTP and HTTPS protocols
-- Cannot handle authentication or cookies
-- Some websites may block automated requests
-- Returns raw content only - no analysis or extraction
-</limitations>
-
-<tips>
-- Use text format for plain text content or simple API responses
-- Use markdown format for content that should be rendered with formatting
-- Use html format when you need raw HTML structure
-- Set appropriate timeouts for potentially slow websites
-- If the user asks to analyze or extract from a page, use agentic_fetch instead
-</tips>
+Fetch raw content from a URL as text, markdown, or html (max 100KB); no AI processing. For analysis or extraction use agentic_fetch.
@@ -20,7 +20,7 @@ import (
const GlobToolName = "glob"
//go:embed glob.md
-var globDescription []byte
+var globDescription string
type GlobParams struct {
Pattern string `json:"pattern" description:"The glob pattern to match files against"`
@@ -35,7 +35,7 @@ type GlobResponseMetadata struct {
func NewGlobTool(workingDir string) fantasy.AgentTool {
return fantasy.NewAgentTool(
GlobToolName,
- FirstLineDescription(globDescription),
+ globDescription,
func(ctx context.Context, params GlobParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Pattern == "" {
return fantasy.NewTextErrorResponse("pattern is required"), nil
@@ -1,40 +1 @@
-Find files by name/pattern (glob syntax), sorted by modification time; max 100 results; skips hidden files. Use grep to search file contents.
-
-<usage>
-- Provide glob pattern to match against file paths
-- Optional starting directory (defaults to current working directory)
-- Results sorted with most recently modified files first
-</usage>
-
-<pattern_syntax>
-- '\*' matches any sequence of non-separator characters
-- '\*\*' matches any sequence including separators
-- '?' matches any single non-separator character
-- '[...]' matches any character in brackets
-- '[!...]' matches any character not in brackets
-</pattern_syntax>
-
-<examples>
-- '*.js' - JavaScript files in current directory
-- '**/*.js' - JavaScript files in any subdirectory
-- 'src/**/*.{ts,tsx}' - TypeScript files in src directory
-- '*.{html,css,js}' - HTML, CSS, and JS files
-</examples>
-
-<limitations>
-- Results limited to 100 files (newest first)
-- Does not search file contents (use Grep for that)
-- Hidden files (starting with '.') skipped
-</limitations>
-
-<cross_platform>
-- Path separators handled automatically (/ and \ work)
-- Uses ripgrep (rg) if available, otherwise Go implementation
-- Patterns should use forward slashes (/) for compatibility
-</cross_platform>
-
-<tips>
-- Combine with Grep: find files with Glob, search contents with Grep
-- For iterative exploration requiring multiple searches, consider Agent tool
-- Check if results truncated and refine pattern if needed
-</tips>
+Find files by name/pattern (glob syntax), sorted by modification time; max 100 results; skips hidden files. Use grep to search file contents.
@@ -90,7 +90,7 @@ const (
)
//go:embed grep.md
-var grepDescription []byte
+var grepDescription string
// escapeRegexPattern escapes special regex characters so they're treated as literal characters
func escapeRegexPattern(pattern string) string {
@@ -107,7 +107,7 @@ func escapeRegexPattern(pattern string) string {
func NewGrepTool(workingDir string, config config.ToolGrep) fantasy.AgentTool {
return fantasy.NewAgentTool(
GrepToolName,
- FirstLineDescription(grepDescription),
+ grepDescription,
func(ctx context.Context, params GrepParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Pattern == "" {
return fantasy.NewTextErrorResponse("pattern is required"), nil
@@ -1,49 +1 @@
-Search file contents by regex or literal text; returns matching file paths sorted by modification time (max 100); respects .gitignore. Use glob to filter by filename, not contents.
-
-<usage>
-- Provide regex pattern to search within file contents
-- Set literal_text=true for exact text with special characters (recommended for non-regex users)
-- Optional starting directory (defaults to current working directory)
-- Optional include pattern to filter which files to search
-- Results sorted with most recently modified files first
-</usage>
-
-<regex_syntax>
-When literal_text=false (supports standard regex):
-
-- 'function' searches for literal text "function"
-- 'log\..\*Error' finds text starting with "log." and ending with "Error"
-- 'import\s+.\*\s+from' finds import statements in JavaScript/TypeScript
-</regex_syntax>
-
-<include_patterns>
-- '\*.js' - Only search JavaScript files
-- '\*.{ts,tsx}' - Only search TypeScript files
-- '\*.go' - Only search Go files
-</include_patterns>
-
-<limitations>
-- Results limited to 100 files (newest first)
-- Performance depends on number of files searched
-- Very large binary files may be skipped
-- Hidden files (starting with '.') skipped
-</limitations>
-
-<ignore_support>
-- Respects .gitignore patterns to skip ignored files/directories
-- Respects .crushignore patterns for additional ignore rules
-- Both ignore files auto-detected in search root directory
-</ignore_support>
-
-<cross_platform>
-- Uses ripgrep (rg) if available for better performance
-- Falls back to Go implementation if ripgrep unavailable
-- File paths normalized automatically for compatibility
-</cross_platform>
-
-<tips>
-- For faster searches: use Glob to find relevant files first, then Grep
-- For iterative exploration requiring multiple searches, consider Agent tool
-- Check if results truncated and refine search pattern if needed
-- Use literal_text=true for exact text with special characters (dots, parentheses, etc.)
-</tips>
+Search file contents by regex or literal text; returns matching file paths sorted by modification time (max 100); respects .gitignore. Use glob to filter by filename, not contents.
@@ -14,7 +14,7 @@ const (
)
//go:embed job_kill.md
-var jobKillDescription []byte
+var jobKillDescription string
type JobKillParams struct {
ShellID string `json:"shell_id" description:"The ID of the background shell to terminate"`
@@ -29,7 +29,7 @@ type JobKillResponseMetadata struct {
func NewJobKillTool() fantasy.AgentTool {
return fantasy.NewAgentTool(
JobKillToolName,
- string(jobKillDescription),
+ jobKillDescription,
func(ctx context.Context, params JobKillParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.ShellID == "" {
return fantasy.NewTextErrorResponse("missing shell_id"), nil
@@ -15,7 +15,7 @@ const (
)
//go:embed job_output.md
-var jobOutputDescription []byte
+var jobOutputDescription string
type JobOutputParams struct {
ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
@@ -33,7 +33,7 @@ type JobOutputResponseMetadata struct {
func NewJobOutputTool() fantasy.AgentTool {
return fantasy.NewAgentTool(
JobOutputToolName,
- FirstLineDescription(jobOutputDescription),
+ jobOutputDescription,
func(ctx context.Context, params JobOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.ShellID == "" {
return fantasy.NewTextErrorResponse("missing shell_id"), nil
@@ -1,22 +1 @@
-Get stdout/stderr from a background shell by ID; set wait=true to block until completion.
-
-<usage>
-- Provide the shell ID returned from a background bash execution
-- Returns the current stdout and stderr output
-- Indicates whether the shell has completed execution
-- Set wait=true to block until the shell completes or the request context is done
-</usage>
-
-<features>
-- View output from running background processes
-- Check if background process has completed
-- Get cumulative output from process start
-- Optionally wait for process completion (returns early on context cancel)
-</features>
-
-<tips>
-- Use this to monitor long-running processes
-- Check the 'done' status to see if process completed
-- Can be called multiple times to view incremental output
-- Use wait=true when you need the final output and exit status (or current output if the request cancels)
-</tips>
+Get stdout/stderr from a background shell by ID; set wait=true to block until completion.
@@ -26,12 +26,12 @@ type ListMCPResourcesPermissionsParams struct {
const ListMCPResourcesToolName = "list_mcp_resources"
//go:embed list_mcp_resources.md
-var listMCPResourcesDescription []byte
+var listMCPResourcesDescription string
func NewListMCPResourcesTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
ListMCPResourcesToolName,
- FirstLineDescription(listMCPResourcesDescription),
+ listMCPResourcesDescription,
func(ctx context.Context, params ListMCPResourcesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
params.MCPName = strings.TrimSpace(params.MCPName)
if params.MCPName == "" {
@@ -1,18 +1 @@
-List available resource URIs from an MCP server by name; use before read_mcp_resource.
-
-<when_to_use>
-Use this tool to discover which resources are available before reading them.
-</when_to_use>
-
-<usage>
-- Provide MCP server name
-- Returns resource titles and URIs
-</usage>
-
-<parameters>
-- mcp_name: The MCP server name
-</parameters>
-
-<notes>
-- Results include resource titles, URIs, and metadata when available
-</notes>
+List available resource URIs from an MCP server by name; use before read_mcp_resource.
@@ -53,12 +53,12 @@ const (
)
//go:embed ls.md
-var lsDescription []byte
+var lsDescription string
func NewLsTool(permissions permission.Service, workingDir string, lsConfig config.ToolLs) fantasy.AgentTool {
return fantasy.NewAgentTool(
LSToolName,
- FirstLineDescription(lsDescription),
+ lsDescription,
func(ctx context.Context, params LSParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
searchPath, err := fsext.Expand(cmp.Or(params.Path, workingDir))
if err != nil {
@@ -1,34 +1 @@
-List files and directories as a tree; skips hidden files and common system dirs; max 1000 files. Use glob to find files by pattern, grep to search contents.
-
-<usage>
-- Provide path to list (defaults to current working directory)
-- Optional glob patterns to ignore
-- Results displayed in tree structure
-</usage>
-
-<features>
-- Hierarchical view of files and directories
-- Auto-skips hidden files/directories (starting with '.')
-- Skips common system directories like __pycache__
-- Can filter files matching specific patterns
-</features>
-
-<limitations>
-- Results limited to 1000 files
-- Large directories truncated
-- No file sizes or permissions shown
-- Cannot recursively list all directories in large projects
-</limitations>
-
-<cross_platform>
-- Hidden file detection uses Unix convention (files starting with '.')
-- Windows hidden files (with hidden attribute) not auto-skipped
-- Common Windows directories (System32, Program Files) not in default ignore
-- Path separators handled automatically (/ and \ work)
-</cross_platform>
-
-<tips>
-- Use Glob for finding files by name patterns instead of browsing
-- Use Grep for searching file contents
-- Combine with other tools for effective exploration
-</tips>
+List files and directories as a tree; skips hidden files and common system dirs; max 1000 files. Use glob to find files by pattern, grep to search contents.
@@ -16,7 +16,7 @@ import (
const LSPRestartToolName = "lsp_restart"
//go:embed lsp_restart.md
-var lspRestartDescription []byte
+var lspRestartDescription string
type LSPRestartParams struct {
// Name is the optional name of a specific LSP client to restart.
@@ -27,7 +27,7 @@ type LSPRestartParams struct {
func NewLSPRestartTool(lspManager *lsp.Manager) fantasy.AgentTool {
return fantasy.NewAgentTool(
LSPRestartToolName,
- FirstLineDescription(lspRestartDescription),
+ lspRestartDescription,
func(ctx context.Context, params LSPRestartParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if lspManager.Clients().Len() == 0 {
return fantasy.NewTextErrorResponse("no LSP clients available to restart"), nil
@@ -1,25 +1 @@
-Restart one or all LSP clients by name; use when diagnostics are stale or the LSP is unresponsive.
-
-<usage>
-- Restart all running LSP clients or a specific LSP client by name
-- Useful when LSP servers become unresponsive or need to be reloaded
-- Parameters:
- - name (optional): Specific LSP client name to restart. If not provided, all clients will be restarted.
-</usage>
-
-<features>
-- Gracefully shuts down all LSP clients
-- Restarts them with their original configuration
-- Reports success/failure for each client
-</features>
-
-<limitations>
-- Only restarts clients that were successfully started
-- Does not modify LSP configurations
-- Requires LSP clients to be already running
-</limitations>
-
-<tips>
-- Use when LSP diagnostics are stale or unresponsive
-- Call this tool if you notice LSP features not working properly
-</tips>
+Restart one or all LSP clients by name; use when diagnostics are stale or the LSP is unresponsive.
@@ -55,7 +55,7 @@ type MultiEditResponseMetadata struct {
const MultiEditToolName = "multiedit"
//go:embed multiedit.md
-var multieditDescription []byte
+var multieditDescription string
func NewMultiEditTool(
lspManager *lsp.Manager,
@@ -66,7 +66,7 @@ func NewMultiEditTool(
) fantasy.AgentTool {
return fantasy.NewAgentTool(
MultiEditToolName,
- FirstLineDescription(multieditDescription),
+ multieditDescription,
func(ctx context.Context, params MultiEditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.FilePath == "" {
return fantasy.NewTextErrorResponse("file_path is required"), nil
@@ -1,125 +1 @@
-Apply multiple find-and-replace edits to a single file in one operation; edits run sequentially. Prefer over edit for multiple changes to the same file. Same exact-match rules as edit apply.
-
-<prerequisites>
-1. Use View tool to understand file contents and context
-2. Verify directory path is correct
-3. CRITICAL: Note exact whitespace, indentation, and formatting from View output
-</prerequisites>
-
-<parameters>
-1. file_path: Absolute path to file (required)
-2. edits: Array of edit operations, each containing:
- - old_string: Text to replace (must match exactly including whitespace/indentation)
- - new_string: Replacement text
- - replace_all: Replace all occurrences (optional, defaults to false)
-</parameters>
-
-<operation>
-- Edits applied sequentially in provided order.
-- Each edit operates on result of previous edit.
-- PARTIAL SUCCESS: If some edits fail, successful edits are still applied. Failed edits are returned in the response.
-- File is modified if at least one edit succeeds.
-- Ideal for several changes to different parts of same file.
-</operation>
-
-<inherited_rules>
-All instructions from the Edit tool documentation apply verbatim to every edit item:
-- Critical requirements for exact matching and uniqueness
-- Warnings and common failures (tabs vs spaces, blank lines, brace placement, etc.)
-- Verification steps before using, recovery steps, best practices, and whitespace checklist
-Use the same level of precision as Edit. Multiedit often fails due to formatting mismatchesβdouble-check whitespace for every edit.
-</inherited_rules>
-
-<critical_requirements>
-1. Apply Edit tool rules to EACH edit (see edit.md).
-2. Edits are applied in order; successful edits are kept even if later edits fail.
-3. Plan sequence carefully: earlier edits change the file content that later edits must match.
-4. Ensure each old_string is unique at its application time (after prior edits).
-5. Check the response for failed edits and retry them if needed.
-</critical_requirements>
-
-<verification_before_using>
-1. View the file and copy exact text (including whitespace) for each target.
-2. Check how many instances each old_string has BEFORE the sequence starts.
-3. Dry-run mentally: after applying edit #N, will edit #N+1 still match? Adjust old_string/new_string accordingly.
-4. Prefer fewer, larger context blocks over many tiny fragments that are easy to misalign.
-5. If edits are independent, consider separate multiedit batches per logical region.
-</verification_before_using>
-
-<warnings>
-- Operation continues even if some edits fail; check response for failed edits.
-- Earlier edits can invalidate later matches (added/removed spaces, lines, or reordered text).
-- Mixed tabs/spaces, trailing spaces, or missing blank lines commonly cause failures.
-- replace_all may affect unintended regionsβuse carefully or provide more context.
-</warnings>
-
-<recovery_steps>
-If some edits fail:
-1. Check the response metadata for the list of failed edits with their error messages.
-2. View the file again to see the current state after successful edits.
-3. Adjust the failed edits based on the new file content.
-4. Retry the failed edits with corrected old_string values.
-5. Consider breaking complex batches into smaller, independent operations.
-</recovery_steps>
-
-<best_practices>
-- Ensure all edits result in correct, idiomatic code; don't leave code broken.
-- Use absolute file paths (starting with /).
-- Use replace_all only when you're certain; otherwise provide unique context.
-- Match existing style exactly (spaces, tabs, blank lines).
-- Review failed edits in the response and retry with corrections.
-</best_practices>
-
-<whitespace_checklist>
-For EACH edit, verify:
-- [ ] Viewed the file first
-- [ ] Counted indentation spaces/tabs
-- [ ] Included blank lines if present
-- [ ] Matched brace/bracket positioning
-- [ ] Included 3β5 lines of surrounding context
-- [ ] Verified text appears exactly once (or using replace_all deliberately)
-- [ ] Copied text character-for-character, not approximated
-</whitespace_checklist>
-
-<examples>
-β
Correct: Sequential edits where the second match accounts for the first change
-
-```
-edits: [
- {
- old_string: "func A() {\n doOld()\n}",
- new_string: "func A() {\n doNew()\n}",
- },
- {
- // Uses context that still exists AFTER the first replacement
- old_string: "func B() {\n callA()\n}",
- new_string: "func B() {\n callA()\n logChange()\n}",
- },
-]
-```
-
-β Incorrect: Second old_string no longer matches due to whitespace change introduced by the first edit
-
-```
-edits: [
- {
- old_string: "func A() {\n doOld()\n}",
- new_string: "func A() {\n\n doNew()\n}", // Added extra blank line
- },
- {
- old_string: "func A() {\n doNew()\n}", // Missing the new blank line, will FAIL
- new_string: "func A() {\n doNew()\n logChange()\n}",
- },
-]
-```
-
-β
Correct: Handling partial success
-
-```
-// If edit 2 fails, edit 1 is still applied
-// Response will indicate:
-// - edits_applied: 1
-// - edits_failed: [{index: 2, error: "...", edit: {...}}]
-// You can then retry edit 2 with corrected context
-```
-</examples>
+Apply multiple find-and-replace edits to a single file in one operation; edits run sequentially. Prefer over edit for multiple changes to the same file. Same exact-match rules as edit apply.
@@ -28,12 +28,12 @@ type ReadMCPResourcePermissionsParams struct {
const ReadMCPResourceToolName = "read_mcp_resource"
//go:embed read_mcp_resource.md
-var readMCPResourceDescription []byte
+var readMCPResourceDescription string
func NewReadMCPResourceTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
ReadMCPResourceToolName,
- FirstLineDescription(readMCPResourceDescription),
+ readMCPResourceDescription,
func(ctx context.Context, params ReadMCPResourceParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
params.MCPName = strings.TrimSpace(params.MCPName)
params.URI = strings.TrimSpace(params.URI)
@@ -1,20 +1 @@
-Read a resource by URI from an MCP server; returns text content.
-
-<when_to_use>
-Use this tool to fetch a specific resource URI exposed by an MCP server.
-</when_to_use>
-
-<usage>
-- Provide MCP server name and resource URI
-- Returns resource text content
-</usage>
-
-<parameters>
-- mcp_name: The MCP server name
-- uri: The resource URI to read
-</parameters>
-
-<notes>
-- Returns text content by concatenating resource parts
-- Binary resources are returned as UTF-8 text when possible
-</notes>
+Read a resource by URI from an MCP server; returns text content.
@@ -31,12 +31,12 @@ type referencesTool struct {
const ReferencesToolName = "lsp_references"
//go:embed references.md
-var referencesDescription []byte
+var referencesDescription string
func NewReferencesTool(lspManager *lsp.Manager) fantasy.AgentTool {
return fantasy.NewAgentTool(
ReferencesToolName,
- FirstLineDescription(referencesDescription),
+ referencesDescription,
func(ctx context.Context, params ReferencesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Symbol == "" {
return fantasy.NewTextErrorResponse("symbol is required"), nil
@@ -1,26 +1 @@
-Find all references to a symbol by name via LSP; more accurate than grep for code symbols.
-
-<usage>
-- Provide symbol name (e.g., "MyFunction", "myVariable", "MyType").
-- Optional path to narrow search to a directory or file (defaults to current directory).
-- Tool automatically locates the symbol and returns all references.
-</usage>
-
-<features>
-- Semantic-aware reference search (more accurate than grep/glob).
-- Returns references grouped by file with line and column numbers.
-- Supports multiple programming languages via LSP.
-- Finds only real references (not comments or unrelated strings).
-</features>
-
-<limitations>
-- May not find references in files not opened or indexed by the LSP server.
-- Results depend on the capabilities of the active LSP providers.
-</limitations>
-
-<tips>
-- Use this first when searching for where a symbol is used.
-- Do not use grep/glob for symbol searches.
-- Narrow scope with the path parameter for faster, more relevant results.
-- Use qualified names (e.g., pkg.Func, Class.method) for higher precision.
-</tips>
+Find all references to a symbol by name via LSP; more accurate than grep for code symbols.
@@ -29,7 +29,7 @@ type SourcegraphResponseMetadata struct {
const SourcegraphToolName = "sourcegraph"
//go:embed sourcegraph.md
-var sourcegraphDescription []byte
+var sourcegraphDescription string
func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
if client == nil {
@@ -45,7 +45,7 @@ func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
}
return fantasy.NewParallelAgentTool(
SourcegraphToolName,
- FirstLineDescription(sourcegraphDescription),
+ sourcegraphDescription,
func(ctx context.Context, params SourcegraphParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Query == "" {
return fantasy.NewTextErrorResponse("Query parameter is required"), nil
@@ -1,55 +1 @@
-Search code across public GitHub repositories via Sourcegraph; supports regex, language/repo/file filters, and symbol search (max 20 results). Only searches public repos.
-
-<usage>
-- Provide search query using Sourcegraph syntax
-- Optional result count (default: 10, max: 20)
-- Optional timeout for request
-</usage>
-
-<basic_syntax>
-- "fmt.Println" - exact matches
-- "file:.go fmt.Println" - limit to Go files
-- "repo:^github\.com/golang/go$ fmt.Println" - specific repos
-- "lang:go fmt.Println" - limit to Go code
-- "fmt.Println AND log.Fatal" - combined terms
-- "fmt\.(Print|Printf|Println)" - regex patterns
-- "\"exact phrase\"" - exact phrase matching
-- "-file:test" or "-repo:forks" - exclude matches
-</basic_syntax>
-
-<key_filters>
-Repository: repo:name, repo:^exact$, repo:org/repo@branch, -repo:exclude, fork:yes, archived:yes, visibility:public
-File: file:\.js$, file:internal/, -file:test, file:has.content(text)
-Content: content:"exact", -content:"unwanted", case:yes
-Type: type:symbol, type:file, type:path, type:diff, type:commit
-Time: after:"1 month ago", before:"2023-01-01", author:name, message:"fix"
-Result: select:repo, select:file, select:content, count:100, timeout:30s
-</key_filters>
-
-<examples>
-- "file:.go context.WithTimeout" - Go code using context.WithTimeout
-- "lang:typescript useState type:symbol" - TypeScript React useState hooks
-- "repo:^github\.com/kubernetes/kubernetes$ pod list type:file" - Kubernetes pod files
-- "file:Dockerfile (alpine OR ubuntu) -content:alpine:latest" - Dockerfiles with base images
-</examples>
-
-<boolean_operators>
-- "term1 AND term2" - both terms
-- "term1 OR term2" - either term
-- "term1 NOT term2" - term1 but not term2
-- "term1 and (term2 or term3)" - grouping with parentheses
-</boolean_operators>
-
-<limitations>
-- Only searches public repositories
-- Rate limits may apply
-- Complex queries take longer
-- Max 20 results per query
-</limitations>
-
-<tips>
-- Use specific file extensions to narrow results
-- Add repo: filters for targeted searches
-- Use type:symbol for function/method definitions
-- Use type:file to find relevant files
-</tips>
+Search code across public GitHub repositories via Sourcegraph; supports regex, language/repo/file filters, and symbol search (max 20 results). Only searches public repos.
@@ -10,7 +10,7 @@ import (
)
//go:embed todos.md
-var todosDescription []byte
+var todosDescription string
const TodosToolName = "todos"
@@ -36,7 +36,7 @@ type TodosResponseMetadata struct {
func NewTodosTool(sessions session.Service) fantasy.AgentTool {
return fantasy.NewAgentTool(
TodosToolName,
- FirstLineDescription(todosDescription),
+ todosDescription,
func(ctx context.Context, params TodosParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
sessionID := GetSessionFromContext(ctx)
if sessionID == "" {
@@ -1,90 +1 @@
-Manage a structured task list for multi-step work; each task has pending/in_progress/completed state. Keep exactly one task in_progress at a time. Skip for simple or single-step tasks.
-
-<when_to_use>
-Use this tool proactively in these scenarios:
-
-- Complex multi-step tasks requiring 3+ distinct steps or actions
-- Non-trivial tasks requiring careful planning or multiple operations
-- User explicitly requests todo list management
-- User provides multiple tasks (numbered or comma-separated list)
-- After receiving new instructions to capture requirements
-- When starting work on a task (mark as in_progress BEFORE beginning)
-- After completing a task (mark completed and add new follow-up tasks)
-</when_to_use>
-
-<when_not_to_use>
-Skip this tool when:
-
-- Single, straightforward task
-- Trivial task with no organizational benefit
-- Task completable in less than 3 trivial steps
-- Purely conversational or informational request
-</when_not_to_use>
-
-<task_states>
-- **pending**: Task not yet started
-- **in_progress**: Currently working on (limit to ONE task at a time)
-- **completed**: Task finished successfully
-
-**IMPORTANT**: Each task requires two forms:
-- **content**: Imperative form describing what needs to be done (e.g., "Run tests", "Build the project")
-- **active_form**: Present continuous form shown during execution (e.g., "Running tests", "Building the project")
-</task_states>
-
-<task_management>
-- Update task status in real-time as you work
-- Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
-- Exactly ONE task must be in_progress at any time (not less, not more)
-- Complete current tasks before starting new ones
-- Remove tasks that are no longer relevant from the list entirely
-</task_management>
-
-<completion_requirements>
-ONLY mark a task as completed when you have FULLY accomplished it.
-
-Never mark completed if:
-- Tests are failing
-- Implementation is partial
-- You encountered unresolved errors
-- You couldn't find necessary files or dependencies
-
-If blocked:
-- Keep task as in_progress
-- Create new task describing what needs to be resolved
-</completion_requirements>
-
-<task_breakdown>
-- Create specific, actionable items
-- Break complex tasks into smaller, manageable steps
-- Use clear, descriptive task names
-- Always provide both content and active_form
-</task_breakdown>
-
-<examples>
-β
Good task:
-```json
-{
- "content": "Implement user authentication with JWT tokens",
- "status": "in_progress",
- "active_form": "Implementing user authentication with JWT tokens"
-}
-```
-
-β Bad task (missing active_form):
-```json
-{
- "content": "Fix bug",
- "status": "pending"
-}
-```
-</examples>
-
-<output_behavior>
-**NEVER** print or list todos in your response text. The user sees the todo list in real-time in the UI.
-</output_behavior>
-
-<tips>
-- When in doubt, use this tool - being proactive demonstrates attentiveness
-- One task in_progress at a time keeps work focused
-- Update immediately after state changes for accurate tracking
-</tips>
+Manage a structured task list for multi-step work; each task has pending/in_progress/completed state. Keep exactly one task in_progress at a time. Skip for simple or single-step tasks.
@@ -2,10 +2,6 @@ package tools
import (
"context"
- "os"
- "strconv"
- "strings"
- "testing"
"charm.land/fantasy"
)
@@ -68,21 +64,3 @@ func NewPermissionDeniedResponse() fantasy.ToolResponse {
resp.StopTurn = true
return resp
}
-
-// FirstLineDescription returns just the first non-empty line from the embedded
-// markdown description. The full description can be used by setting
-// CRUSH_SHORT_TOOL_DESCRIPTIONS=0.
-func FirstLineDescription(content []byte) string {
- if !testing.Testing() {
- if v, err := strconv.ParseBool(os.Getenv("CRUSH_SHORT_TOOL_DESCRIPTIONS")); err == nil && !v {
- return strings.TrimSpace(string(content))
- }
- }
- for line := range strings.SplitSeq(string(content), "\n") {
- line = strings.TrimSpace(line)
- if line != "" {
- return line
- }
- }
- return ""
-}
@@ -24,7 +24,7 @@ import (
)
//go:embed view.md
-var viewDescription []byte
+var viewDescription string
type ViewParams struct {
FilePath string `json:"file_path" description:"The path to the file to read"`
@@ -79,7 +79,7 @@ func NewViewTool(
) fantasy.AgentTool {
return fantasy.NewAgentTool(
ViewToolName,
- FirstLineDescription(viewDescription),
+ viewDescription,
func(ctx context.Context, params ViewParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.FilePath == "" {
return fantasy.NewTextErrorResponse("file_path is required"), nil
@@ -1,38 +1 @@
-Read a file by path with line numbers; supports offset and line limit (default 2000, max 200KB returned file content section); renders images (PNG, JPEG, GIF, WebP); use ls for directories.
-
-<usage>
-- Provide file path to read
-- Optional offset: start reading from specific line (0-based)
-- Optional limit: control lines read (default 2000)
-- Don't use for directories (use LS tool instead)
-- Supports image files (PNG, JPEG, GIF, WebP)
-</usage>
-
-<features>
-- Displays contents with line numbers
-- Can read from any file position using offset
-- Handles large files by limiting lines read
-- Auto-truncates very long lines for display
-- Suggests similar filenames when file not found
-- Renders image files directly in terminal
-</features>
-
-<limitations>
-- Max returned file content section: 200KB after offset/limit are applied
-- Default limit: 2000 lines
-- Lines >2000 chars truncated
-- Binary files (except images) cannot be displayed
-</limitations>
-
-<cross_platform>
-- Handles Windows (CRLF) and Unix (LF) line endings
-- Works with forward slashes (/) and backslashes (\)
-- Auto-detects text encoding for common formats
-</cross_platform>
-
-<tips>
-- Use with Glob to find files first
-- For code exploration: Grep to find relevant files, then View to examine
-- For large files: use offset parameter for specific sections
-- View tool automatically detects and renders image files
-</tips>
+Read a file by path with line numbers; supports offset and line limit (default 2000, max 200KB); renders images (PNG, JPEG, GIF, BMP, SVG, WebP); use ls for directories.
@@ -13,7 +13,7 @@ import (
)
//go:embed web_fetch.md
-var webFetchToolDescription []byte
+var webFetchToolDescription string
// NewWebFetchTool creates a simple web fetch tool for sub-agents (no permissions needed).
func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
@@ -31,7 +31,7 @@ func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
WebFetchToolName,
- FirstLineDescription(webFetchToolDescription),
+ webFetchToolDescription,
func(ctx context.Context, params WebFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.URL == "" {
return fantasy.NewTextErrorResponse("url is required"), nil
@@ -1,28 +1 @@
-Fetch a web URL and return content as markdown; for use inside sub-agents. Large pages (>50KB) are saved to a temp file for grep/view.
-
-<usage>
-- Provide a URL to fetch
-- The tool fetches the content and returns it as markdown
-- Use this when you need to follow links from the current page
-- After fetching, analyze the content to answer the user's question
-</usage>
-
-<features>
-- Automatically converts HTML to markdown for easier analysis
-- For large pages (>50KB), saves content to a temporary file and provides the path
-- You can then use grep/view tools to search through the file
-- Handles UTF-8 content validation
-</features>
-
-<limitations>
-- Max response size: 5MB
-- Only supports HTTP and HTTPS protocols
-- Cannot handle authentication or cookies
-- Some websites may block automated requests
-</limitations>
-
-<tips>
-- For large pages saved to files, use grep to find relevant sections first
-- Don't fetch unnecessary pages - only when needed to answer the question
-- Focus on extracting specific information from the fetched content
-</tips>
+Fetch a web URL and return content as markdown; for use inside sub-agents. Large pages (>50KB) are saved to a temp file for grep/view.
@@ -11,7 +11,7 @@ import (
)
//go:embed web_search.md
-var webSearchToolDescription []byte
+var webSearchToolDescription string
// NewWebSearchTool creates a web search tool for sub-agents (no permissions needed).
func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
@@ -29,7 +29,7 @@ func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
WebSearchToolName,
- FirstLineDescription(webSearchToolDescription),
+ webSearchToolDescription,
func(ctx context.Context, params WebSearchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Query == "" {
return fantasy.NewTextErrorResponse("query is required"), nil
@@ -1,18 +1 @@
-Search the web via DuckDuckGo; returns titles, URLs, and snippets. Follow up with web_fetch to get full page content.
-
-<usage>
-- Provide a search query to find information on the web
-- Returns a list of search results with titles, URLs, and snippets
-- Use this to find relevant web pages, then use web_fetch to get full content
-</usage>
-
-<parameters>
-- query: The search query string (required)
-- max_results: Maximum number of results to return (default: 10, max: 20)
-</parameters>
-
-<tips>
-- Use specific, targeted search queries for better results
-- After getting results, use web_fetch to get the full content of relevant pages
-- Combine multiple searches to gather comprehensive information
-</tips>
+Search the web via DuckDuckGo; returns titles, URLs, and snippets. Follow up with web_fetch to get full page content.
@@ -22,7 +22,7 @@ import (
)
//go:embed write.md
-var writeDescription []byte
+var writeDescription string
type WriteParams struct {
FilePath string `json:"file_path" description:"The path to the file to write"`
@@ -52,7 +52,7 @@ func NewWriteTool(
) fantasy.AgentTool {
return fantasy.NewAgentTool(
WriteToolName,
- FirstLineDescription(writeDescription),
+ writeDescription,
func(ctx context.Context, params WriteParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.FilePath == "" {
return fantasy.NewTextErrorResponse("file_path is required"), nil
@@ -1,30 +1 @@
-Create or overwrite a file with given content; auto-creates parent dirs. Cannot append. Read the file first to avoid conflicts. For surgical changes use edit or multiedit.
-
-<usage>
-- Provide file path to write
-- Include content to write to file (may be empty for a new empty file)
-- Tool creates necessary parent directories automatically
-</usage>
-
-<features>
-- Creates new files or overwrites existing ones
-- Auto-creates parent directories if missing
-- Checks if file modified since last read for safety
-- Avoids unnecessary writes when content unchanged
-</features>
-
-<limitations>
-- Read file before writing to avoid conflicts
-- Cannot append (rewrites entire file)
-</limitations>
-
-<cross_platform>
-- Use forward slashes (/) for compatibility
-</cross_platform>
-
-<tips>
-- Use View tool first to examine existing files before modifying
-- Use LS tool to verify location when creating new files
-- Combine with Glob/Grep to find and modify multiple files
-- Include descriptive comments when changing existing code
-</tips>
+Create or overwrite a file with given content; auto-creates parent dirs. Cannot append. Read the file first to avoid conflicts. For surgical changes use edit or multiedit.