fix(agent): keep the thought-level picker on a resumed conversation - #763
Merged
Conversation
added 3 commits
August 3, 2026 20:43
Opening an existing claude conversation showed the model list but no
thought-level group. Sending a message did not bring it back; only leaving the
conversation and entering it again did.
A resumed conversation rebuilds its pickers from the persisted catalog while
the backend is still starting, and that catalog stored only `{id, label}` per
model. `resolve_current_model_efforts` therefore found no efforts and
`get_config_options` omitted the `reasoning_effort` option entirely. Nothing
re-publishes config options when the live handshake lands, so the group stayed
missing for the whole session — the second entry only worked because the task
was warm by then and the LIVE capabilities were used instead.
Both ends dropped it:
- `catalog_partial_from_caps` projected id and label only;
- `CatalogPreload::from_handshake` set `reasoning_efforts: Vec::new()`,
documented as intentional because the column did not carry them.
The write side now includes `reasoning_efforts` when a model has any, and the
read side pulls it back out of the raw column — the ACP `SessionModelState`
that the shared parser returns has no effort axis, so it cannot carry it.
Models without an effort axis write no key at all: agy folds effort into the
model id and codex has none, and writing an empty array for them would change
the stored column for every backend in order to fix one. Rows already in the
database predate the field and load as "no efforts", which is what they meant.
Present since #609 (2026-07-23) moved claude/codex onto the direct-CLI path —
not related to the Antigravity work, which projects no efforts at all.
Removing the write-side projection fails only the round-trip test; the other
two still pass, so the set neither passes vacuously nor over-reaches.
…ss conversations
`messages.id` is unique across the whole database, but agy's `step_index` only
counts within one agy conversation. The bare `step-<n>` id therefore collided
as soon as a second conversation reached the same index, and the write was
rejected:
Duplicate record: Message with id 'step-6' already exists outside the
requested conversation
52 of these in one log — 42 `run_command`, 4 `write_to_file`, 2 `view_file`,
2 `list_dir`, 2 subagent. Each one is a tool card that never reached the
database: the FIRST conversation to use a given step number keeps its card and
every later conversation silently loses that step.
The module header's premise was the wrong half of the truth: `step_index` is
indeed stable within a conversation and across a `--conversation` resume, but
that is not the same as globally unique, which is what the id needs to be. Ids
are now prefixed with agy's own conversation id.
Found while checking why the new subagent card did not appear — the pump was
emitting ToolCall/ToolResult correctly and the failure was one layer further
down. The bug predates that work and affects ordinary tools far more.
The test drives two Translators bound to different conversations, which is why
none of the existing tests could see this: every one of them ran a single
Translator against a single session.
Three gaps on the same axis, each invisible on its own.
**The new-conversation screen had no effort control.** Its picker is built from
`config_options` alone — `buildAgentRuntimeThoughtLevelOption` looks up category
`thought_level` and, unlike mode, has no fallback to a top-level column — and
`catalog_partial_from_caps` projected mode and model only. The catalog now
carries an effort select for any backend whose models advertise efforts.
`currentValue` is deliberately left null. `caps.current_effort` means "the level
THIS session last set" (claude remembers it because the CLI never echoes effort
back; codex does not track it at all). This catalog is agent-level and shared by
every conversation, so writing a session's level into it would make one
conversation's choice everybody's default. The picker offers the levels; the
chosen one travels per-conversation.
**codex lost its effort on every rebuild.** The replay was gated on
`backend_label == "claude"`, justified by a comment saying codex effort "rides
collaborationMode via SetMode". That is false: `codex_conn` accepts
`SetConfigOption{effort|reasoning_effort|thought_level}` and writes
`thread/settings/update {"effort":…}`. Confirmed live through the HTTP
config-options endpoint, and codex's own rollout log records
`thread_settings_applied` with `"effort":"high"`. So codex persisted an effort
under `EFFORT_CONFIG_KEY` and then silently dropped it.
**An effort chosen at creation was ignored.** `extra.thought_level` has been
carried from the new-conversation screen all along and nothing ever read it.
Resolution now falls back to it, the same snapshot-over-seed precedence `mode`
and `model` already use.
The resolution is extracted as `resolved_effort` so the precedence is testable
without standing up a backend — mirroring `resolved_session_mode`.
Verification of the axis itself, from files the CLIs write and this code does
not touch: codex `~/.codex/sessions/…/rollout-*.jsonl` →
`thread_settings_applied.thread_settings.effort`; claude
`~/.claude/projects/<workspace>/<sessionId>.jsonl` → the `effort` field on
`type:"assistant"` records, which is the level a given reply actually ran at.
kaizhou-lab
pushed a commit
that referenced
this pull request
Aug 4, 2026
🤖 I have created a release *beep* *boop* --- ## [0.1.58](v0.1.57...v0.1.58) (2026-08-04) ### Features * **conversation:** agent-driven session auto-naming (ACP session_info_update + claude generate_session_title) ([#768](#768)) ([c551c66](c551c66)) * **conversation:** fork a conversation into a new one at a chosen message ([#772](#772)) ([bb7c14e](bb7c14e)) * **fs:** add ChatFileRef content endpoints ([#757](#757)) ([e8b6f4c](e8b6f4c)) * **fs:** pdf stream endpoint + office ChatFileRef resolve + retire fs/resolve & WS fs/read ([#762](#762)) ([e4e991a](e4e991a)) ### Bug Fixes * **agent:** keep the thought-level picker on a resumed conversation ([#763](#763)) ([dcf015e](dcf015e)) * **runtime:** add bounded retry to managed node version probe ([#771](#771)) ([1dee9f1](1dee9f1)) * **session:** keep claude session cost cumulative across process respawns ([#767](#767)) ([f498e9d](f498e9d)) * **session:** settle cards through teardown and resume so no stored row spins forever ([#766](#766)) ([3f61cc4](3f61cc4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
acetaxxxx
pushed a commit
to acetaxxxx/AionCore
that referenced
this pull request
Sep 8, 2026
…OfficeAI#763) ## Symptom Opening an existing claude conversation shows the model list but **no thought-level group**. Sending a message does not bring it back. Leaving the conversation and entering it again does. ## Cause A resumed conversation rebuilds its pickers from the **persisted catalog** while the backend is still starting. That catalog stored only `{id, label}` per model: ```json {"available_models":[{"id":"opus[1m]","label":"Opus (1M context)"}, ...], "current_model_id":"opus[1m]"} ``` So `resolve_current_model_efforts` found nothing and `get_config_options` omitted the `reasoning_effort` option entirely. Nothing re-publishes config options when the live handshake lands, so it stayed missing for the whole session. The second entry worked only because the task was warm by then and the LIVE capabilities were used instead of the preload. Both ends dropped the field: - `catalog_partial_from_caps` projected `id` and `label` only; - `CatalogPreload::from_handshake` set `reasoning_efforts: Vec::new()`, documented as intentional *because the column did not carry them* — a self-consistent pair that hid the gap. ## Fix Write side includes `reasoning_efforts` when a model has any. Read side pulls it back out of the raw column: the shared ACP parser returns a `SessionModelState` with no effort axis, so it cannot carry it through. Models **without** an effort axis write no key at all. agy folds effort into the model id (`gemini-3.6-flash-high` / `-low`) and codex has none; writing an empty array for them would change the stored column for every backend in order to fix one. Rows already in the database predate the field and load as "no efforts", which is what they meant. ## When it appeared `iOfficeAI#609` (2026-07-23), which moved claude/codex onto the direct-CLI `SessionAgentTask` and introduced `CatalogPreload`. **Not** related to the Antigravity work (2026-07-31 onward) — that path projects no efforts at all, and its catalog write-back only touches mode/model/slash-commands. ## Tests - efforts survive the write → read round trip, and feed `resolve_current_model_efforts` - a model with no efforts writes no key (guards every other backend's column) - a catalog written before the field still loads Removing the write-side projection fails only the first, so the set neither passes vacuously nor over-reaches. --------- Co-authored-by: zk <>
acetaxxxx
pushed a commit
to acetaxxxx/AionCore
that referenced
this pull request
Sep 8, 2026
🤖 I have created a release *beep* *boop* --- ## [0.1.58](iOfficeAI/AionCore@v0.1.57...v0.1.58) (2026-08-04) ### Features * **conversation:** agent-driven session auto-naming (ACP session_info_update + claude generate_session_title) ([iOfficeAI#768](iOfficeAI#768)) ([c551c66](iOfficeAI@c551c66)) * **conversation:** fork a conversation into a new one at a chosen message ([iOfficeAI#772](iOfficeAI#772)) ([bb7c14e](iOfficeAI@bb7c14e)) * **fs:** add ChatFileRef content endpoints ([iOfficeAI#757](iOfficeAI#757)) ([e8b6f4c](iOfficeAI@e8b6f4c)) * **fs:** pdf stream endpoint + office ChatFileRef resolve + retire fs/resolve & WS fs/read ([iOfficeAI#762](iOfficeAI#762)) ([e4e991a](iOfficeAI@e4e991a)) ### Bug Fixes * **agent:** keep the thought-level picker on a resumed conversation ([iOfficeAI#763](iOfficeAI#763)) ([dcf015e](iOfficeAI@dcf015e)) * **runtime:** add bounded retry to managed node version probe ([iOfficeAI#771](iOfficeAI#771)) ([1dee9f1](iOfficeAI@1dee9f1)) * **session:** keep claude session cost cumulative across process respawns ([iOfficeAI#767](iOfficeAI#767)) ([f498e9d](iOfficeAI@f498e9d)) * **session:** settle cards through teardown and resume so no stored row spins forever ([iOfficeAI#766](iOfficeAI#766)) ([3f61cc4](iOfficeAI@3f61cc4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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.
Symptom
Opening an existing claude conversation shows the model list but no
thought-level group. Sending a message does not bring it back. Leaving the
conversation and entering it again does.
Cause
A resumed conversation rebuilds its pickers from the persisted catalog while
the backend is still starting. That catalog stored only
{id, label}per model:{"available_models":[{"id":"opus[1m]","label":"Opus (1M context)"}, ...], "current_model_id":"opus[1m]"}So
resolve_current_model_effortsfound nothing andget_config_optionsomitted the
reasoning_effortoption entirely. Nothing re-publishes configoptions when the live handshake lands, so it stayed missing for the whole
session. The second entry worked only because the task was warm by then and the
LIVE capabilities were used instead of the preload.
Both ends dropped the field:
catalog_partial_from_capsprojectedidandlabelonly;CatalogPreload::from_handshakesetreasoning_efforts: Vec::new(),documented as intentional because the column did not carry them — a
self-consistent pair that hid the gap.
Fix
Write side includes
reasoning_effortswhen a model has any. Read side pulls itback out of the raw column: the shared ACP parser returns a
SessionModelStatewith no effort axis, so it cannot carry it through.
Models without an effort axis write no key at all. agy folds effort into the
model id (
gemini-3.6-flash-high/-low) and codex has none; writing an emptyarray for them would change the stored column for every backend in order to fix
one. Rows already in the database predate the field and load as "no efforts",
which is what they meant.
When it appeared
#609(2026-07-23), which moved claude/codex onto the direct-CLISessionAgentTaskand introducedCatalogPreload. Not related to theAntigravity work (2026-07-31 onward) — that path projects no efforts at all, and
its catalog write-back only touches mode/model/slash-commands.
Tests
resolve_current_model_effortsRemoving the write-side projection fails only the first, so the set neither
passes vacuously nor over-reaches.