Skip to content

Instantly share code, notes, and snippets.

@mikehostetler
Created February 23, 2026 16:02
Show Gist options
  • Select an option

  • Save mikehostetler/842785d46ddbee83aa5e066c44fbb1aa to your computer and use it in GitHub Desktop.

Select an option

Save mikehostetler/842785d46ddbee83aa5e066c44fbb1aa to your computer and use it in GitHub Desktop.
Opinion on ReAct tool effects tradeoff for jido_ai issue #170

Opinion: ReAct Tool Execution Should Stay Data-First, With Opt-In Effect Application

Issue: agentjido/jido_ai#170
Date: 2026-02-23

TL;DR

Jido.AI should not switch ReAct tool execution to auto-apply effects by default.

Instead:

  1. Keep the default contract data-first ({:ok, result} / {:error, reason}) for tool outputs.
  2. Preserve any returned effects as structured metadata.
  3. 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.

Context

Core Jido execution already supports {:ok, result, effects}:

  • Jido.Agent.Strategy.Direct applies effects.
  • Jido.Agent.Strategy.FSM applies effects.
  • AgentServer normalizes execution payloads with an explicit effects field.

But current AI tool paths are effectively data-only:

  • Turn exposes execute results as {:ok, term} | {:error, map}.
  • Turn.execute_internal/6 drops 3rd tuple values from Jido.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.

The Architectural Choice

Option A: Data-Only Runtime

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.

Option B: Effect-Aware Runtime (Auto-Apply Effects)

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.

Ecosystem-Wide Impact

ReAct

  • 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.

Tree of Thoughts

  • Uses Directive.ToolExec and ai.tool.result signal flow.
  • Would need aligned envelope changes if effects are attached or applied.

Adaptive / Other Strategy Wrappers

  • 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.

ToolCalling Actions (CallWithTools, ExecuteTool)

  • Currently consume Turn.run_tools / run_tool_calls as data.
  • Auto-effect behavior here would be surprising unless explicitly configured.

Policy Plugin

  • 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.

Recommendation

Adopt a staged hybrid:

  1. Capture effects, do not auto-apply (default).
    Preserve effect payloads as metadata (effects) while keeping result unchanged.

  2. Add opt-in effect application at strategy/runtime boundary.
    Example flag: apply_tool_effects?: true, default false.

  3. Start with StateOp only.
    Apply only recognized state ops in controlled order.

  4. Gate directives behind allowlist + explicit config.
    Do not auto-run arbitrary directives from tool return values.

  5. 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.

Practical Workaround Today

If a tool needs to influence state now, do not rely on {:ok, result, effects} in tool actions.

Use one of these:

  1. Emit structured data in result and let the strategy explicitly interpret/apply it.
  2. Move effectful logic into first-class strategy instruction flows where Jido.Exec effect semantics are already handled.
  3. 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.

Open Questions for Maintainers

  1. Should tool effect application be considered part of public Jido.AI contract, or an advanced internal capability?
  2. If enabled, is ordering by completion time acceptable, or do we need deterministic ordered application?
  3. Should standalone ReAct and agent-hosted ReAct share identical effect semantics?
  4. Do we want one unified result envelope across ai.llm.response and ai.tool.result that includes optional effects?
  5. Which directive classes, if any, are safe enough to allow from tool-returned effects?

Bottom Line

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment