Crux
API Reference@use-crux/core

Tasks

Reference for task ledgers, task specs, lifecycle methods, workers, statuses, and errors.

import { task, tasks } from "@use-crux/core/tasks";

import type {
  AddTaskInput,
  JsonObject,
  JsonValue,
  Task,
  TaskCompleteArgs,
  TaskEdit,
  TaskId,
  TaskList,
  TaskListStatus,
  TaskSpecRecord,
  TaskStatus,
  TasksHandle,
  TasksInput,
} from "@use-crux/core/tasks";
import type { PlanHandle } from "@use-crux/core/plan";

Overview

tasks() creates a persistent task ledger and returns a command handle. Use dynamic ledgers for runtime-discovered IDs, or defined ledgers with task() specs for typed IDs and schema-backed completion results.

const work = await tasks({
  title: "Launch tasks",
});
const work = await tasks({
  items: {
    research: task("Research channels"),
    draft: task("Draft announcement"),
  },
});

tasks(input?)

Creates a task ledger.

const work = await tasks({
  plan: roadmap,
  title: "Launch tasks",
  metadata: { threadId: "thread-123" },
});
interface TasksInput<TItems = undefined> {
  plan?: PlanHandle | string;
  title?: string;
  items?: TItems;
  metadata?: JsonObject;
}

plan accepts a plan handle or plan ID. Metadata must be JSON-safe.

task(label, options?)

Defines an initial task spec for tasks({ items }). It does not persist anything by itself.

const spec = task("Draft announcement", {
  description: "Write the announcement in markdown.",
  assignee: { agent: "writer" },
  result: z.object({ markdown: z.string() }),
  metadata: { phase: "writing" },
});

tasks.ref(taskListId)

Returns a handle for an existing task ledger without reading the store first.

const work = tasks.ref(taskListId);
const current = await work.get();

tasks.list(options?)

Lists task ledgers from the configured store.

const ledgers = await tasks.list({
  plan: roadmap,
  limit: 20,
});
interface TaskListListOptions {
  plan?: PlanHandle | string;
  metadata?: Record<string, JsonValue>;
  limit?: number;
  cursor?: string;
}

tasks.tool(options?)

Creates a focused prompt tool that persists a new task ledger. After a model call, use created() to get the created handle.

const makeTasks = tasks.tool({
  plan: roadmap,
  title: "Execution tasks",
  template: "Create short task labels with stable snake_case IDs.",
});

await generate(
  prompt({
    system: "Create a task ledger for the plan.",
    tools: { makeTasks },
  }),
  { model, input: {} },
);

const work = makeTasks.created();

TasksHandle

interface TasksHandle<TItems = undefined> {
  readonly id: string;
  get(): Promise<TaskList | null>;
  list(options?: { includeRemoved?: boolean }): Promise<Task[]>;
  getTask(id: TaskId<TItems>): Promise<Task | null>;
  add(
    input: TItems extends TaskSpecRecord ? never : AddTaskInput,
  ): Promise<Task>;
  edit(id: TaskId<TItems>, patch: TaskEdit): Promise<Task>;
  start(id: TaskId<TItems>): Promise<Task>;
  progress(id: TaskId<TItems>, message: string): Promise<Task>;
  complete<TTaskId extends TaskId<TItems>>(
    id: TTaskId,
    ...args: TaskCompleteArgs<TItems, TTaskId>
  ): Promise<Task>;
  fail(id: TaskId<TItems>, error: string): Promise<Task>;
  skip(id: TaskId<TItems>, reason?: string): Promise<Task>;
  cancel(id: TaskId<TItems>, reason?: string): Promise<Task>;
  remove(id: TaskId<TItems>): Promise<void>;
  discard(reason?: string): Promise<void>;
  asContext(options?: TasksContextOptions): Context;
  asTools(): Record<string, ToolDef>;
  worker(id: TaskId<TItems>, options?: TaskWorkerOptions): TaskWorker;
}

Defined ledgers narrow TaskId<TItems> to the keys in items, disable dynamic add(), and infer complete() arguments from each task's result schema. Dynamic ledgers accept arbitrary string IDs and JSON-safe completion results.

Dynamic add() calls require each task ID to be unique within the ledger. Duplicate IDs reject with DuplicateTaskIdError; RecordStore adapters perform this insert atomically so concurrent writers cannot both create the same task ID.

Lifecycle

await work.add({ id: "research", label: "Research channels" });
await work.start("research");
await work.progress("research", "Checking partner launches");
await work.complete("research", { channels: ["partners"] });

Allowed lifecycle methods:

  • start(id): pending to in_progress
  • progress(id, message): writes a progress message and keeps status
  • complete(id, result?): marks work completed
  • fail(id, error): marks work failed
  • skip(id, reason?): marks work skipped
  • cancel(id, reason?): marks work cancelled
  • remove(id): soft-removes the task from active derivation
  • discard(reason?): discards the whole ledger and cancels active pending or in-progress tasks

edit() updates non-status display fields only:

await work.edit("research", {
  label: "Research launch channels",
  metadata: { owner: "growth" },
});

Workers

worker(id) creates a task-scoped handle with context and tools bound to one task.

const worker = work.worker("research", {
  guidelines: "Return source URLs in the result.",
});

prompt({
  use: [roadmap.asContext(), worker.asContext()],
  tools: worker.asTools(),
});

Statuses

type TaskStatus =
  | "pending"
  | "in_progress"
  | "completed"
  | "failed"
  | "skipped"
  | "cancelled";

type TaskListStatus =
  | "pending"
  | "in_progress"
  | "completed"
  | "failed"
  | "cancelled"
  | "discarded";

Ledger status is derived from active task rows:

  • empty ledgers and all-pending ledgers are pending;
  • any in-progress task makes the ledger in_progress;
  • completed or skipped active tasks derive completed;
  • failures derive failed unless work is still in progress;
  • cancellations derive cancelled when there are no failures or in-progress tasks;
  • discard() sets discarded.

Removed tasks are not active.

Errors

Task lifecycle failures throw typed structural errors:

try {
  await work.complete("missing");
} catch (error) {
  if (error instanceof Error && error.name === "TaskNotFoundError") {
    // Read structured fields from the error object.
  }
}

Public task errors:

  • TaskListNotFoundError
  • TaskNotFoundError
  • DuplicateTaskIdError
  • TaskRemovedError
  • TaskListDiscardedError
  • InvalidTaskTransitionError
  • TaskResultValidationError
  • TaskJsonValueError

Types

interface TaskList {
  id: string;
  planId?: string;
  title?: string;
  status: TaskListStatus;
  metadata?: JsonObject;
  createdAt: number;
  updatedAt: number;
  completedAt?: number;
  discardedAt?: number;
  discardReason?: string;
}

interface Task {
  id: string;
  taskListId: string;
  label: string;
  description?: string;
  status: TaskStatus;
  progress?: string;
  assignee?: { agent?: string; model?: string };
  metadata?: JsonObject;
  result?: JsonValue;
  error?: string;
  durationMs?: number;
  createdAt: number;
  updatedAt: number;
  removedAt?: number;
}

On this page