Files
goclaw/docs/00-architecture-overview.md
Clark Cant 9f719b023a Merge pull request #1464 from connermo/feat/outbound-dispatch-sharding
perf(channels): shard outbound dispatch per conversation
2026-08-01 04:32:20 +07:00

35 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 uses PostgreSQL as its storage backend with full multi-tenant isolation, per-user context files, encrypted credentials, agent delegation, teams, and LLM call tracing.

2. Component Diagram

flowchart TD
    subgraph Clients
        WS[WebSocket Clients]
        HTTP[HTTP Clients]
        TG[Telegram]
        DC[Discord]
        FS[Feishu / Lark]
        ZL[Zalo OA]
        ZLP[Zalo Personal]
        WA[WhatsApp]
        SL[Slack]
    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]
        HOOKS[Hook Dispatcher -- Lifecycle Events]
    end

    subgraph Providers["LLM Providers"]
        ANTH[Anthropic -- Native HTTP + SSE]
        OAI["OpenAI-Compatible -- HTTP + SSE<br/>(OpenAI, Gemini, DeepSeek, DashScope, +8)"]
        ACP["ACP -- JSON-RPC 2.0 stdio<br/>(Claude Code, Codex, Gemini CLI)"]
    end

    subgraph Tools["Tool Registry"]
        FS_T[Filesystem]
        EXEC[Exec / Shell]
        WEB[Web Search / Fetch]
        MEM[Memory]
        SUB[Subagent]
        DEL[Delegation]
        TEAM_T[Teams]
        TTS_T[TTS]
        BROW[Browser]
        SK[Skills]
        MCP_T[MCP Bridge]
        CT[Custom Tools]
    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]
        TM_S[TeamStore]
    end

    WS --> WSS
    HTTP --> HTTPS
    TG & DC & FS & ZL & ZLP & WA & SL --> CM

    WSS --> MR
    HTTPS --> MR
    MR --> RL --> RBAC --> AR

    CM --> BUS
    BUS --> SCHED
    SCHED --> AR
    AR --> LOOP

    LOOP --> Providers
    LOOP --> Tools
    Tools --> Store
    LOOP --> Store

3. Module Map

