You need to modify runtime side effects (LLM/tool/embed/lifecycle) without breaking strategy semantics.
After this guide, you can add directive behavior while preserving correlation, retries, and signal contracts.
Core Directives
Jido.AI.Directive.LLMStreamJido.AI.Directive.LLMGenerateJido.AI.Directive.LLMEmbedJido.AI.Directive.ToolExecJido.AI.Directive.EmitToolErrorJido.AI.Directive.EmitRequestError
Directive-To-Signal Contract Map
LLMStream/LLMGenerate->ai.llm.delta,ai.llm.response,ai.usageLLMEmbed->ai.embed.resultToolExec->ai.tool.started,ai.tool.resultEmitToolError->ai.tool.result(error payload)EmitRequestError->ai.request.error
Result Envelope Contract
For ai.llm.response and ai.tool.result, data.result should be treated as a canonical triple:
{:ok, payload, effects}{:error, reason, effects}
Legacy 2-tuples may appear at boundaries but are normalized by runtime/policy helpers.
Canonical Error Envelope
Runtime-emitted failures for ai.llm.response and ai.tool.result normalize to:
%{
type: atom(),
message: String.t(),
details: map(),
retryable?: boolean()
}Legacy error shapes may still enter at boundaries, but runtime helpers normalize them before the signal leaves the runtime layer.
details is sanitized to JSON-safe values at this boundary so tuple/pid/ref terms
cannot break downstream envelope encoding.
Tool Result Content Contract
For model follow-up turns, the canonical tool result semantics should be represented in the content body:
- success:
%{ok: true, result: ...} - failure:
%{ok: false, error: %{type: ..., message: ..., details: ..., retryable?: ...}}
Runtime may also preserve native outputs in metadata for adapters and local tooling, but metadata is supplementary and should not be the only place the result meaning exists.
Contract Rules
- Directives describe work; they do not own strategy state transitions.
- Every side effect emits a matching signal with correlation IDs.
- Retry/timeout metadata must remain explicit in directive fields.
- Errors must resolve to structured signal payloads, not silent drops.
Example: ToolExec Fields That Matter
%Jido.AI.Directive.ToolExec{
id: "tool_call_1",
tool_name: "multiply",
arguments: %{a: 2, b: 3},
timeout_ms: 15_000,
max_retries: 1,
retry_backoff_ms: 200,
request_id: "req_123",
iteration: 2
}ToolExec.context reserves one runtime-managed snapshot key for action execution:
:state(canonical, core Jido-compatible)
This key is populated by strategy/runtime orchestration and overrides same-named values from user tool context.
Failure Mode: Deadlock Waiting For Tool Result
Symptom:
- strategy remains in
:awaiting_tool
Fix:
- ensure runtime always emits either
ai.tool.resultorEmitToolError - preserve
idcorrelation from tool call to result signal
Contract Parity Tests
If you change directive fields or emitted signal payloads, update directive/runtime parity tests in the same change.
Defaults You Should Know
LLM*directives support either directmodelormodel_aliasToolExecretries default to0unless set- metadata fields are designed for observability and debugging
- action-origin LLM telemetry shares the canonical
[:jido, :ai, :llm, ...]namespace and is distinguished by metadata such asoriginandoperation
When To Use / Not Use
Use this guide when:
- changing execution semantics, timeout policy, or signal emission behavior
Do not use this guide when:
- changing only strategy heuristics or prompts