refactor(self-mod): drop request_rebuild — approvals now bundle rebuild+restart

install_packages and add_mcp_server already did the right thing on approve
(install auto-rebuilt+killed, add_mcp_server just killed), so request_rebuild
was redundant plumbing agents sometimes called after an install — wasting an
admin approval round-trip. Delete it end-to-end:

- container/agent-runner/src/mcp-tools/self-mod.ts: remove requestRebuild
  tool + registration; update install_packages description.
- src/modules/self-mod/{request,apply,index}.ts: drop handleRequestRebuild
  + applyRequestRebuild + registrations; rewrite the rebuild-failed notify
  to point admins at retrying install_packages instead.
- src/modules/{approvals,self-mod}/{agent,project}.md and skill/self-
  customize/SKILL.md: scrub agent-facing references; clarify that
  add_mcp_server needs no rebuild (bun runs TS directly).
- docs/{module-contract,architecture-diagram,checklist,db-central,shared-
  source,v1-vs-v2/*}.md, CLAUDE.md, pending-approvals migration comment,
  approvals/index.ts docstring, REFACTOR.md: trailing references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-22 17:28:36 +03:00
parent e64bdb3016
commit 3b8240a91b
20 changed files with 97 additions and 151 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ Each `/add-<name>` skill is idempotent: `git fetch origin <branch>` → copy mod
One tier of agent self-modification today:
1. **`install_packages` / `add_mcp_server` / `request_rebuild`** — changes to the per-agent-group container config only (apt/npm deps, wire an existing MCP server). Admin approval, rebuild, container restart. `container/agent-runner/src/mcp-tools/self-mod.ts`.
1. **`install_packages` / `add_mcp_server`** — changes to the per-agent-group container config only (apt/npm deps, wire an existing MCP server). Single admin approval per request; on approve, the handler in `src/modules/self-mod/apply.ts` rebuilds the image when needed (`install_packages` only) and restarts the container. `container/agent-runner/src/mcp-tools/self-mod.ts`.
A second tier (direct source-level self-edits via a draft/activate flow) is planned but not yet implemented.
+1 -1
View File
@@ -128,7 +128,7 @@ The `format:fix` pre-commit hook sometimes reformats peer files *after* the comm
4. **Revisit destinations + A2A capability holistically.** The destination projection invariant, dual-purpose routing+ACL table, channel vs agent destination shapes, `createMessagingGroupAgent` auto-wire coupling — more machinery than the feature warrants. Phase 3 moved it out of core intact; a redesign is warranted but scoped post-refactor.
5. **Self-mod approach rethink.** Three separate MCP tools + three delivery actions + three approval handlers for what's essentially "mutate container.config.json and rebuild." Also: post-rebuild latency (host sweep waits up to 60s), and agents sometimes send redundant `add_mcp_server` + `request_rebuild` pairs. Consider collapsing into a single "apply this container-config diff" approval primitive.
5. **Self-mod approach rethink.** _Partially addressed_ — the redundant `request_rebuild` tool was removed; approval of `install_packages` now bundles rebuild + container restart, and `add_mcp_server` approval restarts without rebuilding (bun runs TS directly). Still to consider: collapsing `install_packages` + `add_mcp_server` into a single "apply this container-config diff" approval primitive to reduce post-rebuild latency further.
6. **Per-agent-group source / per-group base image.** Self-mod today layers packages/MCP on a shared base. As groups diverge (different base images, provider configs, runtime toolchains), the shared-base assumption won't scale. Scope post-refactor.
@@ -1,9 +1,13 @@
/**
* Self-modification MCP tools: install_packages, add_mcp_server, request_rebuild.
* Self-modification MCP tools: install_packages, add_mcp_server.
*
* All three are fire-and-forget — the tool writes a system action row and
* returns immediately. The host processes the request (including admin
* approval) and notifies the agent via a chat message when complete.
* Both are fire-and-forget — the tool writes a system action row and returns
* immediately. The host processes the request (including admin approval)
* and notifies the agent via a chat message when complete. Admin approval
* is approval to apply the change: `install_packages` auto-rebuilds the
* per-agent image and restarts the container; `add_mcp_server` just
* updates `container.json` and restarts (bun runs TS directly — no build
* step needed for a pure MCP wiring change).
*
* Package names are sanitized here at the tool boundary AND re-validated on
* the host side (defense in depth).
@@ -36,7 +40,7 @@ export const installPackages: McpToolDefinition = {
tool: {
name: 'install_packages',
description:
'Install apt and/or npm packages into YOUR per-agent container image. Requires admin approval; fire-and-forget. After approval, call `request_rebuild` to apply.',
'Install apt and/or npm packages into YOUR per-agent container image. Requires admin approval; fire-and-forget. On approval, the image is rebuilt and the container is restarted automatically.',
inputSchema: {
type: 'object' as const,
properties: {
@@ -113,32 +117,4 @@ export const addMcpServer: McpToolDefinition = {
},
};
export const requestRebuild: McpToolDefinition = {
tool: {
name: 'request_rebuild',
description:
'Rebuild YOUR container image to pick up approved `install_packages` / `add_mcp_server` changes. Requires admin approval; fire-and-forget.',
inputSchema: {
type: 'object' as const,
properties: {
reason: { type: 'string', description: 'Why the rebuild is needed' },
},
},
},
async handler(args) {
const requestId = generateId();
writeMessageOut({
id: requestId,
kind: 'system',
content: JSON.stringify({
action: 'request_rebuild',
reason: (args.reason as string) || '',
}),
});
log(`request_rebuild: ${requestId}`);
return ok(`Rebuild request submitted. You will be notified when admin approves or rejects.`);
},
};
registerTools([installPackages, addMcpServer, requestRebuild]);
registerTools([installPackages, addMcpServer]);
+8 -11
View File
@@ -11,9 +11,9 @@ You can modify your own environment. Different kinds of changes have different w
**What needs to change?**
- **Your CLAUDE.md or files in your workspace** → Edit directly, no approval needed. Your workspace (`/workspace/agent/`) is persisted on the host.
- **System package (apt) or global npm package** → `install_packages``request_rebuild`. Requires admin approval.
- **MCP server** → `add_mcp_server``request_rebuild`. No approval needed, but rebuild required to apply.
- **`CLAUDE.local.md` or files in your workspace** → Edit directly, no approval needed. Your workspace (`/workspace/agent/`) is persisted on the host. (Note: the composed `CLAUDE.md` itself is read-only and regenerated every spawn — write to `CLAUDE.local.md` instead.)
- **System package (apt) or global npm package** → `install_packages`. Requires admin approval. On approval, image rebuild + container restart happen automatically.
- **MCP server** → `add_mcp_server`. Requires admin approval. On approval, container restarts with the new server wired up (no rebuild — bun runs TS directly).
- **Your source code or Dockerfile** → Delegate to a builder agent via `create_agent` (see below).
- **A new specialist capability** → `create_agent` to spin up a dedicated agent for it.
@@ -25,7 +25,7 @@ For anything that requires editing source files (your own code, Dockerfile, etc.
2. Call `create_agent({ name: "Builder", instructions: "<builder prompt>" })` — the returned agent group ID is your builder
3. Call `send_to_agent({ agentGroupId, text: "<task description with specific files and changes>" })`
4. The builder works in its own container, makes the changes, and reports back
5. You review the builder's summary, confirm with the user, then call `request_rebuild` if the changes require it
5. You review the builder's summary and confirm with the user. Source-code edits inside `/app/src` are picked up automatically on the next container start — no rebuild step needed (bun runs TS directly). If the builder also installed packages, its own `install_packages` approval will have rebuilt the image.
### Builder Agent Instructions (use as CLAUDE.md when creating)
@@ -64,12 +64,11 @@ The limits are **per builder task**, not per session. A 500-line feature is fine
User: "Can you add a tool for reading RSS feeds?"
1. Check [mcp.so](https://mcp.so) for an existing RSS MCP server
2. If one exists → `add_mcp_server({ name: "rss", command: "npx", args: ["some-rss-mcp"] })``request_rebuild` → done
2. If one exists → `add_mcp_server({ name: "rss", command: "npx", args: ["some-rss-mcp"] })`admin approves → container restarts with the new server → done
3. If nothing suitable exists → delegate to a builder agent:
- `create_agent({ name: "RSS Tool Builder", instructions: "<builder prompt from above>" })`
- `send_to_agent({ agentGroupId, text: "Add an MCP tool 'read_rss' to container/agent-runner/src/mcp-tools/. It should fetch an RSS URL and return the latest N items. Register it in mcp-tools/index.ts. Target: <200 new lines." })`
- Wait for builder's report
- `request_rebuild` if needed
- Wait for builder's report — new tool code is picked up on the next container start (bun runs TS directly)
## Example: Installing a System Tool
@@ -78,10 +77,8 @@ User: "Can you transcribe audio?"
1. Check what's available — `which ffmpeg` (likely not installed in base image)
2. Decide approach: `@xenova/transformers` (npm, workspace-local) or `whisper.cpp` (apt + compile)
3. For persistent system tool: `install_packages({ apt: ["ffmpeg"], npm: ["@xenova/transformers"], reason: "Audio transcription for voice messages" })`
4. Wait for admin approval
5. `request_rebuild({ reason: "Apply audio transcription packages" })`
6. Wait for admin approval
7. Test the new capability once the container restarts
4. Wait for admin approval — on approve, the image is rebuilt and your container is restarted automatically
5. Test the new capability once the container restarts
## When NOT to Self-Customize
+1 -1
View File
@@ -32,7 +32,7 @@ flowchart TB
direction TB
PollLoop["Poll Loop<br/>(container/agent-runner)"]
Provider["Agent providers<br/>(claude, opencode, mock; todo: codex)"]
MCP["MCP Tools<br/>send_message, send_file, edit_message,<br/>add_reaction, send_card, ask_user_question,<br/>schedule_task, create_agent,<br/>install_packages, add_mcp_server, request_rebuild"]
MCP["MCP Tools<br/>send_message, send_file, edit_message,<br/>add_reaction, send_card, ask_user_question,<br/>schedule_task, create_agent,<br/>install_packages, add_mcp_server"]
Skills["Container Skills<br/>(container/skills/)"]
InDB[("inbound.db<br/>host writes<br/>even seq<br/>messages_in<br/>destinations<br/>processing_ack")]
OutDB[("outbound.db<br/>container writes<br/>odd seq<br/>messages_out<br/>heartbeat file")]
+4 -6
View File
@@ -135,9 +135,8 @@ Status: [x] done, [~] partial, [ ] not started
- [x] list_tasks
- [x] cancel_task / pause_task / resume_task
- [x] create_agent (any agent, creates agent group + folder + bidirectional destinations; host re-normalizes the name, deduplicates folder, path-traversal guarded)
- [x] install_packages (apt/npm, owner/admin approval required via `pickApprover`, strict name validation)
- [x] add_mcp_server (owner/admin approval required via `pickApprover`)
- [x] request_rebuild (rebuilds per-agent-group Docker image)
- [x] install_packages (apt/npm, owner/admin approval required via `pickApprover`, strict name validation; single approval step covers the image rebuild + container restart)
- [x] add_mcp_server (owner/admin approval required via `pickApprover`; approval triggers container restart, no image rebuild needed — bun runs TS directly)
## Scheduling
@@ -156,9 +155,8 @@ Status: [x] done, [~] partial, [ ] not started
- [x] Approval flow (sensitive action -> card to admin -> approve/reject -> execute) — `pending_approvals` table, `requestApproval()` helper, reuses interactive card infra
- [x] Agent requests dependency/package install (install_packages, admin approval, rebuild on approval)
- [x] Self-modification — direct tools:
- [x] install_packages (apt/npm, admin approval, name validation both sides, max 20 per request)
- [x] add_mcp_server (admin approval)
- [x] request_rebuild (builds per-agent-group Docker image with approved packages)
- [x] install_packages (apt/npm, admin approval, name validation both sides, max 20 per request; on approve → handler rebuilds the image, kills the container, schedules a verify-and-report follow-up prompt)
- [x] add_mcp_server (admin approval; on approve → handler updates `container.json`, kills the container — no image rebuild)
- [x] Fire-and-forget model (write request, return immediately; chat notification on approval; container killed so next wake picks up new config/image)
- [~] OneCLI integration for human-loop approvals on credentialed requests (agent touching a credentialed resource → OneCLI gates → approval card to admin → OneCLI releases credential) — SDK 0.3.1 `configureManualApproval` wired into host, routes to admin via existing `pending_approvals` infra
- [ ] Tunneled OneCLI dashboard for credential addition (Telegram Mini Apps aside, iMessage without Apple Business Register, Matrix, email). Signed short-lived URL → browser form served by OneCLI at 10254 → tunnel via cloudflare durable object. Value never touches the chat surface.
+1 -1
View File
@@ -201,7 +201,7 @@ Access layer: `src/db/agent-destinations.ts`.
Two workflows share this table:
- **Session-bound MCP approvals** — `install_packages`, `request_rebuild`, `add_mcp_server`. `session_id` is set.
- **Session-bound MCP approvals** — `install_packages`, `add_mcp_server`. `session_id` is set.
- **OneCLI credential approvals** — `session_id` may be NULL; `agent_group_id` + `channel_type` + `platform_id` route the admin card.
```sql
+1 -1
View File
@@ -66,7 +66,7 @@ export function registerDeliveryAction(action: string, handler: ActionHandler):
**Default when action is unknown:** log `"Unknown system action"` at `warn` and return. Message is still marked delivered (it was consumed by the host, not sent to a channel).
**Current consumers:** scheduling (5 actions — `schedule_task`, `cancel_task`, `pause_task`, `resume_task`, `update_task`), approvals (3 actions — `install_packages`, `request_rebuild`, `add_mcp_server`), agent-to-agent (`create_agent`, and the agent-routing branch keyed as a pseudo-action `agent_route`).
**Current consumers:** scheduling (5 actions — `schedule_task`, `cancel_task`, `pause_task`, `resume_task`, `update_task`), approvals (2 actions — `install_packages`, `add_mcp_server`), agent-to-agent (`create_agent`, and the agent-routing branch keyed as a pseudo-action `agent_route`).
### 2. Router sender resolver + access gate
+1 -1
View File
@@ -73,7 +73,7 @@ What remains per-group (unchanged):
### Self-modification
Existing config-level self-mod tools (`install_packages`, `add_mcp_server`, `request_rebuild`) mutate `container.json` and per-group images, not source. Unchanged — stays per-group.
Existing config-level self-mod tools (`install_packages`, `add_mcp_server`) mutate `container.json` and per-group images, not source. Unchanged — stays per-group.
Source-level self-modification (not yet implemented) uses staging: edits happen against a copy of `container/agent-runner/src/`, reviewed and swapped in on approval. Owner can also edit source directly.
+4 -5
View File
@@ -20,17 +20,16 @@
| — | `scheduling.ts:221-266` `update_task` | **new** | Modify prompt/recurrence/processAfter/script |
| — | `interactive.ts:36-129` `ask_user_question` | **new** | Blocking with timeout — writes to outbound.db then polls inbound.db for response |
| — | `interactive.ts:131-166` `send_card` | **new** | Structured Chat SDK cards |
| — | `self-mod.ts:34-74` `install_packages` | **new** | apt/npm install, regex name validation, admin approval |
| — | `self-mod.ts:76-113` `add_mcp_server` | **new** | Wire existing MCP server |
| — | `self-mod.ts:115-141` `request_rebuild` | **new** | Async container rebuild |
| — | `self-mod.ts` `install_packages` | **new** | apt/npm install, regex name validation, admin approval; approval handler auto-rebuilds image and restarts container |
| — | `self-mod.ts` `add_mcp_server` | **new** | Wire existing MCP server; approval handler restarts container (no image rebuild) |
| — | `agents.ts:30-63` `create_agent` | **new** | Admin-only sub-agent creation; not exposed to non-admin containers |
## New tools in v2
16 new tools split across 5 capability domains:
15 new tools split across 5 capability domains:
- **Message manipulation**: `send_file`, `edit_message`, `add_reaction`
- **Scheduling**: 6 task-management tools
- **Interactive**: `ask_user_question`, `send_card`
- **Self-modification**: `install_packages`, `add_mcp_server`, `request_rebuild`
- **Self-modification**: `install_packages`, `add_mcp_server`
- **Agent management**: `create_agent`
## Missing from v2
+1 -1
View File
@@ -223,7 +223,7 @@ Per-agent ACL and name-resolution map for `send_message(to="name")`. Projected i
```sql
approval_id, session_id, request_id, action, payload, agent_group_id, channel_type, platform_id, platform_message_id, expires_at, status, title, options_json, created_at
```
Approval queue for `install_packages`, `add_mcp_server`, `request_rebuild`, OneCLI credential flows. v1: no approval model.
Approval queue for `install_packages`, `add_mcp_server`, OneCLI credential flows. v1: no approval model.
**`unregistered_senders` (via migration 008):**
```sql
@@ -3,8 +3,8 @@ import type { Migration } from './index.js';
/**
* `pending_approvals` table — host-side records for any approval-requiring
* request. Used by:
* - install_packages / request_rebuild / add_mcp_server (session-bound,
* `session_id` set, status stays at default 'pending' until handled)
* - install_packages / add_mcp_server (session-bound, `session_id` set,
* status stays at default 'pending' until handled)
* - OneCLI credential approvals from the SDK `configureManualApproval`
* callback (session_id may be null, action='onecli_credential').
*
+2 -10
View File
@@ -16,7 +16,7 @@ install_packages({
- Max 20 packages per request.
- Names must match strict regex (blocks shell injection via `vim; curl evil.com`).
- After approval: rebuild runs automatically. You do NOT need to call `request_rebuild` separately.
- On approval, the image rebuild and container restart happen automatically — there is no separate rebuild step for you to trigger.
### add_mcp_server
@@ -32,15 +32,7 @@ add_mcp_server({
```
- Does NOT install packages. Use `install_packages` first if the command isn't already available.
- On approval, container is killed so the next message wakes it with the new server wired up.
### request_rebuild
Rebuild your container image. Only useful if you've already landed `install_packages` approvals whose rebuild step failed, or if you're recovering from a bad config edit.
```
request_rebuild({ reason: "previous install_packages rebuild failed" })
```
- On approval, the container is killed and the next message wakes it with the new server wired up. No image rebuild — bun runs TS directly.
### How approval works
+3 -3
View File
@@ -12,9 +12,9 @@
* once the delivery adapter is set.
* - A shutdown callback that stops the OneCLI handler cleanly.
*
* Self-mod flows (install_packages, request_rebuild, add_mcp_server) moved
* out to `src/modules/self-mod/` in PR #7 they now register delivery
* actions + approval handlers via this module's public API.
* Self-mod flows (install_packages, add_mcp_server) moved out to
* `src/modules/self-mod/` in PR #7 they now register delivery actions
* + approval handlers via this module's public API.
*/
import { onDeliveryAdapterReady } from '../../delivery.js';
import { registerResponseHandler, onShutdown } from '../../response-registry.js';
+2 -2
View File
@@ -4,13 +4,13 @@ Admin-gated approval flow for agent self-modification and OneCLI credential acce
### Two flows
**Agent-initiated (DB-backed, fire-and-forget).** The container writes a `system`-kind outbound row with one of three actions — `install_packages`, `request_rebuild`, `add_mcp_server`. The module's delivery-action handlers validate, route to the right approver's DM, and persist a `pending_approvals` row. When the admin clicks a button, the registered response handler applies the change (config update → image rebuild → container kill) and notifies the agent via system chat.
**Agent-initiated (DB-backed, fire-and-forget).** The container writes a `system`-kind outbound row with one of two actions — `install_packages`, `add_mcp_server`. The module's delivery-action handlers validate, route to the right approver's DM, and persist a `pending_approvals` row. When the admin clicks a button, the registered response handler applies the change (config update → image rebuild if needed → container kill) and notifies the agent via system chat.
**OneCLI credential (long-poll).** The OneCLI gateway holds an HTTP connection open when it needs credential approval. `onecli-approvals.ts` delivers a card, persists a `pending_approvals` row (action = `onecli_credential`), and waits on an in-memory Promise that resolves on click or expiry timer. Survives host restart: the startup sweep edits stale cards to "Expired (host restarted)" and drops the rows.
### Wiring
- **Delivery actions:** `install_packages`, `request_rebuild`, `add_mcp_server` via `registerDeliveryAction`.
- **Delivery actions:** `install_packages`, `add_mcp_server` via `registerDeliveryAction`.
- **Response handler:** single handler claims both agent-initiated and OneCLI approvals. OneCLI is tried first (in-memory Promise); falls through to `pending_approvals` lookup.
- **Adapter-ready hook (`onDeliveryAdapterReady`):** starts the OneCLI manual-approval handler once the delivery adapter is set.
- **Shutdown hook (`onShutdown`):** stops the OneCLI handler.
+13 -14
View File
@@ -1,29 +1,28 @@
# Self-modification
You can install additional OS or npm packages, rebuild your container image,
or add new MCP servers — but only with admin approval.
You can install additional OS or npm packages or add new MCP servers — but
only with admin approval.
## Tools
- `install_packages({ apt?: string[], npm?: string[], reason?: string })`
adds the listed packages to your container config and rebuilds the image
after admin approval. Package names are validated strictly (`[a-z0-9._+-]`
for apt, standard npm naming with optional scope). Max 20 packages per
request.
- `request_rebuild({ reason?: string })` — rebuilds your container image
without config changes. Useful if the image has drifted from config.
adds the listed packages to your container config, rebuilds the image,
and restarts your container, all in a single admin approval step.
Package names are validated strictly (`[a-z0-9._+-]` for apt, standard
npm naming with optional scope). Max 20 packages per request.
- `add_mcp_server({ name, command, args?, env? })` — adds a new MCP server
to your container config. The container restarts on next message so the
new server is available.
to your container config and restarts the container so the new server
is wired up on the next message. No image rebuild is required (bun runs
TS directly).
## Flow
You call one of these tools → the host asks an admin via DM → admin approves
or rejects. On approve, the config is applied and the container is killed;
the host respawns it on the next message. You'll get a system chat message
confirming the outcome (either "Packages installed..." or a failure reason).
or rejects. On approve, the config is applied, the image is rebuilt if
needed, and the container is killed; the host respawns it on the next
message. You'll get a system chat message confirming the outcome (either
"Packages installed..." or a failure reason).
On reject you'll see "Your X request was rejected by admin."
+6 -13
View File
@@ -5,6 +5,11 @@
* pending_approvals row whose action matches. Each handler mutates the
* container config, rebuilds/kills the container as needed, and lets the
* host sweep respawn it on the new image on the next message.
*
* install_packages: rebuild image + kill container (apt/npm global installs
* must be baked into the image layer).
* add_mcp_server: kill container only bun runs TS directly, so a pure
* MCP wiring change needs nothing more than a process restart.
*/
import { updateContainerConfig } from '../../container-config.js';
import { buildAgentGroupImage, killContainer } from '../../container-runner.js';
@@ -54,24 +59,12 @@ export const applyInstallPackages: ApprovalHandler = async ({ session, payload,
log.info('Container rebuild completed (bundled with install)', { agentGroupId: session.agent_group_id });
} catch (e) {
notify(
`Packages added to config (${pkgs}) but rebuild failed: ${e instanceof Error ? e.message : String(e)}. Call request_rebuild to retry.`,
`Packages added to config (${pkgs}) but rebuild failed: ${e instanceof Error ? e.message : String(e)}. Tell the user — an admin will need to retry the install_packages request or inspect the build logs.`,
);
log.error('Bundled rebuild failed after install approval', { agentGroupId: session.agent_group_id, err: e });
}
};
export const applyRequestRebuild: ApprovalHandler = async ({ session, userId, notify }) => {
try {
await buildAgentGroupImage(session.agent_group_id);
killContainer(session.id, 'rebuild applied');
notify('Container image rebuilt. Your container will restart with the new image on the next message.');
log.info('Container rebuild approved and completed', { agentGroupId: session.agent_group_id, userId });
} catch (e) {
notify(`Rebuild failed: ${e instanceof Error ? e.message : String(e)}`);
log.error('Container rebuild failed', { agentGroupId: session.agent_group_id, err: e });
}
};
export const applyAddMcpServer: ApprovalHandler = async ({ session, payload, userId, notify }) => {
const agentGroup = getAgentGroup(session.agent_group_id);
if (!agentGroup) {
+15 -13
View File
@@ -3,26 +3,28 @@
*
* Optional tier. Depends on the approvals default module for the request/
* handler plumbing. On install the module registers:
* - Three delivery actions (install_packages, request_rebuild, add_mcp_server)
* that validate input and queue an approval via requestApproval().
* - Three matching approval handlers that run on approve: mutate the
* container config, rebuild the image, kill the container so the next
* wake picks up the change.
* - Two delivery actions (install_packages, add_mcp_server) that validate
* input and queue an approval via requestApproval().
* - Two matching approval handlers that run on approve and perform the
* complete follow-up:
* install_packages update container.json, rebuild image, kill
* container (next wake respawns on the new image), schedule a
* verify-and-report follow-up prompt.
* add_mcp_server update container.json, kill container. No image
* rebuild bun runs TS directly, so the new MCP server is wired
* by the next container start.
*
* Without this module: the three MCP tools in the container still write
* outbound system messages with these actions, but delivery logs
* "Unknown system action" and drops them. Admin never sees a card; nothing
* changes.
* Without this module: the MCP tools in the container still write outbound
* system messages with these actions, but delivery logs "Unknown system
* action" and drops them. Admin never sees a card; nothing changes.
*/
import { registerDeliveryAction } from '../../delivery.js';
import { registerApprovalHandler } from '../approvals/index.js';
import { applyAddMcpServer, applyInstallPackages, applyRequestRebuild } from './apply.js';
import { handleAddMcpServer, handleInstallPackages, handleRequestRebuild } from './request.js';
import { applyAddMcpServer, applyInstallPackages } from './apply.js';
import { handleAddMcpServer, handleInstallPackages } from './request.js';
registerDeliveryAction('install_packages', handleInstallPackages);
registerDeliveryAction('request_rebuild', handleRequestRebuild);
registerDeliveryAction('add_mcp_server', handleAddMcpServer);
registerApprovalHandler('install_packages', applyInstallPackages);
registerApprovalHandler('request_rebuild', applyRequestRebuild);
registerApprovalHandler('add_mcp_server', applyAddMcpServer);
+15 -10
View File
@@ -1,20 +1,25 @@
# Self-mod module
Optional-tier module that gives agents admin-gated self-modification:
installing OS/npm packages, rebuilding the container image, and registering
new MCP servers. All three paths go through the approvals module's request
primitive — no unapproved changes ever land.
installing OS/npm packages and registering new MCP servers. Both paths go
through the approvals module's request primitive — no unapproved changes
ever land. The rebuild+restart (or restart-only) follow-up is bundled into
the approval handler itself — there is no separate "request rebuild" step.
## What this module adds
- Three delivery actions (`install_packages`, `request_rebuild`, `add_mcp_server`)
that the container's self-mod MCP tools write into outbound.db. On the host,
each handler validates input and queues an approval via
- Two delivery actions (`install_packages`, `add_mcp_server`) that the
container's self-mod MCP tools write into outbound.db. On the host, each
handler validates input and queues an approval via
`approvals.requestApproval()`.
- Three matching approval handlers that run on approve: mutate the container
config via `updateContainerConfig`, rebuild the image via
`buildAgentGroupImage`, and kill the container so the host sweep respawns
it on the new image.
- Two matching approval handlers that run on approve:
- `install_packages`update `container.json`, rebuild the image via
`buildAgentGroupImage`, and kill the container so the host sweep
respawns it on the new image. Also schedules a verify-and-report
follow-up prompt ~5 s after kill.
- `add_mcp_server` → update `container.json` and kill the container.
No image rebuild — bun runs TS directly, so the new MCP wiring is
picked up on the next container start.
## Dependency
+6 -21
View File
@@ -1,10 +1,12 @@
/**
* Delivery-action handlers for agent-initiated self-modification requests.
*
* Three actions the container can write into messages_out (via the self-mod
* MCP tools): install_packages, request_rebuild, add_mcp_server. Each one
* validates input and queues an approval request. The admin's approval
* triggers the matching approval handler in ./apply.ts.
* Two actions the container can write into messages_out (via the self-mod
* MCP tools): install_packages, add_mcp_server. Each one validates input
* and queues an approval request. The admin's approval triggers the
* matching approval handler in ./apply.ts, which also performs the
* required follow-up (rebuild+restart for install_packages, restart-only
* for add_mcp_server).
*
* Host-side sanitization for install_packages is defense-in-depth the MCP
* tool validates first. Both layers matter: the DB row carries the payload
@@ -61,23 +63,6 @@ export async function handleInstallPackages(content: Record<string, unknown>, se
});
}
export async function handleRequestRebuild(content: Record<string, unknown>, session: Session): Promise<void> {
const agentGroup = getAgentGroup(session.agent_group_id);
if (!agentGroup) {
notifyAgent(session, 'request_rebuild failed: agent group not found.');
return;
}
const reason = (content.reason as string) || '';
await requestApproval({
session,
agentName: agentGroup.name,
action: 'request_rebuild',
payload: { reason },
title: 'Rebuild Request',
question: `Agent "${agentGroup.name}" is attempting to rebuild container.${reason ? `\nReason: ${reason}` : ''}`,
});
}
export async function handleAddMcpServer(content: Record<string, unknown>, session: Session): Promise<void> {
const agentGroup = getAgentGroup(session.agent_group_id);
if (!agentGroup) {