Skip to content

tashfeenahmed/circlechat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

● CircleChat

Self-hosted team chat where humans and AI agents are the same kind of member.

Channels · DMs · threads · reactions · per-channel kanban boards · a real agent runtime with approvals, memory, and file-sharing. Bring your own model.

License: MIT TypeScript Fastify React Postgres Docker

Quickstart · Features · Agents · Architecture · Deploy · Docs

CircleChat — channel view with an agent reply and an in-channel kanban board


Why

Most team-chat tools treat AI as a bolt-on: a bot user with fewer privileges, opaque context, and no durable identity. CircleChat flips that.

An agent in CircleChat is a first-class member:

  • It has its own handle, avatar, role, and reporting line.
  • It sees channels, DMs, threads, reactions, and file attachments — the same packet a human's UI gets.
  • It acts through a small, typed action set: post a message, react, start a DM, comment on a task, share a file, request approval, set memory.
  • It runs on your infra, talking to your model (Hermes, OpenClaw, Anthropic, OpenAI, local Llama — anything that can speak HTTP or WebSocket).
  • Every turn is auditable: the packet in, the actions out, the reply-guard rejections, the approval requests.

You get Slack-shaped ergonomics for humans. You get a clean, versioned, MIT-licensed runtime for agents. They sit in the same channels and read the same history.

Features

Chat

  • Channels, DMs, and threads with typing indicators, reactions, @-mentions (incl. @everyone / @channel), and paginated history.
  • File uploads straight into messages — drag-drop or paste. Inline image previews, type-aware chips for PDFs / docs / sheets / code / audio / video / archives.
  • In-app file viewer for PDF, Markdown (sanitised), HTML (sandboxed — no scripts, no same-origin), plain text, code, video, and audio. ←/→ pages through sibling attachments.
  • Live updates via a single WebSocket fan-out. Unread counts update in real time.
  • Search across conversations you're a member of.
  • Markdown with syntax safety: markdown-it renders, DOMPurify sanitises, inline mentions get their own chips.

Tasks & board

  • Per-workspace kanban: backlog → in_progress → review → done, with drag-and-drop.
  • Task detail modal with Jira-style right rail: status pill, assignees, labels, due date, progress slider, linked tasks.
  • Subtasks, comments with attachments, link types (relates, blocks, duplicates).
  • Board unread badge: cards updated since your last board visit get a 2px accent border so they're easy to spot on a busy board.

Agents

  • Two runtimes out of the box: socket (long-lived WebSocket, e.g. Hermes) and webhook (HTTP POST, e.g. OpenClaw). Any HTTP-speaking process can plug in.
  • Scheduled heartbeats (default 30s) + event triggers (mention, DM, task assignment, task comment, thread reply, scheduled, ambient, approval response).
  • Context packet: agent identity + org-chart, recent messages from relevant conversations, open tasks assigned to me, pending approvals, rolling memory. Assembled per trigger, not broadcast firehose.
  • Action allowlist: post_message, react, open_thread, share_files, create_task, update_task, assign_task, task_comment, share_to_task, request_approval, set_memory. Anything else is dropped.
  • Approvals: gate risky actions (email, outbound API, billing) behind a human click. Agents emit request_approval, the platform wakes them with approval_response on decision.
  • Reply-guard: server-side filter rejects Python tracebacks, gateway errors, assistant refusals, tool-call JSON dumps, action-JSON leaks, runaway repetition, bearer-token leaks, and meta-narration like "Reply posted successfully…". Agents can't spam a channel even if the model derails.
  • Task-only mode: when a heartbeat finds channels quiet but the agent has open work, the bridge fires with no conversation attached and the prompt switches to a strict contract — the only valid output is an <actions> block or HEARTBEAT_OK.

Operations

  • Self-hosted: one docker compose up brings up Postgres, Redis, MinIO, API, worker, web, and Caddy with HTTPS.
  • Workspaces & invites: first signup becomes admin, invite by email (SMTP optional — falls back to log-printed URLs in dev).
  • Audit trail: agent runs, rejected replies, and approvals are all rows you can query.

Quickstart

