* docs(hub): document per-execution worktree branch names
Hub now materializes `${{ paseo.execution.id }}` inside a daemon
environment's `worktree.newBranch` before it persists or dispatches the
launch, so a reusable branch-off environment can get a stable branch per
execution instead of every trigger contending for one literal name.
The reference states the constraint precisely because it is the part the
YAML cannot show: `newBranch` accepts that one execution-scoped path and
nothing else, and any other expression fails bundle activation with the
authored file and field in the error. Literal branch names stay valid.
* docs(hub): reshape the per-execution worktree branch docs
Split the two pages by the job each reader is doing. The daemon page is a
task guide, so it now carries the worktree YAML and only the two facts
that reader needs to choose a branch name, then links out. The reference
owns the exhaustive contract at the `newBranch` field: a table contrasting
a literal with the template, then the accepted expression scope and what
activation rejects.
Dropped the render-before-dispatch ordering. Readers cannot act on where
Hub materializes the value; the reachable behavior is that a retried or
recovered execution keeps its branch, which is what the table says.
* docs(hub): make the per-execution worktree branch the only guidance
A branch-off Hub environment that aims every execution at one branch name
is not a configuration worth teaching, so the docs no longer present it as
an alternative. Both pages now show `newBranch: trigger-${{ paseo.execution.id }}`
and say what it does, with no literal-branch example and no comparison.
The reference keeps the field's type as a bare syntax fact, along with the
accepted expression, what activation rejects, and the authored field the
failure names.
10 KiB
title, description, nav, order, category
| title | description | nav | order | category |
|---|---|---|---|---|
| Hub configuration reference | Canonical Hub resource, workflow, agent, expression, and prompt fields. | Configuration reference | 71 | Hub |
Hub configuration reference
Hub accepts YAML in this layout:
.paseo/
├── hub.yml
└── workflows/
├── <workflow>.yml
└── partials/
└── <partial>.md
Only direct .yml children of .paseo/workflows/ are workflows. Each file contains one trigger and its ordered steps. There is no manifest, includes, uses, reusable step, workflow call, or inheritance.
hub.yml
.paseo/hub.yml contains named project resources. Names are map keys and are not repeated inside each object.
environments:
paseo:
kind: daemon
daemon: laptop
cwd: /Users/you/code/paseo
hub:
kind: daemon
daemon: devbox
cwd: /workspace/hub
agents:
codex-safe:
provider: codex
model: gpt-5.5
thinkingOptionId: xhigh
options:
sandbox_workspace_write:
writable_roots: [/var/cache/npm]
network_access: false
claude:
provider: claude
mode: bypassPermissions
The only top-level keys are environments and agents. A triggers key is rejected with a migration error.
Environments
| Field | Required | Notes |
|---|---|---|
kind |
yes | daemon, fly, or docker; workflow steps must select a daemon environment. |
daemon |
daemon only | Registered daemon slug, resolved when the revision activates. |
cwd |
daemon only | Absolute working directory on the daemon. |
image |
fly/docker | Image name. |
worktree |
no | branch-off, checkout-branch, or checkout-pr target. |
For worktree, use newBranch and optional base with branch-off, branch with checkout-branch, or positive prNumber with checkout-pr.
environments:
review:
kind: daemon
daemon: build-server
cwd: /workspace/project
worktree:
mode: branch-off
newBranch: trigger-${{ paseo.execution.id }}
base: origin/main
newBranch is a branch-name string. Embed ${{ paseo.execution.id }}, which renders the execution's UUID, so every execution branches off base on its own branch and keeps it when Hub retries or recovers that execution.
One execution is one step run, so two steps selecting the same environment get separate branches.
${{ paseo.execution.id }} is the only expression newBranch accepts. paseo.prompt, paseo.context, paseo.inputs.*, values.*, steps.<id>.outputs.*, and provider event fields are unavailable here, and each one fails bundle activation at the authored field, such as .paseo/hub.yml.environments.review.worktree.newBranch.
${{ paseo.execution.id }} fails activation the same way anywhere else in a bundle. branch and prNumber take literal values.
An environment is a complete named object. A step selects its name; objects are not inherited, merged, or partially overridden.
Named agents
Each agent is one complete provider configuration:
| Field | Required | Notes |
|---|---|---|
provider |
yes | Provider ID. |
model |
no | Provider model ID. |
mode |
no | Paseo mode ID. |
thinkingOptionId |
no | Provider thinking option. |
options |
no | JSON-safe provider-native options, preserving names and nesting. |
A named selection preserves the complete object, including structured options. Named agents have no parent, patch, or per-step override.
Hub passes model, mode, thinkingOptionId, and options to the Paseo daemon without renaming or flattening provider fields. The selected daemon validates them against its current provider schema; Hub does not translate provider-native options.
Workflow files
.paseo/workflows/review.yml:
name: review
on: manual.run
max_runtime: 2h
filters:
from_users: [automation]
inputs:
repo:
type: string
required: true
choices: [paseo, hub]
steps:
- id: inspect
environment: ${{ paseo.inputs.repo }}
max_runtime: 30m
idle_timeout: 5m
agent: codex-safe
prompt:
- text: ${{ paseo.prompt }}
| Field | Required | Notes |
|---|---|---|
name |
yes | Workflow name, unique across the bundle. |
on |
yes | Provider event such as manual.run or discord.mention. |
max_runtime |
yes | Hard limit for the complete run, up to 24h. |
filters |
yes | Provider resource filters and the sender allowlist. |
inputs |
no | Typed invocation headers. |
values |
no | Named expressions. |
steps |
yes | One or more ordered inline steps. |
Inputs and values
Inputs have type: string | number | boolean, plus optional required, default, and choices. required and default cannot be combined. Finite choices are required when an input can choose authority such as an environment or named agent.
Values bind expressions:
values:
selected_environment: ${{ steps.classify.outputs.environment }}
selected_agent: ${{ steps.classify.outputs.agent }}
Expressions may read declared paseo.inputs, earlier steps.<id>.outputs, and values. The grammar supports paths, JSON literals, parentheses, !, ==, !=, &&, ||, and ??.
An environment or dynamic named-agent expression must have a finite set of possible string results at activation. Every result must name a configured resource. Runtime selection never falls back to another environment or agent.
Steps
| Field | Required | Notes |
|---|---|---|
id |
yes | Unique within the workflow. |
environment |
yes | Literal environment name or finite expression resolving to one. |
max_runtime |
yes | Step hard limit. |
idle_timeout |
yes | Idle limit no longer than max_runtime. |
agent |
yes | Named agent, finite expression selecting a named agent, or complete static inline agent. |
prompt |
yes | Ordered text and include blocks. |
if |
no | Expression deciding whether the step runs. |
env |
no | Environment variables from connection values. |
output.schema |
no | JSON Schema for structured step output. |
allow_outputs |
no | Provider output capabilities with optional max and required. |
auto_archive |
no | Archive the agent after the step ends. |
github |
no | Explicit GitHub authority for this step. |
An inline agent is static and complete:
agent:
provider: codex
model: gpt-5.5
options:
approval_policy: never
sandbox_mode: read-only
An expression-valued agent selects a named agent. Dynamic provider fields inside an inline object are rejected.
Prompt semantics
prompt:
- include: partials/review.md
- text: |
<user-prompt>
${{ paseo.prompt }}
</user-prompt>
${{ paseo.prompt }} is the normalized request after the provider marker and declared leading key=value inputs are removed. It is not rewritten or augmented with event context.
${{ paseo.context }} opts that step into provider context materialization and renders the result as JSON in the prompt. It is available only in prompt text. Hub does not inject it unless the workflow authors that expression.
Includes resolve relative to .paseo/workflows/, so shared partials use partials/<name>.md. Missing files, absolute or traversing paths, symlinks, content mismatches, and files outside the partial tree are rejected.
Output capabilities
Authority stays on the step that uses it:
allow_outputs:
- type: discord.reply
max: 1
required: true
Slack workflows use slack.reply; Discord workflows use discord.reply. The declaration grants hub.reply, and the prompt must tell the agent to call it. GitHub has no reply output; use an explicit github block.
Every step receives hub.finish_execution. The prompt must tell the agent when to call it; Hub does not append completion or reply instructions. If output.schema is present, hub.finish_execution requires an output value that matches the schema. If an allow_outputs entry is required: true, the agent must emit that output before finishing. max defaults to 1.
Migrating a monolithic file
Keep environments in hub.yml, convert the environment list to a named map, and move each former trigger into its own .paseo/workflows/<name>.yml file. Move shared prompt files to .paseo/workflows/partials/. Define complete named agent configurations under agents and replace dynamic provider fields with finite named-agent selection.
Hub does not read TOML or a monolithic triggers section, and the CLI does not rewrite either format.
See Workflows for complete routing examples.