Retry & abort
The other two resume actions. retry re-runs a node; abort ends the run.
retry re-invokes the node’s execution from the top, with the same arguments, and gates it
again. Everything before it in the run stays as it was — you are not restarting the agent.
Use it when the failure was about the world, not the value:
- a network blip or a 503 from an upstream API;
- an expired token you just refreshed;
- a database or local service you forgot to start;
- a rate limit you have now waited out;
- a fix you made to the tool’s external dependency (a config file, a seed row).
Because a retried node gates again, you can retry, watch it fail the same way, and then switch
to inject without losing your place.
Where retry is available
Section titled “Where retry is available”| Gate | Effect |
|---|---|
error | Re-invoke the original execute — the common case |
after | Discard the result and run the node again |
before | No-op in most adapters (nothing has run yet — continue is the same thing) |
abort stops the run. It exists as its own action, rather than “just throw an error”, for a
concrete reason.
Use it when:
- the run is clearly a write-off and you do not want it burning more tokens;
- the agent is looping — same tool, same arguments, forever;
- it is about to do something you would rather it did not (an email, a refund, a write);
- you have learned what you needed and the remaining ten steps are irrelevant.
It only works if you pass the signal
Section titled “It only works if you pass the signal”Adapters that own the call path (@graphmind-ai/sdk) wire this up for you. If you are
hand-instrumenting, or using an adapter where you make the model calls, you must pass
ctx.signal into them:
await gm.run('handle-ticket', async (ctx) => { const response = await client.messages.create(params, { signal: ctx.signal }); // ^^^^^^^^^^^^^^^^^^});Without it, an abort stops the loop at the next gate rather than cancelling the request already in flight.
An aborted run is a first-class outcome
Section titled “An aborted run is a first-class outcome”status: 'aborted' is distinct from 'error' on both the run and the node. Aborted runs are
not counted as failures in the run list, and adapters do not emit node.error for a node that
was aborted — you stopped it, it did not break.
Choosing between the four
Section titled “Choosing between the four”| Situation | Action |
|---|---|
| The error should propagate; let the framework handle it | continue |
| Transient failure, or the environment is fixed now | retry |
| You know what the call should have returned | inject |
| This run is finished as far as you are concerned | abort |
Adapter contract
Section titled “Adapter contract”const decision = await session.gate('error', node);switch (decision.action) { case 'continue': throw error; // rethrow the ORIGINAL error case 'retry': return await execute(args); // re-run, gate again case 'inject': return decision.output; // swallow the error case 'abort': throw ctx.signal.reason; // AbortError — terminal}The session has already aborted the controller by the time an abort decision resolves, so
ctx.signal.reason is populated. Full contract in
writing an adapter.