diff --git a/CLAUDE.md b/CLAUDE.md index e9414903c..1cf7e6ff7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,7 +72,10 @@ For ad-hoc queries from skills or scripts, use the in-tree wrapper rather than t | `src/onecli-approvals.ts` | OneCLI credentialed-action approval bridge | | `src/user-dm.ts` | Cold-DM resolution + `user_dms` cache | | `src/group-init.ts` | Per-agent-group filesystem scaffold (CLAUDE.md, skills, agent-runner-src overlay) | -| `src/db/` | DB layer — agent_groups, messaging_groups, sessions, user_roles, user_dms, pending_*, migrations | +| `src/db/container-configs.ts` | CRUD for `container_configs` table (per-group container runtime config) | +| `src/backfill-container-configs.ts` | Migrates legacy `container.json` files into the DB on startup | +| `src/container-restart.ts` | Kill + on-wake respawn for agent group containers | +| `src/db/` | DB layer — agent_groups, messaging_groups, sessions, container_configs, user_roles, user_dms, pending_*, migrations | | `src/channels/` | Channel adapter infra (registry, Chat SDK bridge); specific channel adapters are skill-installed from the `channels` branch | | `src/providers/` | Host-side provider container-config (`claude` baked in; `opencode` etc. installed from the `providers` branch) | | `container/agent-runner/src/` | Agent-runner: poll loop, formatter, provider abstraction, MCP tools, destinations | @@ -93,7 +96,7 @@ ncl help | Resource | Verbs | What it is | |----------|-------|------------| -| groups | list, get, create, update, delete | Agent groups (workspace, personality, container config) | +| groups | list, get, create, update, delete, restart, config get/update, config add-mcp-server/remove-mcp-server, config add-package/remove-package | Agent groups (workspace, personality, container config) | | messaging-groups | list, get, create, update, delete | A single chat/channel on one platform | | wirings | list, get, create, update, delete | Links a messaging group to an agent group (session mode, triggers) | | users | list, get, create, update | Platform identities (`:`) | @@ -120,10 +123,32 @@ Each `/add-` skill is idempotent: `git fetch origin ` → copy mod One tier of agent self-modification today: -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`. +1. **`install_packages` / `add_mcp_server`** — changes to the per-agent-group container config in the DB (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), writes an `on_wake` message, kills the container, and respawns via `onExit` callback. The on-wake message is only picked up by the fresh container's first poll — dying containers can never steal it. `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. +## Container Config + +Per-agent-group container runtime config (provider, model, packages, MCP servers, mounts, etc.) lives in the `container_configs` table in the central DB. Materialized to `groups//container.json` at spawn time so the container runner can read it. Managed via `ncl groups config get/update` and the self-mod MCP tools. + +**`cli_scope`** — controls what the agent can do with `ncl` from inside the container: + +| Value | Behavior | +|-------|----------| +| `disabled` | Agent never learns about ncl (instructions excluded from CLAUDE.md). Host dispatch rejects any `cli_request`. | +| `group` (default) | Agent can access `groups`, `sessions`, `destinations`, `members` only, scoped to its own agent group. `--id` and group args are auto-filled. Cross-group access rejected. `cli_scope` changes blocked. | +| `global` | Unrestricted. Set automatically for owner agent groups via `init-first-agent`. | + +Key files: `src/db/container-configs.ts`, `src/container-config.ts`, `src/cli/dispatch.ts` (scope enforcement), `src/claude-md-compose.ts` (instructions exclusion). + +## Container Restart + +`ncl groups restart --id [--rebuild] [--message ]`. Kills running containers; if `--message` is provided, writes an `on_wake` message and respawns via `onExit` callback. Without `--message`, containers come back on the next user message. From inside a container, `--id` is auto-filled and only the calling session is restarted. + +The `on_wake` column on `messages_in` ensures wake messages are only picked up by a fresh container's first poll iteration. This prevents the race where a dying container (still in its SIGTERM grace period) could steal the message. `killContainer` accepts an optional `onExit` callback that fires after the process exits, guaranteeing the old container is gone before the new one spawns. + +Key files: `src/container-restart.ts`, `src/container-runner.ts` (`killContainer`), `container/agent-runner/src/db/messages-in.ts` (`getPendingMessages`). + ## Secrets / Credentials / OneCLI API keys, OAuth tokens, and auth credentials are managed by the OneCLI gateway. Secrets are injected into per-agent containers at request time — none are passed in env vars or through chat context. The container agent sees this via the `onecli-gateway` container skill (`container/skills/onecli-gateway/SKILL.md`), which teaches it how the proxy works, how to handle auth errors, and to never ask for raw credentials. Host-side wiring: `src/onecli-approvals.ts`, `ensureAgent()` in `container-runner.ts`. Run `onecli --help`. diff --git a/container/agent-runner/src/mcp-tools/cli.instructions.md b/container/agent-runner/src/mcp-tools/cli.instructions.md index 9dee60fdd..6a4f72e7f 100644 --- a/container/agent-runner/src/mcp-tools/cli.instructions.md +++ b/container/agent-runner/src/mcp-tools/cli.instructions.md @@ -1,49 +1,51 @@ ## Admin CLI (`ncl`) -The `ncl` command is available at `/usr/local/bin/ncl`. It lets you query and modify NanoClaw's central configuration — agent groups, messaging groups, wirings, users, roles, and more. +The `ncl` command is available at `/usr/local/bin/ncl`. It lets you query and modify NanoClaw's central configuration. ### Usage ``` -ncl [] [--flags] +ncl [--flags] ncl help ncl help ``` +### Scope + +Your CLI access may be scoped. Run `ncl help` to see which resources are available and whether args are auto-filled. Under `group` scope (the default), `--id` and group-related args are auto-filled to your agent group — you don't need to pass them. + ### Resources +Run `ncl help` for the full list. Common resources: + | Resource | Verbs | What it is | |----------|-------|------------| -| groups | list, get, create, update, delete | Agent groups (workspace, personality, container config) | -| messaging-groups | list, get, create, update, delete | A single chat/channel on one platform | -| wirings | list, get, create, update, delete | Links a messaging group to an agent group (session mode, triggers) | -| users | list, get, create, update | Platform identities (`:`) | -| roles | list, grant, revoke | Owner / admin privileges (global or scoped to an agent group) | -| members | list, add, remove | Unprivileged access gate for an agent group | -| destinations | list, add, remove | Where an agent group can send messages | +| groups | list, get, create, update, delete, restart, config get/update, config add-mcp-server/remove-mcp-server, config add-package/remove-package | Agent groups (workspace, personality, container config) | | sessions | list, get | Active sessions (read-only) | -| user-dms | list | Cold-DM cache (read-only) | -| dropped-messages | list | Messages from unregistered senders (read-only) | -| approvals | list, get | Pending approval requests (read-only) | +| destinations | list, add, remove | Where an agent group can send messages | +| members | list, add, remove | Unprivileged access gate for an agent group | + +Additional resources (available under `global` scope only): messaging-groups, wirings, users, roles, user-dms, dropped-messages, approvals. ### When to use -- **Looking up your own config** — `ncl groups get ` to see your agent group settings. -- **Finding who you're wired to** — `ncl wirings list` to see which messaging groups route to which agent groups. -- **Checking user roles** — `ncl roles list` to see who is an owner/admin. -- **Answering questions about the system** — when the user asks about groups, channels, users, or configuration, query `ncl` rather than guessing. +- **Looking up your own config** — `ncl groups get` or `ncl groups config get` to see your container config. +- **Restarting your container** — `ncl groups restart` (with optional `--rebuild` and `--message`). +- **Checking who's in your group** — `ncl members list`. +- **Seeing your destinations** — `ncl destinations list`. +- **Answering questions about the system** — query `ncl` rather than guessing. ### Access rules -Read commands (list, get) are open. Write commands (create, update, delete, grant, revoke, add, remove) require admin approval — the request is held until an admin approves it. +Read commands (list, get) are open. Write commands (create, update, delete, restart, config update, add, remove) require admin approval — the request is held until an admin approves it. ### Approval flow -Write commands (create, update, delete, grant, revoke, add, remove) require admin approval. Here's what happens: +Write commands require admin approval. Here's what happens: -1. You run the command (e.g. `ncl groups create --name "Research" --folder research`). +1. You run the command (e.g. `ncl groups config update --model claude-sonnet-4-5-20250514`). 2. The command returns immediately with an `approval-pending` response — it has **not** been executed yet. -3. An admin or owner gets a notification (on the same channel when possible) showing exactly what you requested, with approve/reject options. +3. An admin or owner gets a notification showing exactly what you requested, with approve/reject options. 4. Once the admin responds: - **Approved:** the command executes and the result is delivered back to you as a system message in this conversation. - **Rejected:** you get a system message saying the request was rejected. @@ -54,25 +56,24 @@ You don't need to poll or retry — the result arrives automatically. ```bash # Read commands (no approval needed) -ncl groups list -ncl groups get abc123 -ncl wirings list --messaging-group-id mg_xyz -ncl roles list -ncl wirings help +ncl groups get +ncl groups config get +ncl sessions list +ncl destinations list +ncl members list # Write commands (approval required) -ncl groups create --name "Research" --folder research -ncl groups update abc123 --name "Research v2" -ncl roles grant --user telegram:jane --role admin -ncl roles grant --user discord:bob --role admin --group abc123 -ncl members add --user-id telegram:jane --agent-group-id abc123 -ncl destinations add --agent-group-id abc123 --messaging-group-id mg_xyz +ncl groups restart +ncl groups restart --rebuild --message "Config updated." +ncl groups config update --model claude-sonnet-4-5-20250514 +ncl groups config add-mcp-server --name rss --command npx --args '["some-rss-mcp"]' +ncl groups config add-package --npm some-package +ncl members add --user telegram:jane ``` ### Tips -- Use `ncl help` to see all available fields, types, enums, and which fields are required or updatable. +- Use `ncl help` to see all available fields, types, enums, and which fields are auto-filled. - Flags use `--hyphen-case` (e.g. `--agent-group-id`), mapped to `underscore_case` DB columns automatically. -- `list` supports filtering by any non-auto column (e.g. `ncl wirings list --messaging-group-id mg_xyz`). Default limit is 200 rows; override with `--limit N`. -- For composite-key resources (roles, members, destinations), use the custom verbs (grant/revoke, add/remove) instead of create/delete. +- `list` supports filtering by any non-auto column. Default limit is 200 rows; override with `--limit N`. - Write commands return `approval-pending` immediately — don't treat this as an error. Wait for the system message with the result. diff --git a/docs/db-central.md b/docs/db-central.md index 8268acf95..75c27f394 100644 --- a/docs/db-central.md +++ b/docs/db-central.md @@ -10,7 +10,7 @@ Access layer: `src/db/`. Authoritative schema reference: `src/db/schema.ts` (com ### 1.1 `agent_groups` -Agent workspaces. Each maps 1:1 to a `groups//` directory containing `CLAUDE.md`, skills, and `container.json`. Container config lives on disk, not in the DB. +Agent workspaces. Each maps 1:1 to a `groups//` directory containing `CLAUDE.md` and skills. Container config lives in `container_configs` (see §1.x below); a `container.json` file is materialized at spawn time for the container runner to read. ```sql CREATE TABLE agent_groups ( @@ -294,6 +294,32 @@ CREATE TABLE schema_version ( ); ``` +### 1.15 `container_configs` + +Per-agent-group container runtime config. Source of truth for provider, model, packages, MCP servers, mounts, CLI scope, etc. Materialized to `groups//container.json` at spawn time. + +```sql +CREATE TABLE container_configs ( + agent_group_id TEXT PRIMARY KEY REFERENCES agent_groups(id) ON DELETE CASCADE, + provider TEXT, + model TEXT, + effort TEXT, + image_tag TEXT, + assistant_name TEXT, + max_messages_per_prompt INTEGER, + skills TEXT NOT NULL DEFAULT '"all"', + mcp_servers TEXT NOT NULL DEFAULT '{}', + packages_apt TEXT NOT NULL DEFAULT '[]', + packages_npm TEXT NOT NULL DEFAULT '[]', + additional_mounts TEXT NOT NULL DEFAULT '[]', + cli_scope TEXT NOT NULL DEFAULT 'group', -- disabled | group | global + updated_at TEXT NOT NULL +); +``` + +- **Readers:** `src/container-config.ts`, `src/container-runner.ts`, `src/cli/dispatch.ts` (scope enforcement), `src/claude-md-compose.ts` +- **Writers:** `src/db/container-configs.ts`, `src/modules/self-mod/apply.ts`, `src/backfill-container-configs.ts` + --- ## 2. Migration system @@ -313,6 +339,8 @@ Migrations live in `src/db/migrations/`, one file per migration. Runner: `runMig | 007 | `007-pending-approvals-title-options.ts` | `ALTER TABLE pending_approvals` add `title`, `options_json` (retrofits DBs created between 003 and 007) | | 008 | `008-dropped-messages.ts` | `unregistered_senders` | | 009 | `009-drop-pending-credentials.ts` | Drop the defunct `pending_credentials` table | +| 014 | `014-container-configs.ts` | `container_configs` — per-agent-group container runtime config | +| 015 | `015-cli-scope.ts` | `ALTER TABLE container_configs ADD COLUMN cli_scope` | Numbers 005 and 006 are intentionally absent — migrations were renumbered during early development. diff --git a/docs/db-session.md b/docs/db-session.md index 9370d90f5..2b9fd233c 100644 --- a/docs/db-session.md +++ b/docs/db-session.md @@ -33,19 +33,22 @@ Every message landing in the session: user chat, scheduled task, recurring task, ```sql CREATE TABLE messages_in ( - id TEXT PRIMARY KEY, - seq INTEGER UNIQUE, -- EVEN only (host assigns) — see §3 - kind TEXT NOT NULL, - timestamp TEXT NOT NULL, - status TEXT DEFAULT 'pending', -- pending|completed|failed|paused - process_after TEXT, - recurrence TEXT, -- cron expr for recurring - series_id TEXT, -- groups occurrences of a recurring task - tries INTEGER DEFAULT 0, - platform_id TEXT, - channel_type TEXT, - thread_id TEXT, - content TEXT NOT NULL -- JSON; shape depends on kind + id TEXT PRIMARY KEY, + seq INTEGER UNIQUE, -- EVEN only (host assigns) — see §3 + kind TEXT NOT NULL, + timestamp TEXT NOT NULL, + status TEXT DEFAULT 'pending', -- pending|completed|failed|paused + process_after TEXT, + recurrence TEXT, -- cron expr for recurring + series_id TEXT, -- groups occurrences of a recurring task + tries INTEGER DEFAULT 0, + trigger INTEGER NOT NULL DEFAULT 1, -- 0 = context only (don't wake), 1 = wake agent + platform_id TEXT, + channel_type TEXT, + thread_id TEXT, + content TEXT NOT NULL, -- JSON; shape depends on kind + source_session_id TEXT, -- agent-to-agent return path + on_wake INTEGER NOT NULL DEFAULT 0 -- 1 = only deliver on container's first poll ); CREATE INDEX idx_messages_in_series ON messages_in(series_id); ``` diff --git a/src/db/schema.ts b/src/db/schema.ts index 533ec5177..56701e69f 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -7,8 +7,7 @@ export const SCHEMA = ` -- Agent workspaces: folder, skills, CLAUDE.md. -- All workspaces are equal; privilege lives on users, not groups. --- Container config (mcpServers, packages, imageTag, additionalMounts) lives --- in groups//container.json on disk, not in the DB. +-- Container config lives in the container_configs table (see migration 014). CREATE TABLE agent_groups ( id TEXT PRIMARY KEY, name TEXT NOT NULL,