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
9 changes: 3 additions & 6 deletions pkg/tools/builtin/deferred.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,10 @@ func (d *DeferredToolset) HasSources() bool {
func (d *DeferredToolset) Instructions() string {
return `## Deferred Tool Loading

This agent has access to additional tools that can be discovered and loaded on-demand.
Additional tools can be discovered and loaded on-demand.

Use the search_tool to find available tools by name or description pattern.
When searching a tool, prefer to search by action keywords (e.g., "remote", "read", "write") rather than specific tool names.
Use single words to maximize matching results.

Use the add_tool to activate a discovered tool for use.`
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.`
}

type SearchToolArgs struct {
Expand Down
15 changes: 2 additions & 13 deletions pkg/tools/builtin/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,20 +287,9 @@ func WithTimeout(timeout time.Duration) FetchToolOption {
}

func (t *FetchTool) Instructions() string {
return `## "fetch" tool instructions
return `## Fetch Tool

This tool allows you to fetch content from HTTP and HTTPS URLs.

FEATURES

- Support for multiple URLs in a single call
- Returns response body and metadata (status code, content type, length)
- Specify the output format (text, markdown, html)
- Respects robots.txt restrictions

USAGE TIPS
- Use single URLs for simple content fetching
- Use multiple URLs for batch operations`
Fetch content from HTTP/HTTPS URLs. Supports multiple URLs in a single call, output format selection (text, markdown, html), and respects robots.txt.`
}

func (t *FetchTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/tools/builtin/fetch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestFetchTool_Instructions(t *testing.T) {

instructions := tools.GetInstructions(tool)

assert.Contains(t, instructions, `"fetch" tool instructions`)
assert.Contains(t, instructions, "Fetch Tool")
}

func TestFetchTool_StartStop(t *testing.T) {
Expand Down
346 changes: 31 additions & 315 deletions pkg/tools/builtin/lsp.go

Large diffs are not rendered by default.

14 changes: 4 additions & 10 deletions pkg/tools/builtin/lsp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package builtin
import (
"encoding/json"
"os/exec"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -68,14 +67,9 @@ func TestLSPTool_ToolDescriptions(t *testing.T) {
// Each tool should have a non-empty description
assert.NotEmpty(t, tool.Description, "Tool %s should have a description", tool.Name)

// Each description should be detailed (more than 100 chars)
assert.Greater(t, len(tool.Description), 100,
"Tool %s should have a detailed description, got: %s", tool.Name, tool.Description)

// Each tool should mention the output format or example
assert.True(t,
strings.Contains(tool.Description, "Output format") || strings.Contains(tool.Description, "Example"),
"Tool %s should document output format or provide example", tool.Name)
// Each description should be meaningful (more than 50 chars)
assert.Greater(t, len(tool.Description), 50,
"Tool %s should have a meaningful description, got: %s", tool.Name, tool.Description)
}
}

Expand All @@ -86,7 +80,7 @@ func TestLSPTool_Instructions(t *testing.T) {
instructions := tool.Instructions()

// Should mention the tools are stateless
assert.Contains(t, instructions, "stateless")
assert.Contains(t, instructions, "Stateless")

// Should list available operations
assert.Contains(t, instructions, "lsp_hover")
Expand Down
7 changes: 2 additions & 5 deletions pkg/tools/builtin/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,8 @@ type DeleteMemoryArgs struct {
func (t *MemoryTool) Instructions() string {
return `## Using the memory tool

Before taking any action or responding to the user use the "get_memories" tool to remember things about the user.
Do not talk about using the tool, just use it.

## Rules
- Use the memory tool generously to remember things about the user.`
Before taking any action or responding, use "get_memories" to recall stored information about the user.
Use the memory tool generously to remember things about the user. Do not mention using this tool.`
}

func (t *MemoryTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
162 changes: 18 additions & 144 deletions pkg/tools/builtin/shell_instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,162 +7,36 @@ Execute shell commands in the user's environment with full control over working

## Core Concepts

**Execution Context**: Commands run in the user's default shell with access to all environment variables and the current workspace.
On Windows, PowerShell (pwsh/powershell) is used when available; otherwise, cmd.exe is used.
On Unix-like systems, ${SHELL} is used or /bin/sh as fallback.

**Working Directory Management**:
- Default execution location: working directory of the agent
- Override with "cwd" parameter for targeted command execution
- Supports both absolute and relative paths

**Command Isolation**: Each tool call creates a fresh shell session - no state persists between executions.

**Timeout Protection**: Commands have a default 30-second timeout to prevent hanging. For longer operations, specify a custom timeout.

## Parameter Reference

| Parameter | Type | Required | Description |
|-----------|--------|----------|-------------|
| cmd | string | Yes | Shell command to execute |
| cwd | string | Yes | Working directory (use "." for current) |
| timeout | int | No | Timeout in seconds (default: 30) |
- Commands run in the user's default shell (Unix: ${SHELL} or /bin/sh; Windows: pwsh/powershell or cmd.exe)
- Each call creates a fresh shell session — no state persists between executions
- Default working directory: agent's working directory. Override with "cwd" parameter (absolute or relative paths)
- Default timeout: 30s. Use "timeout" parameter for longer operations (e.g., builds, tests)

## Best Practices

### ✅ DO
- Leverage the "cwd" parameter for directory-specific commands, rather than cding within commands
- Quote arguments containing spaces or special characters
- Use pipes and redirections
- Write advanced scripts with heredocs, that replace a lot of simple commands or tool calls
- This tool is great at reading and writing multiple files at once
- Avoid writing shell scripts to the disk. Instead, use heredocs to pipe the script to the SHELL
- Use the timeout parameter for long-running operations (e.g., builds, tests)

### Git Commits

When user asks to create git commit

- Add "Assisted-By: cagent" as a trailer line in the commit message
- Use the format: git commit -m "Your commit message" -m "" -m "Assisted-By: cagent"

## Usage Examples

**Basic command execution:**
{ "cmd": "ls -la", "cwd": "." }
- Use "cwd" instead of cd within commands
- Quote arguments with spaces or special characters
- Use pipes, redirections, and heredocs to combine operations
- Prefer inline heredocs over writing shell scripts to disk
- For git commits, add trailer: git commit -m "message" -m "" -m "Assisted-By: cagent"

**Long-running command with custom timeout:**
{ "cmd": "npm run build", "cwd": ".", "timeout": 120 }
## Examples

**Language-specific operations:**
{ "cmd": "go test ./...", "cwd": ".", "timeout": 180 }
{ "cmd": "npm install", "cwd": "frontend" }
{ "cmd": "python -m pytest tests/", "cwd": "backend", "timeout": 90 }

**File operations:**
{ "cmd": "find . -name '*.go' -type f", "cwd": "." }
{ "cmd": "grep -r 'TODO' src/", "cwd": "." }

**Process management:**
{ "cmd": "ps aux | grep node", "cwd": "." }
{ "cmd": "docker ps --format 'table {{.Names}}\t{{.Status}}'", "cwd": "." }

**Complex pipelines:**
{ "cmd": "cat package.json | jq '.dependencies'", "cwd": "frontend" }

**Bash scripts:**
{ "cmd": "cat << 'EOF' | ${SHELL}
echo Hello
EOF" }
{ "cmd": "cat << 'EOF' | ${SHELL}\necho Hello\nEOF" }

## Error Handling

Commands that exit with non-zero status codes will return error information along with any output produced before failure.
Commands that exceed their timeout will be terminated automatically.

---
Non-zero exit codes return error info with output. Timed-out commands are terminated automatically.

# Background Jobs

Run long-running processes in the background while continuing with other tasks. Perfect for starting servers, watching files, or any process that needs to run alongside other operations.

## When to Use Background Jobs

**Use background jobs for:**
- Starting web servers, databases, or other services
- Running file watchers or live reload tools
- Long-running processes that other tasks depend on
- Commands that produce continuous output over time

**Don't use background jobs for:**
- Quick commands that complete in seconds
- Commands where you need immediate results
- One-time operations (use regular shell tool instead)

## Background Job Tools

**run_background_job**: Start a command in the background
- Parameters: cmd (required), cwd (optional, defaults to ".")
- Returns: Job ID for tracking

**list_background_jobs**: List all background jobs
- No parameters required
- Returns: Status, runtime, and command for each job

**view_background_job**: View output of a specific job
- Parameters: job_id (required)
- Returns: Current output and job status

**stop_background_job**: Stop a running job
- Parameters: job_id (required)
- Terminates the process and all child processes

## Background Job Workflow

**1. Start a background job:**
{ "cmd": "npm start", "cwd": "frontend" }
→ Returns job ID (e.g., "job_1731772800_1")

**2. Check running jobs:**
Use list_background_jobs to see all jobs with their status

**3. View job output:**
{ "job_id": "job_1731772800_1" }
→ Shows current output and status

**4. Stop job when done:**
{ "job_id": "job_1731772800_1" }
→ Terminates the process and all child processes

## Important Characteristics

**Output Buffering**: Each job captures up to 10MB of output. Beyond this limit, new output is discarded to prevent memory issues.

**Process Groups**: Background jobs and all their child processes are managed as a group. Stopping a job terminates the entire process tree.

**Environment Inheritance**: Jobs inherit environment variables from when they are started. Changes after job start don't affect running jobs.

**Automatic Cleanup**: All background jobs are automatically terminated when the agent stops.

**Job Persistence**: Job history is kept in memory until agent stops. Completed jobs remain queryable.

## Background Job Examples

**Start a web server:**
{ "cmd": "python -m http.server 8000", "cwd": "." }

**Start a development server:**
{ "cmd": "npm run dev", "cwd": "frontend" }

**Run a file watcher:**
{ "cmd": "go run . watch", "cwd": "." }
Use background jobs for long-running processes (servers, watchers) that should run while other tasks are performed.

**Start a database:**
{ "cmd": "docker run --rm -p 5432:5432 postgres:latest", "cwd": "." }
- **run_background_job**: Start a command, returns job ID. Example: { "cmd": "npm run dev", "cwd": "frontend" }
- **list_background_jobs**: Show all jobs with status and runtime
- **view_background_job**: Get output and status of a job by job_id
- **stop_background_job**: Terminate a job and all its child processes by job_id

**Multiple services pattern:**
1. Start backend: run_background_job with server command
2. Start frontend: run_background_job with dev server
3. Perform tasks: use other tools while services run
4. Check logs: view_background_job to see service output
5. Cleanup: stop_background_job for each service (or let agent cleanup automatically)`
**Notes**: Output capped at 10MB per job. Jobs inherit env vars at start time. All jobs auto-terminate when the agent stops.`
21 changes: 5 additions & 16 deletions pkg/tools/builtin/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,11 @@ func NewTasksTool(storagePath string) *TasksTool {
func (t *TasksTool) Instructions() string {
return `## Using the Tasks Tools

These tools provide persistent task management with priorities, dependencies, and status tracking.

Tasks are persisted to a JSON file so they survive across sessions.

### Key concepts:
- **Priority**: critical > high > medium > low
- **Status**: pending, in_progress, done, blocked
- **Dependencies**: A task is automatically blocked if any of its dependencies are not done
- **Effective status**: The computed status taking dependencies into account

### Workflow:
1. Use create_task to create tasks with titles, descriptions, priorities, and dependencies
2. Use list_tasks to see all tasks sorted by priority (blocked tasks last)
3. Use next_task to find the highest-priority actionable task
4. Use update_task to change status, priority, or other fields as work progresses
5. Use add_dependency / remove_dependency to manage task ordering`
Persistent task management with priorities (critical > high > medium > low), statuses (pending, in_progress, done, blocked), and dependencies.

Tasks are saved to a JSON file and survive across sessions. A task is automatically blocked if any dependency is not done.

Workflow: create_task → list_tasks/next_task → update_task as work progresses. Use add_dependency/remove_dependency to manage ordering.`
}

func (t *TasksTool) load() taskStore {
Expand Down
11 changes: 4 additions & 7 deletions pkg/tools/builtin/think.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ func NewThinkTool() *ThinkTool {
func (t *ThinkTool) Instructions() string {
return `## Using the think tool

Before taking any action or responding to the user after receiving tool results, use the think tool as a scratchpad to:
- List the specific rules that apply to the current request
Use the think tool as a scratchpad before acting or responding. Use it to:
- List rules that apply to the current request
- Check if all required information is collected
- Verify that the planned action complies with all policies
- Iterate over tool results for correctness

## Rules
- Use the think tool generously to jot down thoughts and ideas.`
- Verify planned actions comply with policies
- Iterate over tool results for correctness`
}

func (t *ThinkTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down
50 changes: 8 additions & 42 deletions pkg/tools/builtin/user_prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,51 +79,17 @@ func (t *UserPromptTool) userPrompt(ctx context.Context, params UserPromptArgs)
func (t *UserPromptTool) Instructions() string {
return `## Using the user_prompt tool

Use the user_prompt tool when you need to ask the user a question or gather input interactively.
This tool displays a dialog to the user and waits for their response.

### When to use this tool:
- When you need clarification from the user before proceeding
- When you need to collect specific information (e.g., credentials, preferences, choices)
- When the user needs to make a decision between multiple options

### Schema support:
You can optionally provide a JSON schema to define the expected response structure:
- Object schemas with properties for collecting multiple fields
- Primitive type schemas (string, number, boolean) for simple inputs
- Enum types for presenting a list of choices
- Required fields to ensure necessary information is collected

### Example schemas:

Simple string input:
{
"type": "string",
"title": "API Key",
"description": "Enter your API key"
}
Use user_prompt to ask the user a question or gather input when you need clarification, specific information, or a decision.

Multiple choice:
{
"type": "string",
"enum": ["option1", "option2", "option3"],
"title": "Select an option"
}
Optionally provide a JSON schema to structure the expected response (object, primitive, or enum types).

Object with multiple fields:
{
"type": "object",
"properties": {
"username": {"type": "string", "description": "Your username"},
"remember": {"type": "boolean", "description": "Remember me"}
},
"required": ["username"]
}
Example schema for multiple choice:
{"type": "string", "enum": ["option1", "option2"], "title": "Select an option"}

Example schema for structured input:
{"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}

### Response format:
The tool returns a JSON object with:
- action: "accept" (user provided response), "decline" (user declined), or "cancel" (user cancelled)
- content: The user's response data (only present when action is "accept")`
Response contains "action" (accept/decline/cancel) and "content" (user data, only when accepted).`
}

func (t *UserPromptTool) Tools(context.Context) ([]tools.Tool, error) {
Expand Down