Crux
GuidesPlans & Tasks

Best Practices

Keep Plans & Tasks simple, visible, and easy to compose.

Keep The Plan Reviewable

Plans should explain the objective, constraints, and intended approach. Avoid storing raw conversation history in a plan.

await roadmap.update({
  content: [
    "## Objective",
    "Prepare the launch announcement.",
    "",
    "## Constraints",
    "- Use approved positioning.",
    "- Cite source material.",
  ].join("\n"),
});

Choose Dynamic Or Defined Early

Use dynamic ledgers when task IDs are discovered at runtime.

const work = await tasks({ title: "Runtime checklist" });
await work.add({ id: generatedId, label });

Use defined ledgers when IDs and result schemas are part of your program contract.

import { z } from "zod";

const work = await tasks({
  items: {
    review: task("Review output", {
      result: z.object({ approved: z.boolean() }),
    }),
  },
});

Use Lifecycle Methods

Prefer start(), progress(), complete(), fail(), skip(), cancel(), and remove() over generic status mutation.

await work.start("review");
await work.progress("review", "Checking facts");
await work.complete("review", { approved: true });

Keep Results JSON-Safe

Task metadata, progress details, and results are persisted. Store JSON-safe values only.

await work.complete("extract", {
  rows: 1200,
  warnings: ["Missing phone number on row 42"],
});

Scope Tools Narrowly

Give planners plan tools, coordinators ledger tools, and workers task-scoped tools.

const worker = work.worker("draft");

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

Do Not Treat Ledgers As Queues

Task ledgers record state. They do not provide retries, schedules, locks, or concurrency limits. Use flow() or a durable runner for those guarantees, and store the plan ID plus task-ledger ID in that orchestration layer.

On this page