Skip to content
Merged
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
130 changes: 130 additions & 0 deletions docs/tools/agent-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Agent Tools

The agent tools provide ways to create and interact with sub-agents. There are two approaches available:

1. The original `subAgent` tool (synchronous, blocking)
2. The new `agentStart` and `agentMessage` tools (asynchronous, non-blocking)

## subAgent Tool

The `subAgent` tool creates a sub-agent that runs synchronously until completion. The parent agent waits for the sub-agent to complete before continuing.

```typescript
subAgent({
description: "A brief description of the sub-agent's purpose",
goal: "The main objective that the sub-agent needs to achieve",
projectContext: "Context about the problem or environment",
workingDirectory: "/path/to/working/directory", // optional
relevantFilesDirectories: "src/**/*.ts", // optional
});
```

## agentStart and agentMessage Tools

The `agentStart` and `agentMessage` tools provide an asynchronous approach to working with sub-agents. This allows the parent agent to:

- Start multiple sub-agents in parallel
- Monitor sub-agent progress
- Provide guidance to sub-agents
- Terminate sub-agents if needed

### agentStart

The `agentStart` tool creates a sub-agent and immediately returns an instance ID. The sub-agent runs asynchronously in the background.

```typescript
const { instanceId } = agentStart({
description: "A brief description of the sub-agent's purpose",
goal: "The main objective that the sub-agent needs to achieve",
projectContext: "Context about the problem or environment",
workingDirectory: "/path/to/working/directory", // optional
relevantFilesDirectories: "src/**/*.ts", // optional
enableUserPrompt: false, // optional, default: false
});
```

### agentMessage

The `agentMessage` tool allows interaction with a running sub-agent. It can be used to check the agent's progress, provide guidance, or terminate the agent.

```typescript
// Check agent progress
const { output, completed } = agentMessage({
instanceId: "agent-instance-id",
description: "Checking agent progress",
});

// Provide guidance (note: guidance implementation is limited in the current version)
agentMessage({
instanceId: "agent-instance-id",
guidance: "Focus on the task at hand and avoid unnecessary exploration",
description: "Providing guidance to the agent",
});

// Terminate the agent
agentMessage({
instanceId: "agent-instance-id",
terminate: true,
description: "Terminating the agent",
});
```

## Example: Using agentStart and agentMessage to run multiple sub-agents in parallel

```typescript
// Start multiple sub-agents
const agent1 = agentStart({
description: "Agent 1",
goal: "Implement feature A",
projectContext: "Project X",
});

const agent2 = agentStart({
description: "Agent 2",
goal: "Implement feature B",
projectContext: "Project X",
});

// Check progress of both agents
let agent1Completed = false;
let agent2Completed = false;

while (!agent1Completed || !agent2Completed) {
if (!agent1Completed) {
const result1 = agentMessage({
instanceId: agent1.instanceId,
description: "Checking Agent 1 progress",
});
agent1Completed = result1.completed;

if (agent1Completed) {
console.log("Agent 1 completed with result:", result1.output);
}
}

if (!agent2Completed) {
const result2 = agentMessage({
instanceId: agent2.instanceId,
description: "Checking Agent 2 progress",
});
agent2Completed = result2.completed;

if (agent2Completed) {
console.log("Agent 2 completed with result:", result2.output);
}
}

// Wait before checking again
if (!agent1Completed || !agent2Completed) {
sleep({ seconds: 5 });
}
}
```

## Choosing Between Approaches

