feat(web): categorized context usage display, budget surfacing, and lifecycle inspector [stack 1/2] - #1118
Open
ChrAlpha wants to merge 31 commits into
Open
feat(web): categorized context usage display, budget surfacing, and lifecycle inspector [stack 1/2]#1118ChrAlpha wants to merge 31 commits into
ChrAlpha wants to merge 31 commits into
Conversation
# Conflicts: # apps/web/src/pages/home/components/chat-pane.vue # packages/sdk/src/index.ts
…ly for native runtimes
…ector The list now arrives without selection_decisions, so the dialog fetches one turn on expand and caches it; drop reasons come from that detail while trimmed fragments are counted apart from dropped ones. Rows carry a prompt-diff tag against the older turn, the header only prints observed cache totals, and pages beyond 50 turns say so with a load-older action.
…e compaction UI by runtime The trigger measures history alone, so both marks start where the conversation segment does; the blocking level gets its own tick. Plan-derived bands drop out under a pane model override, Compact Now only shows where the status reports compaction at all, and provider input stays visible next to the estimate.
…er-run query to the team The synchronous backstop only runs on the history path, which no reader can observe per session, so reporting it would label a level that may never fire. The legacy by-run lookup now carries the same team predicate as its run-table sibling.
…den inspector detail loading The trigger's measured quantity differs by turn path (provider input on the pipeline path, history estimate otherwise), so the mark is drawn against the window with no claim about which segment reaches it, and the blocking tick is gone. Per-turn detail only loads while the dialog is open, resets on close so a session switch cannot fetch a foreign run, surfaces its failure, keeps the list on screen while older turns load, treats legacy history as older turns, and the /compact slash command follows the same availability as Compact Now.
The pane always sends its selected model as model_id, so applicability has to be decided where the model is resolved: the newest plan only describes the next turn when it was made for the same provider model.
…pages per session placeholderData carried the previous session's turns into a freshly opened inspector long enough to request a foreign run; the list now starts clean and the page size resets whenever the session changes.
…ummary Every reader only ever needed the trimmed count and the tokens lost per drop reason, so the snapshot now carries those as fixed-size fields computed when the turn is built. Summary() strips the per-fragment audit, the one part that grows with the conversation, and the assistant-message metadata copies persist that summary instead of the full audit.
…eads stay bounded The audit that grows with conversation length moves to selection_decisions, split at the storage boundary by every write statement; the snapshot column is now a bounded summary and 0146 splits and rolls up existing rows per team. Writes return identity columns only, the session status reads one summary row, and the per-run detail endpoint is gone: nothing in the read path scales with history any more.
Drop reasons and trimmed counts come from the rolled-up trace, so the lazy per-turn detail path is gone. The lifecycle query lives inside the dialog content and expires soon after it closes, requests carry the abort signal, a finished turn also refreshes an open inspector, and a storage quota error pauses cache persistence for the page instead of retrying every second.
The per-team loop reads public.teams under its forced policy, which raises without a bound team; open the policy for the migration and restore it, as 0120 does. Also pins the two audit-preserving rewrite paths and the legacy status fallback with tests, and recognizes Firefox's quota error code.
…the compaction mark honestly Read-backs during lifecycle reconciliation no longer project the per-fragment audit; a dedicated query serves it. The auto-compact mark is only reported when a summarizer resolves through the trigger's candidate chain, the threshold helper is exported instead of aliased, the list description no longer points at the removed per-run endpoint, and the migration notes its lock and skips trimmed-only rows on re-run.
…spector honest across refreshes Enter on the ring opens the popover with focus inside while hover still opens it silently, the label speaks the usage percentage, and closing the inspector returns focus to the ring. Turn-end invalidation diffs the set of streaming sessions and refetches once more after the lifecycle row has landed; Compact gates on whichever token basis the session reports; expanded turns survive a refetch; the clamped mark stays visible; the inspector chunk loads on first open; a pane without a session shows its empty state; immediate cache saves honour the quota breaker; prompt diffs distinguish system-and-tools changes.
…-review # Conflicts: # apps/web/src/pages/home/components/chat-pane.vue
…-review # Conflicts: # apps/web/src/store/chat-list.ts # packages/sdk/src/index.ts
ChrAlpha
marked this pull request as ready for review
September 5, 2026 06:53
…window The plan applicability check compared provider model names only, so a pane override to a model of the same name on another provider kept the previous provider's plan and the UI budgeted a 32K model against a 128K window. A plan never exceeds the window of the model it was made for, so a plan wider than the resolved model's window is treated as another model's and dropped.
ChrAlpha
added a commit
to ChrAlpha/Memoh
that referenced
this pull request
Sep 5, 2026
Picks up the budget plan window check (felinics#1118 review) so the stacked base stays current.
ChrAlpha
added a commit
to ChrAlpha/Memoh
that referenced
this pull request
Sep 5, 2026
The per-run audit and the injected texts were fetched under new query keys that the cache persistence did not exclude, so workspace files and hook output landed on disk and the audit rejoined the synchronous whole-cache serialization that felinics#1118 had moved it out of. The three keys are excluded like the lifecycle page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bottom of a two-PR stack. This PR surfaces the fragment-first context orchestration data (#1085/#1099) in the web UI and makes the lifecycle store it reads from safe for long-horizon sessions; #1157 (Trajectory pane) stacks on it, replaces the Phase C inspector dialog with a DeepSeek-Harness-style trajectory, and adds the per-request traces and stored context texts. Review this PR on its own; nothing here depends on #1157.
ContextfragKinds + tool-def buckets map into 8 fixed-order categories (context-categories.ts), rendered as a segmented bar + legend (context-usage-breakdown.vue) with an estimate-basis header (~total / window (%)). Provider-reported input stays a separate fact row, and the provider-basis single bar remains the fallback for sessions without lifecycle data. The ring shows for native and ACP sessions, stays off for direct runtimes, and refreshes when a turn finishes (one invalidation owner, set-diffed across panes, plus one follow-up refetch once the lifecycle row has landed). It is keyboard reachable: Enter opens the popover with focus inside, the label speaks the usage percentage, Escape returns focus to the ring.GET /bots/:id/sessions/:sid/statusreturns the persistedbudget_plan(window, output reserve, tool-def cost, estimator) andcompaction(enabled,auto_tokens). The mark derives from the persisted plan window only, only for native runtimes, and only when a summarizer resolves through the trigger's candidate chain; the plan is omitted when the pane'smodel_idresolves to a different provider model than the newest snapshot. The UI usesbudget_plan.windowas denominator, draws the output reserve band and a single auto-compact threshold tick against the window (the trigger's measured quantity differs by turn path, so the tick states the threshold and makes no claim about which segment reaches it). Compact Now and the/compactslash command follow the same availability./context-lifecycle: newest-first collapsible turns (first expanded, the reader's expansions survive refreshes), status tone, composition bar, selection counts incl. trimmed, drop reasons with token cost, trust breakdown, window/stable-prefix/cache metrics, mutations, per-step drops, a prompt-diff tag per turn (first turn / system / tools / system+tools / history only), page-scoped aggregates that render only observed values, andhas_more/legacy notices with a load-up-to-200 action. The inspector chunk loads on first open and its query unmounts with the dialog.Long-horizon storage boundary
selection_decisionsgrows with the whole history on every turn, so a 1000-turn session made the list endpoint return 42 MB and/statusdetoast a ~1 MB JSONB per poll. This PR moves that cost out of every hot path:context_lifecycles.snapshotis now a bounded summary (about 3 KB per row); the per-fragment audit lives in a newselection_decisionscolumn. Every write statement splits the incoming snapshot at the storage boundary (snapshot - 'selection_decisions'/-> 'selection_decisions') and usesCOALESCEso a summary-only rewrite can never erase an existing audit. Writes return identity columns only, and reconciliation read-backs no longer project the audit.SelectionTracegainstrimmedanddrop_reason_tokens, rolled up when the snapshot is built, so no reader needs the per-fragment audit. Assistant-message metadata copies persist the summary only.0146adds the column, then splits and back-fills existing rows per team (it opens and restores theteamspolicy like0120; it holds an exclusive lock onteamsfor its transaction, so run it with the server stopped as the compose stack does)./statusreads one summary row, the list projects summaries, the per-run detail endpoint is gone, lifecycle queries never persist to localStorage, and a storage quota error pauses cache persistence for the page instead of retrying on every change.Measured on a 1000-turn seeded session on the dev stack:
/status20 ms → 7 ms;/context-lifecycle?limit=5042 MB / 463 ms → 297 KB / 12 ms (Postgres 1,271 shared buffers → 210); inspector open 0.7 to 4.5 s with long tasks → about 100 ms with none.Trade-offs kept
UPDATEon that column.steps/mutationsper row are step-bound, not history-bound.Since opening
origin/mainat 67fe0e6 (fix(compaction): tolerate slow summarizers and show live chat activity #1115, fix(contextview): never trim a compaction summary before newer raw rows #1116, fix(contextview): prune in-flight tool results before failing a protected overflow #1121, feat(compaction): adaptive failure backoff and actionable context budget errors #1122, feat(web): chat 过程展示两层化 —— Adaptive Title 分层 + composer 常驻运行条 #1145). Two conflicts:apps/web/src/store/chat-list.tskeeps both sides' exports (streamingSessionIdsfrom this PR,isSessionCompacting/beginSessionCompactionfrom feat(compaction): adaptive failure backoff and actionable context budget errors #1122), andpackages/sdk/src/index.tswas regenerated from the merged spec. Go build/vet/test and the web checks pass on the merge commit.Test plan
Summary(), handler composition, plan applicability, compaction mark derivation, legacy status fallback, compaction threshold policy.go test -tags integration): query round-trip with the split, summary-only rewrites keep the audit,0146down/up round-trip on a pre-split row incl. the token rollup, migration as a non-superuser database-owner role./statusexactly, noselection_decisionson the wire, the inspector opens without per-turn detail requests, keyboard path ring → popover → inspector → back, turn end refetches once plus one follow-up.QA
mise run dev, open a session with at least one assistant turn.~totals, provider input row, output-reserve band and free space when the window is known.