Dynamic Workflow — plan-in-code fan-out with verification
This is Hermes's answer to Claude Code's "dynamic workflows" (run hundreds of
parallel subagents in one session). The mechanic worth copying is NOT "more
subagents" — it is moving the plan, the loop, and the intermediate results
OUT of the context window and INTO a script. Normally the agent IS the
orchestrator: every intermediate result piles into context, which is exactly
what caps you at a handful of agents. A workflow keeps only the final verified
answer in context; the script holds everything else.
This skill is self-contained, but it builds on standard fan-out hygiene —
chunk inputs to ~50-70KB per child, route structured output to files (not the
summary field, which truncates under load), use delimiter-separated lines
over JSON wrappers, and remember that a "stalled" child often completed its
write anyway (check the filesystem before retrying). If your install has a
delegate-task-output-patterns skill, load it for the detailed thresholds;
the rules above are the load-bearing subset.
The two orchestration-script layers (pick the right one — they are NOT interchangeable)
Hermes has no JS runtime. The "orchestration script" is one of two layers, and
the split is enforced by a real capability boundary, not a style preference:
|
Layer A: execute_code (Python script) |
Layer B: delegate_task batch |
| Use for |
DETERMINISTIC fan-out — fetch N URLs, parse N files, run N shell commands, template N outputs |
LLM-JUDGMENT fan-out — classify, review, decide, write, refute, audit per item |
| The script holds |
the loop + branching + intermediate vars (real Python) |
n/a — you call it once with a tasks=[...] array; each task is its own isolated agent |
| Tools available inside |
web_search, web_extract, read_file, write_file, search_files, terminal, patch ONLY (the SANDBOX_ALLOWED_TOOLS set) |
configured child toolsets, subject to delegate restrictions (leaf children are stripped of delegate_task, clarify, memory, send_message, execute_code — see DELEGATE_BLOCKED_TOOLS) |
Can it call delegate_task? |
NO. delegate_task is NOT in SANDBOX_ALLOWED_TOOLS. Do not write a script that imports it — it will fail. |
itself, if role='orchestrator' and max_spawn_depth>=2 |
| Concurrency |
you control it in Python (ThreadPoolExecutor, batches) |
delegation.max_concurrent_children (default 3; raise in config.yaml) |
| Cost shape |
cheap — most steps are tool calls, no per-item LLM unless you call web_search/aux |
one model call tree PER child task — multiplies linearly, can be very expensive |
Rule of thumb: do the deterministic part in Layer A first (inline, in a
script), then fan out ONLY the irreducibly-LLM step via Layer B. This is
Pattern 1 from delegate-task-output-patterns, applied at workflow scale.
Mixing them: a Layer-A script can write a manifest file, and you (the parent)
then read that manifest and issue a single Layer-B delegate_task batch.
The synchronous trap (READ THIS — it is the #1 way a "workflow" disappoints)
delegate_task runs synchronously inside the parent turn. If the user sends
a new message, hits /stop, or /new, every in-flight child is cancelled and its
work discarded (status interrupted). It does NOT run in the background, and
it does NOT survive the turn. There is no cache-resume of a half-finished fan-out.
So a "workflow" in Hermes is one of:
- Foreground workflow (default): Layer A and/or one Layer-B batch, completed
within a single turn. Good for minutes-long fan-out (dozens of units). The
user waits. This is what you build 90% of the time.
- Durable workflow (hours/days, survives interruption): use the kanban
swarm (the SQLite-backed multi-agent kernel that ships with Hermes —
hermes_cli/kanban_swarm.py + the kanban plugin; if your install has a
kanban-multiagent skill, load it for the workflow). It
writes a task graph (root → parallel workers → verifier → synthesizer) into
the SQLite kanban kernel with a JSON blackboard. State persists across turns
and restarts. This is the ONLY path that matches Claude Code's "runs into
hours and days, resumes where it left off." Reach for it when the foreground
path would time out or when the user must be able to walk away.
Never promise "background, resumable, hundreds of agents over days" from a plain
delegate_task call. For a durable multi-agent workflow graph, the kanban
swarm is the right fit. For simpler durable/out-of-turn cases there are lighter
options too: a cronjob one-shot or scheduled job, or a managed
terminal(background=True, notify_on_complete=True) process — both survive the
turn without standing up a full task graph.
Workflow recipe (foreground)
- Decompose into independent units. What is the unit — a file? an endpoint?
a source? a record? Each unit must be answerable WITHOUT the others' output
(else it's serial, not fan-out — see when_not_to_use).
- Deterministic pre-pass (Layer A). In one
execute_code script, gather the
manifest: list the files, extract the candidate sites, fetch the raw sources,
compute anything regex/parse can compute. Write a manifest to a unique
per-run directory — /tmp/wf_<name>_<uuid>/manifest.jsonl (one unit per
line), never a bare /tmp/wf_<name>/ that a prior interrupted run could have
left stale outputs in. This is the "plan in code." Print the unit count and
the run dir, and stop.
- Size the fan-out against
delegate-task-output-patterns: chunk so each
child handles ~8-12 mechanical file edits OR ~2000-3000 lines of reading OR
~50-70KB of corpus. Look at the LARGEST unit, not the average. One
delegate_task(tasks=[...]) call is bounded by
delegation.max_concurrent_children (default 3) — it does NOT queue hundreds
of tasks internally. For larger fan-out, issue bounded waves yourself (loop:
one batch, collect, next batch) or have the user raise the config
intentionally.
- LLM-judgment fan-out (Layer B). Issue ONE
delegate_task with a tasks=[]
array, one task per chunk. Each task: reads its slice from the manifest,
emits delimiter-separated lines to /tmp/wf_<name>_<uuid>/out_<i>.csv, prints a
status word, stops. Do NOT depend on the summary field for content.
- Synthesize on the parent. Read the out_*.csv files yourself — verify the
file count and freshness (each was written this run) so a stale or missing
output from an interrupted child isn't silently read as success — then merge
and present. The cross-cutting "whole picture" step stays on the parent — only
the per-unit work fanned out.
The novel mechanic worth building: adversarial convergence
This is the part Hermes did NOT already have and the real reason to bother.
Claude Code's quality claim ("independent agents try to refute each other's
findings; only surviving claims surface; iterate until they converge") maps
cleanly onto delegate_task batch mode:
Recipe: N independent attempts + M refuters
For a finding-quality task (security audit, "is this code path actually
vulnerable?", "does this migration preserve behavior?", a high-stakes plan):
- Independent attempts (round 1). Fan out the SAME question to N children
(N=2-4) with DIFFERENT framings/angles in each
context, so they don't
collapse to the same reasoning. Each writes its claims to
/tmp/wf_<name>/attempt_<i>.md as a list of discrete, individually-checkable
claims (one claim per line — atomicity is what makes refutation possible).
- Collect + dedupe (parent or Layer A). Merge all claims into a single
numbered list. Identical claims from independent attempts = higher prior;
note the agreement count per claim.
- Refutation round (round 2). Fan out a refuter batch: each refuter gets the
claim list and is told "your job is to BREAK these claims — for each, find the
counter-evidence (the auth check that DOES exist, the test that DOES cover it,
the edge case the claim ignores). Output
claim_idx|survives|counter_evidence."
Give refuters the codebase/sources, not the original attempts' reasoning.
- Keep only survivors. A claim surfaces to the user only if it survived
refutation (no refuter produced valid counter-evidence). Filtered claims are
dropped, with a one-line note of why if the user asked for completeness.
- Converge (optional). If round 2 surfaced NEW claims (refuters often find
adjacent issues), feed them back through one more refutation round. Stop when
a round produces no new surviving claims — that's convergence. Cap at 3 rounds
to bound cost.
This gives you the "more trustworthy than a single pass" property without a
runtime — it's just two delegate_task batches and a merge, structured so
disagreement is visible and unsupported claims die before they reach the user.
Why atomic claims matter
A refuter cannot break "the auth layer has problems." It CAN break "endpoint
POST /api/users/:id/role in src/routes/users.ts:142 has no role check." Force
attempts to emit specific, located, individually-falsifiable claims or the
refutation round is theater.
Cost discipline (this is the thing that bites)
A workflow can consume dramatically more tokens than a normal turn — that is
inherent, not a bug. Two real multipliers:
- Each Layer-B child is a full agent tree. 20 children ≈ 20× the model calls.
delegation.max_concurrent_children only bounds concurrency, not total.
- Hermes aux/subagent model defaults to main-model-first. Children inherit
the parent's (often expensive reasoning) model.
delegate_task does NOT expose
a per-task model or profile field — its per-task keys are
{goal, context, toolsets, role}. To run the fan-out cheaper you either route
delegation globally via delegation config (model/provider applied to all
children), or — for genuinely model/profile-scoped work — use cron, the kanban
swarm, or a separate Hermes process. The cleanest lever for mechanical fan-out
is still Layer A: do the deterministic part in a script with no per-item LLM at
all.
Always: start on a SCOPED slice (one directory, 20 records, 10 endpoints), prove
the recipe end-to-end, report the token cost, THEN offer to run it at full scale.
Never silently fan out hundreds of children — surface the cost first and let the
user say go.
Pitfalls
- Writing
delegate_task inside an execute_code script. It's not in
SANDBOX_ALLOWED_TOOLS; the import/stub won't exist. Layer A is deterministic
tools only. Fan out LLM judgment from the parent turn, not from inside a script.
- Promising background/resumable from
delegate_task. It's synchronous and
turn-scoped. Durable = kanban swarm.
- Trusting
summary fields for content. Route structured output to files
(Pattern 2 in delegate-task-output-patterns).
- Non-atomic claims in the verify recipe. Unfalsifiable claims survive
refutation by default and pollute the output. Force located, specific claims.
- Same framing in all "independent" attempts. They collapse to one answer and
the cross-check is worthless. Vary the angle in each child's context.
- Fanning out a serial task. If unit B needs unit A's output, parallelism
produces wrong/empty results. Re-check independence before fanning out.
Verification before you call it done
- Did the deterministic pre-pass actually run, and does the manifest line-count
match the expected unit count? (
wc -l /tmp/wf_<name>/manifest.jsonl)
- Did every fan-out child write its output file? (
ls /tmp/wf_<name>/out_*.csv) —
remember stalled children often completed anyway (Pattern 6).
- For the verify recipe: can you point to the refuter counter-evidence for every
DROPPED claim, and confirm every SURFACED claim went through refutation?
- Did you report token cost on the scoped run before offering full scale?
1---2name: dynamic-workflow3description: Orchestrate large fan-out work as a plan-in-code "workflow" so the agent's context holds only the final verified answer, not the exhaust of hundreds of intermediate steps. Use for codebase-wide sweeps, large migrations, multi-angle research, and any task too big for one context window where the split strategy is known enough to script. Includes the adversarial-convergence verification recipe (independent attempts + refuters, keep only surviving claims).4license: MIT5---6
7# Dynamic Workflow — plan-in-code fan-out with verification
8
9This is Hermes's answer to Claude Code's "dynamic workflows" (run hundreds of
10parallel subagents in one session). The mechanic worth copying is NOT "more
11subagents" — it is **moving the plan, the loop, and the intermediate results
12OUT of the context window and INTO a script.** Normally the agent IS the
13orchestrator: every intermediate result piles into context, which is exactly
14what caps you at a handful of agents. A workflow keeps only the *final verified
15answer* in context; the script holds everything else.
16
17> This skill is self-contained, but it builds on standard fan-out hygiene —
18> chunk inputs to ~50-70KB per child, route structured output to files (not the
19> `summary` field, which truncates under load), use delimiter-separated lines
20> over JSON wrappers, and remember that a "stalled" child often completed its
21> write anyway (check the filesystem before retrying). If your install has a
22> `delegate-task-output-patterns` skill, load it for the detailed thresholds;
23> the rules above are the load-bearing subset.
24
25## The two orchestration-script layers (pick the right one — they are NOT interchangeable)
26
27Hermes has no JS runtime. The "orchestration script" is one of two layers, and
28the split is enforced by a real capability boundary, not a style preference:
29
30| | Layer A: `execute_code` (Python script) | Layer B: `delegate_task` batch |
31|---|---|---|
32| Use for | DETERMINISTIC fan-out — fetch N URLs, parse N files, run N shell commands, template N outputs | LLM-JUDGMENT fan-out — classify, review, decide, write, refute, audit per item |
33| The script holds | the loop + branching + intermediate vars (real Python) | n/a — you call it once with a `tasks=[...]` array; each task is its own isolated agent |
34| Tools available inside | `web_search, web_extract, read_file, write_file, search_files, terminal, patch` ONLY (the `SANDBOX_ALLOWED_TOOLS` set) | configured child toolsets, subject to delegate restrictions (leaf children are stripped of `delegate_task`, `clarify`, `memory`, `send_message`, `execute_code` — see `DELEGATE_BLOCKED_TOOLS`) |
35| Can it call `delegate_task`? | **NO.** `delegate_task` is NOT in `SANDBOX_ALLOWED_TOOLS`. Do not write a script that imports it — it will fail. | itself, if `role='orchestrator'` and `max_spawn_depth>=2` |
36| Concurrency | you control it in Python (`ThreadPoolExecutor`, batches) | `delegation.max_concurrent_children` (default 3; raise in config.yaml) |
37| Cost shape | cheap — most steps are tool calls, no per-item LLM unless you call `web_search`/aux | one model call tree PER child task — multiplies linearly, can be very expensive |
38
39**Rule of thumb:** do the deterministic part in Layer A first (inline, in a
40script), then fan out ONLY the irreducibly-LLM step via Layer B. This is
41Pattern 1 from `delegate-task-output-patterns`, applied at workflow scale.
42Mixing them: a Layer-A script can write a manifest file, and you (the parent)
43then read that manifest and issue a single Layer-B `delegate_task` batch.
44
45## The synchronous trap (READ THIS — it is the #1 way a "workflow" disappoints)
46
47`delegate_task` runs **synchronously inside the parent turn**. If the user sends
48a new message, hits /stop, or /new, every in-flight child is **cancelled and its
49work discarded** (status `interrupted`). It does NOT run in the background, and
50it does NOT survive the turn. There is no cache-resume of a half-finished fan-out.
51
52So a "workflow" in Hermes is one of:
53
541. **Foreground workflow (default):** Layer A and/or one Layer-B batch, completed
55 within a single turn. Good for minutes-long fan-out (dozens of units). The
56 user waits. This is what you build 90% of the time.
572. **Durable workflow (hours/days, survives interruption):** use the **kanban
58 swarm** (the SQLite-backed multi-agent kernel that ships with Hermes —
59 `hermes_cli/kanban_swarm.py` + the kanban plugin; if your install has a
60 `kanban-multiagent` skill, load it for the workflow). It
61 writes a task graph (root → parallel workers → verifier → synthesizer) into
62 the SQLite kanban kernel with a JSON blackboard. State persists across turns
63 and restarts. This is the ONLY path that matches Claude Code's "runs into
64 hours and days, resumes where it left off." Reach for it when the foreground
65 path would time out or when the user must be able to walk away.
66
67Never promise "background, resumable, hundreds of agents over days" from a plain
68`delegate_task` call. For a durable multi-agent workflow *graph*, the kanban
69swarm is the right fit. For simpler durable/out-of-turn cases there are lighter
70options too: a `cronjob` one-shot or scheduled job, or a managed
71`terminal(background=True, notify_on_complete=True)` process — both survive the
72turn without standing up a full task graph.
73
74## Workflow recipe (foreground)
75
761. **Decompose into independent units.** What is the unit — a file? an endpoint?
77 a source? a record? Each unit must be answerable WITHOUT the others' output
78 (else it's serial, not fan-out — see when_not_to_use).
792. **Deterministic pre-pass (Layer A).** In one `execute_code` script, gather the
80 manifest: list the files, extract the candidate sites, fetch the raw sources,
81 compute anything regex/parse can compute. Write a manifest to a **unique
82 per-run** directory — `/tmp/wf_<name>_<uuid>/manifest.jsonl` (one unit per
83 line), never a bare `/tmp/wf_<name>/` that a prior interrupted run could have
84 left stale outputs in. This is the "plan in code." Print the unit count and
85 the run dir, and stop.
863. **Size the fan-out** against `delegate-task-output-patterns`: chunk so each
87 child handles ~8-12 mechanical file edits OR ~2000-3000 lines of reading OR
88 ~50-70KB of corpus. Look at the LARGEST unit, not the average. One
89 `delegate_task(tasks=[...])` call is bounded by
90 `delegation.max_concurrent_children` (default 3) — it does NOT queue hundreds
91 of tasks internally. For larger fan-out, issue bounded waves yourself (loop:
92 one batch, collect, next batch) or have the user raise the config
93 intentionally.
944. **LLM-judgment fan-out (Layer B).** Issue ONE `delegate_task` with a `tasks=[]`
95 array, one task per chunk. Each task: reads its slice from the manifest,
96 emits delimiter-separated lines to `/tmp/wf_<name>_<uuid>/out_<i>.csv`, prints a
97 status word, stops. Do NOT depend on the `summary` field for content.
985. **Synthesize on the parent.** Read the out_*.csv files yourself — verify the
99 file count and freshness (each was written this run) so a stale or missing
100 output from an interrupted child isn't silently read as success — then merge
101 and present. The cross-cutting "whole picture" step stays on the parent — only
102 the per-unit work fanned out.
103
104## The novel mechanic worth building: adversarial convergence
105
106This is the part Hermes did NOT already have and the real reason to bother.
107Claude Code's quality claim ("independent agents try to refute each other's
108findings; only surviving claims surface; iterate until they converge") maps
109cleanly onto `delegate_task` batch mode:
110
111### Recipe: N independent attempts + M refuters
112
113For a finding-quality task (security audit, "is this code path actually
114vulnerable?", "does this migration preserve behavior?", a high-stakes plan):
115
1161. **Independent attempts (round 1).** Fan out the SAME question to N children
117 (N=2-4) with DIFFERENT framings/angles in each `context`, so they don't
118 collapse to the same reasoning. Each writes its claims to
119 `/tmp/wf_<name>/attempt_<i>.md` as a list of discrete, individually-checkable
120 claims (one claim per line — atomicity is what makes refutation possible).
1212. **Collect + dedupe (parent or Layer A).** Merge all claims into a single
122 numbered list. Identical claims from independent attempts = higher prior;
123 note the agreement count per claim.
1243. **Refutation round (round 2).** Fan out a refuter batch: each refuter gets the
125 claim list and is told "your job is to BREAK these claims — for each, find the
126 counter-evidence (the auth check that DOES exist, the test that DOES cover it,
127 the edge case the claim ignores). Output `claim_idx|survives|counter_evidence`."
128 Give refuters the codebase/sources, not the original attempts' reasoning.
1294. **Keep only survivors.** A claim surfaces to the user only if it survived
130 refutation (no refuter produced valid counter-evidence). Filtered claims are
131 dropped, with a one-line note of why if the user asked for completeness.
1325. **Converge (optional).** If round 2 surfaced NEW claims (refuters often find
133 adjacent issues), feed them back through one more refutation round. Stop when
134 a round produces no new surviving claims — that's convergence. Cap at 3 rounds
135 to bound cost.
136
137This gives you the "more trustworthy than a single pass" property without a
138runtime — it's just two `delegate_task` batches and a merge, structured so
139disagreement is visible and unsupported claims die before they reach the user.
140
141### Why atomic claims matter
142A refuter cannot break "the auth layer has problems." It CAN break "endpoint
143`POST /api/users/:id/role` in src/routes/users.ts:142 has no role check." Force
144attempts to emit specific, located, individually-falsifiable claims or the
145refutation round is theater.
146
147## Cost discipline (this is the thing that bites)
148
149A workflow can consume dramatically more tokens than a normal turn — that is
150inherent, not a bug. Two real multipliers:
151
152- **Each Layer-B child is a full agent tree.** 20 children ≈ 20× the model calls.
153 `delegation.max_concurrent_children` only bounds *concurrency*, not *total*.
154- **Hermes aux/subagent model defaults to main-model-first.** Children inherit
155 the parent's (often expensive reasoning) model. `delegate_task` does NOT expose
156 a per-task `model` or `profile` field — its per-task keys are
157 `{goal, context, toolsets, role}`. To run the fan-out cheaper you either route
158 delegation globally via `delegation` config (model/provider applied to all
159 children), or — for genuinely model/profile-scoped work — use cron, the kanban
160 swarm, or a separate Hermes process. The cleanest lever for mechanical fan-out
161 is still Layer A: do the deterministic part in a script with no per-item LLM at
162 all.
163
164Always: start on a SCOPED slice (one directory, 20 records, 10 endpoints), prove
165the recipe end-to-end, report the token cost, THEN offer to run it at full scale.
166Never silently fan out hundreds of children — surface the cost first and let the
167user say go.
168
169## Pitfalls
170
171- **Writing `delegate_task` inside an `execute_code` script.** It's not in
172 `SANDBOX_ALLOWED_TOOLS`; the import/stub won't exist. Layer A is deterministic
173 tools only. Fan out LLM judgment from the parent turn, not from inside a script.
174- **Promising background/resumable from `delegate_task`.** It's synchronous and
175 turn-scoped. Durable = kanban swarm.
176- **Trusting `summary` fields for content.** Route structured output to files
177 (Pattern 2 in delegate-task-output-patterns).
178- **Non-atomic claims in the verify recipe.** Unfalsifiable claims survive
179 refutation by default and pollute the output. Force located, specific claims.
180- **Same framing in all "independent" attempts.** They collapse to one answer and
181 the cross-check is worthless. Vary the angle in each child's context.
182- **Fanning out a serial task.** If unit B needs unit A's output, parallelism
183 produces wrong/empty results. Re-check independence before fanning out.
184
185## Verification before you call it done
186
187- Did the deterministic pre-pass actually run, and does the manifest line-count
188 match the expected unit count? (`wc -l /tmp/wf_<name>/manifest.jsonl`)
189- Did every fan-out child write its output file? (`ls /tmp/wf_<name>/out_*.csv`) —
190 remember stalled children often completed anyway (Pattern 6).
191- For the verify recipe: can you point to the refuter counter-evidence for every
192 DROPPED claim, and confirm every SURFACED claim went through refutation?
193- Did you report token cost on the scoped run before offering full scale?