Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
682 changes: 682 additions & 0 deletions agents/__tests__/base2.test.ts

Large diffs are not rendered by default.

221 changes: 202 additions & 19 deletions agents/base2/base2.ts

Large diffs are not rendered by default.

40 changes: 36 additions & 4 deletions agents/base2/gate-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,42 @@ export type Base2GateState = {
gatePassedFileMarkers?: Record<string, string>
/**
* Content fingerprint of the reviewable-source subset the last time the
* final code-reviewer gate passed. Used to skip re-review when a
* subsequent turn (e.g. a git-action turn with no new source edits)
* reopens the gate on an unchanged reviewable set. Backward-compatible:
* older serialized state lacks this field (treated as unset).
* final code-reviewer gate passed.
*
* SOFT-DEPRECATED and WRITE-ONLY as of the receipt-driven reviewer skip.
*
* Readers: NONE. There is no production reader of this field anywhere —
* base2.ts only writes it on the gate-pass path and defaults it to `''` when
* hydrating serialized state, and no CLI/renderer/telemetry surface reads it
* (the pinned active-work message and the gate telemetry payload are built
* from `gatePassedFingerprint`, `gatePassedFiles`, `pendingGateFiles`,
* `currentPhase`, and `reviewReceipts`). It is referenced only by test
* fixtures that seed serialized state.
*
* Why it lost its reader: it used to be a required conjunct of the reviewer
* skip, but a single scalar is overwritten on every gate pass, so an earlier
* wave's reviewable set re-arming produced false misses. That decision now
* reads the durable `reviewReceipts` ledger, matching a LOOKS_GOOD receipt by
* its GATE-COMPUTED `gateId` (`${reviewer}:${expectedFingerprint}`) plus the
* reviewed file set, and an attestability check on the current fingerprint.
* The reviewer-reported `snapshotFingerprint` on a receipt is drift-tolerated
* and is deliberately NOT used as content evidence.
*
* Migration/removal path for consumers:
* 1. Do not add new readers. Anything that needs "was this reviewable set
* already reviewed?" must match a `reviewReceipts` entry on `gateId` +
* `reviewedFiles`, exactly like base2's reviewer-skip rule.
* 2. The field stays written for one deprecation window so a session
* serialized by an older base2 keeps round-tripping unchanged (no
* migration step, no rollback risk: it is additive and optional).
* 3. Removal: once no serialized state in circulation is read by a base2 that
* still declares it, drop the write in base2.ts's gate-pass path, drop the
* `??= ''` default, drop this field, and drop the test-fixture seeds. Older
* serialized state stays loadable because unknown persisted keys are
* ignored.
*
* Backward-compatible: older serialized state lacks this field (treated as
* unset).
*/
reviewedReviewableFingerprint?: string
lastReviewerGateSkipReason: string
Expand Down
8 changes: 7 additions & 1 deletion cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ All notable changes to the `@openbuff/cli` package will be documented in this fi

### Added

- **Additive public-surface changes on the `handleEvent`/SDK contract consumed by this CLI.** All three are optional or new-variant additions, so no consumer migration is required and an unknown `event.type` remains a no-op for `handleEvent` consumers:
- New `context_request_trim` `PrintModeEvent` variant (`common/src/types/print-mode.ts`) reporting the SDK's request-time emergency trim — the last-line-of-defense drop applied at dispatch when a request's messages still exceed the provider-safe budget after every runtime brake ran. It carries the required `messageBudgetTokens`, `beforeTokens`, `afterTokens`, `beforeMessages`, and `afterMessages`, plus optional `runId`, `ancestorRunIds`, `agentId`, `resolvedContextWindowTokens`, and `model`. It is a DIFFERENT brake from `context_compaction`, so the two must not be merged or counted as one pass. The CLI renders it as a `compaction` block marked `trimSource: 'request'` that never consumes an announced pending card and always degrades the turn's compaction chip.
- New optional `compactionTriggerTokens` / `compactionTargetTokens` on the existing `context_window` variant, reporting the runtime's model-aware semantic-compaction budget for the active model so a UI can show where compaction will fire. Both are optional, so replayed events emitted before they existed still validate and a consumer that ignores them keeps its previous behavior; the CLI uses the trigger value to tone the context chip.
- New optional `onRequestContextTrimmed` callback and its `RequestContextTrimInfo` payload type (`common/src/types/contracts/llm.ts`, implemented in `sdk/src/impl/llm.ts`) on the published `promptAiSdk` / `promptAiSdkStream` / `promptAiSdkStructured` signatures. Purely observational: it fires only when the request-time trim actually dropped messages, can never affect the trim result, and a throwing consumer is caught and logged rather than aborting dispatch. Existing callers that omit it are unaffected. Published API details in `sdk/CHANGELOG.md`.

- New `/memory` command (alias `/mem`) for the persisted cross-session task memory of the current project. `/memory status` (the default) reports the record's revision and age, its goal and per-list counts, and how much of its evidence still verifies against disk, listing up to five stale paths. `/memory prune` drops evidence that no longer verifies. Prune reports the store's outcome faithfully: an absent record, a no-op ("nothing to prune") on a fully fresh record, the removal and remaining counts on success, and an explicit failure naming its cause (schema reject, a concurrent task-memory save, or an unwritable target such as a permissions error or a filesystem without atomic renames) together with the stale entries still present. A failed prune is never presented as "nothing to prune" or as a missing record. Published API and type surface documented in `sdk/CHANGELOG.md`.
- Optional gate repair budget caps on `createBase2` / env (see Changed). Shared resolve/format helpers live in `common/src/util/gate-repair-budgets.ts` and are re-exported from `agents/base2/base2.ts`. Documented in `docs/configuration.md` and `docs/environment-variables.md`.
- `/context` (alias `/ctx`) now always prints the effective **Gate repair budgets** section (validation / reviewer / specialist), resolved from env/defaults even when no context-budget ledger exists yet. Ledger output, when present, is shown first with budgets appended after a blank line.
Expand All @@ -27,9 +32,10 @@ All notable changes to the `@openbuff/cli` package will be documented in this fi
- Inline reviewers now receive the orchestrator history as read context without copying their private file reads, tool results, and `set_output` transcript back into the parent prompt. Deliberate `set_messages` control-plane rewrites and context-pruner compaction still propagate.
- Structured reviewer results are bounded before entering parent history, while retaining verdicts, snapshots, findings, corrections, dimensions, and representative evidence.
- Semantic compaction preserves a larger beginning-and-end task contract and next action so trailing instructions survive long pasted diagnostics. The pinned next action's baseline bound is now **1,400** characters (previously 1,000) and the pinned goal's is **2,400**. Both are baselines at the legacy 100k semantic target, not fixed absolutes: they are scaled by `clamp(targetTokens / 100_000, 0.5, 3.0)` (see the scaled-retention entry below), so the default 200k-class window resolves a 72k target and caps the goal/next action at ~1,728/~1,008 characters. `docs/agents-and-tools.md` documents the baseline caps and the scaling.
- **Persisted `compaction` block format widened (additive).** `CompactionContentBlock` in `cli/src/types/chat.ts` — persisted to `chat-messages.json` and replayed on reload — gains a fourth terminal `status` value `'declined'` alongside `pending`/`complete`/`interrupted`, plus the optional `subagent` and `trimSource` fields. `'declined'` is a pass that RAN and reclaimed nothing (the runtime settled it without ever reporting a result), deliberately distinct from `'interrupted'` (the run ended before the pass could report anything): the CLI now rewrites a settled-without-result pending card to it instead of dropping the card, and consecutive declined cards of the same run collapse into one. `subagent: true` marks a pass performed by a foreground subagent or inline agent run, and `trimSource: 'request'` marks the SDK's request-time emergency trim. Forward replay is unchanged: every added field is optional and its absent value is the previous behavior, so blocks persisted by an earlier CLI round-trip as-is. Backward replay is lossy but non-fatal: an older CLI knows only the first three `status` values and falls through to its completed-pass branch, so a `'declined'` block — whose result fields are the zeroed placeholders of a pass that never reported one — renders there as a completed pass reporting `→ 0 tokens (−0%)`, and a dropped `subagent`/`trimSource` presents a nested or request-time trim as a root-level runtime pass. Nothing fails to parse and the session still loads; the mis-rendering is confined to the transcript card. Documented in `docs/agents-and-tools.md` under "Context-window-aware compaction budgets".
- **Consumer-visible (additive) compaction contract changes.** `context_compaction` events on the public `handleEvent` surface gained optional telemetry fields (`resolvedContextWindowTokens`, `triggerBudgetTokens`, `targetBudgetTokens`, `compactionCount`, `consecutiveNoProgressCompactions`, `shortfallTokens`, `fitsBudget`, `escalated` in `common/src/types/print-mode.ts`). All are optional, so persisted/replayed events emitted before the telemetry existed still validate and consumers that ignore them keep their previous behavior. The CLI now records each compaction pass as a typed `compaction` content block (`CompactionContentBlock` in `cli/src/types/chat.ts`, rendered by `CompactionBox`) instead of appending the previous concatenated free-text `text` block; replayed sessions that still hold the old notice keep rendering as plain text. Full contract in `docs/agents-and-tools.md` under "Context-window-aware compaction budgets".
- Pinned `<knowledge_memory>` retention now scales with the resolved semantic target budget (`targetTokens / 100_000`, clamped to `[0.5, 3.0]`) instead of using fixed per-field caps, so a ~1m-token window retains up to 3x the decisions, inspected files, edits, validation results, review receipts, post-edit anchors, and blockers, and a small BYOK window retains half. The same factor scales every per-field **character** cap, including the pinned 2,400-character goal and 1,400-character next action, so those documented numbers are baselines at a 100k target rather than fixed absolutes — the default 200k-class window resolves a 72k target (scale `0.72`) and caps them at ~1,728 and ~1,008 characters. The 100k target remains the scale-`1.0` baseline, but retention at that baseline is **not** byte-identical to the previous release: this change also raised three of the baseline caps — decisions (8 → 12), blockers (8 → 12), and the next action (1,000 → 1,400 characters). Every other baseline cap is unchanged (2,400-character goal, 25 files inspected, 25 edits, 12 validation results, 12 review receipts, 16 post-edit anchors, 480-character entries). Because the block is pinned verbatim and exempt from the normal budget cutoff, it is now bounded by a hard ceiling of `max(1_500, floor(targetTokens * 0.25))` estimated tokens, enforced by oldest-first eviction in a fixed field order; `Goal:` and `Next Action:` are truncated toward 480/240-character floors rather than dropped. Documented in `docs/agents-and-tools.md` under "Context-window-aware compaction budgets".
- **Additive live compaction status event.** A new `context_compaction_status` variant on the public `handleEvent` surface (`common/src/types/print-mode.ts`) reports `state: 'started' | 'settled'` with the required agent/run correlation `runId` and `ancestorRunIds` (plus optional `agentId`) and optional `contextTokens`, `resolvedContextWindowTokens`, `triggerBudgetTokens`, and `targetBudgetTokens`. `started` fires before a programmatic step whose window-derived semantic trigger is exceeded (never for an explicit `maxContextLength` override), and `settled` with the same `runId` always follows it — including from the run's exit path when a step throws or is cancelled first — so a pass that decides not to compact cannot leave a pending state on screen. Because every agent loop (root turn, foreground subagents, inline agents) emits these events, both the event and the `context_compaction` result now carry run correlation and the protocol is scoped by run: `ancestorRunIds` is empty only for the root run, `started`/`settled` pair by `runId`, and `compactionCount` counts the emitting run's own passes rather than a per-turn total. `runId`/`ancestorRunIds` survive every forwarding hop; the optional `agentId` does not, because the `spawn_agents` forwarding path rewrites it to the direct child's agent id, so at nesting depth >= 2 it identifies the nearest forwarding child rather than the emitter and must not be used as a per-agent key. The CLI renders only a root-run `started` as a pending `compaction` block (stamped with that `runId`) plus a live status chip, replaces it in place with the terminal `context_compaction` result of the same run, drops only that run's pending block on its `settled`, and clears any stray pending block at the turn boundary; a subagent's compaction can no longer render as a root-level "Compacting context…" card, cross-settle the root run's live pass, or overwrite the root turn's compaction count. Because a user-initiated abort makes the SDK drop every post-abort event, the transient pending block also carries an optional `liveSessionId` (`CLI_LIVE_SESSION_ID` in `cli/src/types/chat.ts`) stamping the producing CLI process: a replayed pending block from a session the user aborted mid-compaction renders as "Compaction interrupted" instead of a permanent "Compacting context…" card, and the live status chip only honors `pending` while the run is active. Both this event and the scaled retention above are additive and require no consumer migration: unknown event variants are no-ops for `handleEvent` consumers, the result event's correlation fields are optional so replayed events without them stay root-attributed, replayed sessions whose compaction blocks carry no `status` field render as completed passes, and blocks persisted without `liveSessionId`/`runId` round-trip unchanged.
- **Additive live compaction status event.** A new `context_compaction_status` variant on the public `handleEvent` surface (`common/src/types/print-mode.ts`) reports `state: 'started' | 'settled'` with the required agent/run correlation `runId` and `ancestorRunIds` (plus optional `agentId`) and optional `contextTokens`, `resolvedContextWindowTokens`, `triggerBudgetTokens`, and `targetBudgetTokens`. `started` fires before a programmatic step whose window-derived semantic trigger is exceeded (never for an explicit `maxContextLength` override), and `settled` with the same `runId` always follows it — including from the run's exit path when a step throws or is cancelled first — so a pass that decides not to compact cannot leave a pending state on screen. Because every agent loop (root turn, foreground subagents, inline agents) emits these events, both the event and the `context_compaction` result now carry run correlation and the protocol is scoped by run: `ancestorRunIds` is empty only for the root run, `started`/`settled` pair by `runId`, and `compactionCount` counts the emitting run's own passes rather than a per-turn total. `runId`/`ancestorRunIds` survive every forwarding hop; the optional `agentId` does not, because the `spawn_agents` forwarding path rewrites it to the direct child's agent id, so at nesting depth >= 2 it identifies the nearest forwarding child rather than the emitter and must not be used as a per-agent key. The CLI renders only a root-run `started` as a pending `compaction` block (stamped with that `runId`) plus a live status chip, replaces it in place with the terminal `context_compaction` result of the same run, rewrites only that run's still-pending block in place to a terminal `declined` card on its `settled` (a pass that ran and reclaimed nothing keeps an honest trace instead of being deleted; consecutive declined passes of the same run collapse into one card), and clears any stray pending block at the turn boundary; a subagent's compaction can no longer render as a root-level "Compacting context…" card, cross-settle the root run's live pass, or overwrite the root turn's compaction count, though its live pass IS recorded in the notice's `pendingRunIds` set and therefore reported by the shared root-level status chip until that run settles. Because a user-initiated abort makes the SDK drop every post-abort event, the transient pending block also carries an optional `liveSessionId` (`CLI_LIVE_SESSION_ID` in `cli/src/types/chat.ts`) stamping the producing CLI process: a replayed pending block from a session the user aborted mid-compaction renders as "Compaction interrupted" instead of a permanent "Compacting context…" card, and the live status chip only honors `pending` while the run is active. Both this event and the scaled retention above are additive and require no consumer migration: unknown event variants are no-ops for `handleEvent` consumers, the result event's correlation fields are optional so replayed events without them stay root-attributed, replayed sessions whose compaction blocks carry no `status` field render as completed passes, and blocks persisted without `liveSessionId`/`runId` round-trip unchanged.
- `edit_transaction` now strongly requests real edit arrays, continues to repair complete legacy JSON encodings, and reports truncated encodings at the `edits` field with safe recovery guidance instead of a misleading `edits[0]` object error.

## [1.1.11] - 2026-07-07
Expand Down
Loading
Loading