Skip to content

Commit 1c44705

Browse files
committed
Merge remote-tracking branch 'origin/feature/agent-start-message-tools' into HEAD
2 parents d03efa9 + 0562ca3 commit 1c44705

File tree

5 files changed

+560
-0
lines changed

5 files changed

+560
-0
lines changed

Diff for: docs/tools/agent-tools.md

+130
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

Diff for: packages/agent/src/tools/getTools.ts

+4
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,
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)