Skip to content

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.

GateEffect
errorRe-invoke the original execute — the common case
afterDiscard the result and run the node again
beforeNo-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.

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.

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.

SituationAction
The error should propagate; let the framework handle itcontinue
Transient failure, or the environment is fixed nowretry
You know what the call should have returnedinject
This run is finished as far as you are concernedabort
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.