Files
cleveragents-core/features/consolidated_routing.feature
T
hurui200320 b3851693c8
CI / push-validation (push) Successful in 38s
CI / helm (push) Successful in 49s
CI / lint (push) Successful in 1m20s
CI / build (push) Successful in 1m13s
CI / quality (push) Successful in 1m38s
CI / typecheck (push) Successful in 2m6s
CI / security (push) Successful in 2m5s
CI / benchmark-regression (push) Failing after 1m3s
CI / e2e_tests (push) Successful in 57s
CI / integration_tests (push) Successful in 4m37s
CI / unit_tests (push) Successful in 7m0s
CI / docker (push) Successful in 1m31s
CI / coverage (push) Successful in 13m59s
CI / status-check (push) Successful in 2s
CI / benchmark-publish (push) Successful in 1h37m21s
fix(reactive): forward actor options block to LLM constructor for custom backend support
Two code paths in the reactive actor run pipeline silently discarded the
options: block from v3 actor YAML, preventing custom OpenAI-compatible
backends (llama.cpp, Ollama, etc.) from being used.

Review fixes applied:

- Fix 1: Relabeled issue #11223 from Type/Task to Type/Bug; added
  @tdd_issue/@tdd_issue_11223 tags to all 5 Behave scenarios.
- Fix 2: openai_api_key in options now routes through the registry's
  __api_key_sentinel mechanism so user-provided keys correctly override
  environment defaults. (stream_router.py)
- Fix 3: type: graph actors now propagate actor-level options to
  individual node configs via setdefault. (config_parser.py)
- Fix 4: Options keys are validated against an explicit allowlist;
  reserved keys (provider_type, model_id) are excluded; unrecognized
  keys log a WARNING instead of being silently forwarded. (stream_router.py)
- Fix 5: Updated _build_from_v3 docstring to list options as a
  propagated field. (config_parser.py)
- Fix 6: Removed inconsistent and options_raw emptiness guard; empty
  options dicts are now preserved consistently. (config_parser.py)
- Fix 7: Reserved keys provider_type and model_id are excluded from
  the options merge loop to prevent TypeError. (stream_router.py)
- Fix 8: Added Behave scenario verifying top-level temperature takes
  precedence over options duplicate. (consolidated_routing.feature + steps)
- Fix 9: Strengthened "no extra kwargs" assertion to assert kwargs == {}
  directly instead of using an allow-list filter. (stream_router steps)
- Fix 10: Strengthened options assertion to exact dict equality.
  (actor_v3_schema_extended_steps.py)
- N1: Comment style aligned to M5: prefix convention.
- N2: Type annotations changed from Any to Context (behave.runner).
- N3: Added Behave scenario for empty options: {} dict behavior.

Tests: 5 new Behave scenarios (3 in actor_v3_schema.feature, 2 in
consolidated_routing.feature) with @tdd_issue/@tdd_issue_11223 tags.

ISSUES CLOSED: #11223
2026-05-16 13:38:54 +00:00

1403 lines
57 KiB
Gherkin

