61832d34ec fix(teams): filter empty chat_id scopes to prevent Select crash (#216)
* fix(channels): start outbound dispatcher before channel check

StartAll() returned early when no channels existed at boot,
skipping the dispatchOutbound goroutine. Channels loaded later
via Reload() assumed the dispatcher was running, causing outbound
messages (agent responses) to never reach Telegram.

Move dispatcher startup before the empty-channel early return so
dynamically loaded channels always have a running consumer.

* feat(ui): add LLM provider warning on overview page and ignore plans dir

Show alert when no providers configured or all disabled, linking to provider settings. Add plans/ to .gitignore.

* feat(onboard): add provider connectivity verification and placeholder seeding

- Add onboard_verify.go: verify API keys via POST to chat/completions
  endpoint (401/403 = fatal, 400/422 = key valid, 5xx = warn)
- Verify all configured providers before seeding in auto-onboard
- Seed disabled placeholder providers (OpenRouter, Synthetic, AliCloud
  API/Sub) for UI discoverability after managed data seeding

* fix: use model ID as display name in OpenAI-compatible provider list

The `owned_by` field (e.g. "system") was incorrectly used as the model
display name, causing all models to show as "system" in the UI dropdown
for providers like AliCloud DashScope.

* fix(chat): show all active agents in chat dropdown

Chat agent selector showed "No agents available" because:
- WS agents.list only returned in-memory router cache (empty in managed mode)
- useEffect had stale [ws] dep that never re-fired after connect

Frontend: switch agent-selector from WS to HTTP /v1/agents API with
proper access control (ListAccessible). Backend: add store-backed
agents.list for WS consumers + Router.IsRunning() helper.

* feat: channel-isolated workspace, resolvePath fix, create_image workspace, summoner Expertise section, bus Topic constants

- Fix resolvePath for nested non-existent dirs (use resolveThroughExistingAncestors)
- Channel-isolated workspace: user_agent_profiles.workspace stores channel prefix,
  used as source of truth with backward compat for existing users
- Loop caches workspace per-user with CacheKindUserWorkspace invalidation via pubsub
- ContractHome/ExpandHome for portable ~-based paths in DB
- create_image saves to workspace/generated/YYYY-MM-DD/ instead of OS temp dir
- SOUL.md template: add ## Expertise section for domain knowledge
- Summoner buildEditPrompt: section guide, complete file output, frontmatter update
- Bus: Topic* constants for Subscribe/Broadcast keys, CacheKind* for payload kinds
- Teams, delegates, sessions, agent links: various enhancements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(telegram): port forum topic features from TS — per-topic config, DM threads, thread fallback, createForumTopic tool, Web UI

Port 4 missing Telegram forum/topic features from TypeScript OpenClaw:

1. Thread-not-found fallback: retry sends without message_thread_id when
   a topic is deleted (sendHTML, sendPhoto, sendVideo, sendAudio,
   sendDocument, stream flush).

2. Per-topic config: hierarchical config resolution (global → wildcard
   group "*" → specific group → specific topic) for groupPolicy,
   requireMention, allowFrom, enabled, skills, systemPrompt.
   New TelegramGroupConfig/TelegramTopicConfig structs, resolveTopicConfig()
   with 10 unit tests.

3. DM topic support: preserve message_thread_id in private chats for
   session isolation. New BuildDMThreadSessionKey, parseRawChatID handles
   🧵 suffix.

4. createForumTopic agent tool: ForumTopicCreator interface decoupled
   from telego, lazy bot resolution via channel manager.

5. Web UI: structured group/topic config form with tri-state booleans
   (Inherit/Yes/No), nested collapsible group and topic entries.

Also fix: forum group pairing reply and approval notification now
correctly set MessageThreadID so messages land in the right topic.
Send() extracts threadID from localKey suffix as fallback for cases
where metadata is absent (e.g. pairing approval via SendToChannel).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: Propagate local key for subagent, delegation, and team messages to enable topic/thread-specific routing and context.

* feat: add a hint to bot reply bodies indicating full content is in session history for LLM context.

* fix(store): handle NULL JSONB columns in MCP server scan (#40)

* fix(store): handle NULL JSONB columns in MCP server scan

Scan JSONB nullable columns (args, headers, env, settings) into *[]byte
instead of directly into json.RawMessage to prevent silent scan failures
when database values are SQL NULL. Also initialize result slices with
make() to return empty JSON arrays instead of null.

* fix(store): keep settings scanning direct since column is NOT NULL

* feat(security): enforce group file writer restrictions + harden exec against env/config leaks

Group writer enforcement (managed mode):
- GroupWriterCache with 5min TTL wrapping AgentStore.ListGroupFileWriters
- Tool-level blocking: write_file, edit, read_file (SOUL.md/AGENTS.md), cron mutations
- System prompt injection: non-writers get refusal instructions + filtered context files
- Cache invalidation via bus events on add/remove writer
- Wired through resolver, loop, gateway_managed, gateway_callbacks

Exec security hardening:
- Block /proc/PID/environ and /proc/self/environ reads (env var exfiltration)
- Block strings on /proc files (binary env dump)
- DenyPaths() on ExecTool: block data dir, .goclaw/, config file from exec commands
- Scrub VIRTUAL_* env vars from tool output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: Refine tool policies with updated groups and aliases, and enhance credential scrubbing by dynamically detecting and redacting server IPs.

* feat: Add tool allow list configuration and enforcement for Telegram channels, allowing per-group/topic tool restrictions.

* feat: Add support for sending and receiving media attachments in the Feishu channel.

* feat: Add Feishu channel configuration options for topic session mode, message limits, and group allowlist, refine existing field descriptions, and create a staging tarball.

* feat(skills): per-agent skill filtering with grant-based access control (#45)

* fix(store): expand tilde in skills storage directory path

The default skillsDir (~/.goclaw/skills-store) was not expanded,
causing os.MkdirAll to fail when creating skill upload directories.

* feat(skills): per-agent skill filtering with grant-based access control (#42)

Wire skill_agent_grants into the agent resolver so each agent only sees
skills explicitly granted to it. Add Skills tab to the web UI for
managing per-agent skill grants with toggle switches.

- Add SkillAccessStore interface to avoid import cycles
- Filter skills in resolver via ListAccessible + filesystem union
- Add GET /v1/agents/:id/skills endpoint with grant status
- Invoke onGrantChange callback to invalidate agent caches on grant/revoke
- Add agent-skills-tab React component with Switch toggles
- Allow read_file access to managed skills-store directory
- Fix rows.Err() propagation in ListAccessible/ListWithGrantStatus

Closes #42

* feat: centralize agent skill access filtering within the skill search tool and implement optimistic UI updates for skill grants

* feat: Mount channel webhook handlers directly on the main gateway.

* feat: restrict /reset command to file writers in Telegram group chats.

* fix: improve spawn tool team_task_id validation and orphan detection

When LLMs call team_tasks create + spawn in parallel, the spawn
tool receives a hallucinated task_id that fails uuid.Parse, causing
a misleading error and bypassing orphan detection.

- Include pending task IDs in spawn error message so LLM can retry
  with the correct UUID
- Move spawn counting to post-execution so failed spawns don't
  increment teamTaskSpawns, allowing orphan detection to fire

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): group pairing uses group-level ID instead of per-user

Changed Lark group pairing to use "group:<chatID>" as sender_id
(matching Telegram pattern) so one approval covers the entire group.
Added approvedGroups in-memory cache to avoid DB queries per message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): mention detection fallback when botOpenID is empty

If probeBotInfo fails (missing bot:read permission), botOpenID is empty
and mention detection always returns false — causing all group messages
to be silently recorded to history instead of processed.

Now treats any mention as bot mention when botOpenID is unknown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(channels): introduce Zalo Personal channel integration (#32)

* feat(channels): implement Zalo Personal Chat (ZCA) protocol layer

Implement complete Zalo Personal Chat integration including:
- Message protocol layer (request/response/event types)
- Connection management with auth flow
- Message sending/receiving with text and media support
- User/group management and sync
- Telegram-style contact and conversation handling
- Comprehensive unit tests with 85%+ coverage

Architecture follows existing channel patterns (Telegram, Feishu) with
raw API calls for session management and message delivery. Includes
error handling, rate limiting awareness, and logging.

* feat(channels): add Zalo Personal channel integration layer

Wire protocol package to GoClaw's channel system:
- channel.go: Channel struct, Start/Stop/Send, listenLoop, message handlers
- auth.go: credential resolution (preloaded > file > QR), persistence
- policy.go: DM/group policy, @mention gating, pairing with debounce
- factory.go: managed mode factory (requires credentials, no QR)
- cmd/gateway.go: register standalone + managed factory

* feat(ui): add Zalo Personal channel type to web dashboard

Add zalo_personal to channel type dropdown, credential fields
(IMEI, cookie, userAgent), and config schema (DM/group policy,
require_mention, allow_from).

* feat(channels): add WebSocket QR login for Zalo Personal channel

Add real-time QR code login flow for zalo_personal channel instances
in managed mode. Users create an instance without credentials, then
trigger QR login from the web dashboard.

Backend:
- New RPC method zalo.personal.qr.start with per-instance mutex
- QR PNG pushed via client-scoped WS events (not broadcast)
- Credentials encrypted and saved to DB on successful scan
- Cache invalidation triggers automatic channel reload/start
- Factory returns nil,nil for missing credentials (skip, not error)
- Instance loader handles nil-channel gracefully

Frontend:
- ZaloPersonalQRDialog with auto-start, retry, and auto-close
- QR button in channel instances table for zalo_personal type
- Credential fields no longer required (auto-populated via QR)

* fix(channels): skip redundant LoginWithCredentials after QR login

QR flow already validates session via qrCheckSession + qrGetUserInfo.
Calling LoginWithCredentials again conflicts with the active QR session
state, causing "empty response" errors. Credentials are validated when
the channel starts instead. Also rename log prefix from "zca" to
"Zalo Personal".

* fix(channels): fix Zalo Personal cookie domain for login API

BuildCookieJar only set cookies for chat.zalo.me but the login API
uses wpa.chat.zalo.me. Cookies weren't sent to the subdomain, causing
"empty response" on channel startup. Now sets cookies for both hosts.

* fix(channels): move UTF-8 check after gzip decompression in Zalo listener

The UTF-8 validity check in decryptAESGCMPayload ran on raw decrypted
bytes before gzip decompression, causing all encType=2 (AES-GCM+gzip)
messages to fail with "decrypted payload is not valid UTF-8".

Move the check to decryptEventData so it runs after all processing
(decryption + decompression) is complete.

* feat(channels): add QR-only onboarding and contacts picker for Zalo Personal

- Remove credential text fields for zalo_personal, show QR auth info banner
- Add has_credentials boolean to HTTP and WS mask functions
- Implement FetchFriends/FetchGroups protocol (encrypted Zalo API)
- Add zalo.personal.contacts WS RPC method with parallel fetch
- Create ZaloContactsPicker component with search, selection, manual entry
- Integrate picker in channel instance edit dialog for allow_from config

* refactor(channels): rename zca error prefix to zalo_personal across protocol package

* fix(channels): unwrap inner response envelope in Zalo contacts decryption

The Zalo API returns double-wrapped responses: outer envelope contains
encrypted base64 data, which when decrypted yields another Response
envelope with error_code and data fields. The decryptDataField helper
was returning the raw decrypted bytes without unwrapping the inner
envelope, causing json unmarshal failures when parsing friends/groups.

* fix(channels): pass version 0 for group details to get full data

The Zalo group info endpoint uses a version-based caching mechanism.
Passing the actual version from step 1 causes the server to return
the group in "unchangedsGroup" with empty "gridInfoMap". By passing
version 0 for all groups, we force the server to return full group
info including name, avatar, and member count.

* fix(ui): auto-load contacts on modal reopen to resolve display names

When the edit modal is reopened with already-selected contact IDs,
contacts are now auto-fetched so badges show display names instead
of raw numeric IDs.

* fix(channels): handle gzip-compressed response in Zalo SendMessage

SendMessage used io.ReadAll + json.Unmarshal directly but the response
is gzip-compressed (Accept-Encoding: gzip header). Use readJSON() which
handles gzip decompression, fixing "invalid character '\x1f'" errors.

* fix(channels): decrypt encrypted send response in Zalo SendMessage

The Zalo send message API response is encrypted like all other endpoints.
Parse outer envelope, decrypt the data field, then extract msgId from
the decrypted inner response.

* feat(channels): improve Zalo listener reliability and UI channel wizard

- Migrate WebSocket client from gorilla to coder/websocket, eliminating
  unsafe/reflect hacks for RSV1 decompression and buffer inspection
- Add channel-level restart with exponential backoff (2s→60s cap, max 10)
  so channels auto-recover instead of stopping permanently
- Reset listener retry counters after 60s stable connection to prevent
  long-lived connections from exhausting retry budget
- Add code 3000 (duplicate session) recovery with 60s initial delay
- Detect silent disconnects via read deadline (2.5x ping interval)
- Fix Stop() to always cancel context, preventing reconnect timer leaks
- Refactor UI channel form into wizard-based flow with registry pattern
- Auto-refresh channel status after create/update dialog closes

* refactor(channels): move Zalo RPC methods to zalomethods package

Move Zalo personal channel RPC handlers from internal/gateway/methods to
internal/channels/zalo/personal/zalomethods, improving code organization
and removing prefix redundancy. Rename types: ZaloPersonalQRMethods →
QRMethods, ZaloPersonalContactsMethods → ContactsMethods.

- Move zalo_personal_qr.go → zalomethods/qr.go
- Move zalo_personal_contacts.go → zalomethods/contacts.go
- Update imports in cmd/gateway.go (2 call sites)
- Update internal/channels/zalo/personal imports

* feat(channels): add typing indicator to Zalo Personal channel

Show "typing..." in Zalo while the LLM processes messages, matching
the Telegram/Discord pattern. Uses the shared typing.Controller with
4s keepalive (Zalo typing expires ~5s) and 60s TTL safety net.

* feat(channels): handle image attachments in Zalo Personal channel

- Add Raw field to Content struct to preserve non-string JSON payloads
- Add Attachment struct with IsImage() detection (ext + Zalo CDN paths)
- Add AttachmentText() for human-readable placeholders (image/file/other)
- Download image attachments to temp files for agent vision pipeline
- Non-image files get text placeholder only (no download)
- Fix URL query param stripping in file extension detection

* fix(channels): switch Zalo WS client to gorilla/websocket with cookie jar fix

coder/websocket did not propagate session cookies for wss:// URLs,
causing Zalo backend to reject connections with "zpw_sek not found".
Switch to gorilla/websocket which handles wss→https scheme conversion
natively. Add wsJar safety wrapper and fix Close() mutex consistency.

Also update Makefile `up` target to use --no-cache builds.

* fix(channels): inject cookies manually for Zalo WS connection

Replace wsJar wrapper with direct cookie injection from chat.zalo.me
base domain. Fixes host-only cookies (zpw_sek) not matching WS
subdomains (ws*-msg.chat.zalo.me) due to Go cookiejar limitations.

* fix(channels): harden Zalo Personal channel security and concurrency

- Add SSRF protection to downloadFile using CheckSSRF (URL validation,
  private IP blocking, DNS pinning) with context and 30s timeout
- Protect c.sess/c.listener with sync.RWMutex to eliminate data races
  during restart; add thread-safe session()/getListener() accessors
- Add stopped flag + reconnTimer to Listener to prevent zombie reconnects
  after Stop(); timer cancelled on Stop(), checked before Start()
- Fix QR flow using context.Background() detached from WS client; now
  derives from parent ctx so flow cancels on client disconnect
- Set initial 30s read deadline for cipher key handshake to prevent
  indefinite blocking before ping loop starts
- Use defer in WSClient.Close() to prevent connection leak on panic
- Document ReadMessage ctx limitation and two-layer reconnect design

* chore: remove unused gobwas/ws dependency from go.mod

gobwas/ws was a leftover from the previous coder/websocket usage,
no longer imported by any Go source files.

* fix(channels): align Zalo Personal policy defaults across UI and backend

Policy defaults were inconsistent across three layers causing group/DM
allowlist enforcement to silently fail. New() applied "allowlist" default
to local vars but never wrote back to config; checkGroupPolicy() then
read empty string and defaulted to "open", bypassing the allowlist.
UI Select components displayed schema defaults visually without
persisting them to configValues, so DB config never stored the policy.

* feat: Resolve Feishu message mentions by stripping bot mentions and replacing user mentions with names.

* fix(zalo_personal): data races in policy, directory perms, Makefile --no-cache

- Fix 2 data races in policy.go: sendPairingReply and checkGroupPolicy
  accessed c.sess without the read lock — use c.session() accessor
- Fix credentials directory permissions: 0755 → 0700 to prevent other
  users from listing contents
- Revert Makefile --no-cache (debugging leftover that disables Docker
  layer caching)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: mid-loop context compaction + team task user scoping

Add mid-loop compaction to prevent context overflow during long-running
delegated agent runs (e.g. 225K+ tokens causing DashScope timeouts).
Uses same threshold as maybeSummarize (contextWindow * historyShare)
with actual PromptTokens from LLM response. Only compacts the in-memory
messages slice; pendingMsgs preserves full history for session flush.

Add user_id/channel columns to team_tasks so end users only see their
own tasks. Delegate/system channels bypass the filter to see all tasks.
Group chats use the group-scoped UserID (group:channel:chatID) so all
members share visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: bump RequiredSchemaVersion to 8 for team_tasks user_id migration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Handle bot commands before enriching content to prevent parsing issues with reply/forward context.

* fix(web_fetch): replace regex HTML parsing with DOM-based extraction

Regex-based htmlToMarkdown/htmlToText leaked CSS, JS, and non-content
elements. Replaced with golang.org/x/net/html DOM parser that extracts
<body> only and skips 16 non-content element types (script, style,
noscript, svg, template, iframe, form, nav, footer, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(delegation): add pending tasks hint when team_task_id not found

LLM models often hallucinate UUIDs when delegating, passing a wrong
team_task_id that doesn't exist. Previously the error was bare
("task not found") with no guidance, causing the model to get stuck.

Now the error includes a list of pending tasks so the model can
self-correct. Also refactored prepareDelegation to resolve team once
instead of 3 separate GetTeamForAgent calls, and extracted
pendingTasksHint() to deduplicate hint-building logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web_fetch): increase read limit and add empty content detection

- Read limit: maxChars*4 → max(maxChars*10, 512KB) to handle pages with
  large <head> sections (WordPress sites often have 30-50KB+ heads)
- Add warning message when HTML extraction returns empty despite non-empty
  response body (bot protection, JS-only pages)
- Enable HTTP/2 via ForceAttemptHTTP2 on custom Transport

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(delegation): notify user on failure before retry + pending tasks hint

Two improvements to delegation UX:

1. When a delegation fails, the announce now instructs the coordinator
   to send a brief friendly message to the user before retrying, so
   users aren't left waiting in silence for minutes.

2. When spawn is called with a wrong team_task_id (LLM hallucinated
   UUID), the error now includes a list of pending tasks so the model
   can self-correct. Also refactored prepareDelegation to resolve team
   once instead of 3 separate GetTeamForAgent calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(delegation): clear senderID in sync delegation context

Sync delegations inherited the caller's senderID, causing the delegate
agent to check group writer permissions against its own (empty) writer
list instead of bypassing like async delegations do. This resulted in
"permission denied: only file writers can modify files" errors when
delegate agents tried to write files.

Fix: clear senderID from the sync delegation context so it behaves
consistently with async delegations (context.Background has no
senderID). All 4 downstream usages of SenderIDFromContext are
group-writer-related and correctly bypass when senderID is empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: overhaul documentation for v0.2–v0.5 features

Add new docs for agent teams (11) and extended thinking (12).
Major rewrite of channels/messaging (05) with Telegram forum topics,
Feishu streaming cards, Zalo Personal. Update providers (02), tools (03),
bootstrap/skills (07), security (09), architecture (00), scheduling (08),
and tracing (10) with current implementation details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: Update README

* fix(teams): scope ListTasks by userID to prevent cross-group task leaking

Tasks from one Telegram group were being injected into another group's
session because the pending-task hint and /tasks command queried all
tasks team-wide without filtering by the group-scoped userID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(telegram): add draft streaming infrastructure + split dm/group stream config

- Add sendMessageDraft transport (disabled pending Telegram client fix for
  "reply to deleted message" artifact — tdesktop#10315, bugs.telegram.org/c/561)
- Split stream_mode into dm_stream/group_stream boolean flags (both default false)
- DM messages no longer set reply_to_message_id (cleaner UX, matching TS)
- Progressive placeholder editing for DMs: "Thinking..." → stream chunks → final
- Update web UI with separate DM/Group streaming toggles

fix(agent): prevent false MEDIA: detection in tool output

parseMediaResult() used strings.Index to find "MEDIA:" anywhere in tool output,
causing false positives when external content (e.g. GitHub releases page)
contained commit messages like "return MEDIA: path from screenshot".
Changed to strings.HasPrefix to only match at start of output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(delegation): inject dependency results + guard completed task reuse

- Add injectDependencyResults() to auto-inject blocked_by task results
  into delegation context, so delegatees receive prior results without
  needing to search for them (orchestrator-worker pattern)
- Guard against spawning with completed/cancelled team_task_id to
  enforce one-task-per-delegation rule
- Add cross-user scope guard in prepareDelegation() to prevent
  cross-group task leak (delegate/system channels bypass by design)
- Track CompletedTaskIDs in DelegateArtifacts and include them in
  announce messages so lead agent knows not to reuse completed IDs
- UI: reduce trace detail preview heights for better readability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: Prioritize timeout error handling and renumber error classification.

* fix(chat): show all active agents in chat dropdown (#48)

* fix(channels): start outbound dispatcher before channel check

StartAll() returned early when no channels existed at boot,
skipping the dispatchOutbound goroutine. Channels loaded later
via Reload() assumed the dispatcher was running, causing outbound
messages (agent responses) to never reach Telegram.

Move dispatcher startup before the empty-channel early return so
dynamically loaded channels always have a running consumer.

* feat(ui): add LLM provider warning on overview page and ignore plans dir

Show alert when no providers configured or all disabled, linking to provider settings. Add plans/ to .gitignore.

* feat(onboard): add provider connectivity verification and placeholder seeding

- Add onboard_verify.go: verify API keys via POST to chat/completions
  endpoint (401/403 = fatal, 400/422 = key valid, 5xx = warn)
- Verify all configured providers before seeding in auto-onboard
- Seed disabled placeholder providers (OpenRouter, Synthetic, AliCloud
  API/Sub) for UI discoverability after managed data seeding

* fix: use model ID as display name in OpenAI-compatible provider list

The `owned_by` field (e.g. "system") was incorrectly used as the model
display name, causing all models to show as "system" in the UI dropdown
for providers like AliCloud DashScope.

* fix(chat): show all active agents in chat dropdown

Chat agent selector showed "No agents available" because:
- WS agents.list only returned in-memory router cache (empty in managed mode)
- useEffect had stale [ws] dep that never re-fired after connect

Frontend: switch agent-selector from WS to HTTP /v1/agents API with
proper access control (ListAccessible). Backend: add store-backed
agents.list for WS consumers + Router.IsRunning() helper.

* fix(security): prevent agent list leaking on empty userID or store error

- Return error instead of falling through to unfiltered router cache
  when userID is empty or DB query fails in managed mode
- Add empty-string guard to isOwnerUser to prevent false owner match

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: ntduc <ntduc@cpp.ai.vn>
Co-authored-by: viettranx <viettranx@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: correct cron job delivery to Discord channels (#47)

- Override LLM-provided channel ID with context value to prevent
  misrouted deliveries (LLM was confusing guild ID with channel ID)
- Send cron reminder message directly instead of agent response
  so reminders appear as bot notifications in Discord

* fix(teams): filter empty chat_id scopes to prevent Select crash

Radix UI Select.Item requires non-empty value prop. Scope entries
with empty chat_id caused uncaught error on team detail page.

---------

Co-authored-by: ntduc <ntduc@cpp.ai.vn>
Co-authored-by: viettranx <viettranx@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Thieu Nguyen <79964592+thieung@users.noreply.github.com>
Co-authored-by: Duc Nguyen <me@vanducng.dev>
Co-authored-by: Winter279 <103654924+Winter279@users.noreply.github.com>
2026-03-16 07:16:32 +07:00
2026-03-15 14:55:47 +07:00

GoClaw

GoClaw

Go PostgreSQL Docker WebSocket OpenTelemetry Anthropic OpenAI License: MIT

GoClaw is a multi-agent AI gateway that connects LLMs to your tools, channels, and data — deployed as a single Go binary with zero runtime dependencies. It orchestrates agent teams, inter-agent delegation, and quality-gated workflows across 13+ LLM providers with full multi-tenant isolation.

A Go port of OpenClaw with enhanced security, multi-tenant PostgreSQL, and production-grade observability.

What Makes It Different

  • Agent Teams & Orchestration — Teams with shared task boards, inter-agent delegation (sync/async), conversation handoff, evaluate-loop quality gates, and hybrid agent discovery
  • Multi-Tenant PostgreSQL — Per-user workspaces, per-user context files, encrypted API keys (AES-256-GCM), isolated sessions — the only Claw project with DB-native multi-tenancy
  • Single Binary — ~25 MB static Go binary, no Node.js runtime, <1s startup, runs on a $5 VPS
  • Production Security — 5-layer defense: rate limiting, prompt injection detection, SSRF protection, shell deny patterns, AES-256-GCM encryption
  • 13+ LLM Providers — Anthropic (native HTTP+SSE with prompt caching), OpenAI, OpenRouter, Groq, DeepSeek, Gemini, Mistral, xAI, MiniMax, Cohere, Perplexity, DashScope (Qwen), Bailian Coding + Claude CLI (stdio + MCP bridge), Codex (gpt-5.3-codex via OAuth)
  • 7 Messaging Channels — Telegram (forum topics, STT), Discord, Slack, Zalo OA, Zalo Personal (DM + groups), Feishu/Lark (streaming cards, media), WhatsApp with /stop and /stopall commands
  • Extended Thinking — Per-provider thinking mode (Anthropic budget tokens, OpenAI reasoning effort, DashScope thinking budget) with streaming support

Claw Ecosystem

Resource Footprint:

OpenClaw ZeroClaw PicoClaw GoClaw
Language TypeScript Rust Go Go
Binary size 28 MB + Node.js 3.4 MB ~8 MB ~25 MB (base) / ~36 MB (+ OTel)
Docker image — — — ~50 MB (Alpine)
RAM (idle) > 1 GB < 5 MB < 10 MB ~35 MB
Startup > 5 s < 10 ms < 1 s < 1 s
Target hardware $599+ Mac Mini $10 edge $10 edge $5 VPS+

Feature Matrix:

Feature OpenClaw ZeroClaw PicoClaw GoClaw
Multi-tenant (PostgreSQL) — — — ✅
Hooks system — — — ✅ Command + agent evaluators
MCP integration — (uses ACP) — — ✅ (stdio/SSE/streamable-http)
Agent teams — — — ✅ Task board + mailbox
Agent handoff — — — ✅ Conversation transfer
Evaluate loop — — — ✅ Generator-evaluator cycle
Quality gates — — — ✅ Hook-based validation
Security hardening ✅ (SSRF, path traversal, injection) ✅ (sandbox, rate limit, injection, pairing) Basic (workspace restrict, exec deny) ✅ 5-layer defense
OTel observability ✅ (opt-in extension) ✅ (Prometheus + OTLP) — ✅ OTLP (opt-in build tag)
Prompt caching — — — ✅ Anthropic + OpenAI-compat
Knowledge graph — — — ✅ LLM extraction + traversal
Skill system ✅ Embeddings/semantic ✅ SKILL.md + TOML ✅ Basic ✅ BM25 + pgvector hybrid
Lane-based scheduler ✅ Bounded concurrency — ✅ (main/subagent/delegate/cron + concurrent group runs)
Messaging channels 37+ 15+ 10+ 7+
Companion apps macOS, iOS, Android Python SDK — Web dashboard
Live Canvas / Voice ✅ (A2UI + TTS/STT) — Voice transcription TTS (4 providers)
LLM providers 10+ 8 native + 29 compat 13+ 13+
Per-user workspaces ✅ (file-based) — — ✅ (PostgreSQL)
Encrypted secrets — (env vars only) ✅ ChaCha20-Poly1305 — (plaintext JSON) ✅ AES-256-GCM in DB

GoClaw unique strengths: Only project with multi-tenant PostgreSQL, agent teams, conversation handoff, evaluate-loop quality gates, hooks system, knowledge graph, and MCP protocol support.

Architecture

graph TB
    subgraph Clients
        WEB["Web Dashboard<br/>(React SPA)"]
        TG["Telegram"]
        DC["Discord"]
        SL["Slack"]
        FS["Feishu/Lark"]
        ZL["Zalo OA"]
        ZLP["Zalo Personal"]
        API["HTTP API"]
    end

    subgraph Gateway["GoClaw Gateway"]
        direction TB
        WS["WebSocket RPC"] & REST["HTTP Server"] & CM["Channel Manager"]
        WS & REST & CM --> BUS["Message Bus"]
        BUS --> SCHED["Lane-based Scheduler<br/>main · subagent · delegate · cron"]
        SCHED --> ROUTER["Agent Router"]
        ROUTER --> LOOP["Agent Loop<br/>think → act → observe"]
        LOOP --> TOOLS["Tool Registry<br/>fs · exec · web · memory · delegate · team · mcp · custom"]
        LOOP --> LLM["LLM Providers<br/>Anthropic (native + prompt caching) · OpenAI-compat (12+)"]
    end

    subgraph Storage
        PG["PostgreSQL 18 + pgvector<br/>(multi-tenant)"]
    end

    WEB --> WS
    TG & DC & SL & FS & ZL & ZLP --> CM
    API --> REST
    LOOP --> PG

Multi-Agent Orchestration

GoClaw supports four orchestration patterns for agent collaboration, all managed through explicit permission links.

Agent Delegation

Agent delegation enables named agents to delegate tasks to other agents — each running with its own identity, tools, LLM provider, and context files. Unlike subagents (anonymous clones of the parent), delegation targets are fully independent agents.

flowchart TD
    USER((User)) -->|"Research competitor pricing"| SUPPORT

    subgraph TEAM["Agent Team"]
        SUPPORT["Support Bot<br/>(Claude Haiku)"]
        RESEARCH["Research Bot<br/>(GPT-4)"]
        WRITER["Content Writer<br/>(Claude Sonnet)"]
        BILLING["Billing Bot<br/>(Gemini)"]
    end

    SUPPORT -->|"sync: wait for answer"| RESEARCH
    RESEARCH -->|"result"| SUPPORT
    SUPPORT -->|"async: don't wait"| WRITER
    WRITER -.->|"announce when done"| SUPPORT
    SUPPORT -.-x|"no link"| BILLING

    SUPPORT -->|"final answer"| USER

    style USER fill:#e1f5fe
    style SUPPORT fill:#fff3e0
    style RESEARCH fill:#e8f5e9
    style WRITER fill:#f3e5f5
    style BILLING fill:#ffebee
Mode How it works Best for
Sync Agent A asks Agent B and waits for the answer Quick lookups, fact checks
Async Agent A asks Agent B and moves on. B announces the result later Long tasks, reports, deep analysis

Permission Links — Agents communicate through explicit agent links with access control:

# One-way: support-bot can delegate TO research-bot
agents.links.create {
  "sourceAgent": "support-bot",
  "targetAgent": "research-bot",
  "direction": "outbound",
  "maxConcurrent": 3
}

# Bidirectional: both agents can delegate to each other
agents.links.create {
  "sourceAgent": "support-bot",
  "targetAgent": "content-writer",
  "direction": "bidirectional"
}
Direction Meaning
outbound Source can delegate TO target
inbound Target can delegate TO source
bidirectional Both agents can delegate to each other

Concurrency Control — Two layers prevent any agent from being overwhelmed:

Layer Config Example
Per-link agent_links.max_concurrent support → research: max 3
Per-agent agents.other_config.max_delegation_load research-bot: max 5 total

Per-User Restrictions — The settings JSONB on agent links supports per-user deny/allow lists.

Agent Discovery — Each agent has a frontmatter field for discovery. With ≤15 targets, auto-generated AGENTS.md is injected into context. With >15 targets, agents use delegate_search for hybrid FTS + semantic search.

Delegation vs Subagents
Aspect Subagents Agent Delegation
Target Anonymous clone of parent Named agent with own identity
Provider/Model Inherited from parent Target's own configuration
Tools Parent's tools minus deny list Target's own tool registry + policy
Context files Simplified system prompt Target's own SOUL.md, IDENTITY.md, etc.
Session Shared with parent Isolated (fresh per delegation)
Permission Depth-based limits only Explicit agent_links with direction
User control None Per-user deny/allow via settings JSONB
Concurrency Global + per-parent limits Per-link + per-target-agent limits

Agent Teams

Teams enable coordinated multi-agent workflows with a shared task board and peer-to-peer messaging.

flowchart TD
    USER((User)) -->|message| LEAD

    subgraph TEAM["Agent Team"]
        LEAD["Lead Agent<br/>(orchestrator)"]
        A1["Specialist A"]
        A2["Specialist B"]
        A3["Specialist C"]
    end

    subgraph BOARD["Shared Task Board"]
        T1["Task 1: pending"]
        T2["Task 2: in_progress<br/>owner: A1"]
        T3["Task 3: blocked_by T2"]
    end

    subgraph MAIL["Team Mailbox"]
        M1["A1 → LEAD: status update"]
        M2["LEAD → ALL: broadcast"]
    end

    LEAD -->|"create tasks"| BOARD
    A1 -->|"claim"| T2
    T2 -.->|"auto-unblocks"| T3
    A1 -->|"send message"| MAIL
    LEAD -->|"broadcast"| MAIL
    LEAD -->|final answer| USER

    style USER fill:#e1f5fe
    style LEAD fill:#fff3e0
    style A1 fill:#e8f5e9
    style A2 fill:#e8f5e9
    style A3 fill:#e8f5e9
  • Team roles — Lead agent orchestrates work, member agents execute tasks
  • Shared task board — Create, claim, complete, search tasks with blocked_by dependencies. Atomic claiming prevents double-assignment
  • Team mailbox — Direct peer-to-peer messaging (send, broadcast, read unread)
  • Tools: team_tasks for task management, team_message for mailbox

Agent Handoff

Handoff transfers conversation control from one agent to another. Unlike delegation (where A stays in control), handoff means B completely takes over the user conversation.

flowchart LR
    subgraph Delegation["Delegation (A stays in control)"]
        direction TB
        DA["Agent A"] -->|"delegate task"| DB["Agent B"]
        DB -->|"return result"| DA
        DA -->|"reply to user"| DU((User))
    end

    subgraph Handoff["Handoff (B takes over)"]
        direction TB
        HA["Agent A"] -->|"handoff"| HB["Agent B"]
        HB -->|"now handles user"| HU((User))
    end
  • Routing override — Sets a routing rule so all future messages go to the target agent
  • Context transfer — Conversation context is passed to the new agent
  • Revert — handoff(action="clear") returns routing to the original agent

Evaluate Loop

The evaluate loop orchestrates a generator-evaluator feedback cycle between two agents for quality-gated output.

flowchart LR
    TASK["Task + Criteria"] --> GEN["Generator<br/>Agent"]
    GEN -->|"output"| EVAL{"Evaluator<br/>Agent"}
    EVAL -->|"APPROVED"| RESULT["Final Output"]
    EVAL -->|"REJECTED + feedback"| GEN
    EVAL -.->|"max rounds hit"| WARN["Last output + warning"]
  • Configurable rounds — Default 3, max 5 revision cycles
  • Custom pass criteria — Define what "approved" means for the evaluator
  • Tool: evaluate_loop(generator="writer-bot", evaluator="qa-bot", task="...", pass_criteria="...")

Quality Gates

Quality gates validate agent output before it reaches users. Configured in agent other_config:

{
  "quality_gates": [
    {
      "event": "delegation.completed",
      "type": "agent",
      "agent": "qa-reviewer",
      "block_on_failure": true,
      "max_retries": 2
    }
  ]
}
  • Hook types: command (shell exit code: 0 = pass) or agent (delegate to reviewer agent)
  • Blocking — Failed gates can block output and trigger automatic retry with feedback
  • Recursion-safe — Quality gate evaluators skip their own gates to prevent infinite loops

Features

LLM Providers

  • 13+ providers — OpenRouter, Anthropic, OpenAI, Groq, DeepSeek, Gemini, Mistral, xAI, MiniMax, Cohere, Perplexity, DashScope (Qwen), Bailian Coding, and any OpenAI-compatible endpoint
  • Anthropic native — Direct HTTP+SSE integration with prompt caching (cache_control) for ~90% cost reduction on repeated prefixes. Also supports Claude CLI mode (stdio + MCP bridge with session management)
  • OpenAI-compatible — Automatic prompt caching for OpenAI, MiniMax, OpenRouter (cache metrics tracked in traces). Also supports Codex mode (gpt-5.3-codex via OAuth with "phase" metadata)
  • Extended thinking — Per-provider thinking mode: Anthropic (budget tokens), OpenAI-compat (reasoning effort), DashScope (thinking budget) with streaming support

Agent Orchestration

  • Agent loop — Think-act-observe cycle with tool use, session history, and auto-summarization
  • Subagents — Spawn child agents with different models for parallel task execution
  • Agent delegation — Sync/async inter-agent task delegation with permission links, concurrency limits, and per-user restrictions
  • Agent teams — Shared task boards with dependencies, team mailbox, and coordinated multi-agent workflows
  • Agent handoff — Transfer conversation control between agents with routing overrides
  • Evaluate loop — Generator-evaluator feedback cycles for quality-gated output
  • Quality gates — Hook-based output validation with command or agent evaluators
  • Delegation history — Queryable audit trail of all inter-agent delegations
  • Concurrent execution — Lane-based scheduler (main/subagent/delegate/cron), adaptive throttle for group chats

Tools & Integrations

  • 30+ built-in tools — File system, shell exec, web search/fetch, memory, browser automation, TTS, and more
  • MCP integration — Connect external MCP servers via stdio, SSE, or streamable-http with per-agent/per-user grants
  • Hooks system — Event-driven hooks with command evaluators (shell exit code) and agent evaluators (delegate to reviewer) for output validation

Messaging Channels

  • Telegram — Full integration with streaming, rich formatting (HTML, tables, code blocks), reactions, media, forum topics (per-topic config and session isolation), speech-to-text, bot commands, group file writer restrictions
  • Slack — Channel integration with bot commands
  • Feishu/Lark — Streaming card updates, media attachments (images/files), mention resolution, topic session mode
  • Zalo OA — Official Account integration for DM conversations
  • Zalo Personal — Unofficial reverse-engineered protocol supporting DM + group messages with restrictive default policies
  • Discord, WhatsApp — Channel adapters with /stop and /stopall commands
  • Persistent pending messages — Group chat messages persisted to PostgreSQL with auto-compaction (LLM summarization) when queues exceed threshold

Knowledge & Memory

  • Skills — SKILL.md-based knowledge base with BM25 + embedding hybrid search (pgvector)
  • Long-term memory — pgvector hybrid search (full-text + vector similarity) with admin dashboard for CRUD, search, and bulk re-indexing
  • Knowledge graph — LLM-powered entity/relationship extraction from memory, graph traversal (recursive CTE, max depth 3), and force-directed visualization. Agent tool: knowledge_graph_search

Infrastructure

  • Cron scheduling — at, every, and cron expression syntax for scheduled agent tasks
  • Browser automation — Headless Chrome via Rod for web interaction
  • Text-to-Speech — OpenAI, ElevenLabs, Edge, MiniMax providers
  • Docker sandbox — Isolated code execution in containers
  • Tracing — LLM call tracing with cache metrics, span metadata, and optional OpenTelemetry OTLP export
  • Tailscale — Optional VPN mesh listener for secure remote access (build-tag gated)

Security

  • Rate limiting — Token bucket per user/IP, configurable RPM
  • API key management — Multi-key auth with RBAC scopes (admin, read, write, approvals, pairing), SHA-256 hashed storage, optional expiry, revocation
  • Prompt injection detection — 6-pattern regex scanner (detection-only, never blocks)
  • Credential scrubbing — Auto-redact API keys, tokens, passwords from tool outputs
  • Shell deny patterns — Blocks curl|sh, reverse shells, eval $(), base64|sh
  • SSRF protection — DNS pinning, blocked private IPs, blocked hosts
  • AES-256-GCM — Encrypted provider API keys in database
  • Browser pairing — Token-free browser auth with admin-approved pairing codes

Web Dashboard

  • Agent management, traces & spans viewer, skills, teams, MCP servers, pairing approval, memory management (CRUD + search + chunking), knowledge graph (table + force-directed visualization), pending messages dashboard, API key management, and interactive API documentation (Swagger UI)

Quick Start

git clone https://github.com/nextlevelbuilder/goclaw.git
cd goclaw

From Source

# Build
make build

# Interactive setup wizard
./goclaw onboard

# Start the gateway
source .env.local && ./goclaw

With Docker

1. Prepare environment:

# Generate .env with auto-generated secrets (GOCLAW_ENCRYPTION_KEY, GOCLAW_GATEWAY_TOKEN)
chmod +x prepare-env.sh
./prepare-env.sh

The script creates .env from .env.example, auto-generates GOCLAW_ENCRYPTION_KEY and GOCLAW_GATEWAY_TOKEN, and checks for a provider API key. Add at least one GOCLAW_*_API_KEY to .env before starting.

2. Start services:

# Recommended: Gateway + Web Dashboard (http://localhost:3000)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml -f docker-compose.selfservice.yml up -d --build

# Without dashboard
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d --build

# + OpenTelemetry tracing (Jaeger at http://localhost:16686)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml -f docker-compose.otel.yml up -d --build

# + Tailscale (secure remote access)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml -f docker-compose.tailscale.yml up -d --build

When GOCLAW_*_API_KEY environment variables are set, the gateway auto-onboards without interactive prompts — it detects the provider, generates a gateway token, connects to Postgres, runs migrations, and seeds default data.

Auto-onboard detects the first available API key in priority order: OpenRouter → Anthropic → OpenAI → Groq → DeepSeek → Gemini → Mistral → xAI → MiniMax → Cohere → Perplexity. Override with GOCLAW_PROVIDER and GOCLAW_MODEL. Memory is auto-enabled with embedding support if an OpenAI, OpenRouter, or Gemini key is detected.

Minimum .env:

GOCLAW_OPENROUTER_API_KEY=sk-or-your-key    # Required: at least one provider key
GOCLAW_GATEWAY_TOKEN=...                     # Auto-generated by prepare-env.sh
GOCLAW_ENCRYPTION_KEY=...                    # Auto-generated by prepare-env.sh
# GOCLAW_PROVIDER=openrouter                 # Optional: override default provider
# GOCLAW_MODEL=anthropic/claude-sonnet-4     # Optional: override default model
# POSTGRES_PASSWORD=your-secure-password     # Optional: defaults to "goclaw"

Deployment

GoClaw requires PostgreSQL with pgvector. Designed for multi-user and multi-tenant deployments with per-user isolation — each user gets their own context files, session history, and workspace.

# Set up database
export GOCLAW_POSTGRES_DSN="postgres://user:pass@localhost:5432/goclaw?sslmode=disable"
export GOCLAW_ENCRYPTION_KEY=$(openssl rand -hex 32)

# Run database upgrade (schema migrations + data hooks)
./goclaw upgrade

# Start gateway
./goclaw

Features:

  • Per-user context files and workspaces (user_context_files table)
  • Agent types: open (per-user workspace) vs predefined (shared context)
  • Agent teams, delegation, handoff, evaluate loops, quality gates
  • LLM call tracing with spans and prompt cache metrics
  • MCP server integration with per-agent and per-user access grants
  • Event-driven hooks for agent lifecycle with command and agent evaluators
  • Embedding-based skill search (hybrid BM25 + pgvector)
  • Web dashboard for agents, traces, skills, teams, and MCP servers
  • API key encryption (AES-256-GCM)

Installation

Prerequisites

  • Go 1.25+
  • PostgreSQL 18 with pgvector
  • Docker (optional, for sandbox and containerized deployment)

Build

# Production build (~25MB binary, static, stripped symbols)
CGO_ENABLED=0 go build -ldflags="-s -w" -o goclaw .

# With OpenTelemetry support (~36MB binary)
CGO_ENABLED=0 go build -ldflags="-s -w" -tags otel -o goclaw .

# With Tailscale support (~54MB binary)
CGO_ENABLED=0 go build -ldflags="-s -w" -tags tsnet -o goclaw .

# With Redis cache backend
CGO_ENABLED=0 go build -ldflags="-s -w" -tags redis -o goclaw .

# With both OTel + Tailscale
CGO_ENABLED=0 go build -ldflags="-s -w" -tags "otel,tsnet" -o goclaw .

Binary size comparison across the Claw ecosystem:

Build Binary Size Docker Image Notes
GoClaw (base) ~25 MB ~50 MB CGO_ENABLED=0 go build -ldflags="-s -w"
GoClaw (+ OTel) ~36 MB ~60 MB Add -tags otel for OTLP export
GoClaw (+ Tailscale) ~54 MB ~75 MB Add -tags tsnet for Tailscale listener
GoClaw (+ both) ~65 MB ~85 MB -tags "otel,tsnet"
PicoClaw ~8 MB — Single Go binary
ZeroClaw 3.4 MB — Minimal Rust binary
OpenClaw 28 MB — + ~390 MB Node.js runtime required

Optional features are gated behind build tags to avoid binary bloat. OTel adds ~11 MB (gRPC + protobuf). Tailscale adds ~20 MB (tsnet + WireGuard). The base build includes in-app tracing backed by PostgreSQL and localhost-only access.

Docker Build

# Standard image (~50MB Alpine)
docker build -t goclaw .

# With OpenTelemetry (~60MB)
docker build --build-arg ENABLE_OTEL=true -t goclaw:otel .

# With Tailscale (~75MB)
docker build --build-arg ENABLE_TSNET=true -t goclaw:tsnet .

# With both OTel + Tailscale (~85MB)
docker build --build-arg ENABLE_OTEL=true --build-arg ENABLE_TSNET=true -t goclaw:full .

Configuration

Setup Wizard

./goclaw onboard

The wizard configures: provider, model, gateway port, channels, memory, browser, TTS, and tracing. It generates config.json (no secrets) and .env.local (secrets only).

Auto-Onboard (Docker / CI)

When GOCLAW_*_API_KEY environment variables are set, the gateway automatically configures itself without interactive prompts. It retries Postgres connection (up to 5 attempts), runs migrations, and seeds default data.

Environment Variables

Provider API Keys (set at least one)
Variable Provider
GOCLAW_OPENROUTER_API_KEY OpenRouter (recommended)
GOCLAW_ANTHROPIC_API_KEY Anthropic Claude
GOCLAW_OPENAI_API_KEY OpenAI
GOCLAW_GROQ_API_KEY Groq
GOCLAW_DEEPSEEK_API_KEY DeepSeek
GOCLAW_GEMINI_API_KEY Google Gemini
GOCLAW_MISTRAL_API_KEY Mistral AI
GOCLAW_XAI_API_KEY xAI Grok
GOCLAW_MINIMAX_API_KEY MiniMax
GOCLAW_COHERE_API_KEY Cohere
GOCLAW_PERPLEXITY_API_KEY Perplexity
GOCLAW_DASHSCOPE_API_KEY DashScope (Qwen)
GOCLAW_BAILIAN_API_KEY Bailian Coding
Gateway & Application
Variable Description Default
GOCLAW_CONFIG Config file path config.json
GOCLAW_GATEWAY_TOKEN API authentication token (generated)
GOCLAW_HOST Server bind address 0.0.0.0
GOCLAW_PORT Server port 18790
GOCLAW_PROVIDER Default LLM provider anthropic
GOCLAW_MODEL Default model claude-sonnet-4-5-20250929
GOCLAW_WORKSPACE Agent workspace directory ~/.goclaw/workspace
GOCLAW_DATA_DIR Data storage directory ~/.goclaw/data
GOCLAW_SESSIONS_STORAGE Sessions storage path ~/.goclaw/sessions
GOCLAW_SKILLS_DIR Skills directory ~/.goclaw/skills
GOCLAW_OWNER_IDS Admin user IDs (comma-separated) — owners can manage all agents regardless of ownership and are used as default owner for auto-seeded resources
Database
Variable Description
GOCLAW_POSTGRES_DSN PostgreSQL connection string
GOCLAW_ENCRYPTION_KEY AES-256-GCM key for API key encryption
GOCLAW_MIGRATIONS_DIR Path to migration files
Messaging Channels
Variable Description
GOCLAW_TELEGRAM_TOKEN Telegram bot token
GOCLAW_ZALO_TOKEN Zalo access token
GOCLAW_FEISHU_APP_ID Feishu/Lark app ID
GOCLAW_FEISHU_APP_SECRET Feishu/Lark app secret
GOCLAW_FEISHU_ENCRYPT_KEY Feishu message encryption key
GOCLAW_FEISHU_VERIFICATION_TOKEN Feishu verification token
Scheduler Lanes
Variable Description Default
GOCLAW_LANE_MAIN Main lane concurrency 30
GOCLAW_LANE_SUBAGENT Subagent lane concurrency 50
GOCLAW_LANE_DELEGATE Delegation lane concurrency 100
GOCLAW_LANE_CRON Cron lane concurrency 30
Tailscale (requires build tag tsnet)
Variable Description Default
GOCLAW_TSNET_HOSTNAME Tailscale device name (e.g. goclaw-gateway) (disabled)
GOCLAW_TSNET_AUTH_KEY Tailscale auth key
GOCLAW_TSNET_DIR Persistent state directory OS default
Telemetry (requires build tag otel)
Variable Description Default
GOCLAW_TELEMETRY_ENABLED Enable OTel export false
GOCLAW_TELEMETRY_ENDPOINT OTLP endpoint
GOCLAW_TELEMETRY_PROTOCOL grpc or http grpc
GOCLAW_TELEMETRY_INSECURE Skip TLS verification false
GOCLAW_TELEMETRY_SERVICE_NAME Service name in traces goclaw-gateway
GOCLAW_TRACE_VERBOSE Log full LLM input in spans 0
TTS (Text-to-Speech)
Variable Description
GOCLAW_TTS_OPENAI_API_KEY OpenAI TTS API key
GOCLAW_TTS_ELEVENLABS_API_KEY ElevenLabs API key
GOCLAW_TTS_MINIMAX_API_KEY MiniMax TTS API key
GOCLAW_TTS_MINIMAX_GROUP_ID MiniMax group ID

CLI Commands

goclaw                    Start gateway (default command)
goclaw onboard            Interactive setup wizard
goclaw version            Print version and protocol info
goclaw doctor             System health check (includes schema status)

goclaw upgrade            Upgrade database schema and run data hooks
goclaw upgrade --status   Show current vs required schema version
goclaw upgrade --dry-run  Preview pending changes without applying

goclaw agent list         List configured agents
goclaw agent chat         Chat with an agent
goclaw agent add          Add a new agent
goclaw agent delete       Delete an agent

goclaw migrate up         Apply all pending migrations
goclaw migrate down       Roll back migrations
goclaw migrate version    Show current migration version
goclaw migrate force N    Force set migration version
goclaw migrate goto N     Migrate to specific version
goclaw migrate drop       Drop all tables (dangerous)

goclaw config show        Show current configuration
goclaw config path        Show config file path
goclaw config validate    Validate configuration

goclaw sessions list      List active sessions
goclaw sessions delete    Delete a session
goclaw sessions reset     Reset session history

goclaw cron list          List scheduled jobs
goclaw cron delete        Delete a job
goclaw cron toggle        Enable/disable a job

goclaw skills list        List available skills
goclaw skills show        Show skill details

Adding core skills:

Place a skill folder inside skills/ (local dev) or /app/bundled-skills/ (Docker image). Each folder must contain a SKILL.md with YAML frontmatter (name, description, slug). Folders prefixed with _ are treated as shared code, not skills.

On server startup, the seeder automatically discovers all skill folders, upserts them into the database, and runs an async dependency check. No environment variable needed — the seeder falls back to skills/ in local dev and /app/bundled-skills in Docker.


goclaw models             List AI models and providers
goclaw channels           List messaging channels

goclaw pairing approve    Approve a pairing code
goclaw pairing list       List paired devices
goclaw pairing revoke     Revoke a pairing

Flags:

--config, -c    Path to config file (default: config.json)
--verbose, -v   Enable debug logging

API

Interactive API documentation is available at /docs (Swagger UI) when the gateway is running. The OpenAPI 3.0 spec is served at /v1/openapi.json.

Documentation Description
HTTP REST API 130+ HTTP endpoints — chat completions, agents, skills, providers, MCP, memory, knowledge graph, channels, traces, usage, storage, API keys
WebSocket RPC 64+ RPC methods — chat, agents, config, sessions, cron, teams, pairing, delegations, approvals
API Keys & Auth Authentication model, RBAC scopes, API key management, security design
Gateway Protocol WebSocket wire protocol (v3), frame format, connection lifecycle

Docker Compose

Composable files for different deployment scenarios:

File Purpose
docker-compose.yml Base service definition
docker-compose.postgres.yml PostgreSQL (pgvector/pgvector:pg18)
docker-compose.upgrade.yml One-shot database upgrade service
docker-compose.selfservice.yml Web dashboard UI (nginx + React SPA)
docker-compose.browser.yml Headless Chrome for browser automation
docker-compose.sandbox.yml Docker-based code execution sandbox
docker-compose.otel.yml OpenTelemetry + Jaeger tracing
docker-compose.redis.yml Redis cache backend (build-tag gated)
docker-compose.tailscale.yml Tailscale VPN mesh listener

Examples

# Prepare .env (auto-generates secrets, prompts for API key)
chmod +x prepare-env.sh && ./prepare-env.sh

# Managed (PostgreSQL)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d --build

# Managed + Web Dashboard (http://localhost:3000)
docker compose -f docker-compose.yml \
  -f docker-compose.postgres.yml \
  -f docker-compose.selfservice.yml up -d --build

# Managed + Web Dashboard + OpenTelemetry (Jaeger UI at http://localhost:16686)
docker compose -f docker-compose.yml \
  -f docker-compose.postgres.yml \
  -f docker-compose.selfservice.yml \
  -f docker-compose.otel.yml up -d --build

# Managed + Tailscale (secure remote access via VPN mesh)
docker compose -f docker-compose.yml \
  -f docker-compose.postgres.yml \
  -f docker-compose.tailscale.yml up -d --build

# Check health
curl http://localhost:18790/health

Upgrading (Docker Compose)

Simple upgrade — pull the latest code, rebuild, and restart. The entrypoint automatically runs goclaw upgrade (schema migrations + data hooks) before starting:

# Pull latest code
git pull

# Rebuild and restart (auto-upgrades database on start)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml \
  -f docker-compose.selfservice.yml up -d --build

Replace the compose files with whichever overlays you use (e.g. add -f docker-compose.otel.yml for OTel).

Explicit upgrade — if you want to preview changes or run the upgrade separately before restarting:

# Check current schema status
docker compose -f docker-compose.yml -f docker-compose.postgres.yml \
  -f docker-compose.upgrade.yml run --rm upgrade --status

# Preview pending changes (dry-run)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml \
  -f docker-compose.upgrade.yml run --rm upgrade --dry-run

# Apply upgrade (schema migrations + data hooks), then remove container
docker compose -f docker-compose.yml -f docker-compose.postgres.yml \
  -f docker-compose.upgrade.yml run --rm upgrade

# Then rebuild and restart the gateway with the new image
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d --build

Environment File (.env)

Use the prepare-env.sh script to generate .env with auto-generated secrets:

./prepare-env.sh

This creates .env with GOCLAW_ENCRYPTION_KEY and GOCLAW_GATEWAY_TOKEN pre-filled. You only need to add your provider API key. See .env.example for all available variables.

Built-in Tools

Tool Group Description
read_file fs Read file contents (with virtual FS routing)
write_file fs Write/create files
edit_file fs Apply targeted edits to existing files
list_files fs List directory contents
search fs Search file contents by pattern
glob fs Find files by glob pattern
exec runtime Execute shell commands (with approval workflow)
web_search web Search the web (Brave, DuckDuckGo)
web_fetch web Fetch and parse web content
memory_search memory Search long-term memory (FTS + vector)
memory_get memory Retrieve memory entries
skill_search — Search skills (BM25 + embedding hybrid)
knowledge_graph_search memory Search entities and traverse knowledge graph relationships
create_image media Image generation (DashScope, MiniMax)
create_audio media Audio generation (OpenAI, ElevenLabs, MiniMax, Suno)
create_video media Video generation (MiniMax, Veo)
read_document media Document reading (Gemini File API, provider chain)
read_image media Image analysis
read_audio media Audio transcription and analysis
read_video media Video analysis
message messaging Send messages to channels
tts — Text-to-Speech synthesis
spawn — Spawn a subagent
subagents sessions Control running subagents
delegate orchestration Delegate tasks to other agents (sync/async, cancel, list)
delegate_search orchestration Search delegation targets (hybrid FTS + semantic)
handoff orchestration Transfer conversation control to another agent
evaluate_loop orchestration Generate-evaluate-revise quality feedback loop
team_tasks teams Shared task board (list, create, claim, complete, search)
team_message teams Team mailbox (send, broadcast, read)
sessions_list sessions List active sessions
sessions_history sessions View session history
sessions_send sessions Send message to a session
sessions_spawn sessions Spawn a new session
session_status sessions Check session status
cron automation Schedule and manage cron jobs
gateway automation Gateway administration
browser ui Browser automation (navigate, click, type, screenshot)
announce_queue automation Async result announcement (for async delegations)

Browser Pairing

Browser clients can authenticate without pre-shared tokens using a pairing code flow:

  1. User opens the web dashboard and enters their User ID
  2. Clicks "Request Access (Pairing)" — gateway generates an 8-character code
  3. Code is displayed in the browser UI
  4. An admin approves the code via CLI (goclaw pairing approve XXXX) or the web UI
  5. Browser automatically detects approval and gains operator-level access
  6. On subsequent visits, the browser reconnects automatically using the stored pairing (no re-approval needed)

Revoking access:

# List paired devices
goclaw pairing list

# Revoke a specific pairing
goclaw pairing revoke <sender_id>

After revocation, the browser falls back to the pairing flow on next visit.

Tailscale (Remote Access)

GoClaw supports an optional Tailscale listener for secure remote access via VPN mesh. The Tailscale listener runs alongside the main gateway, serving the same routes on both listeners.

Build-tag gated: The tsnet dependency (~20 MB) is only compiled when building with -tags tsnet. The default binary is unaffected.

# Build with Tailscale support
go build -tags tsnet -o goclaw .

# Configure via environment variables
export GOCLAW_TSNET_HOSTNAME=goclaw-gateway
export GOCLAW_TSNET_AUTH_KEY=tskey-auth-xxxxx

# Start — both localhost:18790 and Tailscale listener are active
./goclaw

When Tailscale is enabled and the gateway is still bound to 0.0.0.0, a log suggestion recommends switching to 127.0.0.1 for localhost-only + Tailscale access:

GOCLAW_HOST=127.0.0.1 ./goclaw

This keeps the gateway inaccessible from the LAN while remaining reachable via Tailscale from any device on your tailnet.

Docker:

docker compose -f docker-compose.yml \
  -f docker-compose.postgres.yml \
  -f docker-compose.tailscale.yml up -d

Requires GOCLAW_TSNET_AUTH_KEY in your .env file. Tailscale state is persisted in a tsnet-state Docker volume.

Security

  • Transport: WebSocket CORS validation, 512KB message limit, 1MB HTTP body limit, timing-safe token auth
  • API key management: Multi-key auth with 5 RBAC scopes, SHA-256 hashed storage, optional expiry, revocation, show-once pattern. See API Keys & Auth
  • Rate limiting: Token bucket per user/IP, configurable RPM
  • Prompt injection: Input guard with 6 pattern detection (detection-only, never blocks)
  • Shell security: Deny patterns for curl|sh, wget|sh, reverse shells, eval, base64|sh
  • Network: SSRF protection with blocked hosts + private IP + DNS pinning
  • File system: Path traversal prevention, workspace restriction
  • Encryption: AES-256-GCM for provider API keys in database
  • Browser pairing: Token-free browser auth with admin approval (pairing codes, auto-reconnect)
  • Tailscale: Optional VPN mesh listener for secure remote access (build-tag gated)

Testing

# Unit tests
go test ./...

# Integration tests (requires running gateway)
go test -v -run 'TestHealthHTTP|TestConnectHandshake' ./tests/integration/

# Full integration (requires API key)
GOCLAW_OPENROUTER_API_KEY=sk-or-xxx go test -v ./tests/integration/ -timeout 120s

Project Status

Implemented & Tested in Production

  • Agent management & configuration — Create, update, delete agents via API and web dashboard. Agent types (open / predefined), agent routing, and lazy resolution all tested.
  • Telegram channel — Full integration tested: message handling, streaming responses, rich formatting (HTML, tables, code blocks), reactions, media, chunked long messages.
  • Seed data & bootstrapping — Auto-onboard, DB seeding, migration pipeline tested end-to-end.
  • User-scope & content files — Per-user context files (user_context_files), agent-level context files (agent_context_files), virtual FS interceptors, per-user seeding (SeedUserFiles), and user-agent profile tracking all implemented and tested.
  • Core built-in tools — File system tools (read_file, write_file, edit_file, list_files, search, glob), shell execution (exec), web tools (web_search, web_fetch), and session management tools tested in real agent loops.
  • Memory system — Long-term memory with pgvector hybrid search (FTS + vector) implemented and tested with real conversations.
  • Agent loop — Think-act-observe cycle, tool use, session history, auto-summarization, and subagent spawning tested in production.
  • WebSocket RPC protocol (v3) — Connect handshake, chat streaming, event push all tested with web dashboard and integration tests.
  • Store layer (PostgreSQL) — All PG stores (sessions, agents, providers, skills, cron, pairing, tracing, memory, teams) implemented and running.
  • Browser automation — Rod/CDP integration for headless Chrome, tested in production agent workflows.
  • Lane-based scheduler — Main/subagent/delegate/cron lane isolation with concurrent execution tested. Group chats support up to 3 concurrent agent runs per session with adaptive throttle and deferred session writes for history isolation.
  • Security hardening — Rate limiting, prompt injection detection, CORS, shell deny patterns, SSRF protection, credential scrubbing all implemented and verified.
  • Web dashboard — Channel management, agent management, pairing approval, traces & spans viewer, skills, MCP, cron, sessions, teams, and config pages all implemented and working.
  • Prompt caching — Anthropic (explicit cache_control), OpenAI/MiniMax/OpenRouter (automatic). Cache metrics tracked in trace spans and displayed in web dashboard.
  • Agent delegation — Inter-agent task delegation with permission links, sync/async modes, per-user restrictions, concurrency limits, and hybrid agent search. Tested in production.
  • Agent teams — Team creation with lead/member roles, shared task board (create, claim, complete, search, blocked_by dependencies), team mailbox (send, broadcast, read). Tested in production.
  • Evaluate loop — Generator-evaluator feedback cycles with configurable max rounds and pass criteria. Tested in production.
  • Delegation history — Queryable audit trail of inter-agent delegations. Tested in production.
  • Skill system — BM25 search, ZIP upload, SKILL.md parsing, and embedding hybrid search. Tested in production.
  • MCP integration — stdio, SSE, and streamable-http transports with per-agent/per-user grants. Tested in production.
  • Cron scheduling — at, every, and cron expression scheduling. Tested in production.
  • Docker sandbox — Isolated code execution in containers. Tested in production.
  • Text-to-Speech — OpenAI, ElevenLabs, Edge, MiniMax providers. Tested in production.
  • HTTP API — /v1/chat/completions, /v1/agents, /v1/skills, etc. Tested in production. Interactive Swagger UI at /docs.
  • API key management — Multi-key auth with RBAC scopes, SHA-256 hashed storage, show-once pattern, optional expiry, revocation. HTTP + WebSocket CRUD. Web UI for management.
  • Hooks system — Event-driven hooks with command evaluators (shell exit code) and agent evaluators (delegate to reviewer). Blocking gates with auto-retry and recursion-safe evaluation.
  • Media tools — create_image (DashScope, MiniMax), create_audio (OpenAI, ElevenLabs, MiniMax, Suno), create_video (MiniMax, Veo), read_document (Gemini File API), read_image, read_audio, read_video. Persistent media storage with lazy-loaded MediaRef.
  • Additional provider modes — Claude CLI (Anthropic via stdio + MCP bridge), Codex (OpenAI gpt-5.3-codex via OAuth).
  • Knowledge graph — LLM-powered entity extraction, graph traversal, force-directed visualization, and knowledge_graph_search agent tool.
  • Memory management — Admin dashboard for memory documents (CRUD, semantic search, chunk/embedding details, bulk re-indexing).
  • Persistent pending messages — Channel messages persisted to PostgreSQL with auto-compaction (LLM summarization) and monitoring dashboard.

Implemented but Not Fully Tested

  • Agent handoff — Conversation transfer between agents with routing overrides. Implementation complete, needs E2E testing.
  • Quality gates — Hook-based output validation with command and agent evaluator types. Implementation complete, needs E2E testing.
  • Slack — Channel integration implemented, not yet validated with real users.
  • Other messaging channels — Discord, Zalo OA, Zalo Personal, Feishu/Lark, WhatsApp channel adapters are implemented but have not been tested end-to-end in production. Only Telegram has been validated with real users.
  • OpenTelemetry export — OTLP gRPC/HTTP exporter implemented (build-tag gated). In-app tracing works; external OTel export not validated in production.
  • Tailscale integration — tsnet listener implemented (build-tag gated). Not tested in a real deployment.
  • Redis cache — Optional distributed cache backend (build-tag gated). Not tested in production.
  • Browser pairing — Pairing code flow implemented with CLI and web UI approval. Basic flow tested but not validated at scale.

Acknowledgments

GoClaw is built upon the original OpenClaw project. We are grateful for the architecture and vision that inspired this Go port.

License

MIT

S
Description
GoClaw - GoClaw is OpenClaw rebuilt in Go — with multi-tenant isolation, 5-layer security, and native concurrency. Deploy AI agent teams at scale without compromising on safety.
Readme
42 MiB
0 Stars 1 Watchers 0 Forks
Languages
Go 77.8%
TypeScript 19.9%
Python 1.3%
HTML 0.3%
JavaScript 0.2%
Other 0.3%