Module Description
internal/gateway/ WebSocket + HTTP server, client handling, method router. Decomposed: gateway_deps, gateway_http_wiring, gateway_events, gateway_lifecycle, gateway_tools_wiring
internal/gateway/methods/ RPC method handlers: chat, agents, 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), OpenAI-compatible (HTTP + SSE, 12+ providers), DashScope (Qwen), ACP (JSON-RPC 2.0 subprocess), Claude CLI, Codex, extended thinking support, retry logic. Shared SSEScanner in providers/sse_reader.go
internal/providers/acp/ ACP protocol implementation: ProcessPool (subprocess lifecycle), ToolBridge (fs/terminal), session management
internal/tools/ Tool registry, filesystem ops, exec/shell, policy engine, subagent, delegation manager, team tools, 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/store/ Store interfaces: SessionStore, AgentStore, ProviderStore, SkillStore, MemoryStore, CronStore, PairingStore, TracingStore, MCPServerStore, TeamStore, ChannelInstanceStore, ConfigSecretsStore. Dual-DB support via Dialect pattern
internal/store/base/ Shared store abstractions: Dialect interface, NilStr, BuildMapUpdate, BuildScopeClause, and other common helpers for both PostgreSQL and SQLite
internal/store/pg/ PostgreSQL implementations (database/sql + pgx/v5)
internal/store/sqlitestore/ SQLite implementations (modernc.org/sqlite) for desktop edition
internal/bootstrap/ System prompt files (AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.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 (forum topics, STT, bot commands), Feishu/Lark (streaming cards, media), Zalo OA, Zalo Personal, Discord, WhatsApp, Slack
internal/mcp/ MCP server bridge (stdio, SSE, streamable-HTTP transports)
internal/scheduler/ Lane-based concurrency control (main, subagent, cron, team lanes) with per-session serialization. Self-spawn and Agent Link callbacks additionally use process-wide child-run admission with per-root limits
internal/memory/ Memory system (pgvector hybrid search)
internal/subagent/ Subagent lifecycle: spawn, roster, task persistence (subagent_tasks table), announce queue (producer-consumer), auto-retry, per-edition rate limiting
internal/permissions/ RBAC policy engine (admin, operator, viewer roles)
internal/store/pg/pairing.go DM/device pairing service (8-character codes, database-backed)
internal/sandbox/ Docker-based code execution sandbox
internal/audio/ Unified audio manager: 4 provider interfaces (TTS active; STT/Music/SFX stubbed/partial). Orchestrates ElevenLabs, OpenAI, Edge, MiniMax TTS providers. internal/tts/ retained as backward-compat alias
internal/tts/ Backward-compat alias layer (24 symbols) — all pre-refactor callers compile unchanged
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/cache/ Caching layer for agent state and provider responses
internal/bus/ Event pub/sub message bus for inter-component communication
internal/knowledgegraph/ Knowledge graph storage and traversal
internal/mcp/ Model Context Protocol bridge/server (stdio, SSE, streamable-HTTP)
internal/media/ Media handling utilities
internal/oauth/ OAuth authentication integration
internal/sessions/ Session management and lifecycle
internal/tasks/ Task management system
internal/upgrade/ Database schema version tracking and migrations
internal/pipeline/ 8-stage pluggable agent pipeline (context → history → prompt → think → act → observe → memory → summarize)
internal/orchestration/ Orchestration primitives: BatchQueue[T] generic for result aggregation, ChildResult capture, media conversion helpers
internal/eventbus/ DomainEventBus: typed event publishing, worker pool, dedup, retry, used by consolidation workers
internal/consolidation/ Memory consolidation workers: episodic (recent facts), semantic (embeddings), dreaming (synthesis), dedup
internal/tokencount/ Token counting: tiktoken BPE counter with fallback, used by pipeline for context tracking
internal/workspace/ Workspace context resolver: 6 scenarios (agent default, team lead, team member, dispatch, subagent, cron)
internal/vault/ Knowledge Vault: wikilinks (semantic mesh), hybrid search (BM25+vector), filesystem sync, L0 auto-injection
internal/channels/whatsapp/ Native WhatsApp channel via whatsmeow (replaces WhatsApp API), QR auth, media handling
internal/hooks/ Agent lifecycle hooks: event dispatcher (sync/async), concrete handlers (command/http), matchers (regex + CEL), audit logging, edition gating, cost safeguards. Events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStart/Stop. Handlers: CommandHandler (shell, Lite-only), HTTPHandler (SSRF-hardened, auth decrypt)
internal/hooks/handlers/ Concrete hook handler implementations: CommandHandler (exec wrapper, JSON I/O, env allowlist, edition recheck), HTTPHandler (SSRF-hardened HTTP client, retry-once on 5xx, no redirects, encrypted auth headers)

3.5 Agent Hooks System

Lifecycle hooks allow agents to perform custom logic at key execution points. The system is event-driven (sync/async), integrated into the 8-stage pipeline, and includes audit logging, edition gating, and security safeguards.

Event Types

Event Stage Sync/Async Purpose
SessionStart ContextStage Async Fires once per session (first turn); before history loading
UserPromptSubmit ContextStage Sync Fires on user message arrival; blocks with hook reason or mutates input
PreToolUse ToolStage Sync Fires before each tool execution; blocks tool or mutates arguments
PostToolUse ToolStage Async Fires after tool result is processed; non-blocking
Stop FinalizeStage Async Fires when session terminates (stop/complete/error)
SubagentStart (Delegate tool) Sync Fires before delegated task spawns; blocks delegation
SubagentStop (Domain events) Async Fires on delegation completion/failure; non-blocking

Handler Types

Handler Edition Config Semantics
Command Lite only cmd, allowedEnvVars Exec shell command; stdin=event JSON; stdout=decision JSON; exit 0→allow, exit 2→block; timeout→block (fail-closed)
HTTP All url, headers POST event JSON to webhook URL; parse response for decision, additionalContext, updatedInput; 5xx retry once; 4xx error no-retry; SSRF-hardened with pinned IP
Prompt Phase 3+ TBD Integrates custom prompting logic (deferred)

Sync vs Async

Sync hooks (UserPromptSubmit, PreToolUse, SubagentStart):

  • Block pipeline until decision received
  • Support Copy-on-Write (COW) staged mutations: updatedInput buffered, committed only if ALL sync hooks succeed
  • Timeout ≤5s per hook; chain total ≤10s wall-time
  • Rejection blocks the step (e.g., tool not executed, user message not processed)

Async hooks (SessionStart, PostToolUse, Stop, SubagentStop):

  • Fire-and-forget via worker pool (default 16 workers, bounded queue 512)
  • Non-blocking (pipeline continues immediately)
  • Timeout per hook; chain budget enforced but no blocking

Security Model

  • Edition gating: CommandHandler only on Lite edition; attempts on Standard/other editions rejected (defense-in-depth)
  • SSRF hardening (HTTPHandler): Caller supplies net.Dialer pinning resolved IP, blocking loopback/link-local/private ranges; no HTTP redirects (CheckRedirect returns ErrUseLastResponse)
  • Auth header encryption: Authorization + other sensitive fields in cfg.Config["headers"] encrypted at rest via AES-256-GCM; decrypted only at HTTP send-time
  • Audit logging: All hook invocations logged to hook_executions table (encrypted, PII-redacted) with dedup_key for idempotency
  • Hook recursion guard (M5): Hook dispatch tracks recursive hook re-entry and rejects it after 3 levels. Agent Link delegation uses separate admission, concurrency, and timeout safeguards.
  • Circuit breaker: Auto-disables hook after 3 consecutive failures in recent window (C4 mitigation)

Pipeline Integration

Dispatcher wired into PipelineDeps.HookDispatcher (nil-safe noop fallback). All 8 stages support hook firing with zero overhead when dispatcher not configured. Example sync hook flow:

1. ContextStage: UserPromptSubmit → Hooks.Fire(sync)
2. Sync hooks buffer mutations (updatedInput)
3. All hooks succeed? → Commit mutations, proceed
4. Any hook rejects? → Discard buffer, abort pipeline, user sees reason

4. 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
WebSocket user_id field in connect handshake Required
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

    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 vector-enabled PG stores
    GW->>PG: 8. Backfill missing embeddings in bounded background batches

    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)
    GW->>GW: 12. Create skills loader + register skill_search tool
    GW->>GW: 13. Wire skill embeddings

    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)

    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, scheduler (4 lanes)
    GW->>Engine: 21. Start skills watcher + inbound consumer
    GW->>Engine: 22. Listen on host:port

