Skip to content

Commit 62f8df3

Browse files
committed
feat(agent): implement agentStart and agentMessage tools
Implements the agentStart and agentMessage tools as described in issue #111. These tools provide an asynchronous approach to working with sub-agents, allowing the parent agent to: - Start multiple sub-agents in parallel - Monitor sub-agent progress - Provide guidance to sub-agents - Terminate sub-agents if needed The implementation follows the pattern of shellStart and shellMessage tools and maintains backward compatibility with the existing subAgent tool. Resolves: #111
1 parent 3d3e76b commit 62f8df3

File tree

6 files changed

+564
-0
lines changed

6 files changed

+564
-0
lines changed

docs/tools/agent-tools.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Agent Tools
2+
3+
The agent tools provide ways to create and interact with sub-agents. There are two approaches available:
4+
5+
1. The original `subAgent` tool (synchronous, blocking)
6+
2. The new `agentStart` and `agentMessage` tools (asynchronous, non-blocking)
7+
8+
## subAgent Tool
9+
10+
The `subAgent` tool creates a sub-agent that runs synchronously until completion. The parent agent waits for the sub-agent to complete before continuing.
11+
12+
```typescript
13+
subAgent({
14+
description: "A brief description of the sub-agent's purpose",
15+
goal: "The main objective that the sub-agent needs to achieve",
16+
projectContext: "Context about the problem or environment",
17+
workingDirectory: "/path/to/working/directory", // optional
18+
relevantFilesDirectories: "src/**/*.ts", // optional
19+
});
20+
```
21+
22+
## agentStart and agentMessage Tools
23+
24+
The `agentStart` and `agentMessage` tools provide an asynchronous approach to working with sub-agents. This allows the parent agent to:
25+
26+
- Start multiple sub-agents in parallel
27+
- Monitor sub-agent progress
28+
- Provide guidance to sub-agents
29+
- Terminate sub-agents if needed
30+
31+
### agentStart
32+
33+
The `agentStart` tool creates a sub-agent and immediately returns an instance ID. The sub-agent runs asynchronously in the background.
34+
35+
```typescript
36+
const { instanceId } = agentStart({
37+
description: "A brief description of the sub-agent's purpose",
38+
goal: "The main objective that the sub-agent needs to achieve",
39+
projectContext: "Context about the problem or environment",
40+
workingDirectory: "/path/to/working/directory", // optional
41+
relevantFilesDirectories: "src/**/*.ts", // optional
42+
enableUserPrompt: false, // optional, default: false
43+
});
44+
```
45+
46+
### agentMessage
47+
48+
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.
49+
50+
```typescript
51+
// Check agent progress
52+
const { output, completed } = agentMessage({
53+
instanceId: "agent-instance-id",
54+
description: "Checking agent progress",
55+
});
56+
57+
// Provide guidance (note: guidance implementation is limited in the current version)
58+
agentMessage({
59+
instanceId: "agent-instance-id",
60+
guidance: "Focus on the task at hand and avoid unnecessary exploration",
61+
description: "Providing guidance to the agent",
62+
});
63+
64+
// Terminate the agent
65+
agentMessage({
66+
instanceId: "agent-instance-id",
67+
terminate: true,
68+
description: "Terminating the agent",
69+
});
70+
```
71+
72+
## Example: Using agentStart and agentMessage to run multiple sub-agents in parallel
73+
74+
```typescript
75+
// Start multiple sub-agents
76+
const agent1 = agentStart({
77+
description: "Agent 1",
78+
goal: "Implement feature A",
79+
projectContext: "Project X",
80+
});
81+
82+
const agent2 = agentStart({
83+
description: "Agent 2",
84+
goal: "Implement feature B",
85+
projectContext: "Project X",
86+
});
87+
88+
// Check progress of both agents
89+
let agent1Completed = false;
90+
let agent2Completed = false;
91+
92+
while (!agent1Completed || !agent2Completed) {
93+
if (!agent1Completed) {
94+
const result1 = agentMessage({
95+
instanceId: agent1.instanceId,
96+
description: "Checking Agent 1 progress",
97+
});
98+
agent1Completed = result1.completed;
99+
100+
if (agent1Completed) {
101+
console.log("Agent 1 completed with result:", result1.output);
102+
}
103+
}
104+
105+
if (!agent2Completed) {
106+
const result2 = agentMessage({
107+
instanceId: agent2.instanceId,
108+
description: "Checking Agent 2 progress",
109+
});
110+
agent2Completed = result2.completed;
111+
112+
if (agent2Completed) {
113+
console.log("Agent 2 completed with result:", result2.output);
114+
}
115+
}
116+
117+
// Wait before checking again
118+
if (!agent1Completed || !agent2Completed) {
119+
sleep({ seconds: 5 });
120+
}
121+
}
122+
```
123+
124+
## Choosing Between Approaches
125+
126+
- Use `subAgent` for simpler tasks where blocking execution is acceptable
127+
- Use `agentStart` and `agentMessage` for:
128+
- Parallel execution of multiple sub-agents
129+
- Tasks where you need to monitor progress
130+
- Situations where you may need to provide guidance or terminate early

