Squidie exposes a small tool boundary for workflow steps that need to talk to external systems.
Contract
Tool adapters implement Squidie.Tools.Adapter and are invoked through
Squidie.Tools.invoke/4.
{:ok, result} =
Squidie.Tools.invoke(MyApp.Tools.SomeAdapter, request, context)The shared contract is:
- request: a map owned by the adapter
- context: a workflow or step context map
- success:
{:ok, %Squidie.Tools.Result{}} - failure:
{:error, %Squidie.Tools.Error{}}
Normalized Result
Squidie.Tools.Result contains:
adapter: the adapter modulepayload: the normalized adapter responsemetadata: adapter metadata such as request method or URL
Normalized Error
Squidie.Tools.Error contains:
adapter: the adapter modulekind: normalized error kindmessage: stable human-readable messagedetails: adapter-specific details in a plain mapretryable?: whether the failure is a reasonable candidate for workflow retry
Steps can convert tool errors into plain maps with
Squidie.Tools.Error.to_map/1 before returning them as workflow step
failures.
HTTP Adapter
Squidie.Tools.HTTP is the first concrete adapter.
Supported request shape:
methodurlheadersparamsbodyjsontimeout
Successful responses are normalized to:
statusheaderstrailersbody
HTTP responses with status >= 400, transport failures, and timeouts are
normalized into Squidie.Tools.Error.
HTTP Runtime Action
Squidie.Step.HTTP wraps the HTTP adapter as a native workflow step for
runtime-authored specs. Hosts expose it through the action registry under a
stable key:
registry = %{
"http.request" => [
module: Squidie.Step.HTTP,
category: "HTTP",
action_opts: [allowed_hosts: ["api.example.test"]],
credential_requirements: [%{name: "billing_api", required?: true}]
]
}The step expects a request map with method plus either url or
url_template. Supported request fields are headers, query_params or
params, body, json, and timeout. URLs must not include userinfo or a
query string; use query_params for query data. url_template placeholders
use {{ name }} syntax and are expanded from the bindings map.
Use Squidie.Step.HTTP.validate_request/1 to validate structural request
configuration without policy. Use validate_request/2 or
validate_action_input/2 with the same host-owned action_opts before
starting a runtime-authored run. The runtime also invokes validate_action_input/2
before appending journal facts for planned runtime-spec attempts.
allowed_hosts is required in action_opts for execution. Credential values
do not belong in the request map; pass host-owned references through
credential_refs and let a host wrapper or transport boundary decide how
references become headers. The reusable action rejects common secret-bearing
headers and payload keys rather than persisting them. Raw string bodies require
allow_body?: true in action_opts.
Successful responses are returned as %{http_response: response} with headers
redacted. Response and error bodies are omitted by default; hosts can opt into
bounded body persistence with persist_response_body?: true and
max_body_bytes: .... HTTP and transport errors are converted to structured
step errors with redacted details; retryable tool errors return {:retry, error}
so normal workflow retry policy remains the only retry scheduler. Redirects are
disabled at the shared HTTP adapter boundary.
Elixir Runtime Action
Squidie.Step.Elixir invokes host-approved Elixir adapters from
runtime-authored specs. Hosts expose the step through the action registry and
provide executable adapter definitions in registry-owned action_opts:
registry = %{
"elixir.run" => [
module: Squidie.Step.Elixir,
category: "Elixir",
input_contract: %{
adapter: %{type: :string, required?: true, enum: ["billing.load_invoice"]},
params: %{type: :map, required?: true}
},
action_opts: [
adapters: %{
"billing.load_invoice" => {Billing.Actions, :load_invoice},
"billing.reprice" => Billing.Actions.Reprice
}
]
]
}Runtime input names only an approved adapter key and a params map:
%{
adapter: "billing.load_invoice",
params: %{invoice_id: "inv_123"}
}The reusable action never loads modules, creates atoms, or selects functions
from runtime-authored text. Start-time validation uses the registry action
options, but persisted runtime specs store only safe adapter metadata. Workers
that execute Elixir runtime actions should pass the same host-owned
action_registry: to Squidie.execute_next/1 so current adapter policy is
resolved at execution.
Hosts should override input_contract when they want editor catalogs to show
the approved adapter choices. Adapter definitions may be a module with run/2
or run/1, a {module, function} tuple, or a keyword/map entry with
:module, :function, display metadata, and optional boolean :enabled?.
Adapter functions return {:ok, map}, {:error, reason}, or
{:retry, reason}.
Retry Boundary
The HTTP adapter disables Req's built-in retry loop.
That keeps retry policy in one place:
- adapters report the first failure
- workflow steps declare retry policy
- Squidie appends the next journal dispatch attempt with the resolved retry visibility time
This keeps transport behavior predictable and avoids stacking HTTP-client retries underneath workflow retries.