workflow-authoring
This skill provides a reference for authoring workflow scripts that orchestrate work across multiple subagents.
Identity proven by GitHub's verified organisation badge (letta-ai)
- Agents
- Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, claude.ai and API
- License
- Apache-2.0 (permissive)
- Pin
- c1033d9pinned Oct 9, 2026
- Context cost
- about 3,600 tokenswhen the skill loads
- GitHub stars
- 3,549on letta-ai/letta-code
- Scripts
- None found
What it does
“Reference for writing a Workflow tool script (script API and gotchas, pipeline-vs-barrier rules, quality patterns, worked examples). Load before authoring a script for a workflow the user already opted into; it does not itself authorize running one.”
Generated summary from the skill's own text
This skill provides a reference for authoring workflow scripts that orchestrate work across multiple subagents. It documents the script API (agent, pipeline, parallel, phase, log, decide functions), patterns for structuring complex tasks like reviews and migrations, quality techniques such as adversarial verification and judge panels, and debugging via execution journals.
When to use it
- Authoring a workflow script to decompose work across subagents
- Structuring parallel discovery, verification, or synthesis tasks
- Building multi-stage pipelines with independent processing per item
- Implementing quality patterns like adversarial review or diverse perspectives
When not to
- Running a workflow the user has not already opted into
- Single-agent tasks that do not require parallel orchestration
- Writing tool code outside of workflow context
Install
These commands install from the publisher's repository and need no account.
skills CLI
npx skills add letta-ai/letta-code --skill workflow-authoringInstalls from the repository's default branch, which may have changed since the commit we checked.
GitHub CLI 2.90 or later
gh skill install letta-ai/letta-code workflow-authoring@c1033d9f7e8863a869cd2031805cfd2c66c165b9Installs the commit we checked (c1033d9).
Copy by hand
Download the folder at c1033d9 and put it in .claude/skills/workflow-authoring for Claude Code and in .agents/skills/workflow-authoring for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.
Copy for your agent
A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.
Prompt
Install the agent skill "workflow-authoring" by Letta. Source, pinned to commit c1033d9f7e8863a869cd2031805cfd2c66c165b9: https://github.com/letta-ai/letta-code/tree/c1033d9f7e8863a869cd2031805cfd2c66c165b9/src/skills/builtin/workflow-authoring 1. Download that folder at exactly this commit. Do not use the default branch. 2. Copy every file unchanged, keeping the folder structure, into .claude/skills/workflow-authoring/ for Claude Code and .agents/skills/workflow-authoring/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist. 3. Do not edit the files, and do not run any script in the folder unless I ask you to. 4. When you are done, list the files you copied and where you put them. Listing and checks: https://www.agentnative.dev/skills/letta/workflow-authoring
SKILL.md at the pin
---
name: workflow-authoring
description: Reference for writing a Workflow tool script (script API and gotchas, pipeline-vs-barrier rules, quality patterns, worked examples). Load before authoring a script for a workflow the user already opted into; it does not itself authorize running one.
---
# Workflow authoring reference
A workflow structures work across many subagents — to be comprehensive
(decompose and cover in parallel), to be confident (independent perspectives
and adversarial checks before committing), or to take on scale one context
can't hold (migrations, audits, broad sweeps). The script is where you encode
that structure: what fans out, what verifies, what synthesizes.
When you do call the Workflow tool, the right move is often **hybrid**: scout
inline first (list the files, scope the diff, find the modules) to discover
the work-list, then call Workflow to pipeline over it, passing the list via
`args`. You don't need to know the shape before the *task* — only before the
*orchestration step*.
Common single-phase workflows you can chain across turns:
- **Understand** — parallel readers over relevant subsystems → structured map
- **Design** — judge panel of N independent approaches → scored synthesis
- **Review** — dimensions → find → adversarially verify
- **Research** — multi-modal sweep → deep-read → synthesize
- **Migrate** — discover sites → transform each → verify
For larger work, run several in sequence — read each result before deciding
the next phase. You stay in the loop; each workflow is one well-scoped
fan-out.
Every script must begin with `export const meta = {...}`:
export const meta = {
name: 'find-flaky-tests', // kebab-case, required
description: 'Find flaky tests and propose fixes', // required
phases: [ // one entry per phase() call
{ title: 'Scan', detail: 'grep test logs for retries' },
{ title: 'Fix', detail: 'one agent per flaky test' },
],
}
// script body starts here — use agent()/parallel()/pipeline()/phase()/log()
The `meta` object must be a PURE LITERAL — no variables, function calls,
spreads, or template interpolation.
## What a subagent is
Every `agent()` call is one agent-free ephemeral conversation linked to the
invoking parent agent (`is_subagent: true`, named after the call's `label`).
It starts with nothing but its prompt: no memory, no conversation context, no
skills. Put ALL context a stage needs in the prompt — file paths, the rule it
should apply, what shape to return.
Subagents are told their final text IS the return value (not a human-facing
message), so they return raw data. Workers inherit the invoking session's tools
and permission mode; use `allowedTools` to restrict a workflow or stage. For
stages whose input is entirely in the prompt (synthesis, judging, scoring)
pass `allowedTools: []` — a model that can still read files tends to wander,
and a subagent that re-issues an identical tool call three times is stopped
and rejects with the failure detail.
Their model defaults to the invoking conversation's model. `opts.model` (or
the tool's `model` input) accepts any handle or alias listed by
`letta model list`; an unknown value rejects that call. Use a
cheaper model for mechanical stages only when you know a valid handle.
Use the invoking backend for workflow workers. Local execution requires an Agent
SDK and App Server with local conversation support; `decide()` still uses the
Cloud decisions service.
## Script body hooks
- `agent(prompt, opts?)` → Promise. Spawn one subagent. Resolves to its final
text, or with `schema` (JSON Schema) to a validated object. Prefer `schema`
for shaped results: invalid or missing output is retried, then rejects
with validation detail in the error and journal. `json: true` still parses
without validating; `schema` wins if both are set. Await each call; failures
reject with the cause, `callIndex`, and `conversationId` when available.
Catch errors explicitly to recover. Options: `label` (display name),
`phase` (progress group — use this inside concurrent stages), `schema`,
`json`, `model`, `effort` (`'low'` for mechanical stages, higher for the
hardest verify/judge stages), `allowedTools`, `systemPrompt` (extra system
prompt for this subagent), `timeoutMs` (default 10 minutes), `maxToolCalls`
(positive safe integer; default 1000 unique tool calls for this subagent),
`conversationId` (resume a worker — see "Diagnosing a run").
- `pipeline(items, stage1, stage2, ...)` → run each item through all stages
independently, NO barrier between stages. Item A can be in stage 3 while
item B is still in stage 1. This is the DEFAULT for multi-stage work.
Wall-clock = slowest single-item chain, not sum-of-slowest-per-stage. Every
stage callback receives `(prevResult, originalItem, index)` — use
originalItem/index in later stages to label work without threading context
through stage 1's return value. A stage that throws skips that item's
remaining stages; siblings finish, then the helper rejects.
- `parallel(thunks)` → run zero-arg functions concurrently. This is a
BARRIER: it awaits all thunks, then rejects if any threw. For best-effort
processing, catch errors inside each thunk and return explicit success/error
results. Use ONLY when you genuinely need all results together.
- `phase(title)` — start a new phase; subsequent agent() calls are grouped
under this title in progress output.
- `log(message)` — emit a progress message to the user.
- `decide(state, questions, opts?)` → Promise; not a subagent call. Ask a
calibrated Jev model (chosen internally — no model option) questions about
`state`. `questions` is a non-empty OBJECT keyed by question id — never an
array. Each question needs `instructions` and a `type`: `choice` (criteria
map of id → description, ≤255), `score` (criteria array of strings, unlike
`questions`), `noul` (no criteria). Returns a response whose `answers` are
keyed by the same ids and calibrated, or `null` after one retried invalid
answer — guard `if (!call)`; API errors throw.
const call = await decide(evidence, {
behavior: { type: 'choice', instructions: 'Is this behavior a bug?',
criteria: { bad: 'Wrong or harmful', not_bad: 'Expected or harmless' } },
})
const verdict = call?.answers?.behavior?.choice // 'bad' | 'not_bad'
- `tools.mcp__server__tool(args?)` → Promise; not a subagent call. Calls one
of the invoking agent's MCP tools directly (find names with
`letta mcp search` / `letta mcp tools` before writing the script). Each call
goes through the same permission rules as a normal MCP tool call; a tool
that would ask for approval throws instead, so the user must allow it first.
Resolves to the tool's `structuredContent`, else its text output (parsed
when it is JSON), else the raw `content` array. Throws when the tool is
unavailable, not allowed, or reports an error; `parallel()` and `pipeline()`
propagate that rejection. Use it for deterministic fetches and
writes whose arguments the script already knows; use `agent()` when a step
needs judgment.
const issues = await tools.mcp__linear__list_issues({ team: 'LET', limit: 20 })
- `args` — the value passed as the tool's `args` input, verbatim. Pass
arrays/objects as actual JSON values, NOT as a JSON-encoded string.
`decide()` sees only the `state` you pass; it cannot read files. When compact,
bounded items are already prepared, pass them via `args` and call `decide()`
on each directly — don't spawn `agent()` readers just to relay inputs. Raw
large traces don't belong in `args`: prepare bounded state that preserves the
user instruction, observed action, and outcome, and mark what was omitted.
Scripts are plain JavaScript, NOT TypeScript — type annotations, interfaces,
and generics fail to parse. The script body runs in an async context — use
`await` directly and `return` the final result. Standard JS built-ins (JSON,
Math, Array, etc.) are available; the hooks are the only globals provided.
The script runs inside the CLI process with the CLI's own privileges (the
`vm` context is a scope, not a security boundary), and the user approves it
by reading it. Keep the script to orchestration: decide what runs and combine
results. Direct MCP calls belong in `tools.mcp__*()`; other reading, searching, and
writing belongs in subagents, where the tool allowlist applies.
## Pipeline vs barrier
DEFAULT TO pipeline(). Only reach for a barrier (parallel between stages)
when stage N needs cross-item context from all of stage N−1:
- Dedup/merge across the full result set before expensive downstream work
- Early-exit if the total count is zero ("0 bugs found → skip verification")
- Stage N's prompt references "the other findings" for comparison
A barrier is NOT justified by:
- "I need to flatten/map/filter first" — do it inside a pipeline stage:
`pipeline(items, stageA, r => transform([r]).flat(), stageB)`
- "The stages are conceptually separate" — that's what pipeline() models.
Separate stages ≠ synchronized stages.
- "It's cleaner code" — barrier latency is real. If 5 finders run and the
slowest takes 3× the fastest, a barrier wastes 2/3 of the fast finders'
idle time.
`agent()` and `decide()` share one pool of concurrent slots per run
(`maxConcurrent`, default 16) — excess calls queue and run as slots free up,
so passing 100 items is fine. Each has its own 1000-call lifetime backstop.
A single parallel()/pipeline() call accepts at most 4096 items.
When a barrier IS correct — dedup across all findings before expensive
verification:
const all = await parallel(DIMENSIONS.map(d => () => agent(d.prompt, {json: true})))
const deduped = dedupeByFileAndLine(all.filter(Boolean).flatMap(r => r.findings ?? [])) // needs ALL at once
const verified = await parallel(deduped.map(f => () => agent(verifyPrompt(f), {json: true})))
Loop-until-count pattern — accumulate to a target:
const bugs = []
let rounds = 0
while (bugs.length < 10 && rounds++ < 5) {
const result = await agent('Find bugs in this codebase. Reply {"bugs": [{file, line, summary}]}', {json: true})
bugs.push(...(result?.bugs ?? []))
log(`${bugs.length}/10 found`)
}
Always bound a loop with a round counter as well as the target: a subagent
that keeps returning nothing would otherwise run to the 1000-agent cap.
Composing patterns — exhaustive review (find → dedup vs seen → diverse-lens
panel → loop-until-dry):
const seen = new Set(), confirmed = []
let dry = 0
while (dry < 2) { // loop-until-dry
const found = (await parallel(FINDERS.map(f => () => // barrier: collect all finders this round
agent(f.prompt, {phase: 'Find', json: true})))).filter(Boolean).flatMap(r => r.bugs ?? [])
const fresh = found.filter(b => !seen.has(key(b))) // dedup vs ALL seen — plain code, not an agent
if (!fresh.length) { dry++; continue }
dry = 0; fresh.forEach(b => seen.add(key(b)))
const judged = await parallel(fresh.map(b => () => // every fresh bug judged concurrently...
parallel(['correctness','security','repro'].map(lens => () => // ...each by 3 distinct lenses
agent(`Judge "${b.desc}" via the ${lens} lens — real? Reply {"real": boolean}`, {phase: 'Verify', json: true, allowedTools: []})))
.then(vs => ({ b, real: vs.filter(Boolean).filter(v => v.real).length >= 2 }))))
confirmed.push(...judged.filter(v => v.real).map(v => v.b))
}
return confirmed
// dedup vs `seen`, NOT `confirmed` — else judge-rejected findings reappear every round and it never converges.
## Quality patterns — pick by task and compose freely
- **Adversarial verify**: spawn N independent skeptics per finding, each
prompted to REFUTE. Kill if ≥majority refute. Prevents plausible-but-wrong
findings from surviving.
const votes = await parallel(Array.from({length: 3}, () => () =>
agent(`Try to refute: ${claim}. Default to refuted=true if uncertain. Reply {"refuted": boolean}`, {json: true})))
const survives = votes.filter(Boolean).filter(v => !v.refuted).length >= 2
- **Perspective-diverse verify**: when a finding can fail in more than one
way, give each verifier a distinct lens (correctness, security, perf,
does-it-reproduce) instead of N identical refuters — diversity catches
failure modes redundancy can't.
- **Judge panel**: generate N independent attempts from different angles
(e.g. MVP-first, risk-first, user-first), score with parallel judges,
synthesize from the winner while grafting the best ideas from runners-up.
Beats one-attempt-iterated when the solution space is wide.
- **Loop-until-dry**: for unknown-size discovery (bugs, issues, edge cases),
keep spawning finders until K consecutive rounds return nothing new.
Simple counters (while count < N) miss the tail.
- **Multi-modal sweep**: parallel agents each searching a different way
(by-container, by-content, by-entity, by-time). Each is blind to what the
others surface; useful when one search angle won't find everything.
- **Completeness critic**: a final agent that asks "what's missing —
modality not run, claim unverified, source unread?" What it finds becomes
the next round of work.
- **No silent caps**: if a workflow bounds coverage (top-N, no-retry,
sampling), `log()` what was dropped — silent truncation reads as "covered
everything" when it didn't.
Scale to what the user asked for. "find any bugs" → a few finders,
single-vote verify. "thoroughly audit this" or "be comprehensive" → larger
finder pool, 3–5 vote adversarial pass, synthesis stage. When unsure, lean
toward thoroughness for research/review/audit requests and toward brevity for
quick checks.
## Diagnosing a run
Every run persists its script, args, and a `journal.jsonl` with one line per
completed subagent call (prompt, outcome, conversation id) under
`~/.letta/workflows/executions/<id>/`; the tool result names the paths. Before
diagnosing why a workflow returned an empty or unexpected result, read that
journal — it records each agent's actual return value and failure detail.
A failed run is not resumable: fix the script and launch it again.
The script is never replayed, but one worker can continue:
`agent(prompt, {conversationId})` re-prompts it with history and model intact,
using a journal ID and only once its last Run is terminal. Local workers can
only continue inside the same workflow execution that observed their completed
turn. Tools inherit the invoking session unless `allowedTools` is supplied;
`schema` and `effort` are chosen per turn. Needs
SDK 0.8.20+.
From letta-ai/letta-code at c1033d9, shown as plain text under its license: Apache-2.0 (permissive). The license text is in LICENSE.
View SKILL.md on GitHub at c1033d9Files
- SKILL.md15 KB · fb368dbaebf2
1 file, 15 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.
Skill folder on GitHub at c1033d9More from Letta
acquiring-skills
Enables the agent to discover and install skills from multiple registries including Hermes Skills Hub, ClawHub, GitHub, and community repositories.
Agents not labelled yet · about 3.1K tokens · pinned 6 days ago
browser-use
Control a real browser using Chrome DevTools Protocol over WebSocket to navigate pages, interact with UI, fill forms, scrape sites, and capture screenshots or…
Claude Code, Codex, Cursor +4 · about 3.2K tokens · pinned 6 days ago
Context Doctor
Context Doctor investigates and repairs an agent's context including memory, system prompt, and skills by analyzing observed behavior as evidence.
Claude Code, Codex, Cursor +3 · about 1K tokens · pinned 6 days ago
creating-mods
This skill creates and edits mod files that extend local Letta instances with tools, slash commands, event handlers, custom model providers, permissions, and…
Claude Code, Codex, Cursor +4 · about 2.4K tokens · pinned 6 days ago
creating-skills
This skill guides developers in creating modular packages that extend Letta Code agent capabilities with specialized knowledge, workflows, tool integrations…
Contains scripts
Claude Code, Codex, Cursor +3 · about 4.5K tokens · pinned 6 days ago
curating-memory-palace
This skill lets the agent maintain a Memory Palace, a structured dashboard view that shows the user what the agent is working on, what it could do next, and…
Agents not labelled yet · about 3.1K tokens · pinned 6 days ago
Related in AI and agents
agent-builder
Agent Builder orchestrates the creation, modification, testing, and publishing of n8n Agents.
by n8nOfficial publisher
Claude Code, Codex, Cursor +3 · about 2.3K tokens · pinned 6 days ago
intent-recognition
Classifies automation requests by determining which primitive owns the top-level control flow (workflow, agent, needs clarification, or out-of-scope) and…
by n8nOfficial publisher
Claude Code, Codex, Cursor +4 · about 6.9K tokens · pinned 6 days ago
planned-task-runtime
This skill manages follow-up interactions with a background task execution system, handling task completion synthesis, failure replanning, workflow building…
by n8nOfficial publisher
Claude Code, Codex, Cursor +3 · about 2.2K tokens · pinned 6 days ago
skill-creator
This skill guides agents through creating new skills that extend Gemini CLI with specialized knowledge, workflows, and tool integrations.
by GoogleOfficial publisher
Contains scripts
Gemini CLI · about 5K tokens · pinned 6 days ago
-21risk-automation
This skill automates 21risk operations by connecting to the 21risk toolkit through Composio's Rube MCP server.
by ComposioOfficial publisher
Claude Code, Codex, Cursor +3 · about 690 tokens · pinned 6 days ago
-2chat-automation
This skill automates 2chat tasks by integrating with Composio's 2chat toolkit through Rube MCP.
by ComposioOfficial publisher
Agents not labelled yet · about 680 tokens · pinned 6 days ago
Open roles at Letta
4 open roles on the jobs board
- Research Engineer / Scientist, Post-Training
San Francisco, California, United States (On-site)
- Research Engineer / Scientist, Self-Improvement
San Francisco, California, United States (On-site)
- Software Engineer, Agent Harness
San Francisco, California, United States (On-site)
- Research Engineer / Scientist, Memory
San Francisco, California, United States (On-site)