Feature: Consolidated Routing
Combined scenarios from: message_router_new_coverage, reactive_registry_adapter_coverage, reactive_routing_port, route_bridge_coverage, route_bridge_uncovered_lines, route_coverage_comprehensive, routing_adapter_coverage, routing_langgraph_port, stream_router_additional_coverage, stream_router_agent_tool_coverage, stream_router_coverage, stream_router_misc_uncovered, stream_router_new_branches, stream_router_remaining_coverage, stream_router_uncovered_paths, stream_router_unsafe_and_llm_coverage
# ============================================================
# Originally from: message_router_new_coverage.feature
# Feature: Message router new coverage
# ============================================================
Scenario: build_message_router_node creates NodeConfig with correct type
When I build a message router node named "router1" with rules
Then the node name should be "router1"
And the node type should be MESSAGE_ROUTER
And the node metadata should contain rules
Scenario: build_message_router_edges creates edges from rules
When I build message router edges for "router1" with two target rules
Then the edge count should be 3
And the last edge target should be "end"
Scenario: build_message_router_edges handles empty rules
When I build message router edges for "router1" with no rules
Then the edge count should be 1
And the last edge target should be "end"
Scenario: build_message_router_edges skips rules without target
When I build message router edges for "router1" with a rule missing target
Then the edge count should be 1
# ============================================================
# Originally from: reactive_registry_adapter_coverage.feature
# Feature: Reactive registry adapter coverage
# ============================================================
Scenario: Registry list failure propagates to caller
Given a reactive stream router and route bridge
And an actor registry that raises on list
When I attempt to register registry actors with the adapter
Then a RuntimeError is raised
Scenario: Registers only actors with processing methods
Given a reactive stream router and route bridge
And an actor registry with mixed capabilities
When I register registry actors with the adapter
Then only actors with processing are registered on the stream router
And the route bridge agents cache includes all listed actors
Scenario: Route bridge refresh failure propagates to caller
Given a reactive stream router and route bridge
And an actor registry that returns actors without names
When I attempt to register registry actors with the adapter
Then an AttributeError is raised
Scenario: Route bridge refresh failure propagates when registry returns none
Given a reactive stream router and route bridge
And an actor registry that returns none
When I attempt to register registry actors with the adapter
Then a TypeError is raised
# ============================================================
# Originally from: reactive_routing_port.feature
# Feature: Reactive routing and route bridge port
# ============================================================
Scenario: Create a cold stream and send a message
Given a reactive stream router
When I create a cold stream named "alpha"
And I send message "hello" to stream "alpha"
Then the stream "alpha" should exist
Scenario: Initialize route bridge with dummy registry
Given a reactive stream router
And a dummy actor registry
When I create a route bridge
Then the route bridge should include agent "dummy"
Scenario: Convert stream route to graph config via bridge
Given a reactive stream router
And a dummy actor registry
And a stream route config named "s1" with a map agent operator
When I create a route bridge
And I convert the stream route to a graph config
Then the graph config should contain nodes
# ============================================================
# Originally from: route_bridge_coverage.feature
# Feature: Route Bridge Module Coverage
# ============================================================
Scenario: Test RouteBridge initialization
Given I have a clean test environment for route bridge
Given I have a route bridge stream router and agents
When I create the route bridge instance
Then the route bridge should be initialized correctly
And the route bridge logger should be set up
And route bridge active conversions should be empty
Scenario: Test RouteBridge initialization with scheduler
Given I have a clean test environment for route bridge
Given I have a route bridge stream router and agents
And I have an AsyncIO scheduler
When I create a RouteBridge instance with scheduler
Then the route bridge should be initialized with scheduler
And the route bridge scheduler should be stored correctly
Scenario: Test check_upgrade_conditions - no bridge config
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a route config without bridge configuration
And I have a stream message
When I check upgrade conditions
Then the route bridge upgrade result should be False
Scenario: Test check_upgrade_conditions - wrong route type
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with bridge configuration
And I have a stream message
When I check upgrade conditions
Then the route bridge upgrade result should be False
Scenario: Test check_upgrade_conditions - needs_state condition
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with needs_state upgrade condition
And I have a stream message with requires_state metadata
When I check upgrade conditions
Then the route bridge upgrade result should be True
Scenario: Test check_upgrade_conditions - message_count condition
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with message_count upgrade condition
And I have processed enough messages to trigger upgrade
When I check upgrade conditions
Then the route bridge upgrade result should be True
And the route bridge message count should be at least 5
Scenario: Test check_upgrade_conditions - complexity_threshold condition
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with complexity_threshold upgrade condition
And I have a complex route configuration
When I check upgrade conditions
Then the route bridge upgrade result should be True
Scenario: Test check_upgrade_conditions - non-callable predicate
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with non-callable custom_predicate
And I have a stream message
When I check upgrade conditions
Then the route bridge upgrade result should be False
Scenario: Test check_downgrade_conditions - no bridge config
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a route config without bridge configuration
And I have a graph state
When I check downgrade conditions
Then the downgrade result should be False
Scenario: Test check_downgrade_conditions - wrong route type
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with bridge configuration
And I have a graph state
When I check downgrade conditions
Then the downgrade result should be False
Scenario: Test check_downgrade_conditions - idle_time condition
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with idle_time downgrade condition
And I have a graph state that has been idle too long
When I check downgrade conditions
Then the downgrade result should be True
Scenario: Test check_downgrade_conditions - state_size condition
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with state_size downgrade condition
And I have a graph state with minimal messages
When I check downgrade conditions
Then the downgrade result should be True
Scenario: Test check_downgrade_conditions - no_conditionals_used condition
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with no_conditionals_used downgrade condition
And I have a graph state with no conditional edges used
When I check downgrade conditions
Then the downgrade result should be True
Scenario: Test _get_message_count functionality
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I seed a message count for the route
When I get message count for a route
Then the route bridge message count should be returned
And the route bridge message count result should be a valid integer
Scenario: Test error handling in downgrade_graph_to_stream
Given I have a clean test environment for route bridge
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config for downgrade
And I have a corrupted LangGraph instance
When I attempt to downgrade graph to stream
Then route bridge error handling should occur
And the route bridge system should remain stable
Scenario: Test complex upgrade downgrade cycle
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a route config that supports both upgrade and downgrade
And I have a stream message with requires_state metadata
When I perform an upgrade and then downgrade cycle
Then the cycle should complete successfully
And the final configuration should be consistent
And active conversions should be tracked correctly
# ============================================================
# Originally from: route_bridge_uncovered_lines.feature
# Feature: Additional route bridge uncovered lines coverage
# ============================================================
Scenario: Bridge falls back to empty agents on registry failure
Given I have a clean test environment for route bridge
Given I have a failing agent registry for route bridge
When I create the route bridge instance
Then the route bridge should be initialized correctly
And the route bridge agents should be empty
Scenario: Upgrade conditions not met when below message_count threshold
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with message_count upgrade condition
And I have a pending message count below threshold
And I have a stream message
When I check upgrade conditions
Then the route bridge upgrade result should be False
Scenario: Complexity threshold too high prevents upgrade
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a simple stream route config with high complexity threshold
And I have a stream message
When I check upgrade conditions
Then the route bridge upgrade result should be False
Scenario: Callable custom predicate triggers upgrade
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with callable custom_predicate
And I have a stream message
When I check upgrade conditions synchronously
Then the route bridge upgrade result should be True
Scenario: Upgrade applies state extractor and preserves subscriptions loop
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a stream route config with state extractor and subscriptions
And I have a stream message
When I upgrade the stream route to graph
Then the graph initial state should include extracted data
Scenario: Downgrade uses flattener and checkpointing
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with flattener and checkpointing
And I have a LangGraph instance with checkpointing
When I downgrade the graph with flattener
Then the state flattener should run
And the checkpoint should be saved
Scenario: _create_graph_from_stream uses function node when no agent
Given I have a clean test environment for route bridge
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
When I create a graph config from a stream without agents
Then the resulting graph should use function nodes
Scenario: _get_message_count falls back to update_count attribute
Given I have a clean test environment for route bridge
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I seed a fallback update_count conversion
When I get message count for a route
Then the route bridge fallback message count should be returned
Scenario: Downgrade conditions not met when idle time short
Given I have a clean test environment for route bridge
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with idle_time not exceeded
And I have a graph state
When I check downgrade conditions
Then the downgrade result should be False
Scenario: Downgrade conditions not met when state size large
Given I have a clean test environment for route bridge
Given I have a clean test environment for route bridge
Given I have a RouteBridge instance
And I have a graph route config with large state size
And I have a graph state with multiple messages
When I check downgrade conditions
Then the downgrade result should be False
# ============================================================
# Originally from: route_coverage_comprehensive.feature
# Feature: Route Module Comprehensive Coverage
# ============================================================
Scenario: RouteConfig validation for graph routes without nodes
Given the route system is available
Given I create a RouteConfig with graph type but no nodes
When I validate the configuration
Then I should get a ConfigurationError about missing nodes
Scenario: RouteConfig to_stream_config conversion error
Given the route system is available
Given I create a RouteConfig with graph type
When I try to convert it to StreamConfig
Then I should get a ValueError about invalid conversion
Scenario: RouteConfig to_graph_config conversion error
Given the route system is available
Given I create a RouteConfig with stream type
When I try to convert it to GraphConfig
Then I should get a ValueError about invalid conversion
Scenario: RouteConfig from_stream_config class method
Given the route system is available
Given I have a valid StreamConfig
When I create a RouteConfig from the StreamConfig
Then the RouteConfig should have stream type
And it should preserve all stream configuration details
Scenario: RouteConfig from_graph_config class method
Given the route system is available
Given I have a valid GraphConfig with nodes and edges
When I create a RouteConfig from the GraphConfig
Then the RouteConfig should have graph type
And it should preserve all graph configuration details
And node configurations should be converted to dictionaries
And edge configurations should be converted to dictionaries
Scenario: RouteComplexityAnalyzer bridge type analysis
Given the route system is available
Given I have a RouteConfig with bridge type
When I analyze the route complexity
Then it should return bridge complexity analysis
And the complexity should be "bridge"
And the score should be 0
Scenario: Stream complexity analysis with various features
Given the route system is available
Given I have stream routes with different features
When I analyze each route complexity
Then routes with more operators should have higher complexity scores
And routes with routing connections should get additional score
And hot streams should get additional score
And the complexity classification should match the score ranges
Scenario: Graph complexity analysis with conditional edges
Given the route system is available
Given I have graph routes with conditional edges
When I analyze the graph complexity
Then conditional edges should increase the complexity score
And the features should include conditional edge information
And checkpointing should increase the score
And time travel should increase the score
Scenario: Stream complexity recommendation logic
Given the route system is available
Given I have streams with different complexity scores
When I get recommendations for each stream
Then simple streams should get simple transformation recommendation
And moderate streams should get multi-step processing recommendation
And complex streams should get graph consideration recommendation
Scenario: Graph complexity recommendation logic
Given the route system is available
Given I have graphs with different complexity scores
When I get recommendations for each graph
Then moderate graphs should get conditional logic recommendation
And complex graphs should get stateful workflow recommendation
And advanced graphs should get feature evaluation recommendation
Scenario: Route type suggestion based on requirements
Given the route system is available
Given I have different requirement scenarios
When I ask for route type suggestions
Then persistence requirements should suggest graph
And state with conditionals should suggest graph
And conditionals without continuous should suggest graph
And stateless continuous should suggest stream
And simple cases should default to stream
Scenario: RouteConfig post_init stream type defaulting
Given the route system is available
Given I create a stream RouteConfig without specifying stream_type
When the post_init validation runs
Then the stream_type should default to COLD
Scenario: RouteConfig to_graph_config with complex node types
Given the route system is available
Given I have a RouteConfig with various node types and configurations
When I convert it to GraphConfig
Then all node types should be properly converted
And all node properties should be preserved
And retry policies and timeouts should be included
And conditions and subgraphs should be handled
And node metadata should be preserved in conversion
# ============================================================
# Originally from: routing_adapter_coverage.feature
# Feature: Routing adapter coverage
# ============================================================
Scenario: Map operator creates agent node chain
Given route operators include a map agent and function
When I build route nodes from that stream
Then it creates an agent node and chained edges to end
And the function node is created with its type
Scenario: Map without agent falls back to function node
Given route operators include a map without agent
When I build route nodes from that stream
Then it treats the map as function and terminates chain with end node
# ============================================================
# Originally from: routing_langgraph_port.feature
# Feature: Actor-first port of v2 routing and LangGraph suites
# ============================================================
Scenario: Route bridge converts stream to graph configuration
Given the routing system is initialized for actor-first operation
Given I have a stream route configuration
"""
name: "test-stream"
type: "stream"
stream_type: "sequential"
batch_size: 10
"""
When I bridge the route to graph type
Then the bridged route should be a graph configuration
And the graph should have sequential execution
And the graph should preserve batch settings
Scenario: Unified route handles actor-only context variables
Given the routing system is initialized for actor-first operation
Given I have a unified route with context
"""
name: "actor-route"
type: "graph"
actor: "openai/gpt-4"
context_vars:
temperature: 0.7
max_tokens: 2000
"""
When I process the unified route
Then the route should use the specified actor
And the context should include temperature and max_tokens
And no provider/model fields should exist
Scenario: Stream router validates actor configuration
Given the routing system is initialized for actor-first operation
Given I have a stream router configuration
"""
routes:
- name: "primary"
actor: "anthropic/claude-3"
stream_type: "parallel"
- name: "fallback"
actor: "openai/gpt-3.5-turbo"
stream_type: "sequential"
"""
When I initialize the stream router
Then the router should have 2 routes
And each route should use its configured actor
And the router should support actor-based fallback
Scenario: LangGraph bridge handles state management
Given the routing system is initialized for actor-first operation
Given I have a LangGraph configuration with state
"""
name: "stateful-graph"
type: "graph"
actor: "local/custom-agent"
enable_checkpointing: true
state_schema:
current_task: "string"
completed_steps: "array"
"""
When I create a LangGraph bridge
Then the bridge should enable checkpointing
And the state schema should be preserved
And the bridge should use the custom actor
Scenario: Route complexity analysis with actor paths
Given the routing system is initialized for actor-first operation
Given I have a complex actor route configuration
"""
name: "complex-route"
type: "graph"
nodes:
- name: "analyze"
actor: "openai/gpt-4"
type: "llm"
- name: "generate"
actor: "anthropic/claude-3"
type: "llm"
- name: "validate"
actor: "local/validator"
type: "tool"
edges:
- from: "analyze"
to: "generate"
- from: "generate"
to: "validate"
"""
When I analyze the actor route complexity
Then the complexity score should reflect multi-actor coordination
And the analysis should identify 3 nodes and 2 edges
And each node should use its assigned actor
Scenario: Stream router handles parallel actor execution
Given the routing system is initialized for actor-first operation
Given I have parallel stream routes
"""
routes:
- name: "fast-route"
actor: "openai/gpt-3.5-turbo"
stream_type: "parallel"
priority: 1
- name: "accurate-route"
actor: "anthropic/claude-3-opus"
stream_type: "parallel"
priority: 2
execution_mode: "race"
"""
When I execute the parallel streams
Then both actors should be invoked concurrently
And the first completion should be used
And unused results should be properly cleaned up
Scenario: Conditional routing based on actor capabilities
Given the routing system is initialized for actor-first operation
Given I have conditional routing configuration
"""
name: "conditional-graph"
type: "graph"
nodes:
- name: "router"
type: "conditional"
conditions:
- if: "requires_vision"
then: "vision_node"
- if: "requires_code"
then: "code_node"
- else: "default_node"
- name: "vision_node"
actor: "openai/gpt-4-vision"
type: "llm"
- name: "code_node"
actor: "anthropic/claude-3-opus"
type: "llm"
- name: "default_node"
actor: "openai/gpt-3.5-turbo"
type: "llm"
"""
When I route with vision requirements
Then the vision actor should be selected
And the routing decision should be logged
Scenario: Route validation rejects invalid actors
Given the routing system is initialized for actor-first operation
Given I have a route with invalid actor
"""
name: "invalid-route"
type: "stream"
actor: "nonexistent/model"
"""
When I validate the route configuration
Then validation should fail with actor not found error
And the error should suggest available actors
Scenario: Bridge preserves actor-specific options
Given the routing system is initialized for actor-first operation
Given I have a route with actor options
"""
name: "options-route"
type: "stream"
actor: "openai/gpt-4"
actor_options:
temperature: 0.9
top_p: 0.95
frequency_penalty: 0.5
"""
When I bridge to a different route type
Then the actor options should be preserved
And the options should override defaults
And the bridged route should maintain actor identity
Scenario: Reactive streams with actor-based transformations
Given the routing system is initialized for actor-first operation
Given I have a reactive stream configuration
"""
name: "transform-stream"
type: "stream"
stream_type: "transform"
transformations:
- stage: "parse"
actor: "local/parser"
- stage: "enrich"
actor: "openai/gpt-3.5-turbo"
- stage: "format"
actor: "local/formatter"
"""
When I process data through the stream
Then each transformation should use its actor
And the data should flow through all stages
And errors should be handled per-actor
# ============================================================
# Originally from: stream_router_additional_coverage.feature
# Feature: Stream Router additional uncovered branches
# ============================================================
Scenario: Copying stream message with metadata override uses provided metadata
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
Given I have a base stream message with context metadata
When I copy the message with explicit metadata override
Then the copy should use the provided metadata
Scenario: Creating a hot stream initializes with initial value
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I create a hot stream named "hot_stream" with initial value 5
Then the hot stream should store the initial value
Scenario: Creating a replay stream uses buffer size
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I create a replay stream named "replay_stream" with buffer size 3
Then the replay stream should be created with buffer size 3
Scenario: LangGraph operator without bridge raises routing error
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I build a langgraph operator "graph_execute" without bridge
Then I should get a stream routing error about missing langgraph bridge
Scenario: Map operator with unknown agent raises routing error
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I build a map operator targeting unknown agent "ghost"
Then I should get a stream routing error about missing agent
Scenario: Debounce, throttle, and delay operators are created
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I build debounce, throttle, and delay operators
Then the temporal operators should be created successfully
Scenario: Buffer and window operators with count are created
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I build buffer and window operators with count 2
Then the buffer and window operators should be created successfully
Scenario: Take and skip operators are created
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I build take and skip operators
Then the take and skip operators should be created successfully
Scenario: Merge operator merges existing streams
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
Given I have two source streams named "merge_x" and "merge_y"
When I create a merge operator for those streams
Then the merge operator should be usable
Scenario: Accumulate operator with sum processes numbers
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I build an accumulate operator with sum
Then the accumulate operator should sum incoming numbers
Scenario: Transform extract_field returns field value
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I apply an extract_field transform to a dictionary message
Then the extracted field should be returned
Scenario: Evaluate condition equals and empty cases
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I evaluate empty and equals conditions against messages
Then the empty condition should be true and equals should match
Scenario: Agent mapper uses sync processor and handles errors
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I map messages through agent mapper success and failure paths
Then the mapper should emit processed content and blank on error
Scenario: Append accumulator with non-list leaves accumulator unchanged
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I apply append accumulator to non-list accumulator
Then the accumulator should remain unchanged
Scenario: Setup subscriptions ignores missing sources
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I setup subscriptions including a missing source
Then no subscription should be added for the missing source
Scenario: Split stream routes message when condition matches
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
Given I have a stream "source_split" and target "target_split"
When I split the source stream with a matching condition
Then the target stream should receive the routed message
Scenario: Subscribing to output and error streams delivers messages
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
When I subscribe to output and error streams and emit messages
Then the observers should receive the respective messages
Scenario: Dispose handles stream dispose errors gracefully
Given the CleverAgents reactive system is available
And I have a base stream message with context metadata
Given I have a stream router with disposable streams and subscriptions that may fail
When I dispose the stream router with failing disposables
Then the stream router internals should be cleared even on dispose errors
# ============================================================
# Originally from: stream_router_agent_tool_coverage.feature
# Feature: Stream Router agent and tool coverage
# ============================================================
Scenario: SimpleToolAgent returns content when tool code is missing
Given a SimpleToolAgent with a tool missing code
When I process "payload" through the SimpleToolAgent
Then the SimpleToolAgent result should be "payload"
Scenario: SimpleToolAgent rejects tool with code block
Given a SimpleToolAgent with a code block tool
When I process "payload" through the SimpleToolAgent expecting rejection
Then the SimpleToolAgent should raise a routing error about code blocks
Scenario: SimpleToolAgent rejects code block via process_message_sync
Given a SimpleToolAgent with a code block tool
When I call process_message_sync with "payload" expecting rejection
Then the SimpleToolAgent should raise a routing error about code blocks
# TODO: Uncomment when step definitions are implemented
# Scenario: SimpleLLMAgent returns empty prompt for blank template
# Given a SimpleLLMAgent for prompt rendering
# When I render the template "<empty>" with context:
# | key | value |
# | name | Ada |
# Then the rendered prompt should be "<empty>"
#
# TODO: Uncomment when step definitions are implemented
# Scenario: SimpleLLMAgent returns raw template when environment is missing
# Given a SimpleLLMAgent for prompt rendering
# And the SimpleLLMAgent template environment is "none"
# When I render the template "Hello" with context:
# | key | value |
# | name | Ada |
# Then the rendered prompt should be "Hello"
#
# TODO: Uncomment when step definitions are implemented
# Scenario: SimpleLLMAgent renders template when environment succeeds
# Given a SimpleLLMAgent for prompt rendering
# And the SimpleLLMAgent template environment is "working"
# When I render the template "Hello {{ name }}" with context:
# | key | value |
# | name | Ada |
# Then the rendered prompt should be "Hello Ada"
#
# TODO: Uncomment when step definitions are implemented
# Scenario: SimpleLLMAgent falls back to template on render failure
# Given a SimpleLLMAgent for prompt rendering
# And the SimpleLLMAgent template environment is "failing"
# When I render the template "Hello {{ name }}" with context:
# | key | value |
# | name | Ada |
# Then the rendered prompt should be "Hello {{ name }}"
Scenario: SimpleLLMAgent resolves LLM with configured kwargs and caching
Given a SimpleLLMAgent with provider config
And a stub provider registry is installed
When I resolve the LLM twice
Then the registry should be called once with the configured kwargs
And the resolved LLM should be cached
Scenario: SimpleLLMAgent raises when LangChain messages are unavailable
Given a SimpleLLMAgent with provider config
And a stub provider registry is installed
And LangChain message classes are unavailable
When I process "ping" through the LLM agent
Then I should receive a stream routing error about missing messages
Scenario: SimpleLLMAgent process_message_sync returns LLM content
Given a SimpleLLMAgent with system prompt and provider config
And a stub provider registry is installed
And LangChain message stubs are installed
When I process 123 through the LLM agent via process_message_sync
Then the LLM should receive system and human messages
And the LLM agent result should be "llm-output"
# ============================================================
# Originally from: stream_router_coverage.feature
# Feature: Stream Router Coverage
# ============================================================
# ============================================================
# Originally from: stream_router_misc_uncovered.feature
# Feature: Stream Router remaining uncovered lines
# ============================================================
Scenario: Converting dict route config uses defaults
Given the CleverAgents reactive system is available
When I convert a dict route config with only a name
Then the stream config should use default values
Scenario: Converting object route config uses defaults for missing attrs
Given the CleverAgents reactive system is available
When I convert a minimal object route config
Then the stream config should use default values
Scenario: Transform operator with type parameter applies replace
Given the CleverAgents reactive system is available
When I apply a transform operator with type replace
Then the transform operator should update the field
Scenario: Switch operator emits original message with no matches or defaults
Given the CleverAgents reactive system is available
When I run a switch operator with no matches and no defaults
Then the switch operator should emit the original message
Scenario: Unsupported condition returns false
Given the CleverAgents reactive system is available
When I evaluate an unsupported condition
Then the unsupported condition result should be false
Scenario: Agent mapper falls back when no processing methods exist
Given the CleverAgents reactive system is available
When I map a message with an agent lacking processors
Then the mapper should return the original content with metadata
# ============================================================
# Originally from: stream_router_new_branches.feature
# Feature: Stream Router additional uncovered branches
# ============================================================
Scenario: LangGraph operator uses bridge factory when available
Given the CleverAgents reactive system is available
Given a LangGraph bridge stub is registered on the stream router
When I build a graph_execute operator through the bridge
Then the bridge factory should be invoked and map data
Scenario: Map operator processes message through registered agent
Given the CleverAgents reactive system is available
Given a dummy agent is registered on the stream router
When I map a message using that agent
Then the agent-mapped content should be returned with metadata
Scenario: Filter operator passes matching condition
Given the CleverAgents reactive system is available
When I build a filter operator with equals condition
Then the filter should emit only matching values
Scenario: Transform operator rejects unregistered fn string
Given the CleverAgents reactive system is available
When I build a transform operator with an unregistered function string
Then I should get a stream routing error about unregistered transform
Scenario: Switch operator handles case operators pipeline
Given the CleverAgents reactive system is available
When I run a switch operator with a case operator pipeline
Then the switch case should apply its operators
Scenario: Switch operator routes to a target stream case
Given the CleverAgents reactive system is available
Given I have an existing target stream named "case_target"
When I run a switch operator that routes to that case target
Then the case target stream should receive the message
Scenario: Unknown operator raises routing error
Given the CleverAgents reactive system is available
When I build an operator with an unknown type
Then I should get a stream routing error about unknown operator
Scenario: Extract field transform returns requested value and fallback
Given the CleverAgents reactive system is available
When I apply an extract_field transform to different message types
Then the transform should return the field value or original message
Scenario: Evaluate condition supports equals and field branches
Given the CleverAgents reactive system is available
When I evaluate equals and field conditions
Then both condition checks should return true
Scenario: Append accumulator returns unchanged when accumulator is not a list
Given the CleverAgents reactive system is available
When I apply the append accumulator to a non-list accumulator
Then the accumulator should be returned unchanged
Scenario: Setup subscriptions skips missing sources and subscribes existing
Given the CleverAgents reactive system is available
Given I prepare stream config with existing and missing subscriptions
When I setup subscriptions for that config
Then only existing sources should be subscribed
# ============================================================
# Originally from: stream_router_remaining_coverage.feature
# Feature: Reactive Stream Router Edge Cases
# ============================================================
Scenario: Registering an operation with non-alphanumeric characters is rejected
Given a fresh SimpleToolAgent registry
When I register an operation named "bad-name!" with a lambda
Then a ValueError should be raised about operation name format
Scenario: A tool agent with a malformed tool entry passes content through unchanged
Given a SimpleToolAgent whose tools list contains a non-dict entry
When I process content "hello" through that SimpleToolAgent
Then the SimpleToolAgent should return "hello" unchanged
Scenario: A tool agent with an unrecognised operation name passes content through unchanged
Given a SimpleToolAgent with an unknown operation "nonexistent_op"
When I process content "data" through that SimpleToolAgent
Then the SimpleToolAgent should return "data" unchanged
Scenario: A tool agent safely returns empty output when an operation throws
Given a SimpleToolAgent with an operation that raises an exception
When I process content "boom" through that SimpleToolAgent
Then the SimpleToolAgent should return an empty string
# Transform registration safety
Scenario: Registering a transform with non-alphanumeric characters is rejected
Given a fresh ReactiveStreamRouter registry
When I register a transform named "bad@name" with a lambda
Then a ValueError should be raised about transform name format
# Operator construction validation
Scenario: A transform operator created without a function or type is rejected
Given a reactive stream router for coverage
And a stream named "transform_test" exists on the router
When I create a transform operator with neither fn nor type
Then a StreamRoutingError should be raised about missing transform params
# LLM agent provider resolution
Scenario: An LLM agent resolves its provider without forwarding absent optional parameters
Given a SimpleLLMAgent with no temperature or max_tokens or max_retries
And a stub provider registry is active
When I resolve the LLM on that agent
Then the LLM should be resolved without optional kwargs
Scenario: An LLM agent with no system prompt sends only the user message to the model
Given a SimpleLLMAgent with an empty system prompt
And a stub provider registry is active
When I process content "test input" through that SimpleLLMAgent
Then the LLM should receive only a HumanMessage
# LangGraph bridge delegation
Scenario: A graph operator delegates to the bridge factory when the method exists
Given a reactive stream router for coverage
And a LangGraph bridge stub with a graph_execute factory is registered
When I create a graph_execute operator via the bridge
Then the bridge factory method should produce a valid operator
Scenario: A graph operator raises a clear error when the bridge lacks the factory method
Given a reactive stream router for coverage
And a LangGraph bridge stub without the requested factory method is registered
When I attempt to create a graph_execute operator via the bridge
Then a StreamRoutingError should be raised about LangGraph bridge
# Switch operator resilience
Scenario: A switch case pipeline tolerates an operator that resolves to nothing
Given a reactive stream router for coverage
And a stream named "switch_null_op" exists on the router
When I run a switch operator whose case pipeline includes a None operator
Then the switch should still emit the message through the pipeline
Scenario: A switch case with a nonexistent target stream falls through to the default
Given a reactive stream router for coverage
And a stream named "switch_missing_target" exists on the router
When I run a switch with a matching case whose target stream does not exist
Then the switch should fall through to the default
Scenario: A switch default pipeline tolerates an operator that resolves to nothing
Given a reactive stream router for coverage
And a stream named "switch_default_null" exists on the router
When I run a switch operator whose default pipeline includes a None operator
Then the switch should still emit the message through the default pipeline
# Message transformation edge cases
Scenario: A replace transform on a non-dictionary message passes it through unchanged
Given a reactive stream router for coverage
When I apply a replace transform to a non-dict message
Then the message should be returned unchanged
Scenario: An extract-field transform retrieves the named value from a dictionary message
Given a reactive stream router for coverage
When I apply an extract_field transform to a dict message with the field
Then the extracted field value should be returned
Scenario: An extract-field transform on a non-dictionary message passes it through
Given a reactive stream router for coverage
When I apply an extract_field transform to a non-dict message
Then the non-dict message should be returned from extract_field
Scenario: An unrecognised transform type passes the message through unchanged
Given a reactive stream router for coverage
When I apply a transform with unknown type to a message
Then the original message should be returned
# Condition evaluation edge cases
Scenario: A field condition matches when the dictionary message contains the field
Given a reactive stream router for coverage
When I evaluate a field condition against a dict message containing the field
Then the condition should return true
Scenario: A field condition does not match when the dictionary message lacks the field
Given a reactive stream router for coverage
When I evaluate a field condition against a dict message missing the field
Then the condition should return false
Scenario: A field condition does not match against a non-dictionary message
Given a reactive stream router for coverage
When I evaluate a field condition against a non-dict message
Then the condition should return false
# Stream splitting and merging edge cases
Scenario: Splitting a stream ignores condition targets that do not exist
Given a reactive stream router for coverage
And a stream named "split_source" exists on the router
When I split the stream with a condition targeting a nonexistent stream
Then no error should occur and the message should not route
Scenario: Merging streams into a nonexistent target auto-creates it
Given a reactive stream router for coverage
And a stream named "merge_src_a" exists on the router
And a stream named "merge_src_b" exists on the router
When I merge streams into a target that does not exist yet
Then the target stream should be created and receive merged messages
Scenario: Merging streams silently skips source streams that do not exist
Given a reactive stream router for coverage
And a stream named "merge_src_c" exists on the router
When I merge streams including a nonexistent source into an existing target
Then only the existing source should be subscribed
# Router disposal
Scenario: Disposing the router tolerates streams that lack a dispose method
Given a reactive stream router for coverage
And a stream that lacks a dispose method is injected into the router
When I dispose the router
Then the router should be fully cleared without errors
# ============================================================
# Originally from: stream_router_uncovered_paths.feature
# Feature: Stream Router uncovered branches
# ============================================================
Scenario: Copying stream message without metadata override deep copies entries
Given the CleverAgents reactive system is available
Given I have a base stream message with context metadata
When I copy the message without metadata override
Then the metadata should be deep-copied except context
Scenario: Map operator without params should raise routing error
Given the CleverAgents reactive system is available
When I build a map operator without required params
Then I should get a stream routing error about missing map params
Scenario: Buffer operator without count should raise routing error
Given the CleverAgents reactive system is available
When I build a buffer operator without count
Then I should get a stream routing error about buffer count
Scenario: Window operator without count should raise routing error
Given the CleverAgents reactive system is available
When I build a window operator without count
Then I should get a stream routing error about window count
Scenario: Condition check for missing field returns false
Given the CleverAgents reactive system is available
When I evaluate a field condition against a message without that field
Then the condition result should be false
Scenario: Accumulator append initializes list and appends items
Given the CleverAgents reactive system is available
When I apply the append accumulator with list initialization
Then the accumulator should return a list with the item
Scenario: Sending message to unknown stream raises routing error
Given the CleverAgents reactive system is available
When I send a message to an unknown stream
Then I should get a stream routing error about missing stream
Scenario: Splitting from unknown source raises routing error
Given the CleverAgents reactive system is available
When I split a stream from an unknown source
Then I should get a stream routing error about missing source
Scenario: Creating stream using string name initializes config
Given the CleverAgents reactive system is available
When I create a stream from the string name "string_stream"
Then the stream should exist in the router
Scenario: Duplicate stream creation raises routing error
Given the CleverAgents reactive system is available
Given I have already created a stream named "dup_stream"
When I try to create the stream "dup_stream" again
Then I should get a stream routing error about duplicate stream
Scenario: Map operator with function param uses fallback
Given the CleverAgents reactive system is available
When I map a message using function param "noop_function"
Then the mapped result should equal the original message
Scenario: Transform operator with unregistered fn raises error
Given the CleverAgents reactive system is available
When I build a transform operator with an invalid function string
Then I should get a stream routing error about unregistered transform fn
Scenario: Filter operator without condition raises routing error
Given the CleverAgents reactive system is available
When I build a filter operator without condition
Then I should get a stream routing error about missing filter condition
Scenario: Switch operator applies default operators pipeline
Given the CleverAgents reactive system is available
When I run a switch operator with default operators against a message
Then the switch operator should output the transformed value
Scenario: Switch operator routes to default stream subject
Given the CleverAgents reactive system is available
When I run a switch operator that targets a default stream subject
Then the default stream should emit its initial value
Scenario: Apply replace transform updates dictionary field
Given the CleverAgents reactive system is available
When I apply a replace transform to a dictionary message
Then the dictionary field should be updated
Scenario: Sum accumulator initializes and sums numeric messages
Given the CleverAgents reactive system is available
When I apply the sum accumulator to numeric messages
Then the accumulator should produce the summed value
Scenario: Merge streams creates target when missing
Given the CleverAgents reactive system is available
Given I have two source streams named "merge_a" and "merge_b"
When I merge the streams into a new target "merge_output"
Then the merged target stream should exist
Scenario: Dispose cleans up subscriptions and stream disposables
Given the CleverAgents reactive system is available
Given I have a stream router with disposable streams and subscriptions
When I dispose the stream router
Then the stream router internals should be cleared
# ============================================================
# Originally from: stream_router_unsafe_and_llm_coverage.feature
# Feature: SimpleToolAgent code block rejection and SimpleLLMAgent render_prompt coverage
# ============================================================
Scenario: SimpleToolAgent rejects code block even with unsafe flag
Given a stream router unsafe tool agent with a valid code block
When the stream router unsafe agent processes input expecting rejection
Then the stream router unsafe agent should raise StreamRoutingError about code
Scenario: SimpleToolAgent rejects failing code block even with unsafe flag
Given a stream router unsafe tool agent with a failing code block
When the stream router unsafe agent processes input expecting rejection
Then the stream router unsafe agent should raise StreamRoutingError about code
Scenario: SimpleToolAgent safe mode rejects code blocks
Given a stream router safe tool agent with a code block
When the stream router safe agent processes input expecting rejection
Then the stream router safe agent should raise StreamRoutingError about code
Scenario: SimpleLLMAgent render_prompt with no template env returns template as-is
Given a stream router llm agent with no template env
When the stream router llm agent renders "Hello {{ name }}" with context name "World"
Then the stream router llm render result should be "Hello {{ name }}"
Scenario: SimpleLLMAgent render_prompt with template env failure returns template
Given a stream router llm agent with a failing template env
When the stream router llm agent renders "Hello {{ name }}" with context name "World"
Then the stream router llm render result should be "Hello {{ name }}"
Scenario: SimpleLLMAgent render_prompt with empty template returns empty string
Given a stream router llm agent for empty template test
When the stream router llm agent renders empty template
Then the stream router llm render result should be empty string
Scenario: SimpleToolAgent processes named operation successfully
Given a stream router tool agent with uppercase operation
When the stream router tool agent processes "hello" through operation
Then the stream router tool agent operation result should be "HELLO"
Scenario: SimpleToolAgent processes unknown operation falls back to identity
Given a stream router tool agent with unknown operation "nonexistent_xyz"
When the stream router tool agent processes "keep_me" through operation
Then the stream router tool agent operation result should be "keep_me"
# M5: SimpleLLMAgent._resolve_llm forwards options to registry.create_llm (#11223).
@tdd_issue @tdd_issue_11223
Scenario: SimpleLLMAgent forwards options block to LLM constructor
Given a stream router llm agent with options block containing custom base url
When the stream router llm agent resolves the llm
Then the llm constructor should have received the custom base url
# M5: actor without options block is unaffected (#11223).
@tdd_issue @tdd_issue_11223
Scenario: SimpleLLMAgent without options block calls LLM constructor without extra kwargs
Given a stream router llm agent without options block
When the stream router llm agent resolves the llm
Then the llm constructor should not have received extra kwargs
# M5: top-level keys take precedence over duplicates in options (#11223).
@tdd_issue @tdd_issue_11223
Scenario: SimpleLLMAgent top-level key takes precedence over options duplicate
Given a stream router llm agent with top-level temperature and options block
When the stream router llm agent resolves the llm
Then the llm constructor should have received the top-level temperature