Skip to content

ChatWorkflowManager._current_lightweight_mode is shared instance state — move onto LLMIterationContext #11216

Closed

Description

@mrveiss

Discovered during #11207 PR2 review (PR #11215, internal delegation engine).

`ChatWorkflowManager._current_lightweight_mode` is set on the shared singleton instance at entry to `_execute_llm_continuation_loop` (manager.py:2646), reset in `finally` (2665), and read during streaming (manager.py:579). Because the manager is a `lazy_singleton`, any two concurrent drivers of the loop (e.g. a real lightweight chat + the internal delegation subagent) can interleave entry/reset and momentarily tag streamed messages with the wrong `lightweight_mode_used` cost-indicator flag.

Impact: cosmetic only (a stream-metadata badge) — `self.sessions`/execution history are per-ctx, so no state corruption. Latent today; only reachable once delegation is enabled (`AUTOBOT_DELEGATION_ENABLED`, OFF by default) and run concurrently with a lightweight real chat.

Fix: move `lightweight_mode_used` off the instance and onto `LLMIterationContext` (it is already sourced from `ctx.context.get("lightweight_mode_used")`), and have the streaming read at manager.py:579 take it from ctx. Removes the shared-instance-var pattern entirely.

Pre-existing pattern (not introduced by #11215); documented inline in `chat_workflow/delegation.py::_run_internal_subagent`.

Activity

  1. mrveiss commented on Jul 8, 2026

    @mrveiss
    OwnerAuthor

    Closed — merged to Dev_new_gui as 9c9d536 (PR #11243).

    Fix: replaced the shared-singleton self._current_lightweight_mode with a module-level task-local ContextVar (_current_lightweight_mode): set(token) at loop entry, reset(token) in finally, .get() at the streaming read. Each asyncio task carries its own value, so concurrent drivers (a real lightweight chat + the internal delegation subagent) no longer clobber each other's lightweight_mode_used badge; token-based reset also restores the caller's value for nested/inline drives (stronger than the old reset-to-False).

    Chose the ContextVar over the suggested LLMIterationContext field because the read site (_init_streaming_message, via the un-threaded type-transition path) has neither the flag nor ctx — threading it would touch 5+ methods of the streaming hot path. The ContextVar removes the shared-instance-var pattern entirely with a 4-site change and no hot-path plumbing.

    Verification: new tests/unit/chat_workflow/test_lightweight_mode_isolation.py (3 passed) proves two concurrent drivers each see their own badge — the assertion fails under the old shared-instance implementation. py_compile + black + isort clean; startup-import-smoke + code-quality + SAST green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions