Skip to Content
ProvidersWorkflow

Workflow

Gestalt workflows let you invoke a plugin operation or agent target later instead of right now. Today that primarily means cron-based schedules, triggers, workflow run history, event publishing, and run cancelation. The workflow model is global. A workflow target is always explicit: target.plugin or target.agent.

The public workflow surface now covers schedules, triggers, event publishing, and runs.

Mental model

Start by configuring at least one workflow provider under providers.workflow, then define a workflow target for the plugin operation you want Gestalt to invoke. From there you create either a schedule, which runs on a cron expression, or a trigger, which runs when a published event matches. The workflow provider persists that object, creates workflow runs, and calls back into Gestalt when a run should execute. Gestalt then invokes the target plugin operation, and you inspect or cancel the resulting runs through the global workflow runs surface.

There are two ownership modes. Config-managed workflows live in YAML under top-level workflows: and execute as the system config actor. User-owned schedules and triggers are created through the global API or CLI and execute as the subject that created them.

Each schedule or trigger points at exactly one target operation. If you want a larger flow, the usual pattern is to let one step publish an event and then let later triggers match that event and start the next steps.

How workflow providers work

Workflow provider selection is global, not plugin-scoped. A config-managed workflow object chooses a provider with provider, or inherits the sole/default providers.workflow entry when that field is omitted. User-owned schedules and triggers use the same provider pool through the global HTTP API and CLI. In every case the target is explicit, and a workflow provider may bind a host IndexedDB provider through providers.workflow.<name>.indexeddb.

How workflow execution uses authorization

Workflow authorization is easiest to reason about if you split it into creation time and run time. When a user creates a schedule, Gestalt checks whether the current subject is allowed to invoke the target operation right now. It then stores the owner subject plus a permission ceiling for delayed execution.

When the workflow fires later, Gestalt resolves that stored owner reference and checks authorization again before invoking the target. That means a schedule can still fail at run time if the subject lost access, if the target operation is no longer allowed, or if the original token had narrower permissions than the full subject would otherwise have.

Config-managed workflows follow the same pattern, but the owner is the system config actor rather than a human subject.

First-party workflow providers

First-party workflow providers live under valon-technologies/gestalt-providers/workflow.

ProviderUse case
github.com/valon-technologies/gestalt-providers/workflow/indexeddbStores workflow runs, schedules, and triggers in IndexedDB and invokes target plugin operations through the workflow host

Configuring providers.workflow

You need at least one entry under providers.workflow. The first-party IndexedDB workflow provider is the usual starting point:

providers: indexeddb: workflow_state: source: https://artifacts.example.com/indexeddb/relationaldb/v0.0.1-alpha.1/provider-release.yaml config: dsn: ${DATABASE_URL} workflow: local: source: https://artifacts.example.com/workflow/indexeddb/v0.0.1-alpha.1/provider-release.yaml default: true indexeddb: provider: workflow_state db: workflow config: pollInterval: 1s

If you configure exactly one workflow provider, or mark one as default: true, then schedules and triggers may omit provider.

Defining config-managed workflows

Config-managed workflows live under the top-level workflows: block.

Schedules

workflows: schedules: nightly_sync: provider: local cron: "0 3 * * *" timezone: America/New_York target: plugin: name: roadmap operation: sync connection: default instance: tenant-a input: mode: incremental includeArchived: false

This creates a durable schedule named nightly_sync that invokes roadmap.sync every day at 3:00 AM New York time.

Triggers

workflows: eventTriggers: roadmap_item_updated: provider: local match: type: roadmap.item.updated source: roadmap subject: item target: plugin: name: slack operation: chat.postMessage connection: alerts instance: engineering input: channel: C123456 text: "A roadmap item changed."

This creates a durable trigger that listens for published events with: type = "roadmap.item.updated", plus optional exact matches for source = "roadmap" and subject = "item". When an event matches, Gestalt invokes slack.chat.postMessage with the static input above and records a workflow run.

Match semantics

Trigger matching is exact string matching. match.type is required. match.source and match.subject are optional. If source or subject is omitted, that field is ignored during matching.

Creating user-owned schedules

User-owned schedules are global resources exposed through POST /api/v1/workflow/schedules and gestalt workflow schedules .... These schedules execute as the creating subject, not as a plugin-local or synthetic workflow subject.

gestalt workflow schedules create \ --cron "0 */6 * * *" \ --timezone America/New_York \ --plugin github \ --operation issues.list \ --connection default \ --instance acme-org \ -p owner=valon-technologies \ -p repo=gestalt \ -p state=open

List schedules:

gestalt workflow schedules list gestalt workflow schedules list --plugin github

Inspect, update, pause, resume, or delete a schedule:

