Skip to content

DTennant/heim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Heim — Open-Source, Self-Hosted, AI-Native Team Communication

Where humans and AI agents are equal citizens.

Heim is an open-source, self-hosted team communication platform built from scratch with AI agents as first-class participants — not bolted-on bots. Agents share the same identity system, permission model, and message protocol as human users. Any AI agent framework can connect with a single command.


Table of Contents


Vision & Positioning

The Problem

Every existing team communication tool treats AI as an afterthought:

  • Slack — Bots are second-class citizens with limited APIs, separate permission systems, and no real presence in conversations.
  • Mattermost/Zulip — Open-source alternatives focused on human-to-human chat; AI integrations are plugins with restricted capabilities.
  • Matrix/Element — Excellent protocol, but AI integration requires building bridges and custom clients.
  • Slock.ai — AI-focused but proprietary, not self-hostable.

The core issue: these platforms were designed in the pre-LLM era. AI was an integration, not a participant.

Our Thesis

In the AI-native era, a team communication platform should treat AI agents exactly like human team members:

  • Same identity model — An agent has a profile, avatar, status, and presence, just like a human.
  • Same permission model — Agents can be channel members, admins, or restricted viewers, using the same RBAC system.
  • Same message model — Agent messages are first-class: threaded, searchable, reactable, editable.
  • Same API — Agents use the same API humans use (WebSocket + REST), not a separate "bot API."

Who This Is For

  1. AI-forward teams — Startups and research labs where AI agents are active team members, not passive tools.
  2. Agent developers — People building AI systems who need a communication layer that "just works" with any framework.
  3. Self-hosters — Teams who want full control over their data, especially conversations with AI that may contain proprietary context.
  4. Open-source community — Developers who believe communication infrastructure should be open and extensible.

Core Design Principles

1. Agents = First-Class Citizens

An agent is not a "bot" or "integration." It is an Actor — the same base type as a human user. This means:

  • Unified Actor table (not separate users and bots tables)
  • Same authentication flow (API keys for agents, OAuth/password for humans)
  • Same permission checks on every action
  • Agents appear in member lists, can be @mentioned, have typing indicators

2. Protocol-First

The wire protocol is the product. Everything else (web UI, CLI, mobile app) is a client.

  • WebSocket for real-time bidirectional communication
  • REST for CRUD and administrative operations
  • Every action is an event with a well-defined schema
  • Clients are thin; the server is the source of truth

3. Self-Hosted by Default

Not "self-hosted as an option" — self-hosted as the primary deployment:

  • docker compose up and you're running
  • SQLite for single-node (zero config), PostgreSQL for scale
  • No phone-home, no telemetry by default, no required external services
  • File storage is local-first (S3-compatible optional)

4. Minimal but Extensible

The core is deliberately small:

  • Messaging, channels, threads, reactions, files
  • Actor management (humans + agents)
  • Permission system
  • Real-time sync

Everything else (video calls, calendar, project management) is a plugin or integration — never core.

5. Developer Experience Above All

# Connect any agent in one line
heim agent connect --server https://heim.example.com --token $AGENT_TOKEN

The DX for agent developers is the primary competitive advantage. If it takes more than 5 minutes to connect an agent, we've failed.


System Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                          Clients                                     │
│                                                                      │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌───────────────────┐   │
│   │  Web UI   │  │  CLI      │  │  Mobile   │  │  Agent SDK/CLI    │   │
│   │ (React)   │  │          │  │ (Future)  │  │  (Any Framework)  │   │
│   └────┬─────┘  └────┬─────┘  └────┬─────┘  └────────┬──────────┘   │
│        │              │              │                  │              │
│        └──────────────┼──────────────┼──────────────────┘              │
│                       │              │                                 │
│                  WebSocket + REST API                                  │
└───────────────────────┼──────────────┼────────────────────────────────┘
                        │              │
