How to Implement State Operations (SetState, ReplaceState, DeletePath) in Jido
Jido implements state operations as pure data structures that actions return alongside results, separating state mutation logic from side-effect directives.
Jido, an Elixir agent framework, treats state mutations as first-class operations through the Jido.Agent.StateOp and Jido.Agent.StateOps modules. When you implement state operations like SetState, ReplaceState, and DeletePath, you work with pure structs that strategies apply consistently to agent state. This design keeps your action's cmd/2 function pure while enabling complex state transformations.
Understanding Jido State Operations Architecture
Jido separates state operations from directives to maintain clean boundaries between mutation logic and runtime effects.
StateOp Structs Definition
The Jido.Agent.StateOp module in lib/jido/agent/state_op.ex defines five primary structs for state manipulation:
%StateOp.SetState{}– Deep-merges attributes into current state%StateOp.ReplaceState{}– Replaces the entire state map%StateOp.DeleteKeys{}– Removes top-level keys%StateOp.SetPath{}– Inserts values at nested paths%StateOp.DeletePath{}– Removes values from nested paths
These structs wrap raw data, making state intentions explicit and testable.
StateOps Application Logic
The Jido.Agent.StateOps module in lib/jido/agent/state_ops.ex contains the execution engine. The apply_state_ops/2 function iterates over operation lists, pattern-matching each struct and applying the corresponding transformation to the agent's state map.
Available State Operations
Jido provides five distinct operations for granular state control.
SetState – Deep Merge Attributes
SetState performs a deep merge of attributes into the existing state using State.merge/2. Use this when updating multiple fields without overwriting unrelated data.
alias Jido.Agent.StateOp
# Returns operation that merges %{counter: 10} into current state
%StateOp.SetState{attrs: %{counter: 10, metadata: %{version: "2.0"}}}
ReplaceState – Full State Replacement
ReplaceState substitutes the entire state map with a new one via Map.replace/3 semantics. Use this for resets or when replacing state entirely.
# Completely replaces agent state with new map
%StateOp.ReplaceState{state: %{status: :initialized, config: %{}}}
DeleteKeys – Remove Top-Level Keys
DeleteKeys drops specified keys from the state root using Map.drop/2. This operation only affects top-level keys.
# Removes :tmp and :debug keys from state
%StateOp.DeleteKeys{keys: [:tmp, :debug]}
SetPath – Nested Value Insertion
SetPath walks nested maps and inserts values at specific paths using the private deep_put_in/3 helper. This enables surgical updates to deeply nested configuration or session data.
# Sets config.timeout to 5000 without touching other config keys
%StateOp.SetPath{path: [:config, :timeout], value: 5_000}
DeletePath – Nested Key Removal
DeletePath removes entries at nested paths using pop_in/2. This targets specific nested fields without affecting parent map structure.
# Removes session.token from nested structure
%StateOp.DeletePath{path: [:session, :token]}
Implementing State Operations in Actions
Actions in Jido return a three-element tuple: {:ok, result, [state_operations]}. The result map merges into state automatically, while the operation list passes to StateOps.apply_state_ops/2.
defmodule MyApp.Actions.IncrementCounter do
use Jido.Action
alias Jido.Agent.StateOp
def run(_agent, params, _context) do
new_count = params.current + 1
{:ok,
%{value: new_count},
[
%StateOp.SetState{attrs: %{counter: new_count}},
%StateOp.SetPath{path: [:metadata, :last_updated], value: DateTime.utc_now()}
]
}
end
end
Any struct not matching a StateOp type collects as an external directive for the runtime to process, maintaining separation between state changes and side effects.
Applying State Operations Manually
Strategies like the finite-state-machine implementation in lib/jido/agent/strategy/fsm.ex call StateOps.apply_state_ops/2 at lines 332-341 to process operations consistently.
alias Jido.Agent.StateOps
alias Jido.Agent.StateOp
agent = %Jido.Agent{state: %{counter: 0, config: %{timeout: 1_000}, old_key: "data"}}
operations = [
%StateOp.SetState{attrs: %{counter: 5}},
%StateOp.SetPath{path: [:config, :timeout], value: 2_000},
%StateOp.DeleteKeys{keys: [:old_key]},
%StateOp.DeletePath{path: [:config, :deprecated_flag]}
]
{updated_agent, directives} = StateOps.apply_state_ops(agent, operations)
# updated_agent.state contains: %{counter: 5, config: %{timeout: 2_000}}
The function returns a tuple containing the updated agent and any non-state-op directives that the caller must handle.
Summary
- StateOp structs (
lib/jido/agent/state_op.ex) define pure data representations of state mutations including SetState, ReplaceState, DeleteKeys, SetPath, and DeletePath. - StateOps functions (
lib/jido/agent/state_ops.ex) implement the application logic throughapply_state_ops/2, which pattern-matches operations and applies them usingState.merge/2,Map.drop/2, andpop_in/2. - Actions return state operations in the tuple
{:ok, result, [operations]}to maintain pure functions while enabling complex state updates. - The design separates state mutations from directives, ensuring strategies in files like
lib/jido/agent/strategy/fsm.exhandle state consistently across the Jido framework.
Frequently Asked Questions
What is the difference between StateOp and StateOps in Jido?
StateOp (singular) is a module containing struct definitions like %SetState{} and %DeletePath{}, located in lib/jido/agent/state_op.ex. StateOps (plural) is the module containing the execution functions apply_result/2 and apply_state_ops/2 located in lib/jido/agent/state_ops.ex. StateOp defines what to do; StateOps defines how to do it.
How do I update nested state values in Jido?
Use the SetPath operation with a path list. For example, %StateOp.SetPath{path: [:config, :timeout], value: 5000} updates only the nested :timeout key without overwriting the entire :config map. Jido implements this via the private deep_put_in/3 function in state_ops.ex.
Can I combine multiple state operations in a single action return?
Yes. Actions return a list of state operations that apply_state_ops/2 processes sequentially. You can mix SetState, SetPath, DeleteKeys, and DeletePath in the same list. The function applies them in order and returns any unrecognized structs as directives for runtime handling.
Where are state operations processed in the Jido source code?
State operations are processed in lib/jido/agent/state_ops.ex within the apply_state_ops/2 function. Strategies like the FSM strategy in lib/jido/agent/strategy/fsm.ex (lines 332-341) invoke this function to ensure consistent state mutation across different agent behaviors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →