From fb1df2d4021d25a65c0ebbd79e44cd15023cd427 Mon Sep 17 00:00:00 2001 From: Gabi Simons Date: Wed, 8 Jul 2026 15:24:34 +0300 Subject: [PATCH] docs(harness): capability doc ([BREAKING] anchor), CHANGELOG entries, db-central + CLAUDE.md rows Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 2 ++ CLAUDE.md | 3 ++- docs/db-central.md | 3 +++ docs/harness-capabilities.md | 46 ++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 docs/harness-capabilities.md diff --git a/CHANGELOG.md b/CHANGELOG.md index edfd5607c..dc42d5da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to NanoClaw will be documented in this file. ## [Unreleased] +- [BREAKING] **Claude's experimental agent-teams feature is now OFF by default for every group.** NanoClaw's agent-to-agent system (`create_agent` + destinations) is the authoritative multi-agent path; harness teams multiplied separately-billed agent contexts invisibly and its `SendMessage` collided with `mcp__nanoclaw__send_message`. Per-group settings.json is now *reconciled* on every spawn from the new `container_configs.harness_capabilities` column (defaults live in `src/harness-capabilities.ts`) — the legacy always-on `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` key is removed at each group's next spawn. **Migration:** [docs/harness-capabilities.md](docs/harness-capabilities.md) — re-enable per group with `ncl groups config update --id --harness-capabilities 'agent-teams=on'` + `ncl groups restart --id `. +- [BREAKING] **The harness `Workflow` tool is now OFF by default for every group.** It duplicates NanoClaw's orchestration model, can spawn dozens of agents outside NanoClaw's visibility, and was the single largest per-turn schema (~26KB/turn including its agent-types catalog, measured on claude-code 2.1.197). Disabled via the reconciled `disableWorkflows` settings key plus a runner disallow backstop; `DesignSync` (desktop-only, 9.3KB/turn) joins the fixed disallow list with no toggle. **Migration:** [docs/harness-capabilities.md](docs/harness-capabilities.md) — re-enable per group with `ncl groups config update --id --harness-capabilities 'workflow=on'` + `ncl groups restart --id `. - **Claude tool allowlist reconciled with the pinned CLI, plus a tool-surface drift guard.** `TOOL_ALLOWLIST` in the agent runner named five tools that don't exist on claude-code 2.1.197 (`Task` — renamed `Agent` upstream — plus `TodoWrite`, `TeamCreate`, `TeamDelete`, `ToolSearch`); the phantom entries are removed and the comment corrected: `allowedTools` is a permission auto-approve list, **not an availability filter** — paired wire captures show no allowlist effect on the offered tool surface, and `bypassPermissions` moots its permission role. (The surface itself shows run-to-run variance in conditional tools on this CLI pin, so neither `allowedTools` nor `disallowedTools` alone should be relied on to shape what the model sees — the runner's PreToolUse hook is the deterministic block.) No wire-visible behavior change. A new wire-captured fixture (`container/agent-runner/src/providers/sdk-tools-baseline.json`) and `claude.tools.test.ts` now fail on any future claude-code CLI **or SDK** pin bump until the fixture is regenerated with `dump-sdk-tools.ts` and the lists re-verified. - [BREAKING] **`whatsapp-formatting` and `slack-formatting` container skills moved from trunk to the `channels` branch.** They now install with their channel — `/add-whatsapp` / `/add-slack` and the setup installers copy them in — so installs without those channels stop carrying channel-specific formatting instructions in every agent's context. **Migration — only if this install has the channel wired** (check `src/channels/whatsapp.ts` / `src/channels/slack.ts`): updating removes both skills from the working tree — WhatsApp agents lose the formatting fragment from their composed CLAUDE.md on next spawn, Slack agents lose the mrkdwn skill from `~/.claude/skills`. Re-run the matching skill, `/add-whatsapp` or `/add-slack` (idempotent), to restore. Installs without the channel need nothing — do NOT run the add-skill just in case; it installs the full channel adapter. - **Pre-task script failures back their series off instead of spinning.** A `--script` that errors lands the occurrence as a failed run (`script-skip:error` ack → `failed` status); recurrence reads the series' trailing failed streak and re-arms at `max(cron next, now + 2·2^(n−1) min, cap 60)`; after 8 consecutive failures the series is auto-paused with a host-written note in its run log (`ncl tasks resume` revives it). A deliberate `wakeAgent:false` gate is a normal run and never backs off. Also fixed: an explicitly-addressed `` in a task fire's final text now delivers as a deliberate send (previously suppressed as a turn-reply echo → zero delivery when the agent skipped the MCP tool); identical echoes of an MCP send are dropped in the runner, where the duplication originates. diff --git a/CLAUDE.md b/CLAUDE.md index 4416e14c8..c414f51a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,6 +73,7 @@ For ad-hoc queries from skills or scripts, use the in-tree wrapper rather than t | `src/modules/permissions/user-dm.ts` | Cold-DM resolution + `user_dms` cache | | `src/group-init.ts` | Per-agent-group filesystem scaffold (CLAUDE.md, skills) — agent-runner source is a shared read-only mount, not copied per group | | `src/db/container-configs.ts` | CRUD for `container_configs` table (per-group container runtime config) | +| `src/harness-capabilities.ts` | Harness-capability registry — `agent-teams`/`workflow` toggles (both default off), resolved into `container.json`; settings reconciler lives in `group-init.ts`. See [docs/harness-capabilities.md](docs/harness-capabilities.md) | | `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 | @@ -141,7 +142,7 @@ Per-agent-group container runtime config (provider, model, packages, MCP servers | 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`, `tasks` only, scoped to its own agent group. `--id` and group args are auto-filled. Cross-group access rejected. `cli_scope` changes blocked. | +| `group` (default) | Agent can access `groups`, `sessions`, `destinations`, `members`, `tasks` only, scoped to its own agent group. `--id` and group args are auto-filled. Cross-group access rejected. `cli_scope` and `harness_capabilities` 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). diff --git a/docs/db-central.md b/docs/db-central.md index 6ae540058..a2db0e289 100644 --- a/docs/db-central.md +++ b/docs/db-central.md @@ -320,12 +320,14 @@ CREATE TABLE container_configs ( packages_npm TEXT NOT NULL DEFAULT '[]', additional_mounts TEXT NOT NULL DEFAULT '[]', cli_scope TEXT NOT NULL DEFAULT 'group', -- disabled | group | global + harness_capabilities TEXT NOT NULL DEFAULT '{}', -- sparse overrides: {"agent-teams"|"workflow": "on"|"off"} 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` +- `harness_capabilities` stores per-group overrides only; code defaults + resolution live in `src/harness-capabilities.ts` (see [harness-capabilities.md](harness-capabilities.md)) ### 1.16 `pending_sender_approvals` @@ -425,6 +427,7 @@ Several early migrations were later renamed/retired and replaced by "module" fil | 16 | `messaging-group-instance` | `016-messaging-group-instance.ts` | `messaging_groups` gets an `instance` column (adapter-instance dimension); table recreate (`disableForeignKeys: true`) backfills `instance = channel_type` on every existing row and relaxes the `UNIQUE` to `(channel_type, platform_id, instance)` | | 17 | `agent-message-policies` | `017-agent-message-policies.ts` | `agent_message_policies` (see §1.18) | | 18 | `approvals-approver-user-id` | `018-approvals-approver-user-id.ts` | `pending_approvals.approver_user_id` — names a single required approver for a2a message-gate policies | +| 19 | `harness-capabilities` | `019-harness-capabilities.ts` | `ALTER TABLE container_configs ADD COLUMN harness_capabilities` — per-group harness toggles (see [harness-capabilities.md](harness-capabilities.md)) | Numbers 5 and 6 are intentionally absent — migrations were renumbered during early development. diff --git a/docs/harness-capabilities.md b/docs/harness-capabilities.md new file mode 100644 index 000000000..55c1b9bf0 --- /dev/null +++ b/docs/harness-capabilities.md @@ -0,0 +1,46 @@ +# Harness capabilities + +NanoClaw disables harness-native features that overlap its own systems, and exposes a small per-group toggle surface for the ones where both states are meaningful. Policy (keys, defaults, resolution) lives host-side in [`src/harness-capabilities.ts`](../src/harness-capabilities.ts); per-group overrides live in the `harness_capabilities` column of `container_configs`; mechanisms live in the agent runner and the settings reconciler ([`src/group-init.ts`](../src/group-init.ts)). + +## Capability table + +| Capability | Key | Default | Mechanism | +|---|---|---|---| +| Agent teams (experimental multi-agent coordination inside one session) | `agent-teams` | **off** | Settings reconciler adds/removes `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` in the group's `settings.json` on every spawn. On the pinned CLI, settings env strictly beats SDK options env, so the settings file is the only working switch. | +| Workflow tool (in-session multi-agent orchestration scripts) | `workflow` | **off** | Reconciler sets `disableWorkflows: true` (removes the tool and its agent-types catalog — ~26KB/turn); the runner also adds `Workflow` to `disallowedTools` + PreToolUse hook as a backstop. | +| Cron/scheduling (`CronCreate/CronDelete/CronList`, `ScheduleWakeup`) | — | fixed off | `disallowedTools` + hook. NanoClaw's `schedule_task` MCP suite is the authoritative scheduler. | +| `AskUserQuestion` | — | fixed off | `disallowedTools` (returns a placeholder headless; `ask_user_question` is the real mechanism). | +| Plan/worktree modes | — | fixed off | `disallowedTools` (broken headless). | +| `DesignSync` | — | fixed off | `disallowedTools` (desktop design-tool integration; nothing to sync with in a container; ~9.3KB/turn schema). | +| Task list (`TaskCreate/…`), subagents (`Agent`), web (`WebSearch/WebFetch`) | — | fixed on | No NanoClaw overlap. Harness task lists are per-session scratch — not NanoClaw scheduled tasks. | + +Toggling: + +```bash +ncl groups config get --id # shows raw overrides + resolved view +ncl groups config update --id --harness-capabilities 'agent-teams=on' +ncl groups config update --id --harness-capabilities 'workflow=on,agent-teams=default' +ncl groups restart --id # apply +``` + +`default` clears the per-group override (it is never stored). Changes from inside a container are rejected — like `cli_scope`, this is operator-only. + +## [BREAKING] Agent teams default off + +- **Detect**: after updating, agents in a group that used Claude's experimental agent-teams feature lose team coordination (the expanded `Agent`/`SendMessage`/`TaskCreate`/`TaskList` schemas revert to baseline) at the group's next container spawn. `ncl groups config get` shows `agent-teams: off (default)`; the group's `.claude-shared/settings.json` no longer contains `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`. +- **Why**: it overlaps NanoClaw's agent-to-agent system (`create_agent` + destinations routing), is experimental upstream, multiplies separately-billed agent contexts invisibly to NanoClaw ops, and its `SendMessage` invites confusion with `mcp__nanoclaw__send_message`. +- **Fix**: `ncl groups config update --id --harness-capabilities 'agent-teams=on'` then `ncl groups restart --id `. +- **Verify**: `ncl groups config get --id ` shows `agent-teams: on (override)`; the settings.json env key is back after the next spawn. +- **Rollback**: the fix line above IS the rollback — per group, no code changes. + +## [BREAKING] Workflow tool default off + +- **Detect**: agents no longer have the `Workflow` tool; request payloads shrink ~26KB/turn. `ncl groups config get` shows `workflow: off (default)`. +- **Why**: it is redundant with NanoClaw's orchestration model (sessions + a2a, host stays in control), can spawn dozens of agents invisible to NanoClaw ops, and is the single largest tool schema on every turn (21.3KB measured on CLI 2.1.197). +- **Fix**: `ncl groups config update --id --harness-capabilities 'workflow=on'` then `ncl groups restart --id `. +- **Verify / Rollback**: as above, with `workflow`. + +## Notes for forks + +- If your fork patched `SDK_DISALLOWED_TOOLS` in `container/agent-runner/src/providers/claude.ts`: the fixed list still lives there, but per-group state now composes through `buildDisallowedTools()` — re-apply your patch to the fixed list, or express it as capability keys if it fits. +- The measured numbers above are for `@anthropic-ai/claude-code` 2.1.197 / SDK 0.3.197. When bumping the pin, `claude.tools.test.ts` fails until you regenerate the tool-surface fixture: run [`dump-sdk-tools.ts`](../container/agent-runner/src/providers/dump-sdk-tools.ts) inside the agent image (invocation in its header) and re-verify the allow/disallow lists against the new surface.