Skip to main content
ClaudeWave
Skill30.7k repo starsupdated 3d ago

slack-a2a-rooms

Agent-to-agent Slack rooms — a group DM (MPIM) holding a human plus two or more NanoClaw sibling bots, where each bot hears the room over its own Socket Mode connection. Registers the admission policy on the Slack channel's bot-inbound guard so bot-authored inbound is admitted only for rooms allowlisted in SLACK_A2A_ROOMS (re-attributed as slack:bot:<bot_id>, hop-limited via SLACK_A2A_MAX_HOPS), plus scripts/open-a2a-room.ts to open a room and register it.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/nanocoai/nanoclaw /tmp/slack-a2a-rooms && cp -r /tmp/slack-a2a-rooms/.claude/skills/slack-a2a-rooms ~/.claude/skills/slack-a2a-rooms
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Slack agent-to-agent rooms (SLACK_A2A_ROOMS)

Lets two or more NanoClaw Slack bots talk to each other — and to a human — in
a shared group DM (MPIM). Empirically established against live Slack:
`conversations.open` with `users=[<human>, <other bot user id>…]` works from a
bot token holding `mpim:write` and returns an `is_mpim` channel, and bots
receive each other's messages over their own Socket Mode connections as plain
`message` events with `channel_type: "mpim"`, `bot_id` set, and `subtype`
null.

**Canonical home.** This directory on `main` is the skill's canonical source —
the setup wizard and any direct apply read it from the checkout. The copy on
the `channels` branch is a compatibility mirror for older checkouts whose
setup fetches companions from there; edits land here, never there.

**Where sibling-bot messages die today.** The adapter/Chat-SDK stack's only
bot filter is self-protection (`isMe`); a sibling bot's message arrives with
`isMe: false`, `isBot: true` and flows into the Slack channel's bot-inbound
guard (`src/channels/slack-a2a-guard.ts`, installed with `/add-slack`), which
drops all bot-authored inbound at the bridge boundary by default — before the
router, before any sender-approval flow. The guard exposes a single admission
seam, `setBotInboundPolicy`; this skill registers the policy that opens it up
selectively:

- Rooms listed in `SLACK_A2A_ROOMS`: bot-authored inbound passes, attributed
  to the user id `slack:bot:<bot_id>`, under a consecutive-hop limit.
- Everywhere else: bot-authored inbound stays dropped at the bridge, exactly
  as the guard's default already does.

**Loop safety.** After N consecutive bot-authored inbound messages in an A2A
room without a human message (N = `SLACK_A2A_MAX_HOPS`, default 6), further
bot messages are dropped with a log line until a human speaks. The counter is
per bot identity and per room; any human message resets it.

**Requires:**

- The Slack channel installed (`/add-slack`), current enough to ship the
  bot-inbound guard (`src/channels/slack-a2a-guard.ts` with the
  `setBotInboundPolicy` seam). The default drop arrives with that payload;
  this skill only adds the allowlisted-room admission on top.
- At least two Slack bot identities on this host (or sibling bots on other
  hosts sharing the workspace). Named identities are registered natively by
  the adapter from `SLACK_INSTANCES` — see the `slack-multi-instance` skill
  for the env-key format. `scripts/open-a2a-room.ts` reads tokens by that
  convention (`SLACK_BOT_TOKEN_<NAME>`; `default` → `SLACK_BOT_TOKEN`).
- Every participating app's manifest must carry the MPIM scopes and event:
  `mpim:write` (open the room), `mpim:history` + `mpim:read` (see it), and
  the `message.mpim` bot event (hear it). Apps provisioned through the
  managed flow (`apps.manifest.create` + `apps.managedInstall`) already
  include all of these.

## Apply

### 1. Verify the installed Slack channel ships the bot-inbound guard

The policy module copied next imports `setBotInboundPolicy` from the installed
`src/channels/slack-a2a-guard.ts`. On a Slack payload that predates the guard,
that import takes down the channel barrel — and with it **every** adapter — so
verify the seam first. If the check fails, **stop**: re-run `/add-slack` from a
channels branch that ships the guard, then re-apply this skill.

