Configure Jidoka with a small set of application defaults. Provider keys stay in the process environment, where ReqLLM reads them at call time.
When To Use This
- Use this guide when you want to change the default model, generation parameters, loop budget, or turn timeout for a host application.
- Use this guide as a reference when wiring
:jidokainto a Phoenix or umbrella project for the first time. - Do not use this guide for per-agent configuration; those values live in the DSL or in the imported spec. See Agent DSL.
- Do not use this guide as a credential-management primer. Jidoka does not own provider auth; that is ReqLLM's responsibility.
Prerequisites
- A working Jidoka DSL agent. See Getting Started.
- A
config/config.exs(and ideallyconfig/runtime.exs) in the host application. - For live turns: provider credentials available in the runtime environment.
Quick Example
A minimal config/config.exs for a Jidoka-backed application:
import Config
config :jidoka,
default_model: "openai:gpt-4o-mini",
default_max_model_turns: 8,
default_turn_timeout_ms: 30_000,
default_generation: %{
params: %{
temperature: 0.0,
max_tokens: 500
}
}
import_config "#{config_env()}.exs"Jidoka.Config reads these keys on first use; agents that do not specify
their own model, generation, max_model_turns, or timeout_ms fall
through to these values.
Concepts
╭───────────────────────────╮
│ config/config.exs │
│ config :jidoka, ... │
╰─────────────┬─────────────╯
│ Application.get_env(:jidoka, key)
▼
╭───────────────────────────╮
│ Jidoka.Config │
│ default_model │
│ default_generation │
│ default_max_model_turns │
│ default_turn_timeout_ms │
╰─────────────┬─────────────╯
│ used by
▼
╭───────────────────────────╮ ╭───────────────────────────╮
│ Jidoka.Agent.Spec │ │ Turn.Plan defaults │
│ (when DSL omits values) │ │ (loop + timeout budget) │
╰───────────────────────────╯ ╰───────────────────────────╯
╭───────────────────────────╮
│ Process environment │
│ OPENAI_API_KEY │
│ ANTHROPIC_API_KEY │
│ GEMINI_API_KEY │
╰─────────────┬─────────────╯
│ read by
▼
╭───────────────────────────╮
│ ReqLLM │
│ per-call provider auth │
╰───────────────────────────╯Three concepts cover the config story:
- Four
:jidokakeys.default_model,default_generation,default_max_model_turns, anddefault_turn_timeout_ms. Each one has a built-in fallback insideJidoka.Config, so the application config block is optional for development. - Provider env vars. Jidoka never reads provider keys; ReqLLM does, at
call time. Setting
OPENAI_API_KEY(or the equivalent for another provider) is enough. - ReqLLM dotenv loading. Jidoka does not implement dotenv loading. ReqLLM
loads
.envby default. Setconfig :req_llm, load_dotenv: falsewhen the host application or deployment platform owns credentials.
Security / Trust Boundaries
- Provider credentials must not appear in
config/*.exs. Compiled config is baked into releases; rotating a secret would force a redeploy and bake the old secret into release artifacts. - Per-agent credentials, tenant ids, and actor data belong in the runtime
context for a turn (
Jidoka.turn(spec, input, context: %{...})), not in the application config. Jidoka.Config.default_model/0resolves the configured model throughReqLLM.model/1. That call validates the model id but does not contact a provider; configuration is still side-effect free.- The four
:jidokakeys are public defaults. Treat them as the floor of agent behaviour: any agent may raise the loop budget or timeout, but no agent should silently lower the floor by relying on missing config. mix releasesnapshots application config at build time. Useconfig/runtime.exsfor any value that should be evaluated at boot, and read provider env vars throughSystem.fetch_env!/1if you must surface them at boot rather than at call time.
How To
Step 1: Set The Default Model
The model is the value most callers want to override. The string form is parsed by ReqLLM at first use.
# config/config.exs
import Config
config :jidoka, default_model: "openai:gpt-4o-mini"An agent that does not specify model "..." inherits this value. To pin a
specific agent, declare it in the DSL:
agent :time_agent do
model "anthropic:claude-3-5-sonnet"
endStep 2: Set Generation Defaults
Generation parameters control sampling. The default favours determinism for tests; relax it for product code that wants creative output.
config :jidoka,
default_generation: %{
params: %{
temperature: 0.7,
max_tokens: 1_024
}
}Jidoka.Config.default_generation/0 normalizes the value through
Jidoka.Agent.Spec.Generation.from_input/1 and raises with a useful error if
the map is malformed.
Step 3: Tune The Loop And Timeout Budget
Two integer keys cap a single turn's work.
config :jidoka,
default_max_model_turns: 6,
default_turn_timeout_ms: 20_000default_max_model_turns is the maximum number of model invocations inside
one tool loop; default_turn_timeout_ms is the wall-clock cap for the turn.
Both round-trip through Jidoka.Config.normalize_positive_integer/2, which
rejects zero and negative values with a clear error.
Route, Retry, And Fall Back Model Calls
Use the per-run model_policy: option when one static model is not sufficient.
The ordered models list supplies the primary model and its fallbacks. The
retry bound applies to each model. Only failures classified as :transient
retry the same model.
model_policy = [
models: ["openai:gpt-4o-mini", "anthropic:claude-3-5-haiku"],
retry: [
max_attempts: 2,
backoff: [type: :exponential, min: 100, max: 1_000]
],
select: fn models, context ->
if Jidoka.Context.get_runtime(context, :prefer_anthropic) do
Enum.reverse(models)
else
models
end
end
]
{:ok, result} =
Jidoka.turn(MyApp.SupportAgent, "Help me",
model_policy: model_policy,
llm_context: %{prefer_anthropic: true}
)The selector runs for every model call. Its context includes the trusted
llm_context: map. Do not put these values in public request context. The
built-in classifier retries transport errors, timeouts, rate limits, and HTTP
5xx errors. Use classify: with a one-argument function or module to replace
this rule.
Each LLM effect result and completion event records :model_attempts. Each
entry has the provider, model, attempt number, status, and failure class. The
successful entry has winner: true. Raw provider errors are not copied into
attempt metadata. Model retry stays inside the LLM effect, so it cannot repeat
a completed operation effect.
An injected custom llm: capability must read the selected model from
intent.payload.model. The built-in ReqLLM capability does this automatically.
Step 4: Layer Env-Specific Overrides
The standard config/{dev,test,prod}.exs layering applies to :jidoka like
any other application:
# config/dev.exs
import Config
config :jidoka, default_model: "openai:gpt-4o-mini"# config/test.exs
import Config
config :jidoka,
default_model: %{provider: :test, id: "model"},
default_generation: %{params: %{temperature: 0.0, max_tokens: 200}},
default_max_model_turns: 4,
default_turn_timeout_ms: 5_000# config/prod.exs
import Config
config :jidoka, default_model: "openai:gpt-4o-mini"The :test model lets the deterministic test path bypass any real provider
client.
Step 5: Evaluate Late-Bound Values At Runtime
config/runtime.exs runs after release start. Use it to source values from
the environment without baking them into the release artifact:
# config/runtime.exs
import Config
if model = System.get_env("JIDOKA_DEFAULT_MODEL") do
config :jidoka, default_model: model
end
if budget = System.get_env("JIDOKA_DEFAULT_MAX_TURNS") do
config :jidoka, default_max_model_turns: String.to_integer(budget)
endStep 6: Configure Provider Credential Loading
Jidoka does not implement a .env loader. ReqLLM is a required runtime
dependency, and it loads .env from the current working directory by default
when the application starts. It adds only variables that are not already in
the system environment.
For production, make this policy explicit and let the host application or deployment platform provide credentials:
# config/runtime.exs
import Config
config :req_llm, load_dotenv: falseIn local development, you can keep ReqLLM's default loading behavior or export the variables in the shell:
export OPENAI_API_KEY=...
export ANTHROPIC_API_KEY=...
Use the deployment platform's secret manager in production (systemd
EnvironmentFile, Fly secrets, AWS Parameter Store, Kubernetes secrets) so
that the BEAM process sees them at boot. Inside Livebook, prefer the LB_*
prefix and mirror with Jidoka.Kino.load_provider_env/1. See
Kino Notebooks.
Common Patterns
- Keep
config.exsminimal. The four:jidokakeys plus animport_config "#{config_env()}.exs"line is enough for most apps. - Pin a deterministic model in
config/test.exs.%{provider: :test, id: "model"}keeps tests fully reproducible. - Lower the loop budget in tests. A short
default_max_model_turnssurfaces runaway loops fast. - Treat provider env vars as out-of-band configuration. Document the
required vars in the host application's README; do not try to encode them
in
:jidokakeys.
What Does Not Belong In :jidoka Config
- Per-agent overrides. Use the DSL:
model "...",generation %{...},controls do max_turns ... end. - Provider credentials. Use the process environment.
- Per-tenant or per-actor values. Use the runtime context passed to
Jidoka.turn/3orJidoka.chat/3. - Operation source registrations (
ash_resources,mcp_endpoints). Those belong in the supervision tree (Jido.MCP.register_endpoint/1) or the agent module. - Live LLM endpoints, request retries, or rate limits. Those live in ReqLLM
config (
config :req_llm, ...) or in the host's own request pipeline.
Testing
The configuration helpers are pure functions over Application.get_env/3,
so they are easy to test in isolation:
defmodule MyApp.JidokaConfigTest do
use ExUnit.Case, async: false
setup do
previous_model = Application.get_env(:jidoka, :default_model)
on_exit(fn ->
if previous_model do
Application.put_env(:jidoka, :default_model, previous_model)
else
Application.delete_env(:jidoka, :default_model)
end
end)
:ok
end
test "model defaults are normalized through ReqLLM" do
Application.put_env(:jidoka, :default_model, "openai:gpt-4o-mini")
assert Jidoka.Config.model_ref(Jidoka.Config.default_model()) == "openai:gpt-4o-mini"
end
test "invalid positive integer raises" do
assert_raise ArgumentError, ~r/invalid default_max_model_turns/, fn ->
Jidoka.Config.normalize_positive_integer!(0, :default_max_model_turns)
end
end
endAvoid async: true for tests that mutate Application env. The setup above
also captures and restores the previous value so the suite does not leak
state.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
ArgumentError: invalid default_model: ... | The configured model string is not parseable by ReqLLM. | Use a provider:id string or a %{provider:, id:} map. |
ArgumentError: invalid default_max_model_turns: ... | The value is zero, negative, or not an integer. | Use a positive integer; strings are parsed when they trim to one. |
{:error, :missing_provider_credentials} from a live turn | The provider env var is not set in the BEAM process. | Export the var before starting the application or set it through your deployment platform. |
| Config changes do not take effect after deploy | config/config.exs is compiled into the release. | Move late-bound values to config/runtime.exs. |
Tests interfere with each other on :jidoka config | A test mutated Application.put_env/3 without an on_exit restore. | Capture the previous value in setup and restore it on exit. |
Reference
Key modules touched in this guide:
Jidoka.Config- typed accessors fordefault_model/0,default_generation/0,default_max_model_turns/0,default_turn_timeout_ms/0, and thenormalize_*helpers.Jidoka.Agent.Spec.Generation- shape of the generation map normalized fromdefault_generation.Jidoka- the facade that reads defaults when a turn does not override them.
Related Guides
- Getting Started - the smallest DSL agent end to end.
- Agent DSL - per-agent overrides for the four defaults.
- Errors And Config Reference - the full list of error structs and config keys.
- Jido Process Integration - supervising
Jidoka agents alongside the default
Jidoka.Jidoruntime. - Kino Notebooks - mirroring
LB_*secrets into provider env vars for Livebook.