fix(coding-agent): replace the system prompt when a handler forces it

Add SystemMessage.replace: replaying a system message with the flag discards the
accumulated prompt content, sections, and tools before applying it, and providers
collapse the transcript into one leading system message whenever a later message
replaces it.

A forced prompt from before_agent_start is opaque, so it is persisted as a replace
message holding the text in content with no sections (the agent loop fills in the
full tool set). Leaving force mode persists another replace message with the
structured sections. Previously the forced prompt became a preamble section patch,
so models with native mid-conversation system messages kept the original prompt
as their leading system prompt and received the forced one as a later update.
This commit is contained in:
Armin Ronacher 2026-09-16 23:35:01 +02:00
parent 509ee2bd0b
commit e4c75a7322
11 changed files with 152 additions and 31 deletions

View file

@ -563,7 +563,7 @@ pi.on("before_agent_start", async (event, ctx) => {
});
```
The `systemPromptOptions` field gives extensions access to the same structured data Pi uses to build the system prompt. Collections are mutable. Prefer changing `sections`, `selectedTools`, or `promptGuidelines`: Pi diffs the resulting prompt sections against what the model already has and appends one system message patching only the changed sections. Returning `systemPrompt`, or setting `forceSystemPrompt`, replaces the whole prompt with a single untagged `preamble` section. Tool selection changes update both the prompt contributions and executable provider tools; calling `pi.setActiveTools()` inside the handler has the same effect as editing `selectedTools`. Models that accept system messages mid-conversation receive the patch in place and keep their cached prefix; other models get the replayed prompt as their system prompt, which is a cache miss once per change.
The `systemPromptOptions` field gives extensions access to the same structured data Pi uses to build the system prompt. Collections are mutable. Prefer changing `sections`, `selectedTools`, or `promptGuidelines`: Pi diffs the resulting prompt sections against what the model already has and appends one system message patching only the changed sections. Returning `systemPrompt`, or setting `forceSystemPrompt`, replaces the whole prompt: Pi persists a system message with `replace: true` that discards the prompt the model had, and every provider then receives the forced text as its leading system prompt (a cache miss when it changes). Tool selection changes update both the prompt contributions and executable provider tools; calling `pi.setActiveTools()` inside the handler has the same effect as editing `selectedTools`. Models that accept system messages mid-conversation receive the patch in place and keep their cached prefix; other models get the replayed prompt as their system prompt, which is a cache miss once per change.
Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` both reflect the chained system prompt as of the current handler. Later `before_agent_start` handlers can still modify it again.