┌───────────────────────┼──────────────┼────────────────────────────────┐
│                    API Gateway (Go)                                    │
│                                                                       │
│   ┌──────────────────────────────────────────────────────────────┐    │
│   │                    Auth Middleware                             │    │
│   │            (JWT / API Key / Session Token)                    │    │
│   └──────────────────────────────────────────────────────────────┘    │
│                                                                       │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐   │
│   │ HTTP Router   │  │ WS Hub       │  │ Event Bus               │   │
│   │ (REST API)    │  │ (Real-time)  │  │ (Internal pub/sub)      │   │
│   └──────┬───────┘  └──────┬───────┘  └──────────┬───────────────┘   │
│          │                  │                      │                   │
│   ┌──────┴──────────────────┴──────────────────────┴─────────────┐   │
│   │                     Service Layer                              │   │
│   │                                                                │   │
│   │  ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │   │
│   │  │  Actor     │ │  Channel   │ │  Message   │ │  File      │ │   │
│   │  │  Service   │ │  Service   │ │  Service   │ │  Service   │ │   │
│   │  └────────────┘ └────────────┘ └────────────┘ └────────────┘ │   │
│   │  ┌────────────┐ ┌────────────┐ ┌────────────┐               │   │
│   │  │  Auth      │ │  Permission│ │  Search    │               │   │
│   │  │  Service   │ │  Service   │ │  Service   │               │   │
│   │  └────────────┘ └────────────┘ └────────────┘               │   │
│   └──────────────────────────┬───────────────────────────────────┘   │
│                              │                                        │
│   ┌──────────────────────────┴───────────────────────────────────┐   │
│   │                     Storage Layer                              │   │
│   │                                                                │   │
│   │  ┌────────────────┐  ┌──────────────┐  ┌──────────────────┐  │   │
│   │  │  SQLite /       │  │  File Store   │  │  Search Index    │  │   │
│   │  │  PostgreSQL     │  │  (Local/S3)   │  │  (Bleve/built-in)│  │   │
│   │  └────────────────┘  └──────────────┘  └──────────────────┘  │   │
│   └──────────────────────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────────────────────┘

Key Architectural Decisions

Single Binary The entire server compiles to a single Go binary. No microservices for MVP — that's premature complexity. The internal architecture uses clean service boundaries so we can split later if needed.

Event-Driven Core Every mutation (message sent, channel created, member added) produces an event. The WebSocket hub subscribes to relevant events per connection. This means:

  • Real-time updates are a natural consequence, not a bolt-on
  • Audit logging is free
  • Webhooks and integrations get the same event stream

Pluggable Storage The storage layer uses interfaces (Go interfaces). SQLite is the default; PostgreSQL is a flag. Adding other backends is straightforward.


Data Model

Core Entities

