Changes
July 29, 2026 · View on GitHub
2026-07-29 - Bounded provider stream start (streamStartTimeoutMs)
What changed and why
agent-loop.tsbounds the wait for the FIRST provider stream event with a new optionalAgentLoopConfig.streamStartTimeoutMs. Providers emit their first event only once the HTTP response begins, so a dead upstream that accepts a request and never answers was previously bounded only bytimeoutMs(the idle timeout, default 5 minutes): every attempt froze the session for 300s with zero events, zero usage, and nothing persisted. Observed in a donated 5h session log where the same session hung deterministically on reopen while new sessions worked. After the first event arrives the idle bound governs as before.- The failure message
Provider stream start timed out after <ms>msdeliberately contains "timed out" so the existing retryable-error classifier (isRetryableErrorMessage) retries it instead of dead-ending the session; the request-local abort controller tears the dead request down exactly like an idle timeout. agent.tsplumbsstreamStartTimeoutMsthroughAgentOptions/Agentinto the loop config.
Files modified
agent-loop.tsagent.tstypes.ts../test/agent-loop-stream-start-timeout.test.ts
2026-07-29 - Continuation-scoped queue and timeout controls
What changed and why
Agent.continue()andcontinueWithQueuedMessages()accept continuation-only options that defer queued input from the first provider request and override both stream idle and stream-start bounds for that request without mutating the agent's configured defaults. Later requests in the same run restore the configured bounds; after the first retry event, the configured idle timeout also governs inter-event gaps so healthy silent reasoning is not capped.- Queue-first recompaction recovery takes precedence over deferral: the selected queued message is the continuation input, while first-request timeout overrides still apply.
- The core run lifecycle intentionally parks queued steering and follow-up input after terminal error or abort
responses until an external retry/compaction owner or a later admitted prompt consumes it. This stop-reason policy
is distinct from
suppressQueuedMessageDrain(), which transfers one active run's post-agent_endownership. - Coding-agent retries use these controls after a silent provider stream so a doomed retry cannot consume newly queued user input and a later ordinary provider request automatically returns to the configured timeout.
Files modified
agent.tstypes.tsagent-loop.ts../test/agent.test.ts../README.md
Why the extension system could not handle this
- Provider-request queue polling, event-reader timeout selection, and post-run native queue draining happen inside agent core before coding-agent extensions can safely claim or restore that work.
Expected merge conflict zones on next upstream sync
- MEDIUM:
agent.tscontinuation APIs/config creation and active-run lifecycle queue draining. - MEDIUM:
agent-loop.tsprovider-request timeout selection insiderunLoop().
2026-07-27 - End classifier-refused turns before tool execution
What changed and why
assistant-terminal-state.tsowns terminal assistant classification, including typed classifier refusals;agent-loop.tsnow consults it before any partial tool calls are executed. Anthropic can emit a tool call and then finish the same stream with a refusal/sensitive stop; treating the message as ordinarytoolUsepreviously ran the refused call and continued on the same model.- The terminal
agent_endlets the coding-agent retry/fallback controller immediately apply its configured pinned refusal fallback.
2026-07-23 - Session-owned post-agent_end queue drain suppression
What changed and why
Agentnow exposessuppressQueuedMessageDrain()for the active run. It stops only the lifecycle-owned post-agent_endsteering/follow-up drain, retaining both queues without aborting the run signal.Agentnow exposescontinueWithQueuedMessages()so compaction recovery can deliver retained steer/follow-up input when custom context leaves the transcript tail non-assistant.- The coding-agent compaction admission gate uses this ownership transfer for required recovery. Real user aborts continue to abort the active signal and retain the normal terminal semantics.
- Scheduled continuation can revalidate a model changed by
session_compact, recompact if required, and then deliver retained queues without inventing an empty continuation turn.
Files modified
agent.ts../test/agent.test.ts
Why the extension system could not handle this
- Native queue draining and active-run signal ownership occur inside
Agentafter event subscribers return.
Expected merge conflict zones on next upstream sync
- MEDIUM:
agent.tsactive-run lifecycle and post-agent_endqueue draining.
2026-07-23 - uuidv7 concurrency refutation + immutable launch profile
What changed and why
harness/session/uuid.ts: the inlined UUIDv7 implementation uses a synchronous counter over module state. A concurrency refutation test (test/uuid-concurrency.test.ts) records that N interleaved async tasks callinguuidv7()produce unique, monotonic-per-timestamp ids — the synchronous counter makes uniqueness hold under interleaving (noawaitbetween timestamp read and counter increment). This is a recorded refutation WITH a test, not a bare assertion; it documents that the existing synchronous-counter design is correct under interleaving so future refactors do not "fix" a non-bug by adding an async lock that would change id ordering.core/agent-session-runtime.ts(CreateAgentSessionRuntimeFactory,:35,74-242,411): runtime construction now carries an immutable per-open launch profile{ permissionPreset, creationModel, initialThinkingLevel, cwd }. The profile is retained byAgentSessionRuntimeand survivesnew_session/switch_session/reload unless the command explicitly changes it. This carries per-sessioncwd, permission-preset, model selection, and thinking level with identical semantics to today's spawn flags, withoutmain.tsclosing over process-level parse.
Files modified
harness/session/uuid.ts(no production change; refutation test only)../test/uuid-concurrency.test.ts(new)core/agent-session-runtime.ts
Why the extension system could not handle this
- The UUIDv7 counter and the launch-profile retention live inside
pi-agent-corebefore coding-agent extensions or mode renderers participate; the profile must be carried by the runtime the session registry constructs insiderunWithProviderScope.
Expected merge conflict zones on next upstream sync
- LOW:
harness/session/uuid.ts(unchanged production code; test is fork-only). - MEDIUM:
core/agent-session-runtime.tsaroundCreateAgentSessionRuntimeFactoryoptions.
2026-07-17 - Truncation-recovery flagged-call failure and proxy payload
What changed and why
- A tool call that the text tool-call middleware could only partially recover now arrives at the
agent loop carrying
incomplete: true. Previously a truncated text-protocol call could be silently dropped, leaked as raw markup, or executed from stale arguments; the loop had no way to treat a partially recovered call as a failure and ask the model to retry. prepareToolCallnow produces an immediate error outcome for any flagged call (anisErrortool result carrying a retry diagnostic such as "Re-issue the tool call"), skipping validation/hooks/execution while preserving source-order event emission in the same scheduler. The existing nativelengthstop rule is preserved for provider-native streams; only the text-middleware wrapper converts a terminallengthtotoolUsewhen tool-call activity was finalized.- The flagged error result keeps the inner loop alive (
failToolCallsFromTruncatedMessagealready returns{ terminate: false }), so the loop streams another assistant turn and the model re-issues the truncated call — the retry contract. - Flagged-call diagnostics always append
Re-issue the tool call with complete arguments.to parser-provided error messages without duplicating a final period. proxy.tstoolcall_endwire event gains an optional fulltoolCallpayload so a flagged call (which emits no argument deltas) can still be delivered to clients. The client prefers the payload and falls back to delta reconstruction; against an older server that omits it, the client degrades to the legacy delta-only path. The producing server is external; the in-repo deliverable is the wire type, the client merge, and the skew-degradation tests.
Files modified
agent-loop.tsproxy.ts../test/agent-loop.test.ts,../test/proxy-events.test.ts
Why the extension system could not handle this
- Flagged-call routing into an immediate error outcome, the retry decision, and the proxy wire type
all live inside
pi-agent-corebefore coding-agent extensions or mode renderers participate.
Expected merge conflict zones on next upstream sync
- MEDIUM:
agent-loop.tsaroundprepareToolCallandfailToolCallsFromTruncatedMessage. - LOW:
proxy.tsaround thetoolcall_endwire event and client reconstruction.
2026-07-20 - Terminating queue recovery survives compaction preparation
What changed and why
agent-loop.tsre-polls a terminating turn's drained steering or follow-up queue after next-turn preparation, restoring it on preparation failure or abort and continuing only with work that remains queued.- This keeps queued recovery input owned by agent-core while coding-agent compaction settles, preventing a queued prompt from being dropped or dispatched from stale history.
Files modified
packages/agent/src/agent-loop.tspackages/agent/test/agent.test.ts
Why the extension system could not handle this
- Queue draining, restoration, and next-turn preparation run inside the agent loop before coding-agent extensions can observe or safely requeue the consumed messages.
Expected merge conflict zones on next upstream sync
- MEDIUM:
packages/agent/src/agent-loop.tsaround terminating tool batches, queue polling, and next-turn preparation.
2026-07-06 - Stream idle timeout aborts the dangling provider request
What changed and why
- The idle-timeout reader rejected the turn but left the underlying provider request dangling:
iterator.return()is a no-op onEventStream, so a silently dead connection (network drop + reconnect) kept its socket and stream alive forever. - The agent loop now owns a per-request
AbortController, propagates caller aborts into it through a single listener, and aborts it withStreamIdleTimeoutErrorwhen the reader times out, tearing the request down so auto-retry can recover the turn.
Files modified
packages/agent/src/agent-loop.tspackages/agent/test/agent-loop.test.ts
Why the extension system could not handle this
- Stream lifetime and abort propagation live inside the agent loop's provider-request plumbing, upstream of any coding-agent extension hook.
Expected merge conflict zones on next upstream sync
- MEDIUM:
packages/agent/src/agent-loop.tsaround provider stream creation, idle-timeout reading, and abort-signal wiring.
2026-07-02 - Upstream harness timeout and compaction serialization sync
What changed and why
- Accepted upstream harness changes for rejecting invalid/non-positive Node timeouts and serializing split-turn compaction summary requests.
- This keeps the fork aligned with upstream runtime validation and prevents single-concurrency providers from receiving overlapping compaction-summary generations.
Files modified
packages/agent/src/harness/compaction/compaction.tspackages/agent/src/harness/env/nodejs.ts
Why the extension system could not handle this
- Timeout validation and harness compaction scheduling happen inside shared agent-core helpers before coding-agent extensions or mode renderers participate.
Expected merge conflict zones on next upstream sync
- LOW:
packages/agent/src/harness/env/nodejs.tsaround timeout parsing and validation. - LOW:
packages/agent/src/harness/compaction/compaction.tsaround summary request scheduling.
2026-05-15 - Tool abort loop termination
What changed and why
- Stopped the core agent loop immediately after a tool batch finishes under an aborted signal.
- This prevents a tool-level abort result from continuing into
prepareNextTurn, steering queue polling, follow-up queue polling, or another provider request. - This closes the remaining abort path not covered by terminal assistant stream event normalization.
Files modified
packages/agent/src/agent-loop.tspackages/agent/test/agent-loop.test.ts
Why the extension system could not handle this
- The decision to poll queued steering after tool execution happens inside the core loop before extensions can safely restore UI/editor queue state.
Expected merge conflict zones on next upstream sync
packages/agent/src/agent-loop.tsafterturn_endemission inrunLoop().
2026-05-15 - Upstream harness refactor sync preservation
What changed and why
- Preserved the fork's ES2021 diagnostic compatibility while accepting upstream's result-based harness/environment refactor.
- Kept stream option patching on
Object.prototype.hasOwnProperty.callinstead ofObject.hasOwn. - Kept harness error
causecapture without relying on two-argumentErrorconstruction.
Files modified
packages/agent/src/harness/agent-harness.tspackages/agent/src/harness/types.ts
Why the extension system could not handle this
- These are exported harness primitives and internal option-merging helpers that are evaluated before coding-agent extensions can participate.
Expected merge conflict zones on next upstream sync
packages/agent/src/harness/agent-harness.tsaroundapplyStreamOptionsPatch().packages/agent/src/harness/types.tsaround harness error constructors.
2026-05-15 - Compaction summary metadata
What changed and why
- Added optional
detailsmetadata to the harnessCompactionSummaryMessagetype. - This keeps the shared agent-core message augmentation compatible with coding-agent compaction summaries that carry provider-native compaction route details for TUI rendering and replay.
Files modified
packages/agent/src/harness/messages.ts
Why the extension system could not handle this
- This is exported type metadata in the shared harness message model. Extensions can populate compaction details, but they
cannot alter the core
CustomAgentMessagesdeclaration merge.
Expected merge conflict zones on next upstream sync
- LOW:
packages/agent/src/harness/messages.tsaroundCompactionSummaryMessage.
2026-05-12 - Abort terminal event normalization
What changed and why
- Normalized terminal assistant stream messages in
agent-loop.tsso the event-levelreasonis authoritative fordone/errorevents. - This prevents an abort event with a stale assistant
stopReasonfrom being treated as a normal stop and draining queued steering/follow-up messages after the user interrupted the run.
Files modified
packages/agent/src/agent-loop.tspackages/agent/test/agent.test.ts
Why the extension system could not handle this
- The stale-stopReason decision happens inside the core agent loop before extensions see a completed turn.
- Extensions can observe abort events after the fact, but they cannot prevent the loop from deciding to continue into queued messages.
Expected merge conflict zones on next upstream sync
packages/agent/src/agent-loop.tsaround terminaldone/errorstream handling.
2026-04-05 - Parallel tool completion emission
What changed and why
- Updated
executeToolCallsParallel()to finalize prepared tool calls concurrently after sequential preflight. - This lets
tool_execution_endandtoolResultmessage events appear as soon as each tool finishes instead of waiting behind an earlier slow tool. - The returned
toolResultsarray still stays in assistant source order, which preserves next-turn context ordering and matches existing semantic expectations.
Files modified
packages/agent/src/agent-loop.tspackages/agent/src/types.tspackages/agent/README.mdpackages/agent/test/agent-loop.test.ts
Why the extension system could not handle this
- The scheduling and final result collection logic lives in
@mariozechner/pi-agent-core, specificallyexecuteToolCallsParallel(). - Coding-agent extensions can observe and mutate tool inputs/results, but they cannot replace the agent loop's internal await/collection strategy or
toolExecutionscheduling behavior. - The existing builtin
parallel-tool-callsextension only changes provider payloads (parallel_tool_calls: true) and does not control runtime result finalization.
Expected merge conflict zones on next upstream sync
packages/agent/src/agent-loop.tsaroundexecuteToolCallsParallel()packages/agent/src/types.tstool execution mode docspackages/agent/README.mdtool execution behavior description
2026-05-11 - Inline harness UUIDv7 generation
What changed and why
- Replaced upstream harness imports of
uuid/v7with a local UUIDv7 generator backed by Node'scrypto.randomBytes. - This keeps clean package-manager builds working without adding a new direct
uuiddependency to@earendil-works/pi-agent-core.
Files modified
packages/agent/src/harness/session/uuid.ts(current location; the generator originally landed in the since-restructured session repo/storage files)
Why the extension system could not handle this
- The failing imports live inside the agent harness session storage implementation and run before any coding-agent extension can intercept them.
Expected merge conflict zones on next upstream sync
packages/agent/src/harness/session/uuid.ts- its importers
packages/agent/src/harness/session/{repo-utils,memory-storage,jsonl-storage}.tsaround session/entry id creation.
2026-05-11 - Harness ES2021 diagnostic compatibility
What changed and why
- Replaced
ErrorOptions/two-argumentErrorconstruction inFileErrorwith an equivalent local{ cause }option stored on the class. - Replaced
Object.hasOwnwithObject.prototype.hasOwnProperty.callin the stream option patch helper. - This keeps the upstream harness behavior intact while avoiding diagnostics in environments that type-check the package with ES2021 library declarations.
Files modified
packages/agent/src/harness/types.tspackages/agent/src/harness/agent-harness.ts
Why the extension system could not handle this
- These are type-level compatibility fixes in exported harness primitives and internal option-merging code that run before coding-agent extensions are involved.
Expected merge conflict zones on next upstream sync
packages/agent/src/harness/types.tsaroundFileErrorconstruction.packages/agent/src/harness/agent-harness.tsaroundhasOwn().
2026-07-22 - Per-thinking-block stream timing
What changed and why
agent-loop.tsnow stamps each streamed thinking block'sstartedAtandendedAtwith best-effort receipt timestamps. Every thinking update is restamped because thinking projection middleware may replace the block object between events; terminal completion, error/abort, reader failure, and normal stream fallthrough all close unfinished blocks before emitting the final message.
Files modified
packages/agent/src/agent-loop.tspackages/agent/test/agent-loop.test.ts
Why the extension system could not handle this
- The timestamps must be attached at the agent loop's provider-event choke point, before extensions receive message updates or terminal messages.
Expected merge conflict zones on next upstream sync
- LOW:
packages/agent/src/agent-loop.tsstreaming event switch and terminal response paths.