```nc:run effect:check
grep -sq 'export function setBotInboundPolicy' src/channels/slack-a2a-guard.ts || { echo 'slack-a2a-rooms: src/channels/slack-a2a-guard.ts is missing or does not export setBotInboundPolicy. Installing anyway would break the channel barrel and take down every channel adapter. Update the installed Slack channel first (re-run /add-slack from a channels branch that ships the bot-inbound guard), then re-apply this skill.' >&2; exit 1; }
```

### 2. Copy the policy module, its guard test, and the room-opener script

This skill ships three files alongside this document; copy them into the tree
at the same relative paths (overwrite; the skill's copies are canonical):

```nc:copy
src/channels/slack-a2a.ts
src/channels/slack-a2a.test.ts
scripts/open-a2a-room.ts
```

- `slack-a2a.ts` — the admission policy: `SLACK_A2A_ROOMS` /
  `SLACK_A2A_MAX_HOPS` parsing (re-read with a ~30s cache so a freshly opened
  room needs no restart), the per-room, per-identity consecutive-hop counter
  with human reset, and the `slack:bot:<bot_id>` re-attribution. The module
  registers itself onto the guard's admission seam on import.
- `slack-a2a.test.ts` — the guard: drives the real channel barrel and the
  real bot-inbound guard end-to-end (see step 4 for what it pins).
- `open-a2a-room.ts` — operator CLI to open a room (see Configuration).

### 3. Register the policy module

Append the self-registration import to the channel barrel (skipped if the
line is already present). Appending at the end keeps it after the Slack
channel's own imports:

```nc:append to:src/channels/index.ts
import './slack-a2a.js';
```

### 4. Build and validate

Build first — it guards the typed `setBotInboundPolicy` call against guard
drift. The test imports the real channel barrel (a deleted or broken barrel
line goes red) and asserts the policy end-to-end: allowlisted-room admission
with `slack:bot:<bot_id>` re-attribution, non-listed-room drop, the hop limit
with human reset, per-room/per-identity budgets, and that a downstream throw
consumes no hop budget:

```nc:run effect:build
pnpm run build
```

```nc:run effect:test
pnpm exec vitest run src/channels/slack-a2a.test.ts
```

## Configuration

`.env` keys (both re-read with a ~30s cache — no restart needed after edits):

- `SLACK_A2A_ROOMS` — comma-separated raw Slack channel ids (the MPIM ids the
  opener script prints, e.g. `G0AAAAAAA` — note MPIM ids may start with `G`
  or `C` depending on workspace vintage). Only these rooms admit bot-authored
  inbound.
- `SLACK_A2A_MAX_HOPS` — consecutive bot-authored inbound messages allowed in
  an A2A room without a human mes
add-atomic-chat-toolSkill

Add Atomic Chat MCP server so the container agent can call local models served by the Atomic Chat desktop app via its OpenAI-compatible API.

add-codexSkill

Use Codex (OpenAI's codex app-server) as a full agent provider — planning, tool orchestration, MCP tools, server-side history, session resume — alongside or instead of Claude. ChatGPT subscription or OpenAI API key, vault-only via OneCLI. Per-group via `ncl groups config update --provider codex`. Distinct from using OpenAI as an MCP tool (where Claude remains the planner).

add-dashboardSkill

Add a monitoring dashboard to NanoClaw. Installs @nanoco/nanoclaw-dashboard and a pusher that sends periodic JSON snapshots.

add-deltachatSkill

Add DeltaChat channel integration via @deltachat/stdio-rpc-server. Native adapter — no Chat SDK bridge. Email-based messaging with end-to-end encryption.

add-discordSkill

Add Discord bot channel integration via Chat SDK.

add-emacsSkill

Add Emacs as a channel. Opens an interactive chat buffer and org-mode integration so you can talk to NanoClaw from within Emacs (Doom, Spacemacs, or vanilla). Local HTTP bridge — no bot token or external service needed.

add-gcal-toolSkill

Add Google Calendar as an MCP tool (list calendars, list/search/create events, free/busy queries) using OneCLI-managed OAuth. Multi-calendar and multi-account supported. Mirrors /add-gmail-tool's stub pattern — no raw credentials ever reach the container; OneCLI injects real tokens at request time.

add-gchatSkill

Add Google Chat channel integration via Chat SDK.