View as markdown

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
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:

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
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.

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
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:

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.