You want to extend strategy behavior without breaking machine/runtime contracts.
After this guide, you can safely modify strategy adapters and preserve signal/directive semantics.
Strategy Modules
Jido.AI.Reasoning.ReAct.StrategyJido.AI.Reasoning.ChainOfThought.StrategyJido.AI.Reasoning.AlgorithmOfThoughts.StrategyJido.AI.Reasoning.TreeOfThoughts.StrategyJido.AI.Reasoning.GraphOfThoughts.StrategyJido.AI.Reasoning.TRM.StrategyJido.AI.Reasoning.Adaptive.Strategy
Each strategy acts as a thin adapter around a state machine and implements:
action_spec/1signal_routes/1snapshot/2init/2cmd/3
Extension Pattern
- Add new action atom and schema in
@action_specs. - Route incoming signal in
signal_routes/1. - Translate to machine message in instruction processing.
- Lift machine directives into runtime directives.
- Keep state updates inside strategy state (
__strategy__).
Example: New Strategy Signal Route
@impl true
def signal_routes(_ctx) do
[
{"ai.react.query", {:strategy_cmd, @start}},
{"ai.llm.response", {:strategy_cmd, @llm_result}},
{"ai.request.error", {:strategy_cmd, @request_error}}
]
endFailure Mode: Contract Drift Between Strategy And Machine
Symptom:
- machine receives unknown event shape
- request never completes
Fix:
- keep translation layer explicit and typed
- update both strategy instruction mapping and machine update clauses together
Defaults You Should Know
- most strategies default model alias to
:fast(resolved viaJido.AI.resolve_model/1) - request error routing is standardized via
ai.request.error - Adaptive delegates to selected strategy and can re-evaluate on new prompts
Algorithm-of-Thoughts Specific Internals
Jido.AI.Reasoning.AlgorithmOfThoughts.Strategy is single-query by design:
- one
Directive.LLMStreamcall per request - machine flow is
idle -> exploring -> completed/error - query signal is
ai.aot.query - plugin run signal is
reasoning.aot.run
AoT result contract is structured:
answerfound_solution?first_operations_consideredbacktracking_stepsraw_responseusagetermination(reason,status,duration_ms)diagnostics
Tree-of-Thoughts Specific Internals
Jido.AI.Reasoning.TreeOfThoughts.Strategy now enforces a structured result contract from the machine:
snapshot.resultis a map (best/candidates/termination/tree/usage/diagnostics)- CLI adapters should project
result.best.contentfor human-readable answer text - full structured payload should be preserved in metadata (
tot_result)
ToT parser flow is JSON-first with regex fallback:
- Parse strict JSON for generation/evaluation outputs
- Fallback to regex parsing
- Retry once (configurable) with repair prompt
- Emit structured diagnostics on terminal parse failure
ToT tool orchestration is strategy-managed:
ai.tool.resultsignals (including error envelopes) are routed back into the strategy- machine progression pauses during tool rounds
- tool effects are applied in original tool-call order after the round is complete
- follow-up LLM calls are issued with assistant tool-call + tool messages
- follow-up tool messages preserve original tool-call order
- round trips are bounded by
max_tool_round_trips
Tool Context State Snapshot Contract
For tool-executing strategy paths, action context includes a state snapshot under:
:state(canonical, core Jido-compatible)
Current behavior by strategy:
- ReAct: snapshot is injected at request start and refreshed between tool rounds after applying allowed
StateOpeffects in deterministic tool-call order. - ToT: snapshot is injected into each
Directive.ToolExeccontext when the tool round is started. - Adaptive: inherits this behavior when delegating to ReAct/ToT.
This key is runtime-managed and overrides same-named entries from user tool_context.
ReAct Context Projection Internals
ReAct now uses explicit separation between core event log and LLM projection:
- Core append-only log:
agent.state[:__thread__](Jido.Thread) - ReAct materialized view:
agent.state[:__strategy__].context(Jido.AI.Context)
Canonical ReAct control surface:
ai.react.context.modifyai.react.steerai.react.inject
Core thread entries emitted by ReAct:
:ai_messagefor user/assistant/tool message lifecycle:ai_context_operationfor context operations (replace,switch)
Pending-input semantics:
- active runs own a per-run
Jido.AI.PendingInputServer ai.react.steerandai.react.injectsynchronously enqueue user-style input there- enqueue success means the input is queued for best-effort delivery, not durably accepted
- queued input is not appended to the core thread on enqueue
- runtime emits
:input_injectedonly when it drains queued input intorun_context - strategy appends a user
:ai_messageonly on:input_injected, so undrained input is not persisted - if a run fails, is cancelled, or exits before drain, queued input is dropped
:request_completedis emitted only after the runtime confirms the pending-input queue is empty and sealed
Deferred semantics:
- If a run is active, context operations are stored as
pending_context_op - Deferred op is applied after terminal event (
completed,failed,cancelled, worker-exit failure)
Projection semantics:
- ReAct projects lane-specific context using
context_ref - Projection starts from latest
replaceanchor and folds subsequentai_messageentries
See full model: Thread-Context Projection Model
When To Use / Not Use
Use this when:
- adding strategy features or new control signals
Do not use this when:
- you only need tool definitions or plugin-level changes