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
+190 -136
View File
@@ -1,6 +1,6 @@
---
title: Hub examples
description: Copyable hub.yml configurations for issue triage, PR review, Discord and Slack mentions, and isolated worktrees.
description: Copyable Hub workflow configurations for typed routing, classification, direct answers, implementations, and provider replies.
nav: Examples
order: 70
category: Hub
@@ -8,175 +8,229 @@ category: Hub
# Hub examples
Complete configurations you can adapt. Field details are in the [`hub.yml` reference](/docs/hub/configuration/hub-yml).
These examples use the durable step syntax. Replace the daemon, paths, provider identifiers, and provider filters with resources in your organization. The shared workflow page explains the contract behind each pattern.
Every example assumes one environment:
## Model and provider selection
Use finite choices when caller input selects an agent provider or model.
```yaml
environments:
- name: dev
- name: development
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/api
```
cwd: /Users/you/code/project
## Answer a mention on an issue
The baseline. An agent runs in your checkout and can push, because GitHub-triggered executions get a scoped `GH_TOKEN`.
```yaml
triggers:
- name: mention
on: github.issue_comment
environment: dev
- name: selectable-agent
on: manual.run
max_runtime: 2h
filters:
repo: acme/api
contains: "@paseo"
from_users: [alice, bob]
agent:
provider: codex
mode: full-access
prompt: |
You were mentioned on ${{ paseo.event.github.trigger_url }}.
${{ paseo.event.github.comment.body }}
Reply by commenting on the thread with gh.
from_users: [automation]
inputs:
provider:
type: string
default: codex
choices: [codex, claude]
model:
type: string
default: standard
choices: [standard, fast]
steps:
- id: work
environment: development
max_runtime: 90m
idle_timeout: 10m
agent:
provider: ${{ paseo.inputs.provider }}
model: ${{ paseo.inputs.model }}
mode: full-access
prompt:
- text: ${{ paseo.prompt }}
```
## Review a pull request in its own worktree
Invoke it with `provider=claude model=fast investigate the sync`. An undeclared leading key stops header parsing and becomes prompt text.
`checkout-branch` on the PR's head branch puts the agent on the right code without disturbing your working tree.
## Repository routing
```yaml
triggers:
- name: review
on: github.pull_request_review_comment
environment: review
filters:
repo: acme/api
contains: "@paseo review"
from_users: [alice]
agent:
provider: claude
mode: full-access
timeout: 30m
auto_archive: true
prompt: |
Address this review comment on PR #${{ paseo.event.github.pull_request.number }}:
${{ paseo.event.github.comment.body }}
```
Add the worktree to the environment it uses:
Use a declared input plus `filters.inputs` when separate triggers should own separate repositories or projects.
```yaml
environments:
- name: review
- name: project
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/api
worktree:
mode: checkout-branch
branch: ${{ paseo.event.github.pull_request.head.ref }}
```
## Triage new issues on a fresh branch
```yaml
environments:
- name: triage
cwd: /Users/you/code/project
- name: paseo
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/api
worktree:
mode: branch-off
newBranch: triage/${{ paseo.event.github.issue.number }}
base: main
cwd: /Users/you/code/paseo
triggers:
- name: triage
on: github.issues
environment: triage
filters:
repo: acme/api
from_users: [alice, bob]
agent:
provider: codex
mode: full-access
prompt: |
Investigate issue #${{ paseo.event.github.issue.number }}:
${{ paseo.event.github.issue.title }}
${{ paseo.event.github.issue.body }}
If you find the cause, open a pull request.
```
## Discord mention with repository access
The trigger fires from Discord, so the GitHub token has to be requested by connection slug.
```yaml
triggers:
- name: discord
on: discord.mention
environment: dev
filters:
guild: "123456789012345678"
from_users: ["345678901234567890"]
agent:
provider: codex
mode: full-access
env:
GH_TOKEN: ${{ paseo.connections.acme-github.token }}
allow_outputs:
- type: discord.reply
prompt: |
${{ paseo.event.discord.trigger_message.body }}
Reply in the thread when you are done.
```
## Slack mention that replies in-thread
```yaml
triggers:
- name: slack
- name: project-request
on: slack.mention
environment: dev
max_runtime: 2h
filters:
workspace: T01234567
channels: [C01234567]
from_users: [U01234567]
agent:
provider: codex
mode: full-access
idle_timeout: 10m
allow_outputs:
- type: slack.reply
prompt: ${{ paseo.event.slack.trigger_message.body }}
inputs: { repo: project }
inputs:
repo:
type: string
choices: [project, paseo]
steps:
- id: project-work
environment: project
max_runtime: 90m
idle_timeout: 10m
agent:
provider: codex
mode: full-access
prompt:
- text: ${{ paseo.prompt }}
allow_outputs:
- type: slack.reply
max: 5
- name: paseo-request
on: slack.mention
max_runtime: 2h
filters:
workspace: T01234567
from_users: [U01234567]
inputs: { repo: paseo }
inputs:
repo:
type: string
choices: [project, paseo]
steps:
- id: paseo-work
environment: paseo
max_runtime: 90m
idle_timeout: 10m
agent:
provider: codex
mode: full-access
prompt:
- text: ${{ paseo.prompt }}
allow_outputs:
- type: slack.reply
max: 5
```
## One trigger, several repositories
The same event can match multiple triggers, so the input filters make these routes exclusive.
Omit `repo` and the trigger listens to every repository the GitHub connection can see. Pin the connection when the organization has more than one.
## Safety gate and direct answer
For a request that may either be answered or implemented, let a read-only classifier choose a finite branch. Supplying `kind=answer` or `kind=implementation` skips classification.
```yaml
environments:
- name: development
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/project
triggers:
- name: any-repo
- name: request
on: github.issue_comment
environment: dev
max_runtime: 2h
filters:
connection: acme-github
repo: example/project
contains: "@paseo"
from_users: [alice]
agent:
provider: codex
mode: full-access
prompt: |
${{ paseo.event.github.repository_full_name }}:
${{ paseo.event.github.comment.body }}
from_users: [maintainer]
inputs:
kind:
type: string
choices: [answer, implementation]
steps:
- id: classify
if: ${{ paseo.inputs.kind == null }}
environment: development
max_runtime: 2m
idle_timeout: 30s
agent:
provider: codex
mode: read-only
prompt:
- text: Classify this request as answer or implementation.
- text: ${{ paseo.prompt }}
output:
schema:
type: object
additionalProperties: false
required: [kind]
properties:
kind:
enum: [answer, implementation]
- id: answer
if: ${{ paseo.inputs.kind == 'answer' || steps.classify.outputs.kind == 'answer' }}
environment: development
max_runtime: 10m
idle_timeout: 2m
agent:
provider: codex
mode: read-only
prompt:
- text: Answer the request. Do not change files.
- text: ${{ paseo.prompt }}
- id: implementation
if: ${{ paseo.inputs.kind == 'implementation' || steps.classify.outputs.kind == 'implementation' }}
environment: development
max_runtime: 90m
idle_timeout: 10m
agent:
provider: codex
mode: full-access
prompt:
- text: Implement the request, verify it, and report the result.
- text: ${{ paseo.prompt }}
allow_outputs:
- type: github.reply
max: 5
```
`cwd` is fixed per environment, so a trigger like this makes sense when the agent works from a token rather than a checkout.
The answer and implementation conditions cannot both be true for one classification. The workflow ends after the answer step because there is no later matching step.
## PR progress and final updates
Keep progress and final updates on the implementation step. The step can use up to five replies while it works, then finish normally.
```yaml
environments:
- name: development
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/project
triggers:
- name: pr-work
on: github.pull_request_review_comment
max_runtime: 2h
filters:
repo: example/project
contains: "@paseo"
from_users: [maintainer]
steps:
- id: implement-review
environment: development
max_runtime: 90m
idle_timeout: 10m
auto_archive: true
agent:
provider: codex
mode: full-access
prompt:
- text: |
Address the review request.
Request: ${{ paseo.prompt }}
allow_outputs:
- type: github.reply
max: 5
```
For Slack and Discord, use `slack.reply` or `discord.reply` instead. GitHub-triggered agents also receive the scoped GitHub credential for the triggering repository, so the agent can use `gh` for repository updates.
See [Hub workflows](/docs/hub/workflows) for partials, deadlines, structured output retry, and provider invocation details.