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"])}
enduse 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
@callback cache_ttl() :: non_neg_integer() | :no_cache
@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.
@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.