← Back to DocsRebuild20250712

JIDO ACTION ROUTING ANALYSIS

Documentation for JIDO_ACTION_ROUTING_ANALYSIS from the Foundation repository.

Jido Action Routing Deep Dive Analysis

Date: 2025-07-12
Issue: “No matching handlers found for signal” error in Cognitive Variable tests
Status: RESOLVED - Root cause identified and solution implemented

Executive Summary

Our Jido-native Cognitive Variable implementation was failing with “No matching handlers found for signal” errors because we misunderstood how Jido routes signals to actions. Actions are not automatically routed based on their names - Jido requires explicit route configuration that maps signal types to action instructions.

The Problem

Symptom

[error] SIGNAL: jido.agent.err.execution.error from 01980074-524f-74e3-bab1-9a5c415d3000 
with data=#Jido.Error<
  type: :execution_error
  message: "Error routing signal"
  details: %{reason: #Jido.Signal.Error<
      type: :routing_error
      message: "No matching handlers found for signal"

What We Expected

We expected that defining actions with name: "change_value" and registering them in actions: [...] would automatically route signals with type: "change_value" to those actions.

What Actually Happens

Jido requires explicit route configuration to map signal types to action instructions. Action registration alone is not sufficient.

Deep Technical Analysis

1. Jido.Action Module Investigation

Key Discovery: use Jido.Action generates metadata and validation, but does not create automatic signal routes.

# This creates an action module with metadata...
defmodule MyAction do
  use Jido.Action,
    name: "my_action",  # ← This is metadata only, NOT a routing key
    description: "Does something"
    
  def run(params, context), do: {:ok, %{}}
end

# ...but does NOT automatically route signals with type: "my_action"

The name field is used for:

  • Action discovery and listing
  • Documentation and introspection
  • Instruction creation
  • NOT for automatic signal routing

2. Jido.Agent Action Registration Investigation

Key Discovery: Actions in actions: [...] are registered for instruction-based execution, not signal routing.

defmodule MyAgent do
  use Jido.Agent,
    actions: [MyAction]  # ← Enables instruction execution, NOT signal routing
end

This registration:

  • ✅ Validates action modules implement Jido.Action
  • ✅ Enables actions for instruction-based execution
  • ✅ Adds actions to agent.actions list for introspection
  • Does NOT create signal routes

3. Signal Routing Pipeline Analysis

The Complete Routing Pipeline:

1. Signal Received → Jido.Agent.Server.handle_call/cast
2. Signal Queued → Jido.Agent.ServerState.enqueue/2  
3. Signal Processing → Jido.Agent.ServerRuntime.process_signal/2
4. Signal Routing → Jido.Agent.ServerRouter.route/2
5. Route Resolution → Jido.Signal.Router.route/2
6. Route Matching → Checks explicit route configuration
7. Instruction Creation → Only if route found
8. Action Execution → Via instruction

Critical Step #6: Route matching only succeeds if there are explicit routes configured.

4. Working Examples Analysis

From deps/jido/test/support/test_agent.ex:

# CORRECT: Explicit route configuration
Jido.Agent.Server.start_link(
  agent: agent,
  routes: [
    {"example.event", Instruction.new!(action: JidoTest.TestActions.BasicAction)},
    {"another.signal", Instruction.new!(action: SomeOtherAction)}
  ]
)

From our broken implementation:

# INCORRECT: No route configuration
__MODULE__.start_link(id: id, initial_state: merged_state)

5. Root Cause Identification

The Missing Link: We were creating signals with types like "change_value" and expecting them to route to actions named "change_value", but we never configured the mapping between signal types and action instructions.

# Our signal (correct)
signal = Jido.Signal.new!(%{
  type: "change_value",
  data: %{new_value: 42}
})

# Our agent startup (MISSING ROUTES)
__MODULE__.start_link(id: id, initial_state: merged_state)

# Router searches for routes matching "change_value" → NONE FOUND

Solution Architecture

Two Valid Approaches

Approach A: Pre-configured Routes at Agent Startup

def create(id, initial_state \\ %{}) do
  # ... state preparation ...
  
  # Define signal routes that map types to action instructions
  routes = [
    {"change_value", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.ChangeValue
    )},
    {"get_status", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.GetStatus
    )},
    {"performance_feedback", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.PerformanceFeedback
    )},
    {"gradient_feedback", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.GradientFeedback
    )}
  ]
  
  # Start agent with explicit routes
  Jido.Agent.Server.start_link(
    agent: __MODULE__.new(id, merged_state),
    routes: routes
  )
end

Approach B: Instruction-based Signals

# Instead of signal types, send instructions directly
signal = Jido.Signal.new!(%{
  type: "instruction",
  data: Jido.Instruction.new!(
    action: Foundation.Variables.Actions.ChangeValue,
    params: %{new_value: 42}
  )
})

We chose Approach A because it provides cleaner API for tests and maintains the signal type semantics we want.

Implementation Details

1. Fixed Agent Creation

Before (Broken):

