Skip to content

feat(compaction): capability-gated OpenAI remote compaction with faithful history degradation#294

Merged
code-yeongyu merged 3 commits into
mainfrom
feat/openai-remote-compaction-capability-gate
Jul 22, 2026
Merged

feat(compaction): capability-gated OpenAI remote compaction with faithful history degradation#294
code-yeongyu merged 3 commits into
mainfrom
feat/openai-remote-compaction-capability-gate

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Senpi's OpenAI remote compaction (/responses/compact and the Responses WebSocket context_compaction route) previously only ran when the entire session branch was OpenAI Responses-native — any foreign-provider assistant message, ! bash execution, branch summary, custom message, image tool result, or a prior local compaction silently forced the route back to local text summarization for the rest of the session. This PR changes the gate to a provider-capability check only (current model is openai + openai-responses, matching codex's supports_remote_compaction()), and makes branch-to-input conversion total by degrading non-native entries to their canonical text form via the same sessionEntryToContextMessages + convertToLlm pipeline the normal context path uses.

It also fixes a pre-existing replay bug surfaced by the new end-to-end QA: after a remote compaction, the next provider request's payload was rebuilt from the persisted branch only, and the user's in-flight prompt (not yet persisted) was silently dropped — the model never saw the first message after a remote compaction.

Changes

  • compaction/openai-remote-convert.ts (extracted from openai-remote.ts in a prep refactor): branch conversion is now total. Foreign assistant messages keep text/tool calls and skip unrepresentable thinking/provider-native blocks; bash executions, branch summaries, custom messages, and prior local compaction entries convert to their canonical user-text forms; image-bearing tool results mirror the Responses payload builder (structured input_text/input_image parts for image-capable models, (see attached image) otherwise). Prior remote-compaction checkpoints still splice native replacementInput in order.
  • compaction/openai-remote.ts: route gate = capability only; the session-not-openai-native fallback reason is gone (only empty-compaction-input remains); payload rewrite always applies and now appends the in-flight pending messages after the branch-derived items.
  • compaction/index.ts: the context handler stashes the not-yet-persisted tail messages (pendingProviderMessages) so the rewrite can include them; computed defensively from buildContextEntries(branch) for partial-session-manager contexts.
  • Tests: test/compaction/openai-remote-compaction.test.ts — pure-OpenAI characterization (unchanged expectations), degradation cases (mixed providers, bash executions, local compaction entries, branch/custom entries, image tool results), a mixed-history remote run through runOpenAiRemoteCompaction, rewrite with a non-native tail, and pending-prompt preservation.
  • QA: new senpi-qa channel script .agents/skills/senpi-qa/scripts/compaction-remote-qa.mjs — drives the real CLI (--mode rpc) against a local mock serving Anthropic Messages, OpenAI Responses, and /v1/responses/compact.
  • compaction/changes.md: fork-tracker entries for both changes.

QA & Evidence

  • What was tested: unit/integration — npx vitest run test/compaction/ (22 files, 177 tests) covering every degradation path and both rewrites; static — npm run check (biome, tsgo, pinned deps, shrinkwrap, neo) green.
  • Observed result: all green; pure-OpenAI request building is byte-identical to before (characterization test untouched).
  • What was tested (real surface): node .agents/skills/senpi-qa/scripts/compaction-remote-qa.mjs --self-test — real CLI over RPC: anthropic mock turn → openai mock turn → compact → follow-up turn.
  • Observed result: 6/6 PASS: /v1/responses/compact called despite the anthropic turn in history; the anthropic turn reaches OpenAI as a degraded output_text item; the RPC compact response carries senpi.compaction.openai-remote.v1 details; the post-compaction /v1/responses payload replays the native compaction item and still contains the user's new prompt (this last assertion fails without the replay fix); real ~/.senpi auth untouched.
  • Artifact: local-ignore/qa-evidence/20260722-openai-remote-capability-gate/compaction-remote-qa.json (full request/response capture).
  • Why sufficient: proves the route decision, the degradation wire format, the persisted compaction details, and the replay path end-to-end on the real binary with zero tokens; the mock-loop openai-responses self-test (4/4) confirms no QA-channel regression.