gestalt workflow schedules get <schedule-id> gestalt workflow schedules update <schedule-id> --cron "0 9 * * 1-5" gestalt workflow schedules pause <schedule-id> gestalt workflow schedules resume <schedule-id> gestalt workflow schedules delete <schedule-id>

The provider field is available on the HTTP API. Omit it when you have a sole/default workflow provider; include it when you need to select a specific workflow backend.

Creating user-owned triggers

User-owned triggers are global resources exposed through POST /api/v1/workflow/event-triggers and gestalt workflow triggers .... These triggers execute as the creating subject, not as a plugin-local or synthetic workflow subject.

gestalt workflow triggers create \ --type roadmap.item.updated \ --source roadmap \ --subject item \ --plugin slack \ --operation chat.postMessage \ --connection default \ -p channel=C123 \ -p text='Roadmap item updated'

List triggers:

gestalt workflow triggers list gestalt workflow triggers list --plugin slack --type roadmap.item.updated

Inspect, update, pause, resume, or delete a trigger:

gestalt workflow triggers get <trigger-id> gestalt workflow triggers update <trigger-id> --type roadmap.item.synced gestalt workflow triggers pause <trigger-id> gestalt workflow triggers resume <trigger-id> gestalt workflow triggers delete <trigger-id>

The provider field is available on the HTTP API here too. Omit it when you have a sole/default workflow provider; include it when you need to select a specific workflow backend.

Publishing workflow events

Triggers become useful once something can publish matching events into the workflow system. Gestalt now exposes a public event-ingress route at POST /api/v1/workflow/events and the matching CLI command gestalt workflow events publish.

When you publish an event, Gestalt normalizes the event ID, spec version, and timestamp if you omit them, then fans that event out across the configured workflow providers. Triggers match on the event payload itself. In practice, that means type is required, while source and subject remain optional exact-match fields. This is also the main handoff mechanism for multi-step workflows.

gestalt workflow events publish \ --type roadmap.item.updated \ --source roadmap \ --subject item \ --data-content-type application/json \ -p id=item-1 \ -e traceId=trace-1

Composing larger workflows

Gestalt does not currently let you declare a graph of named steps inside one workflow object. A schedule or trigger always invokes one target. The way to build something that feels like a DAG is to compose small targets with workflow events.

One common pattern is to use a schedule to start the first step, then have that operation publish a completion event. Other triggers listen for that event and start the next steps.

workflows: schedules: nightly_sync: provider: local cron: "0 3 * * *" timezone: UTC target: plugin: name: roadmap operation: sync input: mode: incremental eventTriggers: notify_sync_completed: provider: local match: type: roadmap.sync.completed source: roadmap target: plugin: name: slack operation: chat.postMessage connection: alerts input: channel: C123456 text: "Nightly roadmap sync completed." refresh_search_index: provider: local match: type: roadmap.sync.completed source: roadmap target: plugin: name: search operation: reindex connection: default input: scope: roadmap

In that example, nightly_sync only starts the first step. The roadmap.sync operation is responsible for publishing roadmap.sync.completed when it finishes successfully. Once that event exists, both triggers match it and the flow fans out into Slack notification and search reindexing.

You can publish that handoff event from outside Gestalt through POST /api/v1/workflow/events, or from plugin code through the plugin-facing workflow manager PublishEvent call. External publishing is useful when some other system already knows the step finished. Plugin-side publishing is useful when the operation itself owns the next handoff.

Inspecting and canceling workflow runs

Runs are the execution records produced by schedules, triggers, or other workflow-provider actions. The global run surface is GET /api/v1/workflow/runs, GET /api/v1/workflow/runs/{runID}, POST /api/v1/workflow/runs/{runID}/cancel, and gestalt workflow runs ....

gestalt workflow runs list gestalt workflow runs list --plugin github --status failed gestalt workflow runs get <run-id> gestalt workflow runs cancel <run-id> --reason "operator requested"

Run responses include id, provider, status, target, trigger, createdBy, createdAt, startedAt, completedAt, statusMessage, and resultBody. The trigger.kind value is schedule, event, or manual.

If you are implementing a provider or another event producer, see Custom Providers > Workflow for the PublishEvent interface and the provider-side contract.

How execution works

When a workflow fires, the workflow provider creates a run and calls back into Gestalt through the workflow host. Gestalt invokes the target plugin operation, then the run is updated to succeeded, failed, or canceled.

The execution identity depends on how the workflow was created. Config-managed schedules and triggers execute as system:config, while user-owned schedules and triggers execute as the creating subject.

Building your own workflow provider

Implementation details for the workflow runtime surface, host callbacks, and release flow live under Custom Providers > Workflow.

If you want the surrounding config model, read Configuration and the full Config File reference. For route and command details, see HTTP API and CLI. If you need provider implementation details, continue to Custom Workflow Providers or the Built-in Providers reference.