def create(id, initial_state \\ %{}) do
  # ... state setup ...
  __MODULE__.start_link(id: id, initial_state: merged_state)
end

After (Working):

def create(id, initial_state \\ %{}) do
  # ... state setup ...
  
  # Create signal routes for all supported actions
  routes = build_signal_routes()
  
  # Start with explicit routes 
  {:ok, agent} = __MODULE__.start_link(id: id, initial_state: merged_state)
  
  # Start server with routes
  Jido.Agent.Server.start_link(
    agent: agent,
    routes: routes
  )
end

defp build_signal_routes do
  [
    {"change_value", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.ChangeValue
    )},
    {"get_status", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.GetStatus  
    )},
    {"performance_feedback", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.PerformanceFeedback
    )},
    {"coordinate_agents", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.CoordinateAgents
    )}
  ]
end

2. CognitiveFloat Route Extensions

defp build_signal_routes do
  base_routes = super()  # Get routes from CognitiveVariable
  
  float_routes = [
    {"gradient_feedback", Jido.Instruction.new!(
      action: Foundation.Variables.Actions.GradientFeedback
    )}
  ]
  
  base_routes ++ float_routes
end

3. Test Helper Validation

Our test helpers were already correct - they just needed the agent routes to be configured:

# This was always correct, just needed routes configured
signal = Jido.Signal.new!(%{
  type: "change_value",  # ← Now maps to ChangeValue action via routes
  source: "test",
  data: %{new_value: new_value, requester: :test, context: context}
})

Architectural Insights

1. Jido’s Design Philosophy

Jido follows an explicit configuration over convention approach:

  • Actions must be explicitly registered AND routed
  • Signal types must be explicitly mapped to instructions
  • No automatic routing based on naming conventions

2. Benefits of Explicit Routing

  • Security: Prevents accidental action execution
  • Control: Fine-grained control over which signals trigger which actions
  • Flexibility: Same action can be triggered by multiple signal types
  • Debugging: Clear mapping makes troubleshooting easier

3. Common Pitfalls

  1. Assuming action names auto-route (our mistake)
  2. Forgetting route configuration during agent startup
  3. Mismatching signal types with configured routes
  4. Using instruction signals without understanding the pattern

Testing Strategy

1. Route Configuration Tests

test "agent starts with correct signal routes configured" do
  {:ok, agent_pid} = CognitiveVariable.create("test", %{})
  
  # Verify routes are configured (implementation dependent)
  assert can_handle_signal?(agent_pid, "change_value")
  assert can_handle_signal?(agent_pid, "get_status")
end

2. Signal Routing Tests

test "signals are routed to correct actions" do
  {:ok, agent_pid} = CognitiveVariable.create("test", %{})
  
  # This should now work
  signal = Jido.Signal.new!(%{type: "change_value", data: %{new_value: 42}})
  {:ok, _} = Jido.Agent.Server.cast(agent_pid, signal)
  
  # Verify action was executed
  {:ok, status} = get_agent_status(agent_pid)
  assert status.current_value == 42
end

Performance Considerations

1. Route Lookup Overhead

Explicit routes add a lookup step but are cached in the agent server state, so overhead is minimal.

2. Memory Usage

Each agent instance stores its own route configuration. For thousands of agents, consider shared route configurations.

3. Route Compilation

Routes could be pre-compiled at build time for better performance in production systems.

Lessons Learned

1. Read the Source, Luke

The Jido documentation doesn’t clearly explain the routing requirements. Reading the source code revealed the actual architecture.

2. Test with Working Examples

Finding working examples in the test suite was crucial to understanding the correct pattern.

3. Explicit is Better than Implicit

Jido’s explicit routing requirement initially seemed verbose but provides better control and debuggability.

4. Agent Architecture Complexity

Proper Jido agents require multiple layers:

  • Agent behavior implementation
  • Action registration
  • Route configuration
  • Server startup with routes

Future Improvements

1. Route Generation Macros

defmodule CognitiveVariable do
  use Jido.Agent
  use Foundation.AutoRoutes  # Generate routes automatically from actions
end

2. Route Validation

Add compile-time validation that all configured routes point to registered actions.

3. Dynamic Route Updates

Support adding/removing routes at runtime for adaptive agent behavior.

Conclusion

The “No matching handlers found for signal” error was caused by missing explicit route configuration in our Jido agents. Actions are not automatically routed - they require explicit mapping from signal types to action instructions.

This was a fundamental misunderstanding of Jido’s architecture, but once corrected, provides a robust foundation for intelligent agent-based ML parameter coordination.

Key Takeaway: In Jido, action registration enables instruction execution, but signal routing requires explicit route configuration. Both are necessary for complete agent functionality.

Implementation Status

  • Root cause identified: Missing explicit route configuration
  • Solution designed: Pre-configured routes at agent startup
  • 🔄 Implementation in progress: Updating agent creation code
  • Testing pending: Verification of working signal routing

This analysis resolved a critical architectural issue in the Foundation Variables Jido-native implementation, enabling proper signal-to-action routing for intelligent ML parameter coordination.