Summary
Add a first-class HTTP step type so workflows can call REST APIs without shelling out through type: script + curl and parsing the response by hand.
Motivation
Lots of workflows need to fetch from or push to an HTTP endpoint between agent calls — pulling issue lists, posting comments, hitting a webhook, calling an internal service for ground-truth data. Today the only options are:
type: script + curl — works but is verbose, has to manually parse JSON in a downstream Jinja2 expression, and loses type information.
- Wrap it in a tool — overkill for simple GET/POST.
A native HTTP step would be deterministic, fast (no subprocess fork), and put typed response data straight into context for routing and downstream prompts.
Proposed shape
agents:
- name: fetch_issue
type: http
method: GET
url: "https://api.github.com/repos/{{ workflow.input.repo }}/issues/{{ workflow.input.number }}"
headers:
Authorization: "Bearer {{ env.GITHUB_TOKEN }}"
Accept: "application/vnd.github+json"
timeout_seconds: 30
retry:
max_attempts: 3
backoff: exponential
routes:
- when: "fetch_issue.output.status_code == 404"
to: $end
- when: "fetch_issue.output.status_code >= 500"
to: notify_oncall
- to: triage_agent
The step's output in context should expose:
status_code: int
headers: dict[str, str]
body: str (raw)
json_body: Any | None (parsed if Content-Type is JSON, else None)
elapsed_ms: float
Optional fields on the step: body / json_body (request body, mutually exclusive), params (query string), follow_redirects: bool, verify_ssl: bool.
Why now
- We already have the surrounding machinery:
retry:, routes:, output:, Jinja2 templating, context accumulation, event emission. The HTTP step plugs into all of it.
- Lives next to
ScriptExecutor in executor/ and uses the same dispatch path in the engine — no engine core changes.
Open questions
- Should
output: schema validation apply to json_body? (Probably yes when present.)
- Default timeout? Suggest 60s to match common HTTP client defaults.
- Do we ship with
httpx (already async-native) or add another dependency? httpx is the obvious choice.
- Auth helpers — start with header-based and let users template in tokens, or add typed
auth: bearer | basic | none upfront?
- Body templating: support both
body: "raw {{ template }}" and json_body: { key: "{{ value }}" }, with the latter auto-serialized.
Acceptance criteria
Summary
Add a first-class HTTP step type so workflows can call REST APIs without shelling out through
type: script+curland parsing the response by hand.Motivation
Lots of workflows need to fetch from or push to an HTTP endpoint between agent calls — pulling issue lists, posting comments, hitting a webhook, calling an internal service for ground-truth data. Today the only options are:
type: script+curl— works but is verbose, has to manually parse JSON in a downstream Jinja2 expression, and loses type information.A native HTTP step would be deterministic, fast (no subprocess fork), and put typed response data straight into context for routing and downstream prompts.
Proposed shape
The step's output in context should expose:
status_code: intheaders: dict[str, str]body: str(raw)json_body: Any | None(parsed ifContent-Typeis JSON, elseNone)elapsed_ms: floatOptional fields on the step:
body/json_body(request body, mutually exclusive),params(query string),follow_redirects: bool,verify_ssl: bool.Why now
retry:,routes:,output:, Jinja2 templating, context accumulation, event emission. The HTTP step plugs into all of it.ScriptExecutorinexecutor/and uses the same dispatch path in the engine — no engine core changes.Open questions
output:schema validation apply tojson_body? (Probably yes when present.)httpx(already async-native) or add another dependency?httpxis the obvious choice.auth: bearer | basic | noneupfront?body: "raw {{ template }}"andjson_body: { key: "{{ value }}" }, with the latter auto-serialized.Acceptance criteria
type: httpaccepted by the YAML schema with the fields abovebody+json_body, missingurl)retry:policy (transient5xxand timeouts retry)status_code, headers, andjson_body.*examples/