Skip to content
Back to skills

chatnut

ASecurity

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

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 29, 2026
developmentpythongobashsqlfastapicode-reviewgitapidatabasebackend

Works with

  • claude code
  • cursor
  • api
  • mcp

Security analysis

A100/100

Pro scans all 9 files and shows the line behind each finding

Scanned May 29, 2026

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

Installs into .claude/skills of the current project.

Are you the author of chatnut?

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

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

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 |

## 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.

## Discussion Protocol Is Consumer-Defined

chatnut provides the **wire** (post / read / wait / mentions / status / fallback). It does NOT prescribe a discussion protocol. Consuming skills (e.g. `/plan-draft`, `/code-review`, `/research`) decide their own:

- **How many rounds** (zero / one / several / free-discuss with no rounds)
- **Who facilitates** (orchestrator-in-room / orchestrator-silent / no orchestrator)
- **How discussion ends** (DONE handshake / orchestrator-declared / timeout-only / human-in-loop / **quiescence-poll** — used by `/plan-draft`)
- **Engagement style** (debate-heavy challenge-and-build vs independent parallel verdicts vs single-shot reviews)
- **Triage / synthesis pattern** (orchestrator reads chatroom / spawns a triage subagent / consumes per-teammate DM summaries)
- **Lifecycle ordering** (when to archive, when to teardown relative to artifact updates)

For each of these, read the SKILL.md of the consuming skill.

The only protocol-level rules shipped at the chatnut layer are the **defensive primitives** (read-before-write, @mention dual-post, status reporting, idle-is-informational, MCP fallback) — see `chatnut-protocol.md`. These are wire-truth, not policy.

### Examples of consumer choices

| Consumer | Protocol shape |
|---|---|
| `/plan-draft` | Free-discuss (no rounds), orchestrator-silent (PM posts and reads ZERO messages from init_room until TeamDelete), no completion handshake, external quiescence-poll termination (90s silence), opus triage subagent reads disk-dump |
| Future skills | Whatever fits their problem |

If you're authoring a new consumer, you don't have to ask chatnut for permission to invent your protocol — chatnut is wire, you bring policy.

## Teammate Spawn Prompts (consumer-side)

Consuming skills define their spawn prompts. The only chatnut-level
requirement is to include the `room_id` so teammates can use the wire:

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

The defensive primitives in `chatnut-protocol.md` are auto-loaded into
every spawned agent's context — consumers don't need to repeat them in
spawn prompts. Consumers DO need to include their own protocol rules
(rounds vs free-discuss, completion semantics, engagement style) since
those are not shipped at the chatnut layer.

Install / update the global rule: `chatnut install`

## 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"}`

Files in this skill

  • .coderabbit.yaml9.8 KB
  • CLAUDE.md17.4 KB
  • CODE_OF_CONDUCT.md2.2 KB
  • CONTRIBUTING.md1.5 KB
  • RELEASING.md2.1 KB
  • SKILL.md11.4 KB
  • data/seed.py57.5 KB
  • install.sh503 B
  • working-scope.md1.5 KB

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…