Cyclium.Tool behaviour (Cyclium v0.3.7)

Copy Markdown View Source

Behaviour for tool implementations. Tools are provided by the consuming app and wrapped by ToolExec.

Minimal implementation

defmodule MyApp.Tools.ERP do
  use Cyclium.Tool

  def call(:read_po, args, _ctx), do: {:ok, fetch_po(args["po_id"])}
end

use Cyclium.Tool provides sensible defaults for all optional callbacks. Override any of them as needed.

Summary

Callbacks

Advisory declaration of whether the tool mutates state. This does not drive the approval gate. Whether a planned call is gated for human preview is decided by the strategy's allowed_tool_signatures (the side_effect class — "write" / "external_effect" — matched by Cyclium.Intent.SignatureMatcher), not by this callback, which the runtime never reads at gate time. Setting side_effect?/0 => true alone will NOT gate a write tool; declare the effect in the signature the strategy allows. Keep the two consistent — treat this as the human-readable intent and let a test cross-check it against the signature.

Optional self-description of the tool — its name, side_effect class, constraints, and actions (each with name/description/args) — in the same shape as an allowed_tool_signatures entry. Lets actors introspect tools generically (e.g. to build native tool schemas, or to validate that a tool an actor allows actually exists) instead of re-declaring every signature by hand.

Callbacks

cache_scope(args)

@callback cache_scope(args :: map()) :: binary()

cache_ttl()

@callback cache_ttl() :: non_neg_integer() | :no_cache

call(action, args, ctx)

@callback call(action :: atom(), args :: map(), ctx :: map()) ::
  {:ok, result :: term()} | {:error, reason :: term()}

redact(args)

@callback redact(args :: map()) :: map()

redact_result(result)

@callback redact_result(result :: term()) :: term()

side_effect?()

@callback side_effect?() :: boolean()

Advisory declaration of whether the tool mutates state. This does not drive the approval gate. Whether a planned call is gated for human preview is decided by the strategy's allowed_tool_signatures (the side_effect class — "write" / "external_effect" — matched by Cyclium.Intent.SignatureMatcher), not by this callback, which the runtime never reads at gate time. Setting side_effect?/0 => true alone will NOT gate a write tool; declare the effect in the signature the strategy allows. Keep the two consistent — treat this as the human-readable intent and let a test cross-check it against the signature.

tool_signature()

@callback tool_signature() :: map() | nil

Optional self-description of the tool — its name, side_effect class, constraints, and actions (each with name/description/args) — in the same shape as an allowed_tool_signatures entry. Lets actors introspect tools generically (e.g. to build native tool schemas, or to validate that a tool an actor allows actually exists) instead of re-declaring every signature by hand.

Defaults to nil ("not declared") via use Cyclium.Tool, so it is opt-in per tool: introspection stays generic (MyTool.tool_signature() is always callable) without forcing every existing tool to declare one.