# How to Implement Lifecycle Hooks (on_before_cmd and on_after_cmd) in Jido Agents

> Learn to implement Jido Agent lifecycle hooks on_before_cmd and on_after_cmd execute pure transformations before action normalization and after directive generation.

- Repository: [agentjido/jido](https://github.com/agentjido/jido)
- Tags: how-to-guide
- Published: 2026-03-09

---

**Override the optional `on_before_cmd/2` and `on_after_cmd/3` callbacks in your Jido.Agent module to execute pure transformations before action normalization and after directive generation, ensuring all logic remains side-effect free.**

Jido agents function as immutable data structures, meaning the only way to intercept and modify command processing is through the optional lifecycle callbacks defined in the `Jido.Agent` behaviour. By implementing `on_before_cmd/2` and `on_after_cmd/3` in your agent module, you can inject deterministic transformations at critical execution points without violating the framework's purity constraints. These hooks integrate directly with the `cmd/2` function in [`lib/jido/agent.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent.ex) (lines 815 and 1059), allowing you to enrich actions and directives while maintaining the agent's functional core.

## Understanding the Lifecycle Hook Architecture

The lifecycle hooks are declared as optional callbacks in [`lib/jido/agent.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent.ex) (lines 283-298) with default no-op implementations that simply pass data through. When you create an agent with `use Jido.Agent`, the macro injects `defoverridable` for these functions, allowing your module to replace the defaults.

```elixir
def on_before_cmd(agent, action), do: {:ok, agent, action}
def on_after_cmd(agent, _action, directives), do: {:ok, agent, directives}

```

The **critical constraint** is that both callbacks must remain **pure functions**—they cannot send messages, start processes, or perform I/O. They must return a tuple wrapped in `{:ok, ...}`; any other return value triggers a runtime error.

## Implementing on_before_cmd/2

### When It Runs

The `on_before_cmd/2` callback executes **once** at line 815 in [`lib/jido/agent.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent.ex), immediately after `cmd/2` is called but **before** the action is normalized. This is the ideal location to transform the incoming action tuple or modify the agent's state to inject default parameters or enforce invariants.

### Code Example: Injecting Timestamps

```elixir
defmodule MyApp.Agent do
  use Jido.Agent,
    name: "my_agent",
    schema: [
      last_query: [type: :integer, default: 0]
    ]

  def on_before_cmd(agent, {action, params}) do
    enriched = Map.put(params, :received_at, DateTime.utc_now())
    {:ok, agent, {action, enriched}}
  end
end

```

This hook pattern matches the action as a tuple `{action_module, params_map}`, performs a pure transformation to add metadata, and returns the enriched action wrapped in the required `{:ok, agent, action}` format.

## Implementing on_after_cmd/3

### When It Runs

The `on_after_cmd/3` callback executes **once** after the strategy has produced the final list of directives. According to the source in [`lib/jido/agent.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent.ex) at line 1059, the private helper `__do_after_cmd__/3` invokes this hook, passing the current agent, the original action, and the generated directives.

### Code Example: Validation and Directive Enrichment

```elixir
defmodule MyApp.Agent do
  use Jido.Agent,
    name: "my_agent",
    schema: [
      counter: [type: :integer, default: 0]
    ]

  def on_after_cmd(agent, _action, directives) do
    log_directive = %Jido.Agent.Directive.Emit{
      signal: {:log, "command processed"}
    }

    case Jido.Agent.validate(agent) do
      {:ok, valid_agent} ->
        {:ok, valid_agent, [log_directive | directives]}

      {:error, _reason} ->
        {:ok, agent, directives}
    end
  end
end

```

This example demonstrates adding a derived directive to the list while optionally running pure validation logic. The function must return `{:ok, updated_agent, final_directives}` to satisfy the contract expected by the runtime.

## Complete Implementation with Both Hooks

For agents requiring both pre-processing and post-processing, implement both callbacks in the same module:

```elixir
defmodule MyApp.Agent do
  use Jido.Agent,
    name: "my_agent",
    schema: [
      last_user: [type: :string, default: ""],
      request_count: [type: :integer, default: 0]
    ]

  def on_before_cmd(agent, {action, %{user: user} = params}) do
    new_agent = %{
      agent |
      state: Map.put(agent.state, :last_user, user)
    }

    {:ok, new_agent, {action, params}}
  end

  def on_after_cmd(agent, _action, directives) do
    new_state = Map.update!(agent.state, :request_count, &(&1 + 1))
    
    metric = %Jido.Agent.Directive.Emit{
      signal: {:metric, :requests, new_state.request_count}
    }

    {:ok, %{agent | state: new_state}, [metric | directives]}
  end
end

```

This full implementation captures the issuing user before command processing and emits a metric directive after the strategy completes, all while maintaining pure functional semantics.

## Critical Constraints and Runtime Behavior

### Purity Requirements

Both hooks must be deterministic and side-effect free. The Jido runtime expects these functions to behave like mathematical transformations—given the same inputs, they must always produce the same outputs without altering external state.

### Hard Stops and AgentServer

As documented in [`lib/jido/agent_server.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent_server.ex), lifecycle hooks are **not** executed when a directive returns `{:stop, ...}`. This hard stop mechanism bypasses `on_after_cmd/3` to ensure immediate termination, preventing the after-hook from running during shutdown sequences.

## Summary

- **Override `on_before_cmd/2`** in [`lib/jido/agent.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent.ex) to transform actions before normalization at line 815, returning `{:ok, agent, action}`.
- **Override `on_after_cmd/3`** to modify directives after strategy completion via `__do_after_cmd__/3` at line 1059, returning `{:ok, agent, directives}`.
- **Maintain purity**—hooks must not perform side effects, send messages, or execute I/O operations.
- **Handle the action tuple** `{action_module, params}` in before-hooks and the directives list in after-hooks.
- **Respect hard stops**—when `AgentServer` processes a `{:stop, ...}` directive, the after-hook is skipped.

## Frequently Asked Questions

### Can lifecycle hooks perform side effects like database calls?

No, both `on_before_cmd/2` and `on_after_cmd/3` must remain pure functions. They cannot perform side effects such as sending messages, starting processes, or doing I/O. Any impure operations will violate the Jido agent contract and may cause unpredictable behavior in the `AgentServer` runtime.

### What return format is required for lifecycle hooks?

Both callbacks must return a tuple wrapped in `{:ok, ...}`. Specifically, `on_before_cmd/2` must return `{:ok, agent, action}` and `on_after_cmd/3` must return `{:ok, agent, directives}`. Any other return value will cause a runtime error when invoked by the `cmd/2` function at lines 815 or 1059 in [`lib/jido/agent.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent.ex).

### Do hooks execute when an agent server receives a stop directive?

According to [`lib/jido/agent_server.ex`](https://github.com/agentjido/jido/blob/main/lib/jido/agent_server.ex), lifecycle hooks are **not** run when a directive returns `{:stop, ...}`. The `on_after_cmd/3` hook specifically runs after the strategy produces the final list of directives, but hard stops bypass this mechanism to ensure immediate termination.

### How do I access the action parameters in on_before_cmd?

The action parameter arrives as a tuple `{action, params}` where `action` is the action module and `params` is a map. You can pattern match on this structure to inject or modify values, as shown in the timestamp injection example, before returning the transformed tuple in the `{:ok, agent, {action, modified_params}}` format.