Use Jidoka.inspect/2, Jidoka.preflight/3, and Jidoka.project/1 before
spending tokens. They show what the agent compiled to, what prompt would be
sent, and what data shape your UI or tests can consume.
Use This When
- an agent looks correct but behaves unexpectedly at runtime;
- comparing a DSL-authored spec with an imported one;
- writing golden tests against compact, deterministic projections.
Prerequisites
- A working Jidoka agent module (see Getting Started).
- Familiarity with the operation contract from Tools And Operations.
- No provider keys are required; these calls do not contact an LLM.
mix deps.get
mix test
Inspect An Agent
Start with inspect/2, then use preflight/3 to assemble the exact prompt the
next turn would send.
defmodule MyApp.TimeAgent do
use Jidoka.Agent
agent :time_agent do
model "openai:gpt-4o-mini"
instructions "Call local_time when asked for the time."
end
tools do
action MyApp.Tools.LocalTime
end
end
Jidoka.inspect(MyApp.TimeAgent)
#=> %{kind: :agent, module: "MyApp.TimeAgent",
#=> spec: %{id: "time_agent", operations: [%{name: "local_time", ...}], ...},
#=> plan: %{...}}
{:ok, preflight} = Jidoka.preflight(MyApp.TimeAgent, "What time is it?")
preflight.prompt.messages
preflight.prompt.tool_definitions
preflight.diagnosticsNothing was sent over the network. The view shows what Jidoka would send if the turn ran for real.
Concepts
The three functions cover three layers of the data-first runtime.
Jidoka.project/1is the low-level projector. It turns Jidoka data contracts (Agent.Spec,Turn.Plan,Turn.Result,Effect.Journal, etc.) into JSON-friendly maps. Use it when you need raw compact data for tests, traces, or external rendering.Jidoka.inspect/2is the human-facing wrapper. It dispatches on the value's struct and returns a tagged map with a:kindkey plus the most useful fields for that kind. Internally it callsproject/1and often adds a timeline view, a status badge, or related context.Jidoka.preflight/3is the only one that takes a request. It runs the workflow up to the point where the prompt is ready, but stops before the LLM intent or any operation intent is interpreted. The returnedJidoka.Inspection.Preflightstruct shows the normalized agent, plan, request, prompt, events, timeline, and diagnostics.
╭───────────────╮ project ╭───────────────────╮
│ Jidoka.value │─────────────▶│ Stable data map │
╰───────┬───────╯ ╰───────────────────╯
│
│ inspect ╭───────────────────╮
╰─────────────────────▶│ %{kind: ..., ...} │
╰───────────────────╯
╭────────────────╮ preflight ╭──────────────────────╮
│ spec / module │─────────────▶│ Inspection.Preflight │
│ + request_input│ │ - agent │
╰────────────────╯ │ - plan │
│ - request │
│ - prompt │
│ - events │
│ - timeline │
│ - diagnostics │
╰──────────────────────╯Picking The Right Function
| Question | Tool | Effect-free? |
|---|---|---|
| "What did the DSL/import compile to?" | Jidoka.inspect(agent_or_spec) | yes |
| "How does this turn input shape the prompt?" | Jidoka.preflight(agent, input) | yes |
| "What is the deterministic projection of this value?" | Jidoka.project(value) | yes |
| "What happened during the turn that just ran?" | Jidoka.inspect(turn_result) | yes |
| "Replay this snapshot." | Jidoka.resume(snapshot, opts) | no, runs effects |
How To
Step 1: Inspect An Agent Module Or Spec
Jidoka.inspect/2 accepts a DSL agent module, an Agent.Spec, a
Turn.Plan, or any other Jidoka value. For modules and specs it returns a
combined view with the compiled plan attached.
view = Jidoka.inspect(MyApp.TimeAgent)
view.kind
#=> :agent
view.module
#=> "MyApp.TimeAgent"
view.spec.id
#=> "time_agent"
view.spec.operations
#=> [%{name: "local_time", idempotency: :idempotent, metadata: %{...}}]
view.plan.prompt_strategy
#=> :defaultFor raw structs (Effect intents, results, sessions, eval runs) inspect dispatches on the struct and returns the matching tagged view.
Step 2: Preflight A Turn
Jidoka.preflight/3 mirrors Jidoka.turn/3's arguments minus the
capabilities. It validates the context, calls Memory.Runtime.recall/3
(passing through memory_store: and session_id: like a real turn),
and runs Steps.assemble_prompt/1 to build the final messages.
{:ok, preflight} =
Jidoka.preflight(MyApp.TimeAgent, "What time is it?",
context: %{tenant_id: "acme"},
request_id: "req-1"
)
Enum.map(preflight.prompt.messages, & &1.role)
#=> [:system, :user]
preflight.prompt.tool_definitions
|> Enum.map(& &1.name)
#=> ["local_time"]
preflight.request.input
#=> "What time is it?"
preflight.diagnostics
#=> []A non-empty diagnostics list flags issues the prompt assembler noticed
(missing memory entries, oversized tool descriptions, etc.).
Step 3: Inspect Operation Metadata
When you need to confirm controls do operation ... when: [...] end will
match, project the operations:
MyApp.TimeAgent
|> Jidoka.inspect()
|> Map.fetch!(:spec)
|> Map.fetch!(:operations)
|> Enum.map(&Map.take(&1, [:name, :idempotency, :metadata]))The metadata map is the exact shape control when: clauses match
against (:kind, :name, :source, :idempotency, and any free-form
keys).
Step 4: Project Turn Results And Journals
After a turn, project the result for assertions and external rendering.
{:ok, result} = Jidoka.turn(MyApp.TimeAgent, "ping")
projected = Jidoka.project(result)
projected.content
#=> "now"
projected.journal.intent_count
#=> 1Jidoka.inspect(result) returns a richer map with a :timeline and
:status already filled in - useful for log output during development.
Step 5: Inspect A Snapshot Or Session
When a turn hibernates (typically because an operation control
returned {:interrupt, _}), inspect/2 produces a snapshot view that
exposes the cursor, journal, and pending review request.
case Jidoka.turn(MyApp.TimeAgent, "ping") do
{:hibernate, snapshot} ->
view = Jidoka.inspect(snapshot)
view.kind
#=> :snapshot
view.cursor
view.timeline
{:ok, result} ->
Jidoka.inspect(result)
endFor sessions, Jidoka.inspect(session) adds replay metadata, snapshot
count, pending reviews, and the latest cursor. Sessions are documented in
Runtime And Harness; the inspection view is the
debugging entry point for them.
Step 6: Use Inspect For Logging
Because every view is a plain map, it serializes cleanly:
require Logger
Logger.info(MyApp.TimeAgent |> Jidoka.inspect() |> :json.encode())When you only want a subset, lower with Jidoka.project/1 first and
Map.take/2 the keys you care about. This is the recommended pattern for
production traces; inspect/2 is the developer view.
Common Patterns
- Preflight before live calls. The first sanity check for a new agent
is
Jidoka.preflight(agent, "your prompt"). If the messages and tool definitions look right, the live turn is much less likely to surprise you. - Snapshot views in failure logs. When a session hibernates, log the
result of
Jidoka.inspect(snapshot); the timeline plus pending review data is usually enough to diagnose stuck approvals. - Compare DSL and imported specs.
Jidoka.inspect(dsl_module).spec == Jidoka.inspect(imported_spec).specis the simplest parity assertion. - Strip identifiers in golden tests. Use
Jidoka.project/1and then drop generated id fields before snapshotting. - Never
IO.inspect/1raw structs in production. They print implementation detail; the inspection view is designed for callers.
Testing
A typical preflight test asserts both the prompt content and the absence of diagnostics.
defmodule MyApp.PreflightTest do
use ExUnit.Case, async: true
test "assembles a prompt for the time agent" do
{:ok, preflight} = Jidoka.preflight(MyApp.TimeAgent, "What time is it?")
system_message =
Enum.find(preflight.prompt.messages, &(&1.role == :system))
assert system_message.content =~ "Call local_time"
assert Enum.find(preflight.prompt.tool_definitions, &(&1.name == "local_time"))
assert preflight.diagnostics == []
end
test "inspect/1 returns a tagged map" do
view = Jidoka.inspect(MyApp.TimeAgent)
assert view.kind == :agent
assert view.spec.id == "time_agent"
assert is_list(view.spec.operations)
end
endSnapshot tests should generally compare against Jidoka.project/1 output
rather than the full inspect/2 view; projections are smaller and rotate
less between releases.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
Jidoka.inspect(agent) returns a plain projection without :plan | Turn.Plan.new/1 failed for the spec. | Check the :error key in the view; it carries a normalized error from Jidoka.error_to_map/1. |
Jidoka.preflight/3 returns {:error, %Jidoka.Error.Invalid{}} | The supplied context: did not match the agent's context schema. | Either update the context or relax the schema; preflight runs the same validate_context/2 as a real turn. |
Memory does not appear in preflight.prompt | The memory_store: option was not threaded through. | Pass memory_store: store (and session_id: when needed) to preflight/3. |
preflight.diagnostics is non-empty | The prompt assembler flagged a warning (oversized description, missing schema). | Read the diagnostic and adjust the source; warnings here are runtime issues at slightly higher cost. |
Turn result view has no :timeline entries | The turn never made a model or tool call. | Confirm the model returned an operation or final answer. |
Reference
Jidoka- public facade:Jidoka.inspect/2,Jidoka.preflight/3,Jidoka.project/1.Jidoka.Inspection- implementation ofinspect/2and the per-struct dispatchers.Jidoka.Inspection.Preflight- struct returned byJidoka.preflight/3with fieldsagent,plan,request,prompt,events,timeline,diagnostics.Jidoka.Projection- the data-facing companion used byJidoka.project/1.Jidoka.Agent.Spec- the spec inspect views path.
Related Guides
- Agent DSL - what the DSL compiles into, mirrored by inspect views.
- Tools And Operations - reading operation metadata from inspect views to debug control matches.
- Memory - how memory contributions show up in preflight.
- Testing And Evals - using projections in deterministic tests and golden files.