Powered by AppSignal & Oban Pro

Setup

debugging-and-troubleshooting.livemd

%{ title: “Debugging”, description: “Diagnose agent issues with debug modes, event buffers, and structured diagnostics.”, category: :docs, tags: [:docs, :guides, :livebook], order: 171, draft: false }


Setup

Mix.install([
  {:jido, "~> 2.0"}
])

{:ok, _} = Jido.start()

Define a minimal agent for the examples in this guide.

defmodule MyApp.CounterAgent do
  use Jido.Agent,
    name: "counter_agent",
    schema: [
      count: [type: :integer, default: 0],
      status: [type: :atom, default: :idle]
    ]
end

Debug levels

Jido has three instance-wide debug levels that control logging verbosity and event capture across all agents in that instance.

Level Logging Argument display Debug events
:off Configured defaults N/A None
:on :debug Keys only Minimal
:verbose :trace Full values All

Toggle the level at runtime with Jido.debug/1.

Jido.debug(:on)

Check the current level by calling Jido.debug/0.

Jido.debug()

When you need to see full argument values including sensitive fields, disable redaction.

Jido.debug(:on, redact: false)

Turn debug mode off when you are done investigating.

Jido.debug(:off)

> Warning: Never leave redact: false enabled in production. It exposes sensitive values in log output.

Per-agent debug mode

Instance-level debug controls verbosity globally. Per-agent debug enables a ring buffer on a specific AgentServer process that records internal events as they happen.

Enable at start

Pass debug: true when starting the agent server.

{:ok, pid} = Jido.AgentServer.start_link(
  agent: MyApp.CounterAgent,
  debug: true
)

Enable at runtime

Toggle debug mode on an already-running agent server.

:ok = Jido.AgentServer.set_debug(pid, true)

The ring buffer holds up to 500 events. When the buffer fills, the oldest events are dropped.

Reading the event buffer

Retrieve events from the ring buffer with recent_events/2. Events arrive newest-first.

{:ok, events} = Jido.AgentServer.recent_events(pid, limit: 10)

Each event is a map with three keys.

%{
  at: -576460734,
  type: :signal_received,
  data: %{signal_type: "counter.increment"}
}

The :at value is a monotonic timestamp in milliseconds. Use it for relative timing between events, not absolute wall-clock time.

Event types

Type Recorded when
:signal_received A signal arrives at the agent server
:directive_started A directive begins execution

Filter by event type

Pattern match on the event list to find specific issues.

{:ok, events} = Jido.AgentServer.recent_events(pid)

signals = Enum.filter(events, &(&1.type == :signal_received))
directives = Enum.filter(events, &(&1.type == :directive_started))

If debug mode is off, recent_events/2 returns {:error, :debug_not_enabled}.

Instance-level debug status

Inspect the full debug configuration for an instance with Jido.Debug.status/1.

Jido.Debug.status(Jido.Default)

This returns a map with the current level and all active overrides.

%{
  level: :on,
  overrides: %{
    telemetry_log_level: :debug,
    telemetry_log_args: :keys_only,
    observe_log_level: :debug,
    observe_debug_events: :minimal
  }
}

The override keys control specific subsystems.

Key :on value :verbose value Controls
telemetry_log_level :debug :trace Logger level for telemetry events
telemetry_log_args :keys_only :full How action arguments appear in logs
observe_log_level :debug :debug Logger level for the observer
observe_debug_events :minimal :all Which debug events are emitted

Named instances expose this as MyApp.Jido.debug_status/0.

Timeout diagnostics

When Jido.AgentServer.await_completion/2 times out, it returns a structured diagnostic map instead of a bare :timeout error.

{:error, {:timeout, diagnostic}} =
  Jido.AgentServer.await_completion(pid, timeout: 5_000)

The diagnostic map contains five keys.

%{
  hint: "Agent is idle but await_completion is blocking",
  server_status: :idle,
  queue_length: 0,
  iteration: nil,
  waited_ms: 5000
}

Interpreting server status

Status Queue Meaning
:idle Empty Agent finished processing but its state does not match the await condition
:waiting Any Strategy is waiting for an external response (LLM call, HTTP request)
:running Non-empty Agent is still processing directives

An :idle timeout with an empty queue usually means the agent completed its work but did not set the expected status field. Check state.agent.state.status to see where it ended up.

Querying agent state

Inspect a running agent directly without waiting for completion.

{:ok, state} = Jido.AgentServer.state(pid)

The return value is the full AgentServer.State struct. Access the agent’s domain state through state.agent.state.

state.agent.state.count
state.agent.state.status

For a higher-level view, use status/1 which returns a snapshot of the agent’s strategy state along with process metadata.

{:ok, agent_status} = Jido.AgentServer.status(pid)
agent_status.snapshot.status

Config-driven debug

Enable debug mode through application config so it activates automatically when the instance starts. This is useful for development environments where you always want verbose output.

config :my_app, MyApp.Jido, debug: true

Set :verbose for maximum detail.

config :my_app, MyApp.Jido, debug: :verbose

Jido calls Jido.Debug.maybe_enable_from_config/2 during instance initialization. The config value maps directly: true enables :on level, :verbose enables :verbose level, and any other value (or absence) keeps debug off.

Next steps

Now that you can inspect agent internals, explore related operational topics.