Skip to main content
<Task> is the fundamental unit of work in a Smithers workflow. Every task has a unique id and an output destination. Tasks operate in one of three modes:
  • Agent mode — when agent is provided, children are rendered to markdown text and sent as the prompt to the AI agent.
  • Compute mode — when children is a function (() => Row or () => Promise<Row>) and no agent is provided, the function is called at execution time and its return value is used as the output.
  • Static mode — when children is a plain value and no agent is provided, children are treated as the literal output payload and written directly to the database.

Import

import { Task } from "smithers-orchestrator";

Props

PropTypeDefaultDescription
idstring(required)Stable node identity. Must be unique within the workflow. Duplicate ids throw an error at render time.
outputTable | string(required)Where to store the task’s result. Pass a Drizzle table object directly, or a string key that resolves via the schema registry.
outputSchemaz.ZodObjectundefinedOptional Zod schema describing the expected agent output structure. When provided with a React element child, the schema example is auto-injected via a schema prop.
agentAgentLikeundefinedAn AI SDK agent. When present, the task runs in agent mode and children become the prompt.
keystringundefinedReact key for the element (standard React prop).
skipIfbooleanfalseWhen true, the task is skipped during execution.
needsApprovalbooleanfalseWhen true, the runtime pauses before executing and waits for approval.
timeoutMsnumberundefinedMaximum execution time in milliseconds. The task fails if it exceeds this duration.
retriesnumber0Number of retry attempts on failure before the task is marked as failed.
continueOnFailbooleanfalseWhen true, the workflow continues executing subsequent tasks even if this task fails.
labelstringundefinedHuman-readable label for UI display and metadata.
metaRecord<string, unknown>undefinedArbitrary metadata attached to the task descriptor.
childrenstring | Row | (() => Row | Promise<Row>) | ReactNode(required)In agent mode: the prompt text or JSX that renders to markdown. In compute mode: a callback function invoked at execution time. In static mode: the output object or value.

Agent mode

When the agent prop is provided, the task sends its children as a prompt to the AI agent and stores the agent’s response in the output table. Children can be a plain string, a template literal, or a React/MDX element. In all cases, children are rendered to plain markdown text before being passed to the agent.
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

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

// String prompt
<Task id="analyze" output="analyze" agent={codeAgent}>
  {`Analyze the codebase in ${ctx.input.repoPath}`}
</Task>

// MDX/JSX prompt (rendered to markdown)
<Task id="review" output="review" agent={reviewAgent}>
  <ReviewPrompt code={ctx.output("analyze", { nodeId: "analyze" }).code} />
</Task>

Structured output with outputSchema

When you provide outputSchema and your children are a React element, Smithers auto-injects a schema prop into the element containing a JSON example derived from the Zod schema. This lets your MDX prompt template reference {props.schema} to show the agent the expected output format.
import { z } from "zod";

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

<Task
  id="analyze"
  output="analyze"
  agent={codeAgent}
  outputSchema={analysisSchema}
>
  <AnalysisPrompt repo={ctx.input.repoPath} />
</Task>
Inside your AnalysisPrompt MDX component, props.schema is available as a JSON example string that matches the Zod schema shape.

Compute mode

When children is a function and no agent prop is provided, the function is called at execution time (not render time) and its return value becomes the task output. This is useful for running shell commands, calling APIs, performing data transformations, or any deterministic operation that doesn’t need an LLM. The callback can be synchronous or asynchronous. All task props (timeoutMs, retries, continueOnFail, needsApproval, skipIf) work with compute callbacks.
// Sync callback
<Task id="calculate" output="results">
  {() => ({ total: items.length, status: "complete" })}
</Task>

// Async callback — run shell commands
<Task id="validate" output="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 the normal retry/continueOnFail behavior. If timeoutMs is set, the callback is raced against a timeout.

Static mode

When no agent prop is provided and children is not a function, children are treated as the output payload and written directly to the database. This is useful for seeding data, passing constants between tasks, or producing computed output without calling an AI model.
// Object payload
<Task id="config" output="config">
  {{ environment: "production", debug: false }}
</Task>

// Computed payload from upstream output
<Task id="summary" output="summary">
  {{
    total: ctx.output("results", { nodeId: "results" }).count,
    status: "complete",
  }}
</Task>
In static mode, the children value is stored as-is in the staticPayload field of the task descriptor.

Output resolution

The output prop accepts two forms: Drizzle table object (manual API) — pass the table directly. The runtime calls getTableName() to determine the storage destination.
<Task id="step" output="myTable">
  {{ done: true }}
</Task>
String key (schema-driven API) — pass a string that matches a key in the schema registry. The engine resolves the string to the corresponding table at execution time.
<Task id="step" output="myTable">
  {{ done: true }}
</Task>
Both forms are equivalent when the schema is set up correctly. The string form is more concise and works well with schema-driven workflows.

Full example

import { smithers, Workflow, Task, Sequence } from "smithers-orchestrator";
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core";

const inputTable = sqliteTable("input", {
  runId: text("run_id").primaryKey(),
  description: text("description").notNull(),
});

const analyzeTable = sqliteTable(
  "analyze",
  {
    runId: text("run_id").notNull(),
    nodeId: text("node_id").notNull(),
    summary: text("summary").notNull(),
  },
  (t) => ({
    pk: primaryKey({ columns: [t.runId, t.nodeId] }),
  }),
);

const setupTable = sqliteTable(
  "setup",
  {
    runId: text("run_id").notNull(),
    nodeId: text("node_id").notNull(),
    files: text("files", { mode: "json" }).$type<string[]>(),
  },
  (t) => ({
    pk: primaryKey({ columns: [t.runId, t.nodeId] }),
  }),
);

const schema = {
  input: inputTable,
  output: analyzeTable,
  analyze: analyzeTable,
  setup: setupTable,
};

const db = drizzle("./workflow.db", { schema });

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

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

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 timeoutTask fails after timeoutMs milliseconds. Retries if retries > 0.
Agent failureTask fails. If continueOnFail is true, subsequent tasks still execute. If retries > 0, the task is retried up to that many times.
Compute callback throwsTask fails. Follows the same retry and continueOnFail behavior as agent failures.
Compute callback timeoutTask fails after timeoutMs milliseconds. Retries if retries > 0.

Notes

  • Output tables must include runId and nodeId columns. Tasks inside a <Ralph> loop additionally need an iteration column.
  • The id prop is used as the nodeId in the task descriptor and must be unique across all tasks in the workflow. This uniqueness is enforced at render time.
  • In agent mode, the prompt is the final rendered string after all JSX/MDX has been converted to markdown. The rendering uses custom markdown components that produce clean text output (not HTML).
  • The key prop is a standard React prop and does not affect Smithers execution semantics.