> ## Documentation Index
> Fetch the complete documentation index at: https://gomodel-feat-guardrails.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> How workflow matching works, including user_path-first precedence, provider-name scoping, and guardrail steps per phase.

## Overview

Workflows are immutable workflow-policy versions stored in the gateway and matched per request.

They currently control gateway-owned behavior such as:

* cache
* budgets
* audit logging
* usage tracking
* guardrails: which [plugin instances](/advanced/guardrails) run, in which phase, in what order
* translated-route failover

Each request matches exactly one active workflow.

## Scope Fields

Workflow scope is defined by these fields:

* `scope_provider_name`
* `scope_model`
* `scope_user_path`

`scope_provider_name` is the configured provider instance name, not the provider type.

Examples:

* provider type: `openai`
* provider name: `openai_primary`

If you have multiple configured providers of the same type, workflows can target them independently by provider name.

## Matching Precedence

Workflow matching is user-path first.

For a request with `user_path=/team/team1/user`, the gateway checks path-scoped candidates from deepest to root first:

1. `provider_name + model + /team/team1/user`
2. `provider_name + /team/team1/user`
3. `/team/team1/user`
4. `provider_name + model + /team/team1`
5. `provider_name + /team/team1`
6. `/team/team1`
7. `provider_name + model + /team`
8. `provider_name + /team`
9. `/team`
10. `provider_name + model + /`
11. `provider_name + /`
12. `/`
13. `provider_name + model`
14. `provider_name`
15. `global`

In practice:

* a deeper `user_path` workflow beats a broader provider-only or provider+model workflow
* within the same path depth, provider+model is more specific than provider-only
* if no path-scoped workflow matches, the gateway falls back to provider+model, then provider, then global

## Guardrail Steps

The workflow payload lists the guardrail instances to run as `steps`. Each
step names an instance (`ref`), the phase it runs in, and its position:

```json theme={null}
{
  "schema_version": 2,
  "features": { "cache": true, "audit": true, "usage": true, "budget": true, "guardrails": true, "failover": true },
  "steps": [
    { "ref": "pii-redact",   "phase": "prompt",   "step": 10 },
    { "ref": "policy-judge", "phase": "prompt",   "step": 10 },
    { "ref": "safety-prompt","phase": "prompt",   "step": 20 },
    { "ref": "mask-keys",    "phase": "response", "step": 10 },
    { "ref": "mask-keys",    "phase": "stream",   "step": 10 }
  ]
}
```

* `phase` is `prompt` (default when omitted), `response`, or `stream`. The
  instance's plugin must implement that phase; `GET /admin/workflows/guardrails`
  lists each instance with its `phases`.
* Within a phase, steps run in ascending `step` (a non-negative integer);
  equal `step` values run in parallel. At most one content-editing instance
  may share a `step`.
* The same `ref` may appear once per phase.
* `features.guardrails: false` disables every step of the workflow.

Version 1 payloads (`"schema_version": 1` with `"guardrails": [{"ref", "step"}]`)
are still accepted and compile as prompt-phase steps. Stored version 1
workflows are returned unchanged; the dashboard always writes version 2.

Workflow views (`GET /admin/workflows`, `GET /admin/workflows/:id`) expose the
hash of each compiled chain as `chain_hashes` (`{"prompt": "...", "response": "...", "stream": "..."}`).
The prompt hash is part of the semantic response cache key, so changing a
prompt-phase guardrail invalidates cached answers produced under the old rules.

## Examples

### Path Inheritance

If you have:

* workflow A: `scope_user_path=/team`
* workflow B: `scope_user_path=/team/team1`

and the API key uses `user_path=/team/team1/user`, workflow B wins.

If workflow B does not exist, workflow A wins.

### Provider-Name Scoping

If you have two OpenAI providers:

* `openai_primary`
* `openai_backup`

you can create separate workflows for:

* `scope_provider_name=openai_primary`
* `scope_provider_name=openai_backup`

Even though both have provider type `openai`, they are different workflow scopes.

## API Example

Create a workflow scoped to one configured provider name, one model, and one user-path subtree:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST http://localhost:8080/admin/workflows \
    -H "Authorization: Bearer $GOMODEL_MASTER_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "scope_provider_name": "openai_primary",
      "scope_model": "gpt-5",
      "scope_user_path": "/team/alpha",
      "name": "team-alpha-openai-primary",
      "description": "Disable cache for this tenant on the primary OpenAI provider",
      "workflow_payload": {
        "schema_version": 2,
        "features": {
          "cache": false,
          "budget": true,
          "audit": true,
          "usage": true,
          "guardrails": true,
          "failover": true
        },
        "steps": [
          { "ref": "pii-redact", "phase": "prompt", "step": 10 },
          { "ref": "mask-keys", "phase": "response", "step": 10 }
        ]
      }
    }'
  ```

  ```python Python theme={null}
  import os

  import httpx
  from openai import OpenAI

  client = OpenAI(
      base_url="http://localhost:8080",
      api_key=os.environ["GOMODEL_MASTER_KEY"],
  )

  workflow = client.post(
      "/admin/workflows",
      cast_to=httpx.Response,
      body={
          "scope_provider_name": "openai_primary",
          "scope_model": "gpt-5",
          "scope_user_path": "/team/alpha",
          "name": "team-alpha-openai-primary",
          "description": "Disable cache for this tenant on the primary OpenAI provider",
          "workflow_payload": {
              "schema_version": 2,
              "features": {
                  "cache": False,
                  "budget": True,
                  "audit": True,
                  "usage": True,
                  "guardrails": True,
                  "failover": True,
              },
              "steps": [
                  {"ref": "pii-redact", "phase": "prompt", "step": 10},
                  {"ref": "mask-keys", "phase": "response", "step": 10},
              ],
          },
      },
  )

  print(workflow.json())
  ```

  ```javascript JavaScript theme={null}
  import OpenAI from "openai";

  const client = new OpenAI({
    baseURL: "http://localhost:8080",
    apiKey: process.env.GOMODEL_MASTER_KEY,
  });

  const workflow = await client.post("/admin/workflows", {
    body: {
      scope_provider_name: "openai_primary",
      scope_model: "gpt-5",
      scope_user_path: "/team/alpha",
      name: "team-alpha-openai-primary",
      description: "Disable cache for this tenant on the primary OpenAI provider",
      workflow_payload: {
        schema_version: 2,
        features: {
          cache: false,
          budget: true,
          audit: true,
          usage: true,
          guardrails: true,
          failover: true,
        },
        steps: [
          { ref: "pii-redact", phase: "prompt", step: 10 },
          { ref: "mask-keys", phase: "response", step: 10 },
        ],
      },
    },
  });

  console.log(workflow);
  ```
</CodeGroup>

## Notes

* `scope_model` requires `scope_provider_name`
* `scope_user_path` is normalized to canonical slash form
* managed API keys can override the request user path header; workflow matching uses the effective request user path
* budget enforcement runs only when the global budget feature and the matched workflow's `budget` feature are both enabled; see [Budgets](/features/budgets)
* every `ref` must name an existing guardrail instance; a guardrail referenced by an active workflow cannot be deleted
