# Directives Runtime Contract
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.LLMStream`
- `Jido.AI.Directive.LLMGenerate`
- `Jido.AI.Directive.LLMEmbed`
- `Jido.AI.Directive.ToolExec`
- `Jido.AI.Directive.EmitToolError`
- `Jido.AI.Directive.EmitRequestError`
## Directive-To-Signal Contract Map
- `LLMStream` / `LLMGenerate` -> `ai.llm.delta`, `ai.llm.response`, `ai.usage`
- `LLMEmbed` -> `ai.embed.result`
- `ToolExec` -> `ai.tool.started`, `ai.tool.result`
- `EmitToolError` -> `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:
```elixir
%{
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
```elixir
%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.result` or `EmitToolError`
- preserve `id` correlation 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 direct `model` or `model_alias`
- `ToolExec` retries default to `0` unless 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 as `origin` and `operation`
## 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
## Next
- [Signals, Namespaces, Contracts](signals_namespaces_contracts.md)
- [Security And Validation](security_and_validation.md)
- [Error Model And Recovery](error_model_and_recovery.md)