A process can expose a task to be solved by a human or a machine.
The external action works in a similar way to the child action:
it can have an input and a result_schema.
It parks the instance until a result is submitted.
Example
# approval.genroc.yaml
nameUnique process identifier.: approvalUnique process identifier.
input_schemaJSON Schema used to validate the input payload when starting a new instance.:
typeThe value's JSON type, or a list of types it may take.: objectThe value's JSON type, or a list of types it may take.
propertiesThe named members of an object, each a schema.:
amount: {typeThe value's JSON type, or a list of types it may take.: integerThe value's JSON type, or a list of types it may take.}
requiredWhich properties must be present. A property not listed here may be absent, and reading it yields null.: [amount]
tasksOrdered list of execution tasks. Control advances linearly unless a switch case redirects.:
- idTask identifier, unique within the definition.: reviewTask identifier, unique within the definition.
actionDescribes the action to perform. Omit for switch-only (routing) tasks.:
typeExternal task: parks the instance until an outside caller submits a result. No worker is held.: externalExternal task: parks the instance until an outside caller submits a result. No worker is held.
inputtasks.review.action.input — object{amount}:
amountamount — integer: "$: inputinput → object{amount}.amountinput.amount → integer"
result_schemaJSON Schema the submitted result is validated against. Without it any JSON is accepted.:
typeThe value's JSON type, or a list of types it may take.: objectThe value's JSON type, or a list of types it may take.
propertiesThe named members of an object, each a schema.:
approved: {typeThe value's JSON type, or a list of types it may take.: booleanThe value's JSON type, or a list of types it may take.}
requiredWhich properties must be present. A property not listed here may be absent, and reading it yields null.: [approved]
outputself.result.approved → boolean: "$: selfself → object{result}.resultself.result → object{approved}.approvedself.result.approved → boolean"
switchRequired. Routing: a shorthand ("next", "end", "$task-id") or an ordered list of cases.: endRequired. Routing: a shorthand ("next", "end", "$task-id") or an ordered list of cases.
outputoutput — object{approved}:
approvedapproved — boolean: "$: outputsoutputs → object{review}.reviewoutputs.review → boolean"
Available fields:
| Field | Meaning |
|---|---|
input | What the worker receives; the only data of the instance a worker sees. |
input_schema | Declares that input — the contract a worker reads. |
result_schema | What an answer must look like; without it, any JSON is accepted. |
raises | The error codes a worker may report, with their data schemas. A worker can report only these. |
timeout | How long to wait before failing with external.timeout; waits forever when omitted. |
Let's apply the example process and run it.
> genctl apply
latest: approval v1 (current)
> genctl run approval --set amount=10
started: 5fy765kh approval@v1 (running)
If we get the state of the instance after it has started,
we'll see it has Phase: external.
> genctl get @last
ID: 5fy765kh
Process: approval@v1
Status: running
Task: review
Phase: external
We can resolve the task through the CLI with the signal command.
genctl signal @last --task review --set approved=true
signaled: 5fy765kh task=review (delivered)
Once the task is resolved, the instance continues its execution to completion.
> genctl get 5fy765kh
ID: 5fy765kh
Process: approval@v1
Status: completed
Task: review
Created: 2026-09-24 14:23:27
Updated: 2026-09-24 14:44:51
Output:
approved: true
Listing instances waiting for resolution
You can filter the instances that are waiting for external resolution
with the regular instances command.
> genctl instances --phase external
ID STATUS PROCESS UPDATED CREATED CODE ERROR
3kjfx4vm running·external approval@v1 1m ago 1m ago
02pcayvt running·external approval@v1 1m ago 1m ago
4cqpdx8z running·external approval@v1 1m ago 1m ago
8nte1end running·external approval@v1 1m ago 1m ago
Signals
In the example above we've used the genctl signal command
to submit the result of an external task. Signals can be
submitted at any time (even if the instance is not waiting for one yet).
Genroc puts submitted signals into a queue, and they are picked up
automatically when needed.
Queues
If we want to resolve external tasks in a systematic way, we need a queue and workers resolving the tasks.
We could use the instances filter and pick the instance we want to resolve, but that is problematic when we have multiple workers — they need to know which task is already taken.
Genroc offers a REST API for this.
Claiming
You can use the /claim endpoint, which ensures no other worker
can work on a task at the same time. Claimed tasks are leased to the
worker for lease_ms milliseconds.
Lease renewal
Your worker should regularly prolong its leases (unless you can guarantee it will always finish within a certain time). Lease renewals can be understood as a sort of heartbeat, letting genroc know that the worker is alive.
The /renew endpoint also lets you know if the instance was cancelled
and the result is no longer needed. You can then stop the work (useful
for really long tasks).
Resolving the task
After the work is finished, use the /resolve endpoint with the token
from the claim. Unlike a signal, which waits in a queue until a task asks for it, a resolve
answers exactly the task its token names, so it is refused once that task is answered or the
instance has moved past it. It is accepted while the instance is running, paused or pausing.
Releasing the lease
Hand a claim back to the queue immediately with /release instead of waiting out its lease.