* docs(hub): align public docs with v0.3 Bring the public workflow, API, authority, activity, and self-hosting guidance in line with the released Hub contracts. Clarify the public HTTPS boundary required by provider events while keeping Hub and PostgreSQL private. * docs(hub): clarify daemon logout scope * docs(hub): restore Compose path and simplify TLS guidance * docs(hub): link the single-repo-team-bot example
250 lines
6.5 KiB
Markdown
250 lines
6.5 KiB
Markdown
---
|
|
title: Hub workflows
|
|
description: Build ordered Hub workflows with prompts, routing, outputs, and provider authority.
|
|
nav: Workflows
|
|
order: 64
|
|
category: Hub
|
|
---
|
|
|
|
# Hub workflows
|
|
|
|
A workflow file contains one trigger and the ordered steps it starts. Files are discovered from `.paseo/workflows/*.yml`.
|
|
|
|
## Your first workflow
|
|
|
|
Assume `.paseo/hub.yml` defines an environment named `dev` and an agent named `codex`. Add `.paseo/workflows/slack-help.yml`:
|
|
|
|
```yaml
|
|
name: slack-help
|
|
on: slack.mention
|
|
max_runtime: 1h
|
|
filters:
|
|
workspace: T01234567
|
|
channels: [C01234567]
|
|
from_users: [U01234567]
|
|
steps:
|
|
- id: answer
|
|
environment: dev
|
|
max_runtime: 30m
|
|
idle_timeout: 5m
|
|
agent: codex
|
|
prompt:
|
|
- text: |
|
|
Answer the request. Call hub.reply once, then call hub.finish_execution.
|
|
|
|
<user-prompt>
|
|
${{ paseo.prompt }}
|
|
</user-prompt>
|
|
allow_outputs:
|
|
- { type: slack.reply, max: 1, required: true }
|
|
```
|
|
|
|
Hub removes the mention and declared input headers before exposing the remaining text as `${{ paseo.prompt }}`. The reply capability is explicit beside the Slack trigger. Discord uses `discord.reply`; GitHub uses a step-scoped [`github` block](/docs/hub/github), not `hub.reply`.
|
|
|
|
## Choose where a step runs
|
|
|
|
A literal selects one named environment:
|
|
|
|
```yaml
|
|
environment: dev
|
|
```
|
|
|
|
A finite input can select among complete named environments:
|
|
|
|
```yaml
|
|
name: route-repository
|
|
on: manual.run
|
|
max_runtime: 1h
|
|
filters:
|
|
from_users: [automation]
|
|
inputs:
|
|
repo:
|
|
type: string
|
|
required: true
|
|
choices: [paseo, hub]
|
|
steps:
|
|
- id: work
|
|
environment: ${{ paseo.inputs.repo }}
|
|
max_runtime: 30m
|
|
idle_timeout: 5m
|
|
agent: codex
|
|
prompt:
|
|
- text: ${{ paseo.prompt }}
|
|
```
|
|
|
|
Activation checks every `choices` result. Environment objects are never merged or overridden by a workflow.
|
|
|
|
## Choose an agent
|
|
|
|
A step may select a named agent:
|
|
|
|
```yaml
|
|
agent: codex-safe
|
|
```
|
|
|
|
Or provide one complete static inline configuration:
|
|
|
|
```yaml
|
|
agent:
|
|
provider: claude
|
|
mode: full-access
|
|
```
|
|
|
|
For dynamic routing, select complete named agents from a finite expression:
|
|
|
|
```yaml
|
|
name: route-agent
|
|
on: manual.run
|
|
max_runtime: 1h
|
|
filters:
|
|
from_users: [automation]
|
|
inputs:
|
|
agent:
|
|
type: string
|
|
required: true
|
|
choices: [codex-safe, claude]
|
|
steps:
|
|
- id: work
|
|
environment: paseo
|
|
max_runtime: 30m
|
|
idle_timeout: 5m
|
|
agent: ${{ paseo.inputs.agent }}
|
|
prompt:
|
|
- text: ${{ paseo.prompt }}
|
|
```
|
|
|
|
If `codex-safe` contains structured sandbox options in `hub.yml`, selecting it carries those options unchanged. A dynamic inline object such as `provider: ${{ paseo.inputs.agent }}` is rejected.
|
|
|
|
## Route from a classifier
|
|
|
|
An earlier step can return finite structured output. Later authority must be bounded by `enum` or `const` in that output schema.
|
|
|
|
```yaml
|
|
name: classify-request
|
|
on: discord.mention
|
|
max_runtime: 2h
|
|
filters:
|
|
guild: "123456789012345678"
|
|
from_users: ["345678901234567890"]
|
|
values:
|
|
selected_environment: ${{ steps.classify.outputs.environment }}
|
|
selected_agent: ${{ steps.classify.outputs.agent }}
|
|
steps:
|
|
- id: classify
|
|
environment: hub
|
|
max_runtime: 5m
|
|
idle_timeout: 1m
|
|
agent: claude
|
|
prompt:
|
|
- include: partials/classify.md
|
|
- text: ${{ paseo.prompt }}
|
|
output:
|
|
schema:
|
|
type: object
|
|
required: [environment, agent]
|
|
properties:
|
|
environment: { enum: [paseo, hub] }
|
|
agent: { enum: [codex-safe, claude] }
|
|
additionalProperties: false
|
|
- id: work
|
|
environment: ${{ values.selected_environment }}
|
|
max_runtime: 1h
|
|
idle_timeout: 10m
|
|
agent: ${{ values.selected_agent }}
|
|
prompt:
|
|
- text: |
|
|
Complete the request. Call hub.reply once, then call hub.finish_execution.
|
|
- text: ${{ paseo.prompt }}
|
|
allow_outputs:
|
|
- { type: discord.reply, max: 1, required: true }
|
|
```
|
|
|
|
`.paseo/workflows/partials/classify.md`:
|
|
|
|
```text
|
|
Choose one configured repository environment and one complete named agent configuration.
|
|
```
|
|
|
|
The workflow keeps one classifier branch and one worker branch. It does not duplicate a worker step for every environment/provider pair.
|
|
|
|
## Prompt and context
|
|
|
|
Prompt blocks remain ordered. Includes are literal file contents; Hub does not recursively scan partial text.
|
|
|
|
```yaml
|
|
prompt:
|
|
- include: partials/instructions.md
|
|
- text: |
|
|
Provider evidence:
|
|
${{ paseo.context }}
|
|
|
|
<user-prompt>
|
|
${{ paseo.prompt }}
|
|
</user-prompt>
|
|
```
|
|
|
|
- `${{ paseo.prompt }}` is normalized request text. It is always explicit in the authored prompt.
|
|
- `${{ paseo.context }}` opts this step into provider context materialization and inserts JSON. Without that expression, Hub does not fetch or inject ambient context.
|
|
|
|
Keep untrusted request text in a clearly delimited block. A partial is instruction text, not hidden authority.
|
|
|
|
## Conditions and ordered output
|
|
|
|
Steps run in file order. `if` may read inputs, values, and prior step output:
|
|
|
|
```yaml
|
|
name: conditional-review
|
|
on: manual.run
|
|
max_runtime: 1h
|
|
filters:
|
|
from_users: [automation]
|
|
steps:
|
|
- id: inspect
|
|
environment: paseo
|
|
max_runtime: 10m
|
|
idle_timeout: 2m
|
|
agent: codex-safe
|
|
prompt:
|
|
- text: ${{ paseo.prompt }}
|
|
output:
|
|
schema:
|
|
type: object
|
|
required: [needs_review]
|
|
properties:
|
|
needs_review: { type: boolean }
|
|
additionalProperties: false
|
|
- id: review
|
|
if: ${{ steps.inspect.outputs.needs_review == true }}
|
|
environment: paseo
|
|
max_runtime: 30m
|
|
idle_timeout: 5m
|
|
agent: claude
|
|
prompt:
|
|
- text: Review the prior result and call hub.finish_execution.
|
|
```
|
|
|
|
A step cannot read a later step. Step IDs are unique within the workflow.
|
|
|
|
## Tell the agent which tool to call
|
|
|
|
`allow_outputs` grants a capability; it does not rewrite the prompt. Name the required action:
|
|
|
|
```yaml
|
|
prompt:
|
|
- text: |
|
|
Send the final answer with hub.reply.
|
|
Then call hub.finish_execution.
|
|
allow_outputs:
|
|
- type: slack.reply
|
|
max: 1
|
|
required: true
|
|
```
|
|
|
|
`max` defaults to `1`. `required: true` prevents successful completion until the capability has been emitted. Keep Slack and Discord reply types in their own provider workflow files.
|
|
|
|
## Deadlines
|
|
|
|
The workflow `max_runtime` limits the complete run. Every step has its own `max_runtime` and `idle_timeout`; remaining workflow time caps both. A timeout fails the run and stops later steps.
|
|
|
|
The [configuration reference](/docs/hub/configuration/hub-yml) lists every field. Provider filters and invocation text are in [Triggers](/docs/hub/triggers).
|