packages/agent/src/tools/getTools.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Tool } from '../core/types.js';
33
// Import tools
44
import { browseMessageTool } from './browser/browseMessage.js';
55
import { browseStartTool } from './browser/browseStart.js';
6+
import { agentMessageTool } from './interaction/agentMessage.js';
7+
import { agentStartTool } from './interaction/agentStart.js';
68
import { subAgentTool } from './interaction/subAgent.js';
79
import { userPromptTool } from './interaction/userPrompt.js';
810
import { fetchTool } from './io/fetch.js';
@@ -26,6 +28,8 @@ export function getTools(options?: GetToolsOptions): Tool[] {
2628
const tools: Tool[] = [
2729
textEditorTool as unknown as Tool,
2830
subAgentTool as unknown as Tool,
31+
agentStartTool as unknown as Tool,
32+
agentMessageTool as unknown as Tool,
2933
sequenceCompleteTool as unknown as Tool,
3034
fetchTool as unknown as Tool,
3135
shellStartTool as unknown as Tool,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import { agentMessageTool } from '../agentMessage.js';
4+
import { agentStartTool, agentStates } from '../agentStart.js';
5+
6+
// Mock the toolAgent function
7+
vi.mock('../../../core/toolAgent/toolAgentCore.js', () => ({
8+
toolAgent: vi.fn().mockResolvedValue({
9+
result: 'Mock agent result',
10+
interactions: 1,
11+
}),
12+
}));
13+
14+
// Mock context
15+
const mockContext = {
16+
logger: {
17+
info: vi.fn(),
18+
verbose: vi.fn(),
19+
error: vi.fn(),
20+
debug: vi.fn(),
21+
warn: vi.fn(),
22+
},
23+
tokenTracker: {
24+
tokenUsage: {
25+
add: vi.fn(),
26+
},
27+
},
28+
workingDirectory: '/test',
29+
};
30+
31+
describe('Agent Tools', () => {
32+
describe('agentStartTool', () => {
33+
it('should start an agent and return an instance ID', async () => {
34+
const result = await agentStartTool.execute(
35+
{
36+
description: 'Test agent',
37+
goal: 'Test the agent tools',
38+
projectContext: 'Testing environment',
39+
},
40+
mockContext,
41+
);
42+
43+
expect(result).toHaveProperty('instanceId');
44+
expect(result).toHaveProperty('status');
45+
expect(result.status).toBe('Agent started successfully');
46+
47+
// Verify the agent state was created
48+
expect(agentStates.has(result.instanceId)).toBe(true);
49+
50+
const state = agentStates.get(result.instanceId);
51+
expect(state).toHaveProperty('goal', 'Test the agent tools');
52+
expect(state).toHaveProperty('prompt');
53+
expect(state).toHaveProperty('completed', false);
54+
expect(state).toHaveProperty('aborted', false);
55+
});
56+
});
57+
58+
describe('agentMessageTool', () => {
59+
it('should retrieve agent state', async () => {
60+
// First start an agent
61+
const startResult = await agentStartTool.execute(
62+
{
63+
description: 'Test agent for message',
64+
goal: 'Test the agent message tool',
65+
projectContext: 'Testing environment',
66+
},
67+
mockContext,
68+
);
69+
70+
// Then get its state
71+
const messageResult = await agentMessageTool.execute(
72+
{
73+
instanceId: startResult.instanceId,
74+
description: 'Checking agent status',
75+
},
76+
mockContext,
77+
);
78+
79+
expect(messageResult).toHaveProperty('output');
80+
expect(messageResult).toHaveProperty('completed', false);
81+
});
82+
83+
it('should handle non-existent agent IDs', async () => {
84+
const result = await agentMessageTool.execute(
85+
{
86+
instanceId: 'non-existent-id',
87+
description: 'Checking non-existent agent',
88+
},
89+
mockContext,
90+
);
91+
92+
expect(result).toHaveProperty('error');
93+
expect(result.error).toContain('No sub-agent found with ID');
94+
});
95+
96+
it('should terminate an agent when requested', async () => {
97+
// First start an agent
98+
const startResult = await agentStartTool.execute(
99+
{
100+
description: 'Test agent for termination',
101+
goal: 'Test agent termination',
102+
projectContext: 'Testing environment',
103+
},
104+
mockContext,
105+
);
106+
107+
// Then terminate it
108+
const messageResult = await agentMessageTool.execute(
109+
{
110+
instanceId: startResult.instanceId,
111+
terminate: true,
112+
description: 'Terminating agent',
113+
},
114+
mockContext,
115+
);
116+
117+
expect(messageResult).toHaveProperty('terminated', true);
118+
expect(messageResult).toHaveProperty('completed', true);
119+
120+
// Verify the agent state was updated
121+
const state = agentStates.get(startResult.instanceId);
122+
expect(state).toHaveProperty('aborted', true);
123+
expect(state).toHaveProperty('completed', true);
124+
});
125+
});
126+
});

0 commit comments

Comments
 (0)