Skip to content

fix(agent): keep the thought-level picker on a resumed conversation - #763

Merged
kaizhou-lab merged 3 commits into
mainfrom
fix/cold-start-thought-level
Aug 3, 2026
Merged

fix(agent): keep the thought-level picker on a resumed conversation#763
kaizhou-lab merged 3 commits into
mainfrom
fix/cold-start-thought-level

Conversation

@kaizhou-lab

Copy link
Copy Markdown
Contributor

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_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

#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.

zk 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
kaizhou-lab merged commit dcf015e into main Aug 3, 2026
6 checks passed
@kaizhou-lab
kaizhou-lab deleted the fix/cold-start-thought-level branch August 3, 2026 13:36
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>
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