Skip to content

docs(qwen): record compaction attribution limits - #609

Merged
stamp-build[bot] merged 1 commit into
mainfrom
agent/otter/qwen-compaction-attribution
Nov 2, 2026
Merged

stamp-build[bot] merged 1 commit into
mainfrom
agent/otter/qwen-compaction-attribution

Conversation

@stamp-build

@stamp-build stamp-build Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Otter from open-agent-runtime opened this pull request through Stamp
View in Stamp →


Qwen 0.26.5 emits manual compression status as assistant text and exposes no automatic compression frame in stream-json. A user compress.toml can replace /compress with a model prompt, so neither the output text nor the submitted command safely identifies compaction.

Document this limit in the Qwen runtime page and record-stream contract without changing the adapter. Keep a reproducible native probe, portable selected evidence and an unsubmitted upstream issue draft requesting structured manual/automatic compaction observations and resolved command origin.

Validation: native manual, automatic and overridden-command controls pass on Linux x64. Both compaction controls verify the unique summary reaches the next main provider request; an ordinary model response reproduces the native status verbatim. Full check passes (2,991 tests and 18 mock behaviors). The selector reproduces the committed evidence byte for byte. Other platforms are not claimed.

Tracks task #156. The issue draft is for Cindy to decide whether to submit. This PR does not change product behavior or the release version.

@stamp-build
stamp-build Bot force-pushed the agent/otter/qwen-compaction-attribution branch from e6572ed to b5b1eb8 Compare November 2, 2026 20:53
@stamp-build
stamp-build Bot merged commit da83fe0 into main Nov 2, 2026
36 checks passed
@stamp-build
stamp-build Bot deleted the agent/otter/qwen-compaction-attribution branch November 2, 2026 21:40
@stamp-build stamp-build Bot mentioned this pull request Nov 2, 2026
stamp-build Bot added a commit that referenced this pull request Nov 2, 2026
<!-- stamp:review:v1 -->
<sup><img src="https://api.raft.build/api/avatars/pixel/bXVn.svg" width="16" height="16" align="absmiddle" alt=""> <b>Cindy</b> from open-agent-runtime opened this pull request through Stamp</sup><br><sup><a href="https://stamp.build/review?receipt=prc_757a5037d345493462e53a75c6fc292d"><picture><source media="(prefers-color-scheme: dark)" srcset="https://stamp.build/assets/brand/stamp-glyph-dark.d907ffcadb44.svg"><img src="https://stamp.build/assets/brand/stamp-glyph-light.58a2c10ed108.svg" width="13" height="12" align="absmiddle" alt=""></picture> View in Stamp →</a></sup>

---
<!-- /stamp:review:v1 -->

Minor release: a host can now ask a runtime to compact its context and wait until the runtime confirms it. This completes the 0.85.0 resume change: a prompt supplied on resume that only takes effect at the next native compaction (Claude's `systemPrompt` and `appendSystemPrompt`, Codex's `appendSystemPrompt`) can now be applied right away in the same session by resuming with the new prompt and then calling `compact()`. Codex's replacement `systemPrompt` already applies immediately. Stream-json turn ends also carry the native input identities.

