Skip to content

Execution Metadata

Metadata is a small JSON object attached to an execution. Executions created from a run inherit it, so a request ID, tenant or reply address set at the entry point reaches every task in the workflow without being threaded through each payload.

Typing Metadata

Pass a Standard Schema to type and validate metadata:

import { z } from "zod";

const conductor = Conductor.create({
  sql,
  tasks: TaskSchemas.fromSchema([handleMention, postReply]),
  events: [mentioned],
  metadata: z.object({ tenant: z.string(), replyTo: z.string().optional() }),
  context: {},
});

Without a schema, metadata is any JSON object.

Setting Metadata

Set metadata when invoking a task or emitting an event:

await conductor.invoke({ name: "handle-mention" }, payload, {
  metadata: { tenant: "acme", replyTo: "thread-1" },
});

await conductor.emit("slack.mentioned", payload, {
  metadata: { tenant: "acme", replyTo: "thread-1" },
});

The schema validates metadata on conductor.invoke(), conductor.emit() and ctx.invokeAndWait() overrides, like event payloads.

Reading Metadata

ctx.metadata is readonly and undefined when the execution has none:

const handleMention = conductor.createTask(
  { name: "handle-mention" },
  { invocable: true },
  async (event, ctx) => {
    ctx.logger.info(`Handling mention for ${ctx.metadata?.tenant}`);
  },
);

Inheritance

These executions inherit the metadata of the execution or event that created them:

  • Executions triggered by an event
  • Children created with ctx.invokeAndWait() or ctx.invoke()
  • Executions started by ctx.signalWithStart()
  • Events emitted with ctx.emit(), and the executions they trigger
  • Cron executions scheduled with ctx.schedule()
  • Dead-letter deliveries

Retries and resumes after ctx.sleep(), ctx.invokeAndWait() or ctx.waitForEvent() run the same execution and keep its metadata. Cron schedules declared as task triggers have no metadata.

Overriding Metadata for a Child

Pass metadata to ctx.invokeAndWait(), ctx.invoke(), ctx.signalWithStart() or ctx.emit() to replace the metadata of that child or event only. ctx.signalWithStart() applies it only when it starts the execution. A function receives the current metadata:

await ctx.invokeAndWait("reply", { name: "post-reply" }, payload, {
  metadata: (metadata) => ({ ...metadata, tenant: "acme", replyTo: "thread-2" }),
});

The parent and later children keep the original metadata.

Size Limit

Metadata is limited to 8 KB (8192 bytes of its JSON text in Postgres). Larger metadata is rejected with Execution metadata must not exceed 8192 bytes. An oversized ctx.invokeAndWait() override fails the invoking attempt. Store references in metadata, not payloads.

What's Next?