Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
GOCLAW_GATEWAY_TOKEN=
GOCLAW_ENCRYPTION_KEY=
POSTGRES_PASSWORD=
MCP_RUNTIME_ACCESS_TOKEN=

# --- Database (only for non-Docker deployments) ---
# Docker Compose auto-builds this from POSTGRES_USER/PASSWORD/DB.
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
> [!NOTE]
> This repository is a customized fork of GoClaw maintained by **trwng-thdat**, serving as the execution runtime container for the **AI Claw** SaaS platform ([ai-claw](file:///C:/HCMUS/Jarvis/ai-claw)).

<p align="center">
<img src="_statics/goclaw-logo.svg" alt="GoClaw" height="200" />
</p>

<p align="center"><strong>Multi-Tenant AI Agent Platform</strong></p>
<p align="center"><strong>Multi-Tenant AI Agent Platform (Custom Fork)</strong></p>

<p align="center">
Multi-agent AI gateway built in Go. 20+ LLM providers. 7 channels. Multi-tenant PostgreSQL.<br/>
Expand Down Expand Up @@ -149,13 +152,13 @@ git tag lite-v0.1.0 && git push origin lite-v0.1.0
### From Source

```bash
git clone -b main https://github.com/nextlevelbuilder/goclaw.git && cd goclaw
git clone -b dev https://github.com/trwng-thdat/goclaw.git && cd goclaw
make build
./goclaw onboard # Interactive setup wizard
source .env.local && ./goclaw
```

> **Note:** The default branch is `dev` (active development). Use `-b main` to clone the stable release branch.
> **Note:** The default branch is `dev` (active development). We use the `dev` branch to build and align with the `ai-claw` parent repository.

### With Docker

Expand Down
12 changes: 10 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ services:
dockerfile: Dockerfile
args:
ENABLE_OTEL: "${ENABLE_OTEL:-false}"
ENABLE_EMBEDUI: "${ENABLE_EMBEDUI:-true}" # set to false when using WITH_WEB_NGINX
ENABLE_EMBEDUI: "${ENABLE_EMBEDUI:-true}" # set to false when using WITH_WEB_NGINX
ENABLE_PYTHON: "${ENABLE_PYTHON:-true}"
ENABLE_FULL_SKILLS: "${ENABLE_FULL_SKILLS:-false}"
VERSION: "${GOCLAW_VERSION:-dev}"
Expand All @@ -47,6 +47,7 @@ services:
- GOCLAW_GATEWAY_TOKEN=${GOCLAW_GATEWAY_TOKEN:?run ./prepare-env.sh or set GOCLAW_GATEWAY_TOKEN}
- GOCLAW_ENCRYPTION_KEY=${GOCLAW_ENCRYPTION_KEY:-}
- GOCLAW_SKILLS_DIR=/app/data/skills
- GOCLAW_POSTGRES_DSN=postgres://postgres:postgres@postgres:5432/goclaw?sslmode=disable
# Debug
- GOCLAW_TRACE_VERBOSE=${GOCLAW_TRACE_VERBOSE:-0}
- BITRIX24_LOG_RAW_EVENT=${BITRIX24_LOG_RAW_EVENT:-0}
Expand Down Expand Up @@ -78,7 +79,14 @@ services:
- default
- goclaw-net
restart: unless-stopped

postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: goclaw
ports:
- "5434:5432"
volumes:
goclaw-data:
goclaw-workspace:
Expand Down
47 changes: 47 additions & 0 deletions internal/agent/loop_history_toolnames.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,53 @@ var bootstrapToolAllowlist = map[string]bool{
"Write": true,
}

// modeCoreToolAllowlist is the minimal set of tools sent for minimal/none prompt
// modes (heartbeat and other lightweight sessions). These sessions rarely call the
// full ~30-tool surface, so restricting to a core set saves the token cost of
// shipping every tool schema on each turn.
var modeCoreToolAllowlist = map[string]bool{
"read_file": true,
"write_file": true,
"memory_search": true,
"exec": true,
"datetime": true,
}

// modeTaskToolDenylist is the set of heavyweight tools stripped from task prompt
// mode (subagent/cron). These sessions don't need media generation, browser
// automation, or sub-orchestration, so their schemas are wasted tokens.
var modeTaskToolDenylist = map[string]bool{
"create_image": true,
"create_audio": true,
"create_video": true,
"browser": true,
"spawn": true,
"team_tasks": true,
}

// modeAiClawToolAllowlist is the focused tool set for ai-claw product mode.
// ai-claw agents are company chat assistants backed by MCP integrations; they
// need file/memory/web/skill/media-read tools but not the full ~30-tool surface
// (no media generation, browser, cron, heartbeat, sub-orchestration, sessions).
// Allowlist (not denylist) keeps token cost low and predictable.
var modeAiClawToolAllowlist = map[string]bool{
"read_file": true,
"write_file": true,
"edit": true,
"list_files": true,
"exec": true,
"memory_search": true,
"memory_get": true,
"datetime": true,
"web_search": true,
"web_fetch": true,
"mcp_tool_search": true,
"read_image": true,
"read_document": true,
"skill_search": true,
"use_skill": true,
}

// filterBootstrapTools returns only the bootstrap-allowed tools from the full tool list.
func filterBootstrapTools(toolNames []string) []string {
var filtered []string
Expand Down
50 changes: 50 additions & 0 deletions internal/agent/loop_tool_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,56 @@ func (l *Loop) buildFilteredTools(req *RunRequest, hadBootstrap bool, iteration,
toolDefs = filtered
}

// Mode-aware tool filtering: lightweight sessions don't need the full tool
// surface, so cut their schemas to save tokens. minimal/none (heartbeat) keep
// only core tools; task (subagent/cron) drop heavyweight media/orchestration.
// Mode is resolved the same way as the system prompt (loop_history.go).
switch resolvePromptMode("", req.SessionKey, l.promptMode) {
case PromptMinimal, PromptNone:
filtered := toolDefs[:0:0]
for _, td := range toolDefs {
if modeCoreToolAllowlist[td.Function.Name] {
filtered = append(filtered, td)
} else {
delete(allowedTools, td.Function.Name)
}
}
toolDefs = filtered
case PromptTask:
filtered := toolDefs[:0:0]
for _, td := range toolDefs {
if modeTaskToolDenylist[td.Function.Name] {
delete(allowedTools, td.Function.Name)
continue
}
filtered = append(filtered, td)
}
toolDefs = filtered
case PromptAiClaw:
// ai-claw is MCP-first: keep the allowlisted built-in tools PLUS every MCP
// tool (inline or just activated via mcp_tool_search). MCP tools register
// into the "mcp" tool group, so consult it — a static name allowlist would
// strip MCP tools right after mcp_tool_search activates them, defeating the
// whole mode.
mcpSet := make(map[string]bool)
if l.registry != nil {
if members, ok := l.registry.GetToolGroup("mcp"); ok {
for _, n := range members {
mcpSet[n] = true
}
}
}
filtered := toolDefs[:0:0]
for _, td := range toolDefs {
if modeAiClawToolAllowlist[td.Function.Name] || mcpSet[td.Function.Name] {
filtered = append(filtered, td)
} else {
delete(allowedTools, td.Function.Name)
}
}
toolDefs = filtered
}

// Final iteration: strip all tools to force a text-only response.
// Without this the model may keep requesting tools and exit with "...".
if iteration == maxIter {
Expand Down
65 changes: 37 additions & 28 deletions internal/agent/systemprompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ type PromptMode string
const (
PromptFull PromptMode = "full" // main agent — all sections
PromptTask PromptMode = "task" // enterprise automation — lean but capable
PromptAiClaw PromptMode = "aiclaw" // ai-claw product — task-tier sections + ai-claw tool allowlist
PromptMinimal PromptMode = "minimal" // subagent/cron — reduced sections
PromptNone PromptMode = "none" // identity line only
)

// modeRank defines ordinal ranking for minMode comparison.
var modeRank = map[PromptMode]int{PromptFull: 3, PromptTask: 2, PromptMinimal: 1, PromptNone: 0}
// aiclaw shares task's tier (2): same lean-but-capable section budget.
var modeRank = map[PromptMode]int{PromptFull: 3, PromptTask: 2, PromptAiClaw: 2, PromptMinimal: 1, PromptNone: 0}

// minMode returns the more restrictive of two modes.
func minMode(a, b PromptMode) PromptMode {
Expand Down Expand Up @@ -181,47 +183,47 @@ func (cfg SystemPromptConfig) sectionContent(id string, defaultFn func() []strin
// coreToolSummaries maps tool names to one-line descriptions.
// Shown in the ## Tooling section of the system prompt.
var coreToolSummaries = map[string]string{
"read_file": "Read file contents — only accesses your agent workspace. For docs returned by vault_search (shared/personal/team vault), use vault_read instead",
"write_file": "Create or overwrite files (set deliver=true to also send as chat attachment)",
"send_file": "Send an EXISTING workspace file as a chat attachment — use to resend/share files; does NOT create or modify the file (use write_file for that)",
"read_file": "Read a workspace file. For vault_search results, use vault_read instead",
"write_file": "Create or overwrite files (deliver=true also sends as chat attachment)",
"send_file": "Send an EXISTING workspace file as attachment; does NOT create/modify (use write_file)",
"list_files": "List directory contents",
"exec": "Run shell commands",
"memory_search": "Search indexed memory files (MEMORY.md + memory/*.md)",
"memory_get": "Read specific sections of memory files",
"spawn": "Spawn a self-clone subagent to handle a task in the background",
"spawn": "Spawn a subagent to handle a task in the background",
"web_search": "Search the web",
"web_fetch": "Fetch and extract content from a URL",
"datetime": "Get current date/time with timezone — use before creating cron jobs",
"cron": "Manage scheduled jobs and reminders (e.g. 'remind me at 9am', 'check every morning')",
"heartbeat": "Periodic background monitoring with HEARTBEAT.md. Unlike cron, auto-suppresses 'all OK' via HEARTBEAT_OK",
"skill_search": "Search available skills by keyword (weather, translate, github, etc.)",
"skill_manage": "Create, patch, or delete skills from conversation experience",
"publish_skill": "Register a skill directory in the system database, making it discoverable",
"cron": "Manage scheduled jobs and reminders",
"heartbeat": "Periodic background monitoring with HEARTBEAT.md (auto-suppresses 'all OK')",
"skill_search": "Search available skills by keyword",
"skill_manage": "Create, patch, or delete skills",
"publish_skill": "Register a skill directory to make it discoverable",
"use_skill": "Invoke a skill by name and follow its instructions",
"mcp_tool_search": "Search for available MCP external integration tools by keyword",
"mcp_tool_search": "Search available MCP external integration tools by keyword",
"browser": "Browse web pages interactively",
"tts": "Convert text to speech audio",
"edit": "Edit a file by replacing exact text matches",
"message": "Send a PROACTIVE message to another channel/chat — do NOT use this to reply to the user, just respond directly",
"message": "Send a PROACTIVE message to another channel/chat — NOT for replying to the user",
"sessions_list": "List sessions for this agent",
"session_status": "Show session status (model, tokens, compaction count)",
"sessions_history": "Fetch message history for a session",
"sessions_send": "Send a message into another session",
"read_image": "Analyze images — call with path from <media:image> tags, or a direct HTTP/HTTPS URL via the 'url' parameter",
"read_audio": "Analyze audio — call with media_id from <media:audio> tags",
"read_video": "Analyze video — call with media_id from <media:video> tags, or a direct HTTP/HTTPS URL via the 'url' parameter",
"create_video": "Generate videos from text descriptions using AI",
"read_document": "Analyze documents (PDF, DOCX) from <media:document> tags. If fails, use a skill instead. Path is directly accessible",
"create_image": "Generate images from text descriptions using AI",
"create_audio": "Generate music or sound effects from text descriptions using AI",
"knowledge_graph_search": "Find people, projects, and their connections — use for relationship questions (who works with whom, project dependencies) that memory_search may miss",
"team_tasks": "Team task board — track progress, manage dependencies (spawn auto-creates delegation tasks)",
"list_group_members": "List all members of the current group chat (Feishu/Lark only)",
"read_image": "Analyze images — path from <media:image> tags, or HTTP(S) URL via 'url'",
"read_audio": "Analyze audio — media_id from <media:audio> tags",
"read_video": "Analyze video — media_id from <media:video> tags, or HTTP(S) URL via 'url'",
"create_video": "Generate videos from text using AI",
"read_document": "Analyze PDF/DOCX from <media:document> tags",
"create_image": "Generate images from text using AI",
"create_audio": "Generate music or sound effects from text using AI",
"knowledge_graph_search": "Find people, projects, and their connections (relationship questions)",
"team_tasks": "Team task board — track progress and dependencies",
"list_group_members": "List members of the current group chat (Feishu/Lark only)",
"create_forum_topic": "Create a forum topic in a Telegram supergroup",
"delegate": "Delegate a task to a linked agent (requires agent_links). See ## Delegation Targets for available agents",
"memory_expand": "Retrieve full session details from episodic memory results — use after memory_search returns episodic hits",
"vault_search": "Search documents in the knowledge vault (hybrid keyword + semantic). Pass the returned doc_id to vault_read for full content",
"vault_read": "Read full content of a vault document by doc_id (from vault_search). Use for shared/personal/team vault docs that read_file cannot reach",
"delegate": "Delegate a task to a linked agent (see ## Delegation Targets)",
"memory_expand": "Retrieve full session details after memory_search episodic hits",
"vault_search": "Search the knowledge vault; pass the returned doc_id to vault_read",
"vault_read": "Read full vault document content by doc_id (from vault_search)",

// Tool aliases (edit_file, sessions_spawn, Read, Write, Edit, Bash, etc.)
// are registered in the tool registry but excluded from the system prompt
Expand All @@ -233,7 +235,10 @@ var coreToolSummaries = map[string]string{
func BuildSystemPrompt(cfg SystemPromptConfig) string {
// Mode flags for section gating.
isFull := cfg.Mode == PromptFull || cfg.Mode == ""
isTask := cfg.Mode == PromptTask
// aiclaw mirrors task's section structure (lean but capable); it diverges only
// in its context-file allowlist (bootstrap.ModeAllowlist) and tool allowlist
// (loop_tool_filter.go), so it reuses every task-gated section here.
isTask := cfg.Mode == PromptTask || cfg.Mode == PromptAiClaw
isMinimal := cfg.Mode == PromptMinimal
isNone := cfg.Mode == PromptNone

Expand Down Expand Up @@ -534,7 +539,11 @@ func BuildSystemPrompt(cfg SystemPromptConfig) string {

// 16. Recency reinforcements — full mode only (skip bootstrap, task, minimal)
if isFull && !cfg.IsBootstrap {
if len(personaFiles) > 0 {
// Claude respects instructions at the prompt start, so the persona recency
// reminder is redundant token cost for Anthropic open agents. Predefined
// agents keep it for the confidentiality/owner guardrails it carries.
skipPersonaReminder := isAnthropicProvider(cfg.ProviderType) && cfg.AgentType != store.AgentTypePredefined
if len(personaFiles) > 0 && !skipPersonaReminder {
lines = append(lines, buildPersonaReminder(personaFiles, cfg.AgentType, cfg.ProviderType)...)
}
lines = append(lines, "Reminder: Follow AGENTS.md rules — NO_REPLY when silent, match the user's language.", "")
Expand Down
12 changes: 12 additions & 0 deletions internal/agent/systemprompt_sections.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func buildExecutionBiasSection() []string {
var stableContextFileNames = map[string]bool{
bootstrap.AgentsFile: true,
bootstrap.AgentsTaskFile: true,
bootstrap.AgentsAiClawFile: true,
bootstrap.AgentsCoreFile: true,
bootstrap.ToolsFile: true,
bootstrap.UserPredefinedFile: true,
Expand Down Expand Up @@ -659,6 +660,17 @@ func buildPersonaReminder(files []bootstrap.ContextFile, agentType, providerType
return []string{reminder, ""}
}

// isAnthropicProvider reports whether the provider is Anthropic-native (Claude).
// Claude models follow instructions placed at the start of the system prompt, so
// recency-zone reminders are unnecessary token cost for them.
func isAnthropicProvider(providerType string) bool {
lower := strings.ToLower(providerType)
if strings.Contains(lower, "compat") {
return false // openai_compat may route to non-Anthropic models
}
return strings.Contains(lower, "anthropic") || strings.Contains(lower, "claude")
}

// needsSOULEcho returns true for providers that benefit from recency-zone personality echo.
// GPT models have strong recency bias and tend to lose persona in long prompts.
// Matches first-party OpenAI (chatgpt_oauth) and Codex only — not compat proxies.
Expand Down
15 changes: 15 additions & 0 deletions internal/bootstrap/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
CapabilitiesFile = "CAPABILITIES.md"
AgentsCoreFile = "AGENTS_CORE.md"
AgentsTaskFile = "AGENTS_TASK.md"
AgentsAiClawFile = "AGENTS_AICLAW.md"

// Deprecated: v1 remnant. Heartbeat uses AGENTS_CORE.md via ModeAllowlist("minimal").
AgentsMinimalFile = "AGENTS_MINIMAL.md"
Expand Down Expand Up @@ -83,6 +84,20 @@ func ModeAllowlist(mode string) map[string]bool {
SoulFile: true, // persona (splitPersonaFiles extracts to primacy zone)
IdentityFile: true,
}
case "aiclaw":
// ai-claw product mode: dedicated MCP-first doctrine (AGENTS_AICLAW.md)
// instead of plain task rules, plus the lean file set + per-user profile
// (USER.md for open agents, USER_PREDEFINED.md for predefined) so the
// company assistant keeps user context that plain task mode drops.
return map[string]bool{
AgentsAiClawFile: true,
ToolsFile: true,
CapabilitiesFile: true,
SoulFile: true,
IdentityFile: true,
UserFile: true,
UserPredefinedFile: true,
}
case "minimal":
return map[string]bool{
AgentsCoreFile: true,
Expand Down
1 change: 1 addition & 0 deletions internal/bootstrap/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var templateFiles = []string{
CapabilitiesFile,
AgentsCoreFile,
AgentsTaskFile,
AgentsAiClawFile,
}

// ReadTemplate returns the content of an embedded template file.
Expand Down
1 change: 1 addition & 0 deletions internal/bootstrap/seed_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ var userSeedFilesOpen = []string{
CapabilitiesFile,
AgentsCoreFile,
AgentsTaskFile,
AgentsAiClawFile,
}

// userSeedFilesPredefined is the set of files seeded per-user for predefined agents.
Expand Down
Loading