Skip to main content

Configurations Introduction

Developing comprehensive workflows often requires creating multiple variations of the same workflow with different configuration options. For instance, you might need to run a workflow using different AI models, prompts, or other model parameters. Workflow Configurations provide a streamlined way to define and manage these settings, either through a Configuration UI or a configuration file. These configurations integrate seamlessly into your task implementation, allowing workflows to run with varied settings and supporting experimentation.


Creating Configurations​

We offer two methods for creating configurations for your workflow:

Using the Configuration UI​

  1. Navigate to the CMS Cards page for your environment and click Create Card:
  2. Enter a name for your configuration.
  3. Provide a unique ID in the Content Key field (e.g., example-task-variant1).
  4. Select Pluggable Task Generic Form as the form type.
    • You can also create a custom form to define your own field layout instead of using the generic form. Learn more here.
  5. Add your task's JSON configuration in the Config field. Example:

Configuration UI

Using a Config File in the Worker Repository​

  1. Create a configuration file at the following path:
src/config/tasks/{task-executor-name}.yaml

When defining versioned task executors, use the same path, but replace all / characters in the task name with - to form the file name.

Examples

Task executor nameConfiguration file path
example-task/v1src/config/tasks/example-task-v1.yaml
ideogram/photo/realistic/v2src/config/tasks/ideogram-photo-realistic-v2.yaml
photo/gemini/resize/highsrc/config/tasks/photo-gemini-resize-high.yaml
  1. Add your configuration, using the configuration ID as the key. Example:
example-task-variant1:
config:
model: gpt-4o-mini
example-group-v2-example-task:
config:
model: gpt-4o-pro
default config

The default configuration key follows the pattern {task-executor-name}-default. For example, for a task named example-task, the configuration would be:

example-task-default:
config:
model: gpt-4o-mini
fallback config

In the same file you can also define a {task-executor-name}-fallback key. It acts as a safety net: when a requested configuration ID is not found in CMS and is not a key in this file, the task uses the fallback configuration instead of failing.

example-task-fallback:
config:
model: gpt-4o-mini

Accessing Configurations in Your Task Executor​

You can retrieve the configuration within your task implementation by calling flow.config(). Here's an example

@Executor("example-task")
export class ExampleExecutor implements TaskExecutor {

async processTask(
exampleCommand: ExampleCommand,
meta: TaskMetadata,
flow: Flow
): Promise<ExampleResponse> {
// Retrieve the configuration
const config = await flow.config().get();

// Use the configuration in the response
return {
result: `Using model ${config.model} !!!`
};
}
}
note

flow.config() requires @picsart/pa-pluggable-workers-core 7.35.0 or higher. The property form (flow.config.get(), flow.config.withFieldParams(), flow.config.applyPlaceholders()) still works but is deprecated and will be removed in the next major version.

To know which configuration was loaded — for example to log it or tag metrics with it — read flow.meta().configId(). It returns the resolved ID, {task-executor-name}-default when the request named none.

How the Configuration Is Resolved​

When your task calls flow.config().get(), the configuration is resolved in this order:

  1. A config object was sent in the request body → it is used as-is. Nothing else is checked.
  2. Otherwise, the task takes its config ID — from the x-config-id header, or {task-executor-name}-default when the header was not sent — and looks it up:
    1. in CMS;
    2. if not found in CMS — in the worker's own config file (src/config/tasks/{task-executor-name}.yaml), first under the requested ID, then under the {task-executor-name}-fallback key;
    3. if none of these exist, the task fails with config_not_found.

The same order applies everywhere a configuration is resolved — root tasks and sub-tasks alike.

Declaring the config schema with @ConfigModel​

@ConfigModel("<task-name>") marks a class as the schema for the per-request config a task accepts. When a caller passes a config the framework validates it against this model using class-validator and publishes it in the OpenAPI schema.

@ConfigModel("example-task")
export class ExampleConfig {
@ApiProperty()
@IsString()
public model: string;
}
warning

If a task has no @ConfigModel registered, any request carrying a config is rejected with invalid_request.

API​

When calling to Workflows API you can choose the desired configuration by passing x-config-id header.
Example:

curl --location '$HOST/workflows/example-workflow/submit' \
--header 'Content-Type: application/json' \
--header 'x-config-id: example-task-variant1' \
--data '{
"params": {
}
}'

Subtask Configuration

When building workflows, you may need to configure not only the top-level task but also any nested subtasks it triggers. Subtask configuration enables fine-grained control over each nested task's behavior while preserving a clear, hierarchical structure.

This approach makes it easy to reuse task implementations with different settings and allows parent tasks to dynamically influence their subtasks.

Passing Config to Sub-tasks at Runtime​

When triggering a sub-task, you can choose which configuration it runs with. There are two ways:

  • config — send a ready configuration object together with the call. The sub-task uses exactly what you sent: flow.config().get() returns this object.
  • configId — send only the ID of a configuration that is already defined for the sub-task (in CMS or in its config file). The sub-task looks it up by that ID and uses it — the same way it would for an x-config-id header.

Use one or the other, not both — a call with both fails with invalid_request.

How the sub-task finds its configuration​

The sub-task resolves its configuration in the same order as a root task (see How the Configuration Is Resolved):

  1. A config object came with the call → it is used as-is. Nothing else is checked.
  2. Otherwise, it takes the config ID — the configId from the call, or {commandName}-default when none was sent — and looks it up:
    1. in CMS;
    2. if not found in CMS — in the sub-task worker's own config file (src/config/tasks/{command}.yaml), first under the requested ID, then under the {command}-fallback key;
    3. if none of these exist, the sub-task fails with config_not_found.

When to use which: use configId when the configuration already exists in CMS or in the sub-task's config file — the call stays small and configurations stay centrally managed. Use config when the parent task builds the configuration dynamically at runtime. Keep in mind that config requires the sub-task to declare a @ConfigModel; configId does not.

Sync sub-tasks (executeTask)​

Pass either option inside ExecuteOptions:

// Inline config
const result = await flow.executeTask(
'sub-task',
{ text: 'hello' },
{
config: { model: 'gpt-4o', count: 100 }
}
);

// Config ID resolved by the sub-task worker
const result = await flow.executeTask(
'sub-task',
{ text: 'hello' },
{
configId: 'sub-task-variant1'
}
);

Stream sub-tasks (streamTask)​

Pass either option inside StreamOptions:

const stream = flow.streamTask(
'sub-task',
{ text: 'hello' },
{
config: { model: 'gpt-4o' }
// or: configId: 'sub-task-variant1'
}
);

Async sub-tasks (deferred execution)​

Pass either option via DeferredTaskOptions:

return flow.defer()
.withTask('sub-task-1', { text: 'hello' }, {
config: { model: 'gpt-4o' }
})
.withTask('sub-task-2', { text: 'world' }, {
configId: 'sub-task-2-variant1'
})
.continueOn('onSubTasksCompleted');

Sub-task options (flow.options())​

flow.options() accepts the same choice, so a sub-task quote is resolved against the same configuration the execution would use:

const options = await flow.options('sub-task', { text: 'hello' }, { configId: 'sub-task-variant1' });