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
2 changes: 1 addition & 1 deletion docs/design/live-journal-truncation-recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Context

The daemon keeps a bounded in-memory live journal for an unfinished turn. When the journal exceeds 10,000 events or 8 MiB, it discards the oldest replay events and prepends a `history_truncated` marker. The persisted transcript and turn-boundary compaction remain authoritative, so the complete turn becomes available again after a formal terminal event.
The daemon keeps a bounded in-memory live journal for an unfinished turn. Consecutive compatible text or thought chunks share bounded replay entries, with at most 256 source events per entry. When the journal exceeds 10,000 replay entries or 8 MiB of serialized source events, it discards the oldest entries and prepends a `history_truncated` marker whose retained and truncated counts still describe source events. The persisted transcript and turn-boundary compaction remain authoritative, so the complete turn becomes available again after a formal terminal event.
Comment thread
wenshao marked this conversation as resolved.

The marker previously had no prompt ownership, the SDK rendered a generic message, and WebUI either hid the marker behind history pagination or left the retained tail permanently visible. This design keeps the existing resource limits and eviction policy while making the loss precise and repairing the visible tail without another model request.

Expand Down
13 changes: 8 additions & 5 deletions docs/developers/daemon/03-acp-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,14 @@ In addition to the core `spawnOrAttach`, `sendPrompt`, `cancelSession`,
`liveJournal`, and `lastEventId`. Those replay fields are a bounded in-memory
window for live sessions, capped by `BridgeOptions.compactedReplayMaxBytes`
(default 4 MiB, hard ceiling 256 MiB). The in-flight `liveJournal` is
separately capped by `BridgeOptions.maxJournalEvents` (default 10 000) and
`BridgeOptions.maxJournalBytes` (default 8 MiB). If older retained replay was
dropped, `compactedReplay[0]` is the id-less `history_truncated` marker; if
journal entries were dropped, `liveJournal[0]` carries a `history_truncated`
marker with `scope: 'live_journal'`. The full persisted transcript remains on
separately capped by `BridgeOptions.maxJournalEvents` (default 10 000 replay
entries) and `BridgeOptions.maxJournalBytes` (default 8 MiB of serialized
source events). Consecutive compatible text or thought chunks share a replay
entry, with at most 256 source events per entry; other event and attribution
boundaries remain intact. If older retained replay was dropped,
`compactedReplay[0]` is the id-less `history_truncated` marker; if journal
entries were dropped, `liveJournal[0]` carries a `history_truncated` marker
with `scope: 'live_journal'`. Its retained and truncated counts describe source events, not replay entries. The full persisted transcript remains on
disk and is not exposed by this bridge response.
`BridgeClientRequestContext` is the request context threaded through bridge
calls; it carries `clientId`, `fromLoopback: boolean`, and `promptId`.
Expand Down
2 changes: 1 addition & 1 deletion docs/developers/qwen-serve-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -2082,7 +2082,7 @@ Response:

`attached: true` means the session was already live (either from a prior `session/load`/`session/resume`, or because a coalesced concurrent caller raced just ahead).

**History replay over SSE.** While `loadSession` is in flight on the agent side, the agent may emit `session_update` notifications for persisted turns, or return bulk replay updates in the response metadata. The daemon seeds those events into the session's bounded replay snapshot window before the route response returns. For live sessions, `POST /session/:id/load` only promises that bounded window (`compactedReplay`, `liveJournal`, `lastEventId`), not the full transcript. The window is byte-capped by `--compacted-replay-max-bytes` (default 4 MiB, maximum 256 MiB); if older replay entries were dropped, `compactedReplay[0]` is an id-less `history_truncated` marker. The in-flight `liveJournal` is separately capped by `--max-journal-events` (default 10 000) and `--max-journal-bytes` (default 8 MiB); when exceeded, the oldest journal entries are dropped and a `history_truncated` marker with `scope: 'live_journal'` is prepended. Clients should render that marker as status and continue applying retained events. Full persisted transcript access is exposed separately through `GET /session/:id/transcript`.
**History replay over SSE.** While `loadSession` is in flight on the agent side, the agent may emit `session_update` notifications for persisted turns, or return bulk replay updates in the response metadata. The daemon seeds those events into the session's bounded replay snapshot window before the route response returns. For live sessions, `POST /session/:id/load` only promises that bounded window (`compactedReplay`, `liveJournal`, `lastEventId`), not the full transcript. The window is byte-capped by `--compacted-replay-max-bytes` (default 4 MiB, maximum 256 MiB); if older replay entries were dropped, `compactedReplay[0]` is an id-less `history_truncated` marker. The in-flight `liveJournal` is separately capped by `--max-journal-events` (default 10 000 replay entries) and `--max-journal-bytes` (default 8 MiB of serialized source events). Consecutive compatible `agent_message_chunk` or `agent_thought_chunk` source events share a replay entry, up to 256 source events per entry, while tool, attribution, provenance, and discrete-message boundaries remain intact. When either cap is exceeded, the oldest entries are dropped whole (so the retained tail can be much smaller than the byte cap) and a `history_truncated` marker with `scope: 'live_journal'` is prepended; its `truncatedEvents` and `retainedEvents` fields count source events, not replay entries. Clients should render that marker as status and continue applying retained events. Full persisted transcript access is exposed separately through `GET /session/:id/transcript`.

The replay-window byte caps apply after the child has reconstructed the persisted transcript; they do not cap the on-disk JSONL read. A restore that exceeds the daemon budget returns `504` with a `Retry-After` derived from the restore budget (clamped to 5-120s) and `{code: "session_restore_timeout", errorKind: "restore_timeout", retryable: true, sessionId, action, timeoutMs}`. The daemon fences the still-running ACP request and cleans up any late session instead of registering it. A retry for the same id returns `409 restore_in_progress` with `reason: "awaiting_abandoned_cleanup"` and a `Retry-After` of the restore budget (clamped to 5-120s) until that cleanup settles. If late cleanup is uncertain, or the abandoned restore has still not settled a full restore budget after its deadline, new sessions on that workspace return `503 acp_channel_unavailable` with `reason: "restore_cleanup_failed"` or `"restore_settlement_overdue"`; already-live sessions remain usable while the channel drains.

Expand Down
4 changes: 2 additions & 2 deletions docs/users/qwen-serve.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ Notes:
| `--child-heap-mode <mode>` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. `observe` (default) reports what it would apply — `limits.memory.childHeap.perChildCeilingMb` and `maxConcurrentChildren` — and counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` models nothing, and says so on the wire: `maxConcurrentChildren` and `perChildCeilingMb` are both `null` rather than carrying a partition you switched off. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. |
| `--event-ring-size <n>` | `8000` | Per-session SSE replay ring depth (#3803 §02 target). Sets the backlog available to `GET /session/:id/events` with `Last-Event-ID: N`. Larger = more reconnect headroom at the cost of a few hundred KB extra RAM per session. SDK clients can additionally request a larger per-subscriber backlog cap on a specific subscription via `?maxQueued=N` (range `[16, 2048]`, default 256). Daemons also emit a non-terminal `slow_client_warning` SSE frame at 75% queue fill so clients can drain / reconnect before getting evicted. Pre-flight `caps.features.slow_client_warning`. |
| `--compacted-replay-max-bytes <n>` | `4194304` | Per-live-session byte cap for the retained replay events in the bounded snapshot returned by `POST /session/:id/load`. The cap applies to `compactedReplay`; the current in-flight `liveJournal` is separately capped by `--max-journal-events` and `--max-journal-bytes`. Values must be positive safe integers; invalid values fail at boot, and the hard ceiling is 256 MiB. When older retained replay is dropped, the snapshot begins with `history_truncated`. This does not limit the on-disk transcript. |
| `--max-journal-events <n>` | `10000` | Per-session cap on the number of raw events retained in the in-flight live journal (the current unfinished turn). When exceeded, the oldest journal entries are dropped and a `history_truncated` marker is prepended. Must be a positive safe integer. |
| `--max-journal-bytes <n>` | `8388608` | Per-session byte cap on the in-flight live journal. When exceeded, the oldest journal entries are dropped (at least one entry is always kept). Must be a positive safe integer. Defaults to 8 MiB. |
| `--max-journal-events <n>` | `10000` | Per-session cap on replay entries retained in the in-flight `liveJournal` for the current unfinished turn. Consecutive compatible text or thought chunks share an entry, with at most 256 source events per entry; other event boundaries are preserved. When exceeded, the oldest entries are dropped and a `history_truncated` marker is prepended. The marker's `truncatedEvents` and `retainedEvents` counts describe source events. Must be a positive safe integer. |
| `--max-journal-bytes <n>` | `8388608` | Per-session byte cap on the in-flight `liveJournal`, accounted from the serialized source events even when compatible chunks share a replay entry. When exceeded, the oldest entries are dropped whole (at least one entry is always kept), so the retained tail can be much smaller than the cap. Must be a positive safe integer. Defaults to 8 MiB. |
| `--mcp-client-budget <n>` | — | Positive integer cap on live MCP clients. When `mcp_workspace_pool` is advertised, the cap and transports are shared per workspace runtime; when the tag is absent, the legacy per-session manager enforces it. Combine with `--mcp-budget-mode`. When unset, no accounting-driven enforcement (but `GET /workspace/mcp` still reports `clientCount`). Distinct from claude-code's `MCP_SERVER_CONNECTION_BATCH_SIZE`, which gates startup concurrency rather than total live clients. Pre-flight `caps.features.mcp_guardrails` and `caps.features.mcp_workspace_pool`. |
| `--mcp-budget-mode <m>` | `warn` / `off` | How `--mcp-client-budget` is enforced. `warn` (default when budget set): no refusal, snapshot's `budgets[0].status` flips to `warning` at ≥75% of budget. `enforce`: connects past the cap are refused, per-server cell shows `disabledReason: 'budget'`, deterministic by `mcpServers` declaration order. `off` (default when budget unset): pure observability. Boot rejects `enforce` without a budget. |
| `--external-tool-guard-mode <m>` | `off` | Managed ACP external pre-execution policy. `off` makes no provider calls and advertises no capability. `required` fails startup unless a compatible provider completes the v1 handshake, then fails every supported top-level tool invocation closed unless its single prepare request is allowed. |
Expand Down
Loading
Loading