From e9a08dc50770ddc65bf009d6a1df9b2f4181b2e3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Jan 2026 15:00:14 +0000 Subject: [PATCH] feat: enrich system prompt docs guidance --- CHANGELOG.md | 14 ++++++---- docs/concepts/system-prompt.md | 10 +++++++ src/agents/cli-runner.ts | 8 ++++++ src/agents/cli-runner/helpers.ts | 2 ++ src/agents/docs-path.ts | 27 +++++++++++++++++++ src/agents/pi-embedded-runner/compact.ts | 8 ++++++ src/agents/pi-embedded-runner/run/attempt.ts | 8 ++++++ .../pi-embedded-runner/system-prompt.ts | 2 ++ src/agents/system-prompt.test.ts | 18 +++++++++++++ src/agents/system-prompt.ts | 27 +++++++++++++++++++ 10 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src/agents/docs-path.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d391fd3..ad5a7d419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,19 @@ Docs: https://docs.clawd.bot - Swabble: use the tagged Commander Swift package release. - CLI: add `clawdbot acp client` interactive ACP harness for debugging. - Plugins: route command detection/text chunking helpers through the plugin runtime and drop runtime exports from the SDK. -- Memory: add native Gemini embeddings provider for memory search. (#1151) — thanks @gumadeiras. -- Media: auto-enable audio understanding when provider keys are configured (OpenAI/Groq/Deepgram). -- Docs: add API usage + costs overview. https://docs.clawd.bot/reference/api-usage-costs +- Memory: add native Gemini embeddings provider for memory search. (#1151) +- Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt. ### Fixes - Auth profiles: keep auto-pinned preference while allowing rotation on failover; user pins stay locked. (#1138) — thanks @cheeeee. - macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105) -- Memory: index atomically so failed reindex preserves the previous memory database. (#1151) — thanks @gumadeiras. -- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151) — thanks @gumadeiras. +- Memory: index atomically so failed reindex preserves the previous memory database. (#1151) +- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151) + +## 2026.1.18-5 + +### Changes +- Dependencies: update core + plugin deps (grammy, vitest, openai, Microsoft agents hosting, etc.). ## 2026.1.18-3 diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md index a56ca611f..b46f11578 100644 --- a/docs/concepts/system-prompt.md +++ b/docs/concepts/system-prompt.md @@ -18,6 +18,7 @@ The prompt is intentionally compact and uses fixed sections: - **Skills** (when available): tells the model how to load skill instructions on demand. - **Clawdbot Self-Update**: how to run `config.apply` and `update.run`. - **Workspace**: working directory (`agents.defaults.workspace`). +- **Documentation**: local path to Clawdbot docs (repo or npm package) and when to read them. - **Workspace Files (injected)**: indicates bootstrap files are included below. - **Sandbox** (when enabled): indicates sandboxed runtime, sandbox paths, and whether elevated exec is available. - **Current Date & Time**: user-local time, timezone, and time format. @@ -98,3 +99,12 @@ Skills section is omitted. ``` This keeps the base prompt small while still enabling targeted skill usage. + +## Documentation + +When available, the system prompt includes a **Documentation** section that points to the +local Clawdbot docs directory (either `docs/` in the repo workspace or the bundled npm +package docs) and also notes the public mirror, source repo, community Discord, and +ClawdHub (https://clawdhub.com) for skills discovery. The prompt instructs the model to consult local docs first +for Clawdbot behavior, commands, configuration, or architecture, and to run +`clawdbot status` itself when possible (asking the user only when it lacks access). diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index e4dce5dba..2a0ff5f5b 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -6,6 +6,7 @@ import { shouldLogVerbose } from "../globals.js"; import { createSubsystemLogger } from "../logging.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { resolveUserPath } from "../utils.js"; +import { resolveClawdbotDocsPath } from "./docs-path.js"; import { resolveSessionAgentIds } from "./agent-scope.js"; import { makeBootstrapWarn, resolveBootstrapContextForRun } from "./bootstrap-files.js"; import { resolveCliBackendConfig } from "./cli-backends.js"; @@ -83,6 +84,12 @@ export async function runCliAgent(params: { sessionAgentId === defaultAgentId ? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt) : undefined; + const docsPath = await resolveClawdbotDocsPath({ + workspaceDir, + argv1: process.argv[1], + cwd: process.cwd(), + moduleUrl: import.meta.url, + }); const systemPrompt = buildSystemPrompt({ workspaceDir, config: params.config, @@ -90,6 +97,7 @@ export async function runCliAgent(params: { extraSystemPrompt, ownerNumbers: params.ownerNumbers, heartbeatPrompt, + docsPath, tools: [], contextFiles, modelDisplay, diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 6cb3278c9..e20602e0f 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -168,6 +168,7 @@ export function buildSystemPrompt(params: { extraSystemPrompt?: string; ownerNumbers?: string[]; heartbeatPrompt?: string; + docsPath?: string; tools: AgentTool[]; contextFiles?: EmbeddedContextFile[]; modelDisplay: string; @@ -182,6 +183,7 @@ export function buildSystemPrompt(params: { ownerNumbers: params.ownerNumbers, reasoningTagHint: false, heartbeatPrompt: params.heartbeatPrompt, + docsPath: params.docsPath, runtimeInfo: { host: "clawdbot", os: `${os.type()} ${os.release()}`, diff --git a/src/agents/docs-path.ts b/src/agents/docs-path.ts new file mode 100644 index 000000000..72f219aab --- /dev/null +++ b/src/agents/docs-path.ts @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js"; + +export async function resolveClawdbotDocsPath(params: { + workspaceDir?: string; + argv1?: string; + cwd?: string; + moduleUrl?: string; +}): Promise { + const workspaceDir = params.workspaceDir?.trim(); + if (workspaceDir) { + const workspaceDocs = path.join(workspaceDir, "docs"); + if (fs.existsSync(workspaceDocs)) return workspaceDocs; + } + + const packageRoot = await resolveClawdbotPackageRoot({ + cwd: params.cwd, + argv1: params.argv1, + moduleUrl: params.moduleUrl, + }); + if (!packageRoot) return null; + + const packageDocs = path.join(packageRoot, "docs"); + return fs.existsSync(packageDocs) ? packageDocs : null; +} diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index d06a751ef..e2a6f4d5c 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -17,6 +17,7 @@ import { resolveUserPath } from "../../utils.js"; import { resolveClawdbotAgentDir } from "../agent-paths.js"; import { resolveSessionAgentIds } from "../agent-scope.js"; import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../bootstrap-files.js"; +import { resolveClawdbotDocsPath } from "../docs-path.js"; import type { ExecElevatedDefaults } from "../bash-tools.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../defaults.js"; import { getApiKeyForModel, resolveModelAuthMode } from "../model-auth.js"; @@ -250,6 +251,12 @@ export async function compactEmbeddedPiSession(params: { }); const isDefaultAgent = sessionAgentId === defaultAgentId; const promptMode = isSubagentSessionKey(params.sessionKey) ? "minimal" : "full"; + const docsPath = await resolveClawdbotDocsPath({ + workspaceDir: effectiveWorkspace, + argv1: process.argv[1], + cwd: process.cwd(), + moduleUrl: import.meta.url, + }); const appendPrompt = buildEmbeddedSystemPrompt({ workspaceDir: effectiveWorkspace, defaultThinkLevel: params.thinkLevel, @@ -261,6 +268,7 @@ export async function compactEmbeddedPiSession(params: { ? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt) : undefined, skillsPrompt, + docsPath, promptMode, runtimeInfo, sandboxInfo, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 37c5fe818..d907be2cc 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -18,6 +18,7 @@ import { resolveUserPath } from "../../../utils.js"; import { resolveClawdbotAgentDir } from "../../agent-paths.js"; import { resolveSessionAgentIds } from "../../agent-scope.js"; import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../../bootstrap-files.js"; +import { resolveClawdbotDocsPath } from "../../docs-path.js"; import { resolveModelAuthMode } from "../../model-auth.js"; import { isCloudCodeAssistFormatError, @@ -216,6 +217,12 @@ export async function runEmbeddedAttempt( }); const isDefaultAgent = sessionAgentId === defaultAgentId; const promptMode = isSubagentSessionKey(params.sessionKey) ? "minimal" : "full"; + const docsPath = await resolveClawdbotDocsPath({ + workspaceDir: effectiveWorkspace, + argv1: process.argv[1], + cwd: process.cwd(), + moduleUrl: import.meta.url, + }); const appendPrompt = buildEmbeddedSystemPrompt({ workspaceDir: effectiveWorkspace, @@ -228,6 +235,7 @@ export async function runEmbeddedAttempt( ? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt) : undefined, skillsPrompt, + docsPath, reactionGuidance, promptMode, runtimeInfo, diff --git a/src/agents/pi-embedded-runner/system-prompt.ts b/src/agents/pi-embedded-runner/system-prompt.ts index e869c75d6..3cbbed6db 100644 --- a/src/agents/pi-embedded-runner/system-prompt.ts +++ b/src/agents/pi-embedded-runner/system-prompt.ts @@ -15,6 +15,7 @@ export function buildEmbeddedSystemPrompt(params: { reasoningTagHint: boolean; heartbeatPrompt?: string; skillsPrompt?: string; + docsPath?: string; reactionGuidance?: { level: "minimal" | "extensive"; channel: string; @@ -48,6 +49,7 @@ export function buildEmbeddedSystemPrompt(params: { reasoningTagHint: params.reasoningTagHint, heartbeatPrompt: params.heartbeatPrompt, skillsPrompt: params.skillsPrompt, + docsPath: params.docsPath, reactionGuidance: params.reactionGuidance, promptMode: params.promptMode, runtimeInfo: params.runtimeInfo, diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index cfed9fb45..71a40f859 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -32,12 +32,14 @@ describe("buildAgentSystemPrompt", () => { "\n \n demo\n \n", heartbeatPrompt: "ping", toolNames: ["message", "memory_search"], + docsPath: "/tmp/clawd/docs", extraSystemPrompt: "Subagent details", }); expect(prompt).not.toContain("## User Identity"); expect(prompt).not.toContain("## Skills"); expect(prompt).not.toContain("## Memory Recall"); + expect(prompt).not.toContain("## Documentation"); expect(prompt).not.toContain("## Reply Tags"); expect(prompt).not.toContain("## Messaging"); expect(prompt).not.toContain("## Silent Replies"); @@ -86,6 +88,7 @@ describe("buildAgentSystemPrompt", () => { toolNames: ["Read", "Exec", "process"], skillsPrompt: "\n \n demo\n \n", + docsPath: "/tmp/clawd/docs", }); expect(prompt).toContain("- Read: Read file contents"); @@ -93,6 +96,21 @@ describe("buildAgentSystemPrompt", () => { expect(prompt).toContain( "Use `Read` to load the SKILL.md at the location listed for that skill.", ); + expect(prompt).toContain("Clawdbot docs: /tmp/clawd/docs"); + expect(prompt).toContain("read the docs first using `Read`"); + }); + + it("includes docs guidance when docsPath is provided", () => { + const prompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/clawd", + docsPath: "/tmp/clawd/docs", + }); + + expect(prompt).toContain("## Documentation"); + expect(prompt).toContain("Clawdbot docs: /tmp/clawd/docs"); + expect(prompt).toContain( + "When a user asks about Clawdbot behavior, commands, config, or architecture", + ); }); it("includes user time when provided (12-hour)", () => { diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 5a6a7c699..e29cb483e 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -109,6 +109,26 @@ function buildMessagingSection(params: { ]; } +function buildDocsSection(params: { + docsPath?: string; + isMinimal: boolean; + readToolName: string; +}) { + const docsPath = params.docsPath?.trim(); + if (!docsPath || params.isMinimal) return []; + return [ + "## Documentation", + `Clawdbot docs: ${docsPath}`, + "Mirror: https://docs.clawd.bot", + "Source: https://github.com/clawdbot/clawdbot", + "Community: https://discord.com/invite/clawd", + "Find new skills: https://clawdhub.com", + "For Clawdbot behavior, commands, config, or architecture: consult local docs first.", + "When diagnosing issues, run `clawdbot status` yourself when possible; only ask the user if you lack access (e.g., sandboxed).", + "", + ]; +} + export function buildAgentSystemPrompt(params: { workspaceDir: string; defaultThinkLevel?: ThinkLevel; @@ -125,6 +145,7 @@ export function buildAgentSystemPrompt(params: { contextFiles?: EmbeddedContextFile[]; skillsPrompt?: string; heartbeatPrompt?: string; + docsPath?: string; /** Controls which hardcoded sections to include. Defaults to "full". */ promptMode?: PromptMode; runtimeInfo?: { @@ -295,6 +316,11 @@ export function buildAgentSystemPrompt(params: { readToolName, }); const memorySection = buildMemorySection({ isMinimal, availableTools }); + const docsSection = buildDocsSection({ + docsPath: params.docsPath, + isMinimal, + readToolName, + }); // For "none" mode, return just the basic identity line if (promptMode === "none") { @@ -371,6 +397,7 @@ export function buildAgentSystemPrompt(params: { `Your working directory is: ${params.workspaceDir}`, "Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise.", "", + ...docsSection, params.sandboxInfo?.enabled ? "## Sandbox" : "", params.sandboxInfo?.enabled ? [