feat: enrich system prompt docs guidance

This commit is contained in:
Peter Steinberger
2026-01-18 15:00:14 +00:00
parent f3698e360b
commit e9a08dc507
10 changed files with 119 additions and 5 deletions

View File

@@ -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,

View File

@@ -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()}`,

27
src/agents/docs-path.ts Normal file
View File

@@ -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<string | null> {
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;
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -32,12 +32,14 @@ describe("buildAgentSystemPrompt", () => {
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
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:
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
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)", () => {

View File

@@ -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
? [