Embedding recovery covers memory chunks, knowledge graph entities, active skills, non-cancelled team tasks, active agents, episodic summaries, and vault documents. The process-wide provider is resolved from the master tenant because startup recovery may process semantic content from multiple tenants. Agent, episodic, and vault backfills run sequentially with independent deadlines and bounded batches. They only select rows whose embedding is NULL, re-check the source fields before writing, isolate malformed rows, and can retry remaining rows on the next startup.


7. Database 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 + agent links"] --> W9
    W9["9. Team Tools<br/>team_tasks + team_message + team auto-linking"] --> W10
    W10["10. Team Mailbox<br/>team_message tool for peer communication"]

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: team (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 30 GOCLAW_LANE_MAIN Primary user chat sessions
subagent 50 GOCLAW_LANE_SUBAGENT Spawned subagents
team 100 GOCLAW_LANE_TEAM Agent team/delegation executions
cron 30 GOCLAW_LANE_CRON Scheduled cron jobs

Scaling Beyond the Lane Defaults

main bounds how many users can be answered at once, but raising it alone just moves the queue downstream. These knobs need to move with it:

Setting Default Env Override Why it binds
Postgres max open conns 25 GOCLAW_PG_MAX_OPEN_CONNS Held per query, not per run — undersizing shows up as burst latency when many runs load context at once
Postgres max idle conns 10 GOCLAW_PG_MAX_IDLE_CONNS Clamped to max open
Provider idle conns 100 GOCLAW_HTTP_MAX_IDLE_CONNS Total across all provider hosts
Provider idle conns per host 10 GOCLAW_HTTP_MAX_IDLE_CONNS_PER_HOST When one provider takes nearly all traffic, every request past this limit pays a fresh TCP+TLS handshake. Self-hosted/in-network providers should match main.
Outbound dispatch shards 8 GOCLAW_OUTBOUND_SHARDS See below

Channel-side rate limits are metered by the remote platform and are not covered by any of the above — DingTalk's AI Card API, for instance, meters per app, so card_update_interval_ms decides how many conversations can stream concurrently regardless of local capacity.

Outbound Dispatch

Outbound delivery is sharded by channel + ChatID. A conversation always maps to the same shard and is delivered serially there, so the ordering a run depends on — block replies, retry notices, then the final answer — is preserved. Unrelated conversations run in parallel, so one slow send (a media upload of several seconds) no longer stalls every other reply in the process.

Raising GOCLAW_OUTBOUND_SHARDS past the point where the remote API meters the gateway only moves queueing from this process into theirs.

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:

  1. Broadcast shutdown event to all connected WebSocket clients.
  2. channelMgr.StopAll() -- stop all channel adapters.
  3. cronStore.Stop() -- stop cron scheduler.
  4. sandboxMgr.Stop() + ReleaseAll() -- release Docker containers.
  5. cancel() -- cancel root context, propagating to consumer + scheduler.
  6. Deferred cleanup: flush tracing collector, close memory store, close browser manager, stop scheduler lanes.
  7. 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 postgres_dsn read only from env var

Secret Handling

  • Secrets exist only in env vars or .env.local -- never in config.json.
  • GOCLAW_POSTGRES_DSN is tagged json:"-" 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 fsnotify watcher with 300ms debounce.

11. File Reference

Module Path Purpose
CLI & startup cmd/ Cobra entry point, gateway orchestrator, DB wiring, provider registration, RPC method registration
Gateway server internal/gateway/ WS + HTTP server, client lifecycle, method router, rate limiter
Config internal/config/ JSON5 config loading, env overlay, channel config structs
Store layer internal/store/ Stores container, BaseModel, StoreConfig, GenNewID()

Use grep or your editor's symbol search for specific files.


V3 Architecture (Wave 1 & Wave 2 - dev-v3 branch)

Overview

V3 introduces a pluggable 8-stage pipeline (replacing the monolithic runLoop), an event-driven architecture via DomainEventBus, and advanced memory consolidation. The system maintains backward compatibility via a dual-mode gate at the loop level: agents can opt into v3 pipeline or stay on v2 monolithic loop per-agent via other_config JSONB.

8-Stage Pipeline

Stage Phase Responsibility
ContextStage Setup (once) Inject agent/user/workspace context, compute per-user files, calculate token overhead (system prompt, tools, etc.)
ThinkStage Iteration Build system prompt, filter tools by policy, call LLM
PruneStage Iteration Context pruning (2-pass: soft trim → hard clear), run memory flush if compaction triggered
ToolStage Iteration Execute tool calls (parallel goroutines for multiple calls)
ObserveStage Iteration Process tool results, append to messages
CheckpointStage Iteration Track iteration state, check for loop exit conditions
FinalizeStage Finalize (once) Sanitize output, flush messages, update session metadata

Feature Flags (in agents.other_config JSONB)

Flag Key Type Default Purpose
Pipeline v3_pipeline_enabled bool false Use v3 pipeline instead of v2 monolithic loop
Memory v3_memory_enabled bool false Enable episodic/semantic consolidation workers via DomainEventBus
Retrieval v3_retrieval_enabled bool false Enable Knowledge Vault with wikilinks + hybrid search, L0 auto-injection
Evolution Metrics self_evolution_metrics bool false Track agent metrics for evolution suggestions (tool usage, retrieval patterns)
Evolution Suggestions self_evolution_suggestions bool false Generate and apply evolution suggestions (auto-adapt prompt/tools)

Memory Consolidation System

DomainEventBus drives asynchronous consolidation:

  • Episodic Worker — Extracts facts from recent runs, clusters by topic, stores in episodic_memory table with embeddings
  • Semantic Worker — Reprocesses episodic clusters, generates abstracted summaries, produces semantic_memory entries
  • Dreaming Worker — Synthesizes novel insights from memory clusters, cross-links related memories, drives self-evolution
  • Dedup Worker — Prevents duplicate memory entries, maintains consistency across consolidation cycles

Workspace Context Resolver

Six distinct workspace scenarios:

  1. Agent default — Agent workspace from config, sandbox environment
  2. Team lead — Team workspace as default (agent coordinates tasks)
  3. Team member — Agent workspace with team workspace accessible via WithToolTeamWorkspace()
  4. Dispatch — Temporary workspace from req.TeamWorkspace (one-off delegated task)
  5. Subagent — Inherited workspace from parent agent via context propagation
  6. Cron — Workspace resolved from agent + timezone context at cron trigger time
  • Wikilinks: Bidirectional semantic links ([[related-concept]]) automatically extracted from memories
  • Hybrid Search: BM25 keyword search + vector similarity (pgvector) combined via RRF (reciprocal rank fusion)
  • L0 Auto-Injection: Top-K vault entries injected into system prompt as "relevant context from vault"
  • Filesystem Sync: Vault entries exported as .md files for manual editing, re-imported with change tracking

Audio & Voice System (ElevenLabs + Streaming TTS)

Provider architecture (internal/audio/):

  • Manager: Central orchestrator dispatching TTS/STT/Music/SFX requests to pluggable providers
  • TTSProvider interface: Core text-to-speech contract (blocking, buffered response)
  • StreamingTTSProvider interface: Optional interface for ElevenLabs /stream endpoint (chunked audio via io.ReadCloser)
  • Implementations: ElevenLabs, OpenAI, Edge, MiniMax (phase-gated; STT/Music/SFX partial)

Voice discovery & caching:

  • Voice cache (internal/audio/voice_cache.go): In-memory LRU (cap 1000 tenants, TTL 1h) shared by HTTP /v1/voices + WS voices.list handlers. Thread-safe with sync.Mutex (LRU updates require write lock)
  • Cache miss recovery: HTTP handler auto-fetches from ElevenLabs; WS handler requires prior cache warm (provider resolution deferred to Phase 3)
  • Agent audio context (store.WithAgentAudio / AgentAudioFromCtx): Immutable snapshot bundle (AgentID + OtherConfig JSONB) injected by dispatcher before tool dispatch; consumed by TtsTool.Execute for voice/model resolution

Agent-level configuration (agents.other_config JSONB):

  • tts_voice_id: ElevenLabs voice ID (e.g., "pMsXgVXv3BLzUgSXRplE")
  • tts_model_id: Model choice (eleven_v3, eleven_flash_v2_5, eleven_multilingual_v2, eleven_turbo_v2_5)
  • Resolution precedence: CLI args → agent config → tenant override → provider default

Web UI voice picker (ui/web/src/components/voice-picker.tsx):

  • Combobox with BM25 search, preview playback button (HTML <audio>)
  • Handles preview CDN 403 (expiry) via onError → auto-refresh cache
  • Embedded in PromptSettingsSection, bound to other_config.tts_voice_id

12. Webhook Subsystem

External systems trigger agents or send channel messages via the webhook subsystem without using the gateway token (WebSocket/bearer) protocol.

Components

Component Location Role
Admin CRUD handlers internal/http/webhooks_admin.go Create/list/get/patch/rotate/revoke webhook rows
Auth middleware internal/http/webhooks_auth.go Bearer + HMAC verification, localhost gate, kind check, rate limit, idempotency
LLM endpoint internal/http/webhooks_llm.go POST /v1/webhooks/llm — sync (30s) + async dispatch
Message endpoint internal/http/webhooks_message.go POST /v1/webhooks/message — channel delivery with media
Rate limiter internal/http/webhooks_ratelimit.go Per-webhook + per-tenant token bucket
Idempotency internal/http/webhooks_idempotency.go Idempotency-Key header cache (24h TTL)
Media fetch internal/http/webhooks_media_fetch.go SSRF-guarded HEAD probe + MIME validation
Callback worker internal/webhooks/worker.go Poll loop, claim, agent invoke, HMAC sign, HTTP POST, retry
Backoff internal/webhooks/backoff.go Exponential schedule [30s, 2m, 10m, 1h, 6h] with ±10% jitter
Signing internal/webhooks/sign.go Sign(key, ts, body) → X-Webhook-Signature: t=...,v1=...
Callback limiter internal/webhooks/limiter.go Per-tenant concurrency cap for outbound delivery goroutines
Store interfaces internal/store/ WebhookStore, WebhookCallStore
PG store internal/store/pg/webhook_store.go, webhook_call_store.go Tenant-scoped SQL
SQLite store internal/store/sqlitestore/ Lite edition support
Migrations migrations/ (PG), internal/store/sqlitestore/schema.sql (SQLite) webhooks + webhook_calls tables

Inbound Flow

POST /v1/webhooks/llm or /v1/webhooks/message
  → WebhookAuthMiddleware
      body cap → bearer/HMAC auth → localhost gate → kind check
      → rate limit (per-webhook + per-tenant) → idempotency → inject context
  → Handler (LLM or Message)
      sync: agent.Run(30s timeout) → 200 with output
      async: store WebhookCallData{status=queued} → 202 {call_id}

Outbound Callback Flow (async only)

WebhookWorker.pollOneTenant()
  → calls.ClaimNext(lease_token CAS) → execute goroutine
  → invokeAgent (30s) → build callbackPayload
  → SSRF re-validate callback_url
  → HMAC sign body → POST to callback_url
  → 2xx: UpdateStatus(done, lease_token) | 4xx: failed | 5xx/net: retry with backoff | 429: Retry-After

Lease Token Idempotency: Each call row has a lease_token (UUID). Worker claims the row only if it can CAS the token. On success, worker updates status with the token as proof of ownership. Stale/slow receivers cannot accidentally overwrite a faster delivery attempt.

Secret Encryption: The raw webhook secret is encrypted at rest via AES-256-GCM using the GOCLAW_ENCRYPTION_KEY environment variable (same key as LLM provider credentials). Database leaks do not compromise HMAC material. See docs/webhooks.md § 14 for details.

Security Log Events

Event Level Trigger
security.webhook.auth_failed Warn Invalid bearer / HMAC
security.webhook.hmac_invalid Warn (via auth_failed) HMAC mismatch
security.webhook.body_too_large Warn Body exceeds cap
security.webhook.localhost_only_violation Warn Non-loopback caller on restricted webhook
security.webhook.kind_mismatch Warn Caller path vs webhook kind mismatch
security.webhook.rate_limited Warn Per-webhook or per-tenant rate cap hit
security.webhook.tenant_mismatch Warn Agent UUID does not match webhook tenant
security.webhook.tenant_leak_attempt Warn Channel belongs to different tenant
security.webhook.ssrf_blocked Warn media_url SSRF rejection
security.webhook.callback_ssrf_blocked Warn callback_url SSRF rejection at delivery
security.webhook.worker_panic Error Delivery goroutine panic caught
security.webhook.admin_denied Warn Non-admin access to admin CRUD routes

See docs/webhooks.md for the full integrator reference (auth, retries, HMAC examples).


Cross-References

Document Content
01-agent-loop.md Agent loop detail, v3 pipeline stages, sanitization pipeline, history management, orchestration modes, self-evolution
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, per-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.md Scheduler lanes, cron lifecycle
09-security.md Defense layers, encryption, rate limiting, RBAC, sandbox
10-tracing-observability.md Tracing collector, span hierarchy, OTel export, trace API
11-agent-teams.md Agent teams, task board, mailbox, delegation integration
12-extended-thinking.md Extended thinking, per-provider support, streaming