feat(FN-3973): document spawn agent approval policy
Documents the spawn agent approval evaluation policy by adding a new `docs/spawn-agent-approval-evaluation.md` (48 lines), updating `docs/agents.md` with related guidance, trimming `docs/architecture.md`, and bumping the agent delegation section in `AGENTS.md`. The new evaluation doc captures the FN Fusion-Task-Id: FN-3973
This commit is contained in:
@@ -123,7 +123,7 @@ Resolver decision table (`resolveAgentProvisioningPolicy`):
|
||||
| `approval-mode-trusted-only` | `require-approval` | Untrusted fallback in default mode. |
|
||||
| `approval-mode-always` | `require-approval` | Approval always required unless privileged/never mode. |
|
||||
|
||||
Out of scope in FN-3791: `spawn_agent` (ephemeral child worktree lifecycle). Follow-up task: "Evaluate approval guards for `spawn_agent` (ephemeral worktree children)".
|
||||
FN-3973 follow-through: `spawn_agent` evaluation is complete; governance remains in action-gate `task_agent_mutation` (ephemeral runtime lifecycle), not durable `agentProvisioning`.
|
||||
|
||||
Default and legacy fallback behavior:
|
||||
|
||||
@@ -807,6 +807,17 @@ Behavior:
|
||||
- `maxSpawnedAgentsGlobal` (default 20)
|
||||
- Child sessions terminate when parent task ends
|
||||
|
||||
### Approval-governance relationship (FN-3973)
|
||||
|
||||
`spawn_agent` is intentionally treated as an **ephemeral runtime mutation**, not durable provisioning:
|
||||
|
||||
- `fn_spawn_agent` stays in action-gate `task_agent_mutation` classification.
|
||||
- Spawned children are created with ephemeral metadata (`metadata.type = "spawned"`) and task-scoped ownership (`reportsTo = parentTaskId`).
|
||||
- Parent teardown terminates/deletes spawned children; they are not durable hires.
|
||||
- Therefore `projectSettings.agentProvisioning` (FN-3791 policy for durable `fn_agent_create` / `fn_agent_delete`) does **not** govern `spawn_agent`.
|
||||
|
||||
If a deployment config requires approval for `task_agent_mutation`, `spawn_agent` uses the standard action-gate approval pause/resume path (`awaiting-approval` + `/api/approvals/:id/decision`).
|
||||
|
||||
## Agent Delegation
|
||||
|
||||
Executor and heartbeat agents can coordinate through six built-in tools: `list_agents`, `delegate_task`, `agent_create`, `agent_delete`, `get_agent_config`, and `update_agent_config`.
|
||||
|
||||
@@ -213,13 +213,13 @@ Schema (migration 68 in `db.ts`) adds two tables:
|
||||
Store API (`packages/core/src/approval-request-store.ts`):
|
||||
|
||||
Dashboard approval endpoints (`packages/dashboard/src/routes/register-approval-routes.ts`):
|
||||
- `GET /api/approval-requests`
|
||||
- `GET /api/approval-requests/:id`
|
||||
- `GET /api/approval-requests/:id/audit`
|
||||
- `POST /api/approval-requests/:id/approve`
|
||||
- `POST /api/approval-requests/:id/deny`
|
||||
- `GET /api/approvals`
|
||||
- `GET /api/approvals/:id`
|
||||
- `POST /api/approvals/:id/decision`
|
||||
|
||||
Runtime flow: engine action gate creates/reuses request → pauses task/agent with `pauseReason="awaiting-approval"` → approver calls approve/deny endpoint → request transitions (`pending→approved|denied`) → route resumes matching paused task/agent best-effort → next tool retry consumes `approved` exactly once (then `completed`) or returns structured denial.
|
||||
Runtime flow: engine action gate creates/reuses request → pauses task/agent with `pauseReason="awaiting-approval"` → approver calls decision endpoint (`decision: approve|deny`) → request transitions (`pending→approved|denied`) → route resumes matching paused task/agent best-effort → next tool retry consumes `approved` exactly once (then `completed`) or returns structured denial.
|
||||
|
||||
Provisioning note: durable `fn_agent_create` / `fn_agent_delete` approvals use `agent_provisioning` policy handling on this same decision route; `fn_spawn_agent` stays under action-gate `task_agent_mutation` because spawned children are ephemeral runtime workers.
|
||||
|
||||
- `create(input: ApprovalRequestCreateInput)` — inserts a `pending` request and appends a `created` audit event
|
||||
- `get(id)` — returns one request or `null`
|
||||
|
||||
48
docs/spawn-agent-approval-evaluation.md
Normal file
48
docs/spawn-agent-approval-evaluation.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# `spawn_agent` approval-governance evaluation (FN-3973)
|
||||
|
||||
## Decision
|
||||
|
||||
Keep `fn_spawn_agent` under the existing generic runtime action-gate category (`task_agent_mutation`) and **do not** move it under `projectSettings.agentProvisioning`.
|
||||
|
||||
## Current behavior (verified)
|
||||
|
||||
- `fn_spawn_agent` is implemented in `TaskExecutor.createSpawnAgentTool()` (`packages/engine/src/executor.ts`).
|
||||
- Spawned children are created via `AgentStore.createAgent()` with `metadata.type = "spawned"` and `reportsTo = <parentTaskId>`.
|
||||
- Child worktrees are derived from the parent task worktree, child sessions are started immediately, and children are tracked in executor in-memory maps.
|
||||
- Parent end/cleanup paths call `terminateChildAgents(...)`; spawned ephemeral agents are terminated/deleted with parent lifecycle teardown.
|
||||
- Tool-level runtime classification includes `fn_spawn_agent` in `ACTION_GATE_TASK_AGENT_MANAGEMENT_TOOLS`, so policy disposition comes from permanent-agent action gate (`task_agent_mutation`) rather than provisioning policy.
|
||||
|
||||
## Why not `agentProvisioning`
|
||||
|
||||
`agentProvisioning` (from FN-3791) is purpose-built for **durable** `fn_agent_create`/`fn_agent_delete` with mailbox approval workflows and deferred execution on `/api/approvals/:id/decision`. `spawn_agent` differs materially:
|
||||
|
||||
- **Persistence:** spawned agents are ephemeral runtime workers.
|
||||
- **Ownership:** children are task-scoped to a parent run, not independent hires.
|
||||
- **Reversibility:** teardown is automatic with parent completion/termination.
|
||||
- **Blast radius:** bounded to parent-task worktree lineage and configured spawn limits.
|
||||
|
||||
Forcing `spawn_agent` into durable provisioning policy would conflate two different risk models and create approval UX friction for intentionally short-lived parallelization.
|
||||
|
||||
## Rejected alternatives
|
||||
|
||||
1. **Reuse `agentProvisioning` for spawn** — rejected; wrong policy surface for ephemeral lifecycle and would overfit mailbox deferred-provisioning UX.
|
||||
2. **New spawn-specific policy today** — rejected for now; no demonstrated gap requiring an additional setting surface.
|
||||
3. **Bypass approval entirely** — rejected; current action-gate path already provides configurable allow/block/approval behavior for permanent callers.
|
||||
|
||||
## Approval UX, pause/resume, and audit implications
|
||||
|
||||
- `fn_spawn_agent` remains governed by runtime action-gate decisions (`task_agent_mutation`) for permanent agents.
|
||||
- If configured as `require-approval`, existing action-gate approval flow applies (request creation, task/agent pause with `awaiting-approval`, resume on approval decision route).
|
||||
- This path uses existing approval APIs and mailbox approvals; no new approval endpoint or queue is required.
|
||||
- No runtime behavior change is required by this decision.
|
||||
|
||||
## Migration/follow-up impact
|
||||
|
||||
- **No code migration required.**
|
||||
- Documentation was aligned so future implementers do not route spawn into durable provisioning by default.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Reworking `spawn_agent` runtime lifecycle.
|
||||
- Introducing a new `spawnAgentProvisioning` settings block.
|
||||
- Changing current approval infrastructure endpoints or mailbox behavior.
|
||||
Reference in New Issue
Block a user