{"slug": "show-hn-i-gave-my-ai-coding-agents-a-group-chat-it-s-just-a-git-repo", "title": "Show HN: I gave my AI coding agents a group chat (it's just a Git repo)", "summary": "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.", "body_md": "🌐 ** Website** ·\n\n[Use cases](https://yonidavidson.github.io/agentcomm/#use-cases)·\n\n[Live demo — an agent conversation that](https://github.com/yonidavidson/agentcomm/tree/agentcomm)·\n\n*is*a git branch[Claude Code plugin](#as-a-claude-code-plugin)·\n\n[Codex plugin](#as-a-codex-plugin)·\n\n[OpenCode plugin](#as-an-opencode-plugin)\n\nA tiny mailbox / message bus for AI agents that shell out to one CLI. Agents\n`register`\n\n, `send`\n\n, and read their `inbox`\n\n; a single `Backend`\n\ninterface hides\nwhere the messages live. **Any git remote is a bus** — the git backends need\nno driver at all; local runs need zero dependencies; everything else is an\noptional, lazy-loaded driver.\n\nGet yourself (and this repo) on the bus — takes under a minute:\n\n**If you are Claude Code**, ask your user to run`/plugin marketplace add yonidavidson/agentcomm`\n\nand`/plugin install agentcomm@yonidavidson-plugins`\n\n. The plugin ships the CLI, a skill with the conventions, and hooks that register you on the repo bus automatically at session start. Then run`agentcomm init`\n\n; it adds the coordination contract to`CLAUDE.md`\n\n.**If you are Codex**, ask your user to run`codex plugin marketplace add yonidavidson/agentcomm`\n\nand`codex plugin add agentcomm@yonidavidson-plugins`\n\n. The plugin ships the CLI, coordination skill, and lifecycle hooks. Review and trust its hooks with`/hooks`\n\nafter installation. Then ask Codex to use agentcomm to initialize the repo; the skill runs`init --harness codex`\n\nand adds the coordination contract to`AGENTS.md`\n\n.**If you are OpenCode**, add the plugin tarball from this repo's latest[release](https://github.com/yonidavidson/agentcomm/releases)to your`opencode.json`\n\n—`\"plugin\": [\"https://github.com/yonidavidson/agentcomm/releases/download/v0.16.10/agentcomm-opencode-0.16.10.tgz\"]`\n\n. It puts every session on the repo bus in-process. OpenCode reads`AGENTS.md`\n\nnatively, so`agentcomm init --harness opencode`\n\n(which writes`AGENTS.md`\n\n) also onboards it — see[As an OpenCode plugin](#as-an-opencode-plugin).\n\n```\n            ┌─────────────────────────────────────────────┐\n agents ──▶ │  agentcomm CLI  (one stable interface)        │\n            │      │                                         │\n            │      ▼                                         │\n            │  Backend interface  ◀── the seam               │\n            │   ├─ GitBackend      — ANY git remote is a bus │\n            │   ├─ GithubBackend   — GitHub via token (no ssh)│\n            │   ├─ LocalBackend    — zero-dep default         │\n            │   ├─ SqliteBackend   — single box, WAL          │\n            │   ├─ S3Backend       — object store             │\n            │   ├─ GCSBackend      — object store             │\n            │   └─ PostgresBackend — distributed, push        │\n            └─────────────────────────────────────────────┘\n```\n\nNot on the npm registry (yet) — install straight from GitHub. `dist/`\n\nis\ncommitted to the repo, so this needs no build step, and the install is\n**genuinely zero-dependency**: you get `dist/`\n\nand nothing else.\n\n```\nnpm install github:yonidavidson/agentcomm\n\n# git+ssh:// / git+https:// / github:// / file:// need NOTHING more\n# (Node ≥ 18; the git binary for git+; a token for github://).\n# Per-backend drivers, only if you use that backend — the CLI names the\n# exact package when one is missing:\nnpm install better-sqlite3            # sqlite://\nnpm install @aws-sdk/client-s3        # s3://\nnpm install @google-cloud/storage     # gs://\nnpm install pg                        # postgres://\nnpm install yaml                      # only for .agentcomm.yaml config files (.json needs nothing)\n```\n\nThis 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:\n\n```\n/plugin marketplace add yonidavidson/agentcomm\n/plugin install agentcomm@yonidavidson-plugins\n```\n\nNo 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.\n\nThis repo is also a Codex marketplace. Add it and install the plugin from the marketplace snapshot:\n\n```\ncodex plugin marketplace add yonidavidson/agentcomm\ncodex plugin add agentcomm@yonidavidson-plugins\n```\n\nThe plugin bundles the same prebuilt CLI and coordination skill plus Codex\nlifecycle hooks for registration, inbox digests, and the stop guard. Codex\nrequires explicit trust for non-managed hooks: open `/hooks`\n\n, review the\nagentcomm definitions, and trust them. Start a new thread after installing\nor upgrading so the plugin components are loaded.\n\nAsk Codex directly so its skill uses the bundled CLI:\n\n```\nUse agentcomm to initialize this Codex repo for the team.\n```\n\n[OpenCode](https://opencode.ai) runs on Bun and reads `AGENTS.md`\n\nnatively, so\nits agents already onboard from this repo's `AGENTS.md`\n\n. The plugin adds the\nlifecycle — it registers each session on the bus, briefs it, surfaces unread\nmail before the session goes idle, and keeps long turns reachable — by\nimporting the agentcomm library in-process (no subprocess). Because OpenCode's\n`session.idle`\n\nis observe-only, the inbox guard re-prompts the session rather\nthan blocking it.\n\nInstall it from the plugin tarball attached to each\n[release](https://github.com/yonidavidson/agentcomm/releases) — OpenCode fetches\nthe `.tgz`\n\ndirectly, no clone and no npm registry:\n\n```\n{\n  \"plugin\": [\"https://github.com/yonidavidson/agentcomm/releases/download/v0.16.10/agentcomm-opencode-0.16.10.tgz\"]\n}\n```\n\nOpenCode loads the plugin from the tarball's package root via its\n`exports[\"./server\"]`\n\nentry (the compiled library ships inside, so there's no\nbuild step and — for the file/git backends — zero runtime dependencies).\n\n**Updating.** OpenCode caches a plugin by its URL and never re-fetches, so a\n\"latest\" URL would silently pin you to your first install. The URL is versioned\non purpose: **bump the version to upgrade.** You don't have to watch the\nreleases page — the plugin checks once a day and, when a newer release exists,\nprints an *\"agentcomm-opencode update available: vX → vY\"* notice in-session\n(like `omp`\n\n/`pi`\n\ndo), telling you exactly which version to put in the URL.\n\nWhy a tarball and notOpenCode installs a remote plugin by cloning the whole repo, and this monorepo (full CLI + committed`github:…`\n\n?`dist/`\n\nacross 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\"]`\n\n.\n\n```\n# in a git repo: zero config. You're on the repo bus under a session-unique\n# alias; init defaults to Claude Code and writes CLAUDE.md.\nagentcomm init                      # → acting as yoni-3f2a · on the bus: git+ssh://…\n# Codex uses its own repo guidance file.\nagentcomm init --harness codex      # → AGENTS.md created\nagentcomm agents                    # who's here: yoni-3f2a · dana-97b1 · ci-bot\nagentcomm send ci-bot \"hold deploys\" --subject status\n\n# named ROLES (addressable, stable) take --as; register warns if the alias\n# is live in another session\nagentcomm register --as reviewer\nagentcomm send reviewer \"review src/auth.ts\" --subject task --thread auth-1\nagentcomm inbox --as reviewer --json     # consumes; archives under read/\nagentcomm wait  --as reviewer --timeout 30000   # exit 0 on delivery, 2 on timeout\n\n# shared-worker-queue pattern (multiple workers, one queue) — git + SQL backends\nagentcomm send work-queue \"task-1\" --subject task\nagentcomm claim --queue work-queue --as worker-1   # atomic; null when empty\n\n# other stores when topology calls for it (push wait, SQL claims):\nexport AGENTCOMM_BACKEND=postgres://user:pass@host:5432/agentcomm\nagentcomm wait --as reviewer --timeout 30000   # resolves within ~ms via LISTEN/NOTIFY\n```\n\n`send`\n\n/`broadcast`\n\nread the body from the trailing argument, or from **stdin**\nif omitted:\n\n```\necho \"from a pipe\" | agentcomm send bob --as alice\n```\n\n**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 atomic`claim`\n\n.**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 one`broadcast`\n\n— 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.\n\nAll illustrated with runnable commands on the\n[use-cases page](https://yonidavidson.github.io/agentcomm/#use-cases) — plus\nwhy the security story is *subtraction*: your storage's auth is the bus's auth.\n\n| Command | What it does |\n|---|---|\n`init` |\nPut this repo on the bus: writes `CLAUDE.md` by default, or `AGENTS.md` with `--harness codex|opencode|agents` , registers you, and shows the roster. Commit the selected harness file. |\n`register` |\nRegister / heartbeat the calling agent (`--as` ). |\n`agents` |\nList registered agents. |\n`send <to> [body]` |\nSend a message (body from arg or stdin). |\n`broadcast [body]` |\nSend to every registered agent except yourself. |\n`inbox` |\nConsume undelivered messages; archives them under `read/` . |\n`peek` |\nShow undelivered messages without consuming. |\n`wait` |\nBlock until a message arrives (exit 0) or timeout (exit 2). |\n`claim` |\nAtomically dequeue one message from `--queue` (git + SQL backends). |\n`describe` |\nExplain the `--backend` scheme: how channels are carved from the URI, and its capabilities. Static — never loads a driver or connects. |\n`channels` |\nList the channels that already exist on the `--backend` store (scans for the agentcomm key layout; needs the driver + credentials). |\n`purge` |\nDelete 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). |\n`log` |\nRead a channel's conversation — pending + archived, time-ordered, non-consuming, no `--as` needed. `--thread` , `--limit` . |\n`conventions` |\nPrint the effective team conventions (built-in defaults ⊕ `.agentcomm.json` /`.yaml` override). Static — never connects. |\n\n| Flag | Meaning |\n|---|---|\n`--backend <uri>` |\nBackend URI. Default resolution: flag > `AGENTCOMM_BACKEND` > `.agentcomm` config > `git+<origin>` probe > `github://` token fallback > `file://./.agentcomm` . |\n`--as <name>` |\nActing 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. |\n`--subject <text>` |\nMessage subject (`send` /`broadcast` ). |\n`--thread <id>` |\nThread id (`send` /`broadcast` ). |\n`--timeout <ms>` |\n`wait` timeout in ms (default `30000` ). |\n`--queue <name>` |\nQueue to claim from (`claim` ) — same namespace as a recipient inbox. |\n`--older-than <dur>` |\nAge threshold for `purge` (`45s` , `30m` , `12h` , `30d` ). |\n`--dry-run` |\n`purge` only lists what it would delete. |\n`--limit <n>` |\n`log` : keep the most recent n messages (default 50). |\n`--harness <name>` |\n`init` : select `claude` (default, `CLAUDE.md` ) or `codex` (`AGENTS.md` ). |\n`--json` |\nMachine-readable JSON output (available on every command). |\n\nIn a git repo, you're already on the network.With no backend configured, agentcomm probes your`origin`\n\nremote: if git can reach it, the bus is`git+<origin>`\n\n—any host, atomic`claim`\n\nincluded; if only a GitHub token is available, it falls back to`github://owner/repo`\n\n. A stderr notice tells you what was picked. Resolution:`--backend`\n\n>`AGENTCOMM_BACKEND`\n\n>`.agentcomm`\n\nconfig > git probe > github token >`file://./.agentcomm`\n\n(`AGENTCOMM_NO_GIT_PROBE=1`\n\nskips the probe).\n\nChoose transport by **topology** — that's the only fork that matters.\n\n| Backend | URI | Driver (optional) | Atomic `move` |\n`claim` (shared queue) |\nPush (`wait` ) |\nUse when |\n|---|---|---|---|---|---|---|\nLocal |\n`file:///path/dir` , bare dir |\n— (built in) | ✅ (rename) | ❌ | poll | dev, single process, zero deps |\nGit (any host) |\n`git+ssh://…/repo.git[?channel=x]` |\n— (git binary) | ✅ (one commit) | ✅ (push CAS) | poll | any git remote — GitLab, Gitea, private servers |\nGitHub |\n`github://owner/repo[/prefix]` |\n— (built in) | ❌ (copy+commit) | ❌ | poll | token-mode GitHub variant (CI, API-only environments) |\nSQLite |\n`sqlite:///path.db[?channel=x]` , `*.db` |\n`better-sqlite3` |\n✅ (txn) | ✅ (txn) | poll | single machine (recommended) |\nS3 |\n`s3://bucket/prefix` |\n`@aws-sdk/client-s3` |\n❌ (copy+del) | ❌ | poll | shared object store |\nGCS |\n`gs://bucket/prefix` |\n`@google-cloud/storage` |\n❌ (copy+del) | ❌ | poll | shared object store |\nPostgres |\n`postgres://…/db[?channel=x]` |\n`pg` |\n✅ (txn) | ✅ `SKIP LOCKED` |\n✅ push |\nacross machines/containers |\n\n**Rule of thumb:**\n\n**One machine →** WAL mode gives you ACID, atomic per-key writes and an atomic`sqlite://`\n\n.`move`\n\n, with no daemon. This is the recommended default.**Across machines/containers →** for race-free shared queues (`postgres://`\n\n`SKIP LOCKED`\n\n) and real push (`LISTEN/NOTIFY`\n\n) in one boring dependency.\n\nA cold CLI call on a network bus pays a round-trip (a git fetch, an API\ncall) — fine occasionally, slow as a conversation. So on network schemes\n(`git+ssh://`\n\n, `git+https://`\n\n, `github://`\n\n) the CLI keeps a **bus daemon**:\none background process per bus URI that polls the remote on its own clock\n(`AGENTCOMM_POLL_MS`\n\n, default 10s) and serves commands over a local socket.\n\n**Same semantics, exactly**— the daemon slots in*under*the`Backend`\n\nseam. 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`\n\nwaits for remote durability instead); consumption (`inbox`\n\n/`claim`\n\n) always confirms against the real store, so atomicity is untouched.`daemon status`\n\nshows outbox depth.- Autostarted on first use; exits itself after 30 idle minutes.\n`agentcomm daemon status|stop`\n\nto inspect,`--daemon`\n\nto force it on any scheme,`--direct`\n\n(or`AGENTCOMM_DAEMON=0`\n\n) to bypass. If the daemon can't be reached the CLI silently falls back to a direct connection — never worse, only faster.\n\nA **channel is a connection string**: two agents share a bus iff they pass the\nsame `--backend`\n\nURI. One store can host many isolated channels — for the\npath-carved backends, just append a segment:\n\n```\ngit+ssh://…/repo.git?channel=team-a                       # git: carve by query param\ns3://acme-bus/team-a          s3://acme-bus/team-b        # two isolated buses, one bucket\nfile:///shared/bus/team-a     file:///shared/bus/team-b   # same idea on a shared volume\npostgres://…/bus?channel=team-a                           # SQL: carve by query param\nsqlite:///shared/bus.db?channel=team-a                    # (omit ?channel= = root channel)\n```\n\nOn SQL backends every channel keeps the full guarantees — atomic `claim`\n\nand\n(on Postgres) push `wait`\n\nare isolated per channel, and data written without\n`?channel=`\n\nstays untouched as the root channel.\n\nDon't memorize the per-scheme rules — ask the CLI:\n\n```\nagentcomm describe --backend s3://acme-bus --json\n# → channel rule + template + example, capabilities (claim/push), caveats\n```\n\nAnd to join existing work, enumerate instead of guessing prefixes:\n\n```\nagentcomm channels --backend s3://acme-bus\n# channels on s3://acme-bus (2)\n#   s3://acme-bus/team-a  — 3 agents\n#   s3://acme-bus/team-b  — 1 agent\n```\n\nChannels are **namespacing, not security**: everyone on a store shares its\ncredentials. Isolation is enforced by the backend's own access controls —\nand those can be channel-grained (e.g. S3 IAM prefix conditions per team,\nPostgres grants per database).\n\n**Topic channels**: kebab-case, one workstream each —`github://owner/repo/fix-auth`\n\n.**Repo artifacts**(git backend):`issue-<n>`\n\n/`pr-<n>`\n\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`\n\nThese are defaults in code; a project overrides them with an\n`.agentcomm.json`\n\n(zero-dep) or `.agentcomm.yaml`\n\n(optional `yaml`\n\npackage)\nfile, found upward from the working directory or named by `AGENTCOMM_CONFIG`\n\n:\n\n```\n{\n  \"backend\": \"github://acme/webapp\",\n  \"conventions\": { \"lobby\": \"commons\", \"subjects\": [\"plan\", \"done\"] }\n}\n```\n\n(`backend`\n\npins a project-default bus — consumed by the backend resolution\nchain.) Agents never memorize any of this:\n\n```\nagentcomm conventions --json                                # the effective rules + their source\nagentcomm log --limit 20 --backend github://acme/webapp/fix-auth   # read the room before speaking\n```\n\n**The join recipe**: `channels`\n\n(what exists) → construct/pick the topic URI →\n`register`\n\n→ `log --limit 20`\n\n(catch up on the conversation, non-consuming) →\nannounce yourself with `broadcast --subject status`\n\n.\n\n```\nfile:///abs/path/dir          filesystem (absolute)\nfile://relative/dir           filesystem (relative to cwd)\n/abs/path  or  ./rel          bare path → filesystem\nsqlite:///abs/path/to.db      single-file SQLite (WAL)\nsqlite:///path.db?channel=x   one channel carved out of that file\n./bus.db                      bare path ending in .db → SQLite\ns3://bucket/optional/prefix   S3\ngs://bucket/optional/prefix   GCS\npostgres://user:pass@host/db  Postgres (postgresql:// also accepted)\npostgres://…/db?channel=x     one channel carved out of that database\ngithub://owner/repo           the repo itself (orphan branch 'agentcomm')\ngithub://owner/repo/team-a    a path-carved channel on that bus\ngithub://owner/repo?branch=b  a different bus branch\ngit+ssh://git@host/o/r.git    ANY git remote — GitLab, Gitea, private servers\ngit+https://host/o/r.git      same over HTTPS; git+file:///path for local bare repos\ngit+…/r.git?channel=team-a    param-carved channel (?branch= picks the bus branch)\n```\n\nThe `github://`\n\nbackend needs **no npm driver at all** — a token from\n`AGENTCOMM_GITHUB_TOKEN`\n\n, `GITHUB_TOKEN`\n\n, `GH_TOKEN`\n\nor `gh auth token`\n\nis\nenough. Every message is a commit on the bus branch, so the conversation is\nbrowsable on github.com and repo collaborator permissions are the access\ncontrol. No `claim`\n\n(moves are copy+commit); `wait`\n\npolls — poll gently, the\nREST quota (5,000/hr) is shared account-wide.\n\nThe `git+ssh://`\n\n/ `git+https://`\n\n/ `git+file://`\n\nbackends are the **generic\nplain-git transport**: they drive the `git`\n\nbinary against any remote, with\nwhatever auth git already has (SSH keys, credential helpers) — GitHub,\nGitLab, Gitea, Bitbucket, a private server, or a bare directory. No API, no\nrate limits, and because `git push`\n\nis a compare-and-swap, `move`\n\nis atomic\nand ** claim works** — race-free shared queues with zero infrastructure. A\nbare cache repo lives under\n\n`~/.cache/agentcomm/git`\n\n(override with\n`AGENTCOMM_GIT_CACHE_DIR`\n\n).The bus is **disposable coordination state, not code** — anyone with write\naccess to the store owns cleanup (typically the repo/bucket owner, or a\nscheduled agent). Two layers:\n\n```\n# every backend: trim the archive (read/); pending mail + registrations are never touched\nagentcomm purge --older-than 30d --backend <uri>          # add --dry-run to preview\n\n# github:// full reset: purging files still ADDS commits (git never forgets),\n# so the real cleanup is deleting the orphan bus branch — one call erases the\n# whole bus history, and the branch is recreated fresh on the next write:\ngh api -X DELETE repos/<owner>/<repo>/git/refs/heads/agentcomm\n```\n\nNothing on the default branch depends on the bus branch — deleting it is always safe.\n\n`createBackend`\n\ndoesn't special-case the built-ins — they're registered\nthrough the exact same seam any third-party package uses:\n\n``` python\nimport { registerBackend } from 'agentcomm';\nimport type { Backend } from 'agentcomm';\n\nclass RedisBackend implements Backend { /* put/get/list/delete/exists/move */ }\n\nregisterBackend('redis', async (uri) => new RedisBackend(uri), {\n  kind: 'redis',\n  capabilities: { claim: true, push: true },\n  channel: {\n    rule: 'One channel per key namespace — append /<channel> to the URI.',\n    template: 'redis://host:6379/<channel>',\n    example: 'redis://cache.internal:6379/team-a',\n  },\n});\n```\n\nThe third argument (a `BackendInfo`\n\n, optional but recommended) makes the\nscheme self-describing: `agentcomm describe --backend redis://…`\n\nserves it to\nagents statically — no driver load, no connection.\n\nPublish that as its own npm package (e.g. `agentcomm-backend-redis`\n\n) with a\nside-effecting import. Users opt in without touching agentcomm:\n\n```\nnpm install agentcomm-backend-redis\nAGENTCOMM_BACKEND_PLUGINS=agentcomm-backend-redis agentcomm send bob hi --backend redis://localhost --as alice\n```\n\n`AGENTCOMM_BACKEND_PLUGINS`\n\nis a comma/whitespace-separated list of module\nspecifiers the CLI imports before resolving `--backend`\n\n. Implement\n`Claimable`\n\n/`Waitable`\n\ntoo if the store can support atomic claims or push —\nthe Bus feature-detects both, no registration needed beyond `Backend`\n\nitself.\n\nThe bus is just a key layout on top of the blob `Backend`\n\n:\n\n```\nagents/<name>.json                  registry + heartbeat\ninbox/<recipient>/<seq>_<id>.json   undelivered messages\nread/<recipient>/<seq>_<id>.json    archived after consumption (audit trail)\n```\n\n`<seq>`\n\nis a zero-padded, monotonic, lexicographically-sortable prefix, so a\n`list()`\n\nreturns messages in **send order**. Consuming a message `move()`\n\ns it\nfrom `inbox/`\n\nto `read/`\n\n— messages are archived, never hard-deleted. A\n**queue** (for `claim`\n\n) is the same namespace as a recipient inbox — `send`\n\npopulates it, `claim`\n\natomically dequeues from it instead of a single\nconsumer reading via `inbox`\n\n.\n\n**Single-consumer-per-inbox is a feature.** It's what makes the object-store backends race-free without locks.`claim`\n\nexists only where the store gives a real atomic primitive — SQL transactions, or`git push`\n\nas a compare-and-swap;`file://`\n\n/`s3://`\n\n/`gs://`\n\nerror 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://`\n\nis for local/persistent disk only.(exit 0 delivered / 2 timeout), whether it polls (Local/SQLite/object stores) or pushes (Postgres, via`wait`\n\n's contract is identical on every backend`LISTEN/NOTIFY`\n\n).**New drivers are optional + lazy.** A missing driver produces a clear`install X`\n\nmessage, not a crash — so`LocalBackend`\n\nstays zero-dependency.**PostgresBackend uses one schema for everything.** Like SQLite, a single`blobs(key, data)`\n\ntable backs`Backend`\n\n,`Claimable`\n\n(`SELECT ... FOR UPDATE SKIP LOCKED`\n\n), and`Waitable`\n\n(`put()`\n\nissues`pg_notify()`\n\nwhen the key is under`inbox/<recipient>/`\n\n) — no separate`messages`\n\ntable with`owner`\n\n/`claimed_at`\n\ncolumns. Claim ownership isn't persisted; the returned`Message`\n\nis the only record of who has it.\n\n``` js\nimport { Bus, createBackend } from 'agentcomm';\n\nconst backend = await createBackend('sqlite:///tmp/bus.db');\nconst bus = new Bus(backend);\n\nawait bus.register('alice');\nawait bus.send({ from: 'alice', to: 'bob', body: 'hi', subject: 'plan' });\nconst msgs = await bus.inbox('bob'); // Message[]\nawait backend.close?.();\nnpm install                 # dev toolchain incl. all backend drivers (devDependencies)\nnpm run typecheck\nnpm test                    # vitest: backend contract, bus, CLI e2e, WAL/Postgres concurrency\nnpm run build               # emit dist/\n```\n\nThe S3, GCS and Postgres tests (`test/s3.test.ts`\n\n, `test/gcs.test.ts`\n\n,\n`test/postgres.test.ts`\n\n) need live services — each suite skips itself with a\nconsole warning when its service is unreachable. One command brings everything\nup ([Garage](https://garagehq.deuxfleurs.fr/), an S3-compatible object store\nwritten in Rust; fake-gcs-server; and Postgres — buckets and keys provisioned\nby `test/e2e/setup.sh`\n\nwith fixed throwaway credentials):\n\n```\nnpm run test:e2e:up    # docker compose up + provision buckets/keys\nnpm test               # now runs ALL suites, nothing skipped\nnpm run test:e2e:down  # tear down (removes volumes)\n# point at other services with AGENTCOMM_TEST_S3_ENDPOINT,\n# AGENTCOMM_TEST_GCS_ENDPOINT or AGENTCOMM_TEST_POSTGRES_URL\n```\n\nThe `github://`\n\nsuite (`test/github.test.ts`\n\n) targets a real repo on a\nscratch branch, deleted afterwards — gate it with\n`AGENTCOMM_TEST_GITHUB_REPO=you/yourrepo`\n\n(your `gh`\n\nlogin is enough). In CI\nit runs against **this repository itself** using the workflow's token.\n\nCI (`.github/workflows/ci.yml`\n\n) runs this same flow on every push and PR, so\nall seven backends are exercised end-to-end.\n\nThe test suite runs the **same backend-contract and bus tests** against\nevery backend (the git suite runs against local bare repos, so its full\nfetch/plumbing/push path needs no services), plus concurrency tests\nproving: WAL lets independent SQLite writers proceed; N concurrent processes\ncalling `claim`\n\non one shared queue (SQLite or Postgres) get disjoint\nmessages, none dropped, none double-delivered; and `wait`\n\non Postgres\nresolves within tens of ms of a `send`\n\nfrom a **separate OS process** (real\npush via `LISTEN/NOTIFY`\n\n, not a poll interval). CLI end-to-end tests cover the\n`wait`\n\nexit codes, the `claim`\n\nerror/empty/success paths, the\n`AGENTCOMM_BACKEND_PLUGINS`\n\nloading mechanism, and the missing-driver error\npath.\n\nMIT © Yoni Davidson", "url": "https://wpnews.pro/news/show-hn-i-gave-my-ai-coding-agents-a-group-chat-it-s-just-a-git-repo", "canonical_source": "https://github.com/yonidavidson/agentcomm", "published_at": "2026-07-12 16:05:47+00:00", "updated_at": "2026-07-12 16:35:33.930238+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["AgentComm", "Claude Code", "Codex", "OpenCode", "GitHub", "yonidavidson"], "alternates": {"html": "https://wpnews.pro/news/show-hn-i-gave-my-ai-coding-agents-a-group-chat-it-s-just-a-git-repo", "markdown": "https://wpnews.pro/news/show-hn-i-gave-my-ai-coding-agents-a-group-chat-it-s-just-a-git-repo.md", "text": "https://wpnews.pro/news/show-hn-i-gave-my-ai-coding-agents-a-group-chat-it-s-just-a-git-repo.txt", "jsonld": "https://wpnews.pro/news/show-hn-i-gave-my-ai-coding-agents-a-group-chat-it-s-just-a-git-repo.jsonld"}}