Agent Callbacks
View SourceOverview
Jido Agents provide a rich set of callbacks that allow you to customize behavior at key lifecycle points. These callbacks enable you to implement custom validation logic, manage state transitions, handle errors, and control execution flow.
Core Callbacks
Lifecycle Management
mount/2
Called when the agent server starts up. Use this callback to perform initialization tasks.
defmodule MyAgent do
use Jido.Agent,
name: "my_agent",
schema: [status: [type: :atom]]
def mount(state, _opts) do
# Initialize connections, load configs, etc.
{:ok, state}
end
end
shutdown/2
Called when the agent server is stopping. Use this for cleanup tasks.
def shutdown(state, reason) do
Logger.info("Agent shutting down", reason: reason)
cleanup_resources(state)
{:ok, state}
end
State Management
on_before_validate_state/1
Called before validating any state changes. Useful for preprocessing state attributes.
def on_before_validate_state(agent) do
# Preprocess state before validation
updated_state = Map.update(agent.state, :status, :pending, fn
nil -> :pending
other -> other
end)
{:ok, %{agent | state: updated_state}}
end
on_after_validate_state/1
Called after state validation but before saving changes.
def on_after_validate_state(agent) do
# Post-process validated state
if agent.state.status == :completed do
{:ok, clear_temporary_data(agent)}
else
{:ok, agent}
end
end
Execution Control
on_before_plan/3
Called before planning actions. Allows preprocessing of instructions.
def on_before_plan(agent, instructions, context) do
# Add metadata to context
enhanced_context = Map.put(context, :planned_at, DateTime.utc_now())
case validate_instructions(instructions) do
:ok -> {:ok, agent, enhanced_context}
{:error, reason} -> {:error, reason}
end
end
on_before_run/1
Called before executing planned actions.
def on_before_run(agent) do
case check_preconditions(agent) do
:ok -> {:ok, agent}
{:error, reason} -> {:error, reason}
end
end
on_after_run/3
Called after action execution completes.
def on_after_run(agent, result, unapplied_directives) do
# Process execution results
agent = update_execution_stats(agent, result)
case length(unapplied_directives) do
0 -> {:ok, agent}
n -> Logger.warning("#{n} directives not applied")
{:ok, agent}
end
end
Error Handling
on_error/2
Called when errors occur during execution.
def on_error(agent, reason) do
Logger.error("Agent error", error: reason)
updated_agent = %{agent |
state: %{agent.state | status: :error},
result: %{error: reason}
}
{:ok, updated_agent}
end
Signal Processing
handle_signal/1
Processes incoming signals before routing.
def handle_signal(signal) do
case validate_signal_format(signal) do
:ok ->
enhanced_signal = add_metadata(signal)
{:ok, enhanced_signal}
{:error, reason} ->
{:error, reason}
end
end
transform_result/2
Processes results before returning them.
def transform_result(signal, result) do
case result do
{:ok, data} ->
processed = transform_result(data)
{:ok, processed}
{:error, _} = error ->
error
end
end
Common Patterns
State Preprocessing
defmodule PreprocessingAgent do
use Jido.Agent,
name: "preprocessing_agent",
schema: [
input: [type: :string],
processed: [type: :string]
]
def on_before_validate_state(agent) do
# Ensure input is preprocessed before validation
case agent.state do
%{input: input, processed: nil} when not is_nil(input) ->
processed = String.trim(input)
{:ok, put_in(agent.state.processed, processed)}
_ ->
{:ok, agent}
end
end
end
Execution Guards
defmodule GuardedAgent do
use Jido.Agent,
name: "guarded_agent",
schema: [status: [type: :atom]]
def on_before_run(agent) do
case agent.state.status do
:ready ->
{:ok, agent}
:locked ->
{:error, "Agent is locked"}
other ->
{:error, "Invalid status: #{other}"}
end
end
end
Result Transformation
defmodule TransformingAgent do
use Jido.Agent,
name: "transforming_agent"
def transform_result(_signal, {:ok, result}) do
transformed = %{
data: result,
processed_at: DateTime.utc_now(),
format_version: "1.0"
}
{:ok, transformed}
end
def transform_result(_signal, {:error, _} = error), do: error
end
Comprehensive Error Recovery
defmodule RecoveringAgent do
use Jido.Agent,
name: "recovering_agent",
schema: [
status: [type: :atom],
retry_count: [type: :integer, default: 0],
last_error: [type: :map]
]
def on_error(agent, reason) do
agent = update_in(agent.state.retry_count, &(&1 + 1))
if agent.state.retry_count <= 3 do
# Attempt recovery
{:ok, %{agent |
state: %{agent.state |
status: :retrying,
last_error: %{
reason: reason,
timestamp: DateTime.utc_now()
}
}
}}
else
# Give up after 3 retries
{:ok, %{agent |
state: %{agent.state |
status: :failed
}
}}
end
end
end
Best Practices
Keep Callbacks Focused
- Each callback should have a single responsibility
- Complex logic should be moved to private functions
- Return values should be consistent
Handle All Error Cases
- Use pattern matching for different error scenarios
- Provide meaningful error messages
- Consider implementing recovery strategies
State Management
- Validate state changes thoroughly
- Use schema validations for type safety
- Keep state transitions explicit
Performance Considerations
- Avoid blocking operations in callbacks
- Keep preprocessing lightweight
- Consider using Task for heavy operations
Logging and Monitoring
- Log important state transitions
- Include relevant context in logs
- Use appropriate log levels