feat(FN-3777): add create_agent and delete_agent tools

Adds agent creation and deletion tools to the pi extension, with engine-side implementation in `agent-tools.ts`, core store integration for agent lifecycle management, and corresponding tests and documentation updates across the workspace.

Fusion-Task-Id: FN-3777
This commit is contained in:
Fusion
2026-05-08 19:02:59 -07:00
committed by gsxdsm
parent a79e91d3aa
commit 840653e529
18 changed files with 352 additions and 19 deletions

View File

@@ -30,7 +30,7 @@ Mission → Milestone → Slice → Feature → Task
- **Task tools** — `fn_task_create`, `fn_task_update`, `fn_task_list`, `fn_task_show`, `fn_task_attach`, `fn_task_pause`, `fn_task_unpause`, `fn_task_retry`, `fn_task_duplicate`, `fn_task_refine`, `fn_task_archive`, `fn_task_unarchive`, `fn_task_delete`, `fn_task_plan`
- **GitHub tools** — `fn_task_import_github`, `fn_task_import_github_issue`, `fn_task_browse_github_issues`
- **Mission tools** — `fn_mission_create`, `fn_mission_list`, `fn_mission_show`, `fn_mission_delete`, `fn_milestone_add`, `fn_slice_add`, `fn_feature_add`, `fn_slice_activate`, `fn_feature_link_task`
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_agent_create`, `fn_agent_delete`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
- **Skills tools** — `fn_skills_search`, `fn_skills_install`
- **Insight tools** — `fn_insight_list`, `fn_insight_show`, `fn_insight_run_list`, `fn_insight_run_show`
- **Other tools** — `fn_web_fetch`, `fn_research_run`, `fn_research_list`, `fn_research_get`, `fn_research_cancel`, `fn_research_retry`

View File

@@ -30,6 +30,8 @@ These tools are **not** part of the user-invokable extension surface. They are i
| `fn_delegate_task` | triage, executor, heartbeat | Create and assign a new task to a specific agent | `agent_id` (string), `description` (string), `dependencies?` (string[]) |
| `fn_get_agent_config` | executor, heartbeat | Read full config for a direct-report agent | `agent_id` (string) |
| `fn_update_agent_config` | executor, heartbeat | Update config fields for a direct-report, non-ephemeral agent | `agent_id` (string), optional: `soul`, `instructions_text`, `instructions_path`, `heartbeat_procedure_path`, `heartbeat_interval_ms`, `heartbeat_timeout_ms`, `max_concurrent_runs`, `message_response_mode` |
| `fn_agent_create` | executor, heartbeat | Create a non-ephemeral direct-report agent | `name` (string), `role` (string), optional: `soul`, `instructions_text`, `instructions_path`, `reportsTo`, `heartbeat_interval_ms`, `heartbeat_timeout_ms`, `max_concurrent_runs`, `message_response_mode` |
| `fn_agent_delete` | executor, heartbeat | Delete a non-ephemeral direct-report agent | `agent_id` (string), optional: `force` (boolean), `reassign_to` (string) |
| `fn_send_message` | executor, step-session, heartbeat | Send inbox messages to agents/users | `to_id` (string), `content` (string), `type?` (`agent-to-agent` \| `agent-to-user`), `reply_to_message_id?` (string) |
| `fn_read_messages` | executor, step-session, heartbeat | Read inbox messages | `unread_only?` (boolean), `limit?` (number) |

View File

@@ -263,6 +263,33 @@ Start a stopped agent — resumes its execution. Transitions the agent from paus
|-----------|------|----------|-------------|
| `id` | string | ✓ | Agent ID to start (e.g., agent-abc123) |
### fn_agent_create
Create a new non-ephemeral agent.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | ✓ | Agent name |
| `role` | string | ✓ | Agent role/capability |
| `soul` | string | — | Agent personality/identity text |
| `instructions_text` | string | — | Inline custom instructions |
| `instructions_path` | string | — | Path to instructions markdown |
| `reportsTo` | string | — | Manager agent ID |
| `heartbeat_interval_ms` | number | — | |
| `heartbeat_timeout_ms` | number | — | |
| `max_concurrent_runs` | number | — | |
| `message_response_mode` | union | — | |
### fn_agent_delete
Delete a non-ephemeral agent.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | ✓ | Agent ID to delete |
| `force` | boolean | — | Force delete when holding checkout |
| `reassign_to` | string | — | Optional replacement agent for assigned tasks |
### fn_list_agents
List all available agents in the system. Shows each agent's name, role, state, personality (soul), and current assignment. Use this to discover which agents exist and what they specialize in before delegating work.

View File

@@ -50,6 +50,8 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
| `fn_feature_link_task` | Link a feature to a fn task for implementation. Updates the feature status to 'triaged' and associates it with the task. |
| `fn_agent_stop` | Stop a running agent — pauses its execution. Transitions the agent from running/active to paused state. |
| `fn_agent_start` | Start a stopped agent — resumes its execution. Transitions the agent from paused to active state. |
| `fn_agent_create` | Create a new non-ephemeral agent. |
| `fn_agent_delete` | Delete a non-ephemeral agent. |
| `fn_list_agents` | List all available agents in the system. Shows each agent's name, role, state, personality (soul), and current assignment. Use this to discover which agents exist and what they specialize in before delegating work. |
| `fn_delegate_task` | Create a new task and assign it to a specific agent for execution. The task goes to 'todo' and will be picked up by the target agent on their next heartbeat cycle. Use fn_list_agents first to find available agents and their capabilities. |
| `fn_agent_show` | Show detailed information about a single agent, including their role, state, position in the org hierarchy (reports-to, direct reports), skills, and current assignment. |

View File

@@ -137,6 +137,8 @@ describe.skipIf(!SHOULD_RUN_EXTENSION_INTEGRATION)("built fn pi extension integr
"fn_task_create",
"fn_task_list",
"fn_task_show",
"fn_agent_create",
"fn_agent_delete",
"fn_list_agents",
"fn_delegate_task",
"fn_agent_show",

View File

@@ -204,6 +204,8 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
"fn_feature_link_task",
"fn_agent_stop",
"fn_agent_start",
"fn_agent_create",
"fn_agent_delete",
"fn_list_agents",
"fn_delegate_task",
"fn_agent_show",

View File

@@ -2486,6 +2486,73 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── fn_agent_create ─────────────────────────────────────────────
pi.registerTool({
name: "fn_agent_create",
label: "fn: Create Agent",
description: "Create a new non-ephemeral agent.",
parameters: Type.Object({
name: Type.String({ description: "Agent name" }),
role: Type.String({ description: "Agent role/capability" }),
soul: Type.Optional(Type.String({ description: "Agent personality/identity text" })),
instructions_text: Type.Optional(Type.String({ description: "Inline custom instructions" })),
instructions_path: Type.Optional(Type.String({ description: "Path to instructions markdown" })),
reportsTo: Type.Optional(Type.String({ description: "Manager agent ID" })),
heartbeat_interval_ms: Type.Optional(Type.Number({ minimum: 1000 })),
heartbeat_timeout_ms: Type.Optional(Type.Number({ minimum: 5000 })),
max_concurrent_runs: Type.Optional(Type.Number({ minimum: 1 })),
message_response_mode: Type.Optional(Type.Union([Type.Literal("immediate"), Type.Literal("on-heartbeat")])),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { AgentStore } = await import("@fusion/core");
const agentStore = new AgentStore({ rootDir: getFusionDir(ctx.cwd) });
await agentStore.init();
const runtimeConfig: Record<string, unknown> = {
...(params.heartbeat_interval_ms !== undefined ? { heartbeatIntervalMs: params.heartbeat_interval_ms } : {}),
...(params.heartbeat_timeout_ms !== undefined ? { heartbeatTimeoutMs: params.heartbeat_timeout_ms } : {}),
...(params.max_concurrent_runs !== undefined ? { maxConcurrentRuns: params.max_concurrent_runs } : {}),
...(params.message_response_mode !== undefined ? { messageResponseMode: params.message_response_mode } : {}),
};
const created = await agentStore.createAgent({
name: params.name,
role: params.role as never,
...(params.soul !== undefined ? { soul: params.soul } : {}),
...(params.instructions_text !== undefined ? { instructionsText: params.instructions_text } : {}),
...(params.instructions_path !== undefined ? { instructionsPath: params.instructions_path } : {}),
...(params.reportsTo !== undefined ? { reportsTo: params.reportsTo } : {}),
...(Object.keys(runtimeConfig).length > 0 ? { runtimeConfig } : {}),
});
return {
content: [{ type: "text" as const, text: `Created agent ${created.name} (${created.id})` }],
details: { agent: created },
};
},
});
// ── fn_agent_delete ─────────────────────────────────────────────
pi.registerTool({
name: "fn_agent_delete",
label: "fn: Delete Agent",
description: "Delete a non-ephemeral agent.",
parameters: Type.Object({
id: Type.String({ description: "Agent ID to delete" }),
force: Type.Optional(Type.Boolean({ description: "Force delete when holding checkout" })),
reassign_to: Type.Optional(Type.String({ description: "Optional replacement agent for assigned tasks" })),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const { AgentStore } = await import("@fusion/core");
const agentStore = new AgentStore({ rootDir: getFusionDir(ctx.cwd) });
await agentStore.init();
await agentStore.deleteAgent(params.id, { force: params.force === true, reassignTo: params.reassign_to });
return { content: [{ type: "text" as const, text: `Deleted ${params.id}` }], details: { agentId: params.id } };
},
});
// ── fn_list_agents ───────────────────────────────────────────────
pi.registerTool({