Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_Anthropic_ToolCall.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_Gemini_ToolCall.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_Mistral_ToolCall.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_OpenAI_HideToolCalls.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions e2e/testdata/cassettes/TestExec_OpenAI_ToolCall.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interactions:
proto_minor: 1
content_length: 0
host: api.openai.com
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
url: https://api.openai.com/v1/responses
method: POST
response:
Expand Down Expand Up @@ -91,7 +91,7 @@ interactions:
proto_minor: 1
content_length: 0
host: api.openai.com
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tool Instructions\n\nThis toolset provides comprehensive filesystem operations.\n\n### Working Directory\n- Relative paths (like \".\" or \"src/main.go\") are resolved relative to the working directory\n- Absolute paths (like \"/etc/hosts\") access files directly\n- Paths starting with \"..\" can access parent directories\n\n### Common Patterns\n- Always check if directories exist before creating files\n- Prefer read_multiple_files for batch operations\n- Use search_files_content for finding specific code or text\n\n### Performance Tips\n- Use read_multiple_files instead of multiple read_file calls\n- Use directory_tree with max_depth to limit large traversals\n- Use appropriate exclude patterns in search operations","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"},{"arguments":"{\"content\":\"Hello, World!\",\"path\":\"hello.txt\"}","call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","name":"write_file","type":"function_call"},{"call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","output":"The user rejected the tool call.","type":"function_call_output"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
body: '{"input":[{"content":[{"text":"You are a knowledgeable assistant that can write test files.","type":"input_text"}],"role":"system"},{"content":[{"text":"## Filesystem Tools\n\n- Relative paths resolve from the working directory; absolute paths and \"..\" work as expected\n- Prefer read_multiple_files over sequential read_file calls\n- Use search_files_content to locate code or text across files\n- Use exclude patterns in searches and max_depth in directory_tree to limit output","type":"input_text"}],"role":"system"},{"content":"Create a hello.txt file with \"Hello, World!\" content. Try only once. On error, exit without further message.","role":"user"},{"arguments":"{\"content\":\"Hello, World!\",\"path\":\"hello.txt\"}","call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","name":"write_file","type":"function_call"},{"call_id":"call_5W18F6XkDh9NllAH9r0P9GuF","output":"The user rejected the tool call.","type":"function_call_output"}],"model":"gpt-5-mini","tools":[{"strict":true,"parameters":{"additionalProperties":false,"properties":{"content":{"description":"The content to write to the file","type":"string"},"path":{"description":"The file path to write","type":"string"}},"required":["content","path"],"type":"object"},"name":"write_file","description":"Create a new file or completely overwrite an existing file with new content.","type":"function"}],"stream":true}'
url: https://api.openai.com/v1/responses
method: POST
response:
Expand Down
4 changes: 2 additions & 2 deletions pkg/session/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func TestGetMessages_CacheControl(t *testing.T) {
assert.Equal(t, "instructions", messages[0].Content)
assert.False(t, messages[0].CacheControl)

assert.Contains(t, messages[1].Content, "Using the Todo Tools")
assert.Contains(t, messages[1].Content, "Todo Tools")
assert.True(t, messages[1].CacheControl)
}

