[FEATURE] Expose typed per-agent cancellation handles in dynamic workflow JavaScript

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 25, 2026

Summary

Dynamic workflows expose agent() as an awaited result but do not expose a programmatic handle for inspecting or cancelling that specific in-flight agent. The interactive /workflows UI can stop and restart a selected agent, and TaskStop can stop background agents, so the runtime already has the underlying identity and cancellation capability.

Please expose equivalent typed control to workflow JavaScript.

Verified current behavior

Environment:

  • Claude Code 2.1.220
  • macOS
  • first-party claude-opus-5
  • two parallel agents in one native dynamic workflow

I ran a disposable workflow with two concurrent agents:

  1. stop-target attempted a marked Bash call.
  2. control-peer ran sleep 2; printf 'ORCHESTRATOR_SCOPE_CONTROL_OK\n'.
  3. A PreToolUse hook returned:
{
  "continue": false,
  "stopReason": "Disposable scope probe stopped the target agent."
}

The hook payload correctly included the target workflow agent_id. The target agent stopped before its Bash command, while the already-running peer completed normally and returned ORCHESTRATOR_SCOPE_CONTROL_OK. This confirms that hook cancellation is scoped to the triggering workflow agent in this runtime.

However, Promise.allSettled() reported the stopped agent as fulfilled with an empty string, not rejected or cancelled:

{
  "agent": "stop-target",
  "status": "fulfilled",
  "value": "",
  "reason": null
}

That makes a deliberate cancellation indistinguishable from a legitimate empty result unless every workflow adds an out-of-band control ledger.

Missing capability

A workflow cannot currently:

  • obtain the native agent_id when launching agent();
  • inspect typed state or lastProgressAt while the call is in flight;
  • cancel one in-flight agent() from workflow policy code;
  • distinguish cancellation from a successful empty return value.

Hooks are not a full substitute because they only run at hook boundaries. They cannot asynchronously interrupt an agent that is currently waiting on a model response and has not reached another tool event.

Requested API

Either an agent handle:

const worker = agent.start(prompt, options)
worker.agentId
worker.status
worker.lastProgressAt
await worker.cancel({ reason: 'no progress for 10 minutes' })
const result = await worker.result

or AbortSignal support:

const controller = new AbortController()
const result = agent(prompt, { ...options, signal: controller.signal })
controller.abort('no progress')
await result

Cancellation should reject with a typed error such as WorkflowAgentCancelledError containing agentId and reason. It should preserve parallel peers and already-cached workflow results, matching the current /workflows selected-agent stop behavior.

Why this matters

Long-running research, security review, and adversarial verification workflows need deterministic no-progress policies. A typed cancellation primitive would let saved workflows implement watchdogs without creating a second scheduler or wrapping native Claude agents in external processes, preserving native workflow visibility, pause/resume, token telemetry, and cached progress.

View original on GitHub ↗