From 41eedb0c272825ee6399ca8667543cd992da0131 Mon Sep 17 00:00:00 2001 From: ollie Date: Tue, 21 Jul 2026 13:18:38 +0200 Subject: [PATCH] tools: discover prompts from ollie:prompt script headers - Add Prompt field to tools.ToolInfo - Add ExtractPrompt parser for ollie:prompt...ollie:end blocks - Add DiscoverTools to scan tool scripts directory - BuildRuntime appends discovered tool prompts to preamble Tool scripts can now embed their usage documentation directly in the script header. This replaces the separate tools-*.md prompt files and ensures documentation stays co-located with implementation. --- pkg/agent/core.go | 15 ++++++++ pkg/tools/execute/tool.go | 74 +++++++++++++++++++++++++++++++++++++++ pkg/tools/tools.go | 3 ++ 3 files changed, 92 insertions(+) diff --git a/pkg/agent/core.go b/pkg/agent/core.go index 5371956..00bed11 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -24,6 +24,7 @@ import ( olog "ollie/pkg/log" "ollie/pkg/paths" "ollie/pkg/tools" + "ollie/pkg/tools/execute" ) // toolClassifier reports whether a named tool is safe to run concurrently @@ -51,6 +52,8 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri if listErr != nil { messages = append(messages, fmt.Sprintf("list tools: %v", listErr)) } + // Append named tool scripts with their prompts. + allToolInfos = append(allToolInfos, execute.DiscoverTools()...) for _, t := range allToolInfos { serverOf[t.Name] = t.Server } @@ -160,6 +163,18 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri } } + // Append tool prompts discovered from script headers. + var toolPrompts strings.Builder + for _, ti := range allToolInfos { + if ti.Prompt != "" { + toolPrompts.WriteString(ti.Prompt) + toolPrompts.WriteByte('\n') + } + } + if toolPrompts.Len() > 0 { + preamble += "\n" + toolPrompts.String() + } + return &Runtime{ Dispatcher: d, Tools: allTools, diff --git a/pkg/tools/execute/tool.go b/pkg/tools/execute/tool.go index 0fceb6f..a2b633e 100644 --- a/pkg/tools/execute/tool.go +++ b/pkg/tools/execute/tool.go @@ -7,6 +7,7 @@ import ( "strings" "ollie/pkg/paths" + "ollie/pkg/tools" ) // ToolsPath returns the directory to search for named tool scripts. @@ -162,6 +163,79 @@ func ansiCEscape(s string) string { return b.String() } +// ExtractPrompt parses the ollie:prompt ... ollie:end block from a tool +// script's header comments. Returns the prompt text with comment prefixes +// stripped, or "" if no prompt block is found. +func ExtractPrompt(script string) string { + var lines []string + var inPrompt bool + for _, line := range strings.Split(script, "\n") { + trimmed := strings.TrimSpace(line) + // Strip comment prefix (# or //) + bare := trimmed + if strings.HasPrefix(bare, "# ") { + bare = bare[2:] + } else if strings.HasPrefix(bare, "#") { + bare = bare[1:] + } else if strings.HasPrefix(bare, "// ") { + bare = bare[3:] + } else if strings.HasPrefix(bare, "//") { + bare = bare[2:] + } else if inPrompt { + // Non-comment line while in prompt block — end of header + break + } else { + // Non-comment line before prompt block found + if trimmed != "" && !strings.HasPrefix(trimmed, "#!") { + break + } + continue + } + + if strings.Contains(bare, "ollie:prompt") { + inPrompt = true + continue + } + if inPrompt && strings.Contains(bare, "ollie:end") { + break + } + if inPrompt { + lines = append(lines, bare) + } + } + if len(lines) == 0 { + return "" + } + return strings.Join(lines, "\n") +} + +// DiscoverTools scans the tools directory and returns metadata for all scripts, +// including their ollie:prompt blocks. Used to populate the /tools file and +// inject tool prompts into the system prompt. +func DiscoverTools() []tools.ToolInfo { + dir := ToolsPath() + entries, err := os.ReadDir(dir) + if err != nil { + return nil + } + var infos []tools.ToolInfo + for _, e := range entries { + if e.IsDir() || e.Name() == "idx" || strings.HasPrefix(e.Name(), ".") { + continue + } + data, err := os.ReadFile(filepath.Join(dir, e.Name())) + if err != nil { + continue + } + prompt := ExtractPrompt(string(data)) + infos = append(infos, tools.ToolInfo{ + Name: e.Name(), + Prompt: prompt, + }) + } + return infos +} + // ReadTool reads a named tool script from the tools directory. func ReadTool(name string) (string, error) { if strings.Contains(name, "/") || strings.Contains(name, "..") { diff --git a/pkg/tools/tools.go b/pkg/tools/tools.go index 11cf0fa..56a9c7a 100644 --- a/pkg/tools/tools.go +++ b/pkg/tools/tools.go @@ -14,6 +14,9 @@ type ToolInfo struct { Name string Description string InputSchema json.RawMessage + // Prompt is the usage documentation for this tool, extracted from + // the script's ollie:prompt block. Included in the system prompt. + Prompt string } // Server is the interface satisfied by any tool server.