genroc

docs / Guides / Process definition

External tasks

Tasks resolved from outside.

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:

FieldMeaning
inputWhat the worker receives; the only data of the instance a worker sees.
input_schemaDeclares that input — the contract a worker reads.
result_schemaWhat an answer must look like; without it, any JSON is accepted.
raisesThe error codes a worker may report, with their data schemas. A worker can report only these.
timeoutHow 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.