From badc1602c8f0669eae6bf9b26c104e6c293732dd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 8 Jan 2026 00:01:40 +0000 Subject: [PATCH] fix: avoid duplicate prompt context --- CHANGELOG.md | 1 + src/agents/pi-embedded-runner.test.ts | 22 ++++++- src/agents/pi-embedded-runner.ts | 85 +++++++++++++-------------- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2363b16df..3a8ebebc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Auto-reply: removed `autoReply` from Discord/Slack/Telegram channel configs; use `requireMention` instead (Telegram topics now support `requireMention` overrides). ### Fixes +- Agent: avoid duplicating context/skills when SDK rebuilds the system prompt. (#418) - Signal: reconnect SSE monitor with abortable backoff; log stream errors. Thanks @nexty5870 for PR #430. - Gateway: pass resolved provider as messageProvider for agent runs so provider-specific tools are available. Thanks @imfing for PR #389. - Discord/Telegram: add per-request retry policy with configurable delays and docs. diff --git a/src/agents/pi-embedded-runner.test.ts b/src/agents/pi-embedded-runner.test.ts index e2fc92541..194f233fc 100644 --- a/src/agents/pi-embedded-runner.test.ts +++ b/src/agents/pi-embedded-runner.test.ts @@ -1,10 +1,14 @@ import type { AgentMessage, AgentTool } from "@mariozechner/pi-agent-core"; -import { SessionManager } from "@mariozechner/pi-coding-agent"; +import { + buildSystemPrompt, + SessionManager, +} from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { describe, expect, it, vi } from "vitest"; import { applyGoogleTurnOrderingFix, buildEmbeddedSandboxInfo, + createSystemPromptAppender, splitSdkTools, } from "./pi-embedded-runner.js"; import type { SandboxContext } from "./sandbox.js"; @@ -105,6 +109,22 @@ describe("splitSdkTools", () => { }); }); +describe("createSystemPromptAppender", () => { + it("appends without duplicating context files", () => { + const sentinel = "CONTEXT_SENTINEL_42"; + const defaultPrompt = buildSystemPrompt({ + cwd: "/tmp", + contextFiles: [{ path: "/tmp/AGENTS.md", content: sentinel }], + }); + const appender = createSystemPromptAppender("APPEND_SECTION"); + const finalPrompt = appender(defaultPrompt); + const occurrences = finalPrompt.split(sentinel).length - 1; + expect(typeof appender).toBe("function"); + expect(occurrences).toBe(1); + expect(finalPrompt).toContain("APPEND_SECTION"); + }); +}); + describe("applyGoogleTurnOrderingFix", () => { const makeAssistantFirst = () => [ diff --git a/src/agents/pi-embedded-runner.ts b/src/agents/pi-embedded-runner.ts index cf428d64f..f3b3bbb39 100644 --- a/src/agents/pi-embedded-runner.ts +++ b/src/agents/pi-embedded-runner.ts @@ -10,7 +10,6 @@ import type { } from "@mariozechner/pi-agent-core"; import type { Api, AssistantMessage, Model } from "@mariozechner/pi-ai"; import { - buildSystemPrompt, createAgentSession, discoverAuthStorage, discoverModels, @@ -492,6 +491,16 @@ export function buildEmbeddedSandboxInfo( }; } +export function createSystemPromptAppender( + appendPrompt: string, +): (defaultPrompt: string) => string { + const trimmed = appendPrompt.trim(); + if (!trimmed) { + return (defaultPrompt) => defaultPrompt; + } + return (defaultPrompt) => `${defaultPrompt}\n\n${appendPrompt}`; +} + const BUILT_IN_TOOL_NAMES = new Set(["read", "bash", "edit", "write"]); type AnyAgentTool = AgentTool; @@ -775,28 +784,23 @@ export async function compactEmbeddedPiSession(params: { params.config?.agent?.userTimezone, ); const userTime = formatUserTime(new Date(), userTimezone); - const systemPrompt = buildSystemPrompt({ - appendPrompt: buildAgentSystemPromptAppend({ - workspaceDir: effectiveWorkspace, - defaultThinkLevel: params.thinkLevel, - extraSystemPrompt: params.extraSystemPrompt, - ownerNumbers: params.ownerNumbers, - reasoningTagHint, - heartbeatPrompt: resolveHeartbeatPrompt( - params.config?.agent?.heartbeat?.prompt, - ), - runtimeInfo, - sandboxInfo, - toolNames: tools.map((tool) => tool.name), - modelAliasLines: buildModelAliasLines(params.config), - userTimezone, - userTime, - }), - contextFiles, - skills: promptSkills, - cwd: effectiveWorkspace, - tools, + const appendPrompt = buildAgentSystemPromptAppend({ + workspaceDir: effectiveWorkspace, + defaultThinkLevel: params.thinkLevel, + extraSystemPrompt: params.extraSystemPrompt, + ownerNumbers: params.ownerNumbers, + reasoningTagHint, + heartbeatPrompt: resolveHeartbeatPrompt( + params.config?.agent?.heartbeat?.prompt, + ), + runtimeInfo, + sandboxInfo, + toolNames: tools.map((tool) => tool.name), + modelAliasLines: buildModelAliasLines(params.config), + userTimezone, + userTime, }); + const systemPrompt = createSystemPromptAppender(appendPrompt); // Pre-warm session file to bring it into OS page cache await prewarmSessionFile(params.sessionFile); @@ -1100,28 +1104,23 @@ export async function runEmbeddedPiAgent(params: { params.config?.agent?.userTimezone, ); const userTime = formatUserTime(new Date(), userTimezone); - const systemPrompt = buildSystemPrompt({ - appendPrompt: buildAgentSystemPromptAppend({ - workspaceDir: effectiveWorkspace, - defaultThinkLevel: thinkLevel, - extraSystemPrompt: params.extraSystemPrompt, - ownerNumbers: params.ownerNumbers, - reasoningTagHint, - heartbeatPrompt: resolveHeartbeatPrompt( - params.config?.agent?.heartbeat?.prompt, - ), - runtimeInfo, - sandboxInfo, - toolNames: tools.map((tool) => tool.name), - modelAliasLines: buildModelAliasLines(params.config), - userTimezone, - userTime, - }), - contextFiles, - skills: promptSkills, - cwd: effectiveWorkspace, - tools, + const appendPrompt = buildAgentSystemPromptAppend({ + workspaceDir: effectiveWorkspace, + defaultThinkLevel: thinkLevel, + extraSystemPrompt: params.extraSystemPrompt, + ownerNumbers: params.ownerNumbers, + reasoningTagHint, + heartbeatPrompt: resolveHeartbeatPrompt( + params.config?.agent?.heartbeat?.prompt, + ), + runtimeInfo, + sandboxInfo, + toolNames: tools.map((tool) => tool.name), + modelAliasLines: buildModelAliasLines(params.config), + userTimezone, + userTime, }); + const systemPrompt = createSystemPromptAppender(appendPrompt); // Pre-warm session file to bring it into OS page cache await prewarmSessionFile(params.sessionFile);