Finish a Response, Continue the Workflow
Updated 6 hours ago • September 11, 2026
Use completeResponse() when the client has everything it needs but your workflow
still has internal work to do—for example, evaluating a conversation after answering
it. This is an advanced response-lifecycle feature. Ordinary workflows can continue
to finish naturally; existing useInterrupt() code does not change.
Completing a response does not complete execution. The response closes once; subsequent nodes, handoffs, model calls, execution limits and telemetry continue. Kortyx runs this work in your application's process. It does not create a worker, make a job durable, or require Studio.
Try it in the example
Start examples/kortyx-nextjs-chat-api-route and open /background. No model key
or Studio installation is needed for this example.
- Click Start example. The answer appears and the connection closes.
- Internal analysis continues briefly. Click Refresh pending reviews.
- The application's separate review section shows a pending question.
- Click Save or Skip. The backend resumes the run and the chat stays closed.
The example uses a server-set, HTTP-only cookie to isolate demo sessions. A real application must derive its scope from authenticated users and authorization rules. If telemetry is configured, inspect the same run and interrupt in Studio. Studio shows Response: Completed independently of execution status and flags an interrupt requested after response completion. Studio has no approval controls.
Put completion after the last response-producing node
Connect respondNode to finishResponseNode in your graph. The first node's
returned state and output are already committed when completion runs.
You can also supply final content directly:
message emits a normal message chunk. data emits a final generic structured-data
chunk; it must be JSON-compatible. It does not implicitly update workflow data.
The node's return still updates internal data and routing, but returned UI content
after completion is not sent to the client. Empty completeResponse() emits no
extra content and never automatically exposes accumulated workflow state.
The call resolves when server-side finalization succeeds. It is not an acknowledgement that the client received the bytes. Repeated calls after successful completion are harmless. If the node fails later, execution can fail even though the response has already completed; inspect execution telemetry for that outcome.
Parallel branches and child workflows
Completion applies to the whole response, not just the calling node. It does not
wait for sibling branches. If both branches contribute to the answer, join them
first and call completeResponse() in the following node. A sibling still running
after completion can continue internally, but its later output is not delivered.
This feature does not add support for parallel child calls.
Only root-workflow nodes can close the response. A workflow reached through
transitionTo is a root handoff; a workflow invoked with useWorkflow() is a child
and cannot close its caller's response. Await response-producing work before
completion; do not leave a structured response partially emitted.
Keep the execution attempt alive
The application hosting integration owns the remaining process lifetime. The
onExecution option receives a promise that settles after the execution attempt
finishes, fails, cancels, or suspends. The promise resolving does not mean the run
succeeded. It lets a host retain the request's background work without keeping the
response stream open.
For a Next.js route:
For a custom transport, pass onExecution to agent.streamChat. Connect it to
your host's lifetime mechanism. Host time limits still apply. A process terminating
during active work is not automatically recovered; durable scheduling is a separate
application responsibility. Suspensions can be resumed from configured durable
execution persistence, such as the existing Redis adapter.
Before completion, the chat request's abortSignal and stream cancellation stop
cooperative execution. After completion, client disconnect no longer cancels it.
Pass a separate executionSignal to agent.streamChat when server-controlled
cancellation must remain effective throughout. Models, tools and useAbortSignal()
receive the live execution signal. Completion never removes execution limits.
Human input after the response closes
Keep the normal hook:
Kortyx persists the suspension. The question cannot travel over the closed chat connection, so your application displays it elsewhere. Nodes do not save resume IDs, register callbacks, or implement approval delivery.
There are two ways to discover it:
- Your backend: call
agent.listInterrupts()using an authorized scope. - Studio: inspect pending interrupts and use their public interrupt IDs in your application's approval tooling. Studio is optional and read-only; telemetry is not authoritative execution state and may arrive late.
The returned array contains public IDs, run/session/workflow/node identifiers,
question/options, creation/expiry timestamps, and afterResponseCompleted. It
contains no private resume handles, execution snapshots, or internal hook metadata.
Only fully prepared, unexpired suspensions are returned.
Resolve an answer through an application-owned endpoint:
getInterrupt includes a private resume handle for server-side use. Never
return that object wholesale to the browser. An ID does not authorize an answer.
Authorize the actor and scope on the backend; agent.resume validates the actual
pending request and claims it once. Another actor may resolve it between lookup
and resume, so handle rejected/stale responses.
The scope must contain a nonempty sessionId, runId, or context filter. Supplied
filters are combined with AND; context values use exact equality. These filters
help enforce application scope but do not authenticate callers. Both built-in
memory and Redis adapters implement discovery. Custom adapters can add the optional
pendingRequests.list capability; unsupported discovery throws explicitly.
Discovery currently enumerates the configured pending store: use a dedicated
application namespace and avoid polling it on every render.
A resumed background run keeps the response closed. It can suspend again, so handle
all agent.resume outcomes and rediscover any later question. Limit exhaustion also
creates a discoverable Continue request; only your authorized application should
approve additional work. Without an approval interface, the run remains suspended
until answered or expired. Configure persistence TTL for your expected review time;
the default execution TTL is 15 minutes.
Checkpoints and entry points
The foreground snapshot contains state available at the call. It cannot include future return values or pretend the calling node has finished. Put completion in the next node when the preceding node's return must be in that snapshot. Explicit final response data is independent of the workflow's final output schema; normal execution output validation still runs when the workflow actually finishes.
Boundaries
Kortyx owns execution, suspension, resume, and configurable execution persistence. Applications own chat archives, business records, approval interfaces, authorization, and background scheduling. Studio observes. This feature introduces no memory system, message database, PostgreSQL requirement, or Studio dependency.