fix(agent-loop): compose the session prefix before pre-step; hand it to the pressure gate

ds-review-bot critical (follow-up): on the first step of a resumed or
seeded/forked instance, auto-compaction ran before runStep composed
this instance's prefix, so the gate read the PREVIOUS instance's logged
prefix from the header fold — a contributor that grew across
resume/fork (skills added, AGENTS.md grown: exactly the
environment-dependent case) could under-gate and ship an over-window
first request.

The loop now composes agent/session-prefix before the instance's first
agent/pre-step (still once per instance; runStep just reads the cache),
and agent/pre-step carries the composed prefix to its listeners.
CompactService.compactIfNeeded gains the sessionPrefix parameter;
BasicCompactService.estimatePressure gates on the handed value — the
header-fold read is gone, so the estimate is exact at every step
including a resumed/forked instance's first. Composition moving before
the boundary snapshot also means a composing listener's session append
now joins the CURRENT request (documented on the seam).

New coverage: composition precedes pre-step and the seam receives the
composed prefix; cancel and disposal landing inside the composition
window drop the step cleanly; the compact gate test hands the prefix
directly.
This commit is contained in:
Yichen Jiang
2026-07-08 20:30:10 +08:00
parent e6bd6cc9be
commit 765052a7d1
16 changed files with 244 additions and 107 deletions

View File

@@ -98,11 +98,13 @@ Implementations MUST honor:
- **Blocking**: no compaction begins while another is in progress for the same session. The recommended mechanism is the log-recorded lock — append `compact/start` before the slow work and `compact/end` after (even on failure) — so the lock is visible to replay and crash recovery.
```ts cordis-catalog
abstract compactIfNeeded( agent: CompactAgentContext, fullSystemPrompt: string, signal: AbortSignal, ): Promise<CompactionResult | null>
abstract compactIfNeeded( agent: CompactAgentContext, fullSystemPrompt: string, sessionPrefix: readonly Message[], signal: AbortSignal, ): Promise<CompactionResult | null>
abstract compactRegion( session: Session, start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>
```
Source: [`packages/compact/compact/src/index.ts:63`](../../packages/compact/compact/src/index.ts)
Types: [Message](../core-data-structures/core.md)
Source: [`packages/compact/compact/src/index.ts:64`](../../packages/compact/compact/src/index.ts)
## `ctx.fs` — `FileSystem` (abstract seam)