defineExecutor
workflow.defineExecutor<Input, Output>() declares a project's execution contract. A Workflow executor declares its default entrypoint and independently runnable entrypoints; a Conversation executor declares its workflow, parameters, and input construction directly on the executor.
Signature
workflow.defineExecutor<Input, Output>(
definition: WorkflowExecutorDefinition<Input, Output>,
): WorkflowExecutorDefinition<Input, Output>
Parameter
| Parameter | Type | Description |
|---|---|---|
definition | WorkflowExecutorDefinition<Input, Output> | Platform discovery and implementation of ingress configuration for workflow. |
Workflow executor
| Field | Type | Description |
|---|---|---|
projectType | "workflow" | The project type. Required. |
defaultEntrypoint | string | The entrypoint ID used when no entrypoint is selected. It must be a string literal that names an item in entrypoints. |
entrypoints | WorkflowEntrypointDefinition[] | A non-empty array of static entrypoint definitions. Each item must call workflow.defineEntrypoint() directly. |
workflow, params, createInput, resolveParams, createContext, requiredProviders, tools, tokenUsage, and registry are entrypoint-specific Workflow fields and cannot appear at the top level of an executor. Hosts display entrypoints in declaration order. Selecting one creates a run for that entrypoint directly; the default entrypoint does not run first.
Workflow can declare the proposed schedule in package.json.workflowCode.schedulePresets. Each entry statically references a entrypoint and contains a IANA time zone, time window, or 5 field Cron, and default parameter values. The directory goes into the Structure report, but the host must display a confirmation interface and cannot automatically create a schedule for importing or publishing a project. Conversation and Kanban declare this field to be rejected by Structure. See Workflow Timed Run for the full format and run boundaries.
Conversation executor
| Field | Type | Description |
|---|---|---|
projectType | "conversation" | The type tag for the Conversation item. Required. |
workflow | WorkflowDefinition<Input, Output> | Conversation is unique to workflow. Required. |
createInput | (context) => Input | Converts args, environment, and session IDs into business inputs. Required. |
resolveParams | (context) => WorkflowParamResolveResult | Optional parameter linkage callback. |
createContext | (context) => Partial<WorkflowContext> | Add providers, metadata and hooks. |
requiredProviders | ExecutorProviderName[] | Declares a required provider. |
params | WorkflowParamDefinition[] | The structured parameter of Conversation. |
tools | WorkflowToolPermissionDefinition[] | Conversation Callable tools and default policies. |
conversation | WorkflowConversationDefinition | Configure the default input box, attachment, and input queue. |
tokenUsage | WorkflowTokenUsageDefinition | Enable token usage statistics. |
registry | WorkflowNodeRegistryEntry[] | Register nodes that can be debugged individually. |
Conversation does not declare defaultEntrypoint or entrypoints. CLI, Desktop, and Server reject a non-empty entrypoint selection instead of silently ignoring it.
WorkflowExecutorContext
| Field | Type | Description |
|---|---|---|
workflowName | string | The current workflow name. |
workflowDir | string | The current workflow directory. |
entrypointId | string | undefined | The selected Workflow entry-point ID; undefined for Conversation. |
entrypointTitle | string | undefined | The selected Workflow entry-point title; undefined for Conversation. |
args | string[] | Original CLI style parameter. |
paramValues | Record<string, string | string[]> | The structured value after parsing by params. A single value holds a string, and multiple values are selected as a string array. |
env | NodeJS.ProcessEnv | A collection of environment variables. |
abortSignal | AbortSignal | An interrupt signal;Desktop/server cancellation of the runtime is passed to workflow and SDK/provider calls that support signal. |
conversationId | string | undefined | The session ID. |
toolPermissionPolicies | WorkflowToolPermissionPolicyMap | undefined | The tool policy override value passed in by the host. workflow usually does not read this field directly, but uses context.toolPermissions or workflow.createToolApprovalGate(). |
WorkflowParamDefinition
| Field | Type | Description |
|---|---|---|
name | string | The parameter name. Required. |
flag | string | Long parameters, such as --message. |
shortFlag | string | Short parameters, such as -m. |
type | WorkflowParamType | The parameter type. Required. |
required | boolean | Whether it is required. |
description | string | The parameter description. |
defaultValue | string | Default value. |
multiple | boolean | Whether multiple values are allowed. |
control | "text" | "textarea" | "radio" | "select" | "checkboxes" | "date" | "time" | "datetime" | "date-range" | Desktop/server Run workspace form controls prompt; date-related controls still use type: "string". |
panel | "main" | "auxiliary" | "quick" | The form display area; auxiliary appears under Advanced, while quick enters the composer's combined quick selector. |
resolveOnInput | boolean | Whether to execute resolveParams when the main input content changes. Default false; set to true only if linkage does depend on input text. |
options | { value, label?, description? }[] | Enumerates options; the form renders single-select, drop-down, or multi-select controls when there are options. |
accept | string[] | File type restrictions. |
format | "json" | file param uses the JSON reference format. |
WorkflowParamStatePatch
| Field | Type | Description |
|---|---|---|
value | string | string[] | null | Replace the current parameter value;null indicates an explicit clear. |
visible | boolean | Controls whether the parameter is displayed. |
disabled | boolean | Whether the control parameters are operable. |
resolvedOptions | { value, label?, description? }[] | null | Array replaces the current full list of options for the argument;null restores static options. |
options | Record<string, { visible?, disabled? }> | Modifies the display or disabled state of individual values in the current selection list. |
WorkflowNodeRegistryEntry
| Field | Type | Description |
|---|---|---|
name | string | The name of the debug node in the registry. Required. |
node | NodeDefinition | StreamNodeDefinition | The node object actually executed. Required. |
metadata | WorkflowNodeMetadataInput | The overlay node displays meta information. |
stream | boolean | Marks whether the node is executed as a streaming node. |
createInput | (context) => Input | Input constructor for single-node debugging. |
Execution process
| Phase | Description |
|---|---|
| Platform Selection Configuration | Workflow selects an entry by explicit ID or defaultEntrypoint;Conversation uses executor itself. |
| Create Context | runtime creates a WorkflowExecutorContext based on args, env, workflowName, entry, and conversationId. |
createInput | Invokes the input construct of the selected entry or Conversation executor. |
createContext | An optional context extension that invokes the same execution configuration. |
workflow run | Execute only the workflow of the selected entry, or the only workflow of Conversation. |
Quick parameters and linkage
panel: "quick" is intended for enum parameters that users adjust frequently before a run. Workflow declares these fields on a specific entrypoint, while Conversation declares them on the executor. The composer footer shows one combined summary trigger. When model and reasoningEffort, effort, or variant are present, the summary prioritizes “Model · Effort”; otherwise it falls back to other selected quick values.
The first-level menu lists every visible quick parameter and its current value in declaration order. Selecting a parameter opens its second-level single- or multi-select list. Provider, Permission, Sandbox, and other quick parameters remain configurable even though the summary emphasizes Model and Effort. Wide layouts place the option list beside the parameter menu; narrow layouts drill into the same bounded popover and provide a Back action.
Reset to defaults restores every quick parameter in one update and triggers resolveParams only once. It preserves Main and Advanced values, unknown Raw arguments, and unsent Conversation text and attachments. Selections still write the real args and paramValues.
params: [
{
name: "tone",
flag: "--tone",
type: "string",
panel: "quick",
resolveOnInput: true,
control: "select",
defaultValue: "balanced",
options: [
{ value: "balanced", label: "Balanced" },
{ value: "precise", label: "Precise" },
{ value: "creative", label: "Creative" },
],
},
{
name: "sources",
flag: "--sources",
type: "string",
panel: "quick",
control: "checkboxes",
options: [
{ value: "web", label: "Web" },
{ value: "docs", label: "Docs" },
],
},
],
resolveParams({ draftText, paramValues }) {
const docsQuestion = draftText.includes("documentation");
return {
params: {
tone: {
value: docsQuestion ? "precise" : paramValues.tone,
options: { creative: { disabled: docsQuestion } },
},
sources: { value: docsQuestion ? ["docs"] : paramValues.sources },
},
};
},
createInput({ paramValues }) {
return {
tone: String(paramValues.tone),
sources: Array.isArray(paramValues.sources) ? paramValues.sources : [],
};
},
resolveParams is executed during initialization and when the value of any Form parameter in main, Advanced, and Quick changes. The change of the main input content will only be triggered when at least one parameter is set to resolveOnInput: true, avoiding irrelevant remote evaluation caused by ordinary message input. It should remain free of write side effects and idempotence, and does not perform writing to files, writing to KV, or modifying external state; read-only dynamic directories should be passed in the abortSignal provided by the callback. visible / disabled in the patch does not implicitly empty existing values; value: null is explicitly returned when emptying is required. When initializing the return value patch in Raw mode, the host only updates the corresponding declared parameters, leaving the other Raw parameters and their values.
resolvedOptions is used to dynamically replace enumeration: returning an array will completely replace the static options of the parameter, returning null to restore the static option, and omitting it will keep the current dynamic option. value in the array must be a non-empty unique string, and label and description must be strings. When replacing options, the old option state will be cleared. The host will uniformly apply dynamic options to Quick and Advanced, and distinguish between loading, empty and error. conversation the host is re-evaluated by conversation ID, asynchronous responses that have expired do not overwrite the current session.
Date Parameters
Date related controls use a fixed local time string without time zone conversion:
control | String Format | Behavior |
|---|---|---|
date | YYYY-MM-DD | Write back immediately after selecting a single date. |
time | HH:mm:ss | 24-hour clock, including seconds. |
datetime | YYYY-MM-DDTHH:mm:ss | Write back with OK after selecting date and time. |
date-range | YYYY-MM-DD/YYYY-MM-DD | Closed interval; written back only when the start and end dates are complete and the end is no earlier than the start. |
These controls only support type: "string" and panel: "main" / panel: "auxiliary" and cannot declare options, multiple: true, or panel: "quick";TypeScript, static structural analysis, and runtime all reject illegal combinations. The date parameter also does not return a resolvedOptions or option status patch via resolveParams.
Empty values remain ""; optional parameters can be cleared, and required parameters do not display a clear operation. Non-empty invalid default values, Raw parameters or resolveParams return values will enter the form as they are and display field errors, and will not be silently corrected. Before correction, Run, Send or Submit will remain disabled and will not pass known invalid values to createInput. Legal values of args and paramValues are still passed as the original string.
Clicking on the calendar title will open a compact double-column scroll selector for year and month. The scrolling process only updates the draft, switches the calendar after selecting "Finish", and "Cancel" keeps the original month. The year limit is 0001 to 9999, and the scroll wheel loads only limited options around the current year. In the widescreen interface,date-range will display two calendars that can switch years and months independently. When reopening, it will locate the selected start month and end month respectively, and display the next month on the right when starting and ending in the same month. Switching one of the calendars does not change the month the other calendar is currently showing. The narrow screen interface uses a single calendar, and the value format for the date range and the full range submission rules remain unchanged.
params: [
{ name: "publishDate", flag: "--publish-date", type: "string", control: "date" },
{ name: "startsAt", flag: "--starts-at", type: "string", control: "datetime", panel: "auxiliary" },
{ name: "period", flag: "--period", type: "string", control: "date-range" },
],
resolveParams({ paramValues, trigger }) {
const publishDate = typeof paramValues.publishDate === "string" ? paramValues.publishDate : "";
const period = typeof paramValues.period === "string" ? paramValues.period : "";
const preferPeriod = trigger.kind === "param" && trigger.paramName === "period";
const usePeriod = period !== "" && (preferPeriod || publishDate === "");
const useDate = publishDate !== "" && !usePeriod;
return {
params: {
publishDate: usePeriod
? { visible: false, value: null }
: { visible: true },
period: useDate
? { visible: false, value: null }
: { visible: true },
},
};
},
createInput({ paramValues }) {
return {
publishDate: String(paramValues.publishDate ?? ""),
startsAt: String(paramValues.startsAt ?? ""),
period: String(paramValues.period ?? ""),
};
},
The mutex example hides and clears the range after selecting a single day, hides and clears a single day after selecting a range; both fields reappear after clearing the current value. If the external args provides both at initialization, the single day is deterministically retained and the range is cleared. Only returning visible: false will keep the old value and continue to put it in args and paramValues, which is not suitable for mutex parameters.
Every executor must declare projectType statically. projectType: "conversation" enables conversation IDs, context, and session storage; workflow code can call context.conversation.setTitle(title) to suggest a readable session title for Desktop or Server Embed. projectType: "workflow" prohibits a conversation declaration, leaves session context disabled, and requires static defaultEntrypoint and entrypoints. Missing or unknown types, and the legacy conversation.enabled, are rejected as migration errors rather than treated as Workflow projects.
workflow.defineExecutor(...) must receive an inline object literal, and the result must be the module's named or default executor export. Do not assign the configuration to a variable first or continue using legacy top-level exports such as workflow, createInput, or projectType. Static structure analysis does not execute configuration code, and runtime does not assemble those fields into an executor.
If the conversation workflow wants to reuse the shared default input boxes of Desktop, server embed, and public embed, it can be declared under conversation:
export const executor = workflow.defineExecutor<Input, Output>({
projectType: "conversation",
workflow: conversationWorkflow,
conversation: {
inputQueue: {
enabled: true,
},
defaultInput: {
enabled: true,
attachments: {
images: true,
files: true,
},
},
},
createInput({ args }) {
return workflow.parseConversationDefaultInputArgs(args);
},
});
defaultInput is only the host UI protocol and does not automatically override WorkflowContext. workflow still needs to explicitly read the fixed parameter name in createInput({ args }):
- Text:
--message - Image Attachment:
--images - Common file attachments:
--files
Direct use of workflow.parseConversationDefaultInputArgs(args) is recommended:
createInput({ args }) {
const defaults = workflow.parseConversationDefaultInputArgs(args);
return {
message: defaults.message,
images: defaults.images,
files: defaults.files,
};
}
--images / --files Reuse the existing file reference protocol, which can be a managed file JSON refs or a local path. The host is responsible for converting pasted, dragged, or uploaded File s into managed refs before writing back these fixed parameters.
If conversation.inputQueue: { enabled: true },Desktop, and server/web embed are also declared
will allow continued submission of new input while the current session is running. The new input will not be executed immediately, but will enter the shared input queue:
- Queues only work in Desktop and Web hosts,CLI does not use this capability.
- The host persists the input in the wait; the queue remains after a Desktop restart or embed refresh.
- The restored queue will not continue to run automatically. You need to manually send a new input before the subsequent queued items continue to run in sequence.
- After the current run completes, fails, or is canceled, the host clears the active run flag for that session; subsequent new inputs revert to normal send and do not continue to the wait queue.
- Queue items support bootstrapping, editing, deleting, and dragging sort; bootstrapping interrupts the current run before immediately executing the target queue item.
After tokenUsage.enabled is enabled,runtime writes the token usage to KV and the run aggregation to report. Aggregations contain inputTokens, outputTokens, totalTokens, reasoningTokens, cachedInputTokens, cacheCreationTokens, and calls. If display is omitted, the default value is enabled. Desktop and server embed only display badges when display === true and data is available. Badges display the total amount by default, and hover/focus displays Input, Output, Reasoning, Cache read, Cache write, and Calls respectively.
Tool permissions
tools Default permission for registering workflow business tools. Each tool contains stable name, and optional label, description, enabled, and autoApprove. enabled default true,autoApprove default false. Desktop and server embed can cache user selections and pass in override values before running;runtime only accepts enabled/autoApprove overrides for registered tools, and unknown tools are ignored.
workflow code can use workflow.createToolApprovalGate() to wrap tool functions. When the tool is not registered, it will fail as input_validation; When the tool is disabled, it will be rejected as tool_permission_denied; When the tool is enabled but not automatically approved, the waiting/recovery protocol of workflow.createUserInputNode(...) will be reused to generate a tool-approval request. After approval, the same run will continue and tool_permission_denied will be returned after rejection.
Example
export const executor = workflow.defineExecutor<Input, Output>({
projectType: "workflow",
defaultEntrypoint: "main",
entrypoints: [
workflow.defineEntrypoint<Input, Output>({
id: "main",
title: "Full workflow",
workflow: exampleWorkflow,
tokenUsage: { enabled: true, display: true },
tools: [
{
name: "search",
label: "Search",
description: "Calls an external search tool.",
enabled: true,
autoApprove: false,
},
],
params: [
{
name: "message",
flag: "--message",
type: "string",
required: true,
control: "textarea",
description: "Input text.",
},
],
createInput({ args }) {
return { message: readMessage(args) };
},
}),
],
});
Precautions
| Rules | Description |
|---|---|
createInput is required | Each Workflow entrypoint and Conversation executor must be declared. |
paramValues is a formal structured value | createInput can read parameter values directly;args continues to be used in scenarios that require full CLI parameters or unknown parameters. |
| Workflow The running configuration belongs to entrypoint | The top-level old field gives a migration error and no longer retains the compatible alias. |
tools must be statically declared | Desktop/server embed can only show the selected entry or Conversation executor statically analyzed tools; the tool name must be stable. |
tokenUsage.enabled Off by default | If it is not configured, KV will not be written and the usage will not be displayed. When closed context.tokenUsage.report() is no-op. |
registry for single node debugging | Workflow searches only the registry of the selected portal; nodes that are not registered cannot be run individually through debug API. |