by fujibee
Cross-vendor messaging for CLI AI coding agents — let Claude Code, Codex, Gemini & Copilot talk to each other in one team. Bash + SQLite, no daemon, no framework.
# Add to your Claude Code skills
git clone https://github.com/fujibee/agmsgLast scanned: 6/14/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-06-14T08:16:24.009Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}See how agmsg compares with popular alternatives.
agmsg is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by fujibee. Cross-vendor messaging for CLI AI coding agents — let Claude Code, Codex, Gemini & Copilot talk to each other in one team. Bash + SQLite, no daemon, no framework. It has 1,500 GitHub stars.
Yes. agmsg passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/fujibee/agmsg" and add it to your Claude Code skills directory (see the Installation section above). agmsg ships a SKILL.md manifest, so compatible agents can discover and load it automatically.
agmsg is primarily written in Shell. It is open-source under fujibee on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh agmsg against similar tools.
No comments yet. Be the first to share your thoughts!
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.
Shell requirement: All agmsg scripts are Bash scripts. Always execute them via bash, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with bash -lc '...'. Example: bash -lc '~/.agents/skills/agmsg/scripts/send.sh myteam alice bob "hello"'. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use AGMSG_STORAGE_PATH (the supported override).
agmsg keeps its SQLite database, team registry, and runtime state under ~/.agents/skills/agmsg/. The ./install.sh install path creates that tree; the Claude Code plugin install path does not (the plugin marketplace flow only drops the skill content into ~/.claude/plugins/cache/). Before any other command, bootstrap if needed:
if [ ! -d ~/.agents/skills/agmsg ]; then
# Locate the plugin install script (any version), run it once.
installer=$(ls ~/.claude/plugins/cache/fujibee-agmsg/agmsg/*/install.sh 2>/dev/null | head -1)
if [ -n "$installer" ]; then
bash "$installer" --cmd agmsg
else
echo "agmsg not installed. Either:" >&2
echo " - run ./install.sh in the agmsg repo, or" >&2
echo " - install via /plugin marketplace add fujibee/agmsg && /plugin install agmsg@fujibee-agmsg" >&2
exit 1
fi
fi
After this runs once, ~/.agents/skills/agmsg/ is populated and you can skip Step 0 on future invocations.
~/.agents/skills/agmsg/scripts/whoami.sh "$(pwd)" <type>
# type: claude-code, codex, gemini, antigravity, copilot
# Returns: agent=... / multiple=true ... / suggest=true ... / not_joined=true ...
Before first-time setup, inspect the user's request. If they ask to join,
import, or bring in a team that already exists on a server, do not run
join.sh. Go directly to the remote pull command under Step 2b. Before
pulling, run team-list.sh --json --scope all; if a same-named local team has
binding_state none or disconnected, stop and ask the user how to proceed.
After pull succeeds, return here so the user can register a new local agent in
the team that pull just created.
Ask the user for a team name. If it's an existing team, run team.sh <team> first to see the current roster and note the names already in use. Look for a naming convention already in play (e.g. a shared base name with role and number suffixes (<base>-<role><n>), or names derived from the team name) and, when one exists, propose 2-3 unused names that extend it; otherwise propose 2-3 short, distinctive identity names (not a bare tool-type label like codex/cc). Either way, names must not collide with the roster. For a brand-new team, skip the roster check and just ask. Then run:
~/.agents/skills/agmsg/scripts/join.sh <team> <agent_name> <type> "$(pwd)" [--force]
Do NOT manually edit config files. Always use join.sh. If the name was recently renamed away with rename.sh, join.sh refuses to revive it (printing the new name it maps to) instead of silently re-registering it — this guards against a CLI slash-command history resubmitting actas <old_name> after a rename. Pass --force only for a deliberate, unrelated reuse of that exact name.
Default (no arguments): IMMEDIATELY check inbox. Do NOT ask what to do.
# Check inbox (marks messages as read) — DEFAULT action
~/.agents/skills/agmsg/scripts/inbox.sh <team> <agent_id>
# Send a message (from/to must already be registered in <team>; add --force to bypass)
~/.agents/skills/agmsg/scripts/send.sh <team> <from_agent> <to_agent> "<message>" [--force]
# Message history
~/.agents/skills/agmsg/scripts/history.sh <team> [agent_id] [limit]
# Export a team's message history as JSONL — one message_sent record per line,
# chronological. Default to stdout (pipeable); --out <file> writes a file.
# --agent limits to one agent; --limit keeps the most recent N (omit = all
# currently retained). Output is plaintext (the local store is plaintext).
~/.agents/skills/agmsg/scripts/export.sh --team <team> [--agent <agent>] [--limit N] [--out <file>]
# List team members
~/.agents/skills/agmsg/scripts/team.sh <team>
# List every locally known team (read-only, secret-free — "agmsg team list").
# Distinct from `team.sh <team>` above: check for "team list" FIRST so
# "list" is never mistaken for a team name. --json emits a strict,
# versioned object ({schema_version, teams: [{name, remote_team_id, scope,
# binding_state}]}) and exits non-zero with NO payload if any team was
# unreadable or the count was truncated — never a partial list dressed up
# as complete. See scripts/team-list.sh's own header comment for the exact
# enums and why onboarding_state/promote_eligible/blocked_reason are
# deliberately NOT in this schema yet (their meaning depends on ADR 0010,
# which hasn't landed).
~/.agents/skills/agmsg/scripts/team-list.sh [--json] [--scope all|project] [<project_path>]
# Leave a team
~/.agents/skills/agmsg/scripts/leave.sh <team> <agent_id>
# Rename a team (moves dir, updates config + messages).
# After renaming, each existing member should re-run whoami.sh to refresh
# their cached team name in any running session.
~/.agents/skills/agmsg/scripts/rename-team.sh <old_team> <new_team>
# Show the installed version — the git-describe provenance string recorded at
# install time (tag + commits-since + abbreviated commit, plus -dirty when
# installed from a tree with uncommitted changes). See #117.
~/.agents/skills/agmsg/scripts/version.sh
# Clear registrations for the current project/type.
# A trailing <session_id> additionally releases any actas exclusivity locks
# this session held on <agent_id> so peers can pick them up immediately.
~/.agents/skills/agmsg/scripts/reset.sh "$(pwd)" <type> [agent_id] [session_id]
# Set delivery mode for this project.
# monitor — real-time push via SessionStart + Monitor tool (claude-code only)
# turn — Stop-hook pulls at the end of each assistant turn
# both — monitor primary, turn as fallback
# off — no automatic delivery
~/.agents/skills/agmsg/scripts/delivery.sh set <mode> <type> "$(pwd)"
~/.agents/skills/agmsg/scripts/delivery.sh status <type> "$(pwd)"
# Multiple roles per project (one CC = one active role).
# Claude Code: `actas` claims an exclusivity lock for <name> across sessions
# and restarts the Monitor filtered to <name> only; peer watchers stop
# subscribing to <name> while this session holds the lock. `drop` releases.
# Codex: actas is send-side only (no stable session_id during slash commands
# → no peer-visible lock). See README "Codex caveat" for details.
# If <name> is new and none was given upfront (bare `actas`, or the user asks
# for a suggestion), check the target team's roster first (team.sh <team>).
# Look for a naming convention already in play (e.g. a shared base name with
# role and number suffixes (<base>-<role><n>), or names derived from the
# team name) and, when one exists, propose 2-3 unused names that extend it;
# otherwise propose 2-3 short, distinctive names. Either way, names must not
# collide with the roster. Ask the user to pick before continuing.
~/.agents/skills/agmsg/scripts/actas-claim.sh "$(pwd)" <type> <name> "$session_id"
~/.agents/skills/agmsg/scripts/reset.sh "$(pwd)" <type> <name> "$session_id"
# (Both of the above are normally driven by `/agmsg actas <name>` and
# `/agmsg drop <name>` slash commands, which also handle the Monitor
# TaskStop + relaunch dance described in the cmd template.)
# Spawn a NEW agent process that takes an actas identity on boot.
# Pre-joins <name> to a team, then launches the agent CLI in a tmux pane/window
# (when run inside tmux) or a new OS terminal, with `/agmsg actas <name>` as the
# initial prompt. By default it BLOCKS until the new agent's watcher attaches
# (prints `status=ready`), so a leader can send work right after spawn returns
# without losing it to the agent's cold start. claude-code/codex only; macOS
# primary, Linux/Windows best-effort. Non-tmux + no usable terminal (headless)
# errors out.
# --project <path> project to launch in (default: $PWD)
# --team <team> team to join into (default: auto-resolved from project)
# --window new tmux window instead of splitting the current one
# --split h|v tmux split direction (default h)
# --terminal <tmpl> terminal command template ({cmd} = path to the boot
# script) for the non-tmux path; overrides $AGMSG_TERMINAL
# / config spawn.terminal. macOS default uses `open -a`
# (no Automation/TCC permission prompt).
# --no-wait don't block on readiness (fire-and-forget)
# --ready-timeout N seconds to wait for readiness (default 90; on timeout
# prints status=timeout and exits 3). Codex skips the
# wait (it has no Monitor).
# --boot-prompt <text> hand the new agent an initial task: the boot prompt
# becomes the actas command followed (newline-separated)
# by <text>, so it claims its identity AND starts the task
# in its first turn. The only way to give a one-shot goal
# to a codex peer (no Monitor → a post-spawn send to its
# idle session is never noticed).
~/.agents/skills/agmsg/scripts/spawn.sh <claude-code|codex> <name> [options]
# Tear down a spawned member — the inverse of spawn.
# Default (graceful): sends a `ctrl:despawn` control message to <name>; the
# member's watcher drops its own role (releasing the actas lock + registration)
# and closes its own tmux pane, ending the agent. Blocks until the lock releases
# (--timeout, default 30s) then prints `status=ok`; on timeout prints
# status=timeout and exits 3 (retry with --force). Only an exclusive watcher
# dedicated to <name> acts on it — the despawning session is never torn down.
# --force: skip the message and tear the member down from the placement recorded
# at spawn time (kill its tmux pane/window, drop its registration) — for a dead
# watcher or a codex member (no Monitor). A hand-started member with no placement
# record can't be --forced.
# --force tear down from the recorded placement, no message
# --timeout N seconds to wait for graceful teardown (default 30)
~/.agents/skills/agmsg/scripts/despawn.sh <team> <from> <name> [--force] [--timeout N]
If argument starts with "rename" but not "rename-team":
<team> <old_name> <new_name>,
or <old_name> <new_name> only when this agent belongs to exactly one team.bash ~/.agents/skills/agmsg/scripts/rename.sh <team> <old_name> <new_name>member_renamed journal event
propagates the rename to other machines.If argument starts with "rename-team":
<old_team> <new_team>.bash ~/.agents/skills/agmsg/scripts/rename-team.sh <old_team> <new_team>Remote setup is no-auth. Do not ask for a token or create one.
If argument starts with "remote connect":
--endpoint <url> and <team>, plus optional --e2ee.bash ~/.agents/skills/agmsg/scripts/remote.sh connect --endpoint <url> [--e2ee] <team>--e2ee only
when the user explicitly requests end-to-end encryption. The choice is
fixed by the first connect.bash ~/.agents/skills/agmsg/scripts/remote.sh pull --endpoint <actual-url> <actual-team>If argument starts with "remote pull":
join.sh, create a team, or create a same-named local
team. Always use remote pull.--endpoint <url> and <team>, plus optional
--team-id <uuid>.bash ~/.agents/skills/agmsg/scripts/remote.sh pull --endpoint <url> [--team-id <uuid>] <team>Machine B needs its own install, not just its own environment variables.
Only remote.sh, remote-sync.sh, key.sh and the two internal helpers read
AGMSG_SYNC_CONNECTION_DIR; send.sh, history.sh, team.sh and inbox.sh
resolve the team config from the install directory. So a pull driven by
environment variables alone succeeds, and the send that is supposed to confirm
it then reports the team as missing — the failure lands one step after the
cause. See "Use a separate install for testing" in docs/remote-setup.md.
If argument starts with "remote unlock":
<team>, --bundle <file>, and --confirm-digest <sha256>.bash ~/.agents/skills/agmsg/scripts/remote.sh unlock <team> --bundle <file> --confirm-digest <sha256>--snapshot plus --identity or
--identity-stdin remains available when explicitly requested.If argument starts with "remote status":
<team> and --json.bash ~/.agents/skills/agmsg/scripts/remote.sh status [<team>] [--json]If argument starts with "remote sync start":
<team>.bash ~/.agents/skills/agmsg/scripts/remote.sh sync start <team>If argument starts with "remote disconnect":
<team>.bash ~/.agents/skills/agmsg/scripts/remote.sh disconnect <team>If argument starts with "remote forget":
<team>. This permanently deletes that team's local
roster, history, keys, trust, and sync state, but never changes the server.--yes yourself. Run:
bash ~/.agents/skills/agmsg/scripts/remote.sh forget <team>If argument starts with "key generate" followed by an optional team name:
bash ~/.agents/skills/agmsg/scripts/key.sh generate [<team>]If argument starts with "key show":
--reveal-secret.bash ~/.agents/skills/agmsg/scripts/key.sh show [<team>] [--reveal-secret]--reveal-secret requires a real interactive terminal and is refused in
agent mode. Tell the user to run it directly in their own terminal.If argument starts with "key handoff" followed by a team name:
--out <file> and run:
bash ~/.agents/skills/agmsg/scripts/key.sh handoff <team> [--out <file>]If argument starts with "key import" followed by a team name:
read -rsp 'Identity: ' IDENTITY; echo
printf '%s' "$IDENTITY" | ~/.agents/skills/agmsg/scripts/key.sh import <team> --identity-stdin
unset IDENTITY
If argument starts with "key rotate" followed by a team name:
age; it refuses
with a message naming whichever is missing.bash ~/.agents/skills/agmsg/scripts/key.sh rotate <team>key show <team> --key-id <id> --reveal-secret, which is refused in agent
mode — tell the user to run that in their own terminal.Device pairing (key request / key approve) is not implemented — they are
not key.sh subcommands, so a call prints usage and exits 1. If the user asks
for one, tell them so instead of attempting to run it.
Every agmsg step above runs through the host's Bash tool, so on Claude Code each call is gated by the permission system until you allowlist the script directory. Add these to ~/.claude/settings.json (or project-level .claude/settings.local.json):
{
"permissions": {
"allow": [
"Bash(~/.agents/skills/agmsg/scripts/*)",
"Bash(/Users/<you>/.agents/skills/agmsg/scripts/*)",
"Bash(bash ~/.agents/skills/agmsg/scripts/*)",
"Bash(bash /Users/<you>/.agents/skills/agmsg/scripts/*)"
]
}
}
Four entries rather than one because a rule matches the command string as written: the scripts are invoked both as ~/... and as an absolute path, and with or without an explicit bash prefix. Replace /Users/<you> with your home directory, and the agmsg path segment with your command name if you installed under a different one.
Every subcommand needs its own match. Claude Code's permission docs state that a rule must match each subcommand independently, and that the recognized separators are &&, ||, ;, |, |&, &, and newlines. Chaining two agmsg scripts is fine — both match the entries above. What reintroduces the prompt is mixing in a command those entries do not cover: delivery.sh status … ; printenv AGMSG_SPAWNED prompts because of the printenv, not because of the ;. Splitting it into its own call does not remove that prompt — it only keeps it from gating the agmsg call. Allowlist the command as well if it needs to be prompt-free.
When Claude Code's sandbox is enabled, watch.sh (monitor mode) runs inside the sandbox and needs to write pidfiles and SQLite WAL files under ~/.agents/skills/agmsg/. Add an allowlist entry to ~/.claude/settings.json (or project-level .claude/settings.local.json):
{
"sandbox": {
"filesystem": {
"allowWrite": [
"~/.agents/skills/agmsg/"
]
}
}
}
The allowlist merges across scopes and takes effect immediately — no restart needed. If agmsg was installed under a custom command name (e.g. m), adjust the path accordingly.
Note on BASH_SOURCE: The sandboxed Bash tool runs commands via pipe/eval, so BASH_SOURCE[0] is empty inside sourced functions like storage.sh. This is handled internally — watch.sh resolves SKILL_DIR from $0 (which works correctly when invoked as a command), and storage.sh falls back to that value. No user configuration needed.
~/.agents/skills/agmsg/db/messages.db~/.agents/skills/agmsg/teams/<name>/config.jsonsqlite3 CLIremote/key additionally need curl, python3, and age/age-keygen, but only if those commands are usedCross-agent messaging for CLI AI agents. No daemon, no network, no complexity.
For AI agents: see
/llms.txtfor a quick, machine-friendly orientation.
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.
What it isn't:
bash + sqlite3.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.Two monitor-mode Claude Code instances, 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:

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.
# 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 (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 below.
Prefer to inspect the code first, track the latest main, or pick a custom command name? See 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.
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. There is no daemon, no socket, no broker — the file is the shared floor and the agents take turns on 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.
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).
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) with SLSA provenance — the attestation is visible at https://www.npmjs.com/package/agmsg.
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.
Clone the repo first, then run the installer — this is also the path that always tracks the latest main:
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:
~/.agents/skills/<cmd>//<cmd>$<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.
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 naturally 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:
Set-Alias bash 'C:\Program Files\Git\bin\bash.exe'
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:
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.
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 g