Reduce prompt token overhead with leaner context injections

This commit is contained in:
Tobias Bischoff
2026-01-07 08:14:47 +01:00
committed by Peter Steinberger
parent 7a917602c5
commit 412990a139
6 changed files with 45 additions and 42 deletions

View File

@@ -16,6 +16,25 @@ import type { WorkspaceBootstrapFile } from "./workspace.js";
export type EmbeddedContextFile = { path: string; content: string }; export type EmbeddedContextFile = { path: string; content: string };
const MAX_BOOTSTRAP_CHARS = 4000;
const BOOTSTRAP_HEAD_CHARS = 2800;
const BOOTSTRAP_TAIL_CHARS = 800;
function trimBootstrapContent(content: string, fileName: string): string {
const trimmed = content.trimEnd();
if (trimmed.length <= MAX_BOOTSTRAP_CHARS) return trimmed;
const head = trimmed.slice(0, BOOTSTRAP_HEAD_CHARS);
const tail = trimmed.slice(-BOOTSTRAP_TAIL_CHARS);
return [
head,
"",
`[...truncated, read ${fileName} for full content...]`,
"",
tail,
].join("\n");
}
export async function ensureSessionHeader(params: { export async function ensureSessionHeader(params: {
sessionFile: string; sessionFile: string;
sessionId: string; sessionId: string;
@@ -88,12 +107,18 @@ export async function sanitizeSessionMessagesImages(
export function buildBootstrapContextFiles( export function buildBootstrapContextFiles(
files: WorkspaceBootstrapFile[], files: WorkspaceBootstrapFile[],
): EmbeddedContextFile[] { ): EmbeddedContextFile[] {
return files.map((file) => ({ const result: EmbeddedContextFile[] = [];
path: file.name, for (const file of files) {
content: file.missing if (file.missing) continue;
? `[MISSING] Expected at: ${file.path}` const content = file.content ?? "";
: (file.content ?? ""), const trimmed = content.trimEnd();
})); if (!trimmed) continue;
result.push({
path: file.name,
content: trimBootstrapContent(trimmed, file.name),
});
}
return result;
} }
export function formatAssistantErrorText( export function formatAssistantErrorText(

View File

@@ -34,7 +34,7 @@ describe("buildAgentSystemPromptAppend", () => {
expect(prompt).toContain("<final>...</final>"); expect(prompt).toContain("<final>...</final>");
}); });
it("lists available and unavailable tools when provided", () => { it("lists available tools when provided", () => {
const prompt = buildAgentSystemPromptAppend({ const prompt = buildAgentSystemPromptAppend({
workspaceDir: "/tmp/clawd", workspaceDir: "/tmp/clawd",
toolNames: ["bash", "sessions_list", "sessions_history", "sessions_send"], toolNames: ["bash", "sessions_list", "sessions_history", "sessions_send"],
@@ -44,7 +44,6 @@ describe("buildAgentSystemPromptAppend", () => {
expect(prompt).toContain("sessions_list"); expect(prompt).toContain("sessions_list");
expect(prompt).toContain("sessions_history"); expect(prompt).toContain("sessions_history");
expect(prompt).toContain("sessions_send"); expect(prompt).toContain("sessions_send");
expect(prompt).toContain("Unavailable tools (do not call):");
}); });
it("includes user time when provided", () => { it("includes user time when provided", () => {

View File

@@ -85,7 +85,6 @@ export function buildAgentSystemPromptAppend(params: {
new Set(normalizedTools.filter((tool) => !toolOrder.includes(tool))), new Set(normalizedTools.filter((tool) => !toolOrder.includes(tool))),
); );
const enabledTools = toolOrder.filter((tool) => availableTools.has(tool)); const enabledTools = toolOrder.filter((tool) => availableTools.has(tool));
const disabledTools = toolOrder.filter((tool) => !availableTools.has(tool));
const toolLines = enabledTools.map((tool) => { const toolLines = enabledTools.map((tool) => {
const summary = toolSummaries[tool]; const summary = toolSummaries[tool];
return summary ? `- ${tool}: ${summary}` : `- ${tool}`; return summary ? `- ${tool}: ${summary}` : `- ${tool}`;
@@ -160,9 +159,6 @@ export function buildAgentSystemPromptAppend(params: {
"- sessions_history: fetch session history", "- sessions_history: fetch session history",
"- sessions_send: send to another session", "- sessions_send: send to another session",
].join("\n"), ].join("\n"),
disabledTools.length > 0
? `Unavailable tools (do not call): ${disabledTools.join(", ")}`
: "",
"TOOLS.md does not control tool availability; it is user guidance for how to use external tools.", "TOOLS.md does not control tool availability; it is user guidance for how to use external tools.",
"", "",
params.modelAliasLines && params.modelAliasLines.length > 0 params.modelAliasLines && params.modelAliasLines.length > 0

View File

@@ -1006,7 +1006,7 @@ describe("trigger handling", () => {
describe("group intro prompts", () => { describe("group intro prompts", () => {
const groupParticipationNote = const groupParticipationNote =
"Be a good group participant: lurk and follow the conversation, but only chime in when you have something genuinely helpful or relevant to add. Don't feel obligated to respond to every message — quality over quantity. Even when lurking silently, you can use emoji reactions to acknowledge messages, show support, or react to humor — reactions are always appreciated and don't clutter the chat."; "In groups, respond only when helpful; reactions are ok when available.";
it("labels Discord groups using the surface metadata", async () => { it("labels Discord groups using the surface metadata", async () => {
await withTempHome(async (home) => { await withTempHome(async (home) => {
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({ vi.mocked(runEmbeddedPiAgent).mockResolvedValue({

View File

@@ -161,10 +161,10 @@ export function buildGroupIntro(params: {
: undefined; : undefined;
const cautionLine = const cautionLine =
activation === "always" activation === "always"
? "Be extremely selective: reply only when you are directly addressed, asked a question, or can add clear value. Otherwise stay silent." ? "Be extremely selective: reply only when directly addressed or clearly helpful. Otherwise stay silent."
: undefined; : undefined;
const lurkLine = const lurkLine =
"Be a good group participant: lurk and follow the conversation, but only chime in when you have something genuinely helpful or relevant to add. Don't feel obligated to respond to every message — quality over quantity. Even when lurking silently, you can use emoji reactions to acknowledge messages, show support, or react to humor — reactions are always appreciated and don't clutter the chat."; "In groups, respond only when helpful; reactions are ok when available.";
return [ return [
subjectLine, subjectLine,
membersLine, membersLine,

View File

@@ -1,7 +1,5 @@
import chalk from "chalk";
import { type ClawdbotConfig, loadConfig } from "../config/config.js"; import { type ClawdbotConfig, loadConfig } from "../config/config.js";
import { resolveTelegramToken } from "../telegram/token.js"; import { resolveTelegramToken } from "../telegram/token.js";
import { normalizeE164 } from "../utils.js";
import { import {
getWebAuthAgeMs, getWebAuthAgeMs,
readWebSelfId, readWebSelfId,
@@ -16,37 +14,33 @@ export async function buildProviderSummary(
const webEnabled = effective.web?.enabled !== false; const webEnabled = effective.web?.enabled !== false;
if (!webEnabled) { if (!webEnabled) {
lines.push(chalk.cyan("WhatsApp: disabled")); lines.push("WhatsApp: disabled");
} else { } else {
const webLinked = await webAuthExists(); const webLinked = await webAuthExists();
const authAgeMs = getWebAuthAgeMs(); const authAgeMs = getWebAuthAgeMs();
const authAge = authAgeMs === null ? "unknown" : formatAge(authAgeMs); const authAge = authAgeMs === null ? "" : ` auth ${formatAge(authAgeMs)}`;
const { e164 } = readWebSelfId(); const { e164 } = readWebSelfId();
lines.push( lines.push(
webLinked webLinked
? chalk.green( ? `WhatsApp: linked${e164 ? ` ${e164}` : ""}${authAge}`
`WhatsApp: linked${e164 ? ` as ${e164}` : ""} (auth ${authAge})`, : "WhatsApp: not linked",
)
: chalk.red("WhatsApp: not linked"),
); );
} }
const telegramEnabled = effective.telegram?.enabled !== false; const telegramEnabled = effective.telegram?.enabled !== false;
if (!telegramEnabled) { if (!telegramEnabled) {
lines.push(chalk.cyan("Telegram: disabled")); lines.push("Telegram: disabled");
} else { } else {
const { token: telegramToken } = resolveTelegramToken(effective); const { token: telegramToken } = resolveTelegramToken(effective);
const telegramConfigured = Boolean(telegramToken?.trim()); const telegramConfigured = Boolean(telegramToken?.trim());
lines.push( lines.push(
telegramConfigured telegramConfigured ? "Telegram: configured" : "Telegram: not configured",
? chalk.green("Telegram: configured")
: chalk.cyan("Telegram: not configured"),
); );
} }
const signalEnabled = effective.signal?.enabled !== false; const signalEnabled = effective.signal?.enabled !== false;
if (!signalEnabled) { if (!signalEnabled) {
lines.push(chalk.cyan("Signal: disabled")); lines.push("Signal: disabled");
} else { } else {
const signalConfigured = const signalConfigured =
Boolean(effective.signal) && Boolean(effective.signal) &&
@@ -59,31 +53,20 @@ export async function buildProviderSummary(
typeof effective.signal?.autoStart === "boolean", typeof effective.signal?.autoStart === "boolean",
); );
lines.push( lines.push(
signalConfigured signalConfigured ? "Signal: configured" : "Signal: not configured",
? chalk.green("Signal: configured")
: chalk.cyan("Signal: not configured"),
); );
} }
const imessageEnabled = effective.imessage?.enabled !== false; const imessageEnabled = effective.imessage?.enabled !== false;
if (!imessageEnabled) { if (!imessageEnabled) {
lines.push(chalk.cyan("iMessage: disabled")); lines.push("iMessage: disabled");
} else { } else {
const imessageConfigured = Boolean(effective.imessage); const imessageConfigured = Boolean(effective.imessage);
lines.push( lines.push(
imessageConfigured imessageConfigured ? "iMessage: configured" : "iMessage: not configured",
? chalk.green("iMessage: configured")
: chalk.cyan("iMessage: not configured"),
); );
} }
const allowFrom = effective.whatsapp?.allowFrom?.length
? effective.whatsapp.allowFrom.map(normalizeE164).filter(Boolean)
: [];
if (allowFrom.length) {
lines.push(chalk.cyan(`AllowFrom: ${allowFrom.join(", ")}`));
}
return lines; return lines;
} }