* feat(hub): deploy canonical workflow bundles Keep CLI deployment aligned with Hub's authored bundle boundary so validation and activation receive the same deterministic source files. Public documentation now teaches that boundary as the only configuration format. * test(hub): assert validation diagnostics directly * refactor(hub): deepen HTTP client module * feat(hub): validate named agent configurations * docs(hub): align Claude mode example
6.4 KiB
title, description, nav, order, category
| title | description | nav | order | category |
|---|---|---|---|---|
| Hub workflows | Build ordered Hub workflows with prompts, routing, outputs, and provider authority. | Workflows | 65 | 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:
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, not hub.reply.
Choose where a step runs
A literal selects one named environment:
environment: dev
A finite input can select among complete named environments:
name: route-repository
on: manual.run
max_runtime: 1h
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:
agent: codex-safe
Or provide one complete static inline configuration:
agent:
provider: claude
mode: full-access
For dynamic routing, select complete named agents from a finite expression:
name: route-agent
on: manual.run
max_runtime: 1h
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.
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:
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.
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:
name: conditional-review
on: manual.run
max_runtime: 1h
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:
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 lists every field. Provider filters and invocation text are in Triggers.