This guide explains the optional Livebook helpers in
Jidoka.Kino. The Kino module is not a runtime
dependency: every helper compiles without Kino installed and degrades to a
no-op rendering boundary outside Livebook. The helpers are thin wrappers
around stable Jidoka contracts (Jidoka.inspect/1, Jidoka.preflight/3,
Jidoka.Harness, Jidoka.Turn.Result). By the end you will be able to set up
a notebook, mirror Livebook provider secrets, debug an agent definition,
render a preflight, run a chat cell deterministically, and start an agent
process that survives notebook re-evaluation.
When To Use This
- Use this guide when you want a Livebook to demonstrate, debug, or evaluate a Jidoka agent.
- Use this guide to keep notebook cells idempotent across re-evaluation.
- Do not add
Jidoka.Kinocalls to library code or production callers. These helpers exist for human inspection in notebooks; they render tables and Markdown that have no meaning outside Livebook.
Prerequisites
- A working Jidoka DSL agent. See Getting Started.
- Livebook with
:kinoavailable, when you want rendered output. Without Kino loaded, the helpers still return their data but skip rendering. - For deterministic notebooks: no provider keys are required.
- For live notebooks: a provider key in scope (
OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY) or the matching Livebook secret (LB_*).Jidoka.Kino.load_provider_env/1mirrorsLB_*into the standard provider env so ReqLLM can find it.
Quick Example
A minimum useful notebook cell sequence:
Mix.install([
{:kino, "~> 0.14"},
{:jidoka, "~> 0.1"}
])Jidoka.Kino.setup_notebook(model: "test:notebook-model", check_provider?: false)defmodule Notebook.TimeAgent do
use Jidoka.Agent
agent :notebook_time_agent do
model %{provider: :test, id: "notebook-model"}
instructions "Use a deterministic LLM for the demo."
end
end
{:ok, _inspection} = Jidoka.Kino.debug_agent(Notebook.TimeAgent)Jidoka.Kino.chat("notebook demo", fn ->
llm = fn _intent, _journal ->
{:ok, %{type: :final, content: "Hello from a deterministic notebook."}}
end
Jidoka.turn(Notebook.TimeAgent, "Say hello.", llm: llm)
end)That sequence prints a setup table, a summary of the agent, and a turn result table - all without contacting any provider.
Concepts
╭────────────────────────────╮ ╭─────────────────────────╮
│ Livebook cells │────▶│ Jidoka.Kino │
│ (setup, debug, chat, trace)│ │ thin wrappers │
╰─────────────┬──────────────╯ ╰────┬──────────┬─────────╯
│ │ │
│ ▼ ▼
│ ╭─────────────╮ ╭─────────────╮
│ │ Jidoka │ │ Kino.Render │
│ │ Jidoka.* │ │ tables/md │
│ ╰──────┬──────╯ ╰─────────────╯
│ │
▼ ▼
provider secrets deterministic data
(LB_* mirrored to from Jidoka contracts
OPENAI_API_KEY, etc.) (Spec/Plan/Result/Snapshot)Four concepts cover Kino integration:
- Optional rendering. Every helper returns a stable value first and renders second. If Kino is not loaded, rendering is a no-op and the returned value is unchanged.
- Secret mirroring. Livebook stores secrets under
LB_*env names.load_provider_env/1finds the first matchingLB_*value and mirrors it into the canonical provider env name (OPENAI_API_KEY, etc.). - Repeatable cells.
start_or_reuse/3makes hosted-agent cells idempotent across re-evaluation by reusing an already-registered agent instead of starting a new one. - Stable data shapes. Each helper takes either a module, spec, plan, session, snapshot, or result and projects it through the Jidoka inspection pipeline. The tables you see are derived, not bespoke.
Security / Trust Boundaries
Jidoka.Kinonever persists secrets, never writes to disk, and never changesApplication.put_env/3. The only mutation it performs isSystem.put_env/2to mirror aLB_*secret into the matching provider env name.load_provider_env/1searches an explicit allowlist of env names. The default list is the standard ReqLLM providers; pass a custom list when you need additional names.debug_agent/2,preflight/3, andtimeline/2only project values that already exist inJidoka.Agent.Spec,Turn.Plan,Turn.Result,AgentSnapshot, and the trace event journal. No new data crosses a trust boundary.chat/3withrequire_provider?: trueshort-circuits to an{:error, message}when no provider secret is in scope. This prevents accidental live calls in a deterministic notebook.start_or_reuse/3returns an existing pid when one is registered. Trust that registration the same way you would trust anyJidoka.whereis/2result; do not rely on it as an authentication step.
How To
Step 1: Set Up The Notebook
setup_notebook/1 renders a small status table and returns the same summary
as a plain map. Use it as the first executable cell.
%{
model: model,
provider: provider,
live_provider?: live?
} =
Jidoka.Kino.setup_notebook(
model: "openai:gpt-4o-mini",
check_provider?: true
)Set check_provider?: false when the notebook is intended to be fully
deterministic. The table will then show not required for deterministic notebook instead of probing for credentials.
Step 2: Mirror A Livebook Secret
When the notebook needs a real provider, mirror the Livebook secret first.
{:ok, "LB_OPENAI_API_KEY"} = Jidoka.Kino.load_provider_env()After that call, OPENAI_API_KEY is set in the BEAM env and ReqLLM can
pick it up. Pass a custom list when you want a different precedence:
Jidoka.Kino.load_provider_env(["ANTHROPIC_API_KEY", "LB_ANTHROPIC_API_KEY"])Step 3: Debug An Agent Definition
debug_agent/2 projects a module, plan, session, or result into a table.
{:ok, inspection} = Jidoka.Kino.debug_agent(MyApp.TimeAgent)
inspection.kind
#=> :agentThe returned map is the same value Jidoka.inspect/2 returns - the helper
just renders it. Use it to confirm operations, controls, and timeouts before
running a turn.
Step 4: Preflight Without A Provider
preflight/3 calls Jidoka.preflight/3 and renders a table of prompt
messages, plus a small timeline of preflight events.
{:ok, preflight} =
Jidoka.Kino.preflight(MyApp.TimeAgent, "What time is it in Chicago?")
length(preflight.prompt.messages)
#=> 2This is the cheapest way to confirm the prompt and operation list are wired correctly before you spend a token.
Step 5: Run A Deterministic Chat Cell
chat/3 runs a function, formats the result, and renders a one-row summary
table.
Jidoka.Kino.chat("deterministic time turn", fn ->
llm = fn _intent, _journal ->
{:ok, %{type: :final, content: "Chicago time is 09:30."}}
end
Jidoka.turn(MyApp.TimeAgent, "What time is it in Chicago?", llm: llm)
end)require_provider?: true makes the cell fail fast when no provider key is
in scope. Use it on live-only cells so a missing secret turns into a
predictable error instead of a hang or a partial result.
Step 6: Trace A Run
trace/3 wraps any function call and renders a Jidoka timeline derived from the
result.
result =
Jidoka.Kino.trace("supervised turn", fn ->
Jidoka.turn(MyApp.TimeAgent, "Hello", llm: deterministic_llm())
end)The wrapped result is returned unchanged. Pair with
Jidoka.Kino.timeline/2 or Jidoka.Kino.call_graph/2 when you want a
separate cell to inspect the same data later.
Step 7: Start An Agent Once Per Notebook Session
Plain MyApp.TimeAgent.start(id: "demo") raises on the second cell
evaluation because the id is already registered. start_or_reuse/3 returns
the existing pid instead.
{:ok, pid} =
Jidoka.Kino.start_or_reuse("notebook-time-agent", fn ->
MyApp.TimeAgent.start(id: "notebook-time-agent")
end)The second cell evaluation returns the same pid without restarting the process, which keeps any in-memory session state intact.
Step 8: Render Context Maps Without Leaking Internals
context/3 separates the public and internal halves of a runtime context
map and renders them as two tables.
Jidoka.Kino.context("turn context", %{
public: %{tenant: "acme"},
internal: %{actor_id: "u-1"}
})This is the right surface for notebooks that demonstrate context plumbing without inviting copy-paste of secret values.
Common Patterns
- Pin the model with
model:insetup_notebook/1. The default readsJidoka.Config.default_model/0, which depends on application config. Pinning makes the notebook reproducible across machines. - Set
check_provider?: falsefor deterministic demos. It avoids spurious "missing credentials" rows in the setup table. - Wrap process-hosted cells with
start_or_reuse/3. This is the only reliable way to make hosted-agent notebooks idempotent. - Use
chat/3with deterministic LLMs by default. Reach forrequire_provider?: trueonly when the cell intentionally calls a provider.
Testing
Jidoka.Kino is tested through test/jidoka/kino_test.exs. The same
pattern is the easiest way to confirm a notebook helper behaves the way you
expect locally:
defmodule MyApp.NotebookHelpersTest do
use ExUnit.Case, async: true
test "setup_notebook returns a summary map even without Kino loaded" do
summary =
Jidoka.Kino.setup_notebook(
model: "test:notebook-model",
check_provider?: false,
render?: false
)
assert summary.model == "test:notebook-model"
refute summary.live_provider?
end
test "start_or_reuse returns the existing pid on the second call" do
{:ok, pid_a} =
Jidoka.Kino.start_or_reuse("kino-demo-agent", fn ->
MyApp.TimeAgent.start(id: "kino-demo-agent")
end)
{:ok, ^pid_a} =
Jidoka.Kino.start_or_reuse("kino-demo-agent", fn ->
MyApp.TimeAgent.start(id: "kino-demo-agent")
end)
end
endrender?: false is the most useful test option because it skips the table
rendering path entirely.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
chat/3 returns {:error, message} immediately | require_provider?: true and no *_API_KEY is in scope. | Mirror the secret with load_provider_env/1 or remove the flag for deterministic cells. |
setup_notebook always shows missing <provider> credentials | The Livebook secret is named differently than expected. | Pass provider_env: ["LB_MY_KEY"] to override the lookup list. |
start_or_reuse/3 starts a new agent every evaluation | The id changes between cell runs. | Use a stable string id, not one derived from System.unique_integer/0. |
debug_agent/2 returns {:error, message} | The target is not a Jidoka inspectable value. | Pass a module, spec, plan, session, snapshot, or result. |
| Tables render as raw Markdown lists | The notebook is not running under Livebook. | Expected; outside Livebook, rendering is a no-op and the returned values are still correct. |
Reference
Key modules touched in this guide:
Jidoka.Kino- public surface:setup_notebook/1,debug_agent/2,preflight/3,chat/3,trace/3,timeline/2,context/3,start_or_reuse/3,load_provider_env/1.- Runtime setup helper - notebook summary and provider-env mirroring.
- Chat helper - chat-cell formatting.
- Agent view helper - debug/preflight rendering.
- Trace view helper - trace, timeline, call graph.
Jidoka.Config- default model used bysetup_notebook/1when one is not supplied.
Related Guides
- Getting Started - the smallest DSL agent end to end.
- Inspection And Preflight - the plain
(non-notebook) versions of
inspect/2andpreflight/3. - Tracing And Events - the timeline data structure Kino renders.
- Configuration - where the default model and other knobs come from.
- Jido Process Integration - the underlying
process API used by
start_or_reuse/3.