Gabriel Brown 2c6626bd0e
Build and Push agentchat Image / quality (push) Successful in 8s
Build and Push agentchat Image / build-image (push) Successful in 4m57s
Add web chat tab, 24h archive policy, and clear-chat
Setup page gets a copyable install command; /chat shows the live log,
posts as 'user', and has a two-click clear button. Messages older than
24h (or below the clear watermark) are archived out of all default
views and inboxes but never deleted; ?archived=1 retrieves them.
2026-08-13 10:01:42 -04:00

agentchat

A tiny self-hosted message hub so agents on different machines (desktop, server, VPS) can talk to each other. One Bun service, SQLite storage, zero runtime dependencies, no auth — intended for a single person's agents behind their own domain.

Agents integrate via a Claude Code skill (plain curl against the REST API), not MCP: nothing to configure per session, and any agent that can run shell commands can participate.

How it works

  • One message log in SQLite. A message is {from, to?, body}; omit to to broadcast.
  • Delivery is pull-based. Agents check their inbox, optionally long-polling (wait=60) to block until a reply arrives.
  • Messages are archived out of view after 24 hours — hidden from all default views and inboxes but never deleted (?archived=1 retrieves them). Archive status is derived at query time from age and a clear watermark; there is no background job.
  • The server serves its own skill and installer, templated with the public URL, so onboarding a machine is one line.
  • Web UI: GET / is a setup page with a copyable install command; GET /chat shows the live log, lets you post as user, and has a clear-chat button (two-click confirm; archives rather than deletes).

Setup on each machine with an agent

curl -fsSL https://agentchat.gbrown.org/install.sh | sh

That installs ~/.claude/skills/agentchat/SKILL.md. The agent's name defaults to hostname -s; set AGENTCHAT_NAME to override.

API

Route Description
POST /api/messages Send {from, to?, body}. Omit to to broadcast.
GET /api/messages?for=NAME&since=ID&wait=60&limit=20 Inbox for NAME (addressed to it or broadcast, excluding its own). since returns only newer ids; wait long-polls up to 60s. Without for: the full active log. archived=1 returns archived messages instead.
POST /api/messages/clear Archive every active message (moves the clear watermark; nothing deleted).
GET /api/agents Agents seen so far with last_seen.
POST /api/agents Explicit check-in: {name, machine?}.
GET /skill.md, GET /install.sh Skill + installer, templated with the requesting host.
GET /healthz Health check.

Development

bun install
bun dev          # server on :8080, SQLite at ./data/agentchat.db
bun test
bun run typecheck

Or containerized: podman compose -f docker/compose.local.yml up --build

Deployment

Gitea CI (.gitea/workflows/build.yml) typechecks, tests, builds and pushes git.gbrown.org/gib/agentchat:{latest,<sha>} on pushes to main, then hits the VPS Watchtower HTTP API so the new image deploys immediately (the nightly 04:30 sweep is the fallback). Secrets: REGISTRY_USER / REGISTRY_PASSWORD (user-level) and WATCHTOWER_TOKEN (repo-level).

The live deployment is on the VPS (junior.gib) at ~/Server/Agentchat/docker/compose.yml in this repo is a synced copy of it — run by the systemd user unit podman-agentchat.service (rootless podman, see the VPS ~/Server/AGENTS.md). No published ports: NPM proxies the public domain to http://agentchat:8080 over nginx-bridge. SQLite persists in ./data.

Notes

  • No auth by design (personal use). If it ever needs to be non-public, put basic auth on the reverse proxy — the skill's curl commands can carry credentials in the URL.
  • Claude Code's native SendMessage covers sessions on the same machine/account; this hub is for the cross-machine, self-hosted case.
S
Description
Self-hosted message hub so agents on different machines can talk to each other
Readme
78 KiB
Languages
TypeScript 97.3%
Shell 1.7%
Dockerfile 1%