Psi can delegate reusable tasks to named workflows.
A workflow is a named prompt or orchestration loaded from .psi/workflows/
as a built-in core capability.
.md files author single-step prompt workflows.edn files author multi-step orchestration workflowsSome workflows are single focused agents; others are multi-step flows that pass results from one step to the next.
This document is the primary example-led guide for workflow authoring. It covers the user-facing workflow surface, how to enable and run workflows, and the supported target-authored grammar.
Workflow loading is built in.
No extension manifest entry is required to enable /delegate or .psi/workflows/ discovery.
Optional workflow-adjacent extensions such as psi/mementum still use normal extension install manifests.
For manifest details and install options, see doc/extensions-install.md.
Workflow definitions are discovered from:
~/.psi/workflows/
~/.psi/agent/workflows/
<project>/.psi/workflows/
Accepted file kinds:
*.md ; single-step prompt workflows
*.edn ; multi-step orchestration workflows
Within precedence-ordered roots, later same-kind duplicates win with a warning.
Mixed-kind same-name collisions (planner.md plus planner.edn) are load
errors.
This repository includes many examples there, including:
plan-buildplan-build-reviewdelegate-build-reviewgh-bug-triage-modularplannerbuilderreviewerThe authoritative example set is:
planner / builder / reviewer — single-step markdown prompt workflow examplesplan-build — compact multi-step orchestration exampleplan-build-review — compact multi-step orchestration exampledelegate-build-review — executable delegate-heavy target-authored example proving canonical downstream delegated yielded-text consumptiongh-bug-triage-modular — richer target-authored orchestration example proving delegated yielded text plus structured delegated handoff consumptionNote: this repository still contains transitional checked-in .md workflow
artifacts from the pre-split contract. New authoring should treat .md as
single-step and .edn as multi-step.
The current remaining deferred-migration markdown wrappers are:
gh-bug-discover-and-read.mdgh-bug-post-repro.mdgh-bug-reproduce.mdgh-issue-create-worktree.mdgh-issue-push-intent.mdgh-issue-task-intent.mdimplement-task-in-worktree.mdThose files still begin with legacy EDN workflow maps and are intentionally
tracked by the repo-corpus validation test as outstanding migration blockers,
not as valid single-step markdown examples. A later migration task should move
those orchestration workflows to .edn and leave only true standalone prompt
workflows in .md.
Psi exposes:
/delegate <workflow> <prompt>/delegate-reloadTypical usage:
/delegate planner analyze the scope of the current refactor
/delegate plan-build-review add user-facing workflow docs
/delegate gh-bug-triage-modular issue 123
What happens:
If you want a workflow to continue from a narrow request, put that request after the workflow name as the prompt text.
The underlying delegate tool also exposes management actions for active and
retained workflow runs: list, continue, and remove. delegate list is
scoped to the invoking session; it shows delegated workflow runs owned by that
session and does not show runs from unrelated sessions. Listed ids are canonical
workflow run ids, so the same id can be used with delegate continue when the
workflow status supports continuation, or with delegate remove while the run
still exists.
List output reports the canonical workflow status as the primary status and, when
available, the delegate/background attempt status separately. For example, a
blocked workflow may list as primary status blocked with a separate delegate
attempt status of completed, and a retained timed-out delegate attempt may show
the canonical workflow status plus a separate delegate timed-out status.
delegate remove deletes the canonical workflow run. When the listed run still
has an active delegate/background job, remove first cleans up or terminalizes that
active job so a later delegate list does not report a stale active attempt for a
removed workflow run. If that cleanup cannot be completed, remove fails with an
actionable error and leaves the canonical run visible/manageable.
When editing workflow files under .psi/workflows/, reload them without restarting psi:
/delegate-reload
Reloading:
Use this during workflow authoring or prompt iteration.
Psi automatically cleans up retained terminal workflow runs and their linked workflow-owned child-session trees.
Retention applies per originating agent session. The retained terminal status set
is :completed, :failed, and :cancelled. Non-terminal workflow runs remain
present and are never removed by this cleanup.
The effective retention count is read from runtime config at:
[:config :completed-workflow-run-retention-count]
Behavior:
12 or higher, psi keeps that many newest retained terminal
workflow runs for each originating session0, a newly terminal retained run is removed immediatelyNewest-first ordering uses workflow-run terminal transition time (:finished-at)
with canonical workflow-run creation order as the deterministic tie-breaker when
multiple runs share the same terminal timestamp.
When an older retained terminal workflow run is removed, psi also tree-closes that run's linked workflow-owned child sessions. The cleanup target set is the canonical deduplicated union of linked execution-session ids and judge-session ids recorded on that run. Missing, already-closed, duplicate, or non workflow-owned linked roots are skipped.
This changes user-visible workflow introspection and listing behavior: retained terminal workflow runs and their workflow-owned child sessions no longer remain indefinitely once newer retained terminal runs for the same originating session exist.
Prefer the converged target workflow grammar for new examples and new workflow files.
That grammar has three explicit step forms:
:type :invoke — deterministic operation call:type :session — inline child-session construction:type :delegate — call another named workflow through an explicit boundaryAt a high level:
:invoke when code should do deterministic work and return structured data:session when you want to assemble a child conversation inline:delegate when you want to call a reusable named workflowFor the formal grammar, see doc/workflow-grammar.md.
For the conceptual explanation, see
doc/workflow-grammar-concepts.md.
plan-build is the smallest authoritative example of the preferred inline
session style.
---
name: plan-build
description: Plan and build without review
---
{:steps [{:name "plan"
:type :session
:tools ["read" "bash"]
:contributions [{:type :template
:text "{{input}}"
:vars {"input" {:from :workflow-input
:path [:input]}}}]}
{:name "build"
:type :session
:tools ["read" "bash" "edit" "write"]
:contributions [{:type :source
:from :workflow-original}
{:type :template
:text "Execute this plan:\n\n{{plan}}\n\nOriginal request: {{original}}"
:vars {"plan" {:from {:step "plan" :yield :text}}
"original" {:from :workflow-original
:path [:original]}}}]}]}
What this teaches:
:type :session:contributions:text + :vars{:from {:step "plan" :yield :text}}:workflow-originalplan-build-review extends the same style with one more downstream step.
---
name: plan-build-review
description: Plan, build, and review code changes
---
{:steps [{:name "plan"
:type :session
:tools ["read" "bash"]
:contributions [{:type :template
:text "{{input}}"
:vars {"input" {:from :workflow-input
:path [:input]}}}]}
{:name "build"
:type :session
:tools ["read" "bash" "edit" "write"]
:contributions [{:type :source
:from :workflow-original}
{:type :template
:text "Execute this plan:\n\n{{plan}}\n\nOriginal request: {{original}}"
:vars {"plan" {:from {:step "plan" :yield :text}}
"original" {:from :workflow-original
:path [:original]}}}]}
{:name "review"
:type :session
:tools ["read" "bash"]
:contributions [{:type :source
:from :workflow-original}
{:type :template
:text "Review the following implementation:\n\n{{implementation}}\n\nOriginal request: {{original}}"
:vars {"implementation" {:from {:step "build" :yield :text}}
"original" {:from :workflow-original
:path [:original]}}}]}]}
What this adds:
$INPUT / $ORIGINAL shortcutsdelegate-build-review is the authoritative checked-in target-authored example
for delegate-heavy downstream authoring.
---
name: delegate-build-review
description: Delegate planning and building, then review the delegated build result
---
{:steps [{:name "plan"
:type :delegate
:target "planner"
:prompt-string {:type :template
:text "{{input}}"
:vars {"input" {:from :workflow-input
:path [:input]}}}
:context [{:type :source
:from :workflow-original}]}
{:name "build"
:type :delegate
:target "builder"
:prompt-string {:type :template
:text "Execute this plan:\n\n{{plan}}\n\nOriginal request: {{original}}"
:vars {"plan" {:from {:step "plan" :yield :text}}
"original" {:from :workflow-original
:path [:original]}}}
:context [{:type :source
:from :workflow-original}
{:type :source
:from {:step "plan" :yield :text}}]}
{:name "review"
:type :session
:tools ["read" "bash"]
:contributions [{:type :source
:from :workflow-original}
{:type :template
:text "Review the following delegated implementation:\n\n{{implementation}}\n\nOriginal request: {{original}}"
:vars {"implementation" {:from {:step "build" :yield :text}}
"original" {:from :workflow-original
:path [:original]}}}]}]}
What this teaches:
:type :delegate boundaries for reusable named workflows{:from {:step "..." :yield :text}}:prompt-string as the new immediate ask for the callee:context as forwarded reference material:yield :text ref shape used for prior session resultsMinimum canonical delegated result model:
{:from {:step "..." :yield :text}}:terminal-contract {:handoff {:type :markdown-handoff-data}}{:from {:step "..." :output :handoff}}:output :handoffgh-bug-triage-modular is now the richer orchestration/context/reference
example for realistic bug triage.
It proves the dual-plane delegated model directly:
:output :handoffRepresentative target-style classification step:
{:name "post-repro"
:type :delegate
:target "gh-bug-post-repro"
:outputs {:handoff {:source :delegate/handoff}}
:prompt-string {:type :template
:text "{{report}}"
:vars {"report" {:from {:step "reproduce" :yield :text}}}}
:context [{:type :source
:from :workflow-original}
{:type :source
:from {:step "discover" :output :handoff}}
{:type :source
:from {:step "worktree" :output :handoff}}
{:type :source
:from {:step "reproduce" :output :handoff}}]}
What this teaches:
:yield :text is the human-facing chaining surface:output :handoff is the machine-facing orchestration surface for delegated workflow exports:outputs for local validated model/judge data; see doc/workflow-grammar.md and doc/workflow-ir.md for the formal schema, raw-output envelope, validation, and provider-strategy detailsThe most important authoring references in this guide are:
:workflow-input — the current workflow's input value:workflow-original — carried original request/reference context{:from {:step "..." :yield :text}} — prior step result used as the next ask, including delegate-step yielded text{:from {:step "..." :output :handoff}} — prior delegated workflow's structured terminal handoff{:from {:step "..." :output :classification} :path [:next-action]} — prior session-step structured output field, when that step declares a structured entry in :outputs{:from {:step "..." :output :transcript}} with :projection — projected
transcript/reference contextInterpretation:
:workflow-input is the immediate ask for the current workflow invocation:workflow-original is the carried reference context:yield refs are the simplest way to feed one step's result into
the next step's authored text:output :handoff refs are the stable way to consume machine-facing
exported workflow data without parsing markdown heuristically downstream:outputs are the stable downstream-reference surface for
machine control flow to consume model-generated data through validated fields
instead of prose parsing; each session step may declare at most one structured
output key, with multiple values grouped as fields of one map schema:outputs are judge-local data for transition evaluation;
they are not automatically exported as parent step :output refs, and each
judge may declare at most one structured output key:context on a delegate step carries forwarded material without changing the
delegated workflow's prompt stringKeep the authoring choices practical:
:session:delegate:invokeThis guide intentionally teaches the currently migrated example-led surfaces:
:session authoring:prompt-string and :context:output :handoff:outputs for validated downstream
machine-facing data:outputs for validated transition
evaluation data:path references for multiple fields:workflow-input, :workflow-original, prior
step yields, delegated handoffs, structured output fields, and projected
transcript contextIt does not try to turn arbitrary delegate-local runtime envelopes or diagnostics
into authoring contracts. Delegate handoffs remain the standardized delegated
workflow export, session structured :outputs are the validated downstream
model-data surface, and LLM-judge structured :outputs are validated
judge-local transition data unless a future explicit export contract says
otherwise. When you
need the full formal surface, use the grammar/reference docs.
Prefer:
name and description:type on every authored stepGood first workflow authoring loop:
.psi/workflows/<name>.md for a single-step prompt workflow, or .psi/workflows/<name>.edn for a multi-step orchestration workflow/delegate-reload/delegate <name> <prompt>.md single-step workflow authoring.md single-step workflows support {{input}} and {{original}} template
variables directly in the body — no frontmatter declaration is needed.
{{input}} — expands to the workflow's input text (the prompt string passed
to /delegate){{original}} — expands to the carried original request context
(:workflow-original)Minimal example:
---
name: my-workflow
description: A simple single-step workflow
tools:
- read
- bash
---
Perform the task described by {{input}}.
For custom variable bindings, declare a vars: key in the frontmatter as an
EDN string. Each declared var must specify a :from source — either
:workflow-input (with optional :path) or :workflow-original:
---
name: my-workflow
description: Workflow with a custom var
tools:
- read
vars: '{"task-path" {:from :workflow-input :path [:task-path]}}'
---
Work on the task at {{task-path}}.
Input summary: {{input}}.
Allowed :from values in vars::
:workflow-input — the workflow input map; use :path to extract a nested field:workflow-original — the carried original request contextAny {{varname}} token in the body that is neither a standard var (input,
original) nor declared in vars: produces a compile-time error when the
workflow file is loaded. This catches typos and missing declarations before
runtime.
Tokens that do not match the var pattern (e.g. {{1bad}}, {{}}) pass through
literally and are not subject to this check.
doc/workflow-grammar.md — workflow grammardoc/workflow-grammar-concepts.md — workflow concepts and semanticsdoc/extensions-install.md — install optional extensions that may complement workflow usagedoc/extensions.md — extension/tool details for workflow-adjacent extensions and shared workflow display conventionsdoc/tui.md — general in-session command usageREADME.md — top-level project overviewCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |