Issue: agentjido/jido_ai#170
Date: 2026-02-23
Jido.AI should not switch ReAct tool execution to auto-apply effects by default.
Instead:
- Keep the default contract data-first (
{:ok, result}/{:error, reason}) for tool outputs. - Preserve any returned effects as structured metadata.
- Add an explicit, opt-in strategy/runtime mode that can apply a constrained subset of effects (first
StateOp, then possibly allowlisted directives).
This gives us ecosystem consistency with core Jido capabilities without breaking current signal contracts, policy normalization, strategy assumptions, or checkpoint/replay behavior.
Core Jido execution already supports {:ok, result, effects}:
Jido.Agent.Strategy.Directapplies effects.Jido.Agent.Strategy.FSMapplies effects.- AgentServer normalizes execution payloads with an explicit
effectsfield.
But current AI tool paths are effectively data-only:
Turnexposes execute results as{:ok, term} | {:error, map}.Turn.execute_internal/6drops 3rd tuple values fromJido.Exec.run/4.- ReAct runner and ToolExec consumers are built around 2-tuple result contracts.
- Policy plugin normalizes result envelopes to 2-tuple forms.
So the current system has a mismatch: core engine is effect-capable; AI tool boundary is not.
Runtime emits tool result data; caller applies effects manually if desired.
Pros:
- Stable and predictable public behavior.
- Compatible with current signal/plugin contracts.
- Safer by default for model-driven tool invocation.
- Cleaner for standalone ReAct streams and checkpoint continuation.
Cons:
- Extra plumbing for advanced workflows that want effect semantics.
- Lower parity with core Jido instruction model.
Runtime applies StateOp/directives returned by tool actions.
Pros:
- Full parity with core Jido execution semantics.
- More powerful and expressive tool actions.
- Less custom orchestration glue for advanced agents.
Cons:
- Broad compatibility blast radius (signals, policy plugin, strategies, tests, docs).
- Potential nondeterminism when multiple tools execute concurrently.
- Larger security/trust surface (model-selected tools can mutate runtime state).
- Harder replay/checkpoint semantics and observability contracts.
- Directly impacted in runner and strategy event payloads.
- Existing assumptions on tuple shapes would need migration.
- Concurrency ordering policy becomes a first-class design concern if effects are applied.
- Uses
Directive.ToolExecandai.tool.resultsignal flow. - Would need aligned envelope changes if effects are attached or applied.
- Many routes currently noop
ai.tool.result. - They remain stable if we keep default behavior unchanged.
- They are affected only if we force new result envelopes globally.
- Currently consume
Turn.run_tools/run_tool_callsas data. - Auto-effect behavior here would be surprising unless explicitly configured.
- Today it enforces normalization around 2-tuple result envelopes.
- Any 3-tuple leak or shape change will get treated as malformed unless policy contracts are updated.
Adopt a staged hybrid:
-
Capture effects, do not auto-apply (default).
Preserve effect payloads as metadata (effects) while keepingresultunchanged. -
Add opt-in effect application at strategy/runtime boundary.
Example flag:apply_tool_effects?: true, defaultfalse. -
Start with
StateOponly.
Apply only recognized state ops in controlled order. -
Gate directives behind allowlist + explicit config.
Do not auto-run arbitrary directives from tool return values. -
Define deterministic semantics before enabling by default.
If multiple tools produce effects in one round, define strict ordering guarantees.
This path preserves current behavior while creating a clean migration path to richer semantics.
If a tool needs to influence state now, do not rely on {:ok, result, effects} in tool actions.
Use one of these:
- Emit structured data in
resultand let the strategy explicitly interpret/apply it. - Move effectful logic into first-class strategy instruction flows where
Jido.Execeffect semantics are already handled. - Keep tool actions side-effect free and perform mutations in deterministic post-tool strategy code.
That is less elegant, but it is currently the safest and most predictable pattern.
- Should tool effect application be considered part of public
Jido.AIcontract, or an advanced internal capability? - If enabled, is ordering by completion time acceptable, or do we need deterministic ordered application?
- Should standalone ReAct and agent-hosted ReAct share identical effect semantics?
- Do we want one unified result envelope across
ai.llm.responseandai.tool.resultthat includes optionaleffects? - Which directive classes, if any, are safe enough to allow from tool-returned effects?
Defaulting to effect-aware tool execution now is high-risk for compatibility and safety.
A data-first default with explicit, constrained opt-in effect application is the best tradeoff for Jido today.