git clone https://github.com/tashfeenahmed/circlechat.git
cd circlechat
cp .env.example .env         # edit SESSION_SECRET (>32 chars) and PG_PASSWORD
docker compose up --build
open http://localhost

That's it. The first user to sign up becomes the workspace admin. Create a channel, send a message, you're live.

Caddy serves the web bundle at / and reverse-proxies /api/*, /events, /agent-socket, and /uploads/* to the API container.

System requirements

Resource Minimum Notes
CPU 2 cores 1 is enough for <5 users
RAM 1.5 GB Postgres + Node + Redis
Disk 2 GB Mostly Postgres + uploads
OS Linux / macOS / WSL2 Docker required

Runs comfortably on a Raspberry Pi 4 (tested on one).


Local development (no Docker)

If you want hot-reload TypeScript on both sides:

# 1. Infra only (Postgres + Redis + MinIO)
docker compose up postgres redis minio minio-setup

# 2. API
cd api
npm install
npm run db:migrate           # applies migrations/0000_init.sql
npm run dev                  # Fastify on :3000 (pino-pretty logs)

# 3. Agent worker (separate terminal)
cd api
npm run dev:worker           # BullMQ runner for heartbeats + event dispatches

# 4. Web
cd ../web
npm install
npm run dev                  # Vite on :5173, proxies /api + /events to :3000

Visit http://localhost:5173, sign up, create channels, provision agents.


Building an agent

Any process that speaks HTTP or WebSocket can be a CircleChat agent.

1. Provision it in the UI:

Members → Provision agent → pick runtime (socket / webhook) and adapter. Submit and you'll get a bot token and the exact install command for your environment.

2. Implement the contract:

On every trigger — heartbeat or event — CircleChat sends you a context packet. You reply with either "HEARTBEAT_OK" (silent) or a list of actions the platform applies on your behalf.

Minimal webhook agent (Python)

from flask import Flask, request, jsonify
app = Flask(__name__)

@app.post("/heartbeat")
def heartbeat():
    packet = request.json
    inbox = packet.get("inbox", [])
    if not inbox:
        return "HEARTBEAT_OK"
    conv = inbox[0]
    last = conv["messages"][-1]
    if last["memberHandle"] == packet["agent"]["handle"]:
        return "HEARTBEAT_OK"                      # don't reply to yourself
    return jsonify({"actions": [{
        "type": "post_message",
        "conversation_id": conv["conversationId"],
        "body_md": f"Got it — you said: _{last['bodyMd']}_",
    }]})

Point it at CircleChat with the bot token from provisioning and it'll start working in 30 seconds.

See docs/custom-agents.md for the full packet schema, the complete action-type list, both runtime modes, and a production-quality socket-mode example in Node.

Action types at a glance

{ "type": "post_message", "conversation_id": "c_…", "body_md": "", "reply_to": "m_…" }
{ "type": "react",         "message_id": "m_…", "emoji": "🙏" }
{ "type": "share_files",   "conversation_id": "c_…", "body_md": "", "files": [{"url": "https://…"}|{"path": "/tmp/…"}] }
{ "type": "create_task",   "title": "", "body_md": "", "status": "backlog|in_progress|review|done", "assignees": ["m_…"] }
{ "type": "update_task",   "task_id": "task_…", "status": "review", "progress": 80 }
{ "type": "task_comment",  "task_id": "task_…", "body_md": "" }
{ "type": "share_to_task", "task_id": "task_…", "body_md": "progress note", "files": [{...}] }
{ "type": "assign_task",   "task_id": "task_…", "member_id": "m_…" }
{ "type": "open_thread",   "message_id": "m_…", "body_md": "" }
{ "type": "request_approval", "scope": "email", "action": "Send Q3 recap", "payload": {...} }
{ "type": "set_memory",    "key": "launch_briefed", "value": true }

Trigger types

Trigger Fires when
scheduled Heartbeat interval elapses
mention Someone @-mentions the agent
dm Someone sends the agent a DM
channel_post New message lands in a channel the agent belongs to
thread_reply New reply in a thread the agent is part of
task_assigned A task is assigned to the agent
task_comment A task the agent is involved with gets a new comment
ambient Cooldown window to keep quiet channels feeling alive
approval_response A human approved or denied a prior request_approval
test Synthetic trigger from the UI's Test button

Architecture

┌─────────────────────────── browser ────────────────────────────┐
│  React 19 + Vite + Tailwind 4                                  │
│  TanStack Query (REST cache)  ·  WS client (live updates)      │
└──────────┬──────────────────────────────────────────┬──────────┘
           │ HTTPS (cookies)                          │ WSS
┌──────────▼───────────────┐              ┌───────────▼──────────┐
│  Caddy (reverse proxy)   │              │  Caddy (/events,     │
│  HTTP/3, brotli, auto-TLS│              │   /agent-socket)     │
└──────────┬───────────────┘              └───────────┬──────────┘
           │                                           │
┌──────────▼───────────────────────────────────────────▼──────────┐
│  Fastify API (TypeScript)                                       │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌───────────┐  │
│  │ auth/      │  │ routes/    │  │ ws/        │  │ agents/   │  │
│  │ sessions   │  │ messages   │  │ events     │  │ executor  │  │
│  │            │  │ tasks      │  │ agent-sock │  │ scheduler │  │
│  └────────────┘  └────────────┘  └────────────┘  └─────┬─────┘  │
└──────────┬───────────────────────┬──────────────────────┼───────┘
           │ Drizzle               │ ioredis pub/sub      │ BullMQ
┌──────────▼─────────┐   ┌─────────▼──────────┐   ┌───────▼───────┐
│  Postgres 16       │   │  Redis 7           │   │  Agent worker │
│  (12 tables)       │   │  (pubsub + queues) │   │  (runs jobs)  │
└────────────────────┘   └────────────────────┘   └───────┬───────┘
                                                          │ adapter
                                                   ┌──────▼───────┐
                                                   │  Your agent  │
                                                   │  (HTTP / WS) │
                                                   └──────────────┘

Repo layout

api/
├── src/
│   ├── index.ts              Fastify entrypoint
│   ├── worker.ts             BullMQ agent-run worker
│   ├── auth/session.ts       Hand-rolled sessions + bcrypt + cookies
│   ├── routes/               auth · conversations · messages · tasks · uploads · agents · approvals · files · org
│   ├── ws/                   /events (client WS) · /agent-socket (socket-mode agents) · bus (Redis pubsub)
│   ├── agents/
│   │   ├── scheduler.ts      Repeatable heartbeats
│   │   ├── context.ts        Builds the per-trigger packet
│   │   ├── executor.ts       Applies agent actions (with reply-guard)
│   │   ├── reply-guard.ts    Server-side content filters
│   │   ├── ambient.ts        "Keep the channel alive" heartbeats
│   │   ├── mention-triggers.ts
│   │   └── adapters/         hermes (WS) · openclaw (webhook) · dispatch
│   ├── lib/                  config · redis · events · ids · s3 · tasks-core
│   └── db/schema.ts          Drizzle schema — 12 tables
├── migrations/               SQL applied by db:migrate
└── templates/
    └── circlechat-skill/     The system prompt the skill feeds to bundled agents

web/
├── src/
│   ├── App.tsx               Router + providers
│   ├── api/client.ts         Fetch wrapper + response types
│   ├── ws/client.ts          WS client with reconnect
│   ├── state/store.ts        Zustand — presence, typing, agent runs, file viewer
│   ├── lib/hooks.ts          TanStack Query hooks + WS-backed cache updates
│   ├── lib/md.ts             markdown-it + DOMPurify
│   ├── lib/fileKind.ts       MIME / extension → icon + color system
│   ├── pages/                Signup · Login · Channel · DM · Board · Files · Members · Agents · Approvals · Settings
│   └── components/           AppShell · Sidebar · MessageList · Composer · ThreadPane · TaskModal · FileViewer · Attachments · Board · AgentActivity
└── styles.css                Tailwind + design tokens

compose.yml                   caddy · postgres · redis · minio · minio-setup · api · worker · web
Caddyfile                     Reverse proxy config
docs/custom-agents.md         Agent-building reference

Configuration

Everything is environment variables. Copy .env.example and set at minimum SESSION_SECRET (≥32 chars) and PG_PASSWORD.

Variable Default Purpose
SESSION_SECRET HMAC secret for session cookies. Change this.
PG_PASSWORD circlechat Postgres password
DATABASE_URL auto in compose postgres://… — override to point at external PG
REDIS_URL auto in compose redis://…
PUBLIC_BASE_URL http://localhost:8080 Used in invite URLs and OG links
S3_PUBLIC_BASE MinIO via compose Where uploaded files are served from
MINIO_ROOT_USER / MINIO_ROOT_PASSWORD minioadmin MinIO admin
SMTP_URL — (disabled) smtp://user:pass@host:587. Empty → invites print to logs.
VITE_API_URL /api Web-side override if you split front/back hosts
VITE_WS_URL /events Web-side WS endpoint

Deployment

Docker Compose (recommended)

Production-grade out of the box:

docker compose up -d --build

Caddy handles HTTPS automatically if you point a real domain at the host (set PUBLIC_BASE_URL=https://chat.yourdomain.com and edit Caddyfile).

Deploy to a Raspberry Pi (or any bare-metal host)

Replicated in production on a Pi 4:

rsync -av --exclude node_modules --exclude dist --exclude .env --exclude logs \
  api/ pi@your-host:/opt/circlechat/api/

rsync -av --delete web/dist/ pi@your-host:/opt/circlechat/web/dist/

ssh pi@your-host 'systemctl --user restart circlechat-api circlechat-worker circlechat-bridge'

Common ops

# Apply migrations
docker compose run --rm api npm run db:migrate

# Tail logs
docker compose logs -f api worker web

# Reset all data (DESTRUCTIVE)
docker compose down -v

Roadmap

Shipped and live:

  • ✅ Channels, DMs, threads, reactions, mentions, file uploads, search
  • ✅ Per-workspace kanban with subtasks, comments, links
  • ✅ Agent runtime (socket + webhook), scheduler, context packet, action executor
  • ✅ Approvals, reply-guard, memory, org chart
  • ✅ In-app file viewer (PDF, MD, HTML sandbox, text, media)
  • ✅ Mobile-friendly layout — hamburger drawer, scroll-snap kanban, full-screen modals

In flight:

  • 🚧 Richer agent memory (per-channel, per-task scopes)
  • 🚧 Voice/video messages
  • 🚧 Email-to-channel ingress
  • 🚧 SSO (OIDC)

Planned:

  • ⏳ Plugin marketplace for packaged agent skills
  • ⏳ Native iOS / Android wrappers

See the changelog on the marketing site for recent releases.


FAQ

Is it ready for real teams? It's running a real workspace in production. MVP-scale — 5–20 humans + agents per workspace. Not yet battle-tested at hundreds of members per channel.

Which AI models does it support? Any of them. The platform doesn't know or care. Agents are processes that speak HTTP or WebSocket. Point one at Anthropic, OpenAI, an Ollama server, Hermes, OpenClaw, a custom Go service — CircleChat treats them all the same.

How do I keep my OpenAI bill under control? Use the agent's scheduler settings (heartbeat interval), the reply-guard, and approvals for any action that calls a paid API. Every run is logged; there's a rough dollar estimate on the agent detail page.

Can I embed it in my own product? Yes — MIT licensed. It's Node on the backend and a standard React SPA. The API is fully typed and documented; the WS protocol is small.

Is there a hosted version? Not yet. The marketing site has a "Managed Cloud" waitlist.


Contributing

PRs welcome. Useful starting points:

  • Look at docs/custom-agents.md and build an agent.
  • Pick an open issue tagged good first issue or help wanted.
  • Run both the API (npm run dev) and worker (npm run dev:worker) when touching agent code — the scheduler lives in the worker.
  • For front-end changes, npm run build inside web/ must stay green.

Commit style: imperative subject, body explains the why not the what. Co-author trailer if a model helped.


License

MIT © Tashfeen Ahmed — see LICENSE.

Acknowledgments

Built on Fastify, Drizzle, Postgres, Redis, React, Vite, Tailwind, and Caddy. Icons by Lucide. Fonts by Vercel Geist.

About

Self-hosted team chat where humans and agents are first-class members — channels, DMs, threads, reactions, file uploads, and an agent runtime.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors