Skip to content

Reference

Contract for version: v1alpha1. The loader enforces cross-field rules. docs/workflow.schema.json covers shape only.

Document

KeyRequiredNotes
versionyesMust be v1alpha1
stepsyesNon-empty list
titlenoPicker label. Defaults from humanized filename. Truncated to row width.
descriptionnoPicker subtitle. Wrapped/truncated to row width (at most two lines).
hiddennoHide from picker. hwf run still works.
inputsnoEntry workflow only prompts
returnsnoChild export map or template
on_failurenoEntry-only recovery action

An unsupported format version fails the load. The error gives rewrite-or-upgrade guidance. The package version stays semver 0.x. A later, incompatible alpha version uses v1alphaN. Workflow YAML never declares a herdr version.

Actions

Each step uses exactly one of agent, run, herdr, or workflow. Every step also accepts the optional fields id, when, and continue_on_error.

run:

FormBehavior
listargv, no shell. Templates allowed per element.
stringshell (sh on macOS/Linux, cmd on Windows) unless shell: set

The natural result is {stdout, stderr, exit_code, failed}. Inputs export as HWF_<name> variables. An explicit env: entry cannot use the reserved HWF_ prefix. Omit cwd to use the invocation working directory. Omit timeout to run without a workflow timeout.

A placed run: step with pane.open: beside or pane.open: below keeps the anchor pane, using pane.split. It then submits the argv as a shell-quoted line through pane.send_input. herdr has no socket method pane.run. layout.apply replaces the tab. It does not keep live processes. pane.open: tab still launches the argv directly through layout.apply.

Also: shell, cwd, env, pane, background, ready_when, timeout, retry.

agent:

The agent: value is the prompt text. using: starts a new managed agent. target: addresses an existing agent. The two fields are mutually exclusive.

ModeBehavior
using: / default profileCreate pane → agent.startagent.prompt
target:Require idle/done, then prompt. No pane/cwd/env.

The blocking result is {response, agent, pane_id}. The default turn timeout is 30 minutes. The default native startup timeout is 30 seconds. A blocked state notifies once per episode and keeps waiting. An unknown state never succeeds.

{{context.agent}} holds the invoking agent's live name. It holds the pane ID instead when herdr reports no name. Agents you start yourself have no name. A pane ID is a valid target.

A workflow that targets {{context.agent}} must start while that agent is idle or done. prefix+k from a settled pane works. Asking the agent itself to run the workflow cannot work. The agent is busy serving your request.

Also: cwd, env, pane, background, timeout.

herdr:

yaml
- herdr: notification.show
  params:
    title: done

herdr fills in no context automatically. A denied method fails at load. A success result is the complete structured herdr payload. Also: params, retry.

workflow:

yaml
- workflow: child
  inputs:
    branch: "{{inputs.branch}}"

The child workflow runs in isolation. An optional child returns: value becomes this step's natural result. A child's on_failure action does not run during a parent invocation.

Pane block

yaml
pane:
  open: tab # tab | beside | below
  target: "…" # split anchor. Default: invocation pane
  workspace: "…" # tab workspace. Default: invocation workspace
  size: 40 # percent for the NEW pane
  focus: true
  close: success # agent-only: success | always

beside opens a herdr right split. below opens a down split. herdr clamps split ratios to a range of 0.1 to 0.9 (layout.rs valid_split_ratio). herdr clamps pane.size values below 10 or above 90 to that range.

Background processes are pane-owned. They survive client detach. They do not survive an ordinary server restart. A later workflow failure does not stop them by default.

Readiness

A ready_when: /regex/ field on a placed run: step uses pane.wait_for_output. It reads from source recent, across 80 rendered rows, with ANSI codes stripped, using one logical-line regex. timeout is required. The step succeeds only on a native match. Text already present in the snapshot can also match. This check does not detect process exit.

Templates

Use only {{inputs.name}}, {{steps.id.field}}, and {{context.key}}. A whole-value template in structured YAML keeps its source type. An embedded template renders as text. {{prompt}} is config-only. It is not a workflow template.

Context

KeyMeaning
workspace, tab, pane, worktree, agentInvocation identity
selectionSelected text (empty if none)
platformmacos | linux | windows
transcript, transcript_fileSensitive value. Preflight fails if unavailable.
errorRecovery only (on_failure)

Inputs

A name matches [a-z][a-z0-9_]{0,31}. The types are text, choice (a static list or {run: argv}), and profile (merged profile names, never args). A choice or profile default must exist among the available values. Only the entry workflow prompts, in declaration order.

A dynamic choice runs its argv from the repo root, with a 10-second timeout, a limit of 1,000 options, and an 8 MiB capture cap.

Config

yaml
profiles:
  name:
    kind: claude # non-empty. Live agent.start is authoritative.
    args: ["--model", "…"] # optional
default_profile: name
transcripts:
  claude:
    command: [extractor, argv…]

The layers are the global plugin config directory, then .hwf/config.yaml, then .hwf/config.local.yaml. Each layer replaces whole entries by name. There is no agents: or sessions: key.

Control flow

ConstructBehavior
when:Scalar truthiness or == / !=. False skips (no recovery).
continue_on_errorTolerate failure. Suppress recovery. Run exits nonzero.
retryTotal attempts including first. Local run: / herdr: only.
on_failureEntry-only, once, after first non-tolerated failure
Transport lossStop, keep panes, skip recovery, report uncertain coordination

Caps

SourceLimit
Generated HWF_* env block24 KiB
Command / managed response / transcript / dynamic-choice stdout8 MiB
Dynamic choices1,000
Dynamic choice timeout10s
Transcript extractor timeout30s

Trust, share, and import

A workflow file is reviewed executable code. The picker and the workbench show repo or global provenance. They flag commands, transcript references, and sensitive herdr methods.

Sharing uses the command hwf workflow import "<bundle>". The bundle is a gzip-compressed, base64-encoded {name, yaml}[] array. It carries no version, root, source, or config metadata. Export starts from the exact selected source. It walks workflow: children using the same repo-first resolution as runtime.

Import requires a review of every YAML body and the aggregate warnings. Choose one destination scope for the whole bundle, then confirm. A name conflict keeps the existing entry. The workbench asks for replace-all confirmation interactively. The CLI exits and reports the conflicts. It needs --force on a rerun. The old single-workflow {v, name, body} format is rejected. The workbench share and import views never execute workflows.

The method denylist covers server and plugin lifecycle, identity authority, experimental graphics, and similar cases. It guards against accidental misuse and protects runtime safety. It is not a sandbox. A trusted run: step can call the complete herdr CLI or socket as the current user.

Portability

v1alpha1 syntax and argv behavior work across platforms. Runtime capability follows the installed herdr platform support. Native Windows support is in beta. Use {{context.platform}} with when: for OS-specific steps.