Availability: Per-step stopWhen control is currently a TypeScript SDK feature. Omitting stopWhen preserves the normal run-to-completion behavior.

Quick start

Stop after the first completed tool step, inspect what happened, then continue the same session:

import { Agent, isStepCount } from '@autohandai/agent-sdk';

const agent = await Agent.create({ cwd: '.' });

try {
  const inspected = await agent.run(
    'Read package.json, then explain the package.',
    { stopWhen: isStepCount(1) },
  );

  if (inspected.status === 'stopped') {
    console.log(inspected.steps);

    const completed = await agent.run(
      'Continue from the completed tool result.',
    );
    console.log(completed.text);
  }
} finally {
  await agent.close();
}

Continuation is another call to agent.run() on the same agent. The CLI persists the tool result before the SDK evaluates the condition, so the next prompt sees the completed step.

Built-in stop conditions

HelperStops whenValidation
isStepCount(count) The accumulated completed-step count reaches count. count must be a positive integer.
hasToolCall(toolName) The latest completed step called the named tool. toolName must be a non-empty string.
import {
  hasToolCall,
  isStepCount,
} from '@autohandai/agent-sdk';

const result = await agent.run('Inspect the repository safely.', {
  stopWhen: [
    isStepCount(3),
    hasToolCall('write_file'),
  ],
});

When you provide an array, all conditions are evaluated and the run stops when any condition returns true.

Write a custom condition

A StopCondition receives the ordered completed steps and may return a boolean synchronously or asynchronously.

import type { StopCondition } from '@autohandai/agent-sdk';

const stopAfterSuccessfulWrite: StopCondition = async ({ steps }) => {
  const latest = steps.at(-1);

  return latest?.toolResults.some(
    (result) =>
      result.tool === 'write_file' &&
      result.success,
  ) ?? false;
};

If a predicate throws, the SDK first completes the CLI stop handshake at that step boundary and then surfaces the predicate error. This prevents the subprocess from waiting indefinitely for a decision.

Cancel while a condition is pending

run.abort() stays responsive while an asynchronous condition is pending, even if its promise never settles. The SDK keeps consuming CLI events; after the CLI terminates the turn, run.wait() resolves with status: 'aborted' and queued prompts can run.

The SDK ignores late condition results and rejections after cancellation and does not send a decision for the cancelled step. Aborting does not close the agent: a later prompt can continue from the tool results already stored in the session.

The SDK cannot cancel work inside a user-supplied promise. If your condition starts a network request or other external work, give that work its own cancellation and cleanup policy. A predicate error during an active turn still surfaces after the CLI stop handshake; cancellation is not reported as a rejected step decision.

Bundled runtime and package verification

These fixes require both an updated TypeScript SDK and a compatible CLI. The refreshed bundles record CLI revision d0c05a6f in cli/BUILD_INFO.json, with source provenance and SHA-256 checksums for all five targets. If you provide cliPath, that executable must support step control and rate-limit notifications too.

From the TypeScript SDK repository checkout, run bun run test:package. It packs the SDK, installs only production dependencies in an isolated directory, verifies the bundled checksums, and runs examples/29-stop-when.ts using the default native CLI. The shipped example imports @autohandai/agent-sdk, so it does not depend on the unpublished src directory.

The package check covers stopping after read_file, resuming from persisted output, aborting an unresolved predicate, admitting the next prompt, and emitting a typed rate-limit hook without a session retry. The recorded runtime verification used macOS ARM64 and local authentication and OpenAI-compatible HTTP fixtures, not live OpenAI. Cross-compilation and checksum verification do not establish runtime results on the other platforms.

Result and step contract

agent.run() returns a RunResult with status set to completed, aborted, or stopped. Every result includes its buffered events and completed steps.

interface RunResult {
  id: string;
  status: 'completed' | 'aborted' | 'stopped';
  text: string;
  events: SDKEvent[];
  steps: AgentStep[];
}

interface AgentStep {
  stepNumber: number;
  thought?: string;
  toolCalls: AgentStepToolCall[];
  toolResults: AgentStepToolResult[];
}

Tool calls expose their tool name and validated arguments. Tool results expose the tool name, success state, and available output or error.

Observe step boundaries

A controlled run includes a typed step_end event after each completed tool step. The terminal turn_end event reports reason: 'stop_condition', and agent_end completes with a stopped reason.

const run = await agent.send('Inspect the package.', {
  stopWhen: isStepCount(1),
});

for await (const event of run.stream()) {
  if (event.type === 'step_end') {
    console.log(event.step.stepNumber);
    console.log(event.step.toolResults);
  }
}

const result = await run.wait();

Execution semantics

  • Conditions run only after every tool call in the current step finishes and its results are stored in conversation history.
  • A text-only terminal response has no tool-step boundary and completes normally.
  • Completed steps accumulate for the current run and are passed to every condition in order.
  • Stopping pauses the result; it does not close the Agent or discard session history.
  • Use finally to close the agent when the application is finished.

When to use step control

  • Human review: pause after the agent inspects files but before another step begins.
  • Post-write review: inspect a completed write before the next step. This is not a pre-execution permission gate; use tool approvals to prevent an unapproved write.
  • Budgets: cap the number of tool steps performed by one run.
  • Workflow composition: divide a long agent task into explicit, inspectable application stages.