# agmsg
*[日本語](README.ja.md)*
[](https://github.com/fujibee/agmsg/actions/workflows/tests.yml)
[](https://github.com/fujibee/agmsg/releases/latest)
[](LICENSE)
Cross-agent messaging for CLI AI agents. No daemon required, no network, no complexity.
> **For AI agents:** see [`/llms.txt`](llms.txt) for a quick, machine-friendly orientation.
<a href="https://www.producthunt.com/products/agmsg?utm_source=badge-top-post-badge&utm_medium=badge" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=1165435&theme=dark&period=daily">
<img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=1165435&theme=light&period=daily"
alt="agmsg — #5 Product of the Day on Product Hunt" width="250" height="54">
</picture>
</a>
You stop being the copy-paste courier between your agents. Claude Code, Codex, Gemini CLI, GitHub Copilot CLI, and any other CLI agent message each other directly through a shared local SQLite database — no human in the middle.
<p align="center">
<img src="docs/logos/supported-agents.png" width="780"
alt="Supported agents: Claude Code, Codex, Gemini, GitHub Copilot, Antigravity, OpenCode, Hermes">
</p>
**What it isn't:**
- Not MCP. No MCP server, no extra runtime — just `bash` + `sqlite3`.
- Not subagents. agmsg connects *peer* sessions across different tools. `spawn` can launch a new peer agent in its own terminal, but it's an independent session you talk to over agmsg — not a child process this one manages.
- Not a message queue. There's no broker. The SQLite file is the floor; agents are the players.
## Demo
Two Claude Code instances in `monitor` (legacy) mode, left alone in the same team, play tic-tac-toe against each other with no human in the loop — each picks up the other's move in real time:

In real use it looks like this — Claude Code asking Codex for a code review and getting it back, all over agmsg:

## Quick Start
**Requires:** `bash` and `sqlite3`. macOS ships both. On a minimal Linux box (some Debian/Ubuntu containers, Alpine) you may need to install `sqlite3` first — `sudo apt-get install -y sqlite3` or your distro's equivalent.
```bash
# 1. Install — npx is the fastest path, no clone needed
npx agmsg
# 2. Restart Claude Code / Codex / Gemini CLI / Antigravity / OpenCode to pick up the new skill
# 3. Run the command — it will prompt for team and agent name on first use
# Claude Code: /agmsg
# Codex: $agmsg
# Gemini CLI: $agmsg
# Antigravity: $agmsg
# OpenCode: $agmsg
```
That's it. The slash command prompts you for a team name and an agent name on first use, then asks you to pick a [delivery mode](#delivery-modes) (default on Claude Code and Codex: `monitor` — real-time push; Codex delivers it through a bridge). After that, you talk to your agent naturally — see [First run](#first-run) below.
Prefer to inspect the code first, track the latest `main`, or pick a custom command name? See [Install](#install) below for the `setup.sh` one-liner, `git clone`, and the Claude Code plugin marketplace paths.
To sync a team between two installs through the self-hosted reference server, follow [Remote setup](docs/remote-setup.md).
## How it works
agmsg is a thin transport. Each agent has a hook (or a Monitor stream, depending on delivery mode) that reads from a shared SQLite file and surfaces incoming messages as text the agent can react to. Sending is a `send.sh` call that appends a row. agmsg itself runs with no daemon, no socket and no broker — the file is the shared floor and the agents take turns on it. The optional [agmsgd](#agmsgd-beta-160) is a resident service that is installed only if you choose it.
The store is WAL-mode SQLite, so multiple readers and a single writer coexist without conflicts. History is durable: messages stay in the DB after the session ends, and `history.sh` can replay an old room into a fresh agent.
## Install
agmsg ends up at `~/.agents/skills/agmsg/` no matter which install path you take. Pick whichever fits your setup.
**Which path gets the latest?** The `git clone` and `setup.sh` (curl) paths install straight from `main`, so they're always current. The **npm package and the Claude Code plugin are cut from tagged releases on a cadence**, so they can lag `main` by a few fixes — fine for almost everyone, but if you specifically want a just-merged change, clone the repo. You can always check exactly what you're running with `/agmsg version` (or `scripts/version.sh`): a tagged release reads like `v1.0.3`, while a checkout ahead of the last release reads like `v1.0.3-6-g1a2b3c4` (6 commits past `v1.0.3`).
### npm / npx
```bash
npx agmsg # one-shot, no global install
# or
npm i -g agmsg && agmsg install
```
The npm package is a thin bootstrapper that downloads and runs the canonical `setup.sh`. Published from this repo via [npm Trusted Publisher (OIDC)](https://docs.npmjs.com/trusted-publishers) with [SLSA provenance](https://slsa.dev/) — the attestation is visible at <https://www.npmjs.com/package/agmsg>.
### Claude Code plugin marketplace
Inside Claude Code:
```
/plugin marketplace add fujibee/agmsg
/plugin install agmsg@fujibee-agmsg
/reload-plugins
/agmsg
```
The plugin install path drops the skill into `~/.claude/plugins/cache/`; the first invocation of `/agmsg` runs a bootstrap that populates `~/.agents/skills/agmsg/` (database, scripts, team registry) so the runtime is identical to a script install. If your environment lacks `sqlite3` (some minimal Linux containers don't ship it by default), the bootstrap will surface a clear error message — install `sqlite3` and re-invoke `/agmsg`.
### Direct script
Clone the repo first, then run the installer — this is also the path that always tracks the latest `main`:
```bash
git clone https://github.com/fujibee/agmsg.git
cd agmsg
./install.sh # Interactive (asks command name, default: agmsg)
./install.sh --cmd m # Non-interactive with custom command name
./install.sh --agent-type gemini # Install a Gemini-oriented SKILL.md
./install.sh --agent-type opencode # OpenCode-only: sets shared skill to OpenCode template
```
The **command name** determines:
- Skill folder: `~/.agents/skills/<cmd>/`
- Claude Code / Copilot CLI: `/<cmd>`
- Codex / Gemini CLI / Antigravity: `$<cmd>`
`--cmd` and `--agent-type` are only available via the direct-script path; the `npm` and plugin paths always install as `agmsg` and auto-detect the host agent type.
After install, **restart your agent** (Claude Code / Codex / Gemini CLI / Copilot CLI / Antigravity / OpenCode) so it picks up the new skill.
### Windows: Git Bash
agmsg's implementation is the Bash script set under `scripts/`, so on Windows the
scripts run through **Git Bash** (Git for Windows, with `sqlite3` available on the
Git Bash PATH). There is no PowerShell reimplementation.
- In Windows environments, Claude Code monitor delivery is verified through
Git Bash. If monitor setup fails from PowerShell or a native Windows shell,
retry the same agmsg command from Git Bash so `watch.sh` runs in the expected
Bash environment.
- Claude Code normally works with Bash/Git Bash for
these script calls, but native Windows Codex commands and hooks often start
from PowerShell. Keep the actual agmsg execution path pinned to Git Bash so
all agents share the same `$HOME` and SQLite database.
- **Codex delivery hooks** are wrapped automatically. On native Windows Codex runs
hook commands via PowerShell, which cannot execute a bare `.sh` path, so agmsg
emits a `commandWindows` entry that invokes Git Bash (`& $bash -lc '...'`). No
setup needed — see `windows_wrap()` in `scripts/delivery.sh`.
- **Interactive / agent-typed commands** call the scripts through Git Bash, e.g.
`bash -lc 'scripts/whoami.sh "$(pwd)" codex'`.
- Heads-up: a bare `bash` in PowerShell usually resolves to the **WSL** shim
(`WindowsApps\bash.exe`), which has a separate `$HOME` and database — agents
would then talk to a different DB than Claude Code. Pin Git Bash in your
PowerShell profile so everything shares one database:
```powershell
Set-Alias bash 'C:\Program Files\Git\bin\bash.exe'
```
### agmsgd beta (1.6.0)
The optional agmsgd beta sends Codex sessions a notice to check their inbox through the Codex queue, and sends Claude Code sessions a notice directly through that session's own cross-session messaging socket. It detects whether that Claude Code session is running in bypass-permissions mode and labels notices for that mode automatically, with no action from you. A Claude Code seat delivered this way never needs its Monitor watch re-armed. On Windows, Claude Code must be 2.1.302 or later to receive notices this way (2.1.198 has no endpoint for them). A Claude Code seat whose delivery destination cannot be confirmed, such as one on an older Claude Code, keeps using Monitor as before, even with agmsgd enabled and running. It is offered as the recommended choice in the delivery-method screen and is turned on only if you pick it: install never asks, and `--update` never changes it. It reads the existing SQLite message store; it does not migrate data, deliver message bodies, take over remote sync, or change how any agent other than Claude Code and Codex receives messages. macOS, Linux, and Windows are supported for Codex. Windows requires native Codex 0.157.0 or later; earlier versions are blocked with a reason. Native queue execution and delivery to a logged-in live Windows session were measured with Codex 0.157.0 and 0.160.0. Unreadable version or delivery evidence keeps the existing conservative handling. JSONL storage is unsupported by this beta; keep using the bridge for those sessions.
Requires Node >= 22.13.0 with `node:sqlite` (the experimental SQLite warning is expected). Install Node from [nodejs.org](https://nodejs.org/en/download). Enable and inspect the beta with:
```bash
agmsg daemon enable
agmsg daemon status
```
If `agmsg` is not found, use `<install>/scripts/agmsg daemon enable` (and the same path for `status`, `start`, `stop`, or `disable`); `<install>` is the installation directory printed by the installer. Follow its PATH instructions to make `agmsg` available in your shell.
While enabled, new Codex launches through the shim use plain Codex: no bridge app-server or dispatcher is started. Sessions that already have a bridge keep using it until restarted; agmsgd skips those sessions. `enable` and `start` list the remaining bridge sessions and missing destination records. Restart Codex and run `$agmsg actas <name>` to record a missing destination. Existing remote sync processes remain separate, so this beta does not yet reduce all background work to one process. A Claude Code seat only skips Monitor once agmsgd confirms it can actually be delivered to; every other seat keeps using Monitor as before. `daemon status` reports each such seat's state under `claude_code_seats`.
If agmsgd stops while enabled, the next ordinary agmsg script operation warns at most once every ten minutes per install. Codex startup also warns, and `daemon status` and `scripts/doctor.sh` report the failure with exit code 1. Run `agmsg daemon start` to recover. For a missing or outdated Node executable, install Node and run `agmsg daemon enable` again. Unread messages are preserved.
To return to bridge notices, run `agmsg daemon disable`, then restart the Codex sessions listed as having no bridge (for example, `codex resume`, followed by `$agmsg actas <name>`). They receive no new notices until restarted. Existing bridge sessions need no restart, and subsequent Codex launches get a bridge. Notices already accepted by the Codex queue are not cancelled and may appear once more. Claude Code seats go back to Monitor at their next SessionStart; an open Claude Code session gets no notices until then. To restore notices in an existing session, resume it so SessionStart arms Monitor again. `stop` stops the daemon without removing its resident service; `disable` also unregisters that service.
## First run
Open your project in your agent (Claude Code, Codex, Gemini CLI, etc.) and run:
```
/agmsg # Claude Code, Copilot CLI
$agmsg # Codex, Gemini CLI, Antigravity
```
On first use it asks for a **team name** (joins an existing team or creates a new one) and an **agent name** for this project — that's the whole onboarding. After that, talk to your agent naturally:
- *"send alice a message saying the deploy is done"*
- *"check my messages"*
- *"who's on the team"*
The agent picks the right subcommand and runs it for you. You don't need to memorize anything below — the script reference further down is for automation, scripts, and CI.
For renaming a team, leaving, joining the same team from a second project, or clearing a project's registrations, see [docs/teams.md](docs/teams.md).
### Multiple roles per project (`actas` / `drop`)
Same project, same agent type, different role — for example a `tech-lead` identity for architecture reviews and a `biz-analyst` identity for requirements work, both living on top of the same workspace. Toolset and assets are shared; only the role differs.
```
/agmsg actas tech-lead # switch to tech-lead (creates it if not yet registered)
/agmsg actas biz-analyst # switch to biz-analyst
/agmsg drop biz-analyst # remove the role from this project
```
`actas <name>` is **exclusive across sessions**: it switches both sending and receiving to `<name>`, claims a lock that stops peer sessions from subscribing to the same name, and refuses if another session already holds it. `drop` releases the lock. If a lock gets stuck, drop the role from the holding session or end that session.
See [docs/actas.md](docs/actas.md) for the full mechanics — exclusivity model, recovery, liveness / PID recycling, Codex caveat.
### Spawn a new agent (`spawn`)
Where `actas` switches *this* session to a different role, `spawn` brings up a **separate agent process** that takes a role on boot — handy for fanning out collaborators.
```
/agmsg spawn codex reviewer # new codex agent, joins and becomes "reviewer"
/agmsg spawn claude-code alice --window # new claude-code agent in a fresh tmux window
/agmsg spawn codex reviewer --boot-prompt "review the diff on this branch" # joins AND starts the task
```
`spawn <type> <name>` pre-joins `<name>`, then launches the target CLI with the actas slash command (`/<your-command> actas <name>`, matching your install command name) as its initial prompt. If the current session is inside **tmux**, it opens in a new pane (or `--window` for a new window, `--split h|v` for the direction); otherwise it opens a new **OS terminal** window.
Pass `--boot-prompt <text>` to hand the new agent an initial task: the boot prompt becomes the actas slash command followed (newline-separated) by your text, so the agent claims its identity **and** acts on the task in the same first turn. This is the reliable way to give a one-shot goal to a **codex** peer: a Codex session that is not on monitor (legacy) or agmsgd delivery only looks at its inbox on its next turn, so it never notices a message you `send` after it goes idle.
By default `spawn` **blocks until the new agent is actually listening** — its watcher attaches and touches a readiness sentinel — then prints `status=ready`, so you can send work the moment `spawn` returns without losing it to the agent's cold start. Use `--no-wait` for fire-and-forget, or `--ready-timeout <secs>` to bound the wait (default 90; on timeout it prints `status=timeout` and exits 3 so a caller can re-spawn). Codex skips the wait: it has no spawn-time readiness handshake (this is separate from how it receives messages).
Options: `--boot-prompt <text>` (initial task; see above), `--project <path>` (default: current project), `--team <team>` (auto-resolved when the project has a single team), and `--terminal <tmpl>` / `$AGMSG_TERMINAL` / config `spawn.terminal` to override the terminal command on the non-tmux path (a `{cmd}` placeholder is replaced with the path to the generated boot script). On macOS the default opens whichever terminal you're currently in (iTerm or Terminal, via `$TERM_PROGRAM`) using `open -a` — a plain app launch, so it does **not** trigger the Automation/AppleScript permission prompts that scripting the terminal directly would.
To always pass a given agent type extra CLI flags on spawn (e.g. a default permission mode or sandbox policy), set them in a YAML **spawn options** file — one section per type, a flat `--flag: value` map underneath. Path: `$AGMSG_SPAWN_OPTIONS_FILE`, else `~/.agmsg/config/spawn_options.yaml`; a missing file or section is a no-op.
```yaml
claude-code:
--permission-mode: acceptEdits
--dangerously-skip-permissions: true # a `true` value emits the flag with no argument
codex:
--sandbox: workspace-write
--dangerously-skip-permissions: false # a `false` value suppresses the flag entirely
```
Eight of the ten agent types are spawnable — `claude-code`, `codex`, `grok-build`, `cursor`, `gemini`, `antigravity`, `copilot`, `opencode`. `hermes` is not spawnable because its CLI has no known mode that starts an interactive session pre-seeded with an initial prompt (#279). `devin` is currently not marked spawnable because an equivalent interactive boot mode has not yet been verified. macOS is the primary target; Linux and Windows are best-effort (please open an issue/PR if your terminal isn't handled). Headless environments — no tmux **and** no usable terminal — error out, since the agent CLIs need an interactive terminal.
### Tear down a spawned agent (`despawn`)
`despawn` is the inverse of `spawn` — it cleanly tears down a member you brought up.
```
/agmsg despawn reviewer # graceful: the member drops its role and closes its own pane
/agmsg despawn alice --force # force: tear it down from here when its watcher can't respond
```
By default `despawn <name>` is **graceful**: it sends a `ctrl:despawn` control message to `<name>`, whose watcher drops its own role (releasing the actas lock and registration) and closes its own tmux pane — ending the agent. It blocks until the role is released, up to `--timeout <secs>` (default 30), then prints `status=ok`. If the member's watcher never responds it prints `status=timeout` and exits 3 — retry with `--force`.
`--force` skips the message and tears the member down from the placement recorded at spawn time: it kills the member's tmux pane/window and drops its registration. Use it when the member's watcher can't respond — a dead watcher, or a **codex** member (it never holds an actas lock and runs no Claude Code watcher, so graceful has nothing to confirm). A member started by hand (no spawn placement record) can't be `--force`d; despawn says so and leaves it for you to close.
Despawn only acts on the named member — the session running `despawn` is never torn down, and a broad-subscription watcher ignores a `ctrl:despawn` aimed at another role.
### Bring a role back with its context (session resume)
A role remembers the session that last embodied it: sessions are named
`<team>-<agent>`, and `spawn` **resumes a role's previous session by default** —
so re-spawning after a `despawn`, crash, or restart comes back in the prior
conversation, not blank (`--fresh` forces new). With
[tmux-resurrect](https://github.com/tmux-plugins/tmux-resurrect), one `~/.tmux.conf`
line re-seats every role pane into its session after a tmux-server restart.
See **[docs/session-resurrect.md](docs/session-resurrect.md)** for the tmux-resurrect
setup, how it resolves each pane, what does and doesn't come back automatically, and
the manual fallback.
## Delivery modes
How incoming messages reach your agent. Pick one at first join via the prompt, or change it later with `/agmsg delivery <name>` (`/agmsg mode` still works as an alias; from a shell: `agmsg delivery <name>`). On Claude Code and Codex the prompt offers agmsgd first, as the recommended choice, and lists the monitor as **`monitor (legacy)`** (the mode value is still `monitor`). Installing or updating never turns agmsgd on by itself, and an update changes nothing about the mode you already use.
| mode | mechanism | latency | who it's for |
|---|---|---|---|
| **agmsgd** (recommended on Claude Code and Codex; choose it at the prompt or with `/agmsg delivery agmsgd`) | a background service delivers a notice straight into the session, no Monitor task needed | a few seconds | anyone on Claude Code or Codex who can run the service: it registers one that starts at login, and `agmsg daemon disable` stops and removes it |
| **`monitor (legacy)`** (value `monitor`; default on Claude Code; on OpenCode with the opencode-sentinel plugin) | SessionStart hook → Monitor tool → blocking SQLite stream | ~5s | Claude Code users wanting real-time push |
| **`turn`** (default on Codex / Copilot CLI / OpenCode without the plugin) | Stop hook fires `check-inbox.sh` between assistant turns | until your next interaction | Codex / Copilot CLI / OpenCode users not running monitor; Claude Code users on a quieter loop |
| **`both`** | monitor primary, turn as per-session safety net | ~5s; falls back to turn-end on watcher failure | belt-and-suspenders |
| **`off`** | no automatic delivery | manual `/agmsg` only | minimalists |
agmsgd is not offered where `agmsg daemon enable` would refuse (Node older than 22.13, a JSONL message storage, no systemd user services on Linux, Codex older than 0.157 on Windows); the prompt says why in one line and lists the other modes. When agmsgd is already on, the prompt does not ask which to use: it says so and asks only whether to keep it or switch to `monitor (legacy)`. Choosing agmsgd runs `agmsg daemon enable` and leaves the project on `monitor` as the fallback for a session whose destination is not confirmed yet. Choosing `monitor (legacy)` (or `turn`, `both`, `off`) is a real opt-out for that project: agmsgd stops delivering to its seats (`agmsg daemon status` shows them as chosen), and agmsgd itself stays on for the install's other projects (`agmsg daemon disable` turns it off for all of them). A project that never chose is unchanged.
### Picking a mode
```
/agmsg delivery agmsgd — direct delivery by the background service (Claude Code, Codex)
/agmsg delivery monitor — switch this project to real-time push (Claude Code)
/agmsg delivery turn — switch to between-turns checking
/agmsg delivery both — monitor with turn as a safety net
/agmsg delivery off — manual /agmsg only
/agmsg delivery — show current mode
```
Settings are per-project. Each `<project>/.claude/settings.local.json` gets exactly the hooks the chosen mode needs — repeated `set` calls are idempotent.
**Monitor priming**: in `monitor` mode, the receiving agent doesn't react to its first inbound message until it has taken at least one turn this session. If you've just started a fresh session and a teammate has already sent something, nudge the agent with any short message ("hi") to prime it — subsequent messages stream in real time.
### Claude Code monitor verification
`delivery.sh status claude-code <project>` showing `mode: monitor` means the project is configured for monitor delivery. It does **not** prove that Claude Code has started the runtime Monitor task in the current session.
For real-time delivery, verify the Claude Code runtime state:
1. `ToolSearch select:Monitor` finds Claude Code's generic `Monitor` tool.
2. The session starts `Monitor(agmsg inbox stream)` with the `watch.sh ... claude-code` command from the `AGMSG-DIRECTIVE` (or, after `actas <name>`, `Monitor(agmsg inbox stream (acting as <name>))`).
3. The transcript contains a `Monitor event: "agmsg inbox stream"` (or `"agmsg inbox stream (acting as <name>)"`) notification when messages arrive.
These are failure states, even if `delivery.sh status` says `mode: monitor`:
- `watch.sh` is running only as a Bash/background/nohup shell process, not through the Monitor tool.
- Tool search finds Azure Monitor, an MCP monitor, or any other monitor-branded tool instead of Claude Code's generic `Monitor` tool.
- `ToolSearch select:Monitor` cannot find a generic `Monitor` tool.
TaskList may list this task, but not every environment does (the desktop app's Code tab runs the Monitor and delivers its events without listing it), so a task missing from TaskList is not a failure: judge by the Monitor call starting and its events arriving. The background-task footer is not a reliable check either. If the Monitor tool is unavailable, use `turn` delivery or manual `/agmsg` inbox checks as a fallback. Those modes still deliver queued messages, but they are not real-time monitor delivery.
### Migrating from legacy `hook on/off`
`hook on` is now a thin alias for `mode turn` (with a one-line deprecation hint). To switch to real-time push:
```
/agmsg delivery monitor
```
The command updates `db/config.yaml`, rewrites the project's hook entries, and prints an `AGMSG-DIRECTIVE` that activates `monitor` in the current session — no agent restart needed.
## Usage
### Claude Code
```
/agmsg — check inbox (all teams)
/agmsg history — message history
/agmsg team — list team members
/agmsg send <agent> <message> — send message
/agmsg delivery <agmsgd|monitor|turn|both|off> — switch delivery (alias: /agmsg mode)
/agmsg mode — show current mode
/agmsg actas <name> — switch to another role in this project (create if needed)
/agmsg drop <name> — remove a role from this project
/agmsg spawn <type> <name> — launch a new agent that takes <name> (supported types: see "Spawn a new agent")
/agmsg despawn <name> [--force] — tear down a member you spawned (graceful, or --force)
/agmsg hook on | off — legacy aliases (mode turn | off)
/agmsg version — show the installed version (git-describe provenance)
/agmsg reset — clear current project registration
```
### Codex
```
$agmsg — or /skills → agmsg
```
Codex supports `mode monitor` through an app-server bridge, plus `mode turn` and `mode off`.
> ⚠️ **Monitor mode changes how Codex starts — enable it knowing that.** Codex has no Claude Code Monitor tool, so monitor (legacy) mode (`mode monitor`) prints a shell function that makes `codex` route through agmsg's monitor shim in your interactive shell. In monitor-mode projects the shim routes interactive launches through a bridge that turns incoming agmsg messages into turns on the current Codex thread; `codex exec` and non-monitor projects pass straight through to the real Codex. It depends on Codex app-server behavior and has a known limitation (orphans on TUI close — #149).
If you prefer a global PATH shim, run `~/.agents/skills/<cmd>/scripts/drivers/types/codex/codex-shim-install.sh install` and put `~/.agents/bin` before the real Codex binary on PATH. You can also launch with `~/.agents/skills/<cmd>/scripts/drivers/types/codex/codex-monitor.sh`. Codex sandboxing must allow writes to the skill's `db/`, `teams/`, and `run/` dirs — `install.sh` configures those `writable_roots` when `~/.codex/config.toml` exists. Setup notes and internals: [docs/codex-monitor-beta.md](docs/codex-monitor-beta.md).
### GitHub Copilot CLI
```
/agmsg — invokes the agmsg skill
```
The Copilot installer drops a `SKILL.md` at `~/.copilot/skills/agmsg/` so `/agmsg` is auto-discovered. Per-project hooks live at `<project>/.github/hooks/agmsg.json`. Copilot CLI has no Monitor-tool equivalent, so only `mode turn` and `mode off` are supported. Asking for `monitor` or `both` is rejected with an error.
### OpenCode
```
$agmsg
```
Install with `./install.sh` (when `~/.config/opencode/` exists, the OpenCode-typed skill is placed automatically alongside the default Codex-typed shared skill). Use `--agent-type opencode` only for OpenCode-only environments where Codex is not installed. OpenCode supports `mode monitor` (via the external [`opencode-sentinel`](https://github.com/tsukimiya/opencode-sentinel) plugin; without it the rule instructs a fallback to turn mode, which the agent follows rather than agmsg enforcing it), `mode turn`, and `mode off`. `spawn opencode` is available via `opencode --prompt` (TUI mode, which stays resident after the boot prompt's turn). `both` is not supported.
This makes OpenCode useful as a local coding agent, including configurations backed by local providers such as Ollama.
See [docs/opencode.md](docs/opencode.md) for full setup instructions.
### Shell (any agent)
```bash
~/.agents/skills/<cmd>/scripts/send.sh <team> <from> <to> "<message>" [--force]
~/.agents/skills/<cmd>/scripts/inbox.sh <team> <agent_id>
~/.agents/skills/<cmd>/scripts/history.sh <team> [agent_id] [limit]
~/.agents/skills/<cmd>/scripts/team.sh <team> [--json] [--delete] [--force] [--purge-messages] [--yes]
~/.agents/skills/<cmd>/scripts/placement-collisions.sh
~/.agents/skills/<cmd>/scripts/whoami.sh <project_path> <type>
~/.agents/skills/<cmd>/scripts/delivery.sh set <mode> <type> <project_path>
~/.agents/skills/<cmd>/scripts/delivery.sh status [<type> <project_path>]
~/.agents/skills/<cmd>/scripts/reset.sh <project_path> <type> [agent_id]
```
`team.sh` combines the roster with terminal placement, activity, delivery mode, and identity consistency. Verified identity is collapsed to `identity=ok`; mismatches and values that could not be observed are expanded with their evidence. `--json` emits every field for every registration. `team.sh` is read-only with respect to other seats: it reports an inconsistent or unresponsive seat but never repairs it from outside (the former `--fix`, `--fix-pane-names` and `--rename-sessions` flags were removed in #1152). A seat repairs its own identity cells by running `fix` from itself; one that cannot is despawned and restarted, or taken over by a person. Pane liveness will join this view when the pane-state contract lands; until then the unavailable column is omitted rather than filled with `unknown`.
`--delete` removes a team once every member has left (`leave.sh` each remaining one first) and the team is not actively synced (`binding_state=active` in `team-list.sh --json` refuses; `disconnected` proceeds with an extra warning) — its config, roster, identity history, and per-agent runtime state (actas locks, role-session records, spawn/placement records), but not its message history. `--force` (combined with `--delete`) removes every remaining member first, the same effect as running `leave.sh` for each, then deletes the team as `--delete` alone does — a team the desktop app created always has its app-user as a member, so it could never be deleted from the app without this; a `--force` confirmation names the members it will remove. `--purge-messages` deletes only a team's rows from the sqlite message store (jsonl is not supported yet) and can be used standalone or together with `--delete`; other teams' data is untouched. Both prompt for confirmation, in the same style as `uninstall.sh`'s, unless `--yes` is given.
`placement-collisions.sh` is a separate, read-only installation-wide report. It never repairs or removes a record; keeping this fleet observation outside `team.sh` prevents an operator-level scan from becoming part of a seat's repair path. Today it reports only the record-only layer: two DIFFERENT seats' records resolved to the same canonical (kind, instance, pane) locator, entirely from records on disk — no terminal is ever asked anything. A ref with no instance component to resolve (every herdr ref today; a legacy bare tmux `%N`/`@N`) is not joined by raw string equality; it is listed under `unscoped_records` instead, since record-only evidence cannot tell such refs apart across terminal instances. Orca refs are not in that bucket: `terminal_id_split` always resolves the constant `local` instance for a validated handle (Orca has exactly one local runtime today), so an orca ref reaches the real instance+pane comparison instead of `unscoped_records`. `collisions: none` means the walk completed and found nothing; `collisions: none_observed` paired with `coverage: partial` means something along the way (a team config, a placement record, an empty ref) could not be read, so the empty answer is not a proven one; `collisions: not_attempted` means there was no `teams/` directory to walk at all. An actual-location layer — matching a seat's own record against where a live census actually observes it — is designed but not yet wired here; see the script's header.
Terminal identity has a different number of observable names on each backend. Herdr exposes three independent values: the visible pane label, its internal agent key, and the CLI session name. tmux exposes two: the `@agmsg_agent` pane option is the internal key, while the CLI owns `pane_title`, so there is no independent pane-label field after the CLI starts. `team.sh` reports that tmux field as `n/a` rather than treating an unavailable concept as a mismatch. Orca exposes exactly one: `terminal_name` writes the tab title, and `team.sh` reads it back as `pane_label` (the durable value lives in the tab itself, not in any one pane within it — a tab can hold more than one pane, split). There is no separate `@agmsg_agent`-style key, so `agent_key` reports `n/a` unconditionally, the same way tmux's `pane_label` does for the field it lacks. `cli_session` is not: it follows the AGENT type's own `session_name_source`, same as on every driver — a `title`-sourced type (Claude Code) gets `n/a` too, since orca's own observation has no independent title field either; a `screen`-sourced type (Codex, whose session name comes from a `Thread name:` line) is read straight off the pane's screen, independent of orca's own observe extension, and reports `unknown:name_not_visible` when that line isn't found there.
`send.sh` takes four positional arguments — `<team> <from> <to> "<message>"` — plus an optional trailing `--force`. Quote the message so the shell sees it as one argument; an unquoted message with spaces will be misparsed. Both `from` and `to` must already be registered in `<team>`; an unregistered name errors out (listing the currently registered names) instead of silently storing an undeliverable message. Pass `--force` to bypass this check for an intentional pre-registration send.
## FAQ / Design notes
**Is this MCP? Do I need an MCP server?**
No. agmsg is standalone — `bash` + `sqlite3`, no server, no network, and no daemon unless you choose the optional agmsgd. The two stacks are orthogonal: you can run agmsg alongside any MCP setup you already have.
**Concurrent writes to the same channel — do they conflict?**
The store is SQLite in WAL mode. Multiple readers and a single writer coexist; writes are short and serialized at the file level. In practice, two agents sending into the same team don't collide.
**Does SQLite guarantee turn order? Is there a lock or token?**
SQLite guarantees the ordering of the log itself — every row has a monotonic id and timestamp. Turn-taking between agents is a protocol-level concern, not enforced by the transport. The floor is intentionally dumb; the protocol lives in your prompts.
**Two Claude Code instances grab the same task — claim/lock?**
Not in v1. If two agents are subscribed to the same name, both see the same inbound message, and you'd need a protocol-level claim/lease to decide who acts. A claim table is on the roadmap; the `actas` exclusivity lock already prevents two *sessions* from holding the same role at once, which covers the most common form of this.
**Runaway loops — where does the stop condition live?**
At the protocol/prompt level, not the transport. Common pattern: include a max-turns or explicit done-signal instruction in the kickoff prompt ("stop after N exchanges", "reply DONE when complete"). agmsg won't cut a conversation off for you.
**What's carried on a handoff — context, diffs, or just text?**
Plain text. Messages are short — a sentence, a request, a path. Agents pass *summaries and references* (file paths, commit SHAs, issue numbers), not raw context. Transport is the message; semantic packing is up to the prompt.
**What if the output exceeds the receiver's context window?**
Use the summary + file-reference pattern: write the artifact to disk, send a one-line pointer. The DB stores messages, not files.
**Does it hold up with more than 2 agents?**
Yes. Teams are N-agent. The demo is 2 for clarity; larger rooms work the same way — we run our own 8-agent team on it.
**Does context persist across sessions?**
Yes. Messages live in SQLite and survive sessions. `history.sh <team>` replays the room.
**Can I re-seed a fresh agent from an old room?**
The message store is effectively a replay log. There's no one-shot "rehydrate from room X" command yet, but `history.sh` gives you the transcript and you can prompt a new agent with it. Treat persistence as the unlock that makes that possible.
**How do you pronounce it?**
"AG message" (ay-jee message), or spelled out as A-G-M-S-G. Either is fine.
## Update
```bash
npx agmsg@latest install --update # add --cmd <name> if you have more than one install
```
From a clone:
```bash
cd agmsg
git pull
./install.sh --update
```
DB and team configs are preserved. Only scripts and assets are updated.
### Upgrading from 1.5.x
`--update` does not enable agmsgd. If you have never run `agmsg daemon enable`, nothing changes: agmsgd stays off, and messaging keeps working exactly as it did on 1.5.x. If agmsgd is currently on, an update restarts it on the new version automatically; if `agmsg daemon status` still shows it stopped afterward, run `agmsg daemon start`. If you had turned it off yourself (`agmsg daemon disable`), it stays off through the update, as intended.
No running Codex session needs to be restarted just because you ran `--update`. Restarting a Codex session is only needed to pick up agmsgd itself — either turning it on for the first time (see "agmsgd beta" above) or dropping back to the bridge after `agmsg daemon disable`.
## Uninstall
An `uninstall.sh` copy ships inside every install, so this works whether you
installed via `git clone`, `npx agmsg`, or the curl one-liner:
```bash
~/.agents/skills/agmsg/uninstall.sh # This install only (confirms each step)
~/.agents/skills/agmsg/uninstall.sh --yes # This install only, no confirmation
~/.agents/skills/agmsg/uninstall.sh --keep-data # Remove skill but keep DB and teams
~/.agents/skills/agmsg/uninstall.sh --all # Every agmsg install on this machine
```
With no `--all`, it removes exactly one install — the one it's running from.
Cleans up: skill files, slash commands, hooks, AGENTS.md sections, and team configs.
If you have a `git clone` checkout handy, `./uninstall.sh` from the repo root
works the same way when there's exactly one install on the machine. With more
than one, it refuses rather than guess which one you mean — run the
`uninstall.sh` inside the one you want, or pass `--all` to remove every
install on the machine (with one combined confirmation unless `--yes` is
also given, plus a separate one for whether to remove each install's DB and
teams too).
## Configuration
### Environment variables
| Variable | Default | Purpose |
|---|---|---|
| `AGMSG_STORAGE_PATH` | `<skill>/db` | Directory holding the SQLite message store (`messages.db`). Override to relocate the store — handy for tests, sandboxes, or running isolated instances. |
| `AGMSG_PLUGIN_DIRS` | (unset) | `:`-separated extra directories to search for external drivers, in addition to `<skill>/plugins`. Each holds `<axis>/<name>/` subdirs. Drivers found here are still ignored until opted into with `agmsg plugin trust`. See [docs/plugins.md](docs/plugins.md). |
The message store path resolves as **`AGMSG_STORAGE_PATH` (env) > built-in default**. (A config-file layer is planned to slot in between the two as part of the storage-driver work; the intended order is env > config > default.) The override is scoped to the SQLite store only — team configs under `teams/` are unaffected.
```bash
# Run against an isolated store
AGMSG_STORAGE_PATH=/tmp/agmsg-sandbox ./scripts/send.sh myteam alice bob "hi"
```
### Sandbox compatibility (Claude Code)
Claude Code's sandbox restricts filesystem writes to the project directory. In `monitor` mode, `watch.sh` runs inside the sandbox and needs to write pidfiles and SQLite WAL files under `~/.agents/skills/agmsg/`. If you have sandboxing enabled, add an allowlist entry to your settings:
**`~/.claude/settings.json`** (user-level — applies to all projects):
```json
{
"sandbox": {
"filesystem": {
"allowWrite": [
"~/.agents/skills/agmsg/"
]
}
}
}
```
The allowlist does not enable sandboxing by itself. Use `/sandbox` in Claude Code to choose a sandbox mode, or add `"enabled": true` alongside `"filesystem"` under `"sandbox"` to configure it in settings. The allowlist has no effect until sandboxing is enabled.
This can also go in project-level `.claude/settings.local.json` if you prefer per-project scope. The allowlist merges across all settings scopes and takes effect immediately — no restart needed.
If you installed agmsg under a custom command name (e.g. `m`), adjust the path accordingly (`~/.agents/skills/m/`).
### Sandbox compatibility (Codex)
Codex may run shell commands in a workspace-write sandbox. agmsg stores its
SQLite database and team metadata under `~/.agents/skills/<cmd>/` by default,
which is outside most project workspaces. If the sandbox cannot write there,
commands that append or update state can fail with errors such as
`sqlite3.OperationalError: unable to open database file`.
This affects operations such as:
- sending messages (`send.sh` writes to `db/messages.db`)
- marking inbox rows as read (`inbox.sh` updates `read_at`)
- joining, resetting, switching roles, or changing delivery mode (`teams/` and
config/state files may be updated)
If you use Codex with filesystem sandboxing enabled, allow writes to the agmsg
skill storage directory in your Codex config.
Example `~/.codex/config.toml`:
```toml
sandbox_mode = "workspace-write"
[sandbox_workspace_write]
writable_roots = [
"~/.agents/skills/agmsg/db",
"~/.agents/skills/agmsg/teams",
"~/.agents/skills/agmsg/run",
]
```
If you installed agmsg under a custom command name, adjust the path accordingly:
```toml
[sandbox_workspace_write]
writable_roots = [
"~/.agents/skills/m/db",
"~/.agents/skills/m/teams",
"~/.agents/skills/m/run",
]
```
You can also allow the whole skill directory if your Codex setup supports that:
```toml
[sandbox_workspace_write]
writable_roots = [
"~/.agents/skills/agmsg",
]
```
Codex supports `mode monitor` (legacy) through the app-server bridge, plus `mode turn`
and `mode off`; it just has no Claude Code Monitor tool. The sandbox allowlist is
required for writes performed by manual `$agmsg` commands, turn-end inbox checks and
the bridge.
Some Codex runtimes or automations may inject a managed permission profile for a
single run. In that case, the run-specific writable roots must also include the
agmsg storage paths; the user-level config alone may not be enough.
## Tests
```bash
bats tests/ # requires bats-core: brew install bats-core
```
## Architecture
```
~/.agents/skills/<cmd>/ # Folder name = command name
├── SKILL.md # Skill definition (read by CC & Codex)
├── agents/
│ └── openai.yaml # Codex metadata
├── scripts/ # Bash scripts (the type-agnostic engine)
│ ├── lib/ # Sourced helper libraries
│ └── drivers/types/<name>/ # Built-in agent-type drivers (manifest + runtime)
├── plugins/<axis>/<name>/ # External drivers you opt into (agmsg plugin trust)
├── db/messages.db # SQLite WAL-mode message store
├── run/ # Runtime state: pidfiles, actas locks, agmsgd install record
└── teams/ # Team configs (self-contained)
└── <team>/
└── config.json
```
- **Storage**: Single SQLite file with WAL mode
- **Concurrency**: Multiple readers + 1 writer, no conflicts
- **Dependencies**: `bash`, `sqlite3` (no Python required)
- **Auto detection**: Stop hook checks inbox after each response (60s cooldown, configurable via `delivery.turn.check_interval`)
- **No daemon required**: direct filesystem access (the optional agmsgd is a resident service you choose)
- **No network**: Everything local
## Plugins
agmsg's pluggable units are **drivers** grouped by axis (`types` for agent
runtimes; `storage` and `delivery` to follow). Built-ins ship under
`scripts/drivers/`; you can drop your own under `<skill>/plugins/<axis>/<name>/`
(or point `AGMSG_PLUGIN_DIRS` at a directory) to extend agmsg without forking.
Because a driver is shell code that runs with your privileges, **external drivers
are never loaded until you opt in** — an unexpected drop-in is ignored (with a
warning) until you run `agmsg plugin trust <axis>/<name>`. List what's discovered
and its trust state with `agmsg plugin list`.
Full discovery order, the trust model, and authoring guidance:
[docs/plugins.md](docs/plugins.md) (design rationale in
[ADR 0002](docs/adr/0002-driver-discovery-and-plugin-opt-in.md)).
## Building on agmsg
Writing something *outside* agmsg's own scripts that reads or drives agmsg —
a GUI app, a bot, a derivative project (`agmsg-shogi`, `agmsg-go`,
`agmsg-mcp`, …)? Read data via `scripts/api.sh` (JSON out, no need to touch
`messages.db` or `teams/*/config.json` directly — those are internal and free
to change), and write through the existing scripts (`send.sh`, `join.sh`,
…) rather than the database. Full guidance:
[docs/building-on-agmsg.md](docs/building-on-agmsg.md).
## Community
- **Product Hunt**: #5 Product of the Day, [2026-06-09 launch](https://www.producthunt.com/products/agmsg) — 219 upvotes, 39 comments
- **Community projects** (also on the [showcase](https://agmsg.cc)): [`agkanban`](https://github.com/lucianlamp/agkanban) — multi-agent kanban board that pairs with agmsg; [`agmsg-office`](https://github.com/shinshin86/agmsg-office) — replays message logs as characters speaking on a stage; [`agmsg-viewer`](https://github.com/utenadev/agmsg-viewer) — message history in a chat interface in the browser; [`agmsg-bubblelog`](https://github.com/dreiachse-cyber/agmsg-bubblelog) — replays a team's log locally as a messenger-style thread; [`agmsg-tui`](https://github.com/rrrrnmtsu/agmsg-tui) — Rust/ratatui terminal client, friendly to SSH, mosh, and tmux
- **External contributors**: [@MiuraKatsu](https://github.com/MiuraKatsu) (Gemini support + whoami auto-detect), [@roundrop](https://github.com/roundrop) (Copilot CLI support), [@TOMONOSUKEJP](https://github.com/TOMONOSUKEJP) (native Windows / Git Bash), [@kenshin-yamada](https://github.com/kenshin-yamada) (watcher scoping fix), [@utenadev](https://github.com/utenadev) (OpenCode contribution), [@lucianlamp](https://github.com/lucianlamp) (native Windows PowerShell helpers), [@tatsuya6502](https://github.com/tatsuya6502) (sandboxed Bash tool support), [@Masashi-Ono0611](https://github.com/Masashi-Ono0611) (project-path validation, watchdog and watcher fixes), [@chemica-tan](https://github.com/chemica-tan) (Windows codex bridge: project compare and port parsing), [@otsune](https://github.com/otsune) (Git Bash quoting from PowerShell), [@tsukimiya](https://github.com/tsukimiya) (OpenCode: resident spawn and monitor delivery)
## Project site (agmsg.cc)
[agmsg.cc](https://agmsg.cc) is an Astro project under [`site/`](site/).
- **Source of truth:** `site/` (Astro + Tailwind). The built output is **not** committed — CI builds it.
- **Local preview:**
```bash
cd site
npm install
npm run dev # http://localhost:4321, live reload
# or, to serve the production build:
npm run build && npm run preview
```
- **Publish:** pushing to `main` with changes under `site/` runs [`.github/workflows/pages.yml`](.github/workflows/pages.yml), which builds the site and deploys it to GitHub Pages. The custom domain is set by `site/public/CNAME`.
- The agent-types gallery is generated at build time from `scripts/drivers/types/*/type.conf`, so adding an agent type surfaces it on the site automatically.
`docs/` is developer documentation (markdown, ADRs, spec) read on GitHub — it is **not** the published site.
## Contributing
See [Design & Architecture](docs/design.md) for developer documentation — identity model, data storage, hook system, and script responsibilities.
If agmsg saves you copy-paste round-trips, a GitHub star helps other people find it.
## License
MIT