View as markdown

Hub examples

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.

Model and provider selection

Use finite choices when caller input selects an agent provider or model.

environments:
  - name: development
    kind: daemon
    daemon: my-macbook
    cwd: /Users/you/code/project

triggers:
  - name: selectable-agent
    on: manual.run
    max_runtime: 2h
    filters:
      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 }}

Invoke it with provider=claude model=fast investigate the sync. An undeclared leading key stops header parsing and becomes prompt text.

Repository routing

Use a declared input plus filters.inputs when separate triggers should own separate repositories or projects.

environments:
  - name: project
    kind: daemon
    daemon: my-macbook
    cwd: /Users/you/code/project
  - name: paseo
    kind: daemon
    daemon: my-macbook
    cwd: /Users/you/code/paseo

triggers:
  - name: project-request
    on: slack.mention
    max_runtime: 2h
    filters:
      workspace: T01234567
      from_users: [U01234567]
      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

The same event can match multiple triggers, so the input filters make these routes exclusive.

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.

environments:
  - name: development
    kind: daemon
    daemon: my-macbook
    cwd: /Users/you/code/project

triggers:
  - name: request
    on: github.issue_comment
    max_runtime: 2h
    filters:
      repo: example/project
      contains: "@paseo"
      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

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.

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 for partials, deadlines, structured output retry, and provider invocation details.