21 KiB
00 - Architecture Overview
1. Overview
GoClaw is an AI agent gateway written in Go. It exposes a WebSocket RPC (v3) interface and an OpenAI-compatible HTTP API for orchestrating LLM-powered agents. The system supports two operating modes:
- Standalone -- file-based storage with SQLite for per-user data, zero external dependencies beyond an LLM API key.
- Managed -- PostgreSQL-backed multi-tenant mode with HTTP CRUD APIs, per-user context files, encrypted credentials, agent delegation, teams, and LLM call tracing.
Documentation scope: This documentation covers both modes. Standalone mode now has near-parity with managed mode for core features (per-user context files, workspace isolation, agent types, bootstrap onboarding). Managed mode adds agent delegation, teams, quality gates, tracing, HTTP CRUD APIs, and encrypted secrets.
2. Component Diagram
flowchart TD
subgraph Clients
WS[WebSocket Clients]
HTTP[HTTP Clients]
TG[Telegram]
DC[Discord]
FS[Feishu / Lark]
ZL[Zalo]
WA[WhatsApp]
end
subgraph Gateway["Gateway Server"]
WSS[WebSocket Server]
HTTPS[HTTP API Server]
MR[Method Router]
RL[Rate Limiter]
RBAC[Permission Engine]
end
subgraph Channels["Channel Manager"]
CM[Channel Manager]
PA[Pairing Service]
end
subgraph Core["Core Engine"]
BUS[Message Bus]
SCHED[Scheduler -- 4 Lanes]
AR[Agent Router]
LOOP[Agent Loop -- Think / Act / Observe]
end
subgraph Providers["LLM Providers"]
ANTH[Anthropic -- Native HTTP + SSE]
OAI[OpenAI-Compatible -- HTTP + SSE]
end
subgraph Tools["Tool Registry"]
FS_T[Filesystem]
EXEC[Exec / Shell]
WEB[Web Search / Fetch]
MEM[Memory]
SUB[Subagent]
DEL[Delegation]
TEAM_T[Teams]
EVAL[Evaluate Loop]
HO[Handoff]
TTS_T[TTS]
BROW[Browser]
SK[Skills]
MCP_T[MCP Bridge]
CT[Custom Tools]
end
subgraph Hooks["Hook Engine"]
HE[Engine]
CMD_E[Command Evaluator]
AGT_E[Agent Evaluator]
end
subgraph Store["Store Layer"]
SESS[SessionStore]
AGENT_S[AgentStore]
PROV_S[ProviderStore]
CRON_S[CronStore]
MEM_S[MemoryStore]
SKILL_S[SkillStore]
TRACE_S[TracingStore]
MCP_S[MCPServerStore]
CT_S[CustomToolStore]
AL_S[AgentLinkStore]
TM_S[TeamStore]
end
WS --> WSS
HTTP --> HTTPS
TG & DC & FS & ZL & WA --> CM
WSS --> MR
HTTPS --> MR
MR --> RL --> RBAC --> AR
CM --> BUS
BUS --> SCHED
SCHED --> AR
AR --> LOOP
LOOP --> Providers
LOOP --> Tools
Tools --> Store
Tools --> Hooks
Hooks --> Tools
LOOP --> Store
3. Module Map
| Module | Description |
|---|---|
internal/gateway/ |
WebSocket + HTTP server, client handling, method router |
internal/gateway/methods/ |
RPC method handlers: chat, agents, agent_links, teams, delegations, sessions, config, skills, cron, pairing, exec approval, usage, send |
internal/agent/ |
Agent loop (think, act, observe), router, resolver, system prompt builder, sanitization, pruning, tracing, memory flush, DELEGATION.md + TEAM.md injection |
internal/providers/ |
LLM providers: Anthropic (native HTTP + SSE streaming), OpenAI-compatible (HTTP + SSE), retry logic |
internal/tools/ |
Tool registry, filesystem ops, exec/shell, policy engine, subagent, delegation manager, team tools, evaluate loop, handoff, context file + memory interceptors, credential scrubbing, rate limiting, PathDenyable |
internal/tools/dynamic_loader.go |
Custom tool loader: LoadGlobal (startup), LoadForAgent (per-agent clone), ReloadGlobal (cache invalidation) |
internal/tools/dynamic_tool.go |
Custom tool executor: command template rendering, shell escaping, encrypted env vars |
internal/hooks/ |
Hook engine: quality gates, command evaluator, agent evaluator, recursion prevention (WithSkipHooks) |
internal/store/ |
Store interfaces: SessionStore, AgentStore, ProviderStore, SkillStore, MemoryStore, CronStore, PairingStore, TracingStore, MCPServerStore, AgentLinkStore, TeamStore, ChannelInstanceStore, ConfigSecretsStore |
internal/store/pg/ |
PostgreSQL implementations (database/sql + pgx/v5) |
internal/store/file/ |
File-based implementations: sessions, memory (SQLite), cron, pairing, skills, agents (filesystem + SQLite) |
internal/bootstrap/ |
System prompt files (AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, HEARTBEAT.md, BOOTSTRAP.md) + seeding + truncation |
internal/config/ |
Config loading (JSON5) + env var overlay |
internal/skills/ |
SKILL.md loader (5-tier hierarchy) + BM25 search + hot-reload via fsnotify |
internal/channels/ |
Channel manager + adapters: Telegram, Feishu/Lark, Zalo, Discord, WhatsApp |
internal/mcp/ |
MCP server bridge (stdio, SSE, streamable-HTTP transports) |
internal/scheduler/ |
Lane-based concurrency control (main, subagent, cron, delegate lanes) with per-session serialization |
internal/memory/ |
Memory system (SQLite FTS5 + embeddings for standalone mode) |
internal/permissions/ |
RBAC policy engine (admin, operator, viewer roles) |
internal/pairing/ |
DM/device pairing service (8-character codes) |
internal/sessions/ |
File-based session manager (standalone mode) |
internal/bus/ |
Event pub/sub (Message Bus) |
internal/sandbox/ |
Docker-based code execution sandbox |
internal/tts/ |
Text-to-Speech providers: OpenAI, ElevenLabs, Edge, MiniMax |
internal/http/ |
HTTP API handlers: /v1/chat/completions, /v1/agents, /v1/skills, /v1/traces, /v1/mcp, /v1/delegations, summoner |
internal/crypto/ |
AES-256-GCM encryption for API keys |
internal/tracing/ |
LLM call tracing (traces + spans), in-memory buffer with periodic store flush |
internal/tracing/otelexport/ |
Optional OpenTelemetry OTLP exporter (opt-in via build tags; adds gRPC + protobuf) |
internal/heartbeat/ |
Periodic agent wake-up service |
4. Two Operating Modes
| Aspect | Standalone | Managed |
|---|---|---|
| Config source | config.json + env vars |
config.json + GOCLAW_POSTGRES_DSN |
| Storage | JSON files + SQLite (~/.goclaw/data/agents.db) |
PostgreSQL |
| Agents | Defined in config.json agents.list, created eagerly at startup |
agents table, lazy-resolved via ManagedResolver |
| Agent store | FileAgentStore (filesystem + SQLite) |
PGAgentStore |
| Context files | Agent-level on filesystem, per-user in SQLite | agent_context_files + user_context_files tables |
| Agent types | open / predefined (via config) |
open (7 per-user files) / predefined (agent-level + USER.md per-user) |
| Per-user isolation | Workspace subdirectories (user_alice/, user_bob/) |
Same + DB-scoped context files |
| Bootstrap onboarding | Per-user BOOTSTRAP.md seeding (SQLite) | Same (PostgreSQL) |
| Agent delegation | N/A | Sync/async delegation, agent links, quality gates |
| Agent teams | N/A | Shared task board, mailbox, handoff |
| Skills | Filesystem only (workspace + global dirs) | PostgreSQL + filesystem + embedding search |
| Memory | SQLite FTS5 + embeddings | pgvector hybrid (full-text search + vector similarity) |
| Tracing | N/A | traces + spans tables + optional OTel OTLP export |
| MCP servers | config.json tools.mcp_servers |
mcp_servers table + grants |
| API key storage | .env.local / env vars only |
PostgreSQL (AES-256-GCM encrypted) |
| HTTP CRUD API | N/A | /v1/agents, /v1/skills, /v1/traces, /v1/mcp, /v1/delegations |
| Virtual FS | ContextFileInterceptor routes to SQLite |
ContextFileInterceptor routes to PostgreSQL |
| Custom tools | N/A | custom_tools table + DynamicToolLoader |
| Managed-only stores (nil in standalone) | -- | ProviderStore, TracingStore, MCPServerStore, CustomToolStore, AgentLinkStore, TeamStore |
5. Multi-Tenant Identity Model
GoClaw uses the Identity Propagation pattern (also known as Trusted Subsystem). It does not implement authentication or authorization — instead, it trusts the upstream service that authenticates with the gateway token to provide accurate user identity.
flowchart LR
subgraph "Upstream Service (trusted)"
AUTH["Authenticate end-user"]
HDR["Set X-GoClaw-User-Id header<br/>or user_id in WS connect"]
end
subgraph "GoClaw Gateway"
EXTRACT["Extract user_id<br/>(opaque, VARCHAR 255)"]
CTX["store.WithUserID(ctx)"]
SCOPE["Per-user scoping:<br/>sessions, context files,<br/>memory, traces, agent shares"]
end
AUTH --> HDR
HDR --> EXTRACT
EXTRACT --> CTX
CTX --> SCOPE
Identity Flow
| Entry Point | How user_id is provided | Enforcement |
|---|---|---|
| HTTP API | X-GoClaw-User-Id header |
Required in managed mode |
| WebSocket | user_id field in connect handshake |
Required in managed mode |
| Channels | Derived from platform sender ID (e.g., Telegram user ID) | Automatic |
Compound User ID Convention
The user_id field is opaque to GoClaw — it does not interpret or validate the format. For multi-tenant deployments, the recommended convention is:
tenant.{tenantId}.user.{userId}
This hierarchical format ensures natural isolation between tenants. Since user_id is used as a scoping key across all per-user tables (user_context_files, user_agent_profiles, user_agent_overrides, agent_shares, sessions, traces), the compound format guarantees that users from different tenants cannot access each other's data.
Where user_id is used
| Component | Usage |
|---|---|
| Session keys | agent:{agentId}:{channel}:direct:{peerId} — peerId derived from user_id |
| Context files | user_context_files table scoped by (agent_id, user_id) |
| User profiles | user_agent_profiles table — first/last seen, workspace |
| User overrides | user_agent_overrides — per-user provider/model preferences |
| Agent shares | agent_shares table — user-level access control |
| Memory | Per-user memory entries via context propagation |
| Traces | traces table includes user_id for filtering |
| MCP grants | mcp_user_grants — per-user MCP server access |
| Skills grants | skill_user_grants — per-user skill access |
6. Gateway Startup Sequence
sequenceDiagram
participant CLI as CLI (cmd/root.go)
participant GW as runGateway()
participant PG as PostgreSQL
participant Engine as Core Engine
CLI->>GW: 1. Parse CLI flags + load config
GW->>GW: 2. Resolve workspace + data dirs
GW->>GW: 3. Create Message Bus
alt Managed mode
GW->>PG: 4. Connect to Postgres (pg.NewPGStores)
PG-->>GW: PG stores created
GW->>GW: 5. Start tracing collector
GW->>PG: 6. Register providers from DB
GW->>PG: 7. Wire embedding provider to PGMemoryStore
GW->>PG: 8. Backfill memory embeddings (background)
else Standalone mode
GW->>GW: 4. Create file-based stores
end
GW->>GW: 9. Register config-based providers
GW->>GW: 10. Create tool registry (filesystem, exec, web, memory, browser, TTS, subagent, MCP)
GW->>GW: 11. Load bootstrap files (DB or filesystem)
GW->>GW: 12. Create skills loader + register skill_search tool
GW->>GW: 13. Wire skill embeddings (managed only)
alt Managed mode
GW->>GW: 14. Create agents lazily (set ManagedResolver)
GW->>GW: 15. wireManagedExtras (interceptors, cache subscribers)
GW->>GW: 16. Wire managed HTTP handlers (agents, skills, traces, MCP)
else Standalone mode
GW->>GW: 14. Create agents eagerly from config
GW->>GW: 15. wireStandaloneExtras (FileAgentStore, interceptors, callbacks)
end
GW->>Engine: 17. Create gateway server (WS + HTTP)
GW->>Engine: 18. Register RPC methods
GW->>Engine: 19. Register + start channels (Telegram, Discord, Feishu, Zalo, WhatsApp)
GW->>Engine: 20. Start cron, heartbeat, scheduler (4 lanes)
GW->>Engine: 21. Start skills watcher + inbound consumer
GW->>Engine: 22. Listen on host:port
7. Managed Mode Wiring
The wireManagedExtras() function in cmd/gateway_managed.go wires multi-tenant components:
flowchart TD
W1["1. ContextFileInterceptor<br/>Routes read_file / write_file to DB"] --> W2
W2["2. User Seeding Callback<br/>Seeds per-user context files on first chat"] --> W3
W3["3. Context File Loader<br/>Loads per-user vs agent-level files by agent_type"] --> W4
W4["4. ManagedResolver<br/>Lazy-creates agent Loops from DB on cache miss"] --> W5
W5["5. Virtual FS Interceptors<br/>Wire interceptors on read_file + write_file + memory tools"] --> W6
W6["6. Memory Store Wiring<br/>Wire PGMemoryStore on memory_search + memory_get tools"] --> W7
W7["7. Cache Invalidation Subscribers<br/>Subscribe to MessageBus events"] --> W8
W8["8. Delegation Tools<br/>DelegateManager + delegate_search + agent links"] --> W9
W9["9. Team Tools<br/>team_tasks + team_message + team auto-linking"] --> W10
W10["10. Hook Engine<br/>Quality gates with command + agent evaluators"] --> W11
W11["11. Evaluate Loop + Handoff<br/>evaluate_loop tool + handoff tool"]
A separate wireStandaloneExtras() in cmd/gateway_standalone.go wires the same core callbacks (user seeding, context file loading) using FileAgentStore instead of PostgreSQL.
Cache Invalidation Events
| Event | Subscriber | Action |
|---|---|---|
cache:bootstrap |
ContextFileInterceptor | InvalidateAgent() or InvalidateAll() |
cache:agent |
AgentRouter | InvalidateAgent() -- forces re-resolve from DB |
cache:skills |
SkillStore | BumpVersion() |
cache:cron |
CronStore | InvalidateCache() |
cache:custom_tools |
DynamicToolLoader | ReloadGlobal() + AgentRouter.InvalidateAll() |
8. Scheduler Lanes
The scheduler uses a lane-based concurrency model. Each lane is a named worker pool with a bounded semaphore. Per-session queues control concurrency within each session.
flowchart TD
subgraph Main["Lane: main (concurrency 2)"]
M1[Channel messages]
M2[WebSocket requests]
end
subgraph Sub["Lane: subagent (concurrency 4)"]
S1[Subagent executions]
end
subgraph Del["Lane: delegate (concurrency 100)"]
D1[Delegation executions]
end
subgraph Cron["Lane: cron (concurrency 1)"]
C1[Cron job executions]
end
Main --> SEM1[Semaphore]
Sub --> SEM2[Semaphore]
Del --> SEM3[Semaphore]
Cron --> SEM4[Semaphore]
SEM1 --> Q[Per-Session Queue]
SEM2 --> Q
SEM3 --> Q
SEM4 --> Q
Q --> AGENT[Agent Loop]
Lane Defaults
| Lane | Concurrency | Env Override | Purpose |
|---|---|---|---|
main |
2 | GOCLAW_LANE_MAIN |
Primary user chat sessions |
subagent |
4 | GOCLAW_LANE_SUBAGENT |
Spawned subagents |
delegate |
100 | GOCLAW_LANE_DELEGATE |
Agent delegation executions |
cron |
1 | GOCLAW_LANE_CRON |
Scheduled cron jobs |
Session Queue Concurrency
Per-session queues now support configurable maxConcurrent:
- DMs:
maxConcurrent = 1(single-threaded per user) - Groups:
maxConcurrent = 3(multiple concurrent responses) - Adaptive throttle: When session history exceeds 60% of context window, concurrency drops to 1
Queue Modes
| Mode | Behavior |
|---|---|
queue |
FIFO -- new messages wait until the current run completes |
followup |
Merges incoming message into the pending queue as a follow-up |
interrupt |
Cancels the active run and replaces it with the new message |
Default queue config: capacity 10, drop policy old (drops oldest on overflow), debounce 800ms.
/stop and /stopall
/stop-- Cancel the oldest running task (others keep going)/stopall-- Cancel all running tasks + drain the queue
Both are intercepted before the debouncer to avoid being merged with normal messages.
9. Graceful Shutdown
When the process receives SIGINT or SIGTERM:
- Broadcast
shutdownevent to all connected WebSocket clients. channelMgr.StopAll()-- stop all channel adapters.cronStore.Stop()-- stop cron scheduler.heartbeatSvc.Stop()-- stop heartbeat service.sandboxMgr.Stop()+ReleaseAll()-- release Docker containers.cancel()-- cancel root context, propagating to consumer + scheduler.- Deferred cleanup: flush tracing collector, close memory store, close browser manager, stop scheduler lanes.
- HTTP server shutdown with a 5-second timeout (
context.WithTimeout).
10. Config System
Configuration is loaded from a JSON5 file with environment variable overlay. Secrets are never persisted to the config file.
flowchart TD
A{Config path?} -->|--config flag| B[CLI flag path]
A -->|GOCLAW_CONFIG env| C[Env var path]
A -->|default| D["config.json"]
B & C & D --> LOAD["config.Load()"]
LOAD --> S1["1. Set defaults"]
S1 --> S2["2. Parse JSON5"]
S2 --> S3["3. Env var overlay<br/>(GOCLAW_*_API_KEY)"]
S3 --> S4["4. Apply computed defaults<br/>(context pruning, etc.)"]
S4 --> READY[Config ready]
Key Config Sections
| Section | Purpose |
|---|---|
gateway |
host, port, token, allowed_origins, rate_limit_rpm, max_message_chars |
agents |
defaults (provider, model, context_window) + list (per-agent overrides) |
tools |
profile, allow/deny lists, exec_approval, web, browser, mcp_servers, rate_limit_per_hour |
channels |
Per-channel: enabled, token, dm_policy, group_policy, allow_from |
database |
mode (standalone/managed); postgres_dsn read only from env var |
Secret Handling
- Secrets exist only in env vars or
.env.local-- never inconfig.json. GOCLAW_POSTGRES_DSNis taggedjson:"-"and cannot be read from the config file.MaskedCopy()replaces API keys with"***"when returning config over WebSocket.StripSecrets()removes secrets before writing config to disk.- Config hot-reload via
fsnotifywatcher with 300ms debounce.
11. File Reference
| File | Purpose |
|---|---|
cmd/root.go |
Cobra CLI entry point, flag parsing |
cmd/gateway.go |
Gateway startup orchestrator (runGateway()) |
cmd/gateway_managed.go |
Managed mode wiring (wireManagedExtras(), wireManagedHTTP()) |
cmd/gateway_standalone.go |
Standalone mode wiring (wireStandaloneExtras()) |
cmd/gateway_callbacks.go |
Shared callbacks for managed + standalone (user seeding, context file loading) |
cmd/gateway_consumer.go |
Inbound message consumer (subagent, delegate, teammate, handoff routing) |
cmd/gateway_providers.go |
Provider registration (config-based + DB-based) |
cmd/gateway_methods.go |
RPC method registration |
internal/config/config.go |
Config struct definitions |
internal/config/config_load.go |
JSON5 loading + env overlay |
internal/config/config_channels.go |
Channel config structs |
internal/gateway/server.go |
WS + HTTP server, CORS, rate limiter setup |
internal/gateway/client.go |
WebSocket client handling, read limit (512KB) |
internal/gateway/router.go |
RPC method routing |
internal/scheduler/lanes.go |
Lane definitions, semaphore-based concurrency |
internal/scheduler/queue.go |
Per-session queue, queue modes, debounce |
internal/hooks/engine.go |
Hook engine: evaluator registry, EvaluateHooks |
internal/hooks/command_evaluator.go |
Shell command evaluator (exit 0 = pass) |
internal/hooks/agent_evaluator.go |
Agent delegation evaluator (APPROVED/REJECTED) |
internal/hooks/context.go |
WithSkipHooks / SkipHooksFromContext (recursion prevention) |
internal/store/stores.go |
Stores container struct (all 14 store interfaces) |
internal/store/types.go |
StoreConfig, BaseModel |
Cross-References
| Document | Content |
|---|---|
| 01-agent-loop.md | Agent loop detail, sanitization pipeline, history management |
| 02-providers.md | LLM providers, retry logic, schema cleaning |
| 03-tools-system.md | Tool registry, policy engine, interceptors, custom tools, MCP grants |
| 04-gateway-protocol.md | WebSocket protocol v3, HTTP API, RBAC, identity propagation |
| 05-channels-messaging.md | Channel adapters, Telegram formatting, pairing, managed-mode user scoping |
| 06-store-data-model.md | Store interfaces, PostgreSQL schema, session caching, custom tool store |
| 07-bootstrap-skills-memory.md | Bootstrap files, skills system, memory, skills grants |
| 08-scheduling-cron-heartbeat.md | Scheduler lanes, cron lifecycle, heartbeat |
| 09-security.md | Defense layers, encryption, rate limiting, RBAC, sandbox |
| 10-tracing-observability.md | Tracing collector, span hierarchy, OTel export, trace API |