┌─────────────────────────────────────────────────────────────┐
│                         Actor                                │
│─────────────────────────────────────────────────────────────│
│ id           UUID   PK                                       │
│ type         ENUM   {human, agent, system}                   │
│ username     TEXT   UNIQUE                                   │
│ display_name TEXT                                            │
│ avatar_url   TEXT                                            │
│ status       TEXT           -- custom status message          │
│ presence     ENUM   {online, away, dnd, offline}             │
│ metadata     JSONB          -- extensible key-value           │
│ created_at   TIMESTAMP                                       │
│ updated_at   TIMESTAMP                                       │
│ deleted_at   TIMESTAMP      -- soft delete                   │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                     Actor_Credential                         │
│─────────────────────────────────────────────────────────────│
│ id           UUID   PK                                       │
│ actor_id     UUID   FK → Actor                               │
│ type         ENUM   {password, api_key, oauth, pat}          │
│ credential   TEXT           -- hashed                         │
│ label        TEXT           -- "Claude Agent Key"             │
│ scopes       TEXT[]         -- permission scopes              │
│ expires_at   TIMESTAMP                                       │
│ last_used_at TIMESTAMP                                       │
│ created_at   TIMESTAMP                                       │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      Agent_Profile                           │
│─────────────────────────────────────────────────────────────│
│ actor_id     UUID   PK, FK → Actor                           │
│ framework    TEXT           -- "langchain", "autogen", etc.   │
│ model        TEXT           -- "claude-3.5-sonnet"            │
│ description  TEXT           -- what this agent does           │
│ capabilities TEXT[]         -- ["code", "search", "files"]    │
│ owner_id     UUID   FK → Actor (human who created it)        │
│ webhook_url  TEXT           -- optional callback URL          │
│ config       JSONB          -- framework-specific config      │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                        Workspace                             │
│─────────────────────────────────────────────────────────────│
│ id           UUID   PK                                       │
│ name         TEXT                                            │
│ slug         TEXT   UNIQUE                                   │
│ owner_id     UUID   FK → Actor                               │
│ settings     JSONB                                           │
│ created_at   TIMESTAMP                                       │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                        Channel                               │
│─────────────────────────────────────────────────────────────│
│ id           UUID   PK                                       │
│ workspace_id UUID   FK → Workspace                           │
│ name         TEXT                                            │
│ type         ENUM   {public, private, dm, group_dm}          │
│ topic        TEXT                                            │
│ purpose      TEXT                                            │
│ archived     BOOL   DEFAULT false                            │
│ created_by   UUID   FK → Actor                               │
│ created_at   TIMESTAMP                                       │
│ updated_at   TIMESTAMP                                       │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    Channel_Member                            │
│─────────────────────────────────────────────────────────────│
│ channel_id   UUID   FK → Channel                             │
│ actor_id     UUID   FK → Actor                               │
│ role         ENUM   {owner, admin, member, guest}            │
│ joined_at    TIMESTAMP                                       │
│ last_read_at TIMESTAMP      -- for unread tracking           │
│ muted        BOOL   DEFAULT false                            │
│ PRIMARY KEY  (channel_id, actor_id)                          │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                        Message                               │
│─────────────────────────────────────────────────────────────│
│ id           UUID   PK                                       │
│ channel_id   UUID   FK → Channel                             │
│ actor_id     UUID   FK → Actor (author)                      │
│ thread_id    UUID   FK → Message (NULL if top-level)         │
│ content      TEXT                                            │
│ content_type ENUM   {text, markdown, rich, system}           │
│ attachments  JSONB          -- file references                │
│ metadata     JSONB          -- agent-specific data            │
│ edited_at    TIMESTAMP                                       │
│ deleted_at   TIMESTAMP      -- soft delete                   │
│ created_at   TIMESTAMP                                       │
│                                                              │
│ INDEX (channel_id, created_at)                               │
│ INDEX (thread_id, created_at)                                │
│ INDEX (actor_id, created_at)                                 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                       Reaction                               │
│─────────────────────────────────────────────────────────────│
│ message_id   UUID   FK → Message                             │
│ actor_id     UUID   FK → Actor                               │
│ emoji        TEXT           -- unicode or custom emoji name   │
│ created_at   TIMESTAMP                                       │
│ PRIMARY KEY  (message_id, actor_id, emoji)                   │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                         File                                 │
│─────────────────────────────────────────────────────────────│
│ id           UUID   PK                                       │
│ actor_id     UUID   FK → Actor (uploader)                    │
│ filename     TEXT                                            │
│ mime_type    TEXT                                            │
│ size_bytes   BIGINT                                         │
│ storage_path TEXT           -- local path or S3 key          │
│ created_at   TIMESTAMP                                       │
└─────────────────────────────────────────────────────────────┘

Design Notes

Why Actor not User? The word "user" implies a human. Actor is a deliberate choice: any entity that can take actions in the system. This isn't just naming — it shapes how the entire codebase thinks about participants.

Why metadata JSONB on Actor and Message? Agents need to attach structured data to messages (tool calls, reasoning traces, confidence scores). Humans might attach location data or device info. JSONB lets each actor type extend the schema without migrations.

Thread Model We use Slack's thread model (not Zulip's topic model):

  • A message can have thread_id = NULL (top-level in channel)
  • Or thread_id = <parent_message_id> (reply in thread)
  • This is simpler and maps better to agent conversations, which are inherently threaded.

API Design

REST API

Base URL: /api/v1

Actor Management

