# Workflows | Chersus

Documentation Coming soon

# Workflows

Conditional edges and bounded loops on the pipeline: branch on a verdict, retry with a cap, route on the result. Planned, coming soon.

Last updated 2026-09-03

Workflows extend the pipeline with conditional edges and loops, so a run can branch on a result: “if toxic, rewrite and re-check, up to three times, then route”. They are planned and not live yet; this page describes the contract they will ship with. Every linear pipeline is a valid workflow, so nothing you build on chains today changes.

Billing is unchanged: every executed step bills the characters it processes. A node that runs five times is five billed steps. Conditions and routing are never billed, because neither runs a model.

## Conditional edges

The `pipeline` array stays the primary structure. A node with no `next` continues to the next item in the array, exactly as chains work today. Conditional flow is added, never required.

A node may carry a `next` list of edges. The first edge whose `when` matches is taken; if none match, execution continues to the next array item. `goto` names the target node `id`, or `"end"` to finish the run.

```
{
  "id": "tox",
  "service": "security.toxicity.flag",
  "next": [
    { "when": { "verdict": "flag" }, "goto": "soften", "max_iterations": 3 },
    { "goto": "intent" }
  ]
}
```

A `when` condition reads this node’s own result, using the same vocabulary as `stop_if`: `{ "verdict": "flag" }`, `{ "score_gte": 0.9 }`, plus `path` matching for extract results, such as `{ "path": "data.intent", "equals": "billing.dispute" }`. An edge with no `when` is unconditional and acts as the fallback.

## Bounded loops

An edge that points to an earlier node, or to itself, must carry `max_iterations`: how many times that edge may be taken in one run, with a hard ceiling of 1000. When an edge has spent its iterations and would match again, `on_max` decides:

 - `"skip"` (default): the edge is ignored and the remaining edges are evaluated, so an unconditional fallback edge takes over.
 - `"halt"`: the run stops with `halted_reason: "max_iterations"`.

## A worked example

If a message is flagged toxic, rewrite the tone and re-check, at most three times; otherwise route the intent. `content.tone.rewrite` is a forthcoming content service, shown here because it is the natural fit.

```
{
  "input": { "type": "text", "value": "..." },
  "pipeline": [
    { "id": "shield", "service": "security.jailbreak.shield", "stop_if": { "verdict": "flag" } },
    { "id": "redact", "service": "governance.pii.redact" },
    {
      "id": "tox",
      "service": "security.toxicity.flag",
      "next": [
        { "when": { "verdict": "flag" }, "goto": "soften", "max_iterations": 3 },
        { "goto": "intent" }
      ]
    },
    {
      "id": "soften",
      "service": "content.tone.rewrite",
      "next": [ { "goto": "tox" } ]
    },
    { "id": "intent", "service": "routing.intent.map", "params": { "catalog": "support-v3" } }
  ],
  "options": { "max_steps": 20, "max_ms": 5000 }
}
```

Worst case: shield, redact, tox, then up to three rounds of soften and tox, then intent via the fallback edge. Ten steps. If tox still flags after the third round, the loop edge is skipped and the fallback edge routes to intent.

## Run-level limits

Workflows accept run-level `options`. Caps protect both sides from runaway runs.

| Option | Default | Ceiling | Notes |
| --- | --- | --- | --- |
| `max_steps` | 100 | 1000 | Executed steps only |
| `max_ms` | 30000 | 60000 | Wall clock for the whole run |
| `max_iterations` | required on backward edges | 1000 | Set on the edge, not the run |

## Halting

Any cap hit or `stop_if` trigger returns `status: "halted"` with `halted_at` (the last executed step), a `halted_reason` (`stop_if`, `max_steps`, `max_ms`, or `max_iterations`), and the payload as it stood at the halt. Billing covers what ran.

## Validation

Before any compute, the validator rejects runs that cannot succeed: unknown `goto` targets, backward edges without `max_iterations`, type mismatches along any reachable path, and worst-case step counts above the run’s `max_steps`. A rejected run never runs, and never bills.

## Billing and audit in workflows

The billing rule applies unchanged, counting executed steps. A node that executes five times is five steps; repeated executions are keyed `tox#2`, `tox#3`, and so on in `results`, `usage.billed`, and the audit trace, so each iteration is billed and traced on its own.

The audit trace records `iteration` and `edge_taken` for each executed step: the matched `when` and `goto` target, or `"sequential"`. When a run halts, `halted_reason` appears in the audit header.