docs(hub): document durable workflow routing (#2943)

* docs(hub): document durable workflow routing

* docs(hub): make workflow guide progressive
This commit is contained in:
Mohamed Boudra
2026-08-06 16:07:41 +02:00
committed by GitHub
parent bb55e5bc0d
commit deb5e5c729
13 changed files with 693 additions and 578 deletions
+120 -116
View File
@@ -1,6 +1,6 @@
---
title: hub.yml reference
description: Every field in a Paseo Hub configuration: environments, triggers, filters, outputs, and merge variables.
description: The authored Hub configuration: environments, triggers, typed inputs, workflow steps, routing, and prompt partials.
nav: hub.yml reference
order: 69
category: Hub
@@ -8,143 +8,147 @@ category: Hub
# `hub.yml` reference
The complete field reference for a Hub configuration. For where the file lives and how it activates, see [Configuration](/docs/hub/configuration).
```text
environments: where agents run
triggers: what starts them
```
Both keys are required. `environments` needs at least one entry.
## Environments
An environment says where an agent runs.
A configuration has `environments` and `triggers`. Execution fields belong to each trigger's `steps`.
```yaml
environments:
- name: dev
- name: development
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/your-repo
cwd: /Users/you/code/project
triggers:
- name: request
on: manual.run
max_runtime: 2h
filters:
from_users: [automation]
steps:
- id: work
environment: development
max_runtime: 90m
idle_timeout: 10m
agent:
provider: codex
mode: full-access
prompt:
- text: ${{ paseo.prompt }}
```
| Field | Required | Notes |
| ---------- | -------- | ----------------------------------------------------------- |
| `name` | yes | Referenced by a trigger's `environment`. |
| `kind` | yes | `daemon`. Only daemon environments run today. |
| `daemon` | yes | The daemon's display name, resolved to an id on activation. |
| `cwd` | yes | Absolute path on that machine. |
| `worktree` | no | Run in an isolated git worktree instead of `cwd` directly. |
## Environments
### Worktrees
| Field | Required | Notes |
| ---------- | ----------- | --------------------------------------------------------------------------------------------------------- |
| `name` | yes | Lowercase identifier referenced by a step. |
| `kind` | yes | `daemon`, `fly`, or `docker` in the authored schema; workflow steps must resolve to a daemon environment. |
| `daemon` | daemon only | Daemon name, resolved when the revision activates. |
| `cwd` | daemon only | Absolute path on the daemon. |
| `image` | fly/docker | Image name. |
| `worktree` | no | `branch-off`, `checkout-branch`, or `checkout-pr` target. |
```yaml
worktree:
mode: branch-off
newBranch: hub/issue-${{ paseo.event.github.issue.number }}
base: main
```
Three modes:
| Mode | Fields | Behavior |
| ----------------- | ---------------------------- | ------------------------------------------ |
| `branch-off` | `newBranch`, optional `base` | New branch off `base`, or the current head |
| `checkout-branch` | `branch` | Existing branch |
| `checkout-pr` | `prNumber` | The pull request's head |
`newBranch`, `base`, and `branch` accept merge variables. `prNumber` does not, because it is a literal number. For a per-event pull request use `checkout-branch` with `${{ paseo.event.github.pull_request.head.ref }}`.
See [Git worktrees](/docs/worktrees) for how the daemon sets them up.
The `worktree` object is part of the environment. Its fields are exact authored names: `newBranch` and optional `base` for `branch-off`, `branch` for `checkout-branch`, and positive integer `prNumber` for `checkout-pr`.
## Triggers
```yaml
triggers:
- name: review
on: github.pull_request_review_comment
environment: dev
filters:
repo: acme/api
contains: "@paseo"
from_users: [alice, bob]
agent:
provider: codex
mode: full-access
prompt: ${{ paseo.event.github.comment.body }}
timeout: 45m
idle_timeout: 5m
auto_archive: true
```
| Field | Required | Notes |
| ------------- | ------------ | ----------------------------------------------------------------------------------------------- |
| `name` | yes | Lowercase identifier, unique in the configuration. |
| `on` | yes | `provider.event`, such as `slack.mention` or `manual.run`. |
| `max_runtime` | yes | Positive duration for the complete trigger run, up to 24h. |
| `filters` | no in schema | Provider filters; externally sourced triggers still require a non-empty `from_users` allowlist. |
| `inputs` | no | Typed leading `key=value` invocation headers. |
| `values` | no | Derived expressions. |
| `steps` | yes | One or more ordered steps. |
| Field | Required | Default | Notes |
| --------------- | -------- | ------- | --------------------------------------------------------- |
| `name` | yes | | Unique within the configuration. |
| `on` | yes | | `provider.event`, see below. |
| `environment` | yes | | An environment `name`. |
| `agent` | yes | | `provider`, `mode`, optional `model`, `thinkingOptionId`. |
| `prompt` | yes | | Supports merge variables. |
| `filters` | yes | | `from_users` is mandatory. See below. |
| `env` | no | | Environment variables for the agent process. |
| `allow_outputs` | no | none | Lets the agent reply to the source. |
| `timeout` | no | `1h` | Absolute wall clock. Max `24h`. |
| `idle_timeout` | no | `5m` | Starts on the first idle report from the daemon. |
| `auto_archive` | no | `false` | Archive the agent when the execution ends. |
### Inputs
Durations are a positive integer plus `ms`, `s`, `m`, or `h`.
`agent.provider` and `agent.mode` are the same identifiers the Paseo CLI uses, such as `codex`, `claude`, and `full-access`. See [Supported providers](/docs/supported-providers).
## Events and filters
`on` and `filters` decide which events reach a trigger. Both are documented in [Triggers](/docs/hub/triggers), with a page per provider for the events and data each one exposes.
`filters` is required, and `from_users` must be non-empty. Validation rejects a trigger without it.
## Merge variables
`${{ ... }}` expressions are available in `prompt`, `env`, and worktree branch fields. Values reach the agent's process only through `env`; nothing else is injected.
### Event data
`${{ paseo.event.<provider>.<path> }}` exposes the provider's own payload, unflattened. The full field list per provider lives with its trigger page: [GitHub](/docs/hub/triggers/github), [Slack](/docs/hub/triggers/slack), [Discord](/docs/hub/triggers/discord).
Manual runs expose `actor`, `input`, `config`, `trigger`, and `delivery_id` under `paseo.event.manual`.
### Connection credentials
`${{ paseo.connections.<slug>.token }}` mints a token from a named connection. GitHub connections are the only ones that provide a token today.
Use it when a trigger needs a provider other than the one that fired it:
Each input has:
```yaml
triggers:
- name: discord-fix
on: discord.mention
environment: dev
filters:
guild: "123456789"
from_users: ["987654321"]
agent:
provider: codex
mode: full-access
env:
GH_TOKEN: ${{ paseo.connections.acme-github.token }}
prompt: ${{ paseo.event.discord.trigger_message.body }}
inputs:
repo:
type: string
required: false
choices: [project, paseo]
agent:
type: string
default: codex
choices: [codex, claude]
```
You must name the connection. An organization can hold several GitHub installations and Hub will not guess between them.
`type` is `string`, `number`, or `boolean`. `required`, `default`, and `choices` are optional. `required` and `default` cannot be combined. Defaults and choices must match the declared type; a default must be one of the choices.
GitHub-triggered executions already receive a scoped `GH_TOKEN` for the triggering repository. You do not need to map one.
Inputs may be referenced as `${{ paseo.inputs.name }}`. A dynamic authority-bearing field such as a provider, model, mode, or environment requires finite `choices` at activation. A prompt cannot supply authority.
## Outputs
### Values
Values bind expressions under their own namespace:
```yaml
allow_outputs:
- type: slack.reply
values:
selected_repo: ${{ paseo.inputs.repo ?? steps.classify.outputs.repo }}
```
`slack.reply` and `discord.reply` give the agent a `reply` tool for the conversation that triggered it. The reply is plain text, posted in-thread, and can be sent once per execution.
The grammar supports paths, JSON literals, parentheses, `!`, `==`, `!=`, `&&`, `||`, and `??`. It does not support function calls, JavaScript, arithmetic, mutation, or implicit string coercion. Referenced steps must exist and value dependencies cannot cycle.
Every execution also gets `finish_execution`, whether or not `allow_outputs` is set.
### Steps
| Field | Required | Notes |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `id` | yes | Lowercase step identifier, unique within the trigger. |
| `environment` | yes | Environment name or a finite input expression resolving to one. |
| `max_runtime` | yes | Positive step hard limit, up to 24h. |
| `idle_timeout` | yes | Positive idle limit, no longer than the step hard limit. |
| `agent` | yes | `provider`, optional `model`, `mode`, and `thinkingOptionId`. |
| `prompt` | yes | Non-empty list of `text` and GitHub-only `include` blocks. |
| `if` | no | Expression deciding whether this ordered step runs. |
| `output` | no | `{ schema: <JSON Schema> }` for structured `finish_execution`. |
| `allow_outputs` | no | Reply capabilities such as `slack.reply`, `discord.reply`, or `github.reply`, each with optional `max`. |
| `auto_archive` | no | Archives the step's agent when it ends. |
Prompt blocks are objects, not a scalar prompt:
```yaml
prompt:
- text: Request: ${{ paseo.prompt }}
- include: developer.md
```
Use `${{ paseo.prompt }}`, `${{ paseo.inputs.* }}`, `${{ steps.*.outputs.* }}`, and `${{ values.* }}` in prompts, conditions, and agent selection fields. Provider event payloads are not part of this workflow expression namespace; provider adapters put the normalized request into the prompt and preserve the raw event as evidence.
## Prompt partials
`include` paths are relative to `.paseo/partials/` and are resolved at the exact GitHub configuration commit. Hub stores the resolved content and SHA-256 hash in the immutable revision. Missing files, unsafe paths, symlinks, submodules, directories, and nested includes are rejected. Manual configurations cannot use repository partials.
## Deadlines
The trigger's `max_runtime` is the hard limit for the complete workflow run. Each step also has `max_runtime` and `idle_timeout`:
```yaml
max_runtime: 2h
steps:
- id: classify
max_runtime: 2m
idle_timeout: 30s
- id: implement
max_runtime: 90m
idle_timeout: 10m
```
The effective step hard and idle deadlines are capped by the remaining trigger deadline. Meaningful daemon activity refreshes idle time, but cannot extend a hard deadline. Hub persists absolute deadlines, so a restart or deployment does not reset them. A step timeout fails the run; a trigger timeout stops later steps and interrupts a live agent.
## Provider invocation
The provider removes its mention or marker before Hub parses leading declared input tokens. Slack and Discord place the inputs immediately after the bot mention. GitHub places them after the configured marker. Manual runs send the same string as the API `input`:
```text
@Paseo repo=project investigate the failed sync
```
The first token that is not a declared input begins the prompt. The clean prompt is available as `${{ paseo.prompt }}`. The raw provider message remains separate Activity evidence. See [provider triggers](/docs/hub/triggers) for provider-specific marker and filter behavior.
## Removed fields
Do not put execution fields directly on a trigger. `environment`, `agent`, `prompt`, `timeout`, `idle_timeout`, `auto_archive`, and `allow_outputs` are step fields now. The duration field is `max_runtime`; `timeout` is not an alias.
Next: [Hub workflows](/docs/hub/workflows) for routing patterns and copyable configurations.