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.
| Provider | Use case |
|---|---|
github.com/valon-technologies/gestalt-providers/workflow/indexeddb | Stores 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: 1sIf 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: falseThis 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.
CLI
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=openList schedules:
gestalt workflow schedules list
gestalt workflow schedules list --plugin githubInspect, 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.
CLI
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.updatedInspect, 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.
CLI
gestalt workflow events publish \
--type roadmap.item.updated \
--source roadmap \
--subject item \
--data-content-type application/json \
-p id=item-1 \
-e traceId=trace-1Composing 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: roadmapIn 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 ....
CLI
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.
What to read next
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.