Expand Down Expand Up @@ -212,7 +212,7 @@ func TestGetMessages_CacheControlWithSummary(t *testing.T) {
assert.Len(t, checkpointIndices, 2, "should have 2 checkpoints")

// Verify checkpoint #1 is on toolset instructions
assert.Contains(t, messages[checkpointIndices[0]].Content, "Using the Todo Tools", "checkpoint #1 should be on toolset instructions")
assert.Contains(t, messages[checkpointIndices[0]].Content, "Todo Tools", "checkpoint #1 should be on toolset instructions")

// Verify checkpoint #2 is on date
assert.Contains(t, messages[checkpointIndices[1]].Content, "Today's date", "checkpoint #2 should be on date message")
Expand Down
7 changes: 2 additions & 5 deletions pkg/tools/builtin/deferred.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,9 @@ func (d *DeferredToolset) HasSources() bool {
}

func (d *DeferredToolset) Instructions() string {
return `## Deferred Tool Loading
return `## Deferred Tools

Additional tools can be discovered and loaded on-demand.

Use search_tool to find tools by action keywords (e.g., "remote", "read", "write"). Prefer single words to maximize matches.
Use add_tool to activate a discovered tool.`
Use search_tool to discover additional tools by keyword (e.g., "remote", "read", "write"). Use add_tool to activate a discovered tool.`
}

type SearchToolArgs struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/tools/builtin/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func WithTimeout(timeout time.Duration) FetchToolOption {
func (t *FetchTool) Instructions() string {
return `## Fetch Tool

Fetch content from HTTP/HTTPS URLs. Supports multiple URLs in a single call, output format selection (text, markdown, html), and respects robots.txt.`
Fetch content from HTTP/HTTPS URLs. Supports multiple URLs per call, output format selection (text, markdown, html), and respects robots.txt.`
}

func (t *FetchTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
22 changes: 5 additions & 17 deletions pkg/tools/builtin/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,12 @@ func NewFilesystemTool(workingDir string, opts ...FileSystemOpt) *FilesystemTool
}

func (t *FilesystemTool) Instructions() string {
return `## Filesystem Tool Instructions
return `## Filesystem Tools

This toolset provides comprehensive filesystem operations.

### Working Directory
- Relative paths (like "." or "src/main.go") are resolved relative to the working directory
- Absolute paths (like "/etc/hosts") access files directly
- Paths starting with ".." can access parent directories

### Common Patterns
- Always check if directories exist before creating files
- Prefer read_multiple_files for batch operations
- Use search_files_content for finding specific code or text

### Performance Tips
- Use read_multiple_files instead of multiple read_file calls
- Use directory_tree with max_depth to limit large traversals
- Use appropriate exclude patterns in search operations`
- Relative paths resolve from the working directory; absolute paths and ".." work as expected
- Prefer read_multiple_files over sequential read_file calls
- Use search_files_content to locate code or text across files
- Use exclude patterns in searches and max_depth in directory_tree to limit output`
}

type DirectoryTreeArgs struct {
Expand Down
24 changes: 6 additions & 18 deletions pkg/tools/builtin/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,26 +83,14 @@ type UpdateMemoryArgs struct {
}

func (t *MemoryTool) Instructions() string {
return `## Using the memory tool
return `## Memory Tools

Before taking any action or responding, check stored memories for relevant context.
Use the memory tool generously to remember things about the user. Do not mention using this tool.
Check stored memories for relevant context before acting. Store useful information silently — never mention using this tool.

### When to remember
- User preferences, corrections, and explicit requests to remember something
- Key facts, decisions, and context that may be useful in future conversations
- Project-specific conventions and patterns

### Categories
Organize memories with a category when adding or updating (e.g. "preference", "fact", "project", "decision").

### Searching vs getting all
- Use "search_memories" with keywords and/or a category to find specific memories efficiently.
- Use "get_memories" only when you need a full dump of all stored memories.

### Updating vs creating
- Use "update_memory" to edit an existing memory by ID instead of deleting and re-adding.
- Use "add_memory" only for genuinely new information.`
- Remember: user preferences, corrections, key decisions, project conventions
- Use search_memories with keywords/category for targeted lookup; use get_memories only for a full dump
- Use update_memory to edit existing entries; use add_memory only for new information
- Organize with categories: "preference", "fact", "project", "decision"`
}

func (t *MemoryTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/tools/builtin/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ func TestMemoryTool_Instructions(t *testing.T) {
tool := NewMemoryTool(manager)

instructions := tool.Instructions()
assert.Contains(t, instructions, "Using the memory tool")
assert.Contains(t, instructions, "Memory Tools")
assert.Contains(t, instructions, "search_memories")
assert.Contains(t, instructions, "update_memory")
assert.Contains(t, instructions, "Categories")
assert.Contains(t, instructions, "preference")
}

func TestMemoryTool_DisplayNames(t *testing.T) {
Expand Down
8 changes: 3 additions & 5 deletions pkg/tools/builtin/model_picker.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@ func NewModelPickerTool(models []string) *ModelPickerTool {
// Instructions returns guidance for the LLM on when and how to use the model picker tools.
func (t *ModelPickerTool) Instructions() string {
return "## Model Switching\n\n" +
"You have access to multiple models and can switch between them mid-conversation " +
"using the `" + ToolNameChangeModel + "` and `" + ToolNameRevertModel + "` tools.\n\n" +
"Available models: " + strings.Join(t.models, ", ") + ".\n\n" +
"Use `" + ToolNameChangeModel + "` when the current task would benefit from a different model's strengths " +
"(e.g., switching to a faster model for simple tasks or a more capable model for complex reasoning).\n" +
"Use `" + ToolNameRevertModel + "` to return to the original model after the specialized task is complete."
"Use `" + ToolNameChangeModel + "` to switch to a model better suited for the current task " +
"(e.g., faster model for simple tasks, more capable model for complex reasoning).\n" +
"Use `" + ToolNameRevertModel + "` to return to the original model when done."
}

// AllowedModels returns the list of models this tool allows switching to.
Expand Down
30 changes: 13 additions & 17 deletions pkg/tools/builtin/script_shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,33 +71,29 @@ func validateConfig(toolName string, tool latest.ScriptShellToolConfig) error {
}

func (t *ScriptShellTool) Instructions() string {
var instructions strings.Builder
instructions.WriteString("## Custom Shell Tools\n\n")
instructions.WriteString("The following custom shell tools are available:\n\n")
var sb strings.Builder
sb.WriteString("## Custom Shell Tools\n\n")

for name, tool := range t.shellTools {
fmt.Fprintf(&instructions, "### %s\n", name)
fmt.Fprintf(&sb, "### %s\n", name)
if tool.Description != "" {
fmt.Fprintf(&instructions, "%s\n\n", tool.Description)
fmt.Fprintf(&sb, "%s\n", tool.Description)
} else {
fmt.Fprintf(&instructions, "Execute: `%s`\n\n", tool.Cmd)
fmt.Fprintf(&sb, "Runs: `%s`\n", tool.Cmd)
}

if len(tool.Args) > 0 {
instructions.WriteString("**Parameters:**\n")
for argName, argDef := range tool.Args {
required := ""
if slices.Contains(tool.Required, argName) {
required = " (required)"
}
description := argDef.(map[string]any)["description"].(string)
fmt.Fprintf(&instructions, "- `%s`: %s%s\n", argName, description, required)
for argName, argDef := range tool.Args {
description := argDef.(map[string]any)["description"].(string)
required := ""
if slices.Contains(tool.Required, argName) {
required = " (required)"
}
instructions.WriteString("\n")
fmt.Fprintf(&sb, "- `%s`: %s%s\n", argName, description, required)
}
sb.WriteString("\n")
}

return instructions.String()
return sb.String()
}

func (t *ScriptShellTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
13 changes: 12 additions & 1 deletion pkg/tools/builtin/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,18 @@ func formatCommandOutput(timeoutCtx, ctx context.Context, err error, rawOutput s
}

func (t *ShellTool) Instructions() string {
return nativeInstructions
return `## Shell Tools

- Each call runs in a fresh shell session — no state persists between calls
- Default timeout: 30s. Set "timeout" for longer operations (builds, tests)
- Use "cwd" parameter instead of cd within commands
- Combine operations with pipes, redirections, and heredocs
- For git commits, add trailer: git commit -m "message" -m "" -m "Assisted-By: docker-agent"
- Non-zero exit codes return error info with output; timed-out commands are terminated

### Background Jobs

Use run_background_job for long-running processes (servers, watchers). Output capped at 10MB per job. All jobs auto-terminate when the agent stops.`
}

func (t *ShellTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
42 changes: 0 additions & 42 deletions pkg/tools/builtin/shell_instructions.go

This file was deleted.

2 changes: 1 addition & 1 deletion pkg/tools/builtin/shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func TestShellTool_Instructions(t *testing.T) {
instructions := tool.Instructions()

// Check that native instructions are returned
assert.Contains(t, instructions, "Shell Tool Usage Guide")
assert.Contains(t, instructions, "Shell Tools")
}

func TestResolveWorkDir(t *testing.T) {
Expand Down
40 changes: 16 additions & 24 deletions pkg/tools/builtin/skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,29 +135,28 @@ func (s *SkillsToolset) handleReadSkillFile(_ context.Context, args readSkillFil
return tools.ResultSuccess(content), nil
}

func (s *SkillsToolset) Instructions() string {
if len(s.skills) == 0 {
return ""
}

hasFiles := false
// hasFiles reports whether any loaded skill has supporting files beyond SKILL.md.
func (s *SkillsToolset) hasFiles() bool {
for _, skill := range s.skills {
if len(skill.Files) > 1 {
hasFiles = true
break
return true
}
}
return false
}

func (s *SkillsToolset) Instructions() string {
if len(s.skills) == 0 {
return ""
}

var sb strings.Builder
sb.WriteString("The following skills provide specialized instructions for specific tasks. ")
sb.WriteString("Each skill's description indicates what it does and when to use it.\n\n")
sb.WriteString("When a user's request matches a skill's description, use the read_skill tool to load the skill's content. ")
sb.WriteString("The content contains detailed instructions to follow for that task.\n\n")
sb.WriteString("Skills provide specialized instructions for specific tasks. ")
sb.WriteString("When a user's request matches a skill's description, use read_skill to load its instructions.\n\n")

if hasFiles {
sb.WriteString("Some skills reference supporting files (scripts, documentation, templates). ")
sb.WriteString("When skill instructions reference a file path, use the read_skill_file tool to load it on demand. ")
sb.WriteString("Do not load all files upfront — only load them as needed.\n\n")
if s.hasFiles() {
sb.WriteString("Some skills have supporting files. ")
sb.WriteString("Use read_skill_file to load referenced files on demand — do not preload them.\n\n")
}

sb.WriteString("<available_skills>\n")
Expand Down Expand Up @@ -213,14 +212,7 @@ func (s *SkillsToolset) Tools(context.Context) ([]tools.Tool, error) {
}

// Only expose read_skill_file if any skill has supporting files
hasFiles := false
for _, skill := range s.skills {
if len(skill.Files) > 1 {
hasFiles = true
break
}
}
if hasFiles {
if s.hasFiles() {
result = append(result, tools.Tool{
Name: ToolNameReadSkillFile,
Category: "skills",
Expand Down
Loading
Loading