mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 16:17:25 +00:00
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>
This commit is contained in:
1 parent
9a350eae75
commit
e7939de84b
10 files changed
+1156
-159
No files matched your search
@@ -19,7 +19,8 @@ flowchart TD
|
||||
TG[Telegram]
|
||||
DC[Discord]
|
||||
FS[Feishu / Lark]
|
||||
ZL[Zalo]
|
||||
ZL[Zalo OA]
|
||||
ZLP[Zalo Personal]
|
||||
WA[WhatsApp]
|
||||
end
|
||||
|
||||
@@ -45,7 +46,7 @@ flowchart TD
|
||||
|
||||
subgraph Providers["LLM Providers"]
|
||||
ANTH[Anthropic -- Native HTTP + SSE]
|
||||
OAI[OpenAI-Compatible -- HTTP + SSE]
|
||||
OAI["OpenAI-Compatible -- HTTP + SSE<br/>(OpenAI, Gemini, DeepSeek, DashScope, +8)"]
|
||||
end
|
||||
|
||||
subgraph Tools["Tool Registry"]
|
||||
@@ -87,7 +88,7 @@ flowchart TD
|
||||
|
||||
WS --> WSS
|
||||
HTTP --> HTTPS
|
||||
TG & DC & FS & ZL & WA --> CM
|
||||
TG & DC & FS & ZL & ZLP & WA --> CM
|
||||
|
||||
WSS --> MR
|
||||
HTTPS --> MR
|
||||
@@ -113,7 +114,7 @@ flowchart TD
|
||||
| `internal/gateway/` | WebSocket + HTTP server, client handling, method router |
|
||||
| `internal/gateway/methods/` | RPC method handlers: chat, agents, agent_links, teams, delegations, sessions, config, skills, cron, pairing, exec approval, usage, send |
|
||||
| `internal/agent/` | Agent loop (think, act, observe), router, resolver, system prompt builder, sanitization, pruning, tracing, memory flush, DELEGATION.md + TEAM.md injection |
|
||||
| `internal/providers/` | LLM providers: Anthropic (native HTTP + SSE streaming), OpenAI-compatible (HTTP + SSE), retry logic |
|
||||
| `internal/providers/` | LLM providers: Anthropic (native HTTP + SSE streaming), OpenAI-compatible (HTTP + SSE, 12+ providers), DashScope (Qwen), extended thinking support, retry logic |
|
||||
| `internal/tools/` | Tool registry, filesystem ops, exec/shell, policy engine, subagent, delegation manager, team tools, evaluate loop, handoff, context file + memory interceptors, credential scrubbing, rate limiting, PathDenyable |
|
||||
| `internal/tools/dynamic_loader.go` | Custom tool loader: LoadGlobal (startup), LoadForAgent (per-agent clone), ReloadGlobal (cache invalidation) |
|
||||
| `internal/tools/dynamic_tool.go` | Custom tool executor: command template rendering, shell escaping, encrypted env vars |
|
||||
@@ -124,7 +125,7 @@ flowchart TD
|
||||
| `internal/bootstrap/` | System prompt files (AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, HEARTBEAT.md, BOOTSTRAP.md) + seeding + truncation |
|
||||
| `internal/config/` | Config loading (JSON5) + env var overlay |
|
||||
| `internal/skills/` | SKILL.md loader (5-tier hierarchy) + BM25 search + hot-reload via fsnotify |
|
||||
| `internal/channels/` | Channel manager + adapters: Telegram, Feishu/Lark, Zalo, Discord, WhatsApp |
|
||||
| `internal/channels/` | Channel manager + adapters: Telegram (forum topics, STT, bot commands), Feishu/Lark (streaming cards, media), Zalo OA, Zalo Personal, Discord, WhatsApp |
|
||||
| `internal/mcp/` | MCP server bridge (stdio, SSE, streamable-HTTP transports) |
|
||||
| `internal/scheduler/` | Lane-based concurrency control (main, subagent, cron, delegate lanes) with per-session serialization |
|
||||
| `internal/memory/` | Memory system (SQLite FTS5 + embeddings for standalone mode) |
|
||||
@@ -474,3 +475,5 @@ flowchart TD
|
||||
| [08-scheduling-cron-heartbeat.md](./08-scheduling-cron-heartbeat.md) | Scheduler lanes, cron lifecycle, heartbeat |
|
||||
| [09-security.md](./09-security.md) | Defense layers, encryption, rate limiting, RBAC, sandbox |
|
||||
| [10-tracing-observability.md](./10-tracing-observability.md) | Tracing collector, span hierarchy, OTel export, trace API |
|
||||
| [11-agent-teams.md](./11-agent-teams.md) | Agent teams, task board, mailbox, delegation integration |
|
||||
| [12-extended-thinking.md](./12-extended-thinking.md) | Extended thinking, per-provider support, streaming |
|
||||
+64
-1
@@ -43,6 +43,8 @@ The Anthropic provider uses `x-api-key` header authentication and the `anthropic
|
||||
| minimax | OpenAI-compatible | `https://api.minimax.chat/v1` | `MiniMax-M2.5` |
|
||||
| cohere | OpenAI-compatible | `https://api.cohere.com/v2` | `command-a` |
|
||||
| perplexity | OpenAI-compatible | `https://api.perplexity.ai` | `sonar-pro` |
|
||||
| dashscope | OpenAI-compatible | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | `qwen3-max` |
|
||||
| bailian | OpenAI-compatible | `https://coding-intl.dashscope.aliyuncs.com/v1` | `qwen3.5-plus` |
|
||||
|
||||
---
|
||||
|
||||
@@ -116,6 +118,7 @@ Key difference: non-streaming wraps the entire request in `RetryDo`. Streaming r
|
||||
|
||||
| Aspect | Anthropic | OpenAI-Compatible |
|
||||
|--------|-----------|-------------------|
|
||||
| Base URL override | `WithAnthropicBaseURL()` option | Via config `api_base` field |
|
||||
| Implementation | Native `net/http` | Generic HTTP client |
|
||||
| System messages | Separate `system` field (array of text blocks) | Inline in `messages` array with `role: "system"` |
|
||||
| Tool definitions | `name` + `description` + `input_schema` | Standard OpenAI function schema |
|
||||
@@ -236,7 +239,57 @@ flowchart LR
|
||||
|
||||
---
|
||||
|
||||
## 8. Agent Evaluators (Hook System)
|
||||
## 8. Extended Thinking
|
||||
|
||||
Extended thinking allows LLMs to generate internal reasoning tokens before producing a response, improving quality for complex tasks. GoClaw supports this across multiple providers with a unified `thinking_level` configuration. See [12-extended-thinking.md](./12-extended-thinking.md) for full details.
|
||||
|
||||
### Provider Mapping
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
LEVEL["thinking_level"] --> CHECK{"Provider<br/>supports thinking?"}
|
||||
CHECK -->|No| SKIP["Skip — normal request"]
|
||||
CHECK -->|Yes| TYPE{"Provider type?"}
|
||||
|
||||
TYPE -->|Anthropic| ANTH["Budget tokens:<br/>low=4K, medium=10K, high=32K<br/>+ anthropic-beta header<br/>+ strip temperature"]
|
||||
TYPE -->|OpenAI-compat| OAI["reasoning_effort:<br/>low / medium / high"]
|
||||
TYPE -->|DashScope| DASH["enable_thinking: true<br/>Budget: low=4K, medium=16K, high=32K<br/>⚠ No streaming with tools"]
|
||||
```
|
||||
|
||||
### Streaming
|
||||
|
||||
- **Anthropic**: `thinking_delta` events accumulate into `StreamChunk.Thinking`
|
||||
- **OpenAI-compat**: `reasoning_content` in response delta
|
||||
- **DashScope**: Falls back to non-streaming when tools are present, synthesizes chunk callbacks
|
||||
|
||||
### Tool Loop Handling
|
||||
|
||||
Anthropic requires thinking blocks (including cryptographic signatures) to be echoed back in subsequent tool-use turns. `RawAssistantContent` preserves these raw blocks for API passback. Other providers handle reasoning content as independent per-turn metadata.
|
||||
|
||||
---
|
||||
|
||||
## 9. DashScope and Bailian Providers
|
||||
|
||||
Two providers for the Alibaba Cloud AI ecosystem.
|
||||
|
||||
### DashScope (Alibaba Qwen)
|
||||
|
||||
Wraps the OpenAI-compatible provider with a critical override: when tools are present, streaming is disabled. The provider falls back to a single `Chat()` call and synthesizes chunk callbacks to maintain the event flow.
|
||||
|
||||
- **Default model**: `qwen3-max`
|
||||
- **Thinking support**: Custom budget mapping (low=4,096, medium=16,384, high=32,768)
|
||||
- **Known limitation**: No simultaneous streaming + tools
|
||||
|
||||
### Bailian Coding
|
||||
|
||||
Standard OpenAI-compatible provider targeting the Alibaba Coding API.
|
||||
|
||||
- **Default model**: `qwen3.5-plus`
|
||||
- **Base URL**: `https://coding-intl.dashscope.aliyuncs.com/v1`
|
||||
|
||||
---
|
||||
|
||||
## 10. Agent Evaluators (Hook System)
|
||||
|
||||
Agent evaluators in the quality gate / hook system (see [03-tools-system.md](./03-tools-system.md)) use the same provider resolution as normal agent runs. When a quality gate is configured with `"type": "agent"`, the hook engine delegates to the specified reviewer agent, which resolves its own provider through the standard provider registry. No separate provider configuration is needed for evaluator agents.
|
||||
|
||||
@@ -251,4 +304,14 @@ Agent evaluators in the quality gate / hook system (see [03-tools-system.md](./0
|
||||
| `internal/providers/openai.go` | OpenAI-compatible provider implementation (generic HTTP) |
|
||||
| `internal/providers/retry.go` | RetryDo[T] generic function, RetryConfig, IsRetryableError, backoff computation |
|
||||
| `internal/providers/schema_cleaner.go` | CleanSchemaForProvider, CleanToolSchemas, recursive schema field removal |
|
||||
| `internal/providers/dashscope.go` | DashScope provider: thinking budget, tools+streaming fallback |
|
||||
| `cmd/gateway_providers.go` | Provider registration from config and database during gateway startup |
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
| Document | Relevant Content |
|
||||
|----------|-----------------|
|
||||
| [12-extended-thinking.md](./12-extended-thinking.md) | Full extended thinking documentation |
|
||||
| [01-agent-loop.md](./01-agent-loop.md) | LLM iteration loop, streaming chunk handling |
|
||||
+28
-16
@@ -86,8 +86,7 @@ Context keys ensure each tool call receives the correct per-call values without
|
||||
| `sessions_list` | List active sessions |
|
||||
| `sessions_history` | View session message history |
|
||||
| `sessions_send` | Send a message to a session |
|
||||
| `sessions_spawn` | Spawn an async subagent task |
|
||||
| `subagents` | Manage subagent tasks (list, cancel, steer) |
|
||||
| `spawn` | Spawn subagent or delegate to another agent |
|
||||
| `session_status` | Get current session status |
|
||||
|
||||
### UI (group: `ui`)
|
||||
@@ -109,6 +108,7 @@ Context keys ensure each tool call receives the correct per-call values without
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `message` | Send a message to a channel |
|
||||
| `create_forum_topic` | Create a Telegram forum topic |
|
||||
|
||||
### Delegation (group: `delegation`)
|
||||
|
||||
@@ -130,10 +130,11 @@ Context keys ensure each tool call receives the correct per-call values without
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `skill_search` | Search available skills (BM25) |
|
||||
| `skill_search` | Search available skills (BM25 + vector) |
|
||||
| `image` | Generate images |
|
||||
| `read_image` | Read/analyze an image file |
|
||||
| `create_image` | Create an image from description |
|
||||
| `tts` | Text-to-speech synthesis (OpenAI, ElevenLabs, Edge, MiniMax) |
|
||||
| `spawn` | Spawn subagent (alternative to sessions_spawn) |
|
||||
| `nodes` | Node graph operations |
|
||||
|
||||
---
|
||||
@@ -274,28 +275,32 @@ flowchart TD
|
||||
| Profile | Tools Included |
|
||||
|---------|---------------|
|
||||
| `full` | All registered tools (no restriction) |
|
||||
| `coding` | `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `image` |
|
||||
| `messaging` | `group:messaging`, `sessions_list`, `sessions_history`, `sessions_send`, `session_status` |
|
||||
| `coding` | `group:fs`, `group:runtime`, `group:sessions`, `group:memory`, `group:web`, `read_image`, `create_image`, `skill_search` |
|
||||
| `messaging` | `group:messaging`, `group:web`, `group:sessions`, `read_image`, `skill_search` |
|
||||
| `minimal` | `session_status` only |
|
||||
|
||||
### Tool Groups
|
||||
|
||||
| Group | Members |
|
||||
|-------|---------|
|
||||
| `fs` | `read_file`, `write_file`, `list_files`, `edit_file`, `search`, `glob` |
|
||||
| `runtime` | `exec`, `process` |
|
||||
| `fs` | `read_file`, `write_file`, `list_files`, `edit`, `search`, `glob` |
|
||||
| `runtime` | `exec` |
|
||||
| `web` | `web_search`, `web_fetch` |
|
||||
| `memory` | `memory_search`, `memory_get` |
|
||||
| `sessions` | `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `subagents`, `session_status` |
|
||||
| `ui` | `browser`, `canvas` |
|
||||
| `automation` | `cron`, `gateway` |
|
||||
| `messaging` | `message` |
|
||||
| `delegation` | `delegate`, `delegate_search`, `evaluate_loop`, `handoff` |
|
||||
| `teams` | `team_tasks`, `team_message` |
|
||||
| `sessions` | `sessions_list`, `sessions_history`, `sessions_send`, `spawn`, `session_status` |
|
||||
| `ui` | `browser` |
|
||||
| `automation` | `cron` |
|
||||
| `messaging` | `message`, `create_forum_topic` |
|
||||
| `delegation` | `handoff`, `delegate_search`, `evaluate_loop` |
|
||||
| `team` | `team_tasks`, `team_message` |
|
||||
| `goclaw` | All native tools (composite group) |
|
||||
|
||||
Groups can be referenced in allow/deny lists with the `group:` prefix (e.g., `group:fs`). The MCP manager dynamically registers `mcp` and `mcp:{serverName}` groups at runtime.
|
||||
|
||||
### Per-Request Tool Allow List
|
||||
|
||||
In addition to the static policy pipeline, channels can inject a per-request tool allow list via message metadata. For example, Telegram forum topics can restrict tools per-topic (see [05-channels-messaging.md](./05-channels-messaging.md) Section 5). The allow list is applied as a final intersection step after the policy pipeline completes.
|
||||
|
||||
---
|
||||
|
||||
## 6. Subagent System
|
||||
@@ -662,7 +667,7 @@ flowchart TD
|
||||
|
||||
Tool output is automatically scrubbed before being returned to the LLM. Enabled by default in the registry.
|
||||
|
||||
### Detected Patterns
|
||||
### Static Patterns
|
||||
|
||||
| Type | Pattern |
|
||||
|------|---------|
|
||||
@@ -670,10 +675,17 @@ Tool output is automatically scrubbed before being returned to the LLM. Enabled
|
||||
| Anthropic | `sk-ant-[a-zA-Z0-9-]{20,}` |
|
||||
| GitHub PAT | `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` + 36 alphanumeric characters |
|
||||
| AWS | `AKIA[A-Z0-9]{16}` |
|
||||
| Generic | `(api_key\|token\|secret\|password\|bearer\|authorization)[:=]value` (case-insensitive) |
|
||||
| Generic key-value | `(api_key\|token\|secret\|password\|bearer\|authorization)[:=]value` (case-insensitive) |
|
||||
| Connection strings | `postgres://`, `mysql://`, `mongodb://`, `redis://` patterns |
|
||||
| Env var patterns | `KEY=`, `SECRET=`, `CREDENTIAL=`, `DSN=`, `VIRTUAL_*=` patterns |
|
||||
| Long hex strings | 64+ character hex strings (potential encryption keys) |
|
||||
|
||||
All matches are replaced with `[REDACTED]`.
|
||||
|
||||
### Dynamic Scrubbing
|
||||
|
||||
In addition to static patterns, values can be registered at runtime for scrubbing. This is used for server IPs and other deployment-specific secrets that are not known at compile time. The `AddDynamicScrubValues()` function thread-safely adds values to a scrub list that is checked alongside static patterns.
|
||||
|
||||
---
|
||||
|
||||
## 15. Rate Limiter
|
||||
|
||||
+350
-125
@@ -12,7 +12,8 @@ flowchart LR
|
||||
TG["Telegram"]
|
||||
DC["Discord"]
|
||||
FS["Feishu/Lark"]
|
||||
ZL["Zalo"]
|
||||
ZL["Zalo OA"]
|
||||
ZLP["Zalo Personal"]
|
||||
WA["WhatsApp"]
|
||||
end
|
||||
|
||||
@@ -35,6 +36,7 @@ flowchart LR
|
||||
DC --> CH
|
||||
FS --> CH
|
||||
ZL --> CH
|
||||
ZLP --> CH
|
||||
WA --> CH
|
||||
CH --> HM
|
||||
HM --> BUS
|
||||
@@ -46,6 +48,7 @@ flowchart LR
|
||||
SEND --> DC
|
||||
SEND --> FS
|
||||
SEND --> ZL
|
||||
SEND --> ZLP
|
||||
SEND --> WA
|
||||
```
|
||||
|
||||
@@ -53,60 +56,54 @@ Internal channels (`cli`, `system`, `subagent`) are silently skipped by the outb
|
||||
|
||||
### Handoff Routing (Managed Mode)
|
||||
|
||||
Before normal agent routing, the consumer checks the `handoff_routes` table for an active routing override. If a handoff route exists for the incoming channel + chat ID, the message is redirected to the target agent instead of the original agent.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
MSG["Inbound message"] --> CHECK{"handoff_routes<br/>has override?"}
|
||||
CHECK -->|Yes| TARGET["Route to target agent<br/>(billing, support, etc.)"]
|
||||
CHECK -->|No| NORMAL["Route to default agent"]
|
||||
TARGET --> SESSION["New session for target agent"]
|
||||
NORMAL --> SESSION2["Existing session"]
|
||||
```
|
||||
|
||||
Handoff routes are created by the `handoff` tool (see [03-tools-system.md](./03-tools-system.md)) and can be cleared by the target agent calling `handoff(action="clear")` or by handing back to the original agent.
|
||||
Before normal agent routing, the consumer checks the `handoff_routes` table for an active routing override. If a handoff route exists for the incoming channel + chat ID, the message is redirected to the target agent instead of the original.
|
||||
|
||||
### Message Routing Prefixes
|
||||
|
||||
The consumer routes system messages based on sender ID prefixes:
|
||||
|
||||
| Prefix | Route | Outbound Delivery |
|
||||
| Prefix | Route | Scheduler Lane |
|
||||
|--------|-------|:-:|
|
||||
| `subagent:` | Parent session queue | Yes |
|
||||
| `delegate:` | Delegate scheduler lane | Yes |
|
||||
| `teammate:` | Lead agent session queue | Yes |
|
||||
| `handoff:` | Target agent via delegate lane | Yes |
|
||||
|
||||
### Managed Mode Behavior
|
||||
|
||||
In managed mode, channels provide per-user isolation through compound sender IDs and context propagation:
|
||||
|
||||
- **User scoping**: Each channel constructs a compound sender ID (e.g., `telegram:123456`) which maps to a `user_id` for session key generation. The session key format `agent:{agentId}:{channel}:direct:{peerId}` ensures each user has an isolated conversation history per agent.
|
||||
- **Context propagation**: `HandleMessage()` sets `store.WithAgentID(ctx)`, `store.WithUserID(ctx)`, and `store.WithAgentType(ctx)` on the context. These values flow through to the ContextFileInterceptor, MemoryInterceptor, and per-user file seeding.
|
||||
- **Pairing storage**: In managed mode, pairing state (pending requests and approved pairings) is stored in the `pairing_requests` and `paired_devices` PostgreSQL tables via `PGPairingStore`. In standalone mode, pairing state is stored in JSON files.
|
||||
- **Session persistence**: Chat sessions are stored in the `sessions` PostgreSQL table via `PGSessionStore` with write-behind caching.
|
||||
| `subagent:` | Parent session queue | subagent |
|
||||
| `delegate:` | Parent agent's original session | delegate |
|
||||
| `teammate:` | Target agent session | delegate |
|
||||
| `handoff:` | Target agent via delegate lane | delegate |
|
||||
|
||||
---
|
||||
|
||||
## 2. Channel Interface
|
||||
## 2. Channel Interfaces
|
||||
|
||||
Every channel must implement the following methods:
|
||||
Every channel must implement the base interface:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `Name()` | Channel identifier (e.g., `"telegram"`, `"discord"`) |
|
||||
| `Start(ctx)` | Begin listening for messages (non-blocking after setup) |
|
||||
| `Start(ctx)` | Begin listening for messages (non-blocking) |
|
||||
| `Stop(ctx)` | Graceful shutdown |
|
||||
| `Send(ctx, msg)` | Deliver an outbound message to the platform |
|
||||
| `IsRunning()` | Whether the channel is actively processing |
|
||||
| `IsAllowed(senderID)` | Check if a sender passes the allowlist |
|
||||
|
||||
`BaseChannel` provides a shared implementation that all channels embed. It handles:
|
||||
### Extended Interfaces
|
||||
|
||||
- Allowlist matching with compound `"123456|username"` format and `@` prefix stripping
|
||||
- `HandleMessage()` which builds an `InboundMessage` and publishes it to the bus
|
||||
- `CheckPolicy()` which evaluates DM/Group policies per message
|
||||
- User ID extraction from compound sender IDs (strip `|username` suffix)
|
||||
| Interface | Purpose | Implemented By |
|
||||
|-----------|---------|----------------|
|
||||
| `StreamingChannel` | Real-time streaming updates | Telegram, Feishu |
|
||||
| `WebhookChannel` | Webhook HTTP handler mounting | Feishu |
|
||||
| `ReactionChannel` | Status reactions on messages | Telegram, Feishu |
|
||||
|
||||
`BaseChannel` provides a shared implementation that all channels embed: allowlist matching, `HandleMessage()`, `CheckPolicy()`, and user ID extraction.
|
||||
|
||||
### Webhook Mount
|
||||
|
||||
Channels implementing `WebhookChannel` expose an HTTP handler that can be mounted on the gateway's main HTTP mux. This enables single-port operation — no separate webhook server needed.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
GW["Gateway HTTP Mux"] --> WH{"WebhookChannel?"}
|
||||
WH -->|Yes| MOUNT["Mount handler on main mux<br/>(e.g., /feishu/events)"]
|
||||
WH -->|No| SKIP["Channel uses its own transport<br/>(polling, gateway events, etc.)"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -153,22 +150,26 @@ flowchart TD
|
||||
AL2 -->|No| REJECT
|
||||
```
|
||||
|
||||
Policies are configured per-channel. Default is `"open"` for channels that do not specify a policy.
|
||||
|
||||
---
|
||||
|
||||
## 4. Channel Comparison
|
||||
|
||||
| Feature | Telegram | Discord | Feishu/Lark | Zalo | WhatsApp |
|
||||
|---------|----------|---------|-------------|------|----------|
|
||||
| Connection | Long polling | Gateway events | WebSocket (default) or Webhook | Long polling | External WS bridge |
|
||||
| DM support | Yes | Yes | Yes | Yes (DM only) | Yes |
|
||||
| Group support | Yes (mention gating) | Yes | Yes | No | Yes |
|
||||
| Message limit | 4096 chars | 2000 chars | 4000 chars | 2000 chars | N/A (bridge) |
|
||||
| Streaming | Typing indicator | Edit "Thinking..." message | Streaming message cards | No | No |
|
||||
| Media | Photos, voice, files | Files, embeds | Images, files (30 MB) | Images (5 MB) | JSON messages |
|
||||
| Rich formatting | Markdown to HTML | Markdown | Card messages | Plain text | Plain text |
|
||||
| Pairing support | Yes | No | Yes | Yes | No |
|
||||
| Feature | Telegram | Feishu/Lark | Discord | WhatsApp | Zalo OA | Zalo Personal |
|
||||
|---------|----------|-------------|---------|----------|---------|---------------|
|
||||
| Connection | Long polling | WS (default) / Webhook | Gateway events | External WS bridge | Long polling | Internal protocol |
|
||||
| DM support | Yes | Yes | Yes | Yes | Yes (DM only) | Yes |
|
||||
| Group support | Yes (mention gating) | Yes | Yes | Yes | No | Yes |
|
||||
| Forum/Topics | Yes (per-topic config) | Yes (topic session mode) | -- | -- | -- | -- |
|
||||
| Message limit | 4,096 chars | Configurable (default 4,000) | 2,000 chars | N/A (bridge) | 2,000 chars | 2,000 chars |
|
||||
| Streaming | Typing indicator | Streaming message cards | Edit "Thinking..." | No | No | No |
|
||||
| Media | Photos, voice, files | Images, files (30 MB) | Files, embeds | JSON messages | Images (5 MB) | -- |
|
||||
| Speech-to-text | Yes (STT proxy) | -- | -- | -- | -- | -- |
|
||||
| Voice routing | Yes (VoiceAgentID) | -- | -- | -- | -- | -- |
|
||||
| Rich formatting | Markdown → HTML | Card messages | Markdown | Plain text | Plain text | Plain text |
|
||||
| Bot commands | 10+ commands | -- | -- | -- | -- | -- |
|
||||
| Tool allow list | Per-topic | -- | -- | -- | -- | -- |
|
||||
| Pairing support | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Status reactions | Yes | Yes | -- | -- | -- | -- |
|
||||
|
||||
---
|
||||
|
||||
@@ -176,31 +177,120 @@ Policies are configured per-channel. Default is `"open"` for channels that do no
|
||||
|
||||
The Telegram channel uses long polling via the `telego` library (Telegram Bot API).
|
||||
|
||||
### Key Behaviors
|
||||
### Core Behaviors
|
||||
|
||||
- **Group mention gating**: By default, bot must be @mentioned in groups (`requireMention: true`). Pending group messages without a mention are stored in a history buffer (default 50 messages) and included as context when the bot is eventually mentioned.
|
||||
- **Group mention gating**: By default, bot must be @mentioned in groups (`requireMention: true`). Pending messages without a mention are stored in a history buffer (default 50 messages) and included as context when the bot is eventually mentioned.
|
||||
- **Typing indicator**: A "typing" action is sent while the agent is processing.
|
||||
- **Proxy support**: Optional HTTP proxy configured via the channel config.
|
||||
- **Cancel commands**: `/stop` (cancel oldest running task) and `/stopall` (cancel all + drain queue). Both are intercepted before the 800ms debouncer to avoid being merged with subsequent messages. See [08-scheduling-cron-heartbeat.md](./08-scheduling-cron-heartbeat.md) for details.
|
||||
- **Concurrent group support**: Group sessions support up to 3 concurrent agent runs, allowing multiple users to get responses in parallel.
|
||||
- **Proxy support**: Optional HTTP proxy configured via channel config.
|
||||
- **Cancel commands**: `/stop` and `/stopall` intercepted before the 800ms debouncer. See [08-scheduling-cron-heartbeat.md](./08-scheduling-cron-heartbeat.md).
|
||||
- **Concurrent group support**: Group sessions support up to 3 concurrent agent runs.
|
||||
- **Bot reply as implicit mention**: Replying to a bot message in a group counts as mentioning the bot.
|
||||
|
||||
### Formatting Pipeline
|
||||
|
||||
LLM output is transformed through a multi-step pipeline to produce valid Telegram HTML. Telegram supports only `<b>`, `<i>`, `<s>`, `<a>`, `<code>`, `<pre>`, `<blockquote>` -- no `<table>` support.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
IN["LLM Output (Markdown)"] --> S1["Extract tables as placeholders"]
|
||||
S1 --> S2["Extract code blocks as placeholders"]
|
||||
S2 --> S3["Extract inline code as placeholders"]
|
||||
S3 --> S4["Convert Markdown to HTML<br/>(headers, bold, italic, links, lists)"]
|
||||
S4 --> S5["Restore placeholders:<br/>inline code as code tags<br/>code blocks as pre tags<br/>tables as pre (ASCII-aligned)"]
|
||||
S5 --> S6["Chunk at 4000 chars<br/>(split at paragraph > line > space)"]
|
||||
S4 --> S5["Restore placeholders:<br/>inline code → code tags<br/>code blocks → pre tags<br/>tables → pre (ASCII-aligned)"]
|
||||
S5 --> S6["Chunk at 4,000 chars<br/>(split at paragraph > line > space)"]
|
||||
S6 --> S7["Send as HTML<br/>(fallback: plain text on error)"]
|
||||
```
|
||||
|
||||
- **Table rendering**: Markdown tables are rendered as ASCII-aligned text inside `<pre>` tags (not `<pre><code>` to avoid "Copy" button). Cell content has inline markdown stripped (`**bold**`, `_italic_` markers removed).
|
||||
- **CJK handling**: `displayWidth()` correctly counts CJK and emoji characters as 2-column width for proper table alignment.
|
||||
Tables are rendered as ASCII-aligned text inside `<pre>` tags. CJK and emoji characters are counted as 2-column width for proper alignment.
|
||||
|
||||
### Forum Topics
|
||||
|
||||
Telegram forum topics (supergroup threads) get per-topic configuration with layered merging.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
GLOBAL["Global defaults<br/>(TelegramConfig)"] --> WILD["Wildcard group '*'<br/>(if configured)"]
|
||||
WILD --> GROUP["Specific group<br/>by chat ID"]
|
||||
GROUP --> TOPIC["Specific topic<br/>within group"]
|
||||
TOPIC --> RESOLVED["Resolved topic config"]
|
||||
```
|
||||
|
||||
**Configurable per topic:**
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `groupPolicy` | open, allowlist, pairing, disabled |
|
||||
| `requireMention` | Override mention gating for this topic |
|
||||
| `allowFrom` | User/ID allowlist for this topic |
|
||||
| `enabled` | Enable/disable this specific topic |
|
||||
| `skills` | Override available skills (nil=inherit, []=none, ["x","y"]=whitelist) |
|
||||
| `tools` | Override available tools (supports `group:xxx` syntax) |
|
||||
| `systemPrompt` | Additional system prompt (concatenated at topic level) |
|
||||
|
||||
**Session key format:**
|
||||
|
||||
| Context | Key Format |
|
||||
|---------|------------|
|
||||
| Regular chat | `"-12345"` |
|
||||
| Forum topic | `"-12345:topic:99"` |
|
||||
| DM thread | `"-12345:thread:55"` |
|
||||
|
||||
General topic (ID=1) is stripped during send — Telegram API requires no thread ID for the general topic. Deleted topics are detected via error message matching and retried without the thread ID.
|
||||
|
||||
### Tool Allow List (Per-Topic)
|
||||
|
||||
Each topic can restrict which tools the agent may use. The `tools` field accepts tool names and group references:
|
||||
|
||||
- `nil` = inherit all tools (no restriction)
|
||||
- `[]` = no tools for this topic
|
||||
- `["web_search", "group:fs"]` = only web search and filesystem tools
|
||||
|
||||
The tool allow list is passed via message metadata and applied by the policy engine before the LLM sees the tool definitions.
|
||||
|
||||
### Speech-to-Text
|
||||
|
||||
Voice and audio messages can be transcribed via an external STT proxy service.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
VOICE["Voice/audio message"] --> DOWNLOAD["Download audio file<br/>from Telegram"]
|
||||
DOWNLOAD --> STT["POST to STT proxy<br/>(multipart: file + tenant_id)"]
|
||||
STT --> PARSE["Parse transcript"]
|
||||
PARSE --> INJECT["Prepend to message:<br/>[audio: filename] Transcript: text"]
|
||||
INJECT --> AGENT["Agent receives transcribed content"]
|
||||
|
||||
VOICE --> ROUTING{"VoiceAgentID configured?"}
|
||||
ROUTING -->|Yes| VOICE_AGENT["Route to voice-specific agent"]
|
||||
ROUTING -->|No| DEFAULT_AGENT["Route to channel's default agent"]
|
||||
```
|
||||
|
||||
**Configuration**: STT proxy URL, timeout (default 30s), optional tenant ID and API key. If transcription fails, the media placeholder remains — no error is surfaced.
|
||||
|
||||
**Voice routing**: When `VoiceAgentID` is configured, audio/voice messages are routed to a different agent (e.g., a speech-specialized agent) instead of the channel's default agent.
|
||||
|
||||
### Bot Commands
|
||||
|
||||
Commands are processed before enriching content with reply/forward context (to prevent parsing issues).
|
||||
|
||||
| Command | Description | Group Restriction |
|
||||
|---------|-------------|:-:|
|
||||
| `/help` | Show command list | -- |
|
||||
| `/start` | Passthrough to agent | -- |
|
||||
| `/stop` | Cancel current run | -- |
|
||||
| `/stopall` | Cancel all runs | -- |
|
||||
| `/reset` | Clear session history | Writers only |
|
||||
| `/status` | Bot status + username | -- |
|
||||
| `/tasks` | Team task list | -- |
|
||||
| `/task_detail <id>` | View task detail | -- |
|
||||
| `/addwriter` | Add group file writer (reply to target user) | Writers only |
|
||||
| `/removewriter` | Remove group file writer | Writers only |
|
||||
| `/writers` | List group file writers | -- |
|
||||
|
||||
### Group File Writer Restrictions
|
||||
|
||||
In group chats, write-sensitive operations (file writes, `/reset`) are restricted to designated writers. The group ID format is `group:telegram:{chatID}`.
|
||||
|
||||
- Permission check queries the database: `IsGroupFileWriter(agentID, groupID, senderID)`
|
||||
- Fail-open on database errors (security logged as `security.reset_writer_check_failed`)
|
||||
- Writers are managed via `/addwriter` and `/removewriter` commands
|
||||
|
||||
---
|
||||
|
||||
@@ -212,19 +302,70 @@ The Feishu/Lark channel connects via native HTTP with two transport modes.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
MODE{"Connection mode?"} -->|"ws (default)"| WS["WebSocket Client<br/>Persistent connection<br/>Auto-reconnect"]
|
||||
MODE -->|"webhook"| WH["HTTP Webhook Server<br/>Listens on configured port<br/>Challenge verification"]
|
||||
MODE{"Connection mode?"} -->|"websocket (default)"| WS["WebSocket Client<br/>Persistent connection<br/>Auto-reconnect"]
|
||||
MODE -->|"webhook"| WH["Webhook endpoint<br/>Mounted on gateway mux<br/>or separate port"]
|
||||
```
|
||||
|
||||
### Key Behaviors
|
||||
When using webhook mode with port=0, the handler is mounted directly on the gateway's main HTTP mux (see webhook mount in Section 2). If a separate port is configured, a dedicated server is started.
|
||||
|
||||
- **Default domain**: Lark Global (`open.larksuite.com`). Configurable for Feishu China.
|
||||
- **Streaming message cards**: Responses are delivered as interactive card messages with streaming updates, providing real-time output display. Updates are throttled at 100ms intervals with incrementing sequence numbers.
|
||||
- **Media handling**: Supports image and file uploads/downloads with a default 30 MB limit.
|
||||
- **Mention support**: Processes `@bot` mentions in group chats with mention text stripping.
|
||||
- **Sender caching**: User names are cached with a 10-minute TTL to reduce API calls.
|
||||
- **Deduplication**: Message IDs tracked via `sync.Map` to prevent processing duplicate events.
|
||||
- **Pairing debounce**: 60-second debounce on pairing-related replies.
|
||||
### Configuration
|
||||
|
||||
| Field | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `ConnectionMode` | `"websocket"` | `"websocket"` or `"webhook"` |
|
||||
| `WebhookPort` | 0 | 0 = mount on gateway mux; >0 = separate server |
|
||||
| `WebhookPath` | `"/feishu/events"` | Webhook endpoint path |
|
||||
| `RenderMode` | `"auto"` | `"auto"` (detect code/tables), `"card"`, or default text |
|
||||
| `TextChunkLimit` | 4,000 | Max characters per text message |
|
||||
| `MediaMaxMB` | 30 | Max file size for media (MB) |
|
||||
| `TopicSessionMode` | disabled | `"enabled"` for thread-per-topic session isolation |
|
||||
| `RequireMention` | true | Require bot mention in group |
|
||||
| `GroupAllowFrom` | -- | Group-level allowlist (separate from DM) |
|
||||
| `ReactionLevel` | -- | `"off"`, `"minimal"` (terminal only), or full |
|
||||
|
||||
### Streaming Message Cards
|
||||
|
||||
Responses are delivered as interactive card messages with real-time streaming updates.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
START["Agent starts responding"] --> CREATE["Create card<br/>(streaming_mode: true)"]
|
||||
CREATE --> SEND["Send interactive message<br/>with card JSON"]
|
||||
SEND --> UPDATE["Update card element<br/>with accumulated text<br/>(throttled: 100ms min)"]
|
||||
UPDATE -->|"more chunks"| UPDATE
|
||||
UPDATE -->|"done"| CLOSE["Close stream<br/>(streaming_mode: false)"]
|
||||
```
|
||||
|
||||
Each update increments a sequence number for ordering. Updates are throttled at 100ms minimum intervals to avoid API rate limiting. The streaming card displays content with a print animation effect (50ms frequency, 2-character steps).
|
||||
|
||||
### Media Handling
|
||||
|
||||
**Receive (inbound)**: Images, files, audio, video, and stickers are downloaded from the Feishu API with configurable size limits (default 30 MB). Oversized files are silently skipped.
|
||||
|
||||
| Media Type | Saved As |
|
||||
|------------|----------|
|
||||
| Image | `.png` |
|
||||
| File | Original extension |
|
||||
| Audio | `.opus` |
|
||||
| Video | `.mp4` |
|
||||
| Sticker | `.png` |
|
||||
|
||||
**Send (outbound)**: Files are uploaded to Feishu with automatic type detection (opus, mp4, pdf, doc, xls, ppt, or generic stream).
|
||||
|
||||
### Mention Resolution
|
||||
|
||||
Feishu sends content with placeholder tokens (e.g., `@_user_1`) for mentioned users. GoClaw processes these:
|
||||
|
||||
- **Bot mentions**: Stripped entirely (just the trigger, not meaningful content)
|
||||
- **User mentions**: Replaced with `@DisplayName` from the mention list
|
||||
- Fallback detection when bot ID is unknown
|
||||
|
||||
### Topic Session Mode
|
||||
|
||||
When enabled, each thread gets an isolated session:
|
||||
- Session key includes the thread root message ID: `"{chatID}:topic:{rootID}"`
|
||||
- Different threads within the same group maintain separate conversation histories
|
||||
- Disabled by default
|
||||
|
||||
---
|
||||
|
||||
@@ -234,10 +375,13 @@ The Discord channel uses the `discordgo` library to connect via the Discord Gate
|
||||
|
||||
### Key Behaviors
|
||||
|
||||
- **Gateway intents**: Requests `GuildMessages`, `DirectMessages`, and `MessageContent` intents.
|
||||
- **Message limit**: 2000-character limit per message, with automatic splitting for longer content.
|
||||
- **Placeholder editing**: Sends an initial "Thinking..." message that gets edited with the actual response when complete.
|
||||
- **Bot identity**: Fetches `@me` on startup to detect and ignore own messages.
|
||||
- **Gateway intents**: Requests `GuildMessages`, `DirectMessages`, and `MessageContent` intents
|
||||
- **Message limit**: 2,000-character limit with automatic splitting at newlines
|
||||
- **Placeholder editing**: Sends "Thinking..." → edits with actual response
|
||||
- **Mention gating**: `requireMention` default true; bot mention stripped from content
|
||||
- **Bot identity**: Fetches `@me` on startup to detect and ignore own messages
|
||||
- **Typing indicator**: 9-second keepalive while agent processes
|
||||
- **Group history**: Pending message buffer for context when mentioned
|
||||
|
||||
---
|
||||
|
||||
@@ -247,56 +391,119 @@ The WhatsApp channel communicates through an external WebSocket bridge (e.g., wh
|
||||
|
||||
### Key Behaviors
|
||||
|
||||
- **Bridge connection**: Connects to a configurable `bridge_url` via WebSocket.
|
||||
- **JSON format**: Messages are sent and received as JSON objects over the WebSocket connection.
|
||||
- **Auto-reconnect**: If the initial connection fails, a background listen loop retries automatically.
|
||||
- **DM and group support**: Both are supported through the bridge protocol.
|
||||
- **Bridge connection**: Connects to configurable `bridge_url` via WebSocket
|
||||
- **JSON format**: Messages sent/received as JSON objects
|
||||
- **Auto-reconnect**: Exponential backoff (1s → 30s max)
|
||||
- **DM and group support**: Group detection via `@g.us` suffix in chat ID
|
||||
- **Media handling**: Array of file paths from bridge protocol
|
||||
|
||||
---
|
||||
|
||||
## 9. Zalo
|
||||
## 9. Zalo OA
|
||||
|
||||
The Zalo channel connects to the Zalo OA Bot API.
|
||||
The Zalo OA (Official Account) channel connects to the Zalo OA Bot API.
|
||||
|
||||
### Key Behaviors
|
||||
|
||||
- **DM only**: No group support. Only direct messages are processed.
|
||||
- **Text limit**: 2000-character maximum per message.
|
||||
- **Long polling**: Uses long polling with a default 30-second timeout and 5-second backoff on errors.
|
||||
- **Media**: Image support with a 5 MB default limit.
|
||||
- **Default DM policy**: `"pairing"` (requires pairing code for new users).
|
||||
- **Pairing debounce**: 60-second debounce to avoid flooding users with pairing instructions.
|
||||
- **DM only**: No group support. Only direct messages are processed
|
||||
- **Text limit**: 2,000-character maximum per message
|
||||
- **Long polling**: Default 30-second timeout, 5-second backoff on errors
|
||||
- **Media**: Image support with 5 MB default limit
|
||||
- **Default DM policy**: `"pairing"` (requires pairing code)
|
||||
- **Pairing debounce**: 60-second debounce on pairing instructions
|
||||
|
||||
---
|
||||
|
||||
## 10. Pairing System
|
||||
## 10. Zalo Personal
|
||||
|
||||
The Zalo Personal channel provides access to personal Zalo accounts using a reverse-engineered protocol. This is an unofficial integration.
|
||||
|
||||
### Key Differences from Zalo OA
|
||||
|
||||
| Aspect | Zalo OA | Zalo Personal |
|
||||
|--------|---------|---------------|
|
||||
| Protocol | Official Bot API | Reverse-engineered (zcago, MIT) |
|
||||
| DM support | Yes | Yes |
|
||||
| Group support | No | Yes |
|
||||
| Default DM policy | `pairing` | `allowlist` (restrictive) |
|
||||
| Default group policy | N/A | `allowlist` (restrictive) |
|
||||
| Authentication | API credentials | Pre-loaded credentials or QR scan |
|
||||
| Risk | None | Account may be locked/banned |
|
||||
|
||||
### Security Warning
|
||||
|
||||
Zalo Personal uses an unofficial, reverse-engineered protocol. The account used may be locked or banned by Zalo at any time. A security warning is logged on startup: `security.unofficial_api`.
|
||||
|
||||
### Resilience
|
||||
|
||||
- Maximum 10 restart attempts
|
||||
- Exponential backoff up to 60 seconds
|
||||
- Special handling for error code 3000: 60-second initial delay
|
||||
- Typing controller per thread
|
||||
|
||||
---
|
||||
|
||||
## 11. Channel-Isolated Workspaces
|
||||
|
||||
Each channel instance can target a specific agent, providing workspace isolation across channels.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CH1["Telegram Channel<br/>→ Agent: assistant"] --> WS1["Workspace: assistant/"]
|
||||
CH2["Discord Channel<br/>→ Agent: coder"] --> WS2["Workspace: coder/"]
|
||||
CH3["Feishu Channel<br/>→ Agent: assistant"] --> WS1
|
||||
|
||||
WS1 --> USER1["user_alice/"]
|
||||
WS1 --> USER2["user_bob/"]
|
||||
WS2 --> USER3["user_charlie/"]
|
||||
```
|
||||
|
||||
In managed mode, channel instances are loaded from the database with their assigned agent ID. The agent key is resolved and propagated through the message pipeline, ensuring all filesystem tools, context files, and memory operations use the correct workspace.
|
||||
|
||||
---
|
||||
|
||||
## 12. Local Key Propagation
|
||||
|
||||
Thread/topic context is preserved through the entire message pipeline using a `local_key` in message metadata. This ensures subagent, delegation, and team message results land in the correct thread — not the root chat.
|
||||
|
||||
| Platform | Local Key Format |
|
||||
|----------|-----------------|
|
||||
| Telegram (chat) | `"-12345"` |
|
||||
| Telegram (topic) | `"-12345:topic:99"` |
|
||||
| Telegram (thread) | `"-12345:thread:55"` |
|
||||
| Feishu (chat) | `"oc_xyz"` |
|
||||
| Feishu (topic) | `"oc_xyz:topic:{root_msg_id}"` |
|
||||
|
||||
All channel state — placeholders, streams, reactions, typing controllers, thread IDs — is keyed by this composite `local_key`. When delegation or team messages complete, the `local_key` from the original message is preserved in metadata and used to route the response back to the correct location.
|
||||
|
||||
---
|
||||
|
||||
## 13. Managed Mode Behavior
|
||||
|
||||
In managed mode, channels provide per-user isolation through compound sender IDs and context propagation:
|
||||
|
||||
- **User scoping**: Each channel constructs a compound sender ID (e.g., `telegram:123456`) which maps to a `user_id`. The session key format `agent:{agentId}:{channel}:direct:{peerId}` ensures each user has isolated conversation history per agent.
|
||||
- **Context propagation**: `HandleMessage()` injects `AgentID`, `UserID`, and `AgentType` into the context. These flow to the ContextFileInterceptor, MemoryInterceptor, and per-user file seeding.
|
||||
- **Pairing storage**: PostgreSQL (`pairing_requests` and `paired_devices` tables) in managed mode; JSON files in standalone mode.
|
||||
- **Session persistence**: PostgreSQL `sessions` table with write-behind caching.
|
||||
|
||||
---
|
||||
|
||||
## 14. Pairing System
|
||||
|
||||
The pairing system provides a DM authentication flow for channels using the `pairing` DM policy.
|
||||
|
||||
### Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as New User
|
||||
participant CH as Channel
|
||||
participant PS as Pairing Service
|
||||
participant O as Owner
|
||||
|
||||
U->>CH: First DM message
|
||||
CH->>CH: Check DM policy = "pairing"
|
||||
CH->>PS: Generate 8-char pairing code
|
||||
PS-->>CH: Code (valid 60 min)
|
||||
CH-->>U: "Reply with your pairing code from the admin"
|
||||
|
||||
Note over PS: Max 3 pending codes per account
|
||||
|
||||
O->>PS: Approve code via device.pair.approve
|
||||
PS->>PS: Add sender to paired devices
|
||||
|
||||
U->>CH: Next DM message
|
||||
CH->>PS: Check paired status
|
||||
PS-->>CH: Paired (approved)
|
||||
CH->>CH: Process message normally
|
||||
flowchart TD
|
||||
NEW["New user sends DM"] --> CHECK{"DM policy = pairing?"}
|
||||
CHECK -->|No| PROCESS["Process normally"]
|
||||
CHECK -->|Yes| PAIRED{"Already paired<br/>or in allowlist?"}
|
||||
PAIRED -->|Yes| PROCESS
|
||||
PAIRED -->|No| CODE["Generate 8-char code<br/>(valid 60 min)"]
|
||||
CODE --> REPLY["Send pairing instructions<br/>(debounce 60s)"]
|
||||
REPLY --> WAIT["Wait for admin approval"]
|
||||
WAIT --> APPROVE["Admin approves via<br/>device.pair.approve"]
|
||||
APPROVE --> DONE["User paired, future messages<br/>processed normally"]
|
||||
```
|
||||
|
||||
### Code Specification
|
||||
@@ -315,20 +522,38 @@ sequenceDiagram
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `internal/channels/channel.go` | Channel interface, BaseChannel, DMPolicy/GroupPolicy types, HandleMessage |
|
||||
| `internal/channels/manager.go` | Manager: channel registration, StartAll, StopAll, outbound dispatch |
|
||||
| `internal/channels/telegram/telegram.go` | Telegram channel: long polling, mention gating, typing indicators |
|
||||
| `internal/channels/telegram/commands.go` | /stop, /stopall command handlers, menu registration |
|
||||
| `internal/channels/telegram/format.go` | Markdown-to-Telegram-HTML pipeline, table rendering, CJK width |
|
||||
| `internal/channels/telegram/format_test.go` | Tests for Telegram formatting pipeline |
|
||||
| `internal/channels/feishu/feishu.go` | Feishu/Lark channel: WS/Webhook modes, card messages |
|
||||
| `internal/channels/feishu/streaming.go` | Streaming message card updates |
|
||||
| `internal/channels/feishu/media.go` | Media upload/download handling |
|
||||
| `internal/channels/feishu/larkclient.go` | Native HTTP client for Lark API |
|
||||
| `internal/channels/feishu/larkws.go` | WebSocket transport for Lark |
|
||||
| `internal/channels/feishu/larkevents.go` | Event parsing and routing |
|
||||
| `internal/channels/discord/discord.go` | Discord channel: gateway events, message editing |
|
||||
| `internal/channels/whatsapp/whatsapp.go` | WhatsApp channel: external WS bridge |
|
||||
| `internal/channels/zalo/zalo.go` | Zalo channel: OA Bot API, long polling, DM only |
|
||||
| `internal/pairing/service.go` | Pairing service: code generation, approval, persistence |
|
||||
| `cmd/gateway_consumer.go` | Message consumer: routing prefixes, handoff check, cancel interception |
|
||||
| `internal/channels/channel.go` | Channel interface, BaseChannel, extended interfaces, HandleMessage |
|
||||
| `internal/channels/manager.go` | Manager: registration, StartAll, StopAll, outbound dispatch, webhook collection |
|
||||
| `internal/channels/instance_loader.go` | DB-based channel instance loading (managed mode) |
|
||||
| `internal/channels/telegram/channel.go` | Telegram core: long polling, mention gating, typing indicators |
|
||||
| `internal/channels/telegram/handlers.go` | Message handling, media processing, forum topic detection |
|
||||
| `internal/channels/telegram/topic_config.go` | Per-topic config layering and resolution |
|
||||
| `internal/channels/telegram/commands.go` | Bot commands: /stop, /reset, /tasks, /addwriter, etc. |
|
||||
| `internal/channels/telegram/stt.go` | Speech-to-text proxy integration, voice agent routing |
|
||||
| `internal/channels/telegram/stream.go` | Streaming placeholder management |
|
||||
| `internal/channels/telegram/reactions.go` | Status reactions on messages |
|
||||
| `internal/channels/telegram/format.go` | Markdown → Telegram HTML pipeline, table rendering |
|
||||
| `internal/channels/feishu/feishu.go` | Feishu core: WS/Webhook modes, config |
|
||||
| `internal/channels/feishu/streaming.go` | Streaming card create/update/close |
|
||||
| `internal/channels/feishu/media.go` | Media upload/download, type detection |
|
||||
| `internal/channels/feishu/bot_parse.go` | Mention resolution, message event parsing |
|
||||
| `internal/channels/feishu/bot.go` | Bot message handlers |
|
||||
| `internal/channels/feishu/bot_policy.go` | Policy evaluation |
|
||||
| `internal/channels/discord/discord.go` | Discord: gateway events, placeholder editing |
|
||||
| `internal/channels/whatsapp/whatsapp.go` | WhatsApp: external WS bridge |
|
||||
| `internal/channels/zalo/zalo.go` | Zalo OA: Bot API, long polling |
|
||||
| `internal/channels/zalo/personal/channel.go` | Zalo Personal: reverse-engineered protocol |
|
||||
| `internal/pairing/service.go` | Pairing: code generation, approval, persistence |
|
||||
| `cmd/gateway_consumer.go` | Message routing: prefixes, handoff, cancel interception |
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
| Document | Relevant Content |
|
||||
|----------|-----------------|
|
||||
| [00-architecture-overview.md](./00-architecture-overview.md) | Channel startup in gateway sequence |
|
||||
| [03-tools-system.md](./03-tools-system.md) | Tool policy engine, per-request tool allow list |
|
||||
| [08-scheduling-cron-heartbeat.md](./08-scheduling-cron-heartbeat.md) | /stop and /stopall commands, scheduler lanes |
|
||||
| [09-security.md](./09-security.md) | Group file writer restrictions, security logging |
|
||||
| [11-agent-teams.md](./11-agent-teams.md) | Team message routing, delegation result delivery |
|
||||
@@ -344,6 +344,42 @@ flowchart TD
|
||||
|
||||
---
|
||||
|
||||
## 12.5. Per-Agent Skill Filtering
|
||||
|
||||
In addition to visibility grants, agents can restrict which skills they have access to through a per-agent skill allow list.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
ALL["All accessible skills<br/>(from visibility + grants)"] --> AGENT{"Agent has<br/>skillAllowList?"}
|
||||
AGENT -->|"nil (default)"| ALL_PASS["All accessible skills available"]
|
||||
AGENT -->|"[] (empty)"| NONE["No skills available"]
|
||||
AGENT -->|'["x", "y"]'| FILTER["Only named skills available"]
|
||||
|
||||
FILTER --> REQUEST{"Per-request<br/>SkillFilter?"}
|
||||
ALL_PASS --> REQUEST
|
||||
REQUEST -->|"nil"| USE["Use agent-level filter"]
|
||||
REQUEST -->|"Set"| OVERRIDE["Override with request filter"]
|
||||
|
||||
USE --> MODE{"Count + tokens?"}
|
||||
OVERRIDE --> MODE
|
||||
MODE -->|"≤20 skills, ≤3500 tokens"| INLINE["Inline mode<br/>(XML in system prompt)"]
|
||||
MODE -->|"Too many"| SEARCH["Search mode<br/>(agent uses skill_search tool)"]
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
| Setting | Value | Behavior |
|
||||
|---------|-------|----------|
|
||||
| `skillAllowList = nil` | Default | All accessible skills available |
|
||||
| `skillAllowList = []` | Empty list | No skills — agent has no skill access |
|
||||
| `skillAllowList = ["billing-faq", "returns"]` | Named skills | Only these specific skills are available |
|
||||
|
||||
### Per-Request Override
|
||||
|
||||
Channels can override the skill allow list per request via message metadata. For example, Telegram forum topics can configure different skills per topic (see [05-channels-messaging.md](./05-channels-messaging.md) Section 5). The per-request filter takes priority over the agent-level setting.
|
||||
|
||||
---
|
||||
|
||||
## 13. Hot-Reload
|
||||
|
||||
An fsnotify-based watcher monitors all skill directories for changes to SKILL.md files.
|
||||
|
||||
@@ -18,25 +18,28 @@ Named worker pools (semaphore-based) with configurable concurrency limits. Each
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Lane: main (concurrency = 2)"
|
||||
subgraph "Lane: main (concurrency = 30)"
|
||||
M1["User chat 1"]
|
||||
M2["User chat 2"]
|
||||
M3["..."]
|
||||
end
|
||||
|
||||
subgraph "Lane: subagent (concurrency = 4)"
|
||||
subgraph "Lane: subagent (concurrency = 50)"
|
||||
S1["Subagent 1"]
|
||||
S2["Subagent 2"]
|
||||
S3["Subagent 3"]
|
||||
S4["Subagent 4"]
|
||||
S3["..."]
|
||||
end
|
||||
|
||||
subgraph "Lane: delegate (concurrency = 100)"
|
||||
D1["Delegation 1"]
|
||||
D2["Delegation 2"]
|
||||
D3["..."]
|
||||
end
|
||||
|
||||
subgraph "Lane: cron (concurrency = 1)"
|
||||
C1["Cron job"]
|
||||
subgraph "Lane: cron (concurrency = 30)"
|
||||
C1["Cron job 1"]
|
||||
C2["Cron job 2"]
|
||||
C3["..."]
|
||||
end
|
||||
|
||||
REQ["Incoming request"] --> SCHED["Scheduler.Schedule(ctx, lane, req)"]
|
||||
@@ -49,10 +52,10 @@ flowchart TD
|
||||
|
||||
| Lane | Concurrency | Env Override | Purpose |
|
||||
|------|:-----------:|-------------|---------|
|
||||
| `main` | 2 | `GOCLAW_LANE_MAIN` | Primary user chat sessions |
|
||||
| `subagent` | 4 | `GOCLAW_LANE_SUBAGENT` | Sub-agents spawned by the main agent |
|
||||
| `main` | 30 | `GOCLAW_LANE_MAIN` | Primary user chat sessions |
|
||||
| `subagent` | 50 | `GOCLAW_LANE_SUBAGENT` | Sub-agents spawned by the main agent |
|
||||
| `delegate` | 100 | `GOCLAW_LANE_DELEGATE` | Agent delegation executions |
|
||||
| `cron` | 1 | `GOCLAW_LANE_CRON` | Scheduled cron jobs (sequential to avoid conflicts) |
|
||||
| `cron` | 30 | `GOCLAW_LANE_CRON` | Scheduled cron jobs (per-session serialization prevents same-job races) |
|
||||
|
||||
`GetOrCreate()` allows creating new lanes on demand with custom concurrency. All lane concurrency values are configurable via environment variables.
|
||||
|
||||
@@ -166,6 +169,8 @@ Jobs can be `active` or `paused`. Paused jobs skip execution during the due chec
|
||||
|
||||
Periodically wakes the agent to check on events (calendar, inbox, alerts) and surfaces anything that needs attention.
|
||||
|
||||
> **Standalone mode only in practice**: HEARTBEAT.md is seeded from bootstrap templates only in standalone mode. In managed mode, HEARTBEAT.md is excluded from seeding (replaced by cron jobs). The heartbeat service still runs in both modes, but without a HEARTBEAT.md file with content, Step 2 skips execution.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
TICK["tick() -- every interval (default 30 min)"] --> S1{"Step 1:<br/>Within Active Hours?"}
|
||||
@@ -184,9 +189,13 @@ flowchart TD
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| Interval | 30 minutes | Time between heartbeat wakes |
|
||||
| Interval (`every`) | 30 minutes | Time between heartbeat wakes (`"0m"` to disable) |
|
||||
| ActiveHours | (none) | Time window restriction, supports wrap-around midnight |
|
||||
| Target | `"last"` | `"last"` (last-used channel), `"none"`, or explicit channel name |
|
||||
| To | (empty) | Explicit chat ID (overrides target resolver) |
|
||||
| Prompt | (default) | Custom system prompt for heartbeat runs |
|
||||
| Model | (agent default) | Override LLM model for heartbeat runs |
|
||||
| Session | `agent:{id}:heartbeat:main` | Custom session key |
|
||||
| AckMaxChars | 300 | Content alongside HEARTBEAT_OK up to this length is still treated as OK |
|
||||
|
||||
### HEARTBEAT_OK Detection
|
||||
@@ -205,7 +214,12 @@ Recognizes multiple formatting variants: `HEARTBEAT_OK`, `**HEARTBEAT_OK**`, ``
|
||||
| `internal/cron/retry.go` | Retry with exponential backoff + jitter |
|
||||
| `internal/heartbeat/service.go` | Heartbeat loop, HEARTBEAT_OK detection, active hours |
|
||||
| `internal/store/cron_store.go` | CronStore interface (jobs + run logs) |
|
||||
| `internal/store/file/cron.go` | File-based cron implementation (standalone mode) |
|
||||
| `internal/store/pg/cron.go` | PostgreSQL cron implementation |
|
||||
| `internal/store/pg/cron_scheduler.go` | PG job cache, due-job detection, execution |
|
||||
| `cmd/gateway_cron.go` | makeCronJobHandler (routes cron execution to scheduler) |
|
||||
| `cmd/gateway_agents.go` | setupHeartbeat, agent initialization |
|
||||
| `internal/gateway/methods/cron.go` | RPC method handlers (list, create, update, delete, toggle, run, runs) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+35
-2
@@ -91,7 +91,8 @@ All four filesystem tools (`read_file`, `write_file`, `list_files`, `edit`) impl
|
||||
|
||||
| Mechanism | Detail |
|
||||
|-----------|--------|
|
||||
| Credential scrubbing | Regex detection of: OpenAI (`sk-...`), Anthropic (`sk-ant-...`), GitHub (`ghp_/gho_/ghu_/ghs_/ghr_`), AWS (`AKIA...`), generic key-value patterns. All replaced with `[REDACTED]`. |
|
||||
| Credential scrubbing | Static regex detection of: OpenAI (`sk-...`), Anthropic (`sk-ant-...`), GitHub (`ghp_/gho_/ghu_/ghs_/ghr_`), AWS (`AKIA...`), generic key-value patterns, connection strings (`postgres://`, `mysql://`), env var patterns (`KEY=`, `SECRET=`, `DSN=`), long hex strings (64+ chars). All replaced with `[REDACTED]`. |
|
||||
| Dynamic credential scrubbing | Runtime-registered values (e.g., server IPs) scrubbed alongside static patterns via `AddDynamicScrubValues()` |
|
||||
| Web content wrapping | Fetched content wrapped in `<<<EXTERNAL_UNTRUSTED_CONTENT>>>` tags with security warning |
|
||||
|
||||
### Layer 5: Isolation
|
||||
@@ -292,7 +293,39 @@ A context flag `hooks.WithSkipHooks(ctx, true)` prevents this. Three injection p
|
||||
|
||||
---
|
||||
|
||||
## 8. Delegation Security
|
||||
## 8. Group File Writer Restrictions
|
||||
|
||||
In group chats (Telegram), write-sensitive operations are restricted to designated writers. This prevents unauthorized users from modifying agent files or resetting sessions in shared groups.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CMD["Write-sensitive command<br/>(/reset, /addwriter, file writes)"] --> GROUP{"In group chat?"}
|
||||
GROUP -->|No| ALLOW["Allow (DM = no restriction)"]
|
||||
GROUP -->|Yes| CHECK["Check IsGroupFileWriter()<br/>(agentID, groupID, senderID)"]
|
||||
CHECK -->|Writer| ALLOW
|
||||
CHECK -->|Not writer| DENY["Deny operation"]
|
||||
CHECK -->|DB error| FALLBACK["Fail-open: Allow<br/>(log security.reset_writer_check_failed)"]
|
||||
```
|
||||
|
||||
### Group ID Format
|
||||
|
||||
`group:{channel}:{chatID}` — for example, `group:telegram:-1001234567`.
|
||||
|
||||
### Managed Commands
|
||||
|
||||
| Command | Restriction |
|
||||
|---------|-------------|
|
||||
| `/reset` | Writers only in groups |
|
||||
| `/addwriter` | Writers only (reply to target user to add) |
|
||||
| `/removewriter` | Writers only |
|
||||
| `/writers` | No restriction (informational) |
|
||||
| File writes (exec) | Writers only in groups |
|
||||
|
||||
Writers are managed via `/addwriter` (reply to a user's message) and `/removewriter` commands. The writer list is stored per-agent per-group in the agent store.
|
||||
|
||||
---
|
||||
|
||||
## 9. Delegation Security
|
||||
|
||||
Agent delegation uses directed permissions via the `agent_links` table.
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ Context values (traceID, collector) survive cancellation -- only `ctx.Done()` an
|
||||
| `llm_call` | LLM provider call | Client |
|
||||
| `tool_call` | Tool execution | Internal |
|
||||
| `agent` | Root agent span (parents all child spans) | Internal |
|
||||
| `embedding` | Embedding generation (vector store operations) | Internal |
|
||||
| `event` | Discrete event marker (no duration) | Internal |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -50,6 +52,7 @@ flowchart TD
|
||||
AGENT --> TOOL1["Tool Span: exec<br/>(tool_name, duration)"]
|
||||
AGENT --> LLM2["LLM Call Span 2"]
|
||||
AGENT --> TOOL2["Tool Span: read_file"]
|
||||
AGENT --> EMB["Embedding Span<br/>(vector store operation)"]
|
||||
AGENT --> LLM3["LLM Call Span 3"]
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
# 11 - Agent Teams
|
||||
|
||||
## Overview
|
||||
|
||||
Agent teams enable collaborative multi-agent orchestration. A team consists of a **lead** agent and one or more **member** agents. The lead orchestrates work by creating tasks on a shared task board and delegating them to members. Members execute tasks independently and report results back. Communication happens through a built-in mailbox system.
|
||||
|
||||
Teams build on top of the delegation system (see [03-tools-system.md](./03-tools-system.md) Section 7) by adding structured coordination: task tracking, parallel work distribution, and result aggregation.
|
||||
|
||||
---
|
||||
|
||||
## 1. Team Model
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Team["Agent Team"]
|
||||
LEAD["Lead Agent<br/>Orchestrates work, creates tasks,<br/>delegates to members, synthesizes results"]
|
||||
M1["Member A<br/>Claims and executes tasks"]
|
||||
M2["Member B<br/>Claims and executes tasks"]
|
||||
M3["Member C<br/>Claims and executes tasks"]
|
||||
end
|
||||
|
||||
subgraph Shared["Shared Resources"]
|
||||
TB["Task Board<br/>Create, claim, complete tasks"]
|
||||
MB["Mailbox<br/>Direct messages, broadcasts"]
|
||||
end
|
||||
|
||||
USER["User"] -->|message| LEAD
|
||||
LEAD -->|create task + delegate| M1 & M2 & M3
|
||||
M1 & M2 & M3 -->|results auto-announced| LEAD
|
||||
LEAD -->|synthesized response| USER
|
||||
|
||||
LEAD & M1 & M2 & M3 <--> TB
|
||||
LEAD & M1 & M2 & M3 <--> MB
|
||||
```
|
||||
|
||||
### Key Design Principles
|
||||
|
||||
- **Lead-centric**: Only the lead receives `TEAM.md` in its system prompt with full orchestration instructions. Members discover context on demand through tools — no wasted tokens on idle agents.
|
||||
- **Mandatory task tracking**: Every delegation from a lead must be linked to a task on the board. The system enforces this — delegations without a `team_task_id` are rejected.
|
||||
- **Auto-completion**: When a delegation finishes, its linked task is automatically marked as complete. No manual bookkeeping required.
|
||||
- **Parallel batching**: When multiple members work simultaneously, results are collected and delivered to the lead in a single combined announcement.
|
||||
|
||||
---
|
||||
|
||||
## 2. Team Lifecycle
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CREATE["Admin creates team<br/>(name, lead, members)"] --> LINK["Auto-create delegation links<br/>Lead → each member (outbound)"]
|
||||
LINK --> INJECT["TEAM.md auto-injected<br/>into all members' system prompts"]
|
||||
INJECT --> READY["Team ready"]
|
||||
|
||||
READY --> USE["User messages lead agent"]
|
||||
USE --> LEAD_WORK["Lead orchestrates work<br/>using task board + delegation"]
|
||||
|
||||
MANAGE["Admin manages team"] --> ADD["Add member<br/>→ auto-link lead→member"]
|
||||
MANAGE --> REMOVE["Remove member<br/>→ link remains (manual cleanup)"]
|
||||
MANAGE --> DELETE["Delete team<br/>→ team_id cleared from links"]
|
||||
```
|
||||
|
||||
### Creation Process
|
||||
|
||||
1. Resolve lead agent by key or UUID
|
||||
2. Resolve all member agents
|
||||
3. Create team record with `status=active`
|
||||
4. Add lead as member with `role=lead`
|
||||
5. Add each member with `role=member`
|
||||
6. Auto-create outbound agent links from lead to each member (direction: outbound, max_concurrent: 3, marked with `team_id`)
|
||||
7. Invalidate agent router caches so TEAM.md is injected on next request
|
||||
|
||||
When a member is added later, the same auto-linking happens. Links created by team setup are tagged with `team_id` — this distinguishes them from manually created delegation links.
|
||||
|
||||
---
|
||||
|
||||
## 3. Lead vs Member Roles
|
||||
|
||||
The lead and members have fundamentally different responsibilities and tool access.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Lead["Lead Agent"]
|
||||
L1["Receives TEAM.md with<br/>full orchestration instructions"]
|
||||
L2["Creates tasks on board"]
|
||||
L3["Delegates tasks to members"]
|
||||
L4["Receives combined results"]
|
||||
L5["Synthesizes and replies to user"]
|
||||
end
|
||||
|
||||
subgraph Member["Member Agent"]
|
||||
M1["Receives delegation with task context"]
|
||||
M2["Executes task independently"]
|
||||
M3["Result auto-announced to lead"]
|
||||
M4["Can send progress updates<br/>via mailbox"]
|
||||
end
|
||||
|
||||
L3 --> M1
|
||||
M3 --> L4
|
||||
```
|
||||
|
||||
### What the Lead Sees (TEAM.md)
|
||||
|
||||
The lead's system prompt includes a `TEAM.md` section containing:
|
||||
|
||||
- Team name and description
|
||||
- Complete list of teammates with their roles and expertise (from `frontmatter`)
|
||||
- **Mandatory workflow instructions**: always create a task first, then delegate with the task ID
|
||||
- **Orchestration patterns**: sequential (A→B), iterative (A→B→A), parallel (A+B→review), mixed
|
||||
- **Communication guidelines**: notify user when assigning work, share progress on follow-up rounds
|
||||
|
||||
### What Members See (TEAM.md)
|
||||
|
||||
Members get a simpler version:
|
||||
|
||||
- Team name and teammate list
|
||||
- Instructions to focus on executing delegated work
|
||||
- How to send progress updates to the lead via mailbox
|
||||
- Available task board actions (list, get, search — no create/delegate)
|
||||
|
||||
---
|
||||
|
||||
## 4. Task Board
|
||||
|
||||
The task board is a shared work tracker accessible to all team members via the `team_tasks` tool.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Task Lifecycle"
|
||||
PENDING["Pending<br/>(just created)"] -->|claim| IN_PROGRESS["In Progress<br/>(agent working)"]
|
||||
PENDING -->|blocked_by set| BLOCKED["Blocked<br/>(waiting on dependencies)"]
|
||||
BLOCKED -->|all blockers complete| PENDING
|
||||
IN_PROGRESS -->|complete| COMPLETED["Completed<br/>(with result)"]
|
||||
PENDING -->|cancel| CANCELLED["Cancelled"]
|
||||
IN_PROGRESS -->|cancel| CANCELLED
|
||||
end
|
||||
```
|
||||
|
||||
### Actions
|
||||
|
||||
| Action | Description | Who Uses It |
|
||||
|--------|-------------|-------------|
|
||||
| `create` | Create task with subject, description, priority, blocked_by | Lead |
|
||||
| `claim` | Atomically claim a pending task | Members (or auto-claimed by delegation) |
|
||||
| `complete` | Mark task done with a result summary | Members (or auto-completed by delegation) |
|
||||
| `cancel` | Cancel a task with a reason | Lead |
|
||||
| `list` | List tasks (filter: active/completed/all, order: priority/newest) | All |
|
||||
| `get` | Get full task detail including result | All |
|
||||
| `search` | Full-text search over subject + description | All |
|
||||
|
||||
### Atomic Claiming
|
||||
|
||||
Two agents grabbing the same task is prevented at the database level. The claim operation uses a conditional update: `SET status = 'in_progress', owner = agent WHERE status = 'pending' AND owner IS NULL`. One row updated means claimed; zero rows means someone else got it first. No distributed mutex needed.
|
||||
|
||||
### Task Dependencies
|
||||
|
||||
Tasks can declare `blocked_by` — a list of prerequisite task IDs. A blocked task stays in `blocked` status until all its prerequisites are completed. When a task is completed via `complete`, all dependent tasks whose blockers are now all done automatically transition from `blocked` to `pending`.
|
||||
|
||||
### User Scoping
|
||||
|
||||
- Delegate and system channels see all tasks for the team
|
||||
- End users see only tasks they triggered (filtered by user ID)
|
||||
- List pagination: 20 tasks per page
|
||||
- Result truncation: 8,000 characters for `get`, 500 characters for search snippets
|
||||
|
||||
---
|
||||
|
||||
## 5. Team Mailbox
|
||||
|
||||
The mailbox enables peer-to-peer communication between team members via the `team_message` tool.
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| `send` | Send a direct message to a specific teammate by agent key |
|
||||
| `broadcast` | Send a message to all teammates (except self) |
|
||||
| `read` | Read unread messages, automatically marks them as read |
|
||||
|
||||
### Message Format
|
||||
|
||||
When a team message is sent, it flows through the message bus with a `"teammate:"` prefix:
|
||||
|
||||
```
|
||||
[Team message from {sender_key}]: {message text}
|
||||
```
|
||||
|
||||
The receiving agent processes this as an inbound message, routed through the delegate scheduler lane. The response is published back to the originating channel so the user (and lead) can see it.
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Lead → Member**: "Please claim a task from the board"
|
||||
- **Member → Lead**: "Task partially complete, need clarification on requirements"
|
||||
- **Member → Member**: Cross-coordination between teammates working on related tasks
|
||||
- **Broadcast**: Lead sharing context updates with all members simultaneously
|
||||
|
||||
---
|
||||
|
||||
## 6. Delegation Integration
|
||||
|
||||
Teams integrate deeply with the delegation system. The mandatory workflow ensures every piece of delegated work is tracked.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
LEAD["Lead receives user request"] --> CREATE["1. Create task on board<br/>team_tasks action=create<br/>→ returns task_id"]
|
||||
CREATE --> SPAWN["2. Delegate to member<br/>spawn agent=member, task=...,<br/>team_task_id=task_id"]
|
||||
SPAWN --> LANE["Scheduled through<br/>delegate lane"]
|
||||
LANE --> MEMBER["Member agent executes<br/>in isolated session"]
|
||||
MEMBER --> COMPLETE["3. Task auto-completed<br/>with delegation result"]
|
||||
COMPLETE --> CLEANUP["Session cleaned up"]
|
||||
|
||||
subgraph "Parallel Delegation"
|
||||
SPAWN2["spawn member_A, task_id=1"] --> RUN_A["Member A works"]
|
||||
SPAWN3["spawn member_B, task_id=2"] --> RUN_B["Member B works"]
|
||||
RUN_A --> COLLECT["Results collected"]
|
||||
RUN_B --> COLLECT
|
||||
COLLECT --> ANNOUNCE["4. Single combined<br/>announcement to lead"]
|
||||
end
|
||||
```
|
||||
|
||||
### Task ID Enforcement
|
||||
|
||||
When a team member delegates work, the system requires a valid `team_task_id`:
|
||||
|
||||
- **Missing task ID**: Delegation is rejected with an error explaining the requirement. The error includes a list of pending tasks to help the LLM self-correct.
|
||||
- **Invalid task ID**: If the LLM hallucinates a UUID that doesn't exist, the error includes pending tasks as a hint.
|
||||
- **Cross-team task ID**: If the task belongs to a different team, the delegation is rejected.
|
||||
- **Auto-claim**: When delegation starts, the linked task is automatically claimed (status: pending → in_progress).
|
||||
|
||||
### Auto-Completion
|
||||
|
||||
When a delegation finishes (success or failure):
|
||||
|
||||
1. The linked task is marked as `completed` with the delegation result
|
||||
2. A team message audit record is created (from member → lead)
|
||||
3. The delegation session is cleaned up (deleted)
|
||||
4. Delegation history is saved with team context (team_id, team_task_id, trace_id)
|
||||
|
||||
### Parallel Delegation Batching
|
||||
|
||||
When the lead delegates to multiple members simultaneously:
|
||||
|
||||
- Each delegation runs independently in the delegate lane
|
||||
- Intermediate completions accumulate their results (artifacts)
|
||||
- When the **last** sibling delegation finishes, all accumulated results are collected
|
||||
- A single combined announcement is delivered to the lead with all results
|
||||
- Each individual task is still auto-completed independently — the batching only affects the announcement
|
||||
|
||||
### Announcement Format
|
||||
|
||||
The combined announcement includes:
|
||||
|
||||
- Results from each member agent, separated clearly
|
||||
- Deliverables and media files (auto-delivered)
|
||||
- Elapsed time statistics
|
||||
- Guidance for the lead: present results to user, delegate follow-ups, or ask for revisions
|
||||
|
||||
If all delegations failed, the lead receives a friendly error notification with guidance to retry or handle it directly. The announcement also asks the lead to notify the user of the hiccup before retrying.
|
||||
|
||||
---
|
||||
|
||||
## 7. TEAM.md — System-Injected Context
|
||||
|
||||
`TEAM.md` is a virtual file generated at agent resolution time. It is not stored on disk or in the database — it's rendered dynamically based on the current team configuration and injected into the system prompt wrapped in `<system_context>` tags.
|
||||
|
||||
### Generation Trigger
|
||||
|
||||
During agent resolution, if the agent belongs to a team:
|
||||
1. Load team data
|
||||
2. Load team members
|
||||
3. Generate TEAM.md with role-appropriate content
|
||||
4. Inject as a context file
|
||||
|
||||
### Content Differences
|
||||
|
||||
| Section | Lead | Member |
|
||||
|---------|------|--------|
|
||||
| Team name + description | Yes | Yes |
|
||||
| Teammate list with roles | Yes | Yes |
|
||||
| Mandatory workflow (create→delegate) | Yes | No |
|
||||
| Orchestration patterns | Yes | No |
|
||||
| Communication guidelines | Yes | No |
|
||||
| Task board actions (full) | Yes | Limited |
|
||||
| "Just do the work" instructions | No | Yes |
|
||||
| Progress update guidance | No | Yes |
|
||||
|
||||
### Orchestration Patterns (Lead Only)
|
||||
|
||||
The lead's TEAM.md describes three orchestration patterns:
|
||||
|
||||
- **Sequential**: Member A finishes → lead reviews → delegates to Member B with A's output
|
||||
- **Iterative**: Member A drafts → Member B reviews → back to A with feedback
|
||||
- **Mixed**: Members A+B work in parallel → lead reviews combined output → delegates to C
|
||||
|
||||
### Negative Context Injection
|
||||
|
||||
If an agent is NOT part of any team AND has no delegation targets, the system injects negative context:
|
||||
- "You are NOT part of any team. Do not use team_tasks or team_message tools."
|
||||
- "You have NO delegation targets. Do not use spawn with agent parameter."
|
||||
|
||||
This prevents wasted LLM iterations probing unavailable capabilities.
|
||||
|
||||
---
|
||||
|
||||
## 8. Message Routing
|
||||
|
||||
Team messages flow through the message bus with specific routing rules.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Inbound (Team Member Execution)"
|
||||
LEAD_SPAWN["Lead: spawn agent=member,<br/>team_task_id=X"] --> BUS_IN["Message Bus<br/>SenderID: 'delegate:{id}'"]
|
||||
BUS_IN --> CONSUMER["Consumer routes to<br/>delegate lane"]
|
||||
CONSUMER --> MEMBER["Member agent runs<br/>in isolated session"]
|
||||
end
|
||||
|
||||
subgraph "Outbound (Result Announcement)"
|
||||
MEMBER --> RESULT["Delegation completes"]
|
||||
RESULT --> CHECK{"Last sibling?"}
|
||||
CHECK -->|"No"| ACCUMULATE["Accumulate artifacts"]
|
||||
CHECK -->|"Yes"| COLLECT["Collect all artifacts"]
|
||||
COLLECT --> ANNOUNCE["Publish to parent session<br/>SenderID: 'delegate:{id}'"]
|
||||
ANNOUNCE --> LEAD_SESSION["Lead processes in<br/>original user session"]
|
||||
end
|
||||
|
||||
subgraph "Teammate Messages"
|
||||
SEND["team_message send/broadcast"] --> BUS_TM["Message Bus<br/>SenderID: 'teammate:{key}'"]
|
||||
BUS_TM --> ROUTE_TM["Consumer routes to<br/>target agent session"]
|
||||
ROUTE_TM --> TARGET["Target agent processes"]
|
||||
end
|
||||
```
|
||||
|
||||
### Routing Prefixes
|
||||
|
||||
| Prefix | Source | Destination | Scheduler Lane |
|
||||
|--------|--------|-------------|----------------|
|
||||
| `delegate:` | Delegation completion | Parent agent's original session | delegate |
|
||||
| `teammate:` | Team mailbox message | Target agent's session | delegate |
|
||||
|
||||
### Session Context Preservation
|
||||
|
||||
When a delegation or team message completes, the result is routed back to the **original user session** (not a new session). This is achieved through metadata propagation:
|
||||
|
||||
- `origin_channel`: The channel where the user sent the message (e.g., telegram)
|
||||
- `origin_peer_kind`: DM or group context
|
||||
- `origin_local_key`: Thread/topic context for correct routing (e.g., forum topic ID)
|
||||
|
||||
This ensures results land in the correct conversation thread, even in Telegram forum topics or Feishu thread discussions.
|
||||
|
||||
---
|
||||
|
||||
## 9. Access Control
|
||||
|
||||
Teams support fine-grained access control through team settings.
|
||||
|
||||
### Team-Level Settings
|
||||
|
||||
| Setting | Type | Description |
|
||||
|---------|------|-------------|
|
||||
| `AllowUserIDs` | String list | Only these users can trigger team work |
|
||||
| `DenyUserIDs` | String list | These users are blocked (deny takes priority) |
|
||||
| `AllowChannels` | String list | Only messages from these channels trigger team work |
|
||||
| `DenyChannels` | String list | Block messages from these channels |
|
||||
|
||||
System channels (`delegate`, `system`) always pass access checks. Empty settings mean open access.
|
||||
|
||||
### Link-Level Settings
|
||||
|
||||
Each delegation link (lead→member) has its own settings:
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| `UserAllow` | Only these users can trigger this specific delegation |
|
||||
| `UserDeny` | Block these users from this delegation (deny takes priority) |
|
||||
|
||||
### Concurrency Limits
|
||||
|
||||
| Layer | Scope | Default |
|
||||
|-------|-------|---------|
|
||||
| Per-link | Simultaneous delegations from lead to a specific member | 3 |
|
||||
| Per-agent | Total concurrent delegations targeting any single member | 5 |
|
||||
|
||||
When limits are hit, the error message is written for LLM reasoning: "Agent at capacity (5/5). Try a different agent or handle it yourself."
|
||||
|
||||
---
|
||||
|
||||
## 10. Delegation Context
|
||||
|
||||
### SenderID Clearing
|
||||
|
||||
In sync delegations, the delegate agent's context has the `senderID` cleared. This is critical because delegations are system-initiated — the delegate should not inherit the caller's group writer permissions, which would incorrectly deny file writes. Each delegate agent has its own writer list.
|
||||
|
||||
### Trace Linking
|
||||
|
||||
Delegation traces are linked to the parent trace through `parent_trace_id`. This allows the tracing system to show the full delegation chain: user request → lead processing → member delegation → member execution.
|
||||
|
||||
---
|
||||
|
||||
## 11. Events
|
||||
|
||||
Teams emit events for real-time UI updates and observability.
|
||||
|
||||
| Event | When |
|
||||
|-------|------|
|
||||
| `delegation.started` | Async delegation begins |
|
||||
| `delegation.completed` | Delegation finishes successfully |
|
||||
| `delegation.failed` | Delegation fails |
|
||||
| `delegation.cancelled` | Delegation cancelled |
|
||||
| `team_task.created` | New task added to board |
|
||||
| `team_task.completed` | Task marked as complete |
|
||||
| `team_message.sent` | Mailbox message delivered |
|
||||
|
||||
---
|
||||
|
||||
## File Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `internal/gateway/methods/teams.go` | Team CRUD RPC handlers, auto-link creation on team create/member add |
|
||||
| `internal/tools/team_tool_manager.go` | Shared backend for team tools, team cache (5-min TTL), team resolution |
|
||||
| `internal/tools/team_tasks_tool.go` | Task board tool: list, get, create, claim, complete, cancel, search |
|
||||
| `internal/tools/team_message_tool.go` | Mailbox tool: send, broadcast, read, message routing via bus |
|
||||
| `internal/tools/delegate.go` | DelegateManager: sync/async delegation, team task enforcement, auto-completion |
|
||||
| `internal/tools/delegate_state.go` | Active delegation tracking, artifact accumulation, session cleanup |
|
||||
| `internal/tools/delegate_policy.go` | Access control: user permissions, team access, concurrency checks |
|
||||
| `internal/tools/delegate_events.go` | Delegation event broadcasting |
|
||||
| `internal/agent/resolver.go` | TEAM.md generation (buildTeamMD), injection during agent resolution |
|
||||
| `internal/agent/systemprompt_sections.go` | TEAM.md rendering in system prompt as `<system_context>` |
|
||||
| `internal/store/team_store.go` | TeamStore interface (22 methods), data types |
|
||||
| `internal/store/pg/teams.go` | PostgreSQL implementation: teams, tasks, messages, handoff routes |
|
||||
| `cmd/gateway_managed.go` | Team tool wiring, cache invalidation subscription |
|
||||
| `cmd/gateway_consumer.go` | Message routing for delegate/teammate prefixes |
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
| Document | Relevant Content |
|
||||
|----------|-----------------|
|
||||
| [03-tools-system.md](./03-tools-system.md) | Delegation system, agent links, quality gates, evaluate loop |
|
||||
| [06-store-data-model.md](./06-store-data-model.md) | Team tables schema, delegation_history, handoff_routes |
|
||||
| [08-scheduling-cron-heartbeat.md](./08-scheduling-cron-heartbeat.md) | Delegate scheduler lane (concurrency 100) |
|
||||
| [09-security.md](./09-security.md) | Delegation security, hook recursion prevention |
|
||||
@@ -0,0 +1,169 @@
|
||||
# 12 - Extended Thinking
|
||||
|
||||
## Overview
|
||||
|
||||
Extended thinking allows LLM providers to "think out loud" before producing a final response. When enabled, the model generates internal reasoning tokens that improve response quality for complex tasks — at the cost of additional token usage and latency. GoClaw supports extended thinking across multiple providers with a unified `thinking_level` configuration.
|
||||
|
||||
---
|
||||
|
||||
## 1. Configuration
|
||||
|
||||
Thinking is controlled per-agent through the `thinking_level` setting.
|
||||
|
||||
| Level | Behavior |
|
||||
|-------|----------|
|
||||
| `off` | Thinking disabled (default) |
|
||||
| `low` | Minimal thinking — quick reasoning |
|
||||
| `medium` | Moderate thinking — balanced reasoning |
|
||||
| `high` | Maximum thinking — deep reasoning for complex tasks |
|
||||
|
||||
The setting can be configured:
|
||||
- **Per-agent**: In the agent's configuration (applies to all users of that agent)
|
||||
- **Per-user override**: Via `user_agent_overrides` table (reserved for future use)
|
||||
|
||||
---
|
||||
|
||||
## 2. Provider Support
|
||||
|
||||
Each provider maps the abstract `thinking_level` to its own implementation parameters.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CONFIG["Agent config:<br/>thinking_level = medium"] --> CHECK{"Provider supports<br/>thinking?"}
|
||||
CHECK -->|No| SKIP["Send request<br/>without thinking"]
|
||||
CHECK -->|Yes| MAP{"Provider type?"}
|
||||
|
||||
MAP -->|Anthropic| ANTH["Budget tokens: 10,000<br/>Header: anthropic-beta<br/>Strip temperature"]
|
||||
MAP -->|OpenAI-compat| OAI["Map to reasoning_effort<br/>(low/medium/high)"]
|
||||
MAP -->|DashScope| DASH["enable_thinking: true<br/>Budget: 16,384 tokens<br/>⚠ Disable streaming with tools"]
|
||||
|
||||
ANTH --> SEND["Send to LLM"]
|
||||
OAI --> SEND
|
||||
DASH --> SEND
|
||||
```
|
||||
|
||||
### Anthropic (Native)
|
||||
|
||||
| Thinking Level | Budget Tokens |
|
||||
|:-:|:-:|
|
||||
| low | 4,096 |
|
||||
| medium | 10,000 |
|
||||
| high | 32,000 |
|
||||
|
||||
When thinking is enabled:
|
||||
- Adds `thinking: {type: "enabled", budget_tokens: N}` to the request body
|
||||
- Sets `anthropic-beta: interleaved-thinking-2025-05-14` header
|
||||
- Strips `temperature` parameter (Anthropic requirement — cannot use temperature with thinking)
|
||||
- Auto-adjusts `max_tokens` to accommodate thinking budget (budget + 8,192 buffer)
|
||||
|
||||
### OpenAI-Compatible (OpenAI, Groq, DeepSeek, etc.)
|
||||
|
||||
Maps `thinking_level` directly to `reasoning_effort`:
|
||||
- `low` → `reasoning_effort: "low"`
|
||||
- `medium` → `reasoning_effort: "medium"`
|
||||
- `high` → `reasoning_effort: "high"`
|
||||
|
||||
Reasoning content is returned in the `reasoning_content` field of the response delta during streaming.
|
||||
|
||||
### DashScope (Alibaba Qwen)
|
||||
|
||||
| Thinking Level | Budget Tokens |
|
||||
|:-:|:-:|
|
||||
| low | 4,096 |
|
||||
| medium | 16,384 |
|
||||
| high | 32,768 |
|
||||
|
||||
Enables thinking via `enable_thinking: true` plus a `thinking_budget` parameter.
|
||||
|
||||
**Important limitation**: DashScope does not support streaming when tools are present. When an agent has tools enabled and thinking is active, the provider automatically falls back to non-streaming mode (single `Chat()` call) and synthesizes chunk callbacks to maintain the event flow.
|
||||
|
||||
---
|
||||
|
||||
## 3. Streaming
|
||||
|
||||
When thinking is active, reasoning content streams to the client alongside regular content.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
LLM["LLM generates response"] --> THINK["Thinking tokens<br/>(internal reasoning)"]
|
||||
THINK --> CONTENT["Content tokens<br/>(final response)"]
|
||||
|
||||
THINK -->|Stream| CHUNK_T["StreamChunk<br/>Thinking: 'reasoning text...'"]
|
||||
CONTENT -->|Stream| CHUNK_C["StreamChunk<br/>Content: 'response text...'"]
|
||||
|
||||
CHUNK_T --> CLIENT["Client receives<br/>thinking + content separately"]
|
||||
CHUNK_C --> CLIENT
|
||||
```
|
||||
|
||||
### Provider-Specific Streaming Events
|
||||
|
||||
| Provider | Thinking Event | Content Event |
|
||||
|----------|---------------|---------------|
|
||||
| Anthropic | `thinking_delta` in content blocks | `text_delta` in content blocks |
|
||||
| OpenAI-compat | `reasoning_content` in delta | `content` in delta |
|
||||
| DashScope | No streaming with tools (falls back to non-streaming) | Same |
|
||||
|
||||
### Token Estimation
|
||||
|
||||
Thinking tokens are estimated as `character_count / 4` for context window tracking. This rough estimate ensures the agent loop can account for thinking overhead when calculating context usage.
|
||||
|
||||
---
|
||||
|
||||
## 4. Tool Loop Handling
|
||||
|
||||
Extended thinking interacts with multi-turn tool conversations. When the LLM calls a tool and then needs to continue reasoning, thinking blocks must be preserved correctly across turns.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
TURN1["Turn 1: LLM thinks + calls tool"] --> PRESERVE["Preserve thinking blocks<br/>in raw assistant content"]
|
||||
PRESERVE --> TOOL["Tool executes,<br/>result appended to history"]
|
||||
TOOL --> TURN2["Turn 2: LLM receives history<br/>including preserved thinking blocks"]
|
||||
TURN2 --> CONTINUE["LLM continues reasoning<br/>with full context"]
|
||||
```
|
||||
|
||||
### Anthropic Thinking Block Preservation
|
||||
|
||||
Anthropic requires thinking blocks (including their cryptographic signatures) to be echoed back in subsequent turns. GoClaw handles this through `RawAssistantContent`:
|
||||
|
||||
1. During streaming, raw content blocks are accumulated — including `thinking` type blocks with their `signature` fields
|
||||
2. When the assistant message is appended to history, the raw blocks are preserved
|
||||
3. On the next LLM call, these blocks are sent back as-is, ensuring the API can validate thinking continuity
|
||||
|
||||
This is critical for correctness: if thinking blocks are dropped or modified, the Anthropic API may reject the request or produce degraded responses.
|
||||
|
||||
### Other Providers
|
||||
|
||||
OpenAI-compatible providers handle thinking/reasoning content as metadata. The `reasoning_content` is accumulated during streaming but does not require special passback handling — each turn's reasoning is independent.
|
||||
|
||||
---
|
||||
|
||||
## 5. Limitations
|
||||
|
||||
| Provider | Limitation |
|
||||
|----------|-----------|
|
||||
| DashScope | Cannot stream when tools are present — falls back to non-streaming mode |
|
||||
| Anthropic | Temperature parameter stripped when thinking is enabled |
|
||||
| All | Thinking tokens count against the context window budget |
|
||||
| All | Thinking increases latency and cost proportional to the budget level |
|
||||
|
||||
---
|
||||
|
||||
## File Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `internal/providers/types.go` | ThinkingCapable interface, StreamChunk.Thinking field, OptThinkingLevel constant |
|
||||
| `internal/providers/anthropic.go` | Anthropic thinking implementation: budget mapping, header injection, temperature stripping |
|
||||
| `internal/providers/anthropic_stream.go` | Streaming: thinking_delta handling, RawAssistantContent accumulation |
|
||||
| `internal/providers/anthropic_request.go` | Request building: thinking block preservation for tool loops |
|
||||
| `internal/providers/openai.go` | OpenAI-compat: reasoning_effort mapping, reasoning_content streaming |
|
||||
| `internal/providers/dashscope.go` | DashScope: thinking budget, tools+streaming limitation fallback |
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
| Document | Relevant Content |
|
||||
|----------|-----------------|
|
||||
| [02-providers.md](./02-providers.md) | Provider architecture, supported providers |
|
||||
| [01-agent-loop.md](./01-agent-loop.md) | LLM iteration loop, streaming chunk handling |
|
||||
Reference in new issue
Block a user