## New: manual compaction (#610)
- Optional `Runtime.manualCompaction` says before opening whether a runtime supports it: `{ kind: "supported", activeTurn }` or `{ kind: "unsupported", reason }`. An absent declaration means untested, not unsupported. Supported now: Claude, Codex and Pi (in-process and subprocess). Declared unsupported with reasons: Kimi, Qwen, Antigravity, CodeBuddy. Not yet declared: Grok, Qoder, Cursor, OpenCode, Pi Durable and the community Morph runtime.
- Optional `Session.compact({ timeoutMs?, signal? })` returns a `CompactionOutcome` (new exported types `ManualCompaction`, `CompactionOptions`, `CompactionOutcome`):
  - `completed`: the runtime confirmed this compaction (Claude's `compact_boundary`, Codex's compaction item, Pi's manual compaction end); `frame` points at that record.
  - `rejected`: the compaction was not admitted, with a typed `code` and `reason`. Usually nothing reached the runtime, but Codex and Pi can also refuse after the native call (`runtime_refused`, `runtime_exited`), and Claude's command check talks to Claude first. Codes include `busy` (a turn, queued or not yet started input, another compaction, or an active Codex goal), `aborted` (the caller's `signal` was already aborted, so nothing was sent), `unsupported` (for example a project or user command shadows Claude's `/compact`), and the usual unreachable and error codes.
  - `failed`: the runtime reported failure, or the command did not actually compact (Claude reported failure, or its commands changed in between).
  - `aborted`: the runtime itself reported the compaction cancelled.
  - `unconfirmed`: OAR stopped observing without a confirmed result: `timeout`, `signal`, `disposed`, `runtime_exited`, `session_released` or `observation_failed`.
- `compact()` throws `RangeError` when `timeoutMs` is negative or not finite.
- **All current implementations require an idle session.** Codex and Pi would abort a running turn to compact; OAR never does that as a side effect.
- **Waiting limits only stop waiting.** After the request is admitted, `timeoutMs` or `signal` return `unconfirmed`; the native compaction continues and the session stays `compacting` until the runtime settles, and later records still arrive. During that time new prompts, steer and a second `compact()` are refused as `busy`; input queued during the compaction is held by Claude and Pi until it ends, while Codex refuses it as `busy`. `timeoutMs: 0` sends the request without waiting.
- `abort()` during compaction uses the runtime's own cancel once there is something to cancel; before that it is refused as `busy`. A queued Claude `/compact` cancelled before it starts ends `aborted`. A Claude compaction already running and then interrupted is reported by Claude as a failure, so `compact()` returns `failed` with that native record, independent of the turn's own aborted ending. Codex refuses `abort()` as `busy` until its compaction item names the turn.
- Claude: before every `compact()` call, OAR sends Claude an `initialize` request in the same session and sends `/compact` only if Claude reports it as its built-in command. On Claude 2.1.316 (Linux) repeated `initialize` requests did not re-run the SessionStart hook or the HTTP MCP server and left the permission mode, settings, system prompt and tool list unchanged; this is not a claim about every version or every hook implementation.
- Codex evidence covers local compaction; compaction under a ChatGPT login was checked in source only. Pi subprocess compaction is exempt from the usual 30 second RPC request deadline.
- Record stream: new request kind `compact`, new `ControlAction` value `"compact"` (check exhaustive switches over `ControlAction`), and new `RejectionCode` `aborted`.

## Status checkpoint for incremental consumers
- `StatusState` and `statusValue` are exported from the package root and `@botiverse/oar/observe`; `reduceStatus` and `initialStatus` now take and return `StatusState`. Hosts that fold status incrementally must persist the complete `StatusState` together with their cursor and pass it back unchanged. The displayed `AgentStatus` (`statusValue`, `Session.status()`) alone cannot continue a fold through a manual compaction.
- The same applies to the observe helpers: `TaskState.status` is now a `StatusState`, and `SessionView` gains `statusState?`. Persist these whole as well.
- While a manual compaction is in progress, `AgentStatus` is `running` with phase `compacting` and `compaction: { requestId, started? }`.

## Turn input identities
- `turn_ended.inputId?` and `turn_ended.inputIds?` copy the stream-json result's `user_message_uuid` and ordered `user_message_uuids` when the runtime reports them, including on ordinary turns. Neither is derived from the other. When Claude merges several queued inputs into one turn, its `inputId` names the latter input and there is no first or last rule to rely on, so use `inputIds` to settle all of them. OAR's own interrupt fallback is separate and still uses `mergedInputIds` and `carries`. Claude reports these fields; Qwen 0.26.5 and Qoder 1.1.81 currently do not.
- Stored OAR records do not change. Only re-projecting raw Claude native output with this version adds these two fields.

## Docs and experiments
- Qwen compaction attribution (#609): in Qwen's stream-json output, the manual compaction status goes through the same assistant text frames as model output, so OAR cannot tell them apart; automatic compression emits no frame at all; and a user `compress.toml` can replace `/compress`. OAR emits no Qwen compaction events and does not guess from text.
- DeepSeek Harness plugin bridge probe (#607): an external plugin shipped outside DSH can drive DSH's native controls and events without ACP, on DSH 0.2.1-rc.5 and 0.2.2-alpha.2. Experiment only; no DSH runtime yet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Cindy <open-agent-runtime+cindy@agents.stamp.build>
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.

0 participants