Agent Directives Guide
View SourceOverview
Directives provide a type-safe mechanism for modifying agent behavior and state at runtime. They act as discrete, immutable instructions that tell agents how to change their state or behavior. This guide covers directive types, usage patterns, and state management considerations.
Core Concepts
Directives in Jido fall into two main categories:
Agent Directives: Modify the core agent struct and its internal state
- Queueing instructions
- Managing action registrations
- State transitions
Server Directives: Control server behavior in agent processes
- Process spawning and termination
- Router management
- Event subscription
Agent Directives
Enqueue Directive
The most common directive type, used to add new instructions to an agent's pending queue:
%Directive.Enqueue{
action: :process_data,
params: %{file: "data.csv"},
context: %{user_id: "123"},
opts: [retry: true]
}
RegisterAction Directive
Registers a new action module with the agent:
%Directive.RegisterAction{
action_module: MyApp.Actions.ProcessData
}
DeregisterAction Directive
Removes an action module from the agent:
%Directive.DeregisterAction{
action_module: MyApp.Actions.ProcessData
}
Server Directives
Spawn Directive
Spawns a child process under the agent's supervisor:
%Directive.Spawn{
module: MyWorker,
args: [id: 1]
}
Kill Directive
Terminates a specific child process:
%Directive.Kill{
pid: worker_pid
}
Returning Directives from Actions
Actions can return directives in several ways:
1. Return a Single Directive
defmodule MyAction do
use Jido.Action
def run(_params, _context) do
directive = %Directive.Enqueue{
action: :next_step,
params: %{value: 42}
}
{:ok, %{completed: true}, directive}
end
end
2. Return Multiple Directives
defmodule ChainedAction do
use Jido.Action
def run(_params, _context) do
directives = [
%Directive.Enqueue{action: :validate_data},
%Directive.Enqueue{action: :process_data},
%Directive.Enqueue{action: :save_results}
]
{:ok, %{chain_started: true}, directives}
end
end
3. Return Instructions Directly
Instructions are automatically converted to Enqueue directives:
defmodule InstructionAction do
use Jido.Action
def run(_params, _context) do
instruction = %Instruction{
action: :process_data,
params: %{file: "data.csv"}
}
{:ok, %{setup_complete: true}, instruction}
end
end
State Management Considerations
Stateless Agents
Stateless agents (see Stateless Agents Guide) handle directives differently:
- Will enqueue instructions but not execute them
- Return the full queue for external processing
- Ignore state-modifying directives
- Maintain immutability guarantees
Example of stateless agent behavior:
defmodule StatelessAgent do
use Jido.Agent,
name: "stateless_example",
state_type: :stateless
def run(_params, _context) do
# Instructions are queued but not executed
directive = %Directive.Enqueue{action: :process}
{:ok, %{queued: true}, directive}
end
end
Stateful Agents
Stateful agents (see Stateful Agents Guide) provide full directive support:
- Execute queued instructions
- Apply state modifications
- Handle all directive types
- Maintain execution context
Example of stateful agent behavior:
defmodule StatefulAgent do
use Jido.Agent,
name: "stateful_example",
state_type: :stateful
def run(_params, _context) do
# Instructions are queued and executed
directives = [
%Directive.Enqueue{action: :validate},
%Directive.Enqueue{action: :process},
%Directive.RegisterAction{action_module: NewAction}
]
{:ok, %{processing: true}, directives}
end
end
Directive Processing
When an action returns a directive, it goes through several stages:
- Validation: The directive structure and content are validated
- Classification: Directives are split into agent and server types
- Application: Each directive is applied in order
- State Update: The agent/server state is updated accordingly
Validation Rules
Each directive type has specific validation rules:
# Enqueue requires a valid action atom
validate_directive(%Enqueue{action: nil})
# => {:error, :invalid_action}
# RegisterAction requires a valid module
validate_directive(%RegisterAction{action_module: MyAction})
# => :ok
Error Handling
Directive application uses tagged tuples for consistent error handling:
case Directive.apply_directives(agent, directives) do
{:ok, updated_agent, server_directives} ->
# Handle success
{:error, reason} ->
# Handle error
end
Best Practices
Atomic Changes
- Return related directives together
- Keep directive changes focused and minimal
Validation
- Always validate input parameters
- Use strict typing for directive fields
Error Handling
- Implement compensation logic for failures
- Handle partial directive application
Testing
- Test directive validation
- Verify state changes
- Check error conditions
Common Patterns
Sequential Operations
Chain multiple operations using Enqueue directives:
directives = [
%Directive.Enqueue{action: :validate_input},
%Directive.Enqueue{action: :process_data},
%Directive.Enqueue{action: :save_results}
]
Dynamic Action Registration
Register actions based on runtime conditions:
def run(%{feature_enabled: true} = _params, _context) do
directive = %Directive.RegisterAction{
action_module: MyApp.Actions.FeatureAction
}
{:ok, %{}, directive}
end
Worker Management
Manage worker processes with spawn/kill directives:
def run(%{worker_count: count} = _params, _context) do
directives = for i <- 1..count do
%Directive.Spawn{
module: MyApp.Worker,
args: [id: i]
}
end
{:ok, %{}, directives}
end
Future Considerations
The directive system is designed for extensibility. Future directives might include:
- Mode changes
- Verbosity controls
- Router management
- Skill management
- Dispatcher configuration