cd /news/developer-tools/show-hn-i-gave-my-ai-coding-agents-a… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-56352] src=github.com β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

Show HN: I gave my AI coding agents a group chat (it's just a Git repo)

A developer released AgentComm, an open-source CLI tool that turns any Git repository into a message bus for AI coding agents, enabling them to communicate via a shared mailbox interface. The tool supports multiple backends including Git, GitHub, SQLite, S3, GCS, and PostgreSQL, and ships with plugins for Claude Code, Codex, and OpenCode.

read18 min views1 publishedJul 12, 2026
Show HN: I gave my AI coding agents a group chat (it's just a Git repo)
Image: source

🌐 ** Website** ·

Use casesΒ·

Live demo β€” an agent conversation thatΒ·

isa git branchClaude Code pluginΒ·

Codex pluginΒ·

OpenCode plugin

A tiny mailbox / message bus for AI agents that shell out to one CLI. Agents register

, send

, and read their inbox

; a single Backend

interface hides where the messages live. Any git remote is a bus β€” the git backends need no driver at all; local runs need zero dependencies; everything else is an optional, lazy-loaded driver.

Get yourself (and this repo) on the bus β€” takes under a minute:

If you are Claude Code, ask your user to run/plugin marketplace add yonidavidson/agentcomm

and/plugin install agentcomm@yonidavidson-plugins

. The plugin ships the CLI, a skill with the conventions, and hooks that register you on the repo bus automatically at session start. Then runagentcomm init

; it adds the coordination contract toCLAUDE.md

.If you are Codex, ask your user to runcodex plugin marketplace add yonidavidson/agentcomm

andcodex plugin add agentcomm@yonidavidson-plugins

. The plugin ships the CLI, coordination skill, and lifecycle hooks. Review and trust its hooks with/hooks

after installation. Then ask Codex to use agentcomm to initialize the repo; the skill runsinit --harness codex

and adds the coordination contract toAGENTS.md

.If you are OpenCode, add the plugin tarball from this repo's latestreleaseto youropencode.json

β€”"plugin": ["https://github.com/yonidavidson/agentcomm/releases/download/v0.16.10/agentcomm-opencode-0.16.10.tgz"]

. It puts every session on the repo bus in-process. OpenCode readsAGENTS.md

natively, soagentcomm init --harness opencode

(which writesAGENTS.md

) also onboards it β€” seeAs an OpenCode plugin.

            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 agents ──▢ β”‚  agentcomm CLI  (one stable interface)        β”‚
            β”‚      β”‚                                         β”‚
            β”‚      β–Ό                                         β”‚
            β”‚  Backend interface  ◀── the seam               β”‚
            β”‚   β”œβ”€ GitBackend      β€” ANY git remote is a bus β”‚
            β”‚   β”œβ”€ GithubBackend   β€” GitHub via token (no ssh)β”‚
            β”‚   β”œβ”€ LocalBackend    β€” zero-dep default         β”‚
            β”‚   β”œβ”€ SqliteBackend   β€” single box, WAL          β”‚
            β”‚   β”œβ”€ S3Backend       β€” object store             β”‚
            β”‚   β”œβ”€ GCSBackend      β€” object store             β”‚
            β”‚   └─ PostgresBackend β€” distributed, push        β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Not on the npm registry (yet) β€” install straight from GitHub. dist/

is committed to the repo, so this needs no build step, and the install is genuinely zero-dependency: you get dist/

and nothing else.

npm install github:yonidavidson/agentcomm

npm install better-sqlite3            # sqlite://
npm install @aws-sdk/client-s3        # s3://
npm install @google-cloud/storage     # gs://
npm install pg                        # postgres://
npm install yaml                      # only for .agentcomm.yaml config files (.json needs nothing)

This repo is also a self-hosted Claude Code plugin marketplace β€” install it and Claude picks up a skill that knows the CLI's commands, flags, and backend tradeoffs, and uses them to coordinate with other agents/sessions:

/plugin marketplace add yonidavidson/agentcomm
/plugin install agentcomm@yonidavidson-plugins

No global install or npm registry publish required β€” the plugin ships a prebuilt copy of the CLI and the skill runs it directly. In a git repo it defaults to the repo bus, like everywhere else.

This repo is also a Codex marketplace. Add it and install the plugin from the marketplace snapshot:

codex plugin marketplace add yonidavidson/agentcomm
codex plugin add agentcomm@yonidavidson-plugins

The plugin bundles the same prebuilt CLI and coordination skill plus Codex lifecycle hooks for registration, inbox digests, and the stop guard. Codex requires explicit trust for non-managed hooks: open /hooks

, review the agentcomm definitions, and trust them. Start a new thread after installing or upgrading so the plugin components are loaded.

Ask Codex directly so its skill uses the bundled CLI:

Use agentcomm to initialize this Codex repo for the team.

OpenCode runs on Bun and reads AGENTS.md

natively, so its agents already onboard from this repo's AGENTS.md

. The plugin adds the lifecycle β€” it registers each session on the bus, briefs it, surfaces unread mail before the session goes idle, and keeps long turns reachable β€” by importing the agentcomm library in-process (no subprocess). Because OpenCode's session.idle

is observe-only, the inbox guard re-prompts the session rather than blocking it.

Install it from the plugin tarball attached to each release β€” OpenCode fetches the .tgz

directly, no clone and no npm registry:

{
  "plugin": ["https://github.com/yonidavidson/agentcomm/releases/download/v0.16.10/agentcomm-opencode-0.16.10.tgz"]
}

OpenCode loads the plugin from the tarball's package root via its exports["./server"]

entry (the compiled library ships inside, so there's no build step and β€” for the file/git backends β€” zero runtime dependencies).

Updating. OpenCode caches a plugin by its URL and never re-fetches, so a "latest" URL would silently pin you to your first install. The URL is versioned on purpose: bump the version to upgrade. You don't have to watch the releases page β€” the plugin checks once a day and, when a newer release exists, prints an "agentcomm-opencode update available: vX β†’ vY" notice in-session (like omp

/pi

do), telling you exactly which version to put in the URL.

Why a tarball and notOpenCode installs a remote plugin by cloning the whole repo, and this monorepo (full CLI + committedgithub:…

?dist/

across its history) is a large, slow clone that OpenCode's installer chokes on. The release tarball is ~100 kB (dist only, no history), so it installs in seconds.To develop against a local checkout, point the entry at the repo directory instead:"plugin": ["/absolute/path/to/agentcomm"]

.

agentcomm init                      # β†’ acting as yoni-3f2a Β· on the bus: git+ssh://…
agentcomm init --harness codex      # β†’ AGENTS.md created
agentcomm agents                    # who's here: yoni-3f2a Β· dana-97b1 Β· ci-bot
agentcomm send ci-bot "hold deploys" --subject status

agentcomm register --as reviewer
agentcomm send reviewer "review src/auth.ts" --subject task --thread auth-1
agentcomm inbox --as reviewer --json     # consumes; archives under read/
agentcomm wait  --as reviewer --timeout 30000   # exit 0 on delivery, 2 on timeout

agentcomm send work-queue "task-1" --subject task
agentcomm claim --queue work-queue --as worker-1   # atomic; null when empty

export AGENTCOMM_BACKEND=postgres://user:pass@host:5432/agentcomm
agentcomm wait --as reviewer --timeout 30000   # resolves within ~ms via LISTEN/NOTIFY

send

/broadcast

read the body from the trailing argument, or from stdin if omitted:

echo "from a pipe" | agentcomm send bob --as alice

Agents sharing a repo, talking through itβ€” the repo is the bus: repo permissions are the ACL, every message is a commit you can watch.** Cloud + local worker fleets**splitting one queue with atomicclaim

.A CD pipeline you can ask"what's the status of the build?" mid-deploy.** IoT edge agents**β€” a camera answering "what do you see?", weather sensors reporting humidity to onebroadcast

β€” on nothing but outbound HTTPS.Claude Code, Codex, and OpenCode pairing on one machineβ€” each native plugin uses its own guidance file while both communicate over the same repo bus.

All illustrated with runnable commands on the use-cases page β€” plus why the security story is subtraction: your storage's auth is the bus's auth.

Command What it does
init
Put this repo on the bus: writes CLAUDE.md by default, or AGENTS.md with `--harness codex opencode
register
Register / heartbeat the calling agent (--as ).
agents
List registered agents.
send <to> [body]
Send a message (body from arg or stdin).
broadcast [body]
Send to every registered agent except yourself.
inbox
Consume undelivered messages; archives them under read/ .
peek
Show undelivered messages without consuming.
wait
Block until a message arrives (exit 0) or timeout (exit 2).
claim
Atomically dequeue one message from --queue (git + SQL backends).
describe
Explain the --backend scheme: how channels are carved from the URI, and its capabilities. Static β€” never loads a driver or connects.
channels
List the channels that already exist on the --backend store (scans for the agentcomm key layout; needs the driver + credentials).
purge
Delete archived (read/ ) messages older than --older-than , and/or registrations idle past --agents-older-than . Pending mail is never touched. The daemon runs both automatically (30d / 7d defaults).
log
Read a channel's conversation β€” pending + archived, time-ordered, non-consuming, no --as needed. --thread , --limit .
conventions
Print the effective team conventions (built-in defaults βŠ• .agentcomm.json /.yaml override). Static β€” never connects.
Flag Meaning
--backend <uri>
Backend URI. Default resolution: flag > AGENTCOMM_BACKEND > .agentcomm config > git+<origin> probe > github:// token fallback > file://./.agentcomm .
--as <name>
Acting alias (env AGENTCOMM_AGENT ). Defaults to <git-identity>-<session> (a 4-char per-session id β€” concurrent runners on one machine get distinct mailboxes; set AGENTCOMM_SESSION to pin it). Names are aliases β€” addressing, not authentication; on git backends the commit author in git log is the verifiable identity.
--subject <text>
Message subject (send /broadcast ).
--thread <id>
Thread id (send /broadcast ).
--timeout <ms>
wait timeout in ms (default 30000 ).
--queue <name>
Queue to claim from (claim ) β€” same namespace as a recipient inbox.
--older-than <dur>
Age threshold for purge (45s , 30m , 12h , 30d ).
--dry-run
purge only lists what it would delete.
--limit <n>
log : keep the most recent n messages (default 50).
--harness <name>
init : select claude (default, CLAUDE.md ) or codex (AGENTS.md ).
--json
Machine-readable JSON output (available on every command).

In a git repo, you're already on the network.With no backend configured, agentcomm probes yourorigin

remote: if git can reach it, the bus isgit+<origin>

β€”any host, atomicclaim

included; if only a GitHub token is available, it falls back togithub://owner/repo

. A stderr notice tells you what was picked. Resolution:--backend

AGENTCOMM_BACKEND

.agentcomm

config > git probe > github token >file://./.agentcomm

(AGENTCOMM_NO_GIT_PROBE=1

skips the probe).

Choose transport by topology β€” that's the only fork that matters.

| Backend | URI | Driver (optional) | Atomic move | claim (shared queue) | Push (wait ) | Use when | |---|---|---|---|---|---|---| Local | file:///path/dir , bare dir | β€” (built in) | βœ… (rename) | ❌ | poll | dev, single process, zero deps | Git (any host) | git+ssh://…/repo.git[?channel=x] | β€” (git binary) | βœ… (one commit) | βœ… (push CAS) | poll | any git remote β€” GitLab, Gitea, private servers | GitHub | github://owner/repo[/prefix] | β€” (built in) | ❌ (copy+commit) | ❌ | poll | token-mode GitHub variant (CI, API-only environments) | SQLite | sqlite:///path.db[?channel=x] , *.db | better-sqlite3 | βœ… (txn) | βœ… (txn) | poll | single machine (recommended) | S3 | s3://bucket/prefix | @aws-sdk/client-s3 | ❌ (copy+del) | ❌ | poll | shared object store | GCS | gs://bucket/prefix | @google-cloud/storage | ❌ (copy+del) | ❌ | poll | shared object store | Postgres | postgres://…/db[?channel=x] | pg | βœ… (txn) | βœ… SKIP LOCKED | βœ… push | across machines/containers |

Rule of thumb:

One machine β†’ WAL mode gives you ACID, atomic per-key writes and an atomicsqlite://

.move

, with no daemon. This is the recommended default.Across machines/containers β†’ for race-free shared queues (postgres://

SKIP LOCKED

) and real push (LISTEN/NOTIFY

) in one boring dependency.

A cold CLI call on a network bus pays a round-trip (a git fetch, an API call) β€” fine occasionally, slow as a conversation. So on network schemes (git+ssh://

, git+https://

, github://

) the CLI keeps a bus daemon: one background process per bus URI that polls the remote on its own clock (AGENTCOMM_POLL_MS

, default 10s) and serves commands over a local socket.

Same semantics, exactlyβ€” the daemon slots inundertheBackend

seam. Reads come from its warm mirror (staleness ≀ the poll interval);sends ack from a disk-persisted outbox in ~0.2s and are delivered in order with retries (crash-safe;--sync

waits for remote durability instead); consumption (inbox

/claim

) always confirms against the real store, so atomicity is untouched.daemon status

shows outbox depth.- Autostarted on first use; exits itself after 30 idle minutes. agentcomm daemon status|stop

to inspect,--daemon

to force it on any scheme,--direct

(orAGENTCOMM_DAEMON=0

) to bypass. If the daemon can't be reached the CLI silently falls back to a direct connection β€” never worse, only faster.

A channel is a connection string: two agents share a bus iff they pass the same --backend

URI. One store can host many isolated channels β€” for the path-carved backends, just append a segment:

git+ssh://…/repo.git?channel=team-a                       # git: carve by query param
s3://acme-bus/team-a          s3://acme-bus/team-b        # two isolated buses, one bucket
file:///shared/bus/team-a     file:///shared/bus/team-b   # same idea on a shared volume
postgres://…/bus?channel=team-a                           # SQL: carve by query param
sqlite:///shared/bus.db?channel=team-a                    # (omit ?channel= = root channel)

On SQL backends every channel keeps the full guarantees β€” atomic claim

and (on Postgres) push wait

are isolated per channel, and data written without ?channel=

stays untouched as the root channel.

Don't memorize the per-scheme rules β€” ask the CLI:

agentcomm describe --backend s3://acme-bus --json

And to join existing work, enumerate instead of guessing prefixes:

agentcomm channels --backend s3://acme-bus

Channels are namespacing, not security: everyone on a store shares its credentials. Isolation is enforced by the backend's own access controls β€” and those can be channel-grained (e.g. S3 IAM prefix conditions per team, Postgres grants per database).

Topic channels: kebab-case, one workstream each β€”github://owner/repo/fix-auth

.Repo artifacts(git backend):issue-<n>

/pr-<n>

β€” discussion of issue or PR N has a deterministic home, no coordination needed to find it.: the well-known meeting room per store β€” register there, announce which topic channels you're joining, ask who's on what.lobby

These are defaults in code; a project overrides them with an .agentcomm.json

(zero-dep) or .agentcomm.yaml

(optional yaml

package) file, found upward from the working directory or named by AGENTCOMM_CONFIG

:

{
  "backend": "github://acme/webapp",
  "conventions": { "lobby": "commons", "subjects": ["plan", "done"] }
}

(backend

pins a project-default bus β€” consumed by the backend resolution chain.) Agents never memorize any of this:

agentcomm conventions --json                                # the effective rules + their source
agentcomm log --limit 20 --backend github://acme/webapp/fix-auth   # read the room before speaking

The join recipe: channels

(what exists) β†’ construct/pick the topic URI β†’ register

β†’ log --limit 20

(catch up on the conversation, non-consuming) β†’ announce yourself with broadcast --subject status

.

file:///abs/path/dir          filesystem (absolute)
file://relative/dir           filesystem (relative to cwd)
/abs/path  or  ./rel          bare path β†’ filesystem
sqlite:///abs/path/to.db      single-file SQLite (WAL)
sqlite:///path.db?channel=x   one channel carved out of that file
./bus.db                      bare path ending in .db β†’ SQLite
s3://bucket/optional/prefix   S3
gs://bucket/optional/prefix   GCS
postgres://user:pass@host/db  Postgres (postgresql:// also accepted)
postgres://…/db?channel=x     one channel carved out of that database
github://owner/repo           the repo itself (orphan branch 'agentcomm')
github://owner/repo/team-a    a path-carved channel on that bus
github://owner/repo?branch=b  a different bus branch
git+ssh://git@host/o/r.git    ANY git remote β€” GitLab, Gitea, private servers
git+https://host/o/r.git      same over HTTPS; git+file:///path for local bare repos
git+…/r.git?channel=team-a    param-carved channel (?branch= picks the bus branch)

The github://

backend needs no npm driver at all β€” a token from AGENTCOMM_GITHUB_TOKEN

, GITHUB_TOKEN

, GH_TOKEN

or gh auth token

is enough. Every message is a commit on the bus branch, so the conversation is browsable on github.com and repo collaborator permissions are the access control. No claim

(moves are copy+commit); wait

polls β€” poll gently, the REST quota (5,000/hr) is shared account-wide.

The git+ssh://

/ git+https://

/ git+file://

backends are the generic plain-git transport: they drive the git

binary against any remote, with whatever auth git already has (SSH keys, credential helpers) β€” GitHub, GitLab, Gitea, Bitbucket, a private server, or a bare directory. No API, no rate limits, and because git push

is a compare-and-swap, move

is atomic and ** claim works** β€” race-free shared queues with zero infrastructure. A bare cache repo lives under

~/.cache/agentcomm/git

(override with AGENTCOMM_GIT_CACHE_DIR

).The bus is disposable coordination state, not code β€” anyone with write access to the store owns cleanup (typically the repo/bucket owner, or a scheduled agent). Two layers:

agentcomm purge --older-than 30d --backend <uri>          # add --dry-run to preview

gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/agentcomm

Nothing on the default branch depends on the bus branch β€” deleting it is always safe.

createBackend

doesn't special-case the built-ins β€” they're registered through the exact same seam any third-party package uses:

import { registerBackend } from 'agentcomm';
import type { Backend } from 'agentcomm';

class RedisBackend implements Backend { /* put/get/list/delete/exists/move */ }

registerBackend('redis', async (uri) => new RedisBackend(uri), {
  kind: 'redis',
  capabilities: { claim: true, push: true },
  channel: {
    rule: 'One channel per key namespace β€” append /<channel> to the URI.',
    template: 'redis://host:6379/<channel>',
    example: 'redis://cache.internal:6379/team-a',
  },
});

The third argument (a BackendInfo

, optional but recommended) makes the scheme self-describing: agentcomm describe --backend redis://…

serves it to agents statically β€” no driver load, no connection.

Publish that as its own npm package (e.g. agentcomm-backend-redis

) with a side-effecting import. Users opt in without touching agentcomm:

npm install agentcomm-backend-redis
AGENTCOMM_BACKEND_PLUGINS=agentcomm-backend-redis agentcomm send bob hi --backend redis://localhost --as alice

AGENTCOMM_BACKEND_PLUGINS

is a comma/whitespace-separated list of module specifiers the CLI imports before resolving --backend

. Implement Claimable

/Waitable

too if the store can support atomic claims or push β€” the Bus feature-detects both, no registration needed beyond Backend

itself.

The bus is just a key layout on top of the blob Backend

:

agents/<name>.json                  registry + heartbeat
inbox/<recipient>/<seq>_<id>.json   undelivered messages
read/<recipient>/<seq>_<id>.json    archived after consumption (audit trail)

<seq>

is a zero-padded, monotonic, lexicographically-sortable prefix, so a list()

returns messages in send order. Consuming a message move()

s it from inbox/

to read/

β€” messages are archived, never hard-deleted. A queue (for claim

) is the same namespace as a recipient inbox β€” send

populates it, claim

atomically dequeues from it instead of a single consumer reading via inbox

.

Single-consumer-per-inbox is a feature. It's what makes the object-store backends race-free without locks.claim

exists only where the store gives a real atomic primitive β€” SQL transactions, orgit push

as a compare-and-swap;file://

/s3://

/gs://

error clearly rather than faking it with locks.Don't put SQLite on object storage. SQLite needs a real filesystem with byte-range locks; over S3/GCS/gcsfuse its locking breaks and concurrent writes corrupt the file.sqlite://

is for local/persistent disk only.(exit 0 delivered / 2 timeout), whether it polls (Local/SQLite/object stores) or pushes (Postgres, viawait

's contract is identical on every backendLISTEN/NOTIFY

).New drivers are optional + lazy. A missing driver produces a clearinstall X

message, not a crash β€” soLocalBackend

stays zero-dependency.PostgresBackend uses one schema for everything. Like SQLite, a singleblobs(key, data)

table backsBackend

,Claimable

(SELECT ... FOR UPDATE SKIP LOCKED

), andWaitable

(put()

issuespg_notify()

when the key is underinbox/<recipient>/

) β€” no separatemessages

table withowner

/claimed_at

columns. Claim ownership isn't persisted; the returnedMessage

is the only record of who has it.

import { Bus, createBackend } from 'agentcomm';

const backend = await createBackend('sqlite:///tmp/bus.db');
const bus = new Bus(backend);

await bus.register('alice');
await bus.send({ from: 'alice', to: 'bob', body: 'hi', subject: 'plan' });
const msgs = await bus.inbox('bob'); // Message[]
await backend.close?.();
npm install                 # dev toolchain incl. all backend drivers (devDependencies)
npm run typecheck
npm test                    # vitest: backend contract, bus, CLI e2e, WAL/Postgres concurrency
npm run build               # emit dist/

The S3, GCS and Postgres tests (test/s3.test.ts

, test/gcs.test.ts

, test/postgres.test.ts

) need live services β€” each suite skips itself with a console warning when its service is unreachable. One command brings everything up (Garage, an S3-compatible object store written in Rust; fake-gcs-server; and Postgres β€” buckets and keys provisioned by test/e2e/setup.sh

with fixed throwaway credentials):

npm run test:e2e:up    # docker compose up + provision buckets/keys
npm test               # now runs ALL suites, nothing skipped
npm run test:e2e:down  # tear down (removes volumes)

The github://

suite (test/github.test.ts

) targets a real repo on a scratch branch, deleted afterwards β€” gate it with AGENTCOMM_TEST_GITHUB_REPO=you/yourrepo

(your gh

login is enough). In CI it runs against this repository itself using the workflow's token.

CI (.github/workflows/ci.yml

) runs this same flow on every push and PR, so all seven backends are exercised end-to-end.

The test suite runs the same backend-contract and bus tests against every backend (the git suite runs against local bare repos, so its full fetch/plumbing/push path needs no services), plus concurrency tests proving: WAL lets independent SQLite writers proceed; N concurrent processes calling claim

on one shared queue (SQLite or Postgres) get disjoint messages, none dropped, none double-delivered; and wait

on Postgres resolves within tens of ms of a send

from a separate OS process (real push via LISTEN/NOTIFY

, not a poll interval). CLI end-to-end tests cover the wait

exit codes, the claim

error/empty/success paths, the AGENTCOMM_BACKEND_PLUGINS

mechanism, and the missing-driver error path.

MIT Β© Yoni Davidson

── more in #developer-tools 4 stories Β· sorted by recency
── more on @agentcomm 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/show-hn-i-gave-my-ai…] indexed:0 read:18min 2026-07-12 Β· β€”