refactor: deprecate standalone mode, managed mode is now default (#126)

* refactor: remove managed/standalone mode distinction from codebase

Standalone mode is deprecated; managed mode is now the only mode.
Remove redundant "managed mode" qualifiers from comments, docs,
and error messages. Error strings now reference "database stores"
instead of "managed mode" for clarity.

* improve(onboard): streamline onboard process and env setup

Simplify onboard wizard, extract helpers to dedicated file,
update env example and entrypoint for default managed mode,
clean up prepare-env script, update i18n catalogs.
This commit is contained in:
Thieu Nguyen authored and GitHub committed 2026-03-11 07:27:38 +07:00
1 parent c5b886048e
commit 8ad580521d
32 files changed
+121 -110

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
# Copy to .env and fill in values. For Docker Compose, do NOT use 'export' prefix. # Copy to .env and fill in values. For Docker Compose, do NOT use 'export' prefix.
# #
# Auto-generated by prepare-env.sh: GOCLAW_GATEWAY_TOKEN, GOCLAW_ENCRYPTION_KEY. # Auto-generated by prepare-env.sh: GOCLAW_GATEWAY_TOKEN, GOCLAW_ENCRYPTION_KEY.
# LLM providers and channels are configured via the web dashboard setup wizard. # LLM provider API keys: configure via the web dashboard setup wizard.
# --- Gateway (required) --- # --- Gateway (required) ---
GOCLAW_GATEWAY_TOKEN= GOCLAW_GATEWAY_TOKEN=
+8 -8
View File
@@ -9,13 +9,13 @@
| POST | `/v1/chat/completions` | OpenAI-compatible chat API | | POST | `/v1/chat/completions` | OpenAI-compatible chat API |
| POST | `/v1/responses` | Responses protocol | | POST | `/v1/responses` | Responses protocol |
| POST | `/v1/tools/invoke` | Tool invocation | | POST | `/v1/tools/invoke` | Tool invocation |
| GET/POST | `/v1/agents/*` | Agent management (managed mode) | | GET/POST | `/v1/agents/*` | Agent management |
| GET/POST | `/v1/skills/*` | Skills management (managed mode) | | GET/POST | `/v1/skills/*` | Skills management |
| GET/POST/PUT/DELETE | `/v1/tools/custom/*` | Custom tool CRUD (managed mode) | | GET/POST/PUT/DELETE | `/v1/tools/custom/*` | Custom tool CRUD |
| GET/POST/PUT/DELETE | `/v1/mcp/*` | MCP server + grants management (managed mode) | | GET/POST/PUT/DELETE | `/v1/mcp/*` | MCP server + grants management |
| GET | `/v1/traces/*` | Trace viewer (managed mode) | | GET | `/v1/traces/*` | Trace viewer |
## Custom Tools (Managed Mode) ## Custom Tools
Define shell-based tools at runtime via HTTP API — no recompile or restart needed. The LLM can invoke custom tools identically to built-in tools. Define shell-based tools at runtime via HTTP API — no recompile or restart needed. The LLM can invoke custom tools identically to built-in tools.
@@ -67,7 +67,7 @@ Connect external [Model Context Protocol](https://modelcontextprotocol.io) serve
**Supported transports:** `stdio`, `sse`, `streamable-http` **Supported transports:** `stdio`, `sse`, `streamable-http`
**Standalone mode** — configure in `config.json`: **Static config** — configure in `config.json` (deprecated; use HTTP API for dynamic management):
```json ```json
{ {
@@ -87,7 +87,7 @@ Connect external [Model Context Protocol](https://modelcontextprotocol.io) serve
} }
``` ```
**Managed mode** — full CRUD via HTTP API with per-agent and per-user access grants: **HTTP API** — full CRUD with per-agent and per-user access grants:
| Method | Path | Description | | Method | Path | Description |
|---|---|---| |---|---|---|
-1
View File
@@ -909,7 +909,6 @@ func runGateway() {
slog.Info("goclaw gateway starting", slog.Info("goclaw gateway starting",
"version", Version, "version", Version,
"protocol", protocol.ProtocolVersion, "protocol", protocol.ProtocolVersion,
"mode", "managed",
"agents", agentRouter.List(), "agents", agentRouter.List(),
"tools", toolsReg.Count(), "tools", toolsReg.Count(),
"channels", channelMgr.GetEnabledChannels(), "channels", channelMgr.GetEnabledChannels(),
+7 -7
View File
@@ -37,25 +37,25 @@ func runOnboard() {
} }
} }
// ── Step 1: Postgres DSN ── // ── Step 1: Postgres connection ──
postgresDSN := os.Getenv("GOCLAW_POSTGRES_DSN") postgresDSN := os.Getenv("GOCLAW_POSTGRES_DSN")
if postgresDSN == "" { if postgresDSN == "" {
postgresDSN = cfg.Database.PostgresDSN postgresDSN = cfg.Database.PostgresDSN
} }
if postgresDSN == "" { if postgresDSN == "" {
var err error fmt.Println("── Database Connection ──")
postgresDSN, err = promptString("Postgres DSN", "Connection string (e.g. postgres://user:pass@host:5432/dbname)", "") fmt.Println(" Enter your PostgreSQL connection details (press Enter for defaults).")
fmt.Println()
dsn, err := promptPostgresFields()
if err != nil { if err != nil {
fmt.Println("Cancelled.") fmt.Println("Cancelled.")
return return
} }
postgresDSN = dsn
} else { } else {
fmt.Printf(" Using Postgres DSN from environment\n") fmt.Printf(" Using Postgres DSN from environment\n")
} }
if postgresDSN == "" {
fmt.Println(" Error: Postgres DSN is required.")
return
}
// ── Step 2: Test connection ── // ── Step 2: Test connection ──
fmt.Print(" Testing Postgres connection... ") fmt.Print(" Testing Postgres connection... ")
+39
View File
@@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/url"
"os" "os"
"strings" "strings"
) )
@@ -14,6 +15,44 @@ func onboardGenerateToken(bytes int) string {
return hex.EncodeToString(b) return hex.EncodeToString(b)
} }
// promptPostgresFields prompts for individual database fields and builds a DSN.
func promptPostgresFields() (string, error) {
host, err := promptString("Host", "", "localhost")
if err != nil {
return "", err
}
port, err := promptString("Port", "", "5432")
if err != nil {
return "", err
}
dbName, err := promptString("Database name", "", "goclaw")
if err != nil {
return "", err
}
user, err := promptString("Username", "", "postgres")
if err != nil {
return "", err
}
password, err := promptPassword("Password", "Leave empty if no password")
if err != nil {
return "", err
}
sslMode, err := promptString("SSL mode", "", "disable")
if err != nil {
return "", err
}
// Build DSN with proper escaping
var userInfo *url.Userinfo
if password != "" {
userInfo = url.UserPassword(user, password)
} else {
userInfo = url.User(user)
}
dsn := fmt.Sprintf("postgres://%s@%s:%s/%s?sslmode=%s", userInfo.String(), host, port, dbName, sslMode)
return dsn, nil
}
// onboardWriteEnvFile writes the minimal .env.local with only the 3 required secrets. // onboardWriteEnvFile writes the minimal .env.local with only the 3 required secrets.
func onboardWriteEnvFile(path, postgresDSN, gatewayToken, encryptionKey string) { func onboardWriteEnvFile(path, postgresDSN, gatewayToken, encryptionKey string) {
var lines []string var lines []string
+3 -3
View File
@@ -3,9 +3,9 @@ set -e
case "${1:-serve}" in case "${1:-serve}" in
serve) serve)
# Managed mode: auto-upgrade (schema migrations + data hooks) before starting. # Auto-upgrade (schema migrations + data hooks) before starting.
if [ "$GOCLAW_MODE" = "managed" ] && [ -n "$GOCLAW_POSTGRES_DSN" ]; then if [ -n "$GOCLAW_POSTGRES_DSN" ]; then
echo "Managed mode: running upgrade..." echo "Running database upgrade..."
/app/goclaw upgrade || \ /app/goclaw upgrade || \
echo "Upgrade warning (may already be up-to-date)" echo "Upgrade warning (may already be up-to-date)"
fi fi
+3 -3
View File
@@ -233,7 +233,7 @@ sequenceDiagram
--- ---
## 7. Managed Mode Wiring ## 7. Database Wiring
The `wireManagedExtras()` function in `cmd/gateway_managed.go` wires multi-tenant components: The `wireManagedExtras()` function in `cmd/gateway_managed.go` wires multi-tenant components:
@@ -393,7 +393,7 @@ flowchart TD
|------|---------| |------|---------|
| `cmd/root.go` | Cobra CLI entry point, flag parsing | | `cmd/root.go` | Cobra CLI entry point, flag parsing |
| `cmd/gateway.go` | Gateway startup orchestrator (`runGateway()`) | | `cmd/gateway.go` | Gateway startup orchestrator (`runGateway()`) |
| `cmd/gateway_managed.go` | Managed mode wiring (`wireManagedExtras()`, `wireManagedHTTP()`) | | `cmd/gateway_managed.go` | Database wiring (`wireManagedExtras()`, `wireManagedHTTP()`) |
| `cmd/gateway_callbacks.go` | Shared callbacks (user seeding, context file loading) | | `cmd/gateway_callbacks.go` | Shared callbacks (user seeding, context file loading) |
| `cmd/gateway_consumer.go` | Inbound message consumer (subagent, delegate, teammate, handoff routing) | | `cmd/gateway_consumer.go` | Inbound message consumer (subagent, delegate, teammate, handoff routing) |
| `cmd/gateway_providers.go` | Provider registration (config-based + DB-based) | | `cmd/gateway_providers.go` | Provider registration (config-based + DB-based) |
@@ -423,7 +423,7 @@ flowchart TD
| [02-providers.md](./02-providers.md) | LLM providers, retry logic, schema cleaning | | [02-providers.md](./02-providers.md) | LLM providers, retry logic, schema cleaning |
| [03-tools-system.md](./03-tools-system.md) | Tool registry, policy engine, interceptors, custom tools, MCP grants | | [03-tools-system.md](./03-tools-system.md) | Tool registry, policy engine, interceptors, custom tools, MCP grants |
| [04-gateway-protocol.md](./04-gateway-protocol.md) | WebSocket protocol v3, HTTP API, RBAC, identity propagation | | [04-gateway-protocol.md](./04-gateway-protocol.md) | WebSocket protocol v3, HTTP API, RBAC, identity propagation |
| [05-channels-messaging.md](./05-channels-messaging.md) | Channel adapters, Telegram formatting, pairing, managed-mode user scoping | | [05-channels-messaging.md](./05-channels-messaging.md) | Channel adapters, Telegram formatting, pairing, per-user scoping |
| [06-store-data-model.md](./06-store-data-model.md) | Store interfaces, PostgreSQL schema, session caching, custom tool store | | [06-store-data-model.md](./06-store-data-model.md) | Store interfaces, PostgreSQL schema, session caching, custom tool store |
| [07-bootstrap-skills-memory.md](./07-bootstrap-skills-memory.md) | Bootstrap files, skills system, memory, skills grants | | [07-bootstrap-skills-memory.md](./07-bootstrap-skills-memory.md) | Bootstrap files, skills system, memory, skills grants |
| [08-scheduling-cron.md](./08-scheduling-cron.md) | Scheduler lanes, cron lifecycle | | [08-scheduling-cron.md](./08-scheduling-cron.md) | Scheduler lanes, cron lifecycle |
+3 -3
View File
@@ -88,7 +88,7 @@ flowchart TD
- Increment the `activeRuns` atomic counter (no mutex -- true concurrency, especially in group chats with `maxConcurrent = 3`). - Increment the `activeRuns` atomic counter (no mutex -- true concurrency, especially in group chats with `maxConcurrent = 3`).
- Emit a `run.started` event to notify connected clients. - Emit a `run.started` event to notify connected clients.
- Create a trace record (managed mode) with a generated trace UUID. - Create a trace record with a generated trace UUID.
- Propagate context values: `WithAgentID()`, `WithUserID()`, `WithAgentType()`. Downstream tools and interceptors rely on these. - Propagate context values: `WithAgentID()`, `WithUserID()`, `WithAgentType()`. Downstream tools and interceptors rely on these.
- Compute per-user workspace: `base + "/" + sanitize(userID)`. Inject via `WithToolWorkspace(ctx)` so all filesystem and shell tools use the correct directory. - Compute per-user workspace: `base + "/" + sanitize(userID)`. Inject via `WithToolWorkspace(ctx)` so all filesystem and shell tools use the correct directory.
- Ensure per-user files exist. A `sync.Map` cache guarantees the seeding function runs at most once per user. - Ensure per-user files exist. A `sync.Map` cache guarantees the seeding function runs at most once per user.
@@ -381,7 +381,7 @@ flowchart TD
--- ---
## 10. Resolver (Managed Mode) ## 10. Resolver
The `ManagedResolver` lazy-creates Loop instances from PostgreSQL data when the Router encounters a cache miss. The `ManagedResolver` lazy-creates Loop instances from PostgreSQL data when the Router encounters a cache miss.
@@ -392,7 +392,7 @@ flowchart TD
PROV --> BOOT["Step 3: Load bootstrap files<br/>bootstrap.LoadFromStore(agentID)"] PROV --> BOOT["Step 3: Load bootstrap files<br/>bootstrap.LoadFromStore(agentID)"]
BOOT --> DEFAULTS["Step 4: Apply defaults<br/>contextWindow <= 0 then 200K<br/>maxIterations <= 0 then 20"] BOOT --> DEFAULTS["Step 4: Apply defaults<br/>contextWindow <= 0 then 200K<br/>maxIterations <= 0 then 20"]
DEFAULTS --> CREATE["Step 5: Create Loop<br/>NewLoop(LoopConfig)"] DEFAULTS --> CREATE["Step 5: Create Loop<br/>NewLoop(LoopConfig)"]
CREATE --> WIRE["Step 6: Wire managed-mode hooks<br/>EnsureUserFilesFunc, ContextFileLoaderFunc"] CREATE --> WIRE["Step 6: Wire hooks<br/>EnsureUserFilesFunc, ContextFileLoaderFunc"]
WIRE --> DONE["Return Loop to Router for caching"] WIRE --> DONE["Return Loop to Router for caching"]
``` ```
+2 -2
View File
@@ -202,9 +202,9 @@ The Anthropic provider calls `CleanSchemaForProvider("anthropic", ...)` when con
--- ---
## 7. Managed Mode -- Providers from Database ## 7. Providers from Database
In managed mode, providers are loaded from the `llm_providers` table in addition to the config file. Database providers override config providers with the same name. Providers are loaded from the `llm_providers` table in addition to the config file. Database providers override config providers with the same name.
### Loading Flow ### Loading Flow
+4 -4
View File
@@ -141,7 +141,7 @@ Context keys ensure each tool call receives the correct per-call values without
## 3. Filesystem Tools and Virtual FS Routing ## 3. Filesystem Tools and Virtual FS Routing
In managed mode, filesystem operations are intercepted before hitting the host disk. Two interceptor layers route specific paths to the database instead. Filesystem operations are intercepted before hitting the host disk. Two interceptor layers route specific paths to the database instead.
```mermaid ```mermaid
flowchart TD flowchart TD
@@ -570,9 +570,9 @@ GoClaw integrates with Model Context Protocol (MCP) servers via `internal/mcp/`.
- Tools are registered with a prefix (e.g., `mcp_servername_toolname`) - Tools are registered with a prefix (e.g., `mcp_servername_toolname`)
- Dynamic tool group registration: `mcp` and `mcp:{serverName}` groups - Dynamic tool group registration: `mcp` and `mcp:{serverName}` groups
### Access Control (Managed Mode) ### Access Control
In managed mode, MCP server access is controlled through per-agent and per-user grants stored in PostgreSQL. MCP server access is controlled through per-agent and per-user grants stored in PostgreSQL.
```mermaid ```mermaid
flowchart TD flowchart TD
@@ -602,7 +602,7 @@ flowchart LR
--- ---
## 13. Custom Tools (Managed Mode) ## 13. Custom Tools
Define shell-based tools at runtime via the HTTP API -- no recompile or restart needed. Custom tools are stored in the `custom_tools` PostgreSQL table and loaded dynamically into the agent's tool registry. Define shell-based tools at runtime via the HTTP API -- no recompile or restart needed. Custom tools are stored in the `custom_tools` PostgreSQL table and loaded dynamically into the agent's tool registry.
+8 -8
View File
@@ -96,7 +96,7 @@ flowchart TD
Token comparison uses `crypto/subtle.ConstantTimeCompare` to prevent timing attacks. Token comparison uses `crypto/subtle.ConstantTimeCompare` to prevent timing attacks.
In managed mode, `user_id` in the connect parameters is required for per-user session scoping and context file routing. GoClaw uses the **Identity Propagation** pattern — it trusts the upstream service to provide accurate user identity. The `user_id` is opaque (VARCHAR 255); multi-tenant deployments use the compound format `tenant.{tenantId}.user.{userId}`. See [00-architecture-overview.md Section 5](./00-architecture-overview.md) for details. The `user_id` in the connect parameters is required for per-user session scoping and context file routing. GoClaw uses the **Identity Propagation** pattern — it trusts the upstream service to provide accurate user identity. The `user_id` is opaque (VARCHAR 255); multi-tenant deployments use the compound format `tenant.{tenantId}.user.{userId}`. See [00-architecture-overview.md Section 5](./00-architecture-overview.md) for details.
### Three Roles ### Three Roles
@@ -161,7 +161,7 @@ flowchart TD
| `agent.wait` | Wait for an agent to become available | | `agent.wait` | Wait for an agent to become available |
| `agent.identity.get` | Get agent identity (name, description) | | `agent.identity.get` | Get agent identity (name, description) |
| `agents.list` | List all accessible agents | | `agents.list` | List all accessible agents |
| `agents.create` | Create a new agent (managed mode) | | `agents.create` | Create a new agent |
| `agents.update` | Update agent configuration | | `agents.update` | Update agent configuration |
| `agents.delete` | Soft-delete an agent | | `agents.delete` | Soft-delete an agent |
| `agents.files.list` | List agent context files | | `agents.files.list` | List agent context files |
@@ -301,7 +301,7 @@ flowchart TD
- `Authorization: Bearer <token>` -- timing-safe comparison via `crypto/subtle.ConstantTimeCompare` - `Authorization: Bearer <token>` -- timing-safe comparison via `crypto/subtle.ConstantTimeCompare`
- No token configured: all requests allowed - No token configured: all requests allowed
- `X-GoClaw-User-Id`: required in managed mode for per-user scoping - `X-GoClaw-User-Id`: required for per-user scoping
- `X-GoClaw-Agent-Id`: specify target agent for the request - `X-GoClaw-Agent-Id`: specify target agent for the request
### Endpoints ### Endpoints
@@ -334,9 +334,9 @@ Direct tool invocation without the agent loop. Supports `dryRun: true` to return
Returns `{"status":"ok","protocol":3}`. Returns `{"status":"ok","protocol":3}`.
#### Managed Mode CRUD Endpoints #### CRUD Endpoints
All managed endpoints require `Authorization: Bearer <token>` and `X-GoClaw-User-Id` header for per-user scoping. All CRUD endpoints require `Authorization: Bearer <token>` and `X-GoClaw-User-Id` header for per-user scoping.
**Agents** (`/v1/agents`): **Agents** (`/v1/agents`):
@@ -482,9 +482,9 @@ Error responses include `retryable` (boolean) and `retryAfterMs` (integer) field
| `internal/http/chat_completions.go` | POST /v1/chat/completions (OpenAI-compatible) | | `internal/http/chat_completions.go` | POST /v1/chat/completions (OpenAI-compatible) |
| `internal/http/responses.go` | POST /v1/responses (OpenResponses protocol) | | `internal/http/responses.go` | POST /v1/responses (OpenResponses protocol) |
| `internal/http/tools_invoke.go` | POST /v1/tools/invoke (direct tool execution) | | `internal/http/tools_invoke.go` | POST /v1/tools/invoke (direct tool execution) |
| `internal/http/agents.go` | Agent CRUD HTTP handlers (managed mode) | | `internal/http/agents.go` | Agent CRUD HTTP handlers |
| `internal/http/skills.go` | Skills HTTP handlers (managed mode) | | `internal/http/skills.go` | Skills HTTP handlers |
| `internal/http/traces.go` | Traces HTTP handlers (managed mode) | | `internal/http/traces.go` | Traces HTTP handlers |
| `internal/http/delegations.go` | Delegation history HTTP handlers | | `internal/http/delegations.go` | Delegation history HTTP handlers |
| `internal/http/summoner.go` | LLM-powered agent setup (XML parsing, context file generation) | | `internal/http/summoner.go` | LLM-powered agent setup (XML parsing, context file generation) |
| `internal/http/auth.go` | Bearer token authentication, timing-safe comparison | | `internal/http/auth.go` | Bearer token authentication, timing-safe comparison |
+3 -3
View File
@@ -57,7 +57,7 @@ flowchart LR
Internal channels (`cli`, `system`, `subagent`) are silently skipped by the outbound dispatcher and never forwarded to external platforms. Internal channels (`cli`, `system`, `subagent`) are silently skipped by the outbound dispatcher and never forwarded to external platforms.
### Handoff Routing (Managed Mode) ### Handoff Routing
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. 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.
@@ -504,7 +504,7 @@ flowchart TD
WS2 --> USER3["user_charlie/"] 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. 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.
--- ---
@@ -570,7 +570,7 @@ flowchart TD
|------|---------| |------|---------|
| `internal/channels/channel.go` | Channel interface, BaseChannel, extended interfaces, HandleMessage | | `internal/channels/channel.go` | Channel interface, BaseChannel, extended interfaces, HandleMessage |
| `internal/channels/manager.go` | Manager: registration, StartAll, StopAll, outbound dispatch, webhook collection | | `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/instance_loader.go` | DB-based channel instance loading |
| `internal/channels/telegram/channel.go` | Telegram core: long polling, mention gating, typing indicators | | `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/handlers.go` | Message handling, media processing, forum topic detection |
| `internal/channels/telegram/topic_config.go` | Per-topic config layering and resolution | | `internal/channels/telegram/topic_config.go` | Per-topic config layering and resolution |
+2 -2
View File
@@ -77,7 +77,7 @@ flowchart TD
## 4. Agent Access Control ## 4. Agent Access Control
In managed mode, agent access is checked via a 4-step pipeline. Agent access is checked via a 4-step pipeline.
```mermaid ```mermaid
flowchart TD flowchart TD
@@ -477,7 +477,7 @@ flowchart TD
| Key | Type | Purpose | | Key | Type | Purpose |
|-----|------|---------| |-----|------|---------|
| `goclaw_user_id` | string | External user ID (e.g., Telegram user ID) | | `goclaw_user_id` | string | External user ID (e.g., Telegram user ID) |
| `goclaw_agent_id` | uuid.UUID | Agent UUID (managed mode) | | `goclaw_agent_id` | uuid.UUID | Agent UUID |
| `goclaw_agent_type` | string | Agent type: `"open"` or `"predefined"` | | `goclaw_agent_type` | string | Agent type: `"open"` or `"predefined"` |
| `goclaw_sender_id` | string | Original individual sender ID (in group chats, `user_id` is group-scoped but `sender_id` preserves the actual person) | | `goclaw_sender_id` | string | Original individual sender ID (in group chats, `user_id` is group-scoped but `sender_id` preserves the actual person) |
+7 -7
View File
@@ -186,7 +186,7 @@ This ensures resolver-injected virtual files (`DELEGATION.md`, `TEAM.md`) surviv
--- ---
## 7. Agent Summoning (Managed Mode) ## 7. Agent Summoning
Creating a predefined agent requires 4 context files (SOUL.md, IDENTITY.md, AGENTS.md, TOOLS.md) with specific formatting conventions. Agent summoning generates all 4 files from a natural language description in a single LLM call. Creating a predefined agent requires 4 context files (SOUL.md, IDENTITY.md, AGENTS.md, TOOLS.md) with specific formatting conventions. Agent summoning generates all 4 files from a natural language description in a single LLM call.
@@ -260,9 +260,9 @@ IDF is computed as: `log((N - df + 0.5) / (df + 0.5) + 1)`
--- ---
## 11. Skills -- Embedding Search (Managed Mode) ## 11. Skills -- Embedding Search
In managed mode, skill search uses a hybrid approach combining BM25 and vector similarity. Skill search uses a hybrid approach combining BM25 and vector similarity.
```mermaid ```mermaid
flowchart TD flowchart TD
@@ -283,9 +283,9 @@ flowchart TD
--- ---
## 12. Skills Grants & Visibility (Managed Mode) ## 12. Skills Grants & Visibility
In managed mode, skill access is controlled through a 3-tier visibility model with explicit agent and user grants. Skill access is controlled through a 3-tier visibility model with explicit agent and user grants.
```mermaid ```mermaid
flowchart TD flowchart TD
@@ -315,7 +315,7 @@ flowchart TD
**Resolution**: `ListAccessible(agentID, userID)` performs a DISTINCT join across `skills`, `skill_agent_grants`, and `skill_user_grants` with the visibility filter, returning only active skills the caller can access. **Resolution**: `ListAccessible(agentID, userID)` performs a DISTINCT join across `skills`, `skill_agent_grants`, and `skill_user_grants` with the visibility filter, returning only active skills the caller can access.
**Managed-mode Tier 4**: In managed mode, global skills (Tier 4 in the hierarchy) are loaded from the `skills` PostgreSQL table instead of the filesystem. **Tier 4**: Global skills (Tier 4 in the hierarchy) are loaded from the `skills` PostgreSQL table instead of the filesystem.
--- ---
@@ -489,7 +489,7 @@ The flush is idempotent per compaction cycle -- it will not run again until the
| Document | Relevant Content | | Document | Relevant Content |
|----------|-----------------| |----------|-----------------|
| [00-architecture-overview.md](./00-architecture-overview.md) | Startup sequence, managed mode wiring | | [00-architecture-overview.md](./00-architecture-overview.md) | Startup sequence, database wiring |
| [01-agent-loop.md](./01-agent-loop.md) | Agent loop calls BuildSystemPrompt, compaction flow | | [01-agent-loop.md](./01-agent-loop.md) | Agent loop calls BuildSystemPrompt, compaction flow |
| [03-tools-system.md](./03-tools-system.md) | ContextFileInterceptor routing read_file/write_file to DB | | [03-tools-system.md](./03-tools-system.md) | ContextFileInterceptor routing read_file/write_file to DB |
| [06-store-data-model.md](./06-store-data-model.md) | memory_documents, memory_chunks tables | | [06-store-data-model.md](./06-store-data-model.md) | memory_documents, memory_chunks tables |
+2 -2
View File
@@ -2,7 +2,7 @@
Defense-in-depth with five independent layers from transport to isolation. Each layer operates independently -- even if one layer is bypassed, the remaining layers continue to protect the system. Defense-in-depth with five independent layers from transport to isolation. Each layer operates independently -- even if one layer is bypassed, the remaining layers continue to protect the system.
> **Managed mode**: Adds AES-256-GCM encryption for secrets stored in PostgreSQL (LLM provider API keys, MCP server API keys, custom tool environment variables), plus agent-level access control via the 4-step `CanAccess` pipeline (see [06-store-data-model.md](./06-store-data-model.md)). > AES-256-GCM encryption protects secrets stored in PostgreSQL (LLM provider API keys, MCP server API keys, custom tool environment variables). Agent-level access control uses the 4-step `CanAccess` pipeline (see [06-store-data-model.md](./06-store-data-model.md)).
--- ---
@@ -123,7 +123,7 @@ The workspace is injected into tools via `WithToolWorkspace(ctx)` context inject
--- ---
## 2. Encryption (Managed Mode) ## 2. Encryption
AES-256-GCM encryption for secrets stored in PostgreSQL. Key provided via `GOCLAW_ENCRYPTION_KEY` environment variable. AES-256-GCM encryption for secrets stored in PostgreSQL. Key provided via `GOCLAW_ENCRYPTION_KEY` environment variable.
+2 -2
View File
@@ -106,7 +106,7 @@ The exporter lives in a separate sub-package (`internal/tracing/otelexport/`) so
--- ---
## 5. Trace HTTP API (Managed Mode) ## 5. Trace HTTP API
| Method | Path | Description | | Method | Path | Description |
|--------|------|-------------| |--------|------|-------------|
@@ -126,7 +126,7 @@ The exporter lives in a separate sub-package (`internal/tracing/otelexport/`) so
--- ---
## 6. Delegation History (Managed Mode) ## 6. Delegation History
Delegation history records are stored in the `delegation_history` table and exposed alongside traces for cross-referencing agent interactions. Delegation history records are stored in the `delegation_history` table and exposed alongside traces for cross-referencing agent interactions.
+1 -1
View File
@@ -323,7 +323,7 @@ func NewManagedResolver(deps ResolverDeps) ResolverFunc {
} }
} }
// Managed mode: filter skills by visibility + agent grants. // Filter skills by visibility + agent grants.
// Only public skills and explicitly granted internal skills appear in the system prompt. // Only public skills and explicitly granted internal skills appear in the system prompt.
var skillAllowList []string var skillAllowList []string
if deps.SkillAccessStore != nil { if deps.SkillAccessStore != nil {
+1 -1
View File
@@ -66,7 +66,7 @@ func (m *AgentsMethods) handleCreate(ctx context.Context, client *gateway.Client
} }
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: create agent in DB --- // --- DB-backed: create agent in store ---
ctx := context.Background() ctx := context.Background()
// Check if agent already exists in DB // Check if agent already exists in DB
+1 -1
View File
@@ -39,7 +39,7 @@ func (m *AgentsMethods) handleDelete(ctx context.Context, client *gateway.Client
var removedBindings int var removedBindings int
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: delete from DB --- // --- DB-backed: delete from store ---
ctx := context.Background() ctx := context.Background()
ag, err := m.agentStore.GetByKey(ctx, params.AgentID) ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
if err != nil { if err != nil {
+3 -3
View File
@@ -36,7 +36,7 @@ func (m *AgentsMethods) handleFilesList(ctx context.Context, client *gateway.Cli
} }
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: list from DB --- // --- DB-backed: list from store ---
ctx := context.Background() ctx := context.Background()
ag, err := m.agentStore.GetByKey(ctx, params.AgentID) ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
if err != nil { if err != nil {
@@ -135,7 +135,7 @@ func (m *AgentsMethods) handleFilesGet(ctx context.Context, client *gateway.Clie
} }
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: read from DB --- // --- DB-backed: read from store ---
ctx := context.Background() ctx := context.Background()
ag, err := m.agentStore.GetByKey(ctx, params.AgentID) ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
if err != nil { if err != nil {
@@ -234,7 +234,7 @@ func (m *AgentsMethods) handleFilesSet(ctx context.Context, client *gateway.Clie
} }
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: write to DB --- // --- DB-backed: write to store ---
ctx := context.Background() ctx := context.Background()
ag, err := m.agentStore.GetByKey(ctx, params.AgentID) ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
if err != nil { if err != nil {
+1 -1
View File
@@ -41,7 +41,7 @@ func (m *AgentsMethods) handleIdentityGet(_ context.Context, client *gateway.Cli
} }
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: read identity from DB --- // --- DB-backed: read identity from store ---
ctx := context.Background() ctx := context.Background()
ag, err := m.agentStore.GetByKey(ctx, params.AgentID) ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
if err == nil { if err == nil {
+1 -1
View File
@@ -45,7 +45,7 @@ func (m *AgentsMethods) handleUpdate(ctx context.Context, client *gateway.Client
} }
if m.agentStore != nil { if m.agentStore != nil {
// --- Managed mode: update agent in DB --- // --- DB-backed: update agent in store ---
ctx := context.Background() ctx := context.Background()
ag, err := m.agentStore.GetByKey(ctx, params.AgentID) ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
if err != nil { if err != nil {
+10 -10
View File
@@ -160,52 +160,52 @@ func (s *Server) BuildMux() *http.ServeMux {
mux.Handle("/v1/tools/invoke", toolsHandler) mux.Handle("/v1/tools/invoke", toolsHandler)
} }
// Managed mode: agent CRUD + shares API // Agent CRUD + shares API
if s.agentsHandler != nil { if s.agentsHandler != nil {
s.agentsHandler.RegisterRoutes(mux) s.agentsHandler.RegisterRoutes(mux)
} }
// Managed mode: skill management API // Skill management API
if s.skillsHandler != nil { if s.skillsHandler != nil {
s.skillsHandler.RegisterRoutes(mux) s.skillsHandler.RegisterRoutes(mux)
} }
// Managed mode: LLM trace listing API // LLM trace listing API
if s.tracesHandler != nil { if s.tracesHandler != nil {
s.tracesHandler.RegisterRoutes(mux) s.tracesHandler.RegisterRoutes(mux)
} }
// Managed mode: MCP server management API // MCP server management API
if s.mcpHandler != nil { if s.mcpHandler != nil {
s.mcpHandler.RegisterRoutes(mux) s.mcpHandler.RegisterRoutes(mux)
} }
// Managed mode: custom tool CRUD API // Custom tool CRUD API
if s.customToolsHandler != nil { if s.customToolsHandler != nil {
s.customToolsHandler.RegisterRoutes(mux) s.customToolsHandler.RegisterRoutes(mux)
} }
// Managed mode: channel instance CRUD API // Channel instance CRUD API
if s.channelInstancesHandler != nil { if s.channelInstancesHandler != nil {
s.channelInstancesHandler.RegisterRoutes(mux) s.channelInstancesHandler.RegisterRoutes(mux)
} }
// Managed mode: provider & model CRUD API // Provider & model CRUD API
if s.providersHandler != nil { if s.providersHandler != nil {
s.providersHandler.RegisterRoutes(mux) s.providersHandler.RegisterRoutes(mux)
} }
// Managed mode: delegation history API // Delegation history API
if s.delegationsHandler != nil { if s.delegationsHandler != nil {
s.delegationsHandler.RegisterRoutes(mux) s.delegationsHandler.RegisterRoutes(mux)
} }
// Managed mode: builtin tool management API // Builtin tool management API
if s.builtinToolsHandler != nil { if s.builtinToolsHandler != nil {
s.builtinToolsHandler.RegisterRoutes(mux) s.builtinToolsHandler.RegisterRoutes(mux)
} }
// Managed mode: pending messages API // Pending messages API
if s.pendingMessagesHandler != nil { if s.pendingMessagesHandler != nil {
s.pendingMessagesHandler.RegisterRoutes(mux) s.pendingMessagesHandler.RegisterRoutes(mux)
} }
+1 -1
View File
@@ -53,7 +53,7 @@ func init() {
// HTTP API // HTTP API
MsgInvalidAuth: "invalid authentication", MsgInvalidAuth: "invalid authentication",
MsgMsgsRequired: "messages is required", MsgMsgsRequired: "messages is required",
MsgUserIDHeader: "X-GoClaw-User-Id header is required in managed mode", MsgUserIDHeader: "X-GoClaw-User-Id header is required",
MsgFileTooLarge: "file too large or invalid multipart form", MsgFileTooLarge: "file too large or invalid multipart form",
MsgMissingFileField: "missing 'file' field", MsgMissingFileField: "missing 'file' field",
MsgInvalidFilename: "invalid filename", MsgInvalidFilename: "invalid filename",
+1 -1
View File
@@ -53,7 +53,7 @@ func init() {
// HTTP API // HTTP API
MsgInvalidAuth: "xác thực không hợp lệ", MsgInvalidAuth: "xác thực không hợp lệ",
MsgMsgsRequired: "messages là bắt buộc", MsgMsgsRequired: "messages là bắt buộc",
MsgUserIDHeader: "header X-GoClaw-User-Id là bắt buộc ở chế độ managed", MsgUserIDHeader: "header X-GoClaw-User-Id là bắt buộc",
MsgFileTooLarge: "tệp quá lớn hoặc form multipart không hợp lệ", MsgFileTooLarge: "tệp quá lớn hoặc form multipart không hợp lệ",
MsgMissingFileField: "thiếu trường 'file'", MsgMissingFileField: "thiếu trường 'file'",
MsgInvalidFilename: "tên tệp không hợp lệ", MsgInvalidFilename: "tên tệp không hợp lệ",
+1 -1
View File
@@ -53,7 +53,7 @@ func init() {
// HTTP API // HTTP API
MsgInvalidAuth: "身份验证无效", MsgInvalidAuth: "身份验证无效",
MsgMsgsRequired: "messages 是必填项", MsgMsgsRequired: "messages 是必填项",
MsgUserIDHeader: "托管模式下需要 X-GoClaw-User-Id 请求头", MsgUserIDHeader: "需要 X-GoClaw-User-Id 请求头",
MsgFileTooLarge: "文件过大或 multipart 表单无效", MsgFileTooLarge: "文件过大或 multipart 表单无效",
MsgMissingFileField: "缺少 'file' 字段", MsgMissingFileField: "缺少 'file' 字段",
MsgInvalidFilename: "文件名无效", MsgInvalidFilename: "文件名无效",
+1 -1
View File
@@ -17,7 +17,7 @@ import (
// BridgeToolNames is the subset of GoClaw tools exposed via the MCP bridge. // BridgeToolNames is the subset of GoClaw tools exposed via the MCP bridge.
// Excluded: spawn (agent loop), create_forum_topic (channels), // Excluded: spawn (agent loop), create_forum_topic (channels),
// handoff/delegate_search/evaluate_loop/team_* (managed mode stores). // handoff/delegate_search/evaluate_loop/team_* (require database stores).
var BridgeToolNames = map[string]bool{ var BridgeToolNames = map[string]bool{
// Filesystem // Filesystem
"read_file": true, "read_file": true,
+2 -2
View File
@@ -72,7 +72,7 @@ type Manager struct {
// DB-backed servers // DB-backed servers
store store.MCPServerStore store store.MCPServerStore
// Shared connection pool (nil = standalone mode) // Shared connection pool (nil = config-only mode)
pool *Pool pool *Pool
poolServers map[string]struct{} // server names acquired from pool (for cleanup) poolServers map[string]struct{} // server names acquired from pool (for cleanup)
poolToolNames map[string][]string // per-agent tool names for pool-backed servers poolToolNames map[string][]string // per-agent tool names for pool-backed servers
@@ -179,7 +179,7 @@ func (m *Manager) LoadForAgent(ctx context.Context, agentID uuid.UUID, userID st
continue continue
} }
} else { } else {
// Standalone mode: create per-agent connection // Per-agent mode: create per-agent connection
if err := m.connectServer(ctx, srv.Name, srv.Transport, srv.Command, if err := m.connectServer(ctx, srv.Name, srv.Transport, srv.Command,
args, env, srv.URL, headers, args, env, srv.URL, headers,
srv.ToolPrefix, srv.TimeoutSec); err != nil { srv.ToolPrefix, srv.TimeoutSec); err != nil {
+1 -1
View File
@@ -18,7 +18,7 @@ import (
func (dm *DelegateManager) prepareDelegation(ctx context.Context, opts DelegateOpts, mode string) (*DelegationTask, *store.AgentLinkData, error) { func (dm *DelegateManager) prepareDelegation(ctx context.Context, opts DelegateOpts, mode string) (*DelegationTask, *store.AgentLinkData, error) {
sourceAgentID := store.AgentIDFromContext(ctx) sourceAgentID := store.AgentIDFromContext(ctx)
if sourceAgentID == uuid.Nil { if sourceAgentID == uuid.Nil {
return nil, nil, fmt.Errorf("delegation requires managed mode (no agent ID in context)") return nil, nil, fmt.Errorf("delegation requires database stores (no agent ID in context)")
} }
sourceAgent, err := dm.agentStore.GetByID(ctx, sourceAgentID) sourceAgent, err := dm.agentStore.GetByID(ctx, sourceAgentID)
+1 -1
View File
@@ -104,7 +104,7 @@ func (t *HandoffTool) executeTransfer(ctx context.Context, args map[string]any)
// Get current agent and channel context // Get current agent and channel context
sourceAgentID := store.AgentIDFromContext(ctx) sourceAgentID := store.AgentIDFromContext(ctx)
if sourceAgentID == uuid.Nil { if sourceAgentID == uuid.Nil {
return ErrorResult("handoff requires managed mode") return ErrorResult("handoff requires database stores")
} }
sourceAgent, err := t.delegateMgr.agentStore.GetByID(ctx, sourceAgentID) sourceAgent, err := t.delegateMgr.agentStore.GetByID(ctx, sourceAgentID)
+1 -1
View File
@@ -47,7 +47,7 @@ func (m *TeamToolManager) SetDelegateManager(dm *DelegateManager) {
func (m *TeamToolManager) resolveTeam(ctx context.Context) (*store.TeamData, uuid.UUID, error) { func (m *TeamToolManager) resolveTeam(ctx context.Context) (*store.TeamData, uuid.UUID, error) {
agentID := store.AgentIDFromContext(ctx) agentID := store.AgentIDFromContext(ctx)
if agentID == uuid.Nil { if agentID == uuid.Nil {
return nil, uuid.Nil, fmt.Errorf("no agent context — team tools require managed mode") return nil, uuid.Nil, fmt.Errorf("no agent context — team tools require database stores")
} }
// Check cache first // Check cache first
-27
View File
@@ -81,33 +81,6 @@ else
echo " [exists] GOCLAW_GATEWAY_TOKEN" echo " [exists] GOCLAW_GATEWAY_TOKEN"
fi fi
# 4. Check provider API key
has_provider=false
for key in GOCLAW_OPENROUTER_API_KEY GOCLAW_ANTHROPIC_API_KEY GOCLAW_OPENAI_API_KEY \
GOCLAW_MINIMAX_API_KEY GOCLAW_GROQ_API_KEY GOCLAW_DEEPSEEK_API_KEY \
GOCLAW_GEMINI_API_KEY GOCLAW_MISTRAL_API_KEY GOCLAW_XAI_API_KEY \
GOCLAW_COHERE_API_KEY GOCLAW_PERPLEXITY_API_KEY; do
val="$(get_env_val "$key")"
if [ -n "$val" ]; then
has_provider=true
echo " [exists] $key"
break
fi
done
if [ "$has_provider" = false ]; then
echo " [missing] No LLM provider API key found"
echo ""
echo " Add at least one provider key to .env before starting:"
echo " GOCLAW_OPENROUTER_API_KEY=sk-or-..."
echo " GOCLAW_ANTHROPIC_API_KEY=sk-ant-..."
echo " GOCLAW_MINIMAX_API_KEY=..."
echo ""
echo "=== Done (action required) ==="
echo ""
exit 0
fi
echo "" echo ""
echo "=== Done ===" echo "=== Done ==="
echo "" echo ""