Skip to content
Back to skills

Skill

ASecurity

Use when spawning agent teams that need shared discussion visibility beyond hub-and-spoke DMs

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 29, 2026
developmentpythongobashsqlreactfastapicode-reviewgitapidatabase

Works with

  • claude code
  • cursor
  • api
  • mcp

Security analysis

A100/100

Scanned May 29, 2026

npx -y skills add runno-ai/chatnut --skill skill --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/runno-ai-skill/badge)](https://www.skillsdirectory.com/skills/runno-ai-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: chatnut
title: chatnut
description: Use when spawning agent teams that need shared discussion visibility beyond hub-and-spoke DMs
aliases: [chatnut]
---


# Team Chat

Shared chatroom for agent teams backed by the chatnut server (FastAPI + SQLite). All teammates read from and post to a shared room via MCP tools, giving everyone full visibility into the discussion. Includes a live web UI with SSE streaming for real-time observation.

**Storage:** SQLite database at `~/.chatnut/chatnut.db` (WAL mode). Safe from Claude Code's `TeamDelete`.

**Server:** Always running at your configured URL. MCP endpoint at `/mcp/`.

## Setup

After `TeamCreate`, initialize the chatroom and capture the **room_id** for teammates:

```
result = init_room(project="<project-name>", name="<team-name>", branch="<branch-name>", description="...")
# result contains: { "id": "<room-uuid>", "name": "...", "project": "...", ... }
# Pass result["id"] as ROOM_ID to all teammate spawn prompts
```

- **`project`** = the project being worked on (e.g., `my-app`, `backend`) — NOT the team name
- **`name`** = the chatroom name — naming convention is up to your team (e.g., `plan-auth-refactor`, `review-api-v2`)
- **`branch`** = the git branch being worked on (optional)

The returned `id` is a stable UUID. Pass it to teammates so they can use `room_id` for all reads/writes (faster, no name lookup).

The web UI is automatically opened in the user's browser when `init_room` is called. The response includes a `web_url` field with the direct link.

## Agent Registration (for @mention notifications)

After creating a room and spawning teammates, register each agent for @mention support:

```
register_agent(room_id=ROOM_ID, agent_name="security", task_id="security-agent-task-id")
register_agent(room_id=ROOM_ID, agent_name="architect", task_id="architect-agent-task-id")
```

When any teammate posts a message containing `@security`, the `post_message` response includes:
```json
{"mentions": [{"name": "security", "task_id": "security-agent-task-id"}]}
```

The PM (or posting agent) should then `SendMessage` to each mentioned agent's task_id.

- `agent_name` is case-insensitive (normalized to lowercase)
- Unregistered @mentions are silently skipped
- UPSERT semantics — re-registering updates the `task_id`

## Server Recovery

If any `mcp__chatnut__*` tool call fails with a connection or session error:

1. **Check server health:**

   ```bash
   PORT=$(cat ~/.chatnut/server.port 2>/dev/null || echo "8000")
   curl -s "http://127.0.0.1:${PORT}/api/status"
   ```

2. **If unreachable, restart the server:**

   ```bash
   # Graceful stop (if PID file exists):
   kill -TERM $(cat ~/.chatnut/server.pid 2>/dev/null) 2>/dev/null || true
   # Start in background:
   chatnut serve &
   ```
3. **Wait up to 10s for the health check to pass** — poll `/api/status` until it returns `200`.
4. **Retry the failed tool call once.**
5. **Only fall back to `SendMessage`** if the retry also fails — do not silently drop messages.

## MCP Tools

| Tool | Purpose |
|------|---------|
| `post_message(room_id, sender, content, message_type?)` | Post a message to a room |
| `read_messages(room_id, since_id?, limit?, message_type?)` | Read messages from a room |
| `wait_for_messages(room_id, since_id, timeout?, limit?, message_type?)` | Block until new messages arrive (long-poll, max 60s); returns `timed_out=True` on timeout — **use instead of polling** |
| `init_room(project, name, branch?, description?, team_name?)` | Create a room, returns room_id UUID; writes chatroom.json to team config dir when team_name provided |
| `list_rooms(project?, status?)` | List rooms (filter by project, status) |
| `archive_room(project, name)` | Archive a room (keeps messages) |
| `delete_room(room_id)` | Permanently delete an archived room and its messages |
| `clear_room(project, name)` | Delete all messages in a room |
| `mark_read(room_id, reader, last_read_message_id)` | Mark messages as read (cursor only moves forward) |
| `search(query, project?)` | Search room names + message content |
| `list_projects()` | List distinct project names |
| `ping()` | Health check — returns `db_path`, `status`, `version`, and optionally `latest` + `update_available` when a newer version exists |
| `update_status(room_id, sender, status)` | Update a sender's current status in a room (UPSERT) |
| `get_team_status(room_id)` | Get current status of all team members in a room |
| `register_agent(room_id, agent_name, task_id)` | Register an agent for @mention notifications (UPSERT, case-insensitive) |
| `list_agents(room_id)` | List all registered agents in a room |

## Communication Protocol

### Channels

```
SendMessage = wake-up ping (triggers a teammate's turn)
Chatroom    = content channel (all substantive discussion)
```

**Rule:** SendMessage contains only a short ping (e.g., "Check the chatroom — new findings posted"). ALL substantive content goes in the chatroom so every teammate has full visibility.

### Proactive Engagement Rules

Teammates are **active participants**, not passive responders:

1. **Chatroom-first** — On ANY wake-up (from PM or peer), read the full chatroom before responding. React to everything new, not just the ping that woke you.
2. **Peer-to-peer pings** — When your finding affects another role, ping them directly via SendMessage. Don't route through PM.
3. **@role tagging** — Tag specific roles in chatroom posts for cross-cutting concerns: "**@security** — this endpoint accepts user input without validation"
4. **Challenge and disagree** — Don't just agree. Propose alternatives, flag risks, question assumptions.
5. **Build on others** — Reference and extend other teammates' points. "Agreeing with @backend-dev on X — adding that Y is also affected."

### PM Role

The PM is a **facilitator**, not a message router:
- **Triggers rounds** — pings teammates to start each discussion round
- **Moderates** — posts pointed questions to the chatroom to drive discussion
- **Does NOT relay** — teammates ping each other directly for cross-cutting concerns
- **Synthesizes** — reads the full chatroom after rounds complete and consolidates findings

## Discussion Rounds

Consuming skills trigger rounds; the protocol within each round is standard.

**Round 1 — Initial Posts:**
PM posts context to chatroom → pings all teammates. Each teammate: reads chatroom → posts primary findings → pings relevant peers → goes idle.

**Round 2 — Cross-Review:**
PM pings all teammates (or peers wake each other from Round 1 pings). Each teammate: reads FULL chatroom → responds to others' points → challenges or builds → goes idle.

**Round 3 — Resolution** (optional, PM triggers if disagreements remain):
PM pings specific teammates with pointed questions. Teammates resolve directly via chatroom + peer pings.

**When to skip rounds:** Consuming skills may use fewer rounds (e.g., code-review uses progressive batch processing instead of fixed rounds). The protocol above is the default — skills adapt as needed.

## Teammate Instructions

Teammate protocol rules are installed as a global Claude Code rule at `~/.claude/rules/chatnut-protocol.md`. All spawned agents load this automatically — **no need to include protocol rules in spawn prompts**.

Install/update: `chatnut install`

The only thing consuming skills need to include in spawn prompts is the `room_id`:

```
## Team Chatroom (room_id: <ROOM_ID>)
```

### Multi-Round Protocol
You may be woken up multiple times:
- **Round 1:** Post your primary findings
- **Round 2:** Read others' posts and respond — agree, challenge, or extend
- **Round 3:** Resolve remaining disagreements

## MCP Fallback (SendMessage)

If `mcp__chatnut__*` tools are **unavailable** — server down, tool not in the teammate's tool list, or any tool error — fall back to `SendMessage` directed at the **team leader**.

### Detection

A teammate should switch to fallback mode when:
- Any `mcp__chatnut__*` call returns an error or is not available
- The `mcp__chatnut__ping` health check fails
- The tool is simply absent from the teammate's tool list

### Fallback Behavior (Teammate)

1. **Send full content** — do NOT trim to a ping; include everything the chatroom post would have contained
2. **Prefix with `[CHATROOM FALLBACK]`** — signals to the leader that MCP is down for this agent
3. **Direct to team leader** — always send to the PM/orchestrator, not peers
4. **Continue working** — one failed MCP call does not stop the task; proceed and report via SendMessage

```
SendMessage(
  type="message",
  recipient="<team-leader-name>",
  content="[CHATROOM FALLBACK] mcp__chatnut unavailable.\n\n## My Findings\n\n<full content>",
  summary="<role> findings (MCP fallback)"
)
```

### PM Handling of Fallback Messages

When the PM receives a `[CHATROOM FALLBACK]` message:
1. **If MCP is available on PM's end** — relay the content to the chatroom via `post_message` on behalf of the teammate, then continue normally
2. **If MCP is also down** — incorporate findings directly into PM's own work; note the teammate's contribution in any final summary
3. **Do NOT ignore** — fallback messages carry real work output, treat them as chatroom posts

## Team Lifecycle (PM Rules)

### Dismissing Teammates

1. **Check before dismissing** — before sending `shutdown_request`, check if the teammate is still working (in_progress tasks, recent chatroom posts). If they are, wait up to 1 minute for them to finish before proceeding with your own work.
2. **Partial dismissal is fine** — if some mates are done and others are still working, dismiss the finished ones and keep going. A team is not "done" until ALL mates are dismissed.
3. **Incorporate before dismissing** — when your current job completes, read new chatroom messages, incorporate findings, then dismiss mates whose work is complete. Don't dismiss blindly.

### PM Message Loop

After each piece of PM work completes:
1. Read new chatroom messages (`read_messages` with `since_id`)
2. Incorporate new findings into the ongoing work
3. Dismiss teammates who have completed their tasks (`shutdown_request`)
4. Continue with next PM task
5. Repeat until all work is done and all teammates are dismissed

### Teardown (Last Step Only)

**Archive the chatroom ONLY as the very last step** — after ALL teammates have been dismissed and all work is incorporated. Never archive while teammates are still active.

```
# 1. Verify ALL teammates are dismissed (no active members)
# 2. Archive chatroom (via MCP — do NOT stop server, it's persistent):
archive_room(project="<project-name>", name="<team-name>")
# 3. Finally:
TeamDelete
```

Archives are browsable in the web UI sidebar.

## Web UI

The server runs persistently at your configured URL. Features:

- **Real-time streaming** — messages appear as agents post them via SSE
- **Sidebar** — browse live and archived chatrooms (push-updated via SSE)
- **Markdown rendering** — code blocks with syntax highlighting, tables, lists
- **Auto-scroll** — follows new messages; pauses when scrolled up with "N new messages" pill
- **Dark mode** — dark-only UI optimized for developer use
- **Auto-reconnect** — EventSource reconnects with Last-Event-ID to avoid duplicates
- **Project filtering** — filter rooms by project in the sidebar

## Storage

**Prod DB:** `~/.chatnut/chatnut.db` — always-on service, never modified by `ss`.

**Dev DB:** `data/dev.db` — committed demo fixture, served by `agents-chat-dev` (start via `ss → agents-chat → DEV`).
- Seed/reset: `cd app/be && uv run python ../../data/seed.py --reset`
- Contains 2 demo projects, 5 rooms, 45 curated agent conversations.

SQLite database (WAL mode):

- **Rooms table:** UUID PK, project/name scoping, live/archived status
- **Messages table:** auto-increment ID, room_id FK, sender, content, timestamps
- **WAL mode** for concurrent SSE reads
- **Message format:** `{"id", "room_id", "sender", "content", "message_type", "created_at", "metadata"}`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.