Crux
GuidesPlans & Tasks

Orchestration

Compose plans, task ledgers, workers, flow, and external runners without turning tasks into a queue.

Plans & Tasks tracks the work; something else has to run it. Depending on how much execution control you need, that something can be a single prompt, plain composition, a flow(), or an external durable runner. Here are the four patterns.

Plan Then Execute

const makePlan = plan.tool({
  template: "## Objective\n\n## Constraints\n\n## Work items",
});

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

const roadmap = makePlan.created();
const work = await tasks({ plan: roadmap, title: "Execution tasks" });

Prompt-Level Composition

Use contexts and tools directly when a single model turn can make progress.

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

Flow

Use flow() when the orchestration itself needs named steps and suspend/resume behavior.

const launchFlow = flow("launch", async (run) => {
  const roadmap = await run.step("plan", () =>
    plan({ title: "Launch", content: "Draft launch approach." }),
  );

  await run.suspend("approval");

  return run.step("execute", async () => {
    const work = await tasks({ plan: roadmap, title: "Launch tasks" });
    await work.add({ id: "draft", label: "Draft announcement" });
    return work.id;
  });
});

External Durable Runners

Use a host queue, cron system, Convex action, Inngest, Temporal, or another durable runner when you need:

  • scheduled starts;
  • retries and dead-letter behavior;
  • concurrency limits;
  • long-running jobs;
  • cross-process worker coordination.

Store the plan ID and task-ledger ID in the runner payload, then rebind handles inside the worker:

export async function runTask({ planId, taskListId, taskId }: JobPayload) {
  const roadmap = plan.ref(planId);
  const work = tasks.ref(taskListId);
  const worker = work.worker(taskId);

  return generate(
    prompt({
      use: [roadmap.asContext(), worker.asContext()],
      tools: worker.asTools(),
      system: "Complete the assigned task.",
    }),
    { model, input: {} },
  );
}

On this page