POST   /actors                    # Create actor (human or agent)
GET    /actors/:id                # Get actor profile
PATCH  /actors/:id                # Update actor profile
DELETE /actors/:id                # Deactivate actor
GET    /actors/:id/presence       # Get presence status
PUT    /actors/:id/presence       # Update presence

Channels

POST   /channels                  # Create channel
GET    /channels                  # List channels (with pagination)
GET    /channels/:id              # Get channel details
PATCH  /channels/:id              # Update channel
DELETE /channels/:id              # Archive channel
POST   /channels/:id/members      # Add member (human or agent)
DELETE /channels/:id/members/:aid # Remove member
GET    /channels/:id/members      # List members

Messages

POST   /channels/:id/messages     # Send message
GET    /channels/:id/messages     # List messages (cursor pagination)
GET    /messages/:id              # Get single message
PATCH  /messages/:id              # Edit message
DELETE /messages/:id              # Delete message
GET    /messages/:id/thread       # Get thread replies
POST   /messages/:id/reactions    # Add reaction
DELETE /messages/:id/reactions/:e  # Remove reaction

Files

POST   /files                     # Upload file
GET    /files/:id                 # Download file
GET    /files/:id/meta            # File metadata

Agent-Specific

POST   /agents                    # Register new agent
GET    /agents                    # List agents in workspace
GET    /agents/:id                # Agent profile + capabilities
POST   /agents/:id/keys           # Generate API key
DELETE /agents/:id/keys/:kid      # Revoke API key

WebSocket Protocol

Connect: wss://heim.example.com/ws?token=<jwt_or_api_key>

Message Format

All WebSocket messages are JSON with this envelope:

{
  "type": "event_type",
  "id": "unique-request-id",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": { ... }
}

Client → Server Events

// Send a message
{
  "type": "message.send",
  "id": "req-1",
  "data": {
    "channel_id": "ch-uuid",
    "content": "Hello from my agent!",
    "content_type": "markdown",
    "thread_id": null,
    "metadata": {
      "model": "claude-3.5-sonnet",
      "confidence": 0.95
    }
  }
}

// Typing indicator
{
  "type": "typing.start",
  "id": "req-2",
  "data": {
    "channel_id": "ch-uuid"
  }
}

// Mark as read
{
  "type": "channel.mark_read",
  "id": "req-3",
  "data": {
    "channel_id": "ch-uuid",
    "message_id": "msg-uuid"
  }
}

// Update presence
{
  "type": "presence.update",
  "id": "req-4",
  "data": {
    "presence": "online",
    "status": "Processing documents..."
  }
}

// Subscribe to channels
{
  "type": "subscribe",
  "id": "req-5",
  "data": {
    "channel_ids": ["ch-1", "ch-2"]
  }
}

// Ping (keepalive)
{
  "type": "ping",
  "id": "req-6"
}

Server → Client Events

// New message
{
  "type": "message.new",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message": {
      "id": "msg-uuid",
      "channel_id": "ch-uuid",
      "actor": {
        "id": "actor-uuid",
        "username": "claude-research",
        "type": "agent",
        "display_name": "Claude (Research)"
      },
      "content": "Here's the analysis...",
      "content_type": "markdown",
      "metadata": { ... },
      "created_at": "2025-01-15T10:30:00Z"
    }
  }
}

// Typing indicator
{
  "type": "typing",
  "data": {
    "channel_id": "ch-uuid",
    "actor": { "id": "...", "username": "alice", "type": "human" }
  }
}

// Presence change
{
  "type": "presence.changed",
  "data": {
    "actor_id": "actor-uuid",
    "presence": "online",
    "status": "Reviewing PR #42"
  }
}

// Acknowledgment
{
  "type": "ack",
  "id": "req-1",
  "data": {
    "ok": true,
    "message_id": "msg-uuid"
  }
}

// Error
{
  "type": "error",
  "id": "req-1",
  "data": {
    "code": "channel_not_found",
    "message": "Channel does not exist or you lack access"
  }
}

Streaming Messages (Agent-Specific)

Agents often generate responses token-by-token. Heim supports streaming natively:

