Skip to content
Beta. VectorStep is in beta.Let us know if something breaks.

Choosing an executor for external calls

Three step types can all make a step do something outside the pipeline itself: executor: gateway (an LLM, optionally with MCP tools), executor: webhook (an HTTP request), and executor: shell (a local script). All three are, in the narrow sense, “capable” of calling a REST API — nothing stops a shell script from opening a socket. Capability isn’t the question that decides between them. Trust model and response shape are.

Start with: does this step need to reason, or just fetch?

Section titled “Start with: does this step need to reason, or just fetch?”

If the step’s job is to interpret something — decide what an alert means, draft a message, judge whether data looks anomalous — you need an LLM: executor: gateway. Give the agent an MCP tool grant (tools:) if it needs to call something itself as part of that reasoning; see Executors.

If the step’s job is purely mechanical — fetch a record, POST a status update, run a threshold check — an LLM call is the wrong tool: slower, more expensive, and non-deterministic for a task that has exactly one right answer. That’s the rest of this page.

Between webhook and shell: it’s a trust question, not a capability one

Section titled “Between webhook and shell: it’s a trust question, not a capability one”

executor: webhook — a pipeline author declares a URL, method, headers, and body inline in pipeline YAML. No operator approval needed beyond whatever already lets a pipeline exist at all. parse_json: true parses the response as JSON and exposes its fields to later steps — see Executors — webhook and the tutorial that builds one.

executor: shell — a pipeline can only reference a script by name; an operator must have separately added that exact, checksum-pinned script to security.shell_steps.allowed in config.yaml before any pipeline can use it at all. See Executors — shell and the tutorial that builds one.

Nothing prevents a shell script from making an HTTP call too — there’s no network sandboxing, only CPU/memory/filesize limits and an empty environment (see that tutorial for the gotcha that causes). So in the narrowest sense, shell can do everything webhook can and more. That’s exactly why it’s the wrong default for “call an API”: its trust model requires an operator to pre-vet and pin one specific script before a pipeline author can use it for anything, which is the right bar for “run arbitrary local code” and the wrong bar for “call this URL, I need its JSON fields in my next step” — something that should be as easy to declare as the url:/headers: a pipeline author can already set on any webhook step, with no operator round-trip every time an API’s shape changes or a new endpoint needs calling.

Reach for shell instead when the step genuinely needs local logic an HTTP round-trip can’t express — a deterministic transform, a call to an internal CLI tool, something with no clean single URL. And reach for it when your deployment’s security posture specifically wants every outbound call pinned and reviewed, not just declared — shell’s operator-approval model is strictly tighter than webhook’s “any pipeline author can point this at any URL,” which is a real, deliberate difference worth choosing on purpose rather than defaulting past.

Need Executor
Interpret, judge, draft prose gateway
Call an API, use its JSON response in a later step webhook + parse_json: true
Fire a notification, don’t need the response webhook (parse_json omitted)
Local deterministic logic (transform, threshold check, internal CLI tool) shell
Any outbound call must be operator-vetted and pinned, not just author-declared shell