summaryrefslogtreecommitdiffhomepage
path: root/notes/plan-user-agents.md
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 15:09:58 +0900
committerAdam Malczewski <[email protected]>2026-06-02 15:09:58 +0900
commitc0c08720cceb75b5e635e71190ae1f956f535133 (patch)
treee251eba34a9b0200839c5ae2c7f6a2665776a01e /notes/plan-user-agents.md
parent045b1deb5e31d90f17128f8f6e1dc6e87ba5bc92 (diff)
downloaddispatch-c0c08720cceb75b5e635e71190ae1f956f535133.tar.gz
dispatch-c0c08720cceb75b5e635e71190ae1f956f535133.zip
chore: remove notes/ plan & report docs; gitignore notes/ (keep wishlist local)
Diffstat (limited to 'notes/plan-user-agents.md')
-rw-r--r--notes/plan-user-agents.md206
1 files changed, 0 insertions, 206 deletions
diff --git a/notes/plan-user-agents.md b/notes/plan-user-agents.md
deleted file mode 100644
index 012dbfb..0000000
--- a/notes/plan-user-agents.md
+++ /dev/null
@@ -1,206 +0,0 @@
-# Implementation Plan: User Agents
-
-## Summary
-
-Two changes rolled into one:
-
-1. **`agent` becomes required** on `summon` — all spawned agents must use a definition
-2. **New `top_level` mode** — spawns independent "user agent" tabs, gated by a new `perm_user_agent` permission
-
-A **user agent** is a top-level, independent tab spawned by an AI agent via the `summon` tool. Unlike **subagents** (child tabs owned by a parent), user agents appear as first-class tabs — persistent, independent lifecycle, no parent. They are fire-and-forget: the spawning agent gets an `agent_id` back but cannot `retrieve` the result.
-
-User agents **must** be spawned from a non-subagent agent definition (e.g. `default`). The definition controls their tools, models, working directory, and system prompt. Tools are still intersected with the spawning agent's own tools (can't escalate). A new `perm_user_agent` permission gates access to this capability, surfaced as a separate checkbox in the Tool Permissions UI.
-
----
-
-## Current Architecture (for context)
-
-Today, the `summon` tool creates **subagent tabs**:
-
-- They have a `parentTabId` linking them to the spawning tab
-- They show in a **bottom row** under the parent tab in the tab bar
-- They're **non-persistent** by default (italic, faded) until promoted
-- Their **tools are restricted** — intersected with the parent's tool set (can't escalate)
-- Their **working directory** must be within the parent's working directory
-- They have **completion tracking** (`completionPromise`) — the parent blocks on `retrieve` until the child finishes
-- The `agent` parameter is currently optional — agents can be spawned ad-hoc with just a `tools` list, inheriting the parent's model
-
-### Comparison table
-
-| Property | Subagent (current & updated) | User Agent (new) |
-|---|---|---|
-| `parentTabId` | set to parent | `null` |
-| `persistent` | `false` (promoted on click) | `true` |
-| Tab bar position | Bottom row under parent | Top row with user tabs |
-| Tab lifecycle | Closed when parent closes | Independent |
-| Retrievable | Yes, via `retrieve` tool | No — fire-and-forget |
-| Working directory | Must be within parent's dir | Any dir (from definition or default) |
-| Completion tracking | Yes (`completionPromise`) | No |
-| Agent definition | Required | Required (`is_subagent !== true`) |
-| Models | From agent definition | From agent definition |
-
----
-
-## Summon tool — new parameter shape
-
-```
-summon({
- task: string, // required — what to do
- agent: string, // required — agent definition slug (was optional)
- top_level?: boolean, // optional — spawn as user agent (only in schema if perm_user_agent enabled)
- tools?: string[], // optional — override tools (intersected with spawning agent's tools)
- background?: boolean, // optional — for subagents only (user agents are always fire-and-forget)
- working_directory?: string // optional — override the definition's cwd
-})
-```
-
-### Tools resolution
-
-- **`tools` omitted**: agent definition's tools ∩ spawning agent's tools
-- **`tools` provided**: provided tools ∩ spawning agent's tools
-
-Models always come from the agent definition.
-
----
-
-## Changes by file
-
-### 1. `packages/core/src/tools/summon.ts`
-
-**Schema changes:**
-
-- `agent` — change from `.optional()` to required
-- `top_level` — new `z.boolean().optional()`, **only included in the schema when `userAgentEnabled` is `true`**
-- `tools` — stays optional. Description updated: "Defaults to the agent definition's tools. Intersected with the spawning agent's tools."
-- `background` — stays optional, ignored when `top_level: true`
-- `working_directory` — stays optional
-
-**Factory signature change:**
-
-```ts
-createSummonTool(
- defaultWorkingDirectory: string,
- callbacks: SummonCallbacks,
- availableSubagents: AvailableAgent[], // is_subagent === true
- availableUserAgents: AvailableAgent[], // is_subagent !== true
- agentDirs: string[],
- userAgentEnabled: boolean, // new — controls whether top_level param + user agent catalog exist
-)
-```
-
-**`SummonCallbacks.spawn` interface:**
-
-- Add `topLevel?: boolean` to the spawn options object
-
-**`buildAgentsCatalog` update:**
-
-The catalog in the tool description is built conditionally:
-
-- **When `userAgentEnabled` is `false`**: only show the subagents group:
- ```
- Available agents:
- - programmer: Programmer — Implements code from a given plan
- - flash: Flash — A cheap subagent
- ```
-
-- **When `userAgentEnabled` is `true`**: show two labeled groups:
- ```
- Subagents (spawned as child tabs):
- - programmer: Programmer — Implements code from a given plan
- - flash: Flash — A cheap subagent
-
- User agents (spawned as independent top-level tabs, requires top_level=true):
- - default: Default — Default agent with all tools enabled
- ```
-
-**`toAvailableAgents` → split into two functions:**
-
-- Rename existing to `toAvailableSubagents()` — keeps `is_subagent === true` filter
-- New `toAvailableUserAgents()` — filters `is_subagent !== true`
-
-**Execute logic when `top_level: true`:**
-
-- Always return immediately with `agent_id` (fire-and-forget, ignore `background`)
-- Call `callbacks.spawn(...)` with `topLevel: true`
-
----
-
-### 2. `packages/core/src/index.ts`
-
-- Re-export renamed `toAvailableSubagents` and new `toAvailableUserAgents`
-
----
-
-### 3. `packages/api/src/agent-manager.ts`
-
-**`getOrCreateAgentForTab` — permission reading & tool construction:**
-
-- Read new setting: `const permUserAgent = getSetting("perm_user_agent") === "allow"`
-- Include `permUserAgent` in the `permKey` cache-invalidation string
-- Load user agent definitions via `toAvailableUserAgents(...)`
-- Pass `userAgentEnabled: permUserAgent` and `availableUserAgents` to `createSummonTool`
-
-**`spawnChildAgent` — when `topLevel: true`:**
-
-- **`parentTabId`**: pass `null` to `createTab()` and the `tab-created` event
-- **Working directory**: use the agent definition's `cwd` if set, otherwise the global default (`DISPATCH_WORKING_DIR` / `process.cwd()`). **No containment check** against parent's directory.
-- **Tools**: from definition (or `tools` param if provided), intersected with spawning agent's tools. Same intersection logic as subagents — can't escalate.
-- **Models**: from the agent definition's `models` array
-- **No completion tracking**: skip `completionPromise` / `completionResolve` setup. Leave them `undefined`.
-
-**`getChildResult` guard:**
-
-- If the tab has no `completionPromise` and status is `running`, return error: `"This is a user agent (top-level tab) and cannot be retrieved. User agents are fire-and-forget."`
-
----
-
-### 4. `packages/frontend/src/lib/components/ToolPermissions.svelte`
-
-Add a new entry to the `toolPermissions` array:
-
-```ts
-{
- id: "user_agent",
- label: "Spawn user agents",
- description: "Allow the AI to open new independent top-level tabs"
-}
-```
-
----
-
-### 5. `packages/frontend/src/lib/settings.svelte.ts`
-
-Add `user_agent: false` to the default `toolPerms` and `savedToolPerms` objects.
-
----
-
-## Files NOT changing
-
-| File | Why |
-|---|---|
-| `packages/frontend/src/lib/components/TabBar.svelte` | Already renders `parentTabId === null` tabs in top row as persistent |
-| `packages/frontend/src/lib/tabs.svelte.ts` (`tab-created` handler) | Already sets `persistent: parentTabId == null` |
-| `packages/core/src/tools/retrieve.ts` | Unchanged — the retrieve guard lives in AgentManager |
-| `packages/core/src/agents/loader.ts` | `is_subagent` already exists and distinguishes the two types |
-| DB schema | The `settings` table is key-value, no migration needed |
-| Agent definition TOML format | `is_subagent` already exists |
-
----
-
-## Complete file change list
-
-| File | Change |
-|---|---|
-| `packages/core/src/tools/summon.ts` | `agent` required, conditional `top_level` param, two-group catalog (subagents-only when no perm), `toAvailableSubagents()` + `toAvailableUserAgents()`, spawn interface update |
-| `packages/core/src/index.ts` | Re-export new/renamed functions |
-| `packages/api/src/agent-manager.ts` | Read `perm_user_agent`, pass to factory, handle `topLevel` in spawn, retrieve guard |
-| `packages/frontend/src/lib/components/ToolPermissions.svelte` | Add "Spawn user agents" checkbox |
-| `packages/frontend/src/lib/settings.svelte.ts` | Add `user_agent` default |
-
----
-
-## Risks / edge cases
-
-- **Nested user agents**: A user agent could itself have `perm_user_agent` and spawn more user agents. This is allowed and works naturally since user agents are independent tabs with no parent chain.
-- **Agent definition with no models**: Should not happen in practice — the Agent Builder UI requires at least one model entry. But if it does, the spawn will fail at the model-resolution step with a clear error.
-- **Retrieve on user agent**: Guarded in `getChildResult` — returns an error message explaining user agents are fire-and-forget.