Risks & Residuals

  • Risk: degraded (text-form) foreign history is less faithful than native items. Mitigation: this is exactly what the provider already receives for such messages in normal turns (same conversion pipeline); OpenAI's compact endpoint summarizes what it sees either way. Accepted.
  • Risk: pending-message stash counts on buildContextEntries(branch) alignment with the context messages; earlier context-chain extensions that inject messages could shift the count. Mitigation: miscounts bias toward dropping the pending tail (today's behavior), never toward corrupting history; covered by unit + e2e tests. Accepted, noted in changes.md.
  • Note: the pending-prompt drop was pre-existing on main (same rebuild construction); this PR's wider gate makes the remote route far more common, so the fix is a required companion.

Related Issues

Discord report: senpi fell back to local compaction for GPT sessions whenever history wasn't fully OpenAI-native (mixed-model sessions, ! commands, prior local compactions).


Summary by cubic

Enables OpenAI remote compaction whenever the current model supports it, even with mixed-provider history, by converting non-native entries into faithful text so /v1/responses/compact sees the full context. Also fixes a replay bug that dropped the user's first post-compaction message.

  • New Features

    • Gate remote compaction by capability (openai + openai-responses), not history provenance.
    • Convert the entire branch via sessionEntryToContextMessages + convertToLlm; degrade foreign assistant turns, ! bash, local compactions, branch summaries, and custom messages to canonical text.
    • Mirror image tool results as structured input_text/input_image parts for image-capable models, or a text placeholder otherwise.
    • Always rewrite the next request payload to replay the native compaction item, even with a non-native tail.
  • Bug Fixes

    • Preserve the in-flight prompt after remote compaction by appending pending provider messages to the rewritten payload.

Written for commit 339389a. Summary will update on new commits.

Review in cubic

…mote-convert module

Pure move: wire item types, details schema guard, and branch-to-input
conversion now live in openai-remote-convert.ts. No behavior change;
openai-remote.ts re-exports the moved public symbols. Existing suite:
22 files / 170 tests green, tsgo + biome clean.
The remote-compaction route no longer requires the entire session branch
to be OpenAI Responses-native. Matching codex's supports_remote_compaction(),
the gate is now provider capability only (current model is openai +
openai-responses), and branch conversion is total: entries flow through the
same sessionEntryToContextMessages + convertToLlm pipeline the normal
context path uses, so foreign-provider assistant messages, bash executions,
branch summaries, custom messages, and prior local compaction entries
degrade to their canonical text form instead of forcing a local-summary
fallback. Image-bearing tool results mirror the Responses payload builder
(structured parts for image-capable models, placeholder otherwise). The
payload rewrite no longer silently skips when post-compaction history is
not OpenAI-native.

Evidence: test/compaction 23 files green; senpi-qa compaction-remote-qa
5/5 on the real CLI (mixed anthropic->openai session compacts via
/v1/responses/compact, replay verified), artifacts under
local-ignore/qa-evidence/20260722-openai-remote-capability-gate/.
…eplay

The before_provider_request replay after a remote compaction rebuilt the
payload from the persisted branch only. The in-flight user prompt is not
yet persisted at that point, so the replayed payload silently dropped it
- the model never saw the first message after a remote compaction.
Pre-existing on main; surfaced by the mixed-history e2e QA scenario.

The context handler now stashes the not-yet-persisted tail messages and
rewriteOpenAiPayloadWithRemoteCompaction appends their conversion after
the branch-derived items.

Evidence: test/compaction 22 files / 177 tests green; senpi-qa
compaction-remote-qa 6/6 incl. prompt-presence assertion on the real CLI;
npm run check green.
@gitguardian

gitguardian Bot commented Jul 22, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
34049272 Triggered Generic High Entropy Secret fe6a8bc .agents/skills/senpi-qa/scripts/compaction-remote-qa.mjs View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant