Directives Runtime Contract

Copy Markdown View Source

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

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:

%{
  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.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