Skip to content

fix(dashboard): reset model state on agent switch - #9115

Open
Premshay wants to merge 1 commit into
kirodotdev:mainfrom
Premshay:upstream/dashboard-agent-model-reset
Open

fix(dashboard): reset model state on agent switch#9115
Premshay wants to merge 1 commit into
kirodotdev:mainfrom
Premshay:upstream/dashboard-agent-model-reset

Conversation

@Premshay

@Premshay Premshay commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Switching a dashboard slot to another agent retained the previous agent's model pin and fallback recovery state. A replacement session could therefore send a model identifier that belongs to the old binding.

Why it matters

The next turn can start with an incompatible model instead of the target agent's default configuration.

What changed (motivation → approach → change)

Agent switches already reset their session before the next turn. The transaction now joins the existing model-pick lock, clears model and fallback state only when the agent changes, persists the blank model, and restores the prior state only for a proven pre-pop rollback. Same-agent resets leave model state unchanged.

Tests

  • Added coverage for clearing and persisting model/fallback state on an agent change.
  • Added coverage for preserving model state on a same-agent reset and restoring it after a pre-pop reset failure.
  • �[1m============================= test session starts ==============================�[0m
    platform linux -- Python 3.14.4, pytest-8.4.2, pluggy-1.6.0
    rootdir: /home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset
    configfile: setup.cfg
    plugins: xdist-3.8.0, cov-6.3.0, timeout-2.4.0, hypothesis-6.165.2, asyncio-1.4.0, testmon-2.2.0
    timeout: 120.0s
    timeout method: signal
    timeout func_only: False
    asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
    created: 13/13 workers
    13 workers [769 items]

�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 9%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 18%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 28%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 37%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 46%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 56%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 65%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 74%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 84%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [ 93%]
�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[32m.�[0m�[33m [100%]�[0m
�[33m=============================== warnings summary ===============================�[0m
../../KiroCrew/.venv/lib/python3.14/site-packages/xdist/plugin.py:289: 14 warnings
/home/prems/dev/repos/premshay/KiroCrew/.venv/lib/python3.14/site-packages/xdist/plugin.py:289: DeprecationWarning: The --looponfail command line argument and looponfailroots config variable are deprecated.
The loop-on-fail feature will be removed in pytest-xdist 4.0.
config.issue_config_time_warning(warning, 2)

test/test_dashboard_chat.py: 263 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/chat_test_helpers.py:231: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py: 13 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/chat_test_helpers.py:225: NotAppKeyWarning: It is recommended to use web.RequestKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#request-s-storage
request["app"] = "" # dashboard user, not an app

test/test_dashboard_chat.py: 13 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/chat_test_helpers.py:227: NotAppKeyWarning: It is recommended to use web.RequestKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#request-s-storage
request["user"] = "local-app" # recognized as owner

test/test_dashboard_chat.py: 96 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/src/kiro_crew/dashboard/chat_runner.py:2127: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
report = _session_mcp_report(provider)
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py: 71 warnings
/home/prems/dev/repos/premshay/KiroCrew/.venv/lib/python3.14/site-packages/_pytest/stash.py:108: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
del self._storage[key]
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py: 26 warnings
/usr/lib/python3.14/unittest/mock.py:2259: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
def init(self, name, parent):
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py: 12 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/src/kiro_crew/dashboard/handlers/usage.py:1049: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
return (int(used_fn()), int(window_fn()))
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py: 30 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/chat_test_helpers.py:267: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py::TestPythonStageLoop::test_queued_receipt_carries_the_entry_queue_id
/usr/lib/python3.14/contextlib.py:450: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
def exit(self, exctype, excinst, exctb):
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py: 61 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/chat_test_helpers.py:294: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py::TestRegenerateAndVariants::test_edit_resend_non_dict_body_is_400_not_500
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:13526: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py::TestRegenerateAndVariants::test_restore_preserves_variants
test/test_dashboard_chat.py::TestForkSlot::test_fork_copies_all_messages
/usr/lib/python3.14/collections/init.py:455: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
result = tuple_new(cls, iterable)
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestForkSlot::test_fork_with_prompt
/usr/lib/python3.14/unittest/mock.py:2608: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
return tuple.new(cls, (name, args, kwargs))
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestForkSlot::test_fork_reads_full_history_from_disk_when_memory_capped
test/test_dashboard_chat.py::TestBulkModelSwitch::test_switches_all_differing_slots_and_resets_each
:2: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestStopReasonCancelled::test_monitor_wake_turn_does_not_write_user_memory
/usr/lib/python3.14/threading.py:290: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
def init(self, lock=None):
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestStopReasonCancelled::test_cancelled_turn_restores_the_reinjection_flag
:53: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestStopReasonCancelled::test_cancelled_turn_restores_the_reinjection_flag
test/test_dashboard_chat.py::TestStopReasonCancelled::test_landed_turn_does_not_restore_the_reinjection_flag
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/src/kiro_crew/dashboard/chat_runner.py:7179: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
model_window = window_for_provider_client(client)
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestStopTurnSlotState::test_stop_turn_slot_state_transitions_soft
/usr/lib/python3.14/unittest/mock.py:455: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
if not issubclass(cls, AsyncMockMixin):
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestRunChatTransientRetry::test_first_exhausted_cycle_never_discards
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_canary_empty_reply_is_not_positive_evidence
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_stop_during_canary_vetoes_discard_and_requeue
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_transient_budget_refreshed_after_terminal_error
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_poisoned_discard_is_one_shot_until_a_turn_lands
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_unreadable_session_model_skips_canary_and_discard
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_thinking_only_failures_never_accrue_discard_streak
test/test_dashboard_chat.py::TestRunChatTransientRetry::test_canary_failure_blocks_discard_and_preserves_one_shot
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/src/kiro_crew/llm_helpers.py:610: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
return advertised_model_ids(getter())
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestStopTurnSlotState::test_single_press_empty_queue_goes_idle
/usr/lib/python3.14/unittest/mock.py:460: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
new = type(cls.name, bases, {'doc': cls.doc})
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestRunChatTransientRetry::test_poisoned_conversation_discarded_on_second_exhausted_cycle
/usr/lib/python3.14/unittest/mock.py:430: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
def init(self, /, *args, **kwargs):
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestRunChatTransientRetry::test_poisoned_conversation_discarded_on_second_exhausted_cycle
/usr/lib/python3.14/json/encoder.py:263: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
return _iterencode(o, 0)
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestRunChatTransientRetry::test_cancelled_turn_does_not_rearm_poisoned_one_shot
/usr/lib/python3.14/ast.py:46: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
return compile(source, filename, mode, flags,
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestRunChatTransientRetry::test_posttoken_suppressed_leaves_allowance_for_later_turn
/usr/lib/python3.14/unittest/mock.py:496: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
dict['_mock_mock_calls'] = _CallList()
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestStopTurnSlotState::test_stop_event_replace_in_place
/usr/lib/python3.14/asyncio/selector_events.py:673: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
def _sock_write_done(self, fd, fut, handle=None):
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

test/test_dashboard_chat.py::TestBulkModelSwitch::test_switches_all_differing_slots_and_resets_each
test/test_dashboard_chat.py::TestBulkModelSwitch::test_already_on_target_is_unchanged_not_reset
test/test_dashboard_chat.py::TestBulkModelSwitch::test_reset_failure_is_isolated_and_reported
test/test_dashboard_chat.py::TestBulkModelSwitch::test_skips_running_slot_by_default
test/test_dashboard_chat.py::TestBulkModelSwitch::test_skip_running_false_forces_running_slot
test/test_dashboard_chat.py::TestBulkModelSwitch::test_app_caller_only_switches_own_slots
test/test_dashboard_chat.py::TestBulkModelSwitch::test_non_boolean_skip_running_is_rejected
test/test_dashboard_chat.py::TestBulkModelSwitch::test_canonical_key_rejected_no_slot_touched
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:19088: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py::TestSlotsGetWarmsGitLabAllowlist::test_cold_get_returns_the_authorized_self_hosted_link
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:19007: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py::TestSlotModelGuard::test_canonical_key_rejected_slot_unchanged
test/test_dashboard_chat.py::TestSlotModelGuard::test_valid_kiro_alias_is_allowed
test/test_dashboard_chat.py::TestSlotModelGuard::test_auto_literal_is_allowed
test/test_dashboard_chat.py::TestSlotModelGuard::test_empty_string_auto_default_is_allowed
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:19323: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py: 25 warnings
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:19406: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app["state"] = state

test/test_dashboard_chat.py::TestBulkModelSwitch::test_falsy_non_string_marker_fails_closed
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:19270: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
app_obj["state"] = state

test/test_dashboard_chat.py::TestBulkModelSwitch::test_missing_auth_marker_is_denied
/home/prems/dev/repos/premshay/.worktrees/kc-upstream-dashboard-agent-model-reset/test/test_dashboard_chat.py:19243: NotAppKeyWarning: It is recommended to use web.AppKey instances for keys.
https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
bare_app["state"] = state

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
============================= slowest 5 durations ==============================
1.00s call test/test_dashboard_chat.py::TestOrchestratorPlanGateArming::test_stage_loop_times_out_a_stage_that_swallows_cancellation
0.45s call test/test_dashboard_chat.py::TestFullPaginationWalk::test_walk_all_pages
0.39s call test/test_dashboard_chat.py::TestSlotModelLiveSwitch::test_refused_pick_rollback_spares_a_newer_concurrent_pick
0.39s call test/test_dashboard_chat.py::TestSlotModelLiveSwitch::test_two_refused_picks_leave_the_slot_untouched
0.38s call test/test_dashboard_chat.py::TestResumeDedupe::test_resume_from_disk_sends_the_older_history_cursor
�[33m====================== �[32m769 passed�[0m, �[33m�[1m665 warnings�[0m�[33m in 10.78s�[0m�[33m ======================�[0m — 769 passed.

  • Black, isort, flake8, diff validation, and documentation lint passed.

Manual verification

N/A — handler transaction, persistence, and rollback paths are covered by the dashboard suite.

Related Issues

N/A — identified during reconciliation of the fork's portable dashboard fixes.

Pattern harvest

Rule candidate: review-prompt

Pattern: Any binding switch that retains provider-specific state must clear or revalidate that state before the next provider session starts.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

From https://github.com/kirodotdev/KiroCrew

@Premshay
Premshay requested a review from a team as a code owner September 6, 2026 20:18
@Premshay
Premshay requested a review from iamwhatever September 6, 2026 20:18
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5.1, fork) — ✅ PASS

Design-level review of f481b7097338d01d51ba722eeeaaf937df9601bb via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design is sound. Lock ordering (slot._lock → session lock → _model_pick_lock) matches the existing model handler, rollback is symmetric and identity-scoped, both local and remote paths are covered with tests, and the spec is updated in the same commit.

The change targets a real problem: on an agent switch the slot kept the old agent's provider-specific model id and fallback recovery state, which the replacement session would then send — producing a failed or wrong-model turn. Clearing to "" (provider default) is the proportionate fix; preserving the model only when providers happen to match would add resolution complexity for no guaranteed validity. Contract evolution is safe (persisted model="" resumes on default), and the failure path rolls back on commit-token identity so concurrent explicit picks keep their win.

Design-Verdict: PASS

Clearing provider-bound model/fallback state on agent switch is the right fix at the right layer, with rollback and lock discipline consistent with the existing model handler.

[DESIGN-REVIEWED] f481b70

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — 🔴 changes requested (blocking)

Reviewed f481b7097338d01d51ba722eeeaaf937df9601bb via the fork AI-review pipeline; updated in place on each push.

1 of 1 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands.

BLOCKING -- src/kiro_crew/dashboard/chat_handlers.py:5684 -- Model reset occurs after an await (origin: validation)
if agent_changed: slot.model = _CommitToken("")
Agent switch + concurrent send during binding resolution -> _run_chat reads the old model -> replacement session starts with an incompatible model.
Anchor: residual/crash-data-loss-corruption
Fix: Clear model/fallback state alongside slot.agent, before the first await, retaining rollback tokens.
[BLOCK-MERGE] f481b70
[GPT-REVIEWED] f481b70

Adjudication (Fable 5.1) — is blocking on each finding proportionate?

API Error: 400 Claude Code 2.1.240 does not support this model; version 2.1.255 or newer is required. Run 'claude update', or update the Claude desktop app, then try again.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed f481b7097338d01d51ba722eeeaaf937df9601bb via the fork AI-review pipeline; updated in place on each push.

Review details

Both candidates are self-rated low-confidence, and both fail the falsification bar.

Candidate 2 (same-agent write of model to metadata) requires slot.model="" while transcript metadata carries a stale non-empty "X". But model is a SLOT_OWNED_META_KEY (history.py:176) reconstructed from slot.model by the periodic slot save (chat_persistence.py:2783), and no other writer ever puts model into transcript metadata — the model handler (api_chat_slot_model) never calls update_metadata. Metadata therefore tracks slot.model; the divergence the candidate needs does not occur in practice, and writing str(slot.model) on a same-agent switch converges metadata to the authoritative slot value rather than corrupting it. No observable wrong outcome. Dies.

Candidate 1 (agent rollback restores OLD agent while a broken model-token identity leaves a NEW-agent model) requires a concurrent unlocked slot.model write during the failing reset await. The switch now holds slot._model_pick_lock for its whole body (chat_handlers.py:5599), the same lock every model-pick transaction and the fallback-recovery swap take (chat_runner.py:1112, chat_handlers.py:6200/6849); the pre-turn busy/skip_if_busy guards exclude an in-flight turn. The candidate's own (a) is stated as "could"/"if", and the identity-token design deliberately treats a concurrent writer's value as truth — so standing the model rollback down is intended. Fails concrete input; dies.

Nothing grounded to add in Step 2.

No findings.

[OPUS-REVIEWED] f481b70

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5.1, fork) — 🟡 CONCERNS

Premise-level review of f481b7097338d01d51ba722eeeaaf937df9601bb via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

First-Principles-Verdict: CONCERNS

A real, well-targeted fix, but the same root cause (agent changes → session reset → stale provider-bound model) is left unfixed at the in-turn /agent switch in chat_runner.

What this change ships

Intent: after switching a dashboard slot to a different agent, the next turn starts on the new agent's default model, not an incompatible model id from the old agent's provider. FIX.

  1. Local agent switch to a different agent clears model pin + fallback recovery state before reset — justified.
  2. Blank model persisted to conversation metadata so a post-restart resume doesn't revert — justified (consumed by chat_persistence.py:942).
  3. Remote/peer agent switch clears + persists the same in its mirror after acceptance — justified.
  4. Same-agent reset leaves model state untouched — justified guard against over-clearing.
  5. Pre-pop reset failure rolls model/fallback state back to prior — justified (mirrors existing _rollback_switch).
  6. api_chat_slot_agent now takes a third lock (_model_pick_lock) — justified (serializes vs the model-pick/bulk handlers that mutate _model_pick_gen).

Watch

  • Counted unfixed sibling of the root cause. Grepped slot.agent = across dashboard/: 2 agent-change-with-reset sites are fixed here; chat_runner.py:9660 (the in-turn kiro-cli /agent directive) sets slot.agent = new_agent and needs_session_reset = True but never clears model/fallback state — so an in-turn switch resumes the new agent on the old provider's pinned model (slot.model is re-sent as a set_model override per state.py:3737), the exact defect this PR fixes. openai_compat.py:552 is a weaker candidate. A shared clear covering the in-turn path is the general fix; deferring it is acceptable, but it should be a named follow-up, not silent.

[FIRST-PRINCIPLES-REVIEWED] f481b70

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 6, 2026
A slot model and its fallback recovery state can name the prior agent's provider. Clear that binding before resetting the session so the replacement starts from its own defaults.
@Premshay
Premshay force-pushed the upstream/dashboard-agent-model-reset branch from 9f946b2 to f481b70 Compare September 7, 2026 09:17
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant