> ## Documentation Index
> Fetch the complete documentation index at: https://user-docs.olyrosstudios.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> Workflows let your team automate what happens around your agents without writing code.

A workflow is a small graph a company draws on a canvas: one trigger node starts a run,
logic nodes filter and route the event, AI nodes hand work to an agent, and action nodes
write back to Jira, GitHub or Slack. Workflows live in the client app under the
**Tasks** sidebar group as **Workflows** (`/workflows`): a list, a canvas editor
(`/workflows/new`, `/workflows/:workflowId/canvas`), a run history per workflow
(`/workflows/:workflowId/history`) and a run detail page
(`/workflows/:workflowId/executions/:executionId`).

## What it does

**Composes automations from 15 node types in four categories.** The registry
(`workflowNodeTypes` query) is the single source of truth; the canvas draws from it.

| Category | Node type | What it does |
| - | - | - |
| trigger | `jira_event` | Starts on a Jira webhook for one connection: `status_change`, `issue_created`, `label_added` (fires on added labels only; the event carries `labels_added`) or `issue_deleted`. A connection is required. The event carries `status_from`, `status_to`, the issue key, summary and description text. |
| trigger | `github_event` | Starts on a GitHub event for one installation: `pr_opened`, `pr_merged`, `push`, `issue_created`, `issue_labeled`; optional `branch_filter` glob and `repo_filter`. |
| trigger | `webhook` | Starts on an external `POST` to a unique URL; the request body becomes the event. |
| trigger | `api_trigger` | Starts from an agent chat or an API call. Declares typed input parameters (`string`, `number`, `boolean`, `date`, with optional `enum` and labels); `agent_discoverable` (default true) lets agents list and invoke it. |
| trigger | `event_test` | Emits a fixed test payload for exercising the graph. |
| logic | `filter` | If/else gate over dot-path fields with `equals`, `not_equals`, `contains`, `matches`, `exists`, `not_exists`; `match` is `all` or `any`; outputs `pass` and `reject`. |
| logic | `jira_filter` | The same engine with Jira field shortcuts. |
| logic | `router` | Sends the event to one of up to 4 named routes by the value of `route_field`, else to `default`. |
| logic | `print` | Renders a template, logs it, passes the input through. |
| ai | `create_task` | Creates a cloud task from `title_template` and `description_template`, with optional agent, executor, environment and GitHub repositories (repo, branch; templates allowed). |
| ai | `dispatch_task` | Sends the task to its executor and agent and **suspends the run** until the task ends. Outputs `success` or `failure` with `result_summary`, `task_url` and whatever the executor stage wrote through `context_writes`. |
| action | `create_jira_ticket` | Creates one Jira issue: `project_key`, `issue_type` (`Bug`, `Task`, `Story`, `Epic`, `Sub-task`), summary and description templates, `priority` (`Highest` to `Lowest`), comma-separated labels. |
| action | `jira_update` | Runs a list of actions on an issue key: `transition`, `comment`, `add_label`, `remove_label`. |
| action | `github_action` | `create_issue`, `add_label`, `remove_label` or `add_comment` on a repository. |
| action | `slack_message` | Posts a message to a channel or user as a given agent's Slack bot; supports threads and Slack mrkdwn. |

**Templates everywhere.** Any `*_template` field uses `{{key.subkey}}` against the data
flowing through the edge. Missing keys are left as the literal placeholder, dicts and
lists are JSON-serialised. A node may also declare an `output_transform` to reshape its
output before the next node.

**Five ways a run starts.**

1. A Jira or GitHub webhook arrives for a connection; the trigger matcher finds active
   workflows whose trigger node has that node type and `connection_id`.
2. An external system posts to the generic webhook URL
   `POST /api/webhooks/workflow/{token}` (no login; the token routes and, by default,
   authenticates). Optional HMAC signature verification and a header token can be turned
   on per webhook. Responses: `202` triggered or skipped by a guardrail, `400` bad JSON,
   `401` failed auth, `413` payload over the cap, `404` unknown or disabled token.
3. A schedule with target `workflow` launches it on a cadence, passing the stored
   parameters plus a `_source` envelope (see [Schedules](https://user-docs.olyrosstudios.com/schedules.md)).
4. A user clicks run, which calls `executeWorkflow` with an optional trigger event.
5. An agent calls the built-in tools `olyros_list_workflow_capabilities` and
   `olyros_trigger_workflow` against a workflow whose `api_trigger` node is discoverable.

**Guardrails before every start.** A workflow is skipped when it has reached
`auto_pause_after_failures` consecutive failures, when `max_concurrent` runs are already
active, or when the `cooldown_seconds` since its last trigger have not elapsed. The skip
is recorded (webhook deliveries show `skipped_guardrail`).

**Runs are first-class records.** Each run is a `WorkflowExecution` with status
`running`, `suspended` (waiting on a dispatched task), `completed` or `failed`, plus
cost and token totals, the actor that started it (`olyros_user`, `slack_user`,
`webhook`, `schedule`), the trigger event, and one node execution per node with input,
output and error. Runs can carry cost tags (`setWorkflowTags`, `workflowCostTags`) and
can be cancelled (`cancelWorkflowExecution`).

**Webhook deliveries are logged and replayable.** Every inbound call to a generic
webhook leaves a delivery row with outcome (`accepted`, `rejected_too_large`,
`rejected_bad_json`, `rejected_auth`, `skipped_guardrail`, `error`), a payload preview,
content type and source IP (`webhookDeliveries`); an editor can replay one
(`replayWebhookDelivery`).

## Who can use it

* Creating, editing, saving the graph, filing in a folder, cancelling and deleting
  workflows: **editor or admin** (ability `workflow.manage`). Reading
  workflows, their runs and node types: any company member.
* Running a workflow: any company member who holds **Run** access on it (see
  [Access](#access)); with no Run or View grants at all, that is everyone.
* Changing a workflow's Access lists (`setWorkflowAccess`): **editor or admin**
  (`workflow.manage`); each change writes a `workflow.access_updated` audit event.
* Reading or rotating a generic webhook's secret and token, changing its settings and
  replaying deliveries: **editor or admin**.
* Listing node types (`workflowNodeTypes`) has no company gate; it is static registry
  data for any authenticated user.
* No plan or tier gate exists for workflows.

## How to configure it

1. **Create** the workflow (Workflows -> New): `createWorkflow(name, description,
   sourceSystem, connectionId, trustLevel)`. A `connectionId` requires `sourceSystem`
   (`jira` or `github`). It starts as `draft`.
2. **Draw** the graph on the canvas and save: `saveWorkflowGraph(workflowId, nodes,
   edges)`. Each node has a `node_type`, label, position and a `config` validated
   against the node's schema; a `slack_message` node must have `workspace_id`,
   `agent_id` and `message_template`.
3. **Connect the trigger.** Jira and GitHub triggers need the connection from
   [Integrations](https://user-docs.olyrosstudios.com/integrations.md). A `webhook` node mints its URL on save; read it with
   `webhookForNode`, harden it with `updateWebhookSettings(enabled, verifySignature,
   headerToken, maxPayloadKb)`, and rotate with `regenerateWebhookToken` /
   `regenerateWebhookSecret` (`revealWebhookSecret` shows the current secret once).
4. **Set the guardrails and budget** with `updateWorkflow(status, trustLevel,
   monthlyBudgetUsd, maxExecutionsPerTrigger, cooldownSeconds, maxConcurrent,
   autoPauseAfterFailures)`; set `status` to `active` to let triggers fire.
5. **Test** from the canvas Test tab: an `event_test` node emits a fixed payload, and
   for Jira the tab can fetch a real issue as a synthetic webhook
   (`POST /api/jira/connections/{connection_id}/fetch-issue`).
6. **Watch** runs in History and the execution detail page (`workflowExecutions`,
   `workflowExecution`, `recentWorkflowExecutions`).

## Access

Each saved workflow carries two abilities, **Run** (`workflow.execute`) and **View**
(`workflow.view`), granted to Olyros users, Slack users and Slack groups. The Access
section lives in the canvas under Workflow Settings, above Delete Workflow; a new,
unsaved workflow shows "Save the workflow to manage access." instead.

* **Who may grant:** editors and admins (`workflow.manage`), through the Access table
  (`setWorkflowAccess(workflowId, ability, userIds, slackUserIds, slackGroupIds)`; a
  `null` list leaves that kind unchanged). Viewers see the same table read-only. Admins
  and editors are subject to the lists like everyone else: there is no role bypass, and
  an admin who removes their own Run access loses the Run button until they add
  themselves back.
* **Run** lets a person start the workflow from the web Run button (`executeWorkflow`)
  and from agent chat (`olyros_trigger_workflow`). Run includes View.
* **View** lets a person see the workflow listed as a capability
  (`olyros_list_workflow_capabilities`) without being able to run it. View-only subjects
  appear in the View row; subjects who hold Run appear there as muted "via Run" chips.
* **No grants at all means everyone; any grant means only the listed people and groups,
  in chat and on the web.** A workflow with no Run and no View grant is open to every
  company member and to anyone the agent's Slack bot can reach: the table reads
  "Everyone". As soon as one person or group is granted anything, the workflow is closed
  to everyone else on every channel. Granting Run to the Slack group Sales closes the
  workflow to Slack users outside Sales **and** to company members on the web, except
  members whose linked Slack account is in Sales. Agents read their Chat grants with the
  same rule.
* **View-only closes Run.** Once a workflow has only View grants, nobody can run it; the
  Run row then reads "No one". The status line above the table summarises both rows
  (`Run: Sales · View: Sales, Support`, or `Run: everyone`).
* **What the agent tools show.** A Slack or web user asking an agent what it can do sees
  only the discoverable workflows they may View; with none, the tool answers "No workflow
  capabilities are currently available." Triggering a workflow the person may not View
  answers as if it did not exist (`Error: workflow '<id>' not found.`); triggering one
  they may View but not Run answers "Error: you don't have access to run this workflow.
  Ask a workspace admin to grant it." The web Run button is disabled with "You don't
  have access to run this workflow", and the server refuses `executeWorkflow` with the
  same sentence.
* **Not affected:** schedules, generic webhooks, Jira and GitHub triggers and workflow
  chains (a run started by another workflow) are system actors and always pass. A
  permitted person's run of workflow A that triggers restricted workflow B runs B.
* **Deleting** a workflow removes its grants. A Slack group that any workflow or agent
  still lists cannot be deleted until it is removed from their Access settings.
* Slack users in the lists are keyed by workspace and user id, so the same Slack user id
  in a second workspace does not match. Picking a Slack user who is not yet in the
  workspace cache is allowed with a warning; the cache fills on their first message.

Agents use the same model with one ability, Chat; see [Agents](https://user-docs.olyrosstudios.com/agents.md). The roles and
their abilities are listed on [Company settings](https://user-docs.olyrosstudios.com/company-settings.md).

## Folders

Folders organise a company's workflows. Each folder belongs to one area: the folders
created under Workflows hold workflows only and never appear under Schedules, which has
its own folders (see [Schedules](https://user-docs.olyrosstudios.com/schedules.md#folders)). A workflow is in at most one
folder, and a workflow in no folder is **Unfiled**. Filing a workflow changes nothing
about how it runs, who may run it, or its triggers. Access grants stay on the workflow.

* **Who manages folders.** Creating, editing (name, description, colour) and deleting a
  folder needs the Editor role or higher (ability `folder.manage`). Any member can see
  the folder list (`folders(context: WORKFLOWS)`). Folders are listed alphabetically,
  ignoring case; `createFolder(context: WORKFLOWS, ...)` creates one.
* **Who files a workflow.** Editors and admins (`workflow.manage`), like every other
  workflow edit: `folderId` on `createWorkflow` and `updateWorkflow` (omitted on update
  leaves it unchanged; `null` means Unfiled), or `moveWorkflowsToFolder(workflowIds,
  folderId)` for one or many workflows at once. A move is all or nothing. A Viewer is
  refused. Duplicating a workflow in the client keeps its folder: the copy is created
  with the original's `folderId`.
* **Deleting a folder** never deletes workflows or schedules: `deleteFolder` moves both to
  Unfiled and deletes the folder in one step. A move into the folder that races the
  delete fails with "Folder not found".
* **Listing.** `workflowsPage` filters by `folderId` or by `unfiledOnly` (not both), and
  `workflowFolderCounts(statuses)` returns the total, the Unfiled count and a count per
  folder for the same status filter.
* **Agents.** `olyros_list_workflow_capabilities` shows each listed workflow's folder on
  its own `Folder:` line. The display name is unchanged.
* A Schedules folder, or a folder from another company, answers "Folder not found" when
  used to file, move or filter workflows, like an unknown id. The database also refuses
  filing a workflow in another company's folder.
* Name, description and colour limits are those of
  [schedule folders](https://user-docs.olyrosstudios.com/schedules.md#folders); names are unique per area, so a Workflows
  folder may share a name with a Schedules folder.
* **Existing folders (November 2026).** Before areas existed, one folder could hold
  both. On upgrade, a folder holding only workflows moved to Workflows. A folder holding
  both stayed under Schedules, and a copy with the same name, colour and description was
  created under Workflows, holding its workflows.

## Limits and defaults

| Setting | Default | Where enforced |
| - | - | - |
| `status` | `draft` (`active`, `paused`, `archived`) | trigger matcher only runs `active` |
| `cooldown_seconds` | 300 | `check_safety_guardrails` |
| `max_concurrent` | 10 | `check_safety_guardrails` |
| `auto_pause_after_failures` | 5 | `check_safety_guardrails` (0 disables) |
| `trust_level` | `automate` (`observe`, `assist`, `automate`, `autonomous`) | validated on write, **not read by the engine** |
| `max_executions_per_trigger` | 20 | stored, **not enforced** |
| `monthly_budget_usd` | none | stored, **not enforced** (`current_month_spend_usd` is tracked) |
| Router routes | at most 4 plus `default` | node schema |
| Webhook payload cap | per-webhook `max_payload_kb`, bounded by the platform setting (256 KB default) | webhook endpoint |
| Webhook signature check | off | `verify_signature` per webhook |
| Access list size | 1000 ids per list and ability | `ensure_list_size` in `setWorkflowAccess` |

## What it emits

* `WorkflowExecution` and `WorkflowNodeExecution` rows with cost, tokens, actor and
  tags, visible in History and in usage reporting.
* Cloud tasks created by `create_task`, linked back so the run resumes when the task
  finishes.
* Side effects in connected systems: Jira issues, transitions, comments and labels;
  GitHub issues, labels and comments; Slack messages posted as an agent.
* Webhook delivery rows for generic webhooks (replayable).
* For scheduled runs, the outcome feeds the schedule's failure counter.
* `workflow.access_updated` audit events (ability, before and after lists) for every
  change to the Access table; `access_denied` log lines (ids only) for refused runs.

## Known gaps

* No schedule trigger node: cadence comes from a `TaskSchedule` outside the graph, and
  the graph still needs its own trigger node for the editor.
* No approval or question node: a human gate has to be modelled through Jira (a status
  change or label fires `jira_event`). Workflow-run human-in-the-loop is designed but not
  built.
* No for-each, HTTP request, code or email node; `create_jira_ticket` creates exactly
  one issue per run, so fan-out needs several nodes.
* `trust_level`, `monthly_budget_usd` and `max_executions_per_trigger` are stored and
  validated but no code reads them at run time.
* `github_action` cannot open a pull request; a task can push a branch but the PR is
  manual.
* Jira has no issue search, so a workflow cannot ask "is there already a ticket for
  this?" before `create_jira_ticket`.
* A suspended run resumes only when its dispatched task reaches a terminal state; there
  is no per-node timeout.

## Customer-safe summary

Workflows let your team automate what happens around your agents without writing code.
You draw a small flow: it starts from a Jira or GitHub event, an incoming webhook, a
schedule, a button, or a request from one of your agents; it can filter and branch on the
event; it can hand the work to an agent and wait for the result; and it can update Jira,
comment on GitHub or post to Slack when done. Every run is recorded with its cost, who or
what started it, and what each step produced, and you can replay an incoming webhook or
cancel a run. Safety limits pause a workflow that keeps failing, cap how many runs happen
at once, and space out repeated triggers. You choose who may run each workflow and who may
merely see it offered in chat, by person or by Slack group, on the web and in Slack
separately.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.