Files
buzz/crates
Duncan 90d7e0df14 fix(pool): tri-state effort capability model survives live model switches
Replace the populate-once PoolEffortCapabilities cache + capabilities_ever_discovered
bool pair with a three-state EffortCapabilityState enum:

  Unknown  — no session has returned capabilities yet (pre-first-session window,
             or capabilities reset after a model switch)
  Supported { config_id, valid_values }  — current model advertises thought_level
  Unsupported  — current model confirmed to not have thought_level

The old model populated capabilities once (on the first session/new) and never
refreshed them. After a live model switch — which resets agent.model_capabilities to
None to discard the pre-switch snapshot — return_agent wrote Unknown/kept stale state,
so handle_set_config_option_control kept validating against the old model's option set
and session_config_captured emitted pre-switch configOptions to Desktop.

Changes:

pool.rs:
- EffortCapabilityState enum replaces PoolEffortCapabilities + capabilities_ever_discovered
- return_agent derives tri-state from the returning worker's model_capabilities on
  every return (not just the first), so the pool state always reflects the current model
- AgentPool initialises with Unknown; struct fields updated accordingly

pool.rs (create_session_and_apply_model):
- Capabilities are populated from EVERY session/new response, not just when None
- After a successful apply_model_switch, agent.model_capabilities is reset to None so
  return_agent writes Unknown rather than stale-Supported

pool.rs (session_config_captured):
- On the switch path, configOptions emitted as null so Desktop clears its picker
  state rather than caching pre-switch capability data

lib.rs (handle_set_config_option_control):
- Four-way A/B/C/D classifier replaced with a three-way tri-state dispatch
- Supported(config_id matches) → validate + store (case A)
- Supported(config_id no-match) → synthetic ok, non-effort (case B)
- Unsupported → reject with unsupported_model status (eliminates case-C
  occupancy inference: capability loss is now surfaced immediately)
- Unknown → pre-discovery trust path: category=thought_level stores + pending_session

pool.rs field comment:
- last_acked_effort_gen comment updated to name resolve_effort_report as the sole
  ack authority (Thufir Pass 3 non-blocking MINOR)

Tests (lib.rs control_result_tests):
- pool_with_capabilities_via_return helper drives the PRODUCTION return_agent path,
  not the test-only notify_capabilities_discovered shortcut
- test_tristate_switch_effort_to_no_effort_rejects_pick: effort→no-effort switch
  writes Unsupported; subsequent pick is rejected with unsupported_model status
- test_tristate_switch_no_effort_to_effort_accepts_pick: no-effort→effort switch
  writes Supported with new configId; pick validates and stores correctly
- test_tristate_switch_new_options_validates_against_new_snapshot: both models
  Supported with different option sets; stale value rejected, new value accepted
- test_tristate_unknown_after_switch_uses_pre_discovery_trust_path: capabilities=None
  on return writes Unknown; pre-discovery category trust path remains active

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-10 13:37:00 -04:00
..