Skip to main content
import { Task } from "smithers-orchestrator";
Three modes of operation:
  • Agentagent provided; children become the prompt.
  • Compute — children is a function, no agent; function is called at execution time.
  • Static — children is a plain value, no agent; value is written directly as output.
For human gates, <Task needsApproval> pauses before execution. For explicit decision nodes, use <Approval>. When needsApproval and async are both set, sequence traversal can continue past the gate, but anything that explicitly depends on this task or reads its output still waits for the approval to resolve.

Props

PropTypeDefaultDescription
idstring(required)Stable node identity. Must be unique within the workflow.
outputz.ZodObject | Table | string(required)Output destination. Zod schema from outputs (recommended), Drizzle table, or string key.
outputSchemaz.ZodObjectundefinedExpected agent output structure. Inferred when output is a Zod schema. When provided with a React JSX element child, a schema prop containing a JSON example is auto-injected.
agentAgentLike | AgentLike[]undefinedAI SDK agent or ordered array [primary, fallback1, ...]. Agents are tried in order on retries.
fallbackAgentAgentLikeundefinedSingle retry fallback agent. Appended to the agent chain.
dependsOnstring[]undefinedExplicit dependency on other task IDs. Task waits until all complete.
needsRecord<string, string>undefinedNamed dependencies. Keys become context keys, values are task IDs.
depsRecord<string, OutputTarget>undefinedTyped render-time dependencies. Each key resolves from the task with the same id, or from a matching needs entry.
allowToolsstring[]undefinedCLI-agent tool allowlist override. Supported by ClaudeCodeAgent, PiAgent, and GeminiAgent.
keystringundefinedStandard React key. No effect on execution.
skipIfbooleanfalseSkip this task.
needsApprovalbooleanfalsePause and wait for approval before executing.
asyncbooleanfalseOnly applies with needsApproval. When true, unrelated downstream flow can continue while approval is pending.
timeoutMsnumberundefinedMax execution time in ms. Task fails on timeout.
retriesnumberInfinityRetry attempts on failure. Default: infinite with exponential backoff. Set to 0 to disable.
noRetrybooleanfalseDisable retries entirely. Equivalent to retries={0}.
retryPolicyRetryPolicy{ backoff: "exponential", initialDelayMs: 1000 }{ backoff?: "fixed" | "linear" | "exponential", initialDelayMs?: number }. Delay capped at 5 minutes.
continueOnFailbooleanfalseWorkflow continues even if this task fails.
cacheCachePolicyundefined{ by?: (ctx) => unknown, version?: string }. Skip re-execution when a cached result with matching key/version exists.
labelstringundefinedHuman-readable label for UI and metadata.
metaRecord<string, unknown>undefinedArbitrary metadata on the task descriptor.
scorersScorersMapundefinedMap of scorer configs to evaluate task output after execution. See Evals & Scorers.
memoryTaskMemoryConfigundefinedPer-task memory integration. { recall?: { namespace?, query?, topK? }, remember?: { namespace?, key? }, threadId? }. recall injects relevant memory fragments into the prompt before execution; remember persists the task output back to memory after success.
heartbeatTimeoutMsnumberundefinedHeartbeat monitoring timeout in ms. If the executing task does not emit a heartbeat within this window the task is considered stale and fails. Useful for long-running agent tasks to detect hangs.
childrenstring | Row | (() => Row | Promise<Row>) | ReactNode | ((deps) => Row | ReactNode)(required)Agent mode: prompt text or JSX rendered to markdown. Compute mode: callback. Static mode: output value. With deps, a render function receiving typed upstream outputs.

Agent mode

import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import AnalyzePrompt from "./prompts/analyze.mdx";
import ReviewPrompt from "./prompts/review.mdx";

const codeAgent = new Agent({
  model: anthropic("claude-sonnet-4-20250514"),
  instructions: "You are a senior software engineer.",
});

<Task id="analyze" output={outputs.analyze} agent={codeAgent}>
  <AnalyzePrompt repoPath={ctx.input.repoPath} />
