Controls are Jidoka's policy layer. They are declared on Agent.Spec and run while a turn is executing.

Boundaries

Jidoka currently supports these control points:

  • input runs before prompt assembly and the first model call.
  • operation runs before a model-requested operation capability executes.
  • output runs after structured result validation and before the turn returns.
  • max_turns bounds model/operation loops.
  • timeout bounds wall-clock turn runtime in milliseconds.

Controls may return:

  • :cont, :allow, or :ok to continue;
  • {:block, reason} to fail deterministically;
  • {:interrupt, reason} to pause when supported by that boundary;
  • {:error, reason} to fail as a control error.

Operation interrupts are durable today. Input/output interrupts are currently reported as errors until those boundaries get resumable wait semantics.

Input Controls

Input controls receive a map with the request, context, metadata, and input text:

defmodule MyApp.NoSecrets do
  use Jidoka.Control, name: "no_secrets"

  @impl true
  def call(%{input: input}) do
    if String.contains?(input, "secret") do
      {:block, :secret_input}
    else
      :cont
    end
  end
end

Declare the control in the agent:

defmodule MyApp.SupportAgent do
  use Jidoka.Agent

  agent :support_agent do
    instructions "Answer support questions tersely."
  end

  controls do
    input MyApp.NoSecrets
  end
end

Operation Controls And Approvals

Operation controls receive Jidoka.Runtime.Controls.OperationContext. This is the safety boundary for tool/action execution.

defmodule MyApp.RequireRefundApproval do
  use Jidoka.Control, name: "require_refund_approval"

  @impl true
  def call(%Jidoka.Runtime.Controls.OperationContext{} = operation) do
    if operation.operation == "refund_order" do
      {:interrupt, :approval_required}
    else
      :cont
    end
  end
end

Attach it to a specific operation:

controls do
  operation MyApp.RequireRefundApproval,
    when: [kind: :action, name: :refund_order]
end

Operation matches can be broad or narrow. Supported match keys are kind, name, source, idempotency, and top-level metadata values:

controls do
  operation MyApp.RequireRefundApproval,
    when: [
      kind: :tool,
      source: :payments,
      idempotency: :unsafe_once,
      metadata: %{risk: "high"}
    ]
end

If an operation control interrupts, the turn hibernates:

{:hibernate, snapshot} =
  Jidoka.turn(MyApp.RefundAgent, "Refund order_123")

review = snapshot.metadata["pending_review"]
approval = Jidoka.Review.Response.approve(review.interrupt_id)

{:ok, result} =
  Jidoka.resume(snapshot, approval: approval)

Operations marked :unsafe_once must have a matching operation control before the agent can compile into a plan. This makes risky work visible during preflight instead of after a model chooses the operation.

Output Controls

Output controls run after any configured structured result schema validates. They receive both the assistant text and result_value:

defmodule MyApp.SafeReply do
  use Jidoka.Control, name: "safe_reply"

  @impl true
  def call(%{result: text, result_value: value}) do
    cond do
      String.contains?(text, "forbidden") -> {:block, :unsafe_reply}
      match?(%{approved: false}, value) -> {:block, :unapproved_result}
      true -> :cont
    end
  end
end

Import Shape

JSON/YAML controls use string refs resolved through registries:

controls:
  max_turns: 8
  timeout: 30000
  inputs:
    - control: no_secrets
  operations:
    - control: require_refund_approval
      when:
        kind: action
        name: refund_order
  outputs:
    - control: safe_reply
{:ok, spec} =
  Jidoka.import(yaml,
    registries: %{
      controls: %{
        "no_secrets" => MyApp.NoSecrets,
        "require_refund_approval" => MyApp.RequireRefundApproval,
        "safe_reply" => MyApp.SafeReply
      }
    }
  )

Testing

Use a fake LLM and local operation capability for deterministic control tests. Existing examples live under:

  • test/integration/controls_integration_test.exs
  • test/integration/human_in_the_loop_integration_test.exs
  • test/support/integration/controls/