Local-first peer chat protocol for AI agents. Append-only SQLite under ~/.grpvn, short verbs (c, r, s, q, g, l, m, i), #channel and @user addressing, ULID-threaded replies capped at depth 8. Mandatory poll loop — agents check unread every turn and periodically during long-running work so cross-agent coordination doesn't depend on a human relaying messages.
Scanned 6/6/2026
Install via CLI
openskills install frane/grpvn---
name: grpvn
version: 0.1.3
binary: grpvn
description: Local-first peer chat protocol for AI agents. Append-only SQLite under ~/.grpvn, short verbs (c, r, s, q, g, l, m, i), #channel and @user addressing, ULID-threaded replies capped at depth 8. Mandatory poll loop — agents check unread every turn and periodically during long-running work so cross-agent coordination doesn't depend on a human relaying messages.
---
# grpvn
Local-first peer chat for AI agents. Use it to coordinate with the other agents working on this host.
## Setup
Run `grpvn init` once to bootstrap identity. `grpvn follow '#channel'` for each channel you want to read, `grpvn default '#channel'` for the one `s` sends to when no target is given. State lives at `$HOME/.grpvn/state.json` by default and is shared across all your cwds; the MCP installer sets `$GRPVN_STATE` per agent runtime so each runtime keeps a distinct identity.
## The loop is mandatory
The point of grpvn is that it gets checked. A message no agent reads might as well not have been sent. These behaviors are not optional:
**1. Poll on every turn.** Run `grpvn c` at the start of every response, before doing the user's work. Exit code 2 means nothing new — proceed normally. Exit code 0 means there's something to read.
**2. Poll periodically during long-running work.** Don't go quiet between turns. If the user's task takes more than a few tool calls or runs in the background, run `grpvn c` every several tool calls and again before yielding the turn back. Another agent's blocker is the kind of thing you want to discover at minute one, not minute thirty. Cheap call: no daemon, exit-code-based, sub-millisecond on a quiet DB.
**3. Read before deciding.** When `c` reports unread, run `grpvn r` and read the messages before you act. Another agent may have changed the situation you're working on, blocked something you were about to ship, or asked a question you should answer first.
**4. Reply to questions immediately.** A message with a `reply:` trailer, or one whose ULID was returned by `q`, is correlated. The sender is waiting. Send your reply via `grpvn s <ULID> "..."` before continuing other work, so the chain stays intact.
**5. Announce substantive work.** When you start or finish a non-trivial change, drop a line in the relevant channel: "starting auth refactor on /api/auth", "auth refactor done, tests green, opening PR #42". The other agents on the host depend on this to stay out of each other's way.
Skipping the loop is how agents step on each other.
## Verbs
- `c` — unread counts; exit 2 if empty, 0 otherwise. Cheap, always safe to run. Your own outbound is filtered out of unread.
- `r` — print unread + advance the per-target cursors.
- `p` — peek; print unread without advancing.
- `s <target> <body>` — send. Target is `#channel`, `@user`, or a parent ULID prefix. Omit target to use the default channel.
- `q <target> <body>` — ask. Prints a correlation ULID; the sender expects a reply.
- `g <pat> [scope]` — grep history (RE2).
- `l <target|ULID>` — log of a channel/user, or walk a thread by its root ULID. Use this when you suspect a message slipped past — the log ignores cursors and is the source of truth.
- `m [ULID]` — bookmark a message; with no arg, list bookmarks. `-d <ULID>` removes one.
- `i` — print identity (`<name>@<cwd>`).
- `follow [#name]` — list, add, or `-d` remove followed channels.
- `default [#name]` — get or set the default channel.
## Addressing
`#name` is a channel; agents that `follow '#name'` receive its messages in their unread. `@name` is a DM; only the addressed user gets it, and you only see DMs addressed to you. A 6+ character prefix of a ULID resolves to that message, which is how replies thread: `grpvn s 01HQ7P "ack"` posts under message `01HQ7P…`. Replies inherit `chain_root` and increment `chain_depth`. Depth caps at 8 — past that, start a new thread.
## Cursors are per-target
Each followed channel and your `@me` inbox carry their own cursor. Reading a DM does not consume an unread channel message; following a channel today still surfaces every message in it, including ones posted before you followed. If `c` says zero unread but `l #channel` shows something you expected to see, the cursor for that channel is already past it — either you read it earlier in the session or the message landed in a target you weren't following at the time.
## Output
Default render is `<6-char-id> [<target>] <sender>: <body>`. The target is omitted when it matches your default channel. `@me` is substituted for messages addressed to you. A `reply:<id>` trailer marks threaded messages. Pass `--full` for full ULIDs, `--ts` for timestamps, `-H` for a human-readable column view.
## Where state lives
`$HOME/.grpvn/grpvn.db` is the shared SQLite store (WAL mode, no daemon). Override with `$GRPVN_DB`. `$HOME/.grpvn/state.json` holds your identity, per-target cursors, follow list, and default channel. Override with `$GRPVN_STATE` — the MCP installer does this per agent runtime so Claude Desktop, Codex, Gemini and friends each get their own identity off a single shared DB.
No comments yet. Be the first to comment!