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:
@@ -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').
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user