Section 18 no longer references observability.md, which described the
superseded ALS/global-context mechanism and is being removed. Absorbed
the two load-bearing points: the multi-runtime rationale for rejecting
ambient context (explicit arguments are the only portable abstraction),
and the adapter framing - the application supplies ExecutionContext to
bridge spans into OTel/Sentry/logs/metrics, pi ships no exporter and no
vendor dependency, adapters allocate their own span/trace ids, and may
use AsyncLocalStorage internally. Dropped the reading-list entry.
Full-document review by a fresh-context subagent found 2 blockers, 15
minors, and 12 nits; a second subagent verified every fix. All applied:
- autoCompact ran before_compaction only when no step was unfinished,
making the hook unreachable on the live overflow path where the
abandoned assistant step still occupies LaneState; now keyed on
"not resuming a compaction step".
- Hook-supplied overflow compactions wrote no step_attempt, so the
once-per-input guard never counted them and hook-driven compact-and-
retry was unbounded; for reason overflow the hook path now writes the
compaction step_attempt first.
- Empty compaction preparation is a real code branch, terminal for
overflow. before_resume is invoked in the resume() dispatch. Pending
deferred re-park verifies handle equality. resume() re-tags operation
results as ResumeResult. MessageEntry declares terminate; options gain
toolExecution; before_navigation loses its orphaned result fields;
AbortRequestedRecord loses its unreachable reason field.
- Reduction bullets restated (newest-attempt closure, newest-own entry);
Tier B records lane moves and covers the overflow and cancellation
traces; hook-measured usage is written as hook ledger records at all
three append sites; assorted stale wording from before the deferred-
closure, ledger, and B2 changes brought current.
- Tier B: executable append-only-context assertion - every request in a
run extends the previous request as an exact prefix, except across a
compaction entry.
- Tier C: crash sites derived mechanically - snapshot the backend after
every executeAction() of each section 6 trace, reopen and resume every
snapshot, run recovery twice per snapshot.
- Section 15: fixed-point self-check as a production rule - after every
resume() outcome, recompute the section 7 reduction and compare to live
LaneState; mismatch faults the harness. Matching Tier C invariant.
- Section 19 becomes "Resolved questions": per-lane hook registration
rejected (harness-global with lane field stands); replication stays out
of scope - the append-only single-writer log replicates by log shipping
if ever wanted. Cross-references in sections 1 and 11 updated.
- Section 21: implementation goes to src/harness/experimental/ with tests
in test/harness/experimental/; everything under src/harness/ outside
experimental/ is removed when the experimental implementation replaces
it wholesale.
Resolves GHSA-8xcm-r25x-g524, GHSA-4cwx-7wf7-3272, GHSA-m8rv-5g2x-5cg5,
GHSA-jr45-8vmc-qm54, GHSA-v3r7-h72x-cjcm, and GHSA-rgw5-rvv9-x895.
undici 8.10.0 is blocked by the npm min-release-age gate; 8.9.0 is the
first fixed 8.x release. Reviewed undici 8.6.0-8.9.0 release notes
against http-dispatcher usage; no breaking changes. Adds an AGENTS.md
rule requiring changelog review for future undici updates.
Relativize absolute find results with path.relative() instead of slicing
at searchPath.length + 1. path.resolve() keeps the trailing separator for
root paths (POSIX / and Windows bare drive roots like I:\), so the slice
dropped the first character of the first path segment. Preserve directory
markers using platform-aware separators so Windows paths do not gain "//"
and valid POSIX backslashes remain unchanged.
This also avoids treating sibling paths with a shared prefix as children.
Relative results returned by custom glob operations now pass through
unchanged instead of being resolved against process.cwd(), which produced
invalid paths whenever cwd differed from the search path.
Fixes#6104
Choose the effects variant as the harness v2 design: rename
harness-v2-effects.md to harness-v2.md with the decision rationale, delete
the rejected generator variant (preserved at 01eeafd1).
Fold in everything decided since the split:
- run/turn/step nomenclature and XxxRecord suffixes for op-log records
- restore rework decisions: TaggedError/matchError result surface, section
18 ExecutionContext telemetry contract (with in-effect usage example),
step_attempt.resultEntryId with stable structural ids, hook-durability
wording, Models-level deferred API, typed record plumbing
- context-overflow/length handling per the superseding PR-7540 review:
isRecoverableLength against the intended output cap, discard-don't-append,
one recovery per conversational input, decline/nothing-to-compact
terminal, Responses incomplete_details mapping, neutral truncation
wording, crash-site table and test shapes
- UsageRecord cost ledger: union-shaped record (request/tool/hook/
adjustment causes), cost durability independent of result durability,
immutable entry usage snapshots with read-time effective cost,
lane.recordUsage(), harness-global usage event carrying session totals,
stats as ledger sum via maintained projection, fork/v3-import rules
- prompt normalization, deferred-fulfillment nuance, overflow guard
reduction, ledger and overflow test coverage
* fix(coding-agent): resume after context-limited length stops
Treat length stops as context overflow when prompt usage is within 1% of
Pi's configured context window. Include cache-write tokens in that usage
and allow non-zero output because OpenAI may emit reasoning tokens before
returning an incomplete response.
After compaction, remove any retryable error or truncated-length assistant
message restored from persisted session history so the interrupted agent
turn can continue.
This is specifically for handling this case for openai models:
https://developers.openai.com/api/docs/guides/reasoning
> If the generated tokens reach the context window limit or the max_output_tokens value you’ve set, you’ll receive a response with a status of incomplete and incomplete_details with reason set to max_output_tokens. This might occur before any visible output tokens are produced, meaning you could incur costs for input and reasoning tokens without receiving a visible response.
This detection currently assumes the reported prompt usage is within 1% of
the context window configured in Pi. Its reliability therefore depends on
Pi's model contextWindow metadata matching the provider's effective limit.
Maybe we should consider treating OpenAI incomplete responses as requiring compaction
regardless of the local context-window estimate?
fixes#7020
* fix(coding-agent): recover truncated responses after compaction
Classify length stops by comparing provider-reported output usage with the model's intended output limit. Compact and retry once when generation ends below that limit, avoiding unreliable context-window estimates while preserving genuine max-output stops.
Normalize OpenAI Responses incomplete reasons so only max_output_tokens is treated as a length stop. Preserve qualified provider reasons for diagnostics, surface other reasons as errors, and use neutral truncation wording in the TUI.
* fix(coding-agent): preserve agent events during compaction
Manual compaction already waits for the active agent run to settle, and summary generation does not emit Agent events. Keep the agent subscription connected so unexpected concurrent events are not dropped, and remove the obsolete reconnect helper.
closes#7253
Images produced by tools (extensions, MCP bridges, screenshot tools) went into
session history at full resolution because processImage was only wired to the
read tool and @file CLI attachments. Providers validate every image in the
request, so one oversized image poisons the whole conversation: Anthropic drops
its per-image cap from 8000px to 2000px once a request carries many images, and
every later turn re-sends the same payload. Switching models does not help.
Normalize tool result images in AgentSession.afterToolCall, after the extension
tool_result hook so extension-injected images are covered too. Honors
images.autoResize, and keeps the original block when processing fails so an
unavailable image backend does not delete tool output.
Refs #5369
Assisted-By: devx/86bf3b09-9aba-4d03-8154-50fca3ffc9b5