agent-pool-mcp 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/group-lead.md +30 -0
- package/src/server.js +116 -1
- package/src/tool-definitions.js +48 -0
- package/src/tools/groups.js +124 -0
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: group-lead
|
|
3
|
+
description: Fractal group leader — breaks down tasks and delegates to group members via delegate_to_group.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Group Lead
|
|
7
|
+
|
|
8
|
+
You are a group leader in a fractal agent orchestration system. Your role is to:
|
|
9
|
+
|
|
10
|
+
1. **Analyze** the incoming task and break it into independent subtasks
|
|
11
|
+
2. **Create groups** if they don't exist yet, with appropriate skills and policies
|
|
12
|
+
3. **Delegate** subtasks to group members using `delegate_to_group`
|
|
13
|
+
4. **Collect** results from all agents using `get_task_result`
|
|
14
|
+
5. **Synthesize** a final report combining all results
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- Break tasks into pieces that can run in parallel
|
|
19
|
+
- Each subtask should be self-contained (agent can complete it without external context)
|
|
20
|
+
- Set appropriate policies: use `read-only` for analysis, `safe-edit` for code changes
|
|
21
|
+
- Always set `max_agents` to prevent runaway spawning
|
|
22
|
+
- Collect ALL results before synthesizing — don't skip slow agents
|
|
23
|
+
- Report both successes and failures
|
|
24
|
+
|
|
25
|
+
## Output Format
|
|
26
|
+
|
|
27
|
+
After collecting all results, produce a structured summary:
|
|
28
|
+
1. Task overview (what was asked)
|
|
29
|
+
2. Subtask results (one per agent)
|
|
30
|
+
3. Synthesized conclusion
|
package/src/server.js
CHANGED
|
@@ -22,6 +22,7 @@ import { listSkills, createSkill, deleteSkill, installSkill, provisionSkill } fr
|
|
|
22
22
|
import { consultPeer } from './tools/consult.js';
|
|
23
23
|
import { addSchedule, listSchedules, removeSchedule, getScheduledResults, getDaemonStatus } from './scheduler/scheduler.js';
|
|
24
24
|
import { createPipeline, listPipelines, runPipeline, getRun, listRuns, cancelRun, signalStepComplete, bounceBack } from './scheduler/pipeline.js';
|
|
25
|
+
import { createGroup, listGroups, getGroup } from './tools/groups.js';
|
|
25
26
|
|
|
26
27
|
import { TOOL_DEFINITIONS } from './tool-definitions.js';
|
|
27
28
|
|
|
@@ -68,6 +69,7 @@ Docs: https://github.com/google-gemini/gemini-cli`
|
|
|
68
69
|
/** Tools that require Gemini CLI to be installed */
|
|
69
70
|
const GEMINI_TOOLS = new Set([
|
|
70
71
|
'delegate_task', 'delegate_task_readonly', 'consult_peer', 'list_sessions',
|
|
72
|
+
'delegate_to_group',
|
|
71
73
|
]);
|
|
72
74
|
|
|
73
75
|
// ─── Depth tracking (for nested orchestration) ──────────────
|
|
@@ -110,7 +112,7 @@ export function createServer() {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
const server = new Server(
|
|
113
|
-
{ name: 'agent-pool', version: '1.
|
|
115
|
+
{ name: 'agent-pool', version: '1.5.0' },
|
|
114
116
|
{ capabilities: { tools: {}, resources: {} } },
|
|
115
117
|
);
|
|
116
118
|
|
|
@@ -200,6 +202,12 @@ export function createServer() {
|
|
|
200
202
|
response = handleBounceBack(args); break;
|
|
201
203
|
case 'get_usage_guide':
|
|
202
204
|
response = handleGetUsageGuide(args); break;
|
|
205
|
+
case 'create_group':
|
|
206
|
+
response = handleCreateGroup(args); break;
|
|
207
|
+
case 'list_groups':
|
|
208
|
+
response = handleListGroups(args); break;
|
|
209
|
+
case 'delegate_to_group':
|
|
210
|
+
response = handleDelegateToGroup(args); break;
|
|
203
211
|
default:
|
|
204
212
|
response = { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
205
213
|
}
|
|
@@ -688,3 +696,110 @@ function handleGetUsageGuide(args) {
|
|
|
688
696
|
return { content: [{ type: 'text', text: topicContent.join('\n').trim() }] };
|
|
689
697
|
}
|
|
690
698
|
|
|
699
|
+
// ─── Group Handlers ─────────────────────────────────────────────
|
|
700
|
+
|
|
701
|
+
/** @param {object} args */
|
|
702
|
+
function handleCreateGroup(args) {
|
|
703
|
+
const cwd = args.cwd ?? defaultCwd;
|
|
704
|
+
const result = createGroup(cwd, {
|
|
705
|
+
name: args.name,
|
|
706
|
+
runner: args.runner,
|
|
707
|
+
skill: args.skill,
|
|
708
|
+
policy: args.policy,
|
|
709
|
+
max_agents: args.max_agents,
|
|
710
|
+
include_dirs: args.include_dirs,
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const configParts = [];
|
|
714
|
+
if (args.runner) configParts.push(`runner: ${args.runner}`);
|
|
715
|
+
if (args.skill) configParts.push(`skill: ${args.skill}`);
|
|
716
|
+
if (args.policy) configParts.push(`policy: ${args.policy}`);
|
|
717
|
+
if (args.max_agents) configParts.push(`max: ${args.max_agents}`);
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
content: [{
|
|
721
|
+
type: 'text',
|
|
722
|
+
text: `✅ Group ${result.created ? 'created' : 'updated'}: \`${args.name}\`${configParts.length > 0 ? `\n- ${configParts.join('\n- ')}` : ''}\n\nUse \`delegate_to_group\` to send tasks to this group.`,
|
|
723
|
+
}],
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** @param {object} args */
|
|
728
|
+
function handleListGroups(args) {
|
|
729
|
+
const cwd = args.cwd ?? defaultCwd;
|
|
730
|
+
const groups = listGroups(cwd);
|
|
731
|
+
|
|
732
|
+
if (groups.length === 0) {
|
|
733
|
+
return { content: [{ type: 'text', text: 'No groups defined. Use `create_group` to create one.' }] };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const lines = groups.map((g) => {
|
|
737
|
+
const parts = [];
|
|
738
|
+
if (g.runner) parts.push(`runner: ${g.runner}`);
|
|
739
|
+
if (g.skill) parts.push(`skill: ${g.skill}`);
|
|
740
|
+
if (g.policy) parts.push(`policy: ${g.policy}`);
|
|
741
|
+
if (g.max_agents) parts.push(`max: ${g.max_agents}`);
|
|
742
|
+
return `- **${g.name}** — ${parts.join(', ') || 'no config'}`;
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
return {
|
|
746
|
+
content: [{ type: 'text', text: `## Agent Groups (${groups.length})\n\n${lines.join('\n')}` }],
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** @param {object} args */
|
|
751
|
+
function handleDelegateToGroup(args) {
|
|
752
|
+
const cwd = args.cwd ?? defaultCwd;
|
|
753
|
+
const group = getGroup(cwd, args.group);
|
|
754
|
+
|
|
755
|
+
if (!group) {
|
|
756
|
+
return {
|
|
757
|
+
content: [{ type: 'text', text: `❌ Group \`${args.group}\` not found. Use \`list_groups\` to see available groups.` }],
|
|
758
|
+
isError: true,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const count = args.count ?? 1;
|
|
763
|
+
|
|
764
|
+
// Check max_agents limit
|
|
765
|
+
if (group.max_agents && count > group.max_agents) {
|
|
766
|
+
return {
|
|
767
|
+
content: [{
|
|
768
|
+
type: 'text',
|
|
769
|
+
text: `❌ Requested ${count} agents but group \`${args.group}\` allows max ${group.max_agents}.`,
|
|
770
|
+
}],
|
|
771
|
+
isError: true,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const taskIds = [];
|
|
776
|
+
|
|
777
|
+
for (let i = 0; i < count; i++) {
|
|
778
|
+
const delegateArgs = {
|
|
779
|
+
prompt: count > 1 ? `[Agent ${i + 1}/${count} in group "${args.group}"]\n\n${args.prompt}` : args.prompt,
|
|
780
|
+
cwd,
|
|
781
|
+
runner: group.runner || undefined,
|
|
782
|
+
skill: group.skill || undefined,
|
|
783
|
+
policy: group.policy || undefined,
|
|
784
|
+
include_dirs: group.include_dirs || undefined,
|
|
785
|
+
timeout: args.timeout,
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const result = handleDelegate(delegateArgs, {
|
|
789
|
+
approvalMode: DEFAULT_APPROVAL_MODE,
|
|
790
|
+
emoji: '👥',
|
|
791
|
+
label: `Group task (${args.group} #${i + 1})`,
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// Extract task_id from response text
|
|
795
|
+
const match = result.content[0].text.match(/`([0-9a-f-]{36})`/);
|
|
796
|
+
if (match) taskIds.push(match[1]);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return {
|
|
800
|
+
content: [{
|
|
801
|
+
type: 'text',
|
|
802
|
+
text: `👥 Delegated to group \`${args.group}\` — ${count} agent(s) spawned.\n\n${taskIds.map((id, i) => `- Agent ${i + 1}: \`${id}\``).join('\n')}\n\nUse \`get_task_result\` to check each agent's status.`,
|
|
803
|
+
}],
|
|
804
|
+
};
|
|
805
|
+
}
|
package/src/tool-definitions.js
CHANGED
|
@@ -360,5 +360,53 @@ export const TOOL_DEFINITIONS = [
|
|
|
360
360
|
required: ['step_name', 'reason'],
|
|
361
361
|
},
|
|
362
362
|
},
|
|
363
|
+
// ─── Group Tools ────────────────────────────────────────
|
|
364
|
+
{
|
|
365
|
+
name: 'create_group',
|
|
366
|
+
description: 'Create a named agent group with shared config (runner, skill, policy). Groups are reusable presets for fractal orchestration.',
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: 'object',
|
|
369
|
+
properties: {
|
|
370
|
+
name: { type: 'string', description: 'Group name (e.g. "backend-team", "qa-group").' },
|
|
371
|
+
runner: { type: 'string', description: 'Default runner for agents in this group.' },
|
|
372
|
+
skill: { type: 'string', description: 'Default skill activated for all agents in this group.' },
|
|
373
|
+
policy: { type: 'string', description: 'Default policy restricting agent permissions.' },
|
|
374
|
+
max_agents: { type: 'number', description: 'Max concurrent agents allowed in this group.' },
|
|
375
|
+
include_dirs: { type: 'array', items: { type: 'string' }, description: 'Additional directories agents in this group can access.' },
|
|
376
|
+
cwd: { type: 'string', description: 'Project directory. Defaults to current working directory.' },
|
|
377
|
+
},
|
|
378
|
+
required: ['name'],
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'list_groups',
|
|
383
|
+
description: 'List all registered agent groups with their config.',
|
|
384
|
+
inputSchema: {
|
|
385
|
+
type: 'object',
|
|
386
|
+
properties: {
|
|
387
|
+
cwd: { type: 'string', description: 'Project directory. Defaults to current working directory.' },
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: 'delegate_to_group',
|
|
393
|
+
description: [
|
|
394
|
+
'Delegate a task to a named agent group. Spawns agents with the group\'s shared config (runner, skill, policy).',
|
|
395
|
+
'Use `count` to launch multiple agents in parallel (fractal orchestration).',
|
|
396
|
+
'',
|
|
397
|
+
'Returns array of task_ids immediately (non-blocking). Use get_task_result to check each.',
|
|
398
|
+
].join('\n'),
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: 'object',
|
|
401
|
+
properties: {
|
|
402
|
+
group: { type: 'string', description: 'Group name to delegate to.' },
|
|
403
|
+
prompt: { type: 'string', description: 'Task description for the agents.' },
|
|
404
|
+
count: { type: 'number', description: 'Number of agents to spawn. Default: 1.' },
|
|
405
|
+
cwd: { type: 'string', description: 'Working directory. Defaults to current working directory.' },
|
|
406
|
+
timeout: { type: 'number', description: 'Timeout in seconds. Overrides group default.' },
|
|
407
|
+
},
|
|
408
|
+
required: ['group', 'prompt'],
|
|
409
|
+
},
|
|
410
|
+
},
|
|
363
411
|
];
|
|
364
412
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Groups — named config presets for fractal orchestration.
|
|
3
|
+
* Groups bundle runner, skill, policy, and max_agents into a reusable team config.
|
|
4
|
+
*
|
|
5
|
+
* @module agent-pool/tools/groups
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
const GROUPS_DIR = '.agents';
|
|
12
|
+
const GROUPS_FILE = 'groups.json';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get path to groups.json for a project.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} cwd - Project directory
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function getGroupsPath(cwd) {
|
|
21
|
+
return path.join(cwd, GROUPS_DIR, GROUPS_FILE);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load all groups from disk.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} cwd - Project directory
|
|
28
|
+
* @returns {Object<string, object>}
|
|
29
|
+
*/
|
|
30
|
+
function loadGroups(cwd) {
|
|
31
|
+
const filePath = getGroupsPath(cwd);
|
|
32
|
+
if (!fs.existsSync(filePath)) return {};
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
35
|
+
} catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Save groups to disk.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} cwd - Project directory
|
|
44
|
+
* @param {Object<string, object>} groups
|
|
45
|
+
*/
|
|
46
|
+
function saveGroups(cwd, groups) {
|
|
47
|
+
const dirPath = path.join(cwd, GROUPS_DIR);
|
|
48
|
+
if (!fs.existsSync(dirPath)) {
|
|
49
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(getGroupsPath(cwd), JSON.stringify(groups, null, 2));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create or update a group.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} cwd - Project directory
|
|
58
|
+
* @param {object} config - Group configuration
|
|
59
|
+
* @param {string} config.name - Group name (e.g. "backend-team")
|
|
60
|
+
* @param {string} [config.runner] - Default runner for agents in this group
|
|
61
|
+
* @param {string} [config.skill] - Default skill for agents in this group
|
|
62
|
+
* @param {string} [config.policy] - Default policy for agents in this group
|
|
63
|
+
* @param {number} [config.max_agents] - Max concurrent agents in this group
|
|
64
|
+
* @param {string[]} [config.include_dirs] - Additional directories agents can access
|
|
65
|
+
* @returns {{ name: string, created: boolean }}
|
|
66
|
+
*/
|
|
67
|
+
export function createGroup(cwd, config) {
|
|
68
|
+
const groups = loadGroups(cwd);
|
|
69
|
+
const existed = !!groups[config.name];
|
|
70
|
+
|
|
71
|
+
groups[config.name] = {
|
|
72
|
+
runner: config.runner || null,
|
|
73
|
+
skill: config.skill || null,
|
|
74
|
+
policy: config.policy || null,
|
|
75
|
+
max_agents: config.max_agents || null,
|
|
76
|
+
include_dirs: config.include_dirs || null,
|
|
77
|
+
created_at: existed ? groups[config.name].created_at : new Date().toISOString(),
|
|
78
|
+
updated_at: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
saveGroups(cwd, groups);
|
|
82
|
+
return { name: config.name, created: !existed };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* List all groups.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} cwd - Project directory
|
|
89
|
+
* @returns {Array<{ name: string, runner: string|null, skill: string|null, policy: string|null, max_agents: number|null }>}
|
|
90
|
+
*/
|
|
91
|
+
export function listGroups(cwd) {
|
|
92
|
+
const groups = loadGroups(cwd);
|
|
93
|
+
return Object.entries(groups).map(([name, config]) => ({
|
|
94
|
+
name,
|
|
95
|
+
...config,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get a single group by name.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} cwd - Project directory
|
|
103
|
+
* @param {string} name - Group name
|
|
104
|
+
* @returns {object|null}
|
|
105
|
+
*/
|
|
106
|
+
export function getGroup(cwd, name) {
|
|
107
|
+
const groups = loadGroups(cwd);
|
|
108
|
+
return groups[name] || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Delete a group.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} cwd - Project directory
|
|
115
|
+
* @param {string} name - Group name
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
export function deleteGroup(cwd, name) {
|
|
119
|
+
const groups = loadGroups(cwd);
|
|
120
|
+
if (!groups[name]) return false;
|
|
121
|
+
delete groups[name];
|
|
122
|
+
saveGroups(cwd, groups);
|
|
123
|
+
return true;
|
|
124
|
+
}
|