parley is a compact, real-time instant-messaging backend in Go for humans and AI agents, built as a modular monolith where every message fans out over an internal event bus — the seam an AI agent joins as a first-class participant.
- Accounts & auth — signup / login, bcrypt, JWT with sliding refresh, per-IP rate limiting.
- Chat — 1-1 and group conversations, durable ordered messages, idempotent sends.
- Real-time — WebSocket delivery, with cursor resync for offline clients.
- Read state — persisted unread counts and live read receipts.
- Identities — member and sender names resolved across the module boundary.
Two axes: shared technical capabilities, and self-contained business modules.
| Area | Description |
|---|---|
internal/platform |
Shared capabilities, no business logic: auth, event bus, circuit breaker, WebSocket gateway, postgres / redis |
internal/user |
Accounts, auth, JWT; publishes a user-lookup contract |
internal/chat |
Conversations, messages, read receipts; split into command (write) and query (read) services |
- Durable log vs. push — a message is "sent" once persisted with a sequence number; WebSocket push is a best-effort accelerator, resynced by cursor on reconnect.
- CQRS — commands write and enforce invariants; queries read the stores directly.
- Contracts, not implementations — a module depends only on another's
apipackage, keeping dependencies acyclic.
Every WebSocket frame uses protocol version: 1; a connection's first
application frame is {"version":1,"type":"ready","data":{}}. After ready,
clients take each conversation's lastSeq as a fixed snapshot and page the
durable log with:
GET /chat/conversations/:id/messages?after_seq=<local>&until_seq=<snapshot>&limit=100
The bounded response contains items, nextSeq, untilSeq, and hasMore.
Clients buffer concurrent WebSocket frames while paging, then merge by
conversationId + seq; a gap or close code 1013 triggers the same recovery
flow. Omitting until_seq retains the original array response for older clients.
Go 1.25+, Docker (for PostgreSQL and Redis), and Task.
cp task.yaml.example Taskfile.yml # local runner config (git-ignored)
task dev # starts PostgreSQL + Redis, then runs the API on :8080Config is driven by environment variables with dev defaults (see internal/bootstrap/config.go); set DB_DSN and JWT_SIGN_KEY in any real deployment.
Personal reference implementation — not production-hardened, pre-1.0. Known gaps: no offline push, single-instance WebSocket gateway, tests pending.