</Task>

<Task id="review" output={outputs.review} agent={reviewAgent} deps={{ analyze: outputs.analyze }}>
  {(deps) => <ReviewPrompt code={deps.analyze.code} />}
</Task>

Typed deps

<Task id="parse" output={outputs.parsed} agent={parser}>
  <ParsePrompt document={ctx.input.document} />
</Task>

<Task id="summarize" output={outputs.summary} agent={writer} deps={{ parse: outputs.parsed }}>
  {(deps) => <SummaryPrompt extracted={deps.parse.fields} />}
</Task>

CLI tool allowlists

allowTools narrows the tool surface for supported CLI agents on a per-task basis.
<Task
  id="review"
  output={outputs.review}
  agent={claude}
  allowTools={["read", "grep"]}
>
  Review the patch and summarize risks.
</Task>
  • allowTools={[]} disables CLI tools entirely for supported agents.
  • When the workflow run is started with cliAgentToolsDefault: "explicit-only", omitted allowTools behaves like [] for supported CLI agents.
  • Task-level allowTools always wins over the run-level default.
When the upstream task id differs from the dep key, pair deps with needs:
<Task
  id="typed-calls"
  output={outputs.typedCalls}
  agent={builder}
  deps={{ contract: outputs.contractSource }}
  needs={{ contract: "parse-contract" }}
>
  {(deps) => <TypedCallsPrompt contract={deps.contract} />}
</Task>

Structured output with outputSchema

When outputSchema is provided and children are a React element, a schema prop containing a JSON example is auto-injected. The MDX template can reference {props.schema}.
import { z } from "zod";

const analysisSchema = z.object({
  summary: z.string(),
  risk: z.enum(["low", "medium", "high"]),
  files: z.array(z.string()),
});

// When `output` is a Zod schema, outputSchema is inferred automatically.
<Task id="analyze" output={outputs.analysis} agent={codeAgent}>
  <AnalysisPrompt repo={ctx.input.repoPath} />
</Task>

// You can still pass outputSchema explicitly to override:
<Task
  id="analyze"
  output={outputs.analysis}
  agent={codeAgent}
  outputSchema={analysisSchema}
>
  <AnalysisPrompt repo={ctx.input.repoPath} />
</Task>

Heartbeat monitoring

Use heartbeatTimeoutMs to detect stalled long-running agent tasks. The agent must emit heartbeats periodically; if none arrive within the timeout window, the task fails:
<Task
  id="long-migration"
  output={outputs.migration}
  agent={migrationAgent}
  heartbeatTimeoutMs={60_000}
  timeoutMs={3_600_000}
>
  Run the database migration. Report progress periodically.
</Task>
This is distinct from timeoutMstimeoutMs caps total execution time, while heartbeatTimeoutMs detects hangs mid-execution.

Compute mode

Children is a function, no agent. Called at execution time; return value becomes output. Sync or async.
// Sync callback
<Task id="calculate" output={outputs.results}>
  {() => ({ total: items.length, status: "complete" })}
</Task>

// Async callback — run shell commands
<Task id="validate" output={outputs.validate} timeoutMs={30000} retries={1}>
  {async () => {
    const testResult = await $`bun test`.quiet();
    const typeResult = await $`tsc --noEmit`.quiet();
    return {
      testsPass: testResult.exitCode === 0,
      typesPass: typeResult.exitCode === 0,
    };
  }}
</Task>
If the callback throws, the task fails and follows normal retry/continueOnFail behavior.

Static mode

No agent, children is not a function. Value is written directly as output. deps works in static mode.
// Object payload
<Task id="config" output={outputs.config}>
  {{ environment: "production", debug: false }}
</Task>

// Computed payload from upstream output
<Task id="summary" output={outputs.summary} deps={{ results: outputs.results }}>
  {(deps) => ({
    total: deps.results.count,
    status: "complete",
  })}
</Task>

Output resolution

The output prop accepts three forms: Zod schema from outputs (recommended) — type-checked at compile time; resolved to the correct table via zodToKeyName.
const { outputs } = createSmithers({ results: z.object({ done: z.boolean() }) });

<Task id="step" output={outputs.results}>
  {{ done: true }}
</Task>
Drizzle table object — runtime calls getTableName() to determine storage.
<Task id="step" output={myDrizzleTable}>
  {{ done: true }}
</Task>
String key (escape hatch) — not type-checked. Resolved at execution time.
<Task id="step" output="results">
  {{ done: true }}
</Task>

Full example

import { createSmithers } from "smithers-orchestrator";
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const { Workflow, smithers, outputs } = createSmithers({
  analysis: z.object({ summary: z.string() }),
  setup: z.object({ files: z.array(z.string()) }),
});

const codeAgent = new Agent({
  model: anthropic("claude-sonnet-4-20250514"),
  instructions: "You are a senior software engineer.",
});

export default smithers((ctx) => (
  <Workflow name="tasks-demo">
    <Task id="analyze" output={outputs.analysis} agent={codeAgent}>
      {`Analyze: ${ctx.input.description}`}
    </Task>
    <Task id="setup" output={outputs.setup}>
      {{ files: ["README.md", "package.json"] }}
    </Task>
  </Workflow>
));

Custom Drizzle table output (advanced)

import { z } from "zod";
import { sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core";

const auditTable = sqliteTable(
  "audit",
  {
    runId: text("run_id").notNull(),
    nodeId: text("node_id").notNull(),
    status: text("status").notNull(),
    details: text("details").notNull(),
  },
  (t) => ({
    pk: primaryKey({ columns: [t.runId, t.nodeId] }),
  }),
);

const auditSchema = z.object({
  status: z.enum(["ok", "needs-follow-up"]),
  details: z.string(),
});

<Task id="audit" output={auditTable} outputSchema={auditSchema} agent={reviewAgent}>
  Summarize the review outcome for the audit log.
</Task>
Custom Drizzle tables must be created and migrated separately. Define the workflow with createSmithers(...) as usual.

Error handling

ScenarioBehavior
Duplicate idThrows "Duplicate Task id detected: <id>" at render time.
Missing outputThrows "Task <id> is missing output table." at render time.
Agent timeoutFails after timeoutMs. Retries if retries > 0.
Agent failureFails. Retries if retries > 0. Continues if continueOnFail.
Callback throwsSame retry/continueOnFail behavior as agent failures.
Callback timeoutFails after timeoutMs. Retries if retries > 0.

Agent JSON Output Extraction

When outputSchema is set, the engine extracts structured JSON from the agent’s text response using a multi-strategy pipeline:
  1. Code fence extraction — looks for ```json fenced blocks and parses the content.
  2. Balanced brace extraction — finds the outermost {...} using brace-depth counting, handling nested objects correctly.
  3. Last balanced JSON — if multiple JSON objects appear, the last complete one is used (agents often produce the final answer last).
After extraction, the JSON is validated against outputSchema. If validation fails:
  • The engine sends a retry prompt back to the agent describing the schema violation and asking for corrected output.
  • This schema-validation retry happens within the same attempt (it does not consume a retries count).
  • If the agent still fails to produce valid JSON after retries, the attempt fails.

Auth Failure Circuit Breaker

If an agent returns an authentication error (e.g., invalid API key, expired token), the engine short-circuits without retrying. Auth failures are terminal — retrying with the same credentials will not produce a different result.

Non-Idempotent Tool Retry Warnings

When a task is retried (via retries or manual retryTask()), the engine checks whether non-idempotent tools (tools with sideEffect: true and idempotent: false) were called in prior attempts. If so, a warning message is prepended to the agent’s prompt on the next attempt, alerting it that certain side effects may have already occurred.

Notes

  • Custom Drizzle tables must include runId and nodeId columns. Tasks inside <Loop> additionally need iteration. createSmithers(...) adds these automatically.
  • id is the nodeId in the task descriptor; uniqueness is enforced at render time.
  • In agent mode, JSX/MDX children are rendered to markdown (not HTML) before being sent to the agent.