From 3b8240a91b0db565e5497a30b6fb9f780ab66281 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Wed, 22 Apr 2026 17:28:36 +0300 Subject: [PATCH] =?UTF-8?q?refactor(self-mod):=20drop=20request=5Frebuild?= =?UTF-8?q?=20=E2=80=94=20approvals=20now=20bundle=20rebuild+restart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- CLAUDE.md | 2 +- REFACTOR.md | 2 +- .../agent-runner/src/mcp-tools/self-mod.ts | 44 +++++-------------- container/skills/self-customize/SKILL.md | 19 ++++---- docs/architecture-diagram.md | 2 +- docs/checklist.md | 10 ++--- docs/db-central.md | 2 +- docs/module-contract.md | 2 +- docs/shared-source.md | 2 +- docs/v1-vs-v2/container-mcp-tools.md | 9 ++-- docs/v1-vs-v2/db.md | 2 +- .../module-approvals-pending-approvals.ts | 4 +- src/modules/approvals/agent.md | 12 +---- src/modules/approvals/index.ts | 6 +-- src/modules/approvals/project.md | 4 +- src/modules/self-mod/agent.md | 27 ++++++------ src/modules/self-mod/apply.ts | 19 +++----- src/modules/self-mod/index.ts | 28 ++++++------ src/modules/self-mod/project.md | 25 ++++++----- src/modules/self-mod/request.ts | 27 +++--------- 20 files changed, 97 insertions(+), 151 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3ad777471..ba5f857e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,7 +91,7 @@ Each `/add-` skill is idempotent: `git fetch origin ` → 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. diff --git a/REFACTOR.md b/REFACTOR.md index 2ae7f81ec..d3f6562b7 100644 --- a/REFACTOR.md +++ b/REFACTOR.md @@ -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. diff --git a/container/agent-runner/src/mcp-tools/self-mod.ts b/container/agent-runner/src/mcp-tools/self-mod.ts index 775ec3b9d..3e2a2d8c6 100644 --- a/container/agent-runner/src/mcp-tools/self-mod.ts +++ b/container/agent-runner/src/mcp-tools/self-mod.ts @@ -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]); diff --git a/container/skills/self-customize/SKILL.md b/container/skills/self-customize/SKILL.md index e1d55887a..c8bad1674 100644 --- a/container/skills/self-customize/SKILL.md +++ b/container/skills/self-customize/SKILL.md @@ -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: "" })` — the returned agent group ID is your builder 3. Call `send_to_agent({ agentGroupId, text: "" })` 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: "" })` - `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 diff --git a/docs/architecture-diagram.md b/docs/architecture-diagram.md index d7c5eadba..4d8671cc2 100644 --- a/docs/architecture-diagram.md +++ b/docs/architecture-diagram.md @@ -32,7 +32,7 @@ flowchart TB direction TB PollLoop["Poll Loop
(container/agent-runner)"] Provider["Agent providers
(claude, opencode, mock; todo: codex)"] - MCP["MCP Tools
send_message, send_file, edit_message,
add_reaction, send_card, ask_user_question,
schedule_task, create_agent,
install_packages, add_mcp_server, request_rebuild"] + MCP["MCP Tools
send_message, send_file, edit_message,
add_reaction, send_card, ask_user_question,
schedule_task, create_agent,
install_packages, add_mcp_server"] Skills["Container Skills
(container/skills/)"] InDB[("inbound.db
host writes
even seq
messages_in
destinations
processing_ack")] OutDB[("outbound.db
container writes
odd seq
messages_out
heartbeat file")] diff --git a/docs/checklist.md b/docs/checklist.md index 16b363018..156feb1b7 100644 --- a/docs/checklist.md +++ b/docs/checklist.md @@ -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. diff --git a/docs/db-central.md b/docs/db-central.md index 8be6ee82c..8268acf95 100644 --- a/docs/db-central.md +++ b/docs/db-central.md @@ -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 diff --git a/docs/module-contract.md b/docs/module-contract.md index 01662ddb0..04919b920 100644 --- a/docs/module-contract.md +++ b/docs/module-contract.md @@ -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 diff --git a/docs/shared-source.md b/docs/shared-source.md index ab725eab5..95ea94d4f 100644 --- a/docs/shared-source.md +++ b/docs/shared-source.md @@ -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. diff --git a/docs/v1-vs-v2/container-mcp-tools.md b/docs/v1-vs-v2/container-mcp-tools.md index 95c23b391..41282e186 100644 --- a/docs/v1-vs-v2/container-mcp-tools.md +++ b/docs/v1-vs-v2/container-mcp-tools.md @@ -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 diff --git a/docs/v1-vs-v2/db.md b/docs/v1-vs-v2/db.md index 97ee0f868..0614b446f 100644 --- a/docs/v1-vs-v2/db.md +++ b/docs/v1-vs-v2/db.md @@ -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 diff --git a/src/db/migrations/module-approvals-pending-approvals.ts b/src/db/migrations/module-approvals-pending-approvals.ts index 91aa08eae..699e305ad 100644 --- a/src/db/migrations/module-approvals-pending-approvals.ts +++ b/src/db/migrations/module-approvals-pending-approvals.ts @@ -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'). * diff --git a/src/modules/approvals/agent.md b/src/modules/approvals/agent.md index f992040f4..57b65d0e6 100644 --- a/src/modules/approvals/agent.md +++ b/src/modules/approvals/agent.md @@ -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 diff --git a/src/modules/approvals/index.ts b/src/modules/approvals/index.ts index 2bd8446a7..f70a43f4e 100644 --- a/src/modules/approvals/index.ts +++ b/src/modules/approvals/index.ts @@ -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'; diff --git a/src/modules/approvals/project.md b/src/modules/approvals/project.md index 19dae6767..6a1f10f0e 100644 --- a/src/modules/approvals/project.md +++ b/src/modules/approvals/project.md @@ -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. diff --git a/src/modules/self-mod/agent.md b/src/modules/self-mod/agent.md index 33bca9b44..9d67a4ad1 100644 --- a/src/modules/self-mod/agent.md +++ b/src/modules/self-mod/agent.md @@ -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." diff --git a/src/modules/self-mod/apply.ts b/src/modules/self-mod/apply.ts index 1a3daa893..52919377a 100644 --- a/src/modules/self-mod/apply.ts +++ b/src/modules/self-mod/apply.ts @@ -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) { diff --git a/src/modules/self-mod/index.ts b/src/modules/self-mod/index.ts index aedf1dc95..e1f49e212 100644 --- a/src/modules/self-mod/index.ts +++ b/src/modules/self-mod/index.ts @@ -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); diff --git a/src/modules/self-mod/project.md b/src/modules/self-mod/project.md index bb6a0eccc..556bcfe30 100644 --- a/src/modules/self-mod/project.md +++ b/src/modules/self-mod/project.md @@ -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 diff --git a/src/modules/self-mod/request.ts b/src/modules/self-mod/request.ts index d96561648..6cd7f05c9 100644 --- a/src/modules/self-mod/request.ts +++ b/src/modules/self-mod/request.ts @@ -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, se }); } -export async function handleRequestRebuild(content: Record, session: Session): Promise { - 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, session: Session): Promise { const agentGroup = getAgentGroup(session.agent_group_id); if (!agentGroup) {