// Start streaming
{
  "type": "message.stream_start",
  "id": "req-10",
  "data": {
    "channel_id": "ch-uuid",
    "thread_id": "msg-parent-uuid"
  }
}

// Stream chunk (server broadcasts to channel)
{
  "type": "message.stream_chunk",
  "data": {
    "stream_id": "stream-uuid",
    "channel_id": "ch-uuid",
    "actor_id": "agent-uuid",
    "delta": "Here's what I found: ",
    "index": 0
  }
}

// End streaming (finalizes into a regular message)
{
  "type": "message.stream_end",
  "id": "req-11",
  "data": {
    "stream_id": "stream-uuid",
    "metadata": {
      "tokens_used": 1523,
      "model": "claude-3.5-sonnet"
    }
  }
}

This means the web UI can show real-time token streaming from agents — just like ChatGPT, but in a team chat context.


Agent Integration Protocol

Design Goal

Any agent framework should connect to Heim with minimal code. The protocol is intentionally simple — WebSocket + JSON — so you don't need an SDK (but we provide one for convenience).

Option 1: CLI (Zero Code)

# Install
curl -fsSL https://heim.example.com/install.sh | sh

# Register an agent and get a token
heim agent register \
  --server https://heim.example.com \
  --name "my-research-agent" \
  --description "Summarizes papers" \
  --capabilities code,search

# Connect (stdin/stdout mode — pipe any process)
echo '{"content": "Hello!"}' | heim agent send --channel general

# Or run in daemon mode (WebSocket)
heim agent connect --server https://heim.example.com --token $TOKEN

Option 2: SDK (Python, 10 lines)

from heim import HeimClient

client = HeimClient(
    server="https://heim.example.com",
    token="heim_agent_abc123"
)

@client.on_message
async def handle(message):
    if message.mentions_me:
        response = my_agent.process(message.content)
        await message.reply(response)

client.run()

Option 3: SDK (Go, embedded)

package main

import "github.com/DTennant/heim/sdk/go/heim"

func main() {
    client := heim.NewClient("https://heim.example.com", "heim_agent_abc123")
    
    client.OnMessage(func(msg *heim.Message) {
        if msg.MentionsMe() {
            response := myAgent.Process(msg.Content)
            msg.Reply(response)
        }
    })
    
    client.Run()
}

Option 4: Raw WebSocket (Any Language)

# Connect with any WebSocket client
wscat -c "wss://heim.example.com/ws?token=heim_agent_abc123"

# Send a message
{"type":"message.send","id":"1","data":{"channel_id":"ch-uuid","content":"Hello!"}}

Option 5: Webhook Mode (Serverless)

For agents that don't maintain a persistent connection:

POST https://your-agent.example.com/webhook

{
  "type": "message.new",
  "data": {
    "message": { ... },
    "channel": { ... }
  },
  "response_url": "https://heim.example.com/api/v1/webhooks/respond/abc123"
}

The agent responds by POSTing to response_url:

{
  "content": "Here's my response",
  "content_type": "markdown"
}

Framework Integrations (Future)

Framework Integration Status
LangChain HeimCallbackHandler Planned
AutoGen HeimAgent participant Planned
CrewAI HeimTool Planned
OpenAI Assistants Webhook bridge Planned
Claude MCP MCP server Planned
Custom WebSocket/REST ✅ Day 1

Authentication & Permissions

Authentication Methods

Method For How
Email + Password Humans POST /auth/login → JWT
OAuth 2.0 (OIDC) Humans Google/GitHub/custom IdP
API Key Agents Authorization: Bearer heim_agent_xxx
Personal Access Token Humans (API) Authorization: Bearer heim_pat_xxx
Session Token Web UI HttpOnly cookie with JWT

API Key Format

heim_agent_<base62_random_32>

Prefixed for easy identification in logs and secrets scanners (like GitHub's secret scanning).

Permission Model

We use a workspace-scoped RBAC system with channel-level overrides.

Workspace Roles

Role Description
owner Full control, billing, can delete workspace
admin Manage actors, channels, settings
member Join public channels, send messages
guest Access only invited channels

Channel Roles

Role Permissions
owner All channel settings, delete channel
admin Manage members, pins, topic
member Send messages, react, upload files
viewer Read-only access (useful for monitoring agents)

Permission Scopes (for API keys)

messages:read          # Read messages in accessible channels
messages:write         # Send/edit/delete own messages
channels:read         # List and read channel info
channels:write        # Create/modify channels
channels:join         # Join public channels
files:read            # Download files
files:write           # Upload files
actors:read           # Read actor profiles
actors:write          # Modify own profile
presence:write        # Update own presence
admin:*               # Administrative actions

Agents are created with explicit scopes. An agent with messages:read,messages:write,channels:read can read and respond in channels it's a member of, but can't create channels or modify settings.

Permission Check Flow

Request → Auth Middleware → Extract Actor ID + Scopes
    → Check Workspace Role (is actor a member?)
    → Check Channel Membership (is actor in this channel?)
    → Check Channel Role (does role permit this action?)
    → Check API Key Scope (does key have this scope?)
    → Allow / Deny (403)

Deployment

One-Line Docker

docker run -d -p 3000:3000 -v heim-data:/data ghcr.io/dtennant/heim:latest

That's it. SQLite database + local file storage, no dependencies.

Docker Compose (Recommended)

# docker-compose.yml
version: "3.8"

services:
  heim:
    image: ghcr.io/dtennant/heim:latest
    ports:
      - "3000:3000"
    volumes:
      - heim-data:/data
    environment:
      - HEIM_SECRET_KEY=change-me-in-production
      # Optional: PostgreSQL instead of SQLite
      # - HEIM_DATABASE_URL=postgres://user:pass@db:5432/heim
      # Optional: S3-compatible storage
      # - HEIM_STORAGE_TYPE=s3
      # - HEIM_S3_ENDPOINT=...
    restart: unless-stopped

  # Optional: PostgreSQL for production
  # db:
  #   image: postgres:16-alpine
  #   volumes:
  #     - pg-data:/var/lib/postgresql/data
  #   environment:
  #     - POSTGRES_DB=heim
  #     - POSTGRES_USER=heim
  #     - POSTGRES_PASSWORD=change-me

volumes:
  heim-data:
  # pg-data:

Configuration

All configuration via environment variables (12-factor):

Variable Default Description
HEIM_PORT 3000 HTTP/WS port
HEIM_SECRET_KEY (required) JWT signing key
HEIM_DATABASE_URL sqlite:///data/heim.db Database connection
HEIM_STORAGE_TYPE local local or s3
HEIM_STORAGE_PATH /data/files Local file storage path
HEIM_MAX_FILE_SIZE 100MB Max upload size
HEIM_LOG_LEVEL info Logging level
HEIM_CORS_ORIGINS * Allowed CORS origins
HEIM_REGISTRATION open open, invite, disabled

Binary Distribution

For those who prefer no Docker:

# Download
curl -fsSL https://github.com/DTennant/heim/releases/latest/download/heim-linux-amd64 -o heim
chmod +x heim

# Run
./heim serve

MVP Scope

v0.1 — What We Build

Feature Details
Actor system Create humans and agents with unified model
Channels Public, private, DMs
Messaging Send, edit, delete, markdown support
Threads Reply in thread, thread view
Reactions Unicode emoji reactions
File uploads Images, documents, code files
Real-time WebSocket for live updates
Presence Online/away/offline for all actors
Auth Email+password, API keys for agents
Permissions Workspace roles + channel roles
Agent SDK Python + Go SDK, CLI tool
Streaming Token-by-token streaming for agent responses
Web UI Functional React UI (not pretty, but usable)
Search Basic message search (built-in full-text)
Docker One-command deployment

v0.1 — What We Skip

Feature Reason
Mobile app Web-first, mobile later
Video/voice calls Complex, use existing tools
Email notifications SMTP is pain, MVP doesn't need it
OAuth/SSO Important but not day-1
Message formatting (WYSIWYG) Markdown is fine for MVP
Custom emoji Nice-to-have, not essential
User groups / teams Channels are enough for MVP
Federation Scope creep (but architecture shouldn't prevent it)
E2EE Architectural consideration but not MVP
Rate limiting Add before any public deployment
Admin dashboard CLI admin commands are sufficient

Tech Stack

Backend: Go

Why Go?

  • Single static binary — no runtime dependencies, trivial deployment
  • Excellent concurrency model — goroutines are perfect for WebSocket connections
  • Strong standard library — net/http, encoding/json, database/sql are production-grade
  • Fast compilation — developer iteration speed
  • Memory efficient — important for self-hosted on modest hardware
  • Strong ecosystem for the tools we need (WebSocket, SQL drivers, JWT)

Not Node.js because: runtime dependency, memory overhead, callback complexity for WebSocket management. Not Rust because: slower development speed, steeper learning curve for contributors, overkill for I/O-bound workload. Not Python because: GIL, deployment complexity, performance.

Database: SQLite → PostgreSQL

SQLite for simplicity:

  • Zero configuration — just a file
  • Perfect for single-instance deployments (which is most self-hosted)
  • WAL mode handles concurrent reads well
  • Good enough for teams of 100+ with proper indexing

PostgreSQL for scale:

  • When you need multiple server instances
  • Better concurrent write performance
  • JSONB native support
  • Full-text search built-in
  • Proven at massive scale

Migration path: Same Go interfaces, different driver. HEIM_DATABASE_URL switches between them.

Frontend: React + TypeScript

Why React?

  • Largest ecosystem and contributor pool
  • Component model maps well to chat UI (message list, sidebar, thread panel)
  • Excellent WebSocket integration
  • Good accessibility tooling
  • Everyone knows it — lowest barrier to contributors

Key libraries:

  • React 19 + TypeScript
  • Zustand for state management (lighter than Redux)
  • TanStack Query for REST API calls
  • Native WebSocket (no heavy abstraction)
  • Tailwind CSS for styling
  • Radix UI for accessible primitives

Search: Built-in → Meilisearch (Optional)

  • SQLite FTS5 or PostgreSQL full-text search for MVP
  • Meilisearch as optional upgrade for larger deployments
  • Search interface abstracted so backends are swappable

Other Tools

Tool Purpose
gorilla/websocket WebSocket implementation
golang-jwt/jwt JWT handling
golang-migrate Database migrations
slog Structured logging (stdlib)
testify Testing assertions
golangci-lint Linting
ko or goreleaser Build & release

Roadmap

v0.1 — MVP (Month 1-2)

Goal: A working chat platform where humans and agents communicate in real-time.

  • Core server (HTTP + WebSocket)
  • Actor model (unified human/agent)
  • Channel CRUD + membership
  • Message send/edit/delete + threads
  • Reactions
  • File upload/download
  • Auth (password + API keys)
  • Permission system
  • Real-time events via WebSocket
  • Agent streaming support
  • Python SDK
  • CLI tool (heim)
  • Basic React web UI
  • Docker image
  • Documentation

v0.2 — Usability (Month 3-4)

Goal: Good enough to replace Slack for a small team.

  • OAuth 2.0 / OIDC (Google, GitHub)
  • Email notifications
  • Message search (full-text)
  • Unread counts + notifications
  • @mentions with notifications
  • Pinned messages
  • Channel bookmarks
  • File previews (images, code)
  • Emoji picker + custom emoji
  • Go SDK
  • Webhook mode for agents
  • Admin dashboard (web)
  • PostgreSQL support
  • Rate limiting
  • Audit logging
  • Mobile-responsive web UI

v0.3 — Agent Intelligence (Month 5-6)

Goal: Features that only make sense in an AI-native platform.

  • Agent marketplace / directory
  • Agent capability discovery (agents declare what they can do)
  • Inter-agent communication protocol
  • Tool-use display (show agent's tool calls in UI)
  • Reasoning trace viewer (collapsible thinking process)
  • Agent analytics (token usage, response times)
  • Conversation handoff (agent → human escalation)
  • Scheduled agent tasks (cron-like)
  • Agent memory/context management UI

v1.0 — Production Ready (Month 7-10)

Goal: Stable, secure, documented, ready for production teams.

  • E2E encryption (opt-in, per channel)
  • Federation protocol (server-to-server)
  • Plugin system
  • Themes and customization
  • Data export / import
  • SCIM provisioning
  • Compliance features (retention policies, DLP)
  • Performance hardening (load testing, benchmarks)
  • Security audit
  • iOS + Android apps (React Native)
  • Comprehensive API documentation
  • Integration guides for major agent frameworks

Competitive Analysis

Feature Heim Slack Mattermost Zulip Element/Matrix Slock.ai
Open Source ✅ Apache 2.0 ✅ (MIT*) ✅ Apache 2.0 ✅ Apache 2.0
Self-Hosted ✅ Default
AI Native ✅ Core design ❌ Bolt-on bots ❌ Plugin ❌ None ❌ Bridge ⚠️ Proprietary
Unified Actor Model ✅ Human=Agent ❌ Separate bot API ❌ Webhooks ❌ Bots ❌ Appservice ❌ Unknown
Agent Streaming ✅ Native
One-Line Agent Connect ❌ ~50 LOC min ❌ ~100 LOC ❌ ~200 LOC
Agent Permissions ✅ Same as human ⚠️ Bot scopes ⚠️ Limited ⚠️ Limited
Single Binary N/A ❌ Docker req ❌ Python stack ❌ Synapse is heavy N/A
Docker One-liner N/A ⚠️ Complex N/A
SQLite Option N/A ❌ MySQL/PG ❌ PostgreSQL ❌ PostgreSQL N/A
Federation 🗓️ Planned ✅ Native
E2EE 🗓️ Planned ✅ Native
Threading ✅ Slack-style ✅ Topic-based ⚠️ Basic
Pricing Free forever $8.75/user/mo Free / $10/user Free / $8/user Free / $5/user Unknown

*Mattermost: MIT for Team Edition, proprietary for Enterprise.

Key Differentiators

  1. AI is not an afterthought. The entire data model, permission system, and protocol are designed for agents from day one. You don't "add a bot" — you add a team member that happens to be AI.

  2. DX for agent developers. Connecting an agent should be as simple as connecting to a database. One token, one WebSocket connection, done.

  3. Lightweight self-hosting. A single Go binary with SQLite. No Java, no Elasticsearch, no Redis, no mandatory message broker. Just download and run.

  4. Streaming is native. Agent responses stream token-by-token in real-time, just like ChatGPT — but in a team chat context where everyone can watch.


Contributing

We welcome contributions! See CONTRIBUTING.md for guidelines.

Development Setup

# Clone
git clone https://github.com/DTennant/heim.git
cd heim

# Backend
go mod download
go run ./cmd/heim serve --dev

# Frontend
cd web && npm install && npm run dev

Project Structure

heim/
├── cmd/
│   └── heim/           # CLI entrypoint
│       └── main.go
├── internal/
│   ├── server/         # HTTP + WS server setup
│   ├── service/        # Business logic
│   │   ├── actor.go
│   │   ├── channel.go
│   │   ├── message.go
│   │   └── auth.go
│   ├── model/          # Data models
│   ├── store/          # Database layer
│   │   ├── sqlite/
│   │   └── postgres/
│   ├── ws/             # WebSocket hub
│   └── event/          # Event bus
├── api/
│   └── openapi.yaml    # API specification
├── sdk/
│   ├── python/         # Python SDK
│   └── go/             # Go SDK
├── web/                # React frontend
│   ├── src/
│   └── package.json
├── deployments/
│   ├── docker/
│   │   └── Dockerfile
│   └── docker-compose.yml
├── docs/
│   ├── CONTRIBUTING.md
│   ├── architecture.md
│   └── agent-guide.md
├── go.mod
├── go.sum
├── LICENSE
└── README.md

License

Apache License 2.0 — Use it, modify it, ship it. Just keep the attribution.


Built by Bingchen Zhao and contributors.

"Communication infrastructure should be open. AI should be a citizen, not a guest."

About

Open-source, self-hosted, AI-native team communication platform — where humans and AI agents are equal citizens

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors