fix(strategize): propagate actor options (openai_api_base, openai_api_key) to LLM client creation in Strategize/Execute paths #11257

Merged
HAL9000 merged 4 commits from bugfix/m5-actor-options-ignored into master 2026-05-29 00:09:42 +00:00
Member

Summary

Propagates actor-level YAML options (such as openai_api_base and openai_api_key) through to ProviderRegistry.create_llm() in the Strategize and Execute phases, and in session-based workflows.

Previously these options were silently ignored, causing LLM calls to fall back to the real OpenAI internet endpoint instead of the user-configured local backend.

Root Cause

Two independent gaps:

  1. ActorConfigSchema silently dropped options — no options field, and Pydantic v2 defaults to extra='ignore', so options was stripped during validation.

  2. Four call sites never forwarded options to create_llm()StrategyActor._execute_with_llm(), LLMStrategizeActor.execute(), LLMExecuteActor.execute(), and SessionWorkflow._resolve_llm() all created the LLM with zero extra kwargs.

Changes

New shared utility (src/cleveragents/actor/config.py)

  • build_llm_kwargs_from_options() — extracts openai_api_base, openai_api_key (via __api_key_sentinel), and other allowed kwargs from actor options dict
  • _ALLOWED_LLM_OPTIONS / _RESERVED_LLM_OPTIONS constants

Schema fix (src/cleveragents/actor/schema.py)

  • Added options: dict[str, Any] field to ActorConfigSchema

Lifecycle/protocol additions

  • PlanLifecycleService.resolve_actor_options() — retrieves options from actor config_blob
  • Added resolve_actor_options() to LifecycleService protocol (strategy_resolution.py)
  • Added resolve_actor_options() to PlanLifecycleProtocol (llm_actors.py)
  • build_actor_options_resolver() utility in strategy_resolution.py

Fixed 4 call sites

  • StrategyActor._execute_with_llm() — resolves options before calling create_llm()
  • LLMStrategizeActor.execute() — resolves options before calling create_llm()
  • LLMExecuteActor.execute() — resolves options before calling create_llm()
  • SessionWorkflow._resolve_llm() — new actor_options_resolver parameter

Refactored reactive path

  • reactive/stream_router.py and reactive/tool_caller.py now use build_llm_kwargs_from_options() instead of inline copies

Wiring

  • CLI _build_actor_options_resolver() wired to SessionWorkflow
  • A2A facade _build_actor_options_resolver_for_session_workflow() wired

Tests

  • features/actor_options_propagation.feature — 14 BDD scenarios passing

Closes #11256

## Summary Propagates actor-level YAML `options` (such as `openai_api_base` and `openai_api_key`) through to `ProviderRegistry.create_llm()` in the Strategize and Execute phases, and in session-based workflows. Previously these options were silently ignored, causing LLM calls to fall back to the real OpenAI internet endpoint instead of the user-configured local backend. ## Root Cause Two independent gaps: 1. **`ActorConfigSchema` silently dropped `options`** — no `options` field, and Pydantic v2 defaults to `extra='ignore'`, so `options` was stripped during validation. 2. **Four call sites never forwarded options to `create_llm()`** — `StrategyActor._execute_with_llm()`, `LLMStrategizeActor.execute()`, `LLMExecuteActor.execute()`, and `SessionWorkflow._resolve_llm()` all created the LLM with zero extra kwargs. ## Changes ### New shared utility (`src/cleveragents/actor/config.py`) - `build_llm_kwargs_from_options()` — extracts `openai_api_base`, `openai_api_key` (via `__api_key_sentinel`), and other allowed kwargs from actor options dict - `_ALLOWED_LLM_OPTIONS` / `_RESERVED_LLM_OPTIONS` constants ### Schema fix (`src/cleveragents/actor/schema.py`) - Added `options: dict[str, Any]` field to `ActorConfigSchema` ### Lifecycle/protocol additions - `PlanLifecycleService.resolve_actor_options()` — retrieves options from actor `config_blob` - Added `resolve_actor_options()` to `LifecycleService` protocol (strategy_resolution.py) - Added `resolve_actor_options()` to `PlanLifecycleProtocol` (llm_actors.py) - `build_actor_options_resolver()` utility in strategy_resolution.py ### Fixed 4 call sites - `StrategyActor._execute_with_llm()` — resolves options before calling `create_llm()` - `LLMStrategizeActor.execute()` — resolves options before calling `create_llm()` - `LLMExecuteActor.execute()` — resolves options before calling `create_llm()` - `SessionWorkflow._resolve_llm()` — new `actor_options_resolver` parameter ### Refactored reactive path - `reactive/stream_router.py` and `reactive/tool_caller.py` now use `build_llm_kwargs_from_options()` instead of inline copies ### Wiring - CLI `_build_actor_options_resolver()` wired to `SessionWorkflow` - A2A facade `_build_actor_options_resolver_for_session_workflow()` wired ### Tests - `features/actor_options_propagation.feature` — 14 BDD scenarios passing Closes #11256
CoreRasurae added this to the v3.4.0 milestone 2026-05-28 13:40:51 +00:00
fix(strategize): propagate actor options.openai_api_base and options.openai_api_key to LLM client creation in Strategize/Execute paths
Some checks failed
CI / lint (pull_request) Failing after 58s
CI / typecheck (pull_request) Successful in 1m3s
CI / security (pull_request) Successful in 1m11s
CI / unit_tests (pull_request) Has started running
CI / quality (pull_request) Successful in 52s
CI / push-validation (pull_request) Successful in 36s
CI / helm (pull_request) Successful in 37s
CI / build (pull_request) Successful in 44s
CI / integration_tests (pull_request) Failing after 4m27s
CI / coverage (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
CI / status-check (pull_request) Has been cancelled
7a7d3b8121
Extract shared build_llm_kwargs_from_options() utility in actor/config.py
that converts actor-level YAML options (openai_api_base, openai_api_key,
temperature, max_tokens, etc.) into kwargs for ProviderRegistry.create_llm().

Add options field to ActorConfigSchema so it survives Pydantic validation
instead of being silently dropped by extra='ignore' default.

Add resolve_actor_options() to PlanLifecycleService and both LifecycleService
and PlanLifecycleProtocol protocols so every call site can retrieve the
actor's config_blob.options dict.

Fix the four call sites that previously called create_llm() with zero extra
kwargs, preventing local/custom LLM backends from working:

- StrategyActor._execute_with_llm() in strategy_actor.py
- LLMStrategizeActor.execute() in llm_actors.py
- LLMExecuteActor.execute() in llm_actors.py
- SessionWorkflow._resolve_llm() in session_workflow.py

Refactor reactive path (stream_router.py, tool_caller.py) to use the
shared build_llm_kwargs_from_options() utility instead of inline copies.

Wire actor_options_resolver through CLI session builder and A2A facade.

ISSUES CLOSED: #11256
CoreRasurae force-pushed bugfix/m5-actor-options-ignored from 7a7d3b8121
Some checks failed
CI / lint (pull_request) Failing after 58s
CI / typecheck (pull_request) Successful in 1m3s
CI / security (pull_request) Successful in 1m11s
CI / unit_tests (pull_request) Has started running
CI / quality (pull_request) Successful in 52s
CI / push-validation (pull_request) Successful in 36s
CI / helm (pull_request) Successful in 37s
CI / build (pull_request) Successful in 44s
CI / integration_tests (pull_request) Failing after 4m27s
CI / coverage (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
CI / status-check (pull_request) Has been cancelled
to 0ecacf9f22
Some checks failed
CI / lint (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 48s
CI / typecheck (pull_request) Successful in 55s
CI / helm (pull_request) Successful in 27s
CI / build (pull_request) Successful in 45s
CI / security (pull_request) Successful in 1m11s
CI / push-validation (pull_request) Successful in 20s
CI / integration_tests (pull_request) Failing after 2m48s
CI / unit_tests (pull_request) Failing after 5m57s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 5s
2026-05-28 13:48:32 +00:00
Compare
HAL9000 force-pushed bugfix/m5-actor-options-ignored from 0ecacf9f22
Some checks failed
CI / lint (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 48s
CI / typecheck (pull_request) Successful in 55s
CI / helm (pull_request) Successful in 27s
CI / build (pull_request) Successful in 45s
CI / security (pull_request) Successful in 1m11s
CI / push-validation (pull_request) Successful in 20s
CI / integration_tests (pull_request) Failing after 2m48s
CI / unit_tests (pull_request) Failing after 5m57s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 5s
to 9a10e79b02
Some checks failed
CI / push-validation (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 44s
CI / build (pull_request) Successful in 42s
CI / helm (pull_request) Successful in 45s
CI / typecheck (pull_request) Successful in 57s
CI / quality (pull_request) Successful in 59s
CI / security (pull_request) Successful in 1m36s
CI / integration_tests (pull_request) Failing after 3m3s
CI / unit_tests (pull_request) Failing after 5m13s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
2026-05-28 14:02:28 +00:00
Compare
HAL9000 force-pushed bugfix/m5-actor-options-ignored from 9a10e79b02
Some checks failed
CI / push-validation (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 44s
CI / build (pull_request) Successful in 42s
CI / helm (pull_request) Successful in 45s
CI / typecheck (pull_request) Successful in 57s
CI / quality (pull_request) Successful in 59s
CI / security (pull_request) Successful in 1m36s
CI / integration_tests (pull_request) Failing after 3m3s
CI / unit_tests (pull_request) Failing after 5m13s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
to 080e1d5e3f
Some checks failed
CI / push-validation (pull_request) Successful in 31s
CI / helm (pull_request) Successful in 42s
CI / build (pull_request) Successful in 45s
CI / lint (pull_request) Successful in 1m11s
CI / quality (pull_request) Successful in 1m10s
CI / typecheck (pull_request) Successful in 1m22s
CI / security (pull_request) Successful in 1m23s
CI / integration_tests (pull_request) Failing after 3m33s
CI / unit_tests (pull_request) Failing after 6m4s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
2026-05-28 19:46:09 +00:00
Compare
fix(tests): add resolve_actor_options to mock lifecycle helpers
Some checks failed
CI / lint (pull_request) Successful in 49s
CI / typecheck (pull_request) Successful in 1m8s
CI / quality (pull_request) Successful in 1m16s
CI / security (pull_request) Successful in 1m24s
CI / build (pull_request) Successful in 40s
CI / push-validation (pull_request) Successful in 26s
CI / helm (pull_request) Successful in 36s
CI / integration_tests (pull_request) Successful in 3m4s
CI / unit_tests (pull_request) Failing after 5m23s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
4f53c21a7e
The PR added resolve_actor_options() to StrategyActor, LLMStrategizeActor,
and LLMExecuteActor, but the three mock lifecycle SimpleNamespace objects
used by tests did not include this method, causing AttributeError at
runtime:

- features/mocks/mock_strategy_llm.py: make_mock_lifecycle() used by
  robot/helper_strategy_actor.py (strategy_actor.robot llm-json test)
  and features/steps/strategy_actor_llm_steps.py
- features/steps/llm_actors_coverage_steps.py: _make_mock_lifecycle()
  used by llm_actors_coverage.feature scenarios
- robot/helper_m5_e2e_context.py: inline lifecycle SimpleNamespace
  used by m5_e2e_verification.robot Execute Phase LLM Uses ACMS Context

All three now include resolve_actor_options=MagicMock(return_value=None)
matching the None-return contract the actors use when no custom backend
is configured.

ISSUES CLOSED: #11256
fix(actor): top-level config keys take precedence over options block duplicates
All checks were successful
CI / helm (pull_request) Successful in 31s
CI / lint (pull_request) Successful in 35s
CI / build (pull_request) Successful in 33s
CI / push-validation (pull_request) Successful in 32s
CI / security (pull_request) Successful in 1m3s
CI / quality (pull_request) Successful in 1m2s
CI / typecheck (pull_request) Successful in 1m14s
CI / integration_tests (pull_request) Successful in 2m49s
CI / unit_tests (pull_request) Successful in 4m34s
CI / docker (pull_request) Successful in 1m39s
CI / coverage (pull_request) Successful in 11m11s
CI / status-check (pull_request) Successful in 3s
8d9cedce8c
In both ToolCallingLLMCaller._resolve_llm and SimpleLLMAgent._resolve_llm,
the options block was merged after top-level keys were applied, causing
llm_kwargs.update(...) to overwrite top-level temperature/max_tokens/
max_retries with any duplicate values from the options block.

Fix: apply build_llm_kwargs_from_options first, then re-apply the
top-level extracted values so they always win.

ISSUES CLOSED: #11243 #11223
Owner

Claimed by merge_drive.py (pid 1264876) until 2026-05-28T23:05:06.184455+00:00.

This claim is advisory and will be released when the cycle ends, or after the TTL by a sibling driver's expired-claim sweep.

<!-- merge_drive.py: claim --> Claimed by `merge_drive.py` (pid 1264876) until `2026-05-28T23:05:06.184455+00:00`. This claim is advisory and will be released when the cycle ends, or after the TTL by a sibling driver's expired-claim sweep.
HAL9000 force-pushed bugfix/m5-actor-options-ignored from 8d9cedce8c
All checks were successful
CI / helm (pull_request) Successful in 31s
CI / lint (pull_request) Successful in 35s
CI / build (pull_request) Successful in 33s
CI / push-validation (pull_request) Successful in 32s
CI / security (pull_request) Successful in 1m3s
CI / quality (pull_request) Successful in 1m2s
CI / typecheck (pull_request) Successful in 1m14s
CI / integration_tests (pull_request) Successful in 2m49s
CI / unit_tests (pull_request) Successful in 4m34s
CI / docker (pull_request) Successful in 1m39s
CI / coverage (pull_request) Successful in 11m11s
CI / status-check (pull_request) Successful in 3s
to fae4384370
Some checks failed
CI / push-validation (pull_request) Successful in 31s
CI / lint (pull_request) Successful in 34s
CI / quality (pull_request) Successful in 47s
CI / helm (pull_request) Successful in 47s
CI / build (pull_request) Successful in 53s
CI / typecheck (pull_request) Successful in 1m2s
CI / security (pull_request) Successful in 1m2s
CI / integration_tests (pull_request) Successful in 2m49s
CI / unit_tests (pull_request) Successful in 6m36s
CI / docker (pull_request) Failing after 15m28s
CI / coverage (pull_request) Failing after 15m28s
CI / status-check (pull_request) Has been cancelled
2026-05-28 21:35:09 +00:00
Compare
chore: re-trigger CI [controller]
All checks were successful
CI / push-validation (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 29s
CI / lint (pull_request) Successful in 38s
CI / build (pull_request) Successful in 49s
CI / quality (pull_request) Successful in 1m1s
CI / typecheck (pull_request) Successful in 1m21s
CI / security (pull_request) Successful in 1m22s
CI / integration_tests (pull_request) Successful in 4m11s
CI / unit_tests (pull_request) Successful in 4m27s
CI / docker (pull_request) Successful in 1m24s
CI / coverage (pull_request) Successful in 13m0s
CI / status-check (pull_request) Successful in 4s
8cace12f66
Owner

Claimed by merge_drive.py (pid 1264876) until 2026-05-29T01:39:37.590838+00:00.

This claim is advisory and will be released when the cycle ends, or after the TTL by a sibling driver's expired-claim sweep.

<!-- merge_drive.py: claim --> Claimed by `merge_drive.py` (pid 1264876) until `2026-05-29T01:39:37.590838+00:00`. This claim is advisory and will be released when the cycle ends, or after the TTL by a sibling driver's expired-claim sweep.
HAL9001 approved these changes 2026-05-29 00:09:41 +00:00
HAL9001 left a comment

Approved by the controller reviewer stage (workflow 19).

Approved by the controller reviewer stage (workflow 19).
HAL9000 merged commit 0fa82ee6a0 into master 2026-05-29 00:09:42 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core!11257
No description provided.