Processes can expose a task to be solved by human or machine.
External task works in a similar way as a child task, it can have specified input and result_schema. It parks the process until it a result is submited.
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"
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 process after it 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 cli with signal command.
genctl signal @last --task review --set approved=true
signaled: 5fy765kh task=review (delivered)
Once resolved, the process continues it’s 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 processes waiting for resolution
You can filter process which 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 genctl signal command
to submit the result of external task. Signals can be
submitted any time (even if the process is not waiting for it).
Genroc puts submitted signals into a queue and it is picked up
automatically when needed.
Queues
If we want to resolve external tasks in a systematic way, we need to have a queue and workers resolving the tasks.
We could use instances filter and pick the process we want to resolve, but that is problematic when we have multiple workers - they need to know which process is already taken.
Genroc offers a REST API for this.
Claiming
You can use /claim endpoint wich will ensure no other worker
can work on it at the same time. Claimed tasks are leased to the
worker for the lease_ms time.
Lease renewal
Your worker should regularly prolong the leases (unless you can guarantee it will always finish in certain time). Lease renewals can be understood as a sort of heartbeat letting genroc know that the worker is alive.
The /renew endpoint will also let you know if the process is cancelled
and the result is no longer needed. You can then kill the worker (usefull
for really long tasks).
Resolving task
After the work is finished, use /resolve endpoint with submitted token
from the claim. Resolve endpoint is different from signal, because it can only
be used with previously claimed task and it requires the process to be waiting for
the result.
Releasing the lease
Hand a claim back to the queue immediately wit /release instead of waiting out its lease.