mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
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:
@@ -91,7 +91,7 @@ Each `/add-<name>` skill is idempotent: `git fetch origin <branch>` → copy mod
|
|||||||
|
|
||||||
One tier of agent self-modification today:
|
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.
|
A second tier (direct source-level self-edits via a draft/activate flow) is planned but not yet implemented.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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.
|
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.
|
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
|
* Both are fire-and-forget — the tool writes a system action row and returns
|
||||||
* returns immediately. The host processes the request (including admin
|
* immediately. The host processes the request (including admin approval)
|
||||||
* approval) and notifies the agent via a chat message when complete.
|
* 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
|
* Package names are sanitized here at the tool boundary AND re-validated on
|
||||||
* the host side (defense in depth).
|
* the host side (defense in depth).
|
||||||
@@ -36,7 +40,7 @@ export const installPackages: McpToolDefinition = {
|
|||||||
tool: {
|
tool: {
|
||||||
name: 'install_packages',
|
name: 'install_packages',
|
||||||
description:
|
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: {
|
inputSchema: {
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
properties: {
|
properties: {
|
||||||
@@ -113,32 +117,4 @@ export const addMcpServer: McpToolDefinition = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestRebuild: McpToolDefinition = {
|
registerTools([installPackages, addMcpServer]);
|
||||||
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]);
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ You can modify your own environment. Different kinds of changes have different w
|
|||||||
|
|
||||||
**What needs to change?**
|
**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.
|
- **`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` → `request_rebuild`. Requires admin approval.
|
- **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` → `request_rebuild`. No approval needed, but rebuild required to apply.
|
- **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).
|
- **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.
|
- **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
|
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>" })`
|
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
|
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)
|
### 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?"
|
User: "Can you add a tool for reading RSS feeds?"
|
||||||
|
|
||||||
1. Check [mcp.so](https://mcp.so) for an existing RSS MCP server
|
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:
|
3. If nothing suitable exists → delegate to a builder agent:
|
||||||
- `create_agent({ name: "RSS Tool Builder", instructions: "<builder prompt from above>" })`
|
- `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." })`
|
- `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
|
- Wait for builder's report — new tool code is picked up on the next container start (bun runs TS directly)
|
||||||
- `request_rebuild` if needed
|
|
||||||
|
|
||||||
## Example: Installing a System Tool
|
## 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)
|
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)
|
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" })`
|
3. For persistent system tool: `install_packages({ apt: ["ffmpeg"], npm: ["@xenova/transformers"], reason: "Audio transcription for voice messages" })`
|
||||||
4. Wait for admin approval
|
4. Wait for admin approval — on approve, the image is rebuilt and your container is restarted automatically
|
||||||
5. `request_rebuild({ reason: "Apply audio transcription packages" })`
|
5. Test the new capability once the container restarts
|
||||||
6. Wait for admin approval
|
|
||||||
7. Test the new capability once the container restarts
|
|
||||||
|
|
||||||
## When NOT to Self-Customize
|
## When NOT to Self-Customize
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ flowchart TB
|
|||||||
direction TB
|
direction TB
|
||||||
PollLoop["Poll Loop<br/>(container/agent-runner)"]
|
PollLoop["Poll Loop<br/>(container/agent-runner)"]
|
||||||
Provider["Agent providers<br/>(claude, opencode, mock; todo: codex)"]
|
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/)"]
|
Skills["Container Skills<br/>(container/skills/)"]
|
||||||
InDB[("inbound.db<br/>host writes<br/>even seq<br/>messages_in<br/>destinations<br/>processing_ack")]
|
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")]
|
OutDB[("outbound.db<br/>container writes<br/>odd seq<br/>messages_out<br/>heartbeat file")]
|
||||||
|
|||||||
+4
-6
@@ -135,9 +135,8 @@ Status: [x] done, [~] partial, [ ] not started
|
|||||||
- [x] list_tasks
|
- [x] list_tasks
|
||||||
- [x] cancel_task / pause_task / resume_task
|
- [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] 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] 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`)
|
- [x] add_mcp_server (owner/admin approval required via `pickApprover`; approval triggers container restart, no image rebuild needed — bun runs TS directly)
|
||||||
- [x] request_rebuild (rebuilds per-agent-group Docker image)
|
|
||||||
|
|
||||||
## Scheduling
|
## 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] 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] Agent requests dependency/package install (install_packages, admin approval, rebuild on approval)
|
||||||
- [x] Self-modification — direct tools:
|
- [x] Self-modification — direct tools:
|
||||||
- [x] install_packages (apt/npm, admin approval, name validation both sides, max 20 per request)
|
- [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)
|
- [x] add_mcp_server (admin approval; on approve → handler updates `container.json`, kills the container — no image rebuild)
|
||||||
- [x] request_rebuild (builds per-agent-group Docker image with approved packages)
|
|
||||||
- [x] Fire-and-forget model (write request, return immediately; chat notification on approval; container killed so next wake picks up new config/image)
|
- [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
|
- [~] 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.
|
- [ ] 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
@@ -201,7 +201,7 @@ Access layer: `src/db/agent-destinations.ts`.
|
|||||||
|
|
||||||
Two workflows share this table:
|
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.
|
- **OneCLI credential approvals** — `session_id` may be NULL; `agent_group_id` + `channel_type` + `platform_id` route the admin card.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
|
|||||||
@@ -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).
|
**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
|
### 2. Router sender resolver + access gate
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ What remains per-group (unchanged):
|
|||||||
|
|
||||||
### Self-modification
|
### 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -20,17 +20,16 @@
|
|||||||
| — | `scheduling.ts:221-266` `update_task` | **new** | Modify prompt/recurrence/processAfter/script |
|
| — | `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: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 |
|
| — | `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` `install_packages` | **new** | apt/npm install, regex name validation, admin approval; approval handler auto-rebuilds image and restarts container |
|
||||||
| — | `self-mod.ts:76-113` `add_mcp_server` | **new** | Wire existing MCP server |
|
| — | `self-mod.ts` `add_mcp_server` | **new** | Wire existing MCP server; approval handler restarts container (no image rebuild) |
|
||||||
| — | `self-mod.ts:115-141` `request_rebuild` | **new** | Async container rebuild |
|
|
||||||
| — | `agents.ts:30-63` `create_agent` | **new** | Admin-only sub-agent creation; not exposed to non-admin containers |
|
| — | `agents.ts:30-63` `create_agent` | **new** | Admin-only sub-agent creation; not exposed to non-admin containers |
|
||||||
|
|
||||||
## New tools in v2
|
## 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`
|
- **Message manipulation**: `send_file`, `edit_message`, `add_reaction`
|
||||||
- **Scheduling**: 6 task-management tools
|
- **Scheduling**: 6 task-management tools
|
||||||
- **Interactive**: `ask_user_question`, `send_card`
|
- **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`
|
- **Agent management**: `create_agent`
|
||||||
|
|
||||||
## Missing from v2
|
## Missing from v2
|
||||||
|
|||||||
+1
-1
@@ -223,7 +223,7 @@ Per-agent ACL and name-resolution map for `send_message(to="name")`. Projected i
|
|||||||
```sql
|
```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_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):**
|
**`unregistered_senders` (via migration 008):**
|
||||||
```sql
|
```sql
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import type { Migration } from './index.js';
|
|||||||
/**
|
/**
|
||||||
* `pending_approvals` table — host-side records for any approval-requiring
|
* `pending_approvals` table — host-side records for any approval-requiring
|
||||||
* request. Used by:
|
* request. Used by:
|
||||||
* - install_packages / request_rebuild / add_mcp_server (session-bound,
|
* - install_packages / add_mcp_server (session-bound, `session_id` set,
|
||||||
* `session_id` set, status stays at default 'pending' until handled)
|
* status stays at default 'pending' until handled)
|
||||||
* - OneCLI credential approvals from the SDK `configureManualApproval`
|
* - OneCLI credential approvals from the SDK `configureManualApproval`
|
||||||
* callback (session_id may be null, action='onecli_credential').
|
* callback (session_id may be null, action='onecli_credential').
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ install_packages({
|
|||||||
|
|
||||||
- Max 20 packages per request.
|
- Max 20 packages per request.
|
||||||
- Names must match strict regex (blocks shell injection via `vim; curl evil.com`).
|
- 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
|
### 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.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
### 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" })
|
|
||||||
```
|
|
||||||
|
|
||||||
### How approval works
|
### How approval works
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
* once the delivery adapter is set.
|
* once the delivery adapter is set.
|
||||||
* - A shutdown callback that stops the OneCLI handler cleanly.
|
* - A shutdown callback that stops the OneCLI handler cleanly.
|
||||||
*
|
*
|
||||||
* Self-mod flows (install_packages, request_rebuild, add_mcp_server) moved
|
* Self-mod flows (install_packages, add_mcp_server) moved out to
|
||||||
* out to `src/modules/self-mod/` in PR #7 — they now register delivery
|
* `src/modules/self-mod/` in PR #7 — they now register delivery actions
|
||||||
* actions + approval handlers via this module's public API.
|
* + approval handlers via this module's public API.
|
||||||
*/
|
*/
|
||||||
import { onDeliveryAdapterReady } from '../../delivery.js';
|
import { onDeliveryAdapterReady } from '../../delivery.js';
|
||||||
import { registerResponseHandler, onShutdown } from '../../response-registry.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
|
### 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.
|
**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
|
### 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.
|
- **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.
|
- **Adapter-ready hook (`onDeliveryAdapterReady`):** starts the OneCLI manual-approval handler once the delivery adapter is set.
|
||||||
- **Shutdown hook (`onShutdown`):** stops the OneCLI handler.
|
- **Shutdown hook (`onShutdown`):** stops the OneCLI handler.
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
# Self-modification
|
# Self-modification
|
||||||
|
|
||||||
You can install additional OS or npm packages, rebuild your container image,
|
You can install additional OS or npm packages or add new MCP servers — but
|
||||||
or add new MCP servers — but only with admin approval.
|
only with admin approval.
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
- `install_packages({ apt?: string[], npm?: string[], reason?: string })` —
|
- `install_packages({ apt?: string[], npm?: string[], reason?: string })` —
|
||||||
adds the listed packages to your container config and rebuilds the image
|
adds the listed packages to your container config, rebuilds the image,
|
||||||
after admin approval. Package names are validated strictly (`[a-z0-9._+-]`
|
and restarts your container, all in a single admin approval step.
|
||||||
for apt, standard npm naming with optional scope). Max 20 packages per
|
Package names are validated strictly (`[a-z0-9._+-]` for apt, standard
|
||||||
request.
|
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.
|
|
||||||
|
|
||||||
- `add_mcp_server({ name, command, args?, env? })` — adds a new MCP server
|
- `add_mcp_server({ name, command, args?, env? })` — adds a new MCP server
|
||||||
to your container config. The container restarts on next message so the
|
to your container config and restarts the container so the new server
|
||||||
new server is available.
|
is wired up on the next message. No image rebuild is required (bun runs
|
||||||
|
TS directly).
|
||||||
|
|
||||||
## Flow
|
## Flow
|
||||||
|
|
||||||
You call one of these tools → the host asks an admin via DM → admin approves
|
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;
|
or rejects. On approve, the config is applied, the image is rebuilt if
|
||||||
the host respawns it on the next message. You'll get a system chat message
|
needed, and the container is killed; the host respawns it on the next
|
||||||
confirming the outcome (either "Packages installed..." or a failure reason).
|
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."
|
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
|
* pending_approvals row whose action matches. Each handler mutates the
|
||||||
* container config, rebuilds/kills the container as needed, and lets the
|
* container config, rebuilds/kills the container as needed, and lets the
|
||||||
* host sweep respawn it on the new image on the next message.
|
* 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 { updateContainerConfig } from '../../container-config.js';
|
||||||
import { buildAgentGroupImage, killContainer } from '../../container-runner.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 });
|
log.info('Container rebuild completed (bundled with install)', { agentGroupId: session.agent_group_id });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notify(
|
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 });
|
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 }) => {
|
export const applyAddMcpServer: ApprovalHandler = async ({ session, payload, userId, notify }) => {
|
||||||
const agentGroup = getAgentGroup(session.agent_group_id);
|
const agentGroup = getAgentGroup(session.agent_group_id);
|
||||||
if (!agentGroup) {
|
if (!agentGroup) {
|
||||||
|
|||||||
@@ -3,26 +3,28 @@
|
|||||||
*
|
*
|
||||||
* Optional tier. Depends on the approvals default module for the request/
|
* Optional tier. Depends on the approvals default module for the request/
|
||||||
* handler plumbing. On install the module registers:
|
* handler plumbing. On install the module registers:
|
||||||
* - Three delivery actions (install_packages, request_rebuild, add_mcp_server)
|
* - Two delivery actions (install_packages, add_mcp_server) that validate
|
||||||
* that validate input and queue an approval via requestApproval().
|
* input and queue an approval via requestApproval().
|
||||||
* - Three matching approval handlers that run on approve: mutate the
|
* - Two matching approval handlers that run on approve and perform the
|
||||||
* container config, rebuild the image, kill the container so the next
|
* complete follow-up:
|
||||||
* wake picks up the change.
|
* 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
|
* Without this module: the MCP tools in the container still write outbound
|
||||||
* outbound system messages with these actions, but delivery logs
|
* system messages with these actions, but delivery logs "Unknown system
|
||||||
* "Unknown system action" and drops them. Admin never sees a card; nothing
|
* action" and drops them. Admin never sees a card; nothing changes.
|
||||||
* changes.
|
|
||||||
*/
|
*/
|
||||||
import { registerDeliveryAction } from '../../delivery.js';
|
import { registerDeliveryAction } from '../../delivery.js';
|
||||||
import { registerApprovalHandler } from '../approvals/index.js';
|
import { registerApprovalHandler } from '../approvals/index.js';
|
||||||
import { applyAddMcpServer, applyInstallPackages, applyRequestRebuild } from './apply.js';
|
import { applyAddMcpServer, applyInstallPackages } from './apply.js';
|
||||||
import { handleAddMcpServer, handleInstallPackages, handleRequestRebuild } from './request.js';
|
import { handleAddMcpServer, handleInstallPackages } from './request.js';
|
||||||
|
|
||||||
registerDeliveryAction('install_packages', handleInstallPackages);
|
registerDeliveryAction('install_packages', handleInstallPackages);
|
||||||
registerDeliveryAction('request_rebuild', handleRequestRebuild);
|
|
||||||
registerDeliveryAction('add_mcp_server', handleAddMcpServer);
|
registerDeliveryAction('add_mcp_server', handleAddMcpServer);
|
||||||
|
|
||||||
registerApprovalHandler('install_packages', applyInstallPackages);
|
registerApprovalHandler('install_packages', applyInstallPackages);
|
||||||
registerApprovalHandler('request_rebuild', applyRequestRebuild);
|
|
||||||
registerApprovalHandler('add_mcp_server', applyAddMcpServer);
|
registerApprovalHandler('add_mcp_server', applyAddMcpServer);
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
# Self-mod module
|
# Self-mod module
|
||||||
|
|
||||||
Optional-tier module that gives agents admin-gated self-modification:
|
Optional-tier module that gives agents admin-gated self-modification:
|
||||||
installing OS/npm packages, rebuilding the container image, and registering
|
installing OS/npm packages and registering new MCP servers. Both paths go
|
||||||
new MCP servers. All three paths go through the approvals module's request
|
through the approvals module's request primitive — no unapproved changes
|
||||||
primitive — no unapproved changes ever land.
|
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
|
## What this module adds
|
||||||
|
|
||||||
- Three delivery actions (`install_packages`, `request_rebuild`, `add_mcp_server`)
|
- Two delivery actions (`install_packages`, `add_mcp_server`) that the
|
||||||
that the container's self-mod MCP tools write into outbound.db. On the host,
|
container's self-mod MCP tools write into outbound.db. On the host, each
|
||||||
each handler validates input and queues an approval via
|
handler validates input and queues an approval via
|
||||||
`approvals.requestApproval()`.
|
`approvals.requestApproval()`.
|
||||||
- Three matching approval handlers that run on approve: mutate the container
|
- Two matching approval handlers that run on approve:
|
||||||
config via `updateContainerConfig`, rebuild the image via
|
- `install_packages` → update `container.json`, rebuild the image via
|
||||||
`buildAgentGroupImage`, and kill the container so the host sweep respawns
|
`buildAgentGroupImage`, and kill the container so the host sweep
|
||||||
it on the new image.
|
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
|
## Dependency
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* Delivery-action handlers for agent-initiated self-modification requests.
|
* Delivery-action handlers for agent-initiated self-modification requests.
|
||||||
*
|
*
|
||||||
* Three actions the container can write into messages_out (via the self-mod
|
* Two actions the container can write into messages_out (via the self-mod
|
||||||
* MCP tools): install_packages, request_rebuild, add_mcp_server. Each one
|
* MCP tools): install_packages, add_mcp_server. Each one validates input
|
||||||
* validates input and queues an approval request. The admin's approval
|
* and queues an approval request. The admin's approval triggers the
|
||||||
* triggers the matching approval handler in ./apply.ts.
|
* 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
|
* Host-side sanitization for install_packages is defense-in-depth — the MCP
|
||||||
* tool validates first. Both layers matter: the DB row carries the payload
|
* 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> {
|
export async function handleAddMcpServer(content: Record<string, unknown>, session: Session): Promise<void> {
|
||||||
const agentGroup = getAgentGroup(session.agent_group_id);
|
const agentGroup = getAgentGroup(session.agent_group_id);
|
||||||
if (!agentGroup) {
|
if (!agentGroup) {
|
||||||
|
|||||||
Reference in New Issue
Block a user