fix(coding-agent): emit before_agent_start for runs started by custom messages (#10739)

Runs started by pi.sendMessage(..., { triggerTurn: true }) skipped before_agent_start. After their first tool call, the next-turn refresh fell back to the base prompt options and patched away the sections handlers had added, changing the system prompt mid-run and breaking the provider prompt cache.

These runs now go through the same setup as prompt(): compaction check, before_agent_start, tool loadout, nextTurn messages, handler messages, and the system patch. BeforeAgentStartEvent is a union on messageType ("user" or "custom"); custom events carry the starting message.

The session counts as busy from the start of setup, so messages sent while handlers run go into the starting run instead of starting a second one, and an abort requested meanwhile aborts the run once it starts.

closes #10267, closes #5581, closes #10639, closes #10289
partly fixes #10606
This commit is contained in:
David Brailovsky 2026-10-12 09:53:24 +02:00 • committed by GitHub
parent c661bc574f
commit a447ba11f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 312 additions and 92 deletions

View file

@ -60,6 +60,7 @@ Start long-lived resources from `session_start` or from the command or tool that
Close session-scoped resources from an idempotent `session_shutdown` handler.
A run proceeds from input and `before_agent_start`, through model, message, and tool events, to `agent_end`.
Pi counts as busy while a run starts, including the compaction check and `before_agent_start`, so messages sent meanwhile go into the starting run.
Automatic retries, recovery, compaction, or queued work can continue afterward.
<a id="agent_start--agent_end--agent_before_settle--agent_settled"></a>
@ -100,7 +101,7 @@ Use each event’s declared result type rather than assuming every return value
Events cover resource discovery, sessions, agent and message lifecycle, providers, tools, and raw input.
`before_agent_start` exposes both the current prompt and its structured `systemPromptOptions`. Prefer changing prompt sections, selected tools, or guidelines so Pi can append a transcript delta. Returning `systemPrompt`, or setting `forceSystemPrompt`, replaces the whole prompt for that run while the transcript continues recording the structured sections. Providers receive the forced text as their leading system prompt.
`before_agent_start` exposes both the current prompt and its structured `systemPromptOptions`. `event.messageType` says what started the run: `"user"` for a prompt or `pi.sendUserMessage()`, with `prompt` and `images`; `"custom"` for `pi.sendMessage(..., { triggerTurn: true })`, with the starting `message` and no `prompt`. A returned `message` is added to either kind of run; check `messageType` to skip runs started by custom messages. Prefer changing prompt sections, selected tools, or guidelines so Pi can append a transcript delta. Returning `systemPrompt`, or setting `forceSystemPrompt`, replaces the whole prompt for that run while the transcript continues recording the structured sections. Providers receive the forced text as their leading system prompt.
`message_end` can replace a finalized message while preserving its role. `tool_call` can mutate input or block execution. `tool_result` handlers compose, with each handler seeing prior changes.