docs(hub): document durable workflow routing (#2943)
* docs(hub): document durable workflow routing * docs(hub): make workflow guide progressive
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user