Agent Workflows let you manage multi-step workflow definitions and run them onto live sessions. Use these endpoints to list, read, create, replace, hide, and delete workflow definitions, dispatch a workflow run onto a session, and snapshot, inspect, cancel, or resume workflow runs. Definitions are realm-scoped to the gateway’s own realm; a client-supplied realm is ignored on definition and hide routes.
The base URL for every endpoint on this page is:
https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.icu
List the workflow definitions visible to the requesting cwd/config_dir (name, summary, step briefs, system flag). The result is realm-scoped to the gateway’s own realm; a client-supplied X-Hoody-Realm/?realm= is ignored.
Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd.
X-Hoody-Config-Dir
header
string
No
Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves (HoodyPaths).
X-Hoody-Container
header
string
No
Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension.
X-Hoody-Realm
header
string
No
Per-request realm selector: "global" or a 24-hex id (also accepted as ?realm=). Rejected (400 realm_scope_unsupported) on active-only / no-realm routes.
realm
query
string
No
Per-request realm selector — the in-query alias of the X-Hoody-Realm header (read only when the header is absent): "global" or a 24-hex id. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes.
Read one workflow definition. Returns the validated definition in the {output, is_error} tool envelope. With ?include_revision=true the output’s first line is revision: r1:<64hex> — the optimistic-concurrency baseline to send back as expected_revision on putWorkflow.
If "true", the tool output’s first line is revision: <opaque> — pass that value as putWorkflow’s expected_revision to guard against concurrent edits; the JSON below it is unchanged. Strictly parsed: exactly one value, "true" or "false"; anything else (empty, "TRUE", "1", repeated) is a 400 bad_request.
Create or replace one workflow definition. The body carries the definition object; the {name} path value is authoritative. The daemon validates the definition strictly before writing atomically (unknown fields rejected, the loader’s structural gate enforced). The system flag is authoritative from the embedded defaults — a caller cannot forge it.
The request was malformed or carried invalid parameters.
Correct the request body or query parameters.
tool_mutation_refused
Tool mutation refused
A sessionless tool run resolved to a mutating tool with no confirmation posture.
Open a session and run the tool there, or supply allow_mutations:true / confirm:true on the sessionless run.
realm_scope_unsupported
Realm scope unsupported
A per-request realm header was supplied to an active-only / global-no-realm RPC.
Omit the realm header on this route, or open a session to scope by realm.
reserved_name
Reserved workflow name
The workflow name collides with a literal /workflows/ route segment (e.g. "runs"), so its definition GET would be permanently shadowed.
Choose a workflow name that is not a reserved /workflows/ route segment.
{
"code":"forbidden",
"message":"request must arrive through the Hoody proxy"
}
Error Code
Title
Description
Resolution
forbidden
Forbidden (not via the Hoody proxy)
Forbidden — the request did not reach the service through the public endpoint.
Reach the agent through hoody-proxy, not by connecting to the container directly.
{
"code":"tool_not_found",
"message":"no tool with that name"
}
Error Code
Title
Description
Resolution
tool_not_found
Tool not found
No tool with the given name exists in the catalogue or in the session’s effective tool list.
List the catalogue or the session’s tools and use a valid name.
{
"code":"realm_unpinned_conditional_write",
"message":"conditional workflow write requires a realm-pinned gateway"
}
Error Code
Title
Description
Resolution
realm_unpinned_conditional_write
Conditional write needs a realm-pinned gateway
An expected_revision workflow upsert was issued through an unpinned agent gateway, which resolves its realm per request and cannot guarantee the baseline read and this write target the same realm.
Use a realm-pinned agent gateway for optimistic-concurrency writes, or save unconditionally by omitting expected_revision. expected_absent (create-only) is always allowed.
{
"code":"payload_too_large",
"message":"request body exceeds the configured size limit"
}
Error Code
Title
Description
Resolution
payload_too_large
Payload too large
The request body exceeds the configured size cap (MaxBodyBytes).
Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests.
{
"code":"rate_limited",
"message":"request rate limit exceeded"
}
Error Code
Title
Description
Resolution
rate_limited
Too many requests
The per-client request rate limit was exceeded.
Honor the Retry-After header and retry; reduce the request rate.
{
"code":"internal_error",
"message":"internal server error"
}
Error Code
Title
Description
Resolution
internal_error
Internal error
An unexpected error occurred while handling the request.
The full workflow definition object (steps, entry_point, summary). Validated strictly server-side before an atomic write.
expected_revision
string
No
The revision: value from getWorkflow (?include_revision=true). If the stored workflow changed since that read, the upsert is refused with [revision_conflict] and nothing is written. Omit to save unconditionally.
expected_absent
boolean
No
Create-only guard: refuse with [already_exists] (writing nothing) if any workflow with this name already exists. Mutually exclusive with expected_revision.
Hide or un-hide a workflow. Hiding is the only way to remove a system workflow from view — system workflows are re-seeded on every boot and can never be deleted. Pass hidden: false in the body to un-hide. Realm-scoped to the gateway’s own realm so a hide stays realm-private.
Snapshot of in-flight and recently-finished workflow runs. The registry exposes Snapshot(), not Subscribe() — poll this endpoint to track a run’s progress; live events also flow on the owning session’s stream.
Get one workflow run by id, including per-step outcomes (step id, status, duration, tokens, and error) that the leaner list snapshot omits. Returns 404 when the run id is no longer retained (evicted or never existed), or belongs to another realm or account.
Dispatch a workflow run onto a live session. Returns a job_id; run_id is null during the brief dispatch window and is populated once the workflow loop registers the run (observe workflow_start on the session’s stream). Run events flow on the owning session’s WS/SSE attach; there is no per-run event bus.
The request was malformed or carried invalid parameters.
Correct the request body or query parameters.
realm_scope_unsupported
Realm scope unsupported
A per-request realm header was supplied to an active-only / global-no-realm RPC.
Omit the realm header on this route, or open a session to scope by realm.
{
"code":"forbidden",
"message":"request must arrive through the Hoody proxy"
}
Error Code
Title
Description
Resolution
forbidden
Forbidden (not via the Hoody proxy)
Forbidden — the request did not reach the service through the public endpoint.
Reach the agent through hoody-proxy, not by connecting to the container directly.
{
"code":"workflow_not_found",
"message":"no workflow named build"
}
Error Code
Title
Description
Resolution
not_found
Not found
The requested resource does not exist.
Verify the path and identifier.
workflow_not_found
Workflow not found
No workflow definition with the requested name is visible to this cwd/config_dir.
List the available workflows (GET /workflows) and use an existing name.
{
"code":"turn_in_flight",
"message":"a turn is already running on this session"
}
Error Code
Title
Description
Resolution
turn_in_flight
Turn in flight
A turn is already running on this session; the single serial turn slot is occupied, so a new turn/workflow run is refused.
Wait for the running turn to finish (observe agent_done on the session stream), then retry.
gate_parked
Gate parked
A confirm/question gate is parked on this session, so a new turn/workflow run is refused until it is answered. The parked gate is surfaced under details.pending_gate.
Answer the parked gate (/confirm or /answer) and retry; read it via GET /sessions/{id} or details.pending_gate.
{
"code":"payload_too_large",
"message":"request body exceeds the configured size limit"
}
Error Code
Title
Description
Resolution
payload_too_large
Payload too large
The request body exceeds the configured size cap (MaxBodyBytes).
Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests.
{
"code":"rate_limited",
"message":"request rate limit exceeded"
}
Error Code
Title
Description
Resolution
rate_limited
Too many requests
The per-client request rate limit was exceeded.
Honor the Retry-After header and retry; reduce the request rate.
{
"code":"internal_error",
"message":"internal server error"
}
Error Code
Title
Description
Resolution
internal_error
Internal error
An unexpected error occurred while handling the request.
Retry; if persistent, inspect the daemon logs.
{
"code":"service_unavailable",
"message":"service unavailable"
}
Error Code
Title
Description
Resolution
service_unavailable
Service unavailable
The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit).
Optional input text fed to the workflow run ($(workflow.prompt)).
inputs
object
No
Optional run-time values for the workflow’s declared input parameters (declared name → string value; resolves to $(input.<name>) in every step). A workflow with a REQUIRED declared parameter cannot run without these. Values must be strings; a non-string value is a 400.
Resume a terminal (failed or cancelled) workflow run on a live session. Committed steps replay from the run’s recorded journal and execution continues live from the first incomplete step. The resuming session must match the run’s realm, owner, working directory, and container binding.