- Use `subAgent` for simpler tasks where blocking execution is acceptable
- Use `agentStart` and `agentMessage` for:
- Parallel execution of multiple sub-agents
- Tasks where you need to monitor progress
- Situations where you may need to provide guidance or terminate early
4 changes: 4 additions & 0 deletions packages/agent/src/tools/getTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Tool } from '../core/types.js';
// Import tools
import { browseMessageTool } from './browser/browseMessage.js';
import { browseStartTool } from './browser/browseStart.js';
import { agentMessageTool } from './interaction/agentMessage.js';
import { agentStartTool } from './interaction/agentStart.js';
import { subAgentTool } from './interaction/subAgent.js';
import { userPromptTool } from './interaction/userPrompt.js';
import { fetchTool } from './io/fetch.js';
Expand All @@ -26,6 +28,8 @@ export function getTools(options?: GetToolsOptions): Tool[] {
const tools: Tool[] = [
textEditorTool as unknown as Tool,
subAgentTool as unknown as Tool,
agentStartTool as unknown as Tool,
agentMessageTool as unknown as Tool,
sequenceCompleteTool as unknown as Tool,
fetchTool as unknown as Tool,
shellStartTool as unknown as Tool,
Expand Down
126 changes: 126 additions & 0 deletions packages/agent/src/tools/interaction/__tests__/agentTools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, expect, it, vi } from 'vitest';

import { agentMessageTool } from '../agentMessage.js';
import { agentStartTool, agentStates } from '../agentStart.js';

// Mock the toolAgent function
vi.mock('../../../core/toolAgent/toolAgentCore.js', () => ({
toolAgent: vi.fn().mockResolvedValue({
result: 'Mock agent result',
interactions: 1,
}),
}));

// Mock context
const mockContext = {
logger: {
info: vi.fn(),
verbose: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
warn: vi.fn(),
},
tokenTracker: {
tokenUsage: {
add: vi.fn(),
},
},
workingDirectory: '/test',
};

describe('Agent Tools', () => {
describe('agentStartTool', () => {
it('should start an agent and return an instance ID', async () => {
const result = await agentStartTool.execute(
{
description: 'Test agent',
goal: 'Test the agent tools',
projectContext: 'Testing environment',
},
mockContext,
);

expect(result).toHaveProperty('instanceId');
expect(result).toHaveProperty('status');
expect(result.status).toBe('Agent started successfully');

// Verify the agent state was created
expect(agentStates.has(result.instanceId)).toBe(true);

const state = agentStates.get(result.instanceId);
expect(state).toHaveProperty('goal', 'Test the agent tools');
expect(state).toHaveProperty('prompt');
expect(state).toHaveProperty('completed', false);
expect(state).toHaveProperty('aborted', false);
});
});

describe('agentMessageTool', () => {
it('should retrieve agent state', async () => {
// First start an agent
const startResult = await agentStartTool.execute(
{
description: 'Test agent for message',
goal: 'Test the agent message tool',
projectContext: 'Testing environment',
},
mockContext,
);

// Then get its state
const messageResult = await agentMessageTool.execute(
{
instanceId: startResult.instanceId,
description: 'Checking agent status',
},
mockContext,
);

expect(messageResult).toHaveProperty('output');
expect(messageResult).toHaveProperty('completed', false);
});

it('should handle non-existent agent IDs', async () => {
const result = await agentMessageTool.execute(
{
instanceId: 'non-existent-id',
description: 'Checking non-existent agent',
},
mockContext,
);

expect(result).toHaveProperty('error');
expect(result.error).toContain('No sub-agent found with ID');
});

it('should terminate an agent when requested', async () => {
// First start an agent
const startResult = await agentStartTool.execute(
{
description: 'Test agent for termination',
goal: 'Test agent termination',
projectContext: 'Testing environment',
},
mockContext,
);

// Then terminate it
const messageResult = await agentMessageTool.execute(
{
instanceId: startResult.instanceId,
terminate: true,
description: 'Terminating agent',
},
mockContext,
);

expect(messageResult).toHaveProperty('terminated', true);
expect(messageResult).toHaveProperty('completed', true);

// Verify the agent state was updated
const state = agentStates.get(startResult.instanceId);
expect(state).toHaveProperty('aborted', true);
expect(state).toHaveProperty('completed', true);
});
});
});
Loading
Loading