From a5799a60a0a0d096fe82fe14a538c2fcf7932b9a Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 09:19:43 +0200 Subject: [PATCH 01/12] docs: restructure nav; promote Workflows and introduce Primitives section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nav changes: - 'Basics' becomes 'Primitives' and groups related pages semantically: * Message (expandable): Channels, DMs, Threads, Emoji reactions * File (was 'File sharing') * Auth (expandable): Permissions * Schedule (was 'Scheduling') - 'Spawning an agent' + 'Event handlers' move up into Getting Started - Workflows promoted from a single bullet under Advanced to a top-level section with 7 pages: Introduction, Quickstart, Builder API, Patterns, Setup helpers, Common mistakes, Run from CLI - CLI's 'Run workflows' folds into the Workflows section - Advanced now only contains Cloud + Workforce Nav renderer: - NavItem gains optional children: NavItem[] for nested sub-items - DocsNav.tsx renders children as an indented sub-list with left border - getAllDocSlugs() walks the tree for static-generation coverage New pages (stubbed with real content, not placeholders): - workflows-introduction.mdx: landing, mental model, when to use - workflows-quickstart.mdx: end-to-end 5-min example - workflows-patterns.mdx: 80-to-100, lead+workers, multi-file edit, cross-repo, supervisor, parallelism/waves - workflows-setup-helpers.mdx: per-repo setup helper convention + applySiblingLinks (landing in relay SDK #776) - workflows-common-mistakes.mdx: the table every workflow author needs once, ported from the writing-agent-relay-workflows skill Content porting is from the two private Claude-Code skills (writing-agent-relay-workflows and relay-80-100-workflow) so this content is now public + addressable by URL. DRAFT PR. Open questions for review: - Should 'Permissions' be under Auth or its own primitive? - 'Sending messages' acts as the Message landing — separate page or umbrella? - Should there be an 'Agent' primitive (for Spawning + Event handlers) rather than putting them in Getting Started? --- web/components/docs/DocsNav.tsx | 39 +-- web/components/docs/docs.module.css | 1 + .../docs/workflows-common-mistakes.mdx | 114 ++++++++ web/content/docs/workflows-introduction.mdx | 104 +++++++ web/content/docs/workflows-patterns.mdx | 272 ++++++++++++++++++ web/content/docs/workflows-quickstart.mdx | 147 ++++++++++ web/content/docs/workflows-setup-helpers.mdx | 168 +++++++++++ web/lib/docs-nav.ts | 65 +++-- 8 files changed, 877 insertions(+), 33 deletions(-) create mode 100644 web/content/docs/workflows-common-mistakes.mdx create mode 100644 web/content/docs/workflows-introduction.mdx create mode 100644 web/content/docs/workflows-patterns.mdx create mode 100644 web/content/docs/workflows-quickstart.mdx create mode 100644 web/content/docs/workflows-setup-helpers.mdx diff --git a/web/components/docs/DocsNav.tsx b/web/components/docs/DocsNav.tsx index fba44e6ee..c9797a0f4 100644 --- a/web/components/docs/DocsNav.tsx +++ b/web/components/docs/DocsNav.tsx @@ -1,6 +1,6 @@ 'use client'; -import type { ComponentType } from 'react'; +import type { ComponentType, ReactElement } from 'react'; import { useEffect, useRef } from 'react'; import { usePathname } from 'next/navigation'; import { @@ -30,7 +30,7 @@ import { PiBroadcastFill, PiLockKeyDuotone } from 'react-icons/pi'; import { RiLayout5Line } from 'react-icons/ri'; import { SiClaude, SiPython, SiTypescript } from 'react-icons/si'; -import { docsNav } from '../../lib/docs-nav'; +import { docsNav, type NavItem } from '../../lib/docs-nav'; import styles from './docs.module.css'; type NavIcon = ComponentType<{ className?: string; 'aria-hidden'?: boolean | 'true' | 'false' }>; @@ -124,23 +124,28 @@ export function DocsNav({ variant = 'sidebar' }: { variant?: 'sidebar' | 'mobile {docsNav.map((group) => (

{group.title}

- +
))} ); } + +function renderNavItem(item: NavItem, pathname: string): ReactElement { + const href = `/docs/${item.slug}`; + const isActive = pathname === href || (item.slug === 'introduction' && pathname === '/docs'); + const Icon = navIcons[item.slug]; + return ( +
  • + + {Icon && + {item.children && item.children.length > 0 && ( + + )} +
  • + ); +} diff --git a/web/components/docs/docs.module.css b/web/components/docs/docs.module.css index 3fbc699ad..b8dcb7cc4 100644 --- a/web/components/docs/docs.module.css +++ b/web/components/docs/docs.module.css @@ -211,6 +211,7 @@ } .navList { list-style: none; padding: 0; margin: 0; } +.navChildren { padding-left: 0.9rem; margin-top: 0.1rem; border-left: 1px solid rgba(255, 255, 255, 0.08); } .navLink { display: flex; diff --git a/web/content/docs/workflows-common-mistakes.mdx b/web/content/docs/workflows-common-mistakes.mdx new file mode 100644 index 000000000..40de5f1a5 --- /dev/null +++ b/web/content/docs/workflows-common-mistakes.mdx @@ -0,0 +1,114 @@ +--- +title: 'Common mistakes' +description: 'Every workflow author hits these once. Here is the list, so you can hit them zero times.' +--- + +Every one of these has bitten a real workflow. Copy the fix column. + +## General + +| Mistake | Fix | +|---------|-----| +| Using raw top-level `await` in a workflow file | Wrap in `async function main() { ... }` — executor-driven files sometimes behave like CJS. | +| `export default workflow(...)...build()` | No `.build()`. Chain ends with `.run()` — and the file MUST call `.run()`, not just export config. | +| `createWorkflowRenderer` | Does not exist. Use `.run({ cwd: process.cwd() })`. | +| Hardcoded model strings (`model: 'opus'`) | Use constants: `import { ClaudeModels } from '@agent-relay/config'` → `model: ClaudeModels.OPUS`. | +| Using `require()` in ESM projects | Check `package.json` for `"type": "module"` — use `import` if ESM. | +| Relative import `'../workflows/builder.js'` | Use `import { workflow } from '@agent-relay/sdk/workflows'`. | + +## Parallelism and waves + +| Mistake | Fix | +|---------|-----| +| Every step depends on the previous one | Only add `dependsOn` when there's a real data dependency. Independent steps with the same `dependsOn` parallelize. | +| One giant workflow per feature | Split into smaller workflows that can run in parallel waves (4-7× speedup). | +| `maxConcurrency: 16` with many parallel steps | Cap at 5-6. Broker times out at 10+. | +| Workers depending on lead step (DAG deadlock) | Both depend on the shared context step; downstream depends on the lead. | + +**DAG deadlock anti-pattern:** +```yaml +# WRONG +steps: + - name: coordinate + dependsOn: [context] # lead waits for WORKER_DONE... + - name: work-a + dependsOn: [coordinate] # ...but work-a can't start until coordinate finishes + +# RIGHT +steps: + - name: work-a + dependsOn: [context] # starts with lead + - name: coordinate + dependsOn: [context] # starts with workers + - name: merge + dependsOn: [work-a, coordinate] +``` + +Rule: if a lead step's task mentions downstream step names alongside waiting keywords, that's a deadlock. + +## Steps and verification + +| Mistake | Fix | +|---------|-----| +| Self-review step with no timeout | Set `timeout: 300_000` (5 min). Codex hangs in non-interactive review. | +| Adding exit instructions to tasks | Runner handles self-termination automatically. | +| Setting `timeoutMs` on agents/steps | Use global `.timeout()` only. | +| Using the `general` channel | Set `.channel('wf-name')` for isolation. | +| `{{steps.X.output}}` without `dependsOn: ['X']` | Output won't be available yet. | +| Requiring exact sentinel as only completion gate | Use `exit_code` or `file_exists` verification. | +| Writing 100-line task prompts | Split into lead + workers on a channel. | +| Single step editing 4+ files | Agents modify 1-2 then exit. Split to one file per step with verify gates. | +| Relying on agents to `git commit` | Agents emit markers without running git. Use deterministic commit step. | +| File-writing steps without `file_exists` verification | `exit_code` auto-passes even if no file written. Use `{ type: 'file_exists', value: 'path' }` for creation. | +| Non-interactive agent reading large files via tools | Pre-read in deterministic step, inject via `{{steps.X.output}}`. | + +**Verification token gotcha:** If the token (e.g. `STEP_COMPLETE`) appears in the task text, the runner requires it **twice** in output (once from task echo, once from agent). Prefer `exit_code` for code-editing steps to avoid this. + +Only these four verification types are valid: `exit_code`, `output_contains`, `file_exists`, `custom`. Invalid types are silently ignored and fall through to process-exit auto-pass. + +## Patterns + +| Mistake | Fix | +|---------|-----| +| `pattern('single')` on cloud runner | Not supported — use `dag`. | +| `pattern('supervisor')` with one agent | Same agent is owner + specialist. Use `dag`. | +| `pipeline` but expecting auto-supervisor | Only hub patterns auto-harden. Use `.pattern('supervisor')`. | +| `fan-out` / `hub-spoke` for simple parallel workers | Use `dag` instead. | +| Workers without `preset: 'worker'` in one-shot DAG lead+worker flows | Add preset for clean stdout when chaining `{{steps.X.output}}`. Not needed for interactive team patterns. | +| Separate reviewer agent from lead in interactive team | Merge lead + reviewer into one interactive Claude agent — reviews between rounds, fewer agents. | +| Chaining `{{steps.X.output}}` from interactive agents | PTY output is garbled. Use deterministic steps or `preset: 'worker'`. | + +## Shell and YAML + +| Mistake | Fix | +|---------|-----| +| Using `_` in YAML numbers (`timeoutMs: 1_200_000`) | YAML doesn't support `_` separators. | +| Workflow timeout under 30 min for complex workflows | Use `3600000` (1 hour) as default. | +| Raw fenced code blocks inside workflow task template literals | Avoid fenced examples inside template strings, or move to referenced files. Especially fragile with language tags like `swift` or `diff`. | +| `grep "foo\|bar\|baz"` (basic alternation) | Use `grep -Eq "foo\|bar\|baz"` — basic alternation can silently misbehave. | +| Cloud sandbox `/bin/sh` trying to parse `${PIPESTATUS[0]}` | Wrap in `bash -c '...'` (SINGLE quotes). `/bin/sh` on Daytona is dash; single quotes stop sh from expanding bash-only syntax. | +| `SIBLING_PATH="$RAW_VALUE"` (double-quoted shell assignments from user input) | Use single-quoted form: `SIBLING_PATH='...'`. Double quotes still expand `$(...)`, backticks, `\`. | + +## Cross-repo / worktrees + +| Mistake | Fix | +|---------|-----| +| Workflow ending without worktree + PR for cross-repo changes | Add `setup-worktree` at start and `push-and-pr` + `cleanup-worktree` at end. | +| Not printing PR URL after `gh pr create` | Add a final deterministic step: `echo "PR: $(cat pr-url.txt)"` or capture in the `gh pr create` command. | +| Touching the user's main `../other-repo` checkout | Create a sibling worktree instead: `git -C ../other-repo worktree add ../other-repo- -b `. | +| Linking sibling packages via relative imports (`file:`) | Use `applySiblingLinks` from `@agent-relay/sdk/workflows`. `file:` paths end up in committed `package.json`, which is wrong. | +| Agent fabricates an interface via `declare module` | See [Setup helpers → applySiblingLinks](/docs/workflows-setup-helpers#applysiblinglinks). Link the real sibling so agents see head-of-main types. | + +## Output and exit codes + +| Mistake | Fix | +|---------|-----| +| Relying on `agent-relay run` exit code for sub-workflow status | The runner exits 0 even when the inner workflow fails. Check `$?` AND grep `"Workflow status: failed"` from the log. | +| Committing `.trajectories/`, `logs/`, or `package-lock.json` along with code | Stage only product files: `git add src/ test/` etc. Execution exhaust shouldn't be in PRs unless the user explicitly asks. | +| `--sync-code` leaves out untracked files | Cloud tarball contains git-tracked/staged files only. `git add` before `agent-relay cloud --sync-code`. | + +## See also + +- [Patterns](/docs/workflows-patterns) — shapes that avoid most of these mistakes. +- [Setup helpers](/docs/workflows-setup-helpers) — conventions that compound (everyone benefits when the helper's right). +- [Builder API reference](/docs/reference-workflows) — the canonical API surface. diff --git a/web/content/docs/workflows-introduction.mdx b/web/content/docs/workflows-introduction.mdx new file mode 100644 index 000000000..f7ba10091 --- /dev/null +++ b/web/content/docs/workflows-introduction.mdx @@ -0,0 +1,104 @@ +--- +title: 'Workflows' +description: 'Orchestrate multi-step, multi-agent execution across Relay workers. Write once, run locally or in the cloud.' +--- + +Workflows are how Relay runs real work. A workflow is a DAG of steps — some are deterministic shell commands, others spawn agents that edit code, run tests, and push PRs. You write them in TypeScript, Python, or YAML; you run them with `agent-relay run`. + +## When to use a workflow + +Use workflows when you need: + +- **Multiple agents coordinating** — a lead that plans, workers that implement, a reviewer that gates. +- **Cross-repo changes** — edit two repos as part of one unit of work, open linked PRs. +- **Verification gates** — tests must pass before the commit. Build must pass before the push. Regressions must be zero before the PR opens. +- **Parallelism with barriers** — fan out independent work, wait at the right moments, move on. + +If you just want to spawn one agent and talk to it, [spawning an agent](/docs/spawning-an-agent) is simpler. Workflows earn their weight when you have steps that depend on each other. + +## Mental model + +A workflow is a typed DAG. Steps come in two flavors: + +- **Agent steps** — spawn a CLI (`claude`, `codex`, `gemini`, etc.) with a task prompt. Agent reads the prompt and whatever context the workflow has injected, edits files, writes tests, commits. +- **Deterministic steps** — shell commands. Reads, builds, tests, verifications, commits, pushes. No LLM. + +Steps declare dependencies (`dependsOn`). Steps with no shared dependency run in parallel. The runner enforces this topology, captures each step's stdout/stderr, and feeds one step's output into a downstream step's prompt via `{{steps.X.output}}`. + +## A minimal example + +```typescript +import { workflow } from '@agent-relay/sdk/workflows'; + +async function main() { + const result = await workflow('add-tests-to-parser') + .description('Write tests for the parser and run them until green') + .pattern('dag') + .channel('wf-parser-tests') + + .agent('impl', { cli: 'codex', preset: 'worker' }) + .agent('tester', { cli: 'codex', preset: 'worker' }) + + .step('read-parser', { + type: 'deterministic', + command: 'cat src/parser.ts', + captureOutput: true, + }) + .step('write-tests', { + agent: 'tester', + dependsOn: ['read-parser'], + task: `Write vitest tests for the parser. Current contents: + +{{steps.read-parser.output}} + +Create test/parser.test.ts.`, + verification: { type: 'file_exists', value: 'test/parser.test.ts' }, + }) + .step('run-tests', { + type: 'deterministic', + dependsOn: ['write-tests'], + command: 'npx vitest run test/parser.test.ts', + failOnError: true, + }) + + .run({ cwd: process.cwd() }); + + console.log('Status:', result.status); + if (result.status !== 'completed') process.exit(1); +} + +main().catch((e) => { console.error(e); process.exit(1); }); +``` + +Run it: + +```bash +agent-relay run workflows/add-tests-to-parser.ts +``` + +See also: + +- [Quickstart](/docs/workflows-quickstart) — walks through a working example end-to-end. +- [Builder API reference](/docs/reference-workflows) — every method on `workflow()`. +- [Patterns](/docs/workflows-patterns) — 80-to-100, lead+workers, test-fix-rerun, cross-repo. +- [Setup helpers](/docs/workflows-setup-helpers) — `applySageRepoSetup`, `applySiblingLinks`, repo-setup conventions. +- [Common mistakes](/docs/workflows-common-mistakes) — the bugs every workflow author hits once. +- [Run from CLI](/docs/cli-workflows) — the `agent-relay run` command, dry-runs, resumes. + +## Two-minute concept guide + +**Patterns.** `dag` (default), `fan-out`, `pipeline`, `hub-spoke`, `supervisor`, `debate`, and a dozen more. Pick based on how work flows between agents. See [Patterns](/docs/workflows-patterns). + +**Verification gates.** Every step can declare `verification: { type: 'exit_code' }` / `file_exists` / `output_contains`. The runner won't mark a step complete without the gate passing. Use `exit_code` for code-editing steps; use `file_exists` for creation steps. + +**Step output chaining.** `{{steps..output}}` in a downstream task injects the upstream step's stdout. Works great for deterministic steps; fragile for interactive agents (their output has ANSI/spinner noise). + +**Completion signals.** Steps complete through a multi-signal pipeline: verification gate → `OWNER_DECISION` → evidence + clean exit → marker fast-path → process-exit fallback. You describe the deliverable; you don't have to add sentinels. + +**Cloud execution.** Submit the same workflow file to `agent-relay cloud` to run it in a Daytona sandbox. Files get committed + pushed; PRs open automatically if your master workflow is set up for it. See [Cloud](/docs/cloud). + +## Next + +- Start with the [Quickstart](/docs/workflows-quickstart) for a working example you can run in 5 minutes. +- Already comfortable? Jump to [Patterns](/docs/workflows-patterns) for canonical multi-agent shapes. +- Fighting a bug? [Common mistakes](/docs/workflows-common-mistakes) has the usual suspects. diff --git a/web/content/docs/workflows-patterns.mdx b/web/content/docs/workflows-patterns.mdx new file mode 100644 index 000000000..8d694d2a4 --- /dev/null +++ b/web/content/docs/workflows-patterns.mdx @@ -0,0 +1,272 @@ +--- +title: 'Workflow patterns' +description: 'Canonical shapes for multi-agent workflows: test-fix-rerun, lead + workers, cross-repo, supervisor.' +--- + +These are the patterns that show up over and over in production workflows. Copy the shape, adapt the details. + +## Test-fix-rerun (the 80-to-100 pattern) + +Most workflows get features to ~80%: code written, types check, maybe a build passes. The gap to 100% is **running the tests inside the workflow and fixing failures before commit**. + +Three-step loop: + +```typescript +// 1. Run tests (don't fail the workflow — let the agent fix it) +.step('run-tests', { + type: 'deterministic', + dependsOn: ['write-tests'], + command: 'npx vitest run test/my-feature.test.ts 2>&1 | tail -60', + captureOutput: true, + failOnError: false, +}) + +// 2. Agent reads output, fixes, re-runs +.step('fix-tests', { + agent: 'tester', + dependsOn: ['run-tests'], + task: `Check the test output and fix any failures. + +Output: +{{steps.run-tests.output}} + +If all pass, do nothing. If there are failures, read the test + source, +fix, re-run via \`npx vitest run test/my-feature.test.ts\`. Keep iterating +until ALL tests pass.`, + verification: { type: 'exit_code' }, +}) + +// 3. Deterministic final run — this one MUST pass +.step('run-tests-final', { + type: 'deterministic', + dependsOn: ['fix-tests'], + command: 'npx vitest run test/my-feature.test.ts 2>&1', + failOnError: true, +}) +``` + +**Why three steps instead of one:** +- First run captures output for the agent to diagnose. +- Agent step can iterate (read errors, fix, re-run) multiple times. +- Final deterministic run is the gate — no agent judgment, pass/fail. + +Follow the same shape for `npx tsc --noEmit`, `npm run build`, `npm test` — any deterministic check you want a gate behind. + +## Verify gates after every agent edit + +Never trust that an agent edited a file correctly. Add a deterministic verify step after every agent edit: + +```typescript +.step('edit-schema', { + agent: 'impl', + task: 'Edit packages/web/lib/db/schema.ts ...', + verification: { type: 'exit_code' }, +}) +.step('verify-schema', { + type: 'deterministic', + dependsOn: ['edit-schema'], + command: `if git diff --quiet packages/web/lib/db/schema.ts; then echo "NOT MODIFIED"; exit 1; fi +grep "my_new_table" packages/web/lib/db/schema.ts >/dev/null && echo OK`, + failOnError: true, +}) +``` + +What to verify: +- File was actually modified (`git diff --quiet` returns non-zero). +- Key content exists (grep for table names, function names, imports). +- For new files: `verification: { type: 'file_exists', value: '...' }`. + +What NOT to verify: +- Exact content (too brittle — agents format differently). +- Line counts (meaningless). + +## Lead + workers (interactive team) + +When a task touches multiple files and may need iteration, use a **lead + workers team on a shared channel** rather than a sequential DAG of one-shot agents. The lead coordinates, reviews, and posts feedback; workers implement and iterate. + +```typescript +.agent('lead', { + cli: 'claude', + preset: 'lead', + role: 'Architect and reviewer — assigns work, reviews diffs, posts feedback', +}) +.agent('impl-new', { + cli: 'codex', + role: 'Creates new files. Listens on channel for assignments + feedback.', + // No preset — interactive, receives channel messages +}) +.agent('impl-modify', { + cli: 'codex', + role: 'Edits existing files. Listens on channel.', +}) + +// All three share the same dependsOn — they start concurrently +.step('lead-coordinate', { + agent: 'lead', + dependsOn: ['context'], + task: `You are the lead on #channel. Workers: impl-new, impl-modify. +Post the plan. Assign files. Review their work. Post feedback. +Workers iterate. Exit when all files are correct.`, +}) +.step('impl-new-work', { + agent: 'impl-new', + dependsOn: ['context'], // same dep as lead = parallel start + task: 'You are impl-new on #channel. Wait for the lead\'s plan. Create files as assigned.', +}) +.step('impl-modify-work', { + agent: 'impl-modify', + dependsOn: ['context'], + task: 'You are impl-modify on #channel. Wait for plan. Edit as assigned.', +}) + +// Downstream gates on lead — lead exits when satisfied +.step('verify', { type: 'deterministic', dependsOn: ['lead-coordinate'], ... }) +``` + +**Key behaviors:** +- **Workers self-organize from channel context.** They read each other's completion messages and start dependent work without waiting for the lead to relay. +- **Lead-as-reviewer is more efficient than a separate reviewer agent.** The lead reads actual files and runs typecheck between rounds. +- **No feedback loop needed = fast path.** If workers get it right first try, the interactive pattern completes just as fast as one-shot. + +When to use interactive team vs one-shot DAG: + +| Scenario | Pattern | +|----------|---------| +| 4+ files, likely needs iteration | Interactive team | +| Simple edits, well-specified | One-shot DAG with `preset: 'worker'` | +| Cross-agent review feedback loop | Interactive team | +| Independent tasks, no coordination | Fan-out with non-interactive workers | + +## Multi-file edit pattern (one file per step) + +Agents reliably edit 1-2 files per step but fail on 4+. Split multi-file edits into **one agent step per file**, each with a deterministic verify gate: + +```typescript +.step('read-types', { + type: 'deterministic', + command: 'cat src/types.ts', + captureOutput: true, +}) +.step('edit-types', { + agent: 'impl', + dependsOn: ['read-types'], + task: `Edit src/types.ts. Current contents: +{{steps.read-types.output}} +Add 'pending' to the Status union. Only edit this one file.`, + verification: { type: 'exit_code' }, +}) +.step('verify-types', { + type: 'deterministic', + dependsOn: ['edit-types'], + command: `if git diff --quiet src/types.ts; then echo "NOT MODIFIED"; exit 1; fi; echo OK`, + failOnError: true, +}) + +.step('read-service', { + type: 'deterministic', + dependsOn: ['verify-types'], + command: 'cat src/service.ts', + captureOutput: true, +}) +.step('edit-service', { ... }) +.step('verify-service', { ... }) + +.step('commit', { + type: 'deterministic', + dependsOn: ['verify-service'], + command: 'git add src/types.ts src/service.ts && git commit -m "feat: add pending status"', +}) +``` + +**Rules:** +- Read the file in a deterministic step **right before** the edit (not all files upfront — agent sees stale state if main moved). +- Tell the agent "Only edit this one file" to prevent it touching others. +- Verify with `git diff --quiet` after each edit — fail fast if the agent didn't write. +- Always commit with a deterministic step, never an agent step. + +## Cross-repo workflows + +When the unit of work spans two repos (e.g. a feature that touches both `sage` and `cloud/infra`), the master creates a sibling worktree for the second repo and orchestrates work across both: + +```typescript +const CLOUD_WORKTREE = '../cloud-my-feature'; + +// Wave 0: ensure both repos are ready +.step('setup-cloud-worktree', { + type: 'deterministic', + command: `if [ ! -d "${CLOUD_WORKTREE}" ]; then + git -C ../cloud worktree add -B feat/my-change "${CLOUD_WORKTREE}" origin/main + fi`, +}) + +// Wave 1: parallel work on each repo +.step('wave1-cloud-change', { + type: 'deterministic', + dependsOn: ['setup-cloud-worktree'], + command: `cd ${CLOUD_WORKTREE} && agent-relay run $OLDPWD/workflows/01-cloud.ts`, +}) +.step('wave1-sage-change', { + type: 'deterministic', + dependsOn: ['install-deps'], + command: 'agent-relay run workflows/02-sage.ts', +}) + +// Final: cross-repo commit + push + two linked PRs +.step('open-cloud-pr', { + type: 'deterministic', + dependsOn: ['wave1-cloud-change'], + command: `cd ${CLOUD_WORKTREE} && gh pr create --title '...' --body '...'`, +}) +.step('open-sage-pr', { ... }) +``` + +**Gotchas:** +- Don't touch the user's main `../cloud` checkout — always create a sibling worktree. +- Sibling repos linked as dependencies? See [Setup helpers → applySiblingLinks](/docs/workflows-setup-helpers#applysiblinglinks) to avoid "agents see stale interfaces" bugs. +- Each repo gets its own PR; merge order depends on dependency direction (infra usually first). + +## Supervisor pattern + +Use `.pattern('supervisor')` (or `hub-spoke` / `fan-out`) when workers need oversight — the runner auto-assigns a supervisor agent as owner for worker steps. The supervisor monitors progress, nudges idle workers, and issues `OWNER_DECISION`. + +| Use case | Pattern | Why | +|----------|---------|-----| +| Sequential, no monitoring | `pipeline` | Simple, no overhead | +| Workers need oversight | `supervisor` | Auto-owner monitors | +| Local/small models | `supervisor` | Supervisor catches stuck workers | +| All non-interactive | `pipeline` or `dag` | No PTY = no supervision needed | + +Auto-hardening only activates for hub patterns (`supervisor`, `hub-spoke`, `fan-out`) — not `pipeline` or `dag`. + +## Parallelism: waves and fan-out + +Two workflows or two steps can run in parallel if they don't conflict on files. Heuristics: + +| Touch zone | Can parallelize? | +|---|---| +| Different `packages/*/src/` dirs | ✅ Yes | +| Different `app/` routes | ✅ Yes | +| Same package, different subdirs | ⚠️ Usually yes | +| Same files (shared config, root `package.json`) | ❌ No | +| Explicit dep | ❌ No — ordered waves | + +Fan out by having multiple steps share the same `dependsOn`: + +```typescript +// BAD — unnecessary sequential chain +.step('fix-component-a', { dependsOn: ['review'] }) +.step('fix-component-b', { dependsOn: ['fix-component-a'] }) // why wait? + +// GOOD — parallel fan-out, merge at the end +.step('fix-component-a', { agent: 'impl-1', dependsOn: ['review'] }) +.step('fix-component-b', { agent: 'impl-2', dependsOn: ['review'] }) // parallel +.step('verify-all', { agent: 'reviewer', dependsOn: ['fix-component-a', 'fix-component-b'] }) +``` + +Cap `maxConcurrency` at **4-6**. Spawning 10+ agents simultaneously causes broker timeouts. + +## See also + +- [Setup helpers](/docs/workflows-setup-helpers) — repo-setup conventions and `applySiblingLinks`. +- [Common mistakes](/docs/workflows-common-mistakes) — the bugs that break these patterns in practice. +- [Builder API reference](/docs/reference-workflows) — every method on `workflow()`. diff --git a/web/content/docs/workflows-quickstart.mdx b/web/content/docs/workflows-quickstart.mdx new file mode 100644 index 000000000..9b0279726 --- /dev/null +++ b/web/content/docs/workflows-quickstart.mdx @@ -0,0 +1,147 @@ +--- +title: 'Workflows quickstart' +description: 'A working workflow in 5 minutes: plan, implement, test, verify, commit.' +--- + +This page walks through building and running a real workflow end-to-end. By the end, you'll have a workflow that plans a small feature with a lead agent, implements it with a worker agent, runs tests, and commits — only if everything passes. + +## Prerequisites + +- `@agent-relay/cli` installed globally: `npm i -g @agent-relay/cli` +- `@agent-relay/sdk` installed in the target repo: `npm i @agent-relay/sdk` +- One CLI installed and authenticated: `claude`, `codex`, `gemini`, `aider`, or `goose` + +Check: `agent-relay --version`. + +## 1. Create the workflow file + +```bash +mkdir -p workflows +``` + +`workflows/add-greeting.ts`: + +```typescript +import { workflow } from '@agent-relay/sdk/workflows'; +import { ClaudeModels, CodexModels } from '@agent-relay/config'; + +async function main() { + const result = await workflow('add-greeting') + .description('Add a greeting function + tests') + .pattern('dag') + .channel('wf-add-greeting') + .maxConcurrency(3) + .timeout(900_000) + + .agent('lead', { + cli: 'claude', + model: ClaudeModels.SONNET, + preset: 'lead', + role: 'Plans the shape of the greeting function and tests', + retries: 1, + }) + .agent('impl', { + cli: 'codex', + model: CodexModels.GPT_5_4, + role: 'Writes src/greeting.ts and its tests', + retries: 2, + }) + + .step('plan', { + agent: 'lead', + task: `Post a short plan for: + src/greeting.ts — export greet(name: string): string + returns "Hello, !"; empty name -> "Hello, friend!" + test/greeting.test.ts — vitest covering both cases + +Keep plan to 5 bullets.`, + }) + + .step('write-code', { + agent: 'impl', + dependsOn: ['plan'], + task: 'Create src/greeting.ts per the lead plan. Only this file.', + verification: { type: 'file_exists', value: 'src/greeting.ts' }, + }) + + .step('write-tests', { + agent: 'impl', + dependsOn: ['write-code'], + task: 'Create test/greeting.test.ts per the lead plan. Only this file.', + verification: { type: 'file_exists', value: 'test/greeting.test.ts' }, + }) + + .step('run-tests', { + type: 'deterministic', + dependsOn: ['write-tests'], + command: 'npx vitest run test/greeting.test.ts 2>&1 | tail -30', + captureOutput: true, + failOnError: true, + }) + + .step('commit', { + type: 'deterministic', + dependsOn: ['run-tests'], + command: 'git add src/greeting.ts test/greeting.test.ts && git commit -m "feat: add greet()"', + failOnError: true, + }) + + .run({ cwd: process.cwd() }); + + console.log('Workflow status:', result.status); + if (result.status !== 'completed') process.exit(1); +} + +main().catch((e) => { console.error(e); process.exit(1); }); +``` + +## 2. Dry-run to validate + +Always dry-run before a real run — catches typos, missing dependencies, and invalid patterns before you spend agent time: + +```bash +agent-relay run --dry-run workflows/add-greeting.ts +``` + +You should see: + +``` +Validation: PASS (0 errors, 0 warnings) + +Execution Plan (5 steps, 5 waves): + Wave 1: plan (lead) + Wave 2: write-code (impl) + Wave 3: write-tests (impl) + Wave 4: run-tests (undefined) + Wave 5: commit (undefined) +``` + +## 3. Run it + +```bash +agent-relay run workflows/add-greeting.ts +``` + +You'll see the lead agent post a plan on the workflow channel, the worker implement each file in sequence, vitest run against the fresh code, and the commit land only after tests pass. + +## 4. What just happened + +- **`workflow('add-greeting')`** — named the run. Log files + trajectories use this name. +- **`.pattern('dag')`** — steps run in dependency order; independent steps parallelize. +- **`.agent(...)`** — declares the CLIs and their roles. The `preset: 'lead'` gives the lead an interactive shell so it can post to the channel; workers are non-interactive by default. +- **`.step(...)` with `agent: ...`** — spawns the named CLI with the task prompt. The agent runs in the workflow's cwd and can edit files. +- **`type: 'deterministic'`** — pure shell command. No LLM. Use these for reads, tests, builds, commits. +- **`verification`** — gates the step. `file_exists` for creation; `exit_code` for edits; `output_contains` for marker-based checks. +- **`failOnError: true`** — a failed deterministic step stops the whole workflow. + +## 5. Things to try next + +- Add a **reviewer step** that reads the diff and posts either `LGTM` or `BLOCKERS:`. Gate the commit behind it. +- Swap `pattern('dag')` for `pattern('supervisor')` and see the auto-spawned supervisor monitor the workers. +- Break `write-code` and `write-tests` into a **team** by giving them a shared channel and having the lead post feedback between rounds. See [Patterns → Lead + workers](/docs/workflows-patterns#lead--workers). + +## Full reference + +- [Builder API](/docs/reference-workflows) — every method on `workflow()`. +- [YAML alternative](/docs/reference-workflows#yaml-shape) — same concepts, config-driven. +- [Common mistakes](/docs/workflows-common-mistakes) — bugs everyone hits once. diff --git a/web/content/docs/workflows-setup-helpers.mdx b/web/content/docs/workflows-setup-helpers.mdx new file mode 100644 index 000000000..b918339a1 --- /dev/null +++ b/web/content/docs/workflows-setup-helpers.mdx @@ -0,0 +1,168 @@ +--- +title: 'Setup helpers' +description: 'Shared setup steps for workflows — branch checkout, install, build, sibling linking. Keep boilerplate out of each workflow file.' +--- + +Every workflow that produces code needs roughly the same prelude: checkout a branch, install deps, maybe build a shared package or two. Without a shared helper, the first workflow that adds a new prerequisite (e.g. "build the platform package because its types point at `dist/`") only fixes itself — every other workflow silently misses it. + +This page covers two conventions that compose well: + +1. **Per-repo setup helper** — one file per consumer repo that adds `setup-branch` + `install-deps` steps. +2. **`applySiblingLinks`** — the SDK helper that links sibling-repo packages into the workflow's working directory so agents see the real, current interface instead of whatever's on npm. + +## Per-repo setup helper + +Put shared prelude in `workflows/lib/-setup.ts`. Every workflow in that repo calls it. + +```typescript +// workflows/lib/my-repo-setup.ts +export interface MyRepoSetupOptions { + branch: string; + committerName?: string; + extraSetupCommands?: string[]; + skipWorkspaceBuild?: boolean; +} + +interface StepChain { + step: (name: string, cfg: unknown) => StepChain; +} + +export function applyMyRepoSetup(wf: T, opts: MyRepoSetupOptions): T { + const committerName = opts.committerName ?? 'My Workflow Bot'; + const setupBranchCommand = [ + 'set -e', + 'git config user.email "agent@my-org.local"', + `git config user.name ${JSON.stringify(committerName)}`, + `git checkout -B ${opts.branch}`, + ...(opts.extraSetupCommands ?? []), + ].join(' && '); + + const installCommand = opts.skipWorkspaceBuild + ? 'npm install --legacy-peer-deps --no-audit --no-fund 2>&1 | tail -10' + : [ + 'npm install --legacy-peer-deps --no-audit --no-fund 2>&1 | tail -10', + 'npm run build --workspaces --if-present 2>&1 | tail -20', + ].join(' && '); + + const chain = wf as unknown as StepChain; + chain + .step('setup-branch', { + type: 'deterministic', + command: setupBranchCommand, + captureOutput: true, + failOnError: true, + }) + .step('install-deps', { + type: 'deterministic', + dependsOn: ['setup-branch'], + command: installCommand, + captureOutput: true, + failOnError: true, + }); + + return wf; +} +``` + +Usage: + +```typescript +import { workflow } from '@agent-relay/sdk/workflows'; +import { applyMyRepoSetup } from './lib/my-repo-setup'; + +const baseWf = workflow(NAME) + .description('...') + .pattern('dag') + .agent('impl', { ... }); + +const wf = applyMyRepoSetup(baseWf, { + branch: 'feat/my-change', + committerName: 'Feature X Bot', +}); + +await wf + .step('read-spec', { type: 'deterministic', dependsOn: ['install-deps'], ... }) + // ... rest of the workflow + .run({ cwd: process.cwd() }); +``` + +**Rules:** +- The helper lives in the **consumer repo**, not the SDK. Different repos have different languages, package managers, build graphs. +- Pre-build any workspace package whose `package.json` `main`/`types` point at a generated `dist/`. Fresh sandboxes don't have that `dist/` yet; agents will invent workarounds (e.g. `external-modules.d.ts` shims) rather than run the build. +- Every install step includes `--legacy-peer-deps --no-audit --no-fund 2>&1 | tail -10` (or equivalent) because full install output blows past `captureOutput` size limits. + +## `applySiblingLinks` + +Ships as part of `@agent-relay/sdk/workflows`. Adds one deterministic step that links sibling-repo packages into the workflow's working directory so agents see the real, head-of-main interface — not whatever `npm install` resolved. + +**Problem it solves:** a workflow consumes `@my-org/some-package`. The producer repo has new exports on its `main` branch that aren't published to npm yet. Without linking, agents writing workflow code hit `has no exported member 'newThing'` and — rather than stop — reach for `declare module` augmentations or fallback implementations. The workflow ships code that can't integrate with the real package once it publishes. + +```typescript +import { workflow, applySiblingLinks } from '@agent-relay/sdk/workflows'; + +const baseWf = workflow('my-feature').pattern('dag').agent('impl', { ... }); + +const wf = applySiblingLinks(baseWf, { + dependsOn: ['install-deps'], + links: [ + { + name: '@my-org/some-package', + path: '../some-repo/packages/some-package', + expect: ['newThing', 'anotherThing'], + }, + { + name: 'my_python_pkg', + path: '../py-repo/src/my_python_pkg', + expect: ['classify_signal'], + }, + ], +}); +``` + +**Auto-detect by manifest:** + +| Manifest in sibling | Mechanism | +|---|---| +| `package.json` | `npm link` (symlinks into `node_modules` — committed files untouched) | +| `pyproject.toml` / `setup.py` / `setup.cfg` | `uv pip install --system -e` (falls back to `pip` / `pip3`) | + +**Fail-fast on:** +- Missing sibling path. +- Unknown manifest (none of the supported files present). +- Link command failure. +- Missing expected export (post-link smoke test via `node --input-type=module` or `python3 -c`). + +The `expect` array is the final guardrail. Even if the link command succeeded, if the real interface doesn't match what the workflow expects, the step fails before any agent writes a line. + +### When to use it + +Use `applySiblingLinks` whenever your workflow consumes a package that: +- Lives in a sibling repo (monorepo-of-repos setup). +- Has changes on its `main` branch that may not be published yet. +- Your workflow targets head-of-main semantics, not published semantics. + +Don't use it for third-party packages (no sibling worktree). Use it for your own org's packages that you're iterating on concurrently. + +### Options + +| Option | Default | Notes | +|---|---|---| +| `links` | (required) | Array of `{name, path, expect?}` | +| `stepName` | `'setup-sibling-links'` | Name of the emitted step | +| `dependsOn` | `['install-deps']` | Typically after install so `node_modules` exists | + +## Shell rules for setup helpers + +Setup helpers emit shell commands. A few hard-won rules from debugging them in Daytona sandboxes: + +- **Use `bash -c '...'` (single-quoted) for multi-line scripts.** Daytona's `/bin/sh` is dash, which doesn't support `${PIPESTATUS[0]}`. Bash with single-quoted outer prevents dash from expanding bash-only syntax. +- **Single-quote values in bash assignments** (`VAR='literal'`, not `VAR="literal"`). Double-quoted strings still expand `$VAR`, `$(cmd)`, and backticks — which is fine for tokens you control but dangerous for user-supplied paths / names. +- **`set -euo pipefail` at the top.** Fail on unset vars, fail on any pipe stage failure. +- **`2>&1 | tail -N` on noisy installers.** Full npm/pip output blows past `captureOutput` size limits; tail keeps enough to diagnose. + +See the SDK source for [`applySiblingLinks`](https://github.com/AgentWorkforce/relay/blob/main/packages/sdk/src/workflows/sibling-links.ts) for a reference implementation that handles all of these. + +## See also + +- [Patterns](/docs/workflows-patterns) — how these helpers plug into real workflow shapes. +- [Common mistakes](/docs/workflows-common-mistakes) — shell-quoting pitfalls, hook-related failures. diff --git a/web/lib/docs-nav.ts b/web/lib/docs-nav.ts index 1dcc38027..8d89395f1 100644 --- a/web/lib/docs-nav.ts +++ b/web/lib/docs-nav.ts @@ -1,6 +1,12 @@ export interface NavItem { title: string; slug: string; + /** + * Optional nested items rendered as an indented sub-list beneath this + * item. Used to group related pages (e.g. all messaging primitives under + * "Message") without creating a separate top-level nav group. + */ + children?: NavItem[]; } export interface NavGroup { @@ -14,28 +20,47 @@ export const docsNav: NavGroup[] = [ items: [ { title: 'Introduction', slug: 'introduction' }, { title: 'Quickstart', slug: 'quickstart' }, + { title: 'Spawning an agent', slug: 'spawning-an-agent' }, + { title: 'Event handlers', slug: 'event-handlers' }, ], }, { - title: 'Basics', + title: 'Primitives', items: [ - { title: 'Spawning an agent', slug: 'spawning-an-agent' }, - { title: 'Sending messages', slug: 'sending-messages' }, - { title: 'Event handlers', slug: 'event-handlers' }, - { title: 'Channels', slug: 'channels' }, - { title: 'DMs', slug: 'dms' }, - { title: 'Threads', slug: 'threads' }, - { title: 'Emoji reactions', slug: 'emoji-reactions' }, - { title: 'File sharing', slug: 'file-sharing' }, - { title: 'Authentication', slug: 'authentication' }, - { title: 'Permissions', slug: 'permissions' }, - { title: 'Scheduling', slug: 'scheduling' }, + { + title: 'Message', + slug: 'sending-messages', + children: [ + { title: 'Channels', slug: 'channels' }, + { title: 'DMs', slug: 'dms' }, + { title: 'Threads', slug: 'threads' }, + { title: 'Emoji reactions', slug: 'emoji-reactions' }, + ], + }, + { title: 'File', slug: 'file-sharing' }, + { + title: 'Auth', + slug: 'authentication', + children: [{ title: 'Permissions', slug: 'permissions' }], + }, + { title: 'Schedule', slug: 'scheduling' }, + ], + }, + { + title: 'Workflows', + items: [ + { title: 'Introduction', slug: 'workflows-introduction' }, + { title: 'Quickstart', slug: 'workflows-quickstart' }, + { title: 'Builder API', slug: 'reference-workflows' }, + { title: 'Patterns', slug: 'workflows-patterns' }, + { title: 'Setup helpers', slug: 'workflows-setup-helpers' }, + { title: 'Common mistakes', slug: 'workflows-common-mistakes' }, + { title: 'Run from CLI', slug: 'cli-workflows' }, ], }, { title: 'Advanced', items: [ - { title: 'Workflows', slug: 'reference-workflows' }, { title: 'Cloud', slug: 'cloud' }, { title: 'Workforce', slug: 'workforce' }, ], @@ -54,7 +79,6 @@ export const docsNav: NavGroup[] = [ { title: 'Broker lifecycle', slug: 'cli-broker-lifecycle' }, { title: 'Agent management', slug: 'cli-agent-management' }, { title: 'Messaging', slug: 'cli-messaging' }, - { title: 'Run workflows', slug: 'cli-workflows' }, { title: 'Cloud commands', slug: 'cli-cloud-commands' }, { title: 'On the relay', slug: 'cli-on-the-relay' }, { title: 'CLI reference', slug: 'reference-cli' }, @@ -79,9 +103,19 @@ export const docsNav: NavGroup[] = [ }, ]; +/** Walk a NavItem tree and collect every slug (root + children). */ +function collectSlugs(items: NavItem[]): string[] { + const out: string[] = []; + for (const item of items) { + out.push(item.slug); + if (item.children) out.push(...collectSlugs(item.children)); + } + return out; +} + /** All doc slugs including hidden pages (for static generation + search) */ const ALL_SLUGS = [ - ...docsNav.flatMap((group) => group.items.map((item) => item.slug)), + ...docsNav.flatMap((group) => collectSlugs(group.items)), // Hidden from nav but still routable 'communicate', 'communicate-ai-sdk', @@ -94,7 +128,6 @@ const ALL_SLUGS = [ 'communicate-crewai', 'local-mode', 'reference-openclaw', - 'reference-workflows', ]; /** Flat list of all doc slugs for static generation */ From c0fb01da1081bf3caf7ae19ba59cecafb9382cec Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 09:27:36 +0200 Subject: [PATCH 02/12] docs: collapse Primitives sub-items by default, auto-expand on active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Primitives sub-items (Message, Auth) now render collapsed by default with a chevron toggle button. Rules: - Click the chevron to expand/collapse. Parent link stays active as a real nav link (clicking the label navigates to the parent page). - Auto-expands when the current URL is the parent or any of its children, so users don't lose their bearings after clicking a child link. - Chevron rotates 90deg when open; hover highlights the toggle. - Accessible: aria-expanded + aria-controls + aria-label on toggle. NavItemRow is now a component (uses useState) so each expandable item maintains independent open state. Non-expandable items (no children) render with the same layout as before — no chevron. --- web/components/docs/DocsNav.tsx | 76 ++++++++++++++++++++++++----- web/components/docs/docs.module.css | 42 ++++++++++++++++ 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/web/components/docs/DocsNav.tsx b/web/components/docs/DocsNav.tsx index c9797a0f4..9622f67f8 100644 --- a/web/components/docs/DocsNav.tsx +++ b/web/components/docs/DocsNav.tsx @@ -1,11 +1,12 @@ 'use client'; import type { ComponentType, ReactElement } from 'react'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { usePathname } from 'next/navigation'; import { Activity, Bot, + ChevronRight, Cloud, Clock3, Compass, @@ -124,26 +125,77 @@ export function DocsNav({ variant = 'sidebar' }: { variant?: 'sidebar' | 'mobile {docsNav.map((group) => (

    {group.title}

    -
      {group.items.map((item) => renderNavItem(item, pathname))}
    +
      + {group.items.map((item) => ( + + ))} +
    ))} ); } -function renderNavItem(item: NavItem, pathname: string): ReactElement { +function isLinkActive(slug: string, pathname: string): boolean { + return pathname === `/docs/${slug}` || (slug === 'introduction' && pathname === '/docs'); +} + +function containsActive(item: NavItem, pathname: string): boolean { + if (isLinkActive(item.slug, pathname)) return true; + return item.children?.some((child) => containsActive(child, pathname)) ?? false; +} + +function NavItemRow({ item, pathname }: { item: NavItem; pathname: string }): ReactElement { const href = `/docs/${item.slug}`; - const isActive = pathname === href || (item.slug === 'introduction' && pathname === '/docs'); + const isActive = isLinkActive(item.slug, pathname); const Icon = navIcons[item.slug]; + const hasChildren = Boolean(item.children && item.children.length > 0); + + // Collapsed by default; auto-expanded if the current page is in this + // item's subtree so users don't lose their bearings when navigating. + const activeInSubtree = hasChildren && containsActive(item, pathname); + const [open, setOpen] = useState(activeInSubtree); + + // Re-sync open state when the pathname changes (e.g. nav click). + useEffect(() => { + if (activeInSubtree) setOpen(true); + }, [activeInSubtree]); + + if (!hasChildren) { + return ( +
  • + + {Icon && +
  • + ); + } + + const childListId = `nav-children-${item.slug}`; return ( -
  • - - {Icon && - {item.children && item.children.length > 0 && ( -
      - {item.children.map((child) => renderNavItem(child, pathname))} +
    • +
      + + {Icon && + +
      + {open && ( +
        + {item.children!.map((child) => ( + + ))}
      )}
    • diff --git a/web/components/docs/docs.module.css b/web/components/docs/docs.module.css index b8dcb7cc4..1f671dad2 100644 --- a/web/components/docs/docs.module.css +++ b/web/components/docs/docs.module.css @@ -213,6 +213,48 @@ .navList { list-style: none; padding: 0; margin: 0; } .navChildren { padding-left: 0.9rem; margin-top: 0.1rem; border-left: 1px solid rgba(255, 255, 255, 0.08); } +.navLinkRow { + display: flex; + align-items: center; + gap: 0.15rem; +} + +.navLinkRow .navLink { + flex: 1; + min-width: 0; +} + +.navToggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.35rem; + height: 1.35rem; + padding: 0; + background: transparent; + border: 0; + border-radius: 0.3rem; + color: var(--fg-muted); + cursor: pointer; + opacity: 0.65; + transition: transform 0.15s ease, opacity 0.15s, background 0.15s; +} + +.navToggle:hover { + opacity: 1; + background: rgba(255, 255, 255, 0.06); +} + +.navToggleOpen { + transform: rotate(90deg); + opacity: 1; +} + +.navToggleIcon { + width: 0.85rem; + height: 0.85rem; +} + .navLink { display: flex; align-items: center; From fa3fe33a30acc2e5b124ee8fb67d05fe8345b9d8 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 09:32:28 +0200 Subject: [PATCH 03/12] docs: document --sync-code semantics + add cloud flow to workflow docs --sync-code was previously mentioned once in cli-cloud-commands.mdx with no explanation. New workflow authors kept discovering the 'untracked files silently excluded' footgun the hard way. Adds: - A full section in cli-cloud-commands.mdx covering what --sync-code does (tarballs git-tracked + staged files), when to use it (~always), when not to (rare cases), and the typical flow. - A cloud-execution block in workflows-introduction.mdx showing the common invocation pattern + linking to the full reference. - A 'Run it in the cloud' section in workflows-quickstart.mdx that walks through stage-then-sync-run-sync-back. Cross-links from the intro and quickstart to the cli page section. Reinforces the existing 'untracked files excluded' entry in workflows-common-mistakes.mdx. --- web/content/docs/cli-cloud-commands.mdx | 47 +++++++++++++++++++++ web/content/docs/workflows-introduction.mdx | 12 +++++- web/content/docs/workflows-quickstart.mdx | 19 ++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/web/content/docs/cli-cloud-commands.mdx b/web/content/docs/cli-cloud-commands.mdx index 13dc0dab2..1456641e6 100644 --- a/web/content/docs/cli-cloud-commands.mdx +++ b/web/content/docs/cli-cloud-commands.mdx @@ -47,6 +47,53 @@ agent-relay cloud sync - `logs` streams workflow or per-agent output. - `sync` downloads the generated patch and applies it locally. +## `--sync-code`: uploading your local repo + +`--sync-code` tarballs your current working copy and ships it to the cloud sandbox as the starting point for the run. Without it, the sandbox pulls from the remote tracked branch (whatever's on `origin`) — which means local changes, uncommitted edits, and untracked helper files are missing. + +**In almost every case, you want `--sync-code`.** Running a workflow against whatever is on `origin/main` is rarely what you mean; you almost always want to test against the exact state of your worktree. + +### What gets uploaded + +The tarball is built from `git ls-files` — every **tracked or staged** file in your repo: + +- ✅ Files you've committed +- ✅ Files you've `git add`ed but not committed yet +- ❌ **Untracked files are silently excluded** — `.gitignore`d or just never-added files will not appear in the sandbox + +This catches new workflow authors regularly. If you just created `workflows/my-new.ts` and didn't `git add` it, the cloud sandbox receives nothing at that path and the run fails with "file not found." + +**Rule of thumb:** `git add` (or commit) before `agent-relay cloud run --sync-code`. You don't need to push — staged is enough. + +### When NOT to use `--sync-code` + +Rare but real: + +- You want to run exactly what's on `origin/main` (e.g. reproducing a CI failure against a known-good commit). +- You're running a workflow that explicitly checks out a branch in its `setup-branch` step and doesn't care about local state. + +### Typical flow + +```bash +# Edit your workflow locally +vim workflows/fix-bug.ts + +# Stage it (and anything else the workflow needs) +git add workflows/fix-bug.ts + +# Ship the current worktree state to cloud and run +agent-relay cloud run workflows/fix-bug.ts --sync-code +# note the run ID printed... + +# Stream logs as it runs +agent-relay cloud logs --follow + +# When complete, pull the produced diff back into your local worktree +agent-relay cloud sync +``` + +See [Workflows → Common mistakes](/docs/workflows-common-mistakes#output-and-exit-codes) for the "untracked files silently excluded" pitfall. + ## Inspect a patch before applying it ```bash diff --git a/web/content/docs/workflows-introduction.mdx b/web/content/docs/workflows-introduction.mdx index f7ba10091..561823fdd 100644 --- a/web/content/docs/workflows-introduction.mdx +++ b/web/content/docs/workflows-introduction.mdx @@ -95,7 +95,17 @@ See also: **Completion signals.** Steps complete through a multi-signal pipeline: verification gate → `OWNER_DECISION` → evidence + clean exit → marker fast-path → process-exit fallback. You describe the deliverable; you don't have to add sentinels. -**Cloud execution.** Submit the same workflow file to `agent-relay cloud` to run it in a Daytona sandbox. Files get committed + pushed; PRs open automatically if your master workflow is set up for it. See [Cloud](/docs/cloud). +**Cloud execution.** Submit the same workflow file to `agent-relay cloud` to run it in a Daytona sandbox. Files get committed + pushed; PRs open automatically if your master workflow is set up for it. + +```bash +# Almost always pass --sync-code — without it, the sandbox pulls from +# origin and your local worktree (including new workflow files you haven't +# pushed yet) isn't there. +git add workflows/my-workflow.ts +agent-relay cloud run workflows/my-workflow.ts --sync-code +``` + +The tarball covers every git-tracked or staged file. **Untracked files are silently excluded** — if you forget to `git add` a helper file, the cloud sandbox receives nothing at that path and the run fails "file not found." See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full flow. ## Next diff --git a/web/content/docs/workflows-quickstart.mdx b/web/content/docs/workflows-quickstart.mdx index 9b0279726..30bda5418 100644 --- a/web/content/docs/workflows-quickstart.mdx +++ b/web/content/docs/workflows-quickstart.mdx @@ -134,7 +134,24 @@ You'll see the lead agent post a plan on the workflow channel, the worker implem - **`verification`** — gates the step. `file_exists` for creation; `exit_code` for edits; `output_contains` for marker-based checks. - **`failOnError: true`** — a failed deterministic step stops the whole workflow. -## 5. Things to try next +## 5. Run it in the cloud + +The same workflow file runs unchanged in a Daytona sandbox: + +```bash +# IMPORTANT: stage the workflow file first. `--sync-code` tarballs +# tracked + staged files — untracked ones are silently excluded. +git add workflows/add-greeting.ts +agent-relay cloud run workflows/add-greeting.ts --sync-code + +# ...note the run ID printed, then: +agent-relay cloud logs --follow +agent-relay cloud sync # pull the produced diff back locally +``` + +Almost every cloud run should pass `--sync-code` — it uploads your current worktree as the starting point. Without it, the sandbox starts from `origin/main` and your local changes (including the workflow file you just wrote) are missing. See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo). + +## 6. Things to try next - Add a **reviewer step** that reads the diff and posts either `LGTM` or `BLOCKERS:`. Gate the commit behind it. - Swap `pattern('dag')` for `pattern('supervisor')` and see the auto-spawned supervisor monitor the workers. From 105ab926e391b7d485cf50189df399af0633be68 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 09:34:11 +0200 Subject: [PATCH 04/12] =?UTF-8?q?docs:=20correct=20--sync-code=20=E2=80=94?= =?UTF-8?q?=20only=20committed=20files=20are=20synced,=20not=20staged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrects the staging-is-enough claim that appeared in the prior commit. --sync-code tarballs HEAD: ✅ Committed files (pushed or local) ❌ Staged-but-not-committed ❌ Unstaged edits ❌ Untracked files Updates all four affected pages (cli-cloud-commands, workflows- introduction, workflows-quickstart, workflows-common-mistakes) with consistent wording: 'commit' rather than 'git add', with a callout that pushing isn't required but committing is. --- web/content/docs/cli-cloud-commands.mdx | 19 +++++++++++-------- .../docs/workflows-common-mistakes.mdx | 2 +- web/content/docs/workflows-introduction.mdx | 8 +++++--- web/content/docs/workflows-quickstart.mdx | 7 ++++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/web/content/docs/cli-cloud-commands.mdx b/web/content/docs/cli-cloud-commands.mdx index 1456641e6..c21349e15 100644 --- a/web/content/docs/cli-cloud-commands.mdx +++ b/web/content/docs/cli-cloud-commands.mdx @@ -55,15 +55,16 @@ agent-relay cloud sync ### What gets uploaded -The tarball is built from `git ls-files` — every **tracked or staged** file in your repo: +**Only committed files.** The tarball is built from `HEAD` — anything that isn't in a commit on your current branch is excluded: -- ✅ Files you've committed -- ✅ Files you've `git add`ed but not committed yet -- ❌ **Untracked files are silently excluded** — `.gitignore`d or just never-added files will not appear in the sandbox +- ✅ Files committed on the current branch (even if not pushed) +- ❌ **Staged-but-not-committed changes are excluded** +- ❌ Uncommitted working-tree edits are excluded +- ❌ Untracked files are excluded -This catches new workflow authors regularly. If you just created `workflows/my-new.ts` and didn't `git add` it, the cloud sandbox receives nothing at that path and the run fails with "file not found." +This catches new workflow authors regularly. If you just created `workflows/my-new.ts` or edited an existing file but haven't committed, the cloud sandbox sees the pre-edit state and the run fails with "file not found" or unexpected old behavior. -**Rule of thumb:** `git add` (or commit) before `agent-relay cloud run --sync-code`. You don't need to push — staged is enough. +**Rule of thumb:** `git commit` before `agent-relay cloud run --sync-code`. You don't need to push — local commits are enough, but staging isn't. ### When NOT to use `--sync-code` @@ -78,10 +79,12 @@ Rare but real: # Edit your workflow locally vim workflows/fix-bug.ts -# Stage it (and anything else the workflow needs) +# Commit it (and anything else the workflow needs). --sync-code only +# picks up committed files, so staging alone is NOT enough. git add workflows/fix-bug.ts +git commit -m "wip: new workflow" -# Ship the current worktree state to cloud and run +# Ship HEAD to the cloud sandbox and run agent-relay cloud run workflows/fix-bug.ts --sync-code # note the run ID printed... diff --git a/web/content/docs/workflows-common-mistakes.mdx b/web/content/docs/workflows-common-mistakes.mdx index 40de5f1a5..8c9414baf 100644 --- a/web/content/docs/workflows-common-mistakes.mdx +++ b/web/content/docs/workflows-common-mistakes.mdx @@ -105,7 +105,7 @@ Only these four verification types are valid: `exit_code`, `output_contains`, `f |---------|-----| | Relying on `agent-relay run` exit code for sub-workflow status | The runner exits 0 even when the inner workflow fails. Check `$?` AND grep `"Workflow status: failed"` from the log. | | Committing `.trajectories/`, `logs/`, or `package-lock.json` along with code | Stage only product files: `git add src/ test/` etc. Execution exhaust shouldn't be in PRs unless the user explicitly asks. | -| `--sync-code` leaves out untracked files | Cloud tarball contains git-tracked/staged files only. `git add` before `agent-relay cloud --sync-code`. | +| `--sync-code` leaves out changes I just made | Cloud tarball contains **committed** files only (HEAD). Staged and unstaged changes are both excluded. `git commit` before `agent-relay cloud --sync-code` — you don't need to push, but you do need to commit. | ## See also diff --git a/web/content/docs/workflows-introduction.mdx b/web/content/docs/workflows-introduction.mdx index 561823fdd..c973f6d87 100644 --- a/web/content/docs/workflows-introduction.mdx +++ b/web/content/docs/workflows-introduction.mdx @@ -99,13 +99,15 @@ See also: ```bash # Almost always pass --sync-code — without it, the sandbox pulls from -# origin and your local worktree (including new workflow files you haven't -# pushed yet) isn't there. +# origin and any local commits you haven't pushed aren't there. +# Note: --sync-code uploads HEAD, so you must COMMIT (not just stage) +# the files the run needs. git add workflows/my-workflow.ts +git commit -m "wip" agent-relay cloud run workflows/my-workflow.ts --sync-code ``` -The tarball covers every git-tracked or staged file. **Untracked files are silently excluded** — if you forget to `git add` a helper file, the cloud sandbox receives nothing at that path and the run fails "file not found." See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full flow. +The tarball is built from `HEAD` — only committed files go. **Staged, unstaged, and untracked files are all excluded.** If you forget to commit a helper file, the cloud sandbox starts without it and the run fails "file not found." See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full flow. ## Next diff --git a/web/content/docs/workflows-quickstart.mdx b/web/content/docs/workflows-quickstart.mdx index 30bda5418..49decbef8 100644 --- a/web/content/docs/workflows-quickstart.mdx +++ b/web/content/docs/workflows-quickstart.mdx @@ -139,9 +139,10 @@ You'll see the lead agent post a plan on the workflow channel, the worker implem The same workflow file runs unchanged in a Daytona sandbox: ```bash -# IMPORTANT: stage the workflow file first. `--sync-code` tarballs -# tracked + staged files — untracked ones are silently excluded. +# IMPORTANT: commit the workflow file first. `--sync-code` tarballs +# HEAD — staged-but-uncommitted and untracked files are BOTH excluded. git add workflows/add-greeting.ts +git commit -m "wip: greet workflow" agent-relay cloud run workflows/add-greeting.ts --sync-code # ...note the run ID printed, then: @@ -149,7 +150,7 @@ agent-relay cloud logs --follow agent-relay cloud sync # pull the produced diff back locally ``` -Almost every cloud run should pass `--sync-code` — it uploads your current worktree as the starting point. Without it, the sandbox starts from `origin/main` and your local changes (including the workflow file you just wrote) are missing. See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo). +Almost every cloud run should pass `--sync-code` — it uploads your current HEAD as the starting point. Without it, the sandbox starts from `origin/main` and your local commits (including the workflow file you just wrote) are missing. See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo). ## 6. Things to try next From 2e0aa46687cf50b5451f55d77188cf2d90773a27 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 13:54:27 +0200 Subject: [PATCH 05/12] docs: fix CLI install formatting in quickstart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CLI install section rendered the two install options as a bullet list with inline backtick code, which crammed the long curl URL onto the same visual line as the label and wrapped awkwardly. Switch to the same CodeGroup pattern used by the SDK install block higher on the page — npm vs install-script tabs, each with its own code block. Also adds a 'Start a local broker and spawn agents' intro line before the subsequent command block. --- web/content/docs/quickstart.mdx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/web/content/docs/quickstart.mdx b/web/content/docs/quickstart.mdx index 241c384b4..710a3ebaf 100644 --- a/web/content/docs/quickstart.mdx +++ b/web/content/docs/quickstart.mdx @@ -125,10 +125,22 @@ asyncio.run(main()) ## CLI -If you want to run Agent Relay directly from the terminal instead of embedding the SDK in an app, install the CLI globally and start a local relay session: +If you want to run Agent Relay directly from the terminal instead of embedding the SDK in an app, install the CLI globally and start a local relay session. + +Install `agent-relay`: + + +```bash npm +npm install -g agent-relay +``` +```bash install script +curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash +``` + + +Start a local broker and spawn agents: ```bash -npm i -g agent-relay agent-relay up agent-relay spawn planner claude "Break the work into steps" agent-relay spawn coder codex "Implement the approved plan" From df6aa5dba5ee33b078fc9c095192b274a974ff8e Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 13:55:11 +0200 Subject: [PATCH 06/12] docs: make install script the preferred CLI install option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder the CodeGroup so install script shows first (becomes the default tab), label it 'recommended', and add a one-sentence note explaining why — install script pulls the native broker binary for the user's platform alongside the CLI, while npm only gets the JavaScript entrypoint. --- web/content/docs/quickstart.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/content/docs/quickstart.mdx b/web/content/docs/quickstart.mdx index 710a3ebaf..efe4c6f27 100644 --- a/web/content/docs/quickstart.mdx +++ b/web/content/docs/quickstart.mdx @@ -127,15 +127,15 @@ asyncio.run(main()) If you want to run Agent Relay directly from the terminal instead of embedding the SDK in an app, install the CLI globally and start a local relay session. -Install `agent-relay`: +Install `agent-relay`. The install script is the preferred option — it pulls the native broker binary for your platform along with the CLI. +```bash install script (recommended) +curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash +``` ```bash npm npm install -g agent-relay ``` -```bash install script -curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash -``` Start a local broker and spawn agents: From dca27259c9dc5625d114ba7a92b0dcff165dc57c Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 13:59:33 +0200 Subject: [PATCH 07/12] docs: add headless-orchestrator skill to quickstart Let readers discover the pattern where a host agent (Claude, Codex, etc.) spawns and manages its own team of sub-agents from inside a session, via the running-headless-orchestrator skill. Two install paths shown (prpm as recommended with the --as host[,host] variant, plus the direct skills-add command as an alternative), and a one-line prompt example so readers see what to tell the host once the skill is loaded. --- web/content/docs/quickstart.mdx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/web/content/docs/quickstart.mdx b/web/content/docs/quickstart.mdx index efe4c6f27..27e99a516 100644 --- a/web/content/docs/quickstart.mdx +++ b/web/content/docs/quickstart.mdx @@ -149,3 +149,26 @@ agent-relay who ``` See [CLI Overview](/docs/cli-overview) for the full command surface and [Broker Lifecycle](/docs/cli-broker-lifecycle) for running the local broker. + +### Let another agent orchestrate the team + +If you want a host agent (Claude, Codex, or any CLI) to autonomously spawn and coordinate sub-agents from inside its own session, install the `running-headless-orchestrator` skill. It wires the `agent-relay spawn` / `agent-relay dm` commands into the host's tool surface so the host can run the team without leaving its session. + + +```bash prpm (recommended) +# Install for a specific host +npx prpm install @agent-relay/running-headless-orchestrator --as claude + +# Or install for multiple hosts at once +npx prpm install @agent-relay/running-headless-orchestrator --as claude,codex +``` +```bash skills +npx skills add https://github.com/agentworkforce/skills --skill running-headless-orchestrator +``` + + +Once installed, you can prompt the host like: + +> "Spawn a claude agent called `reviewer`, DM it the diff, and wait for its verdict before you proceed." + +The host uses the skill's tools to run `agent-relay spawn reviewer claude ...`, send DMs via `agent-relay dm`, and read replies — all without you touching the shell. From f8b91558271b22f750eaf563ea78425eb4e71aa3 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 14:05:47 +0200 Subject: [PATCH 08/12] =?UTF-8?q?docs:=20correct=20workflow=20intro=20?= =?UTF-8?q?=E2=80=94=2025=20patterns,=204=20step=20types,=20primitives=20f?= =?UTF-8?q?raming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'A workflow is a DAG of steps' was only true for the default pattern. The SDK ships 25 swarm patterns (debate, mesh, review-loop, saga, circuit-breaker, swarm, etc.) — many are NOT DAGs. Replaced with a grouped table (DAG/trees, hubs, iterative loops, non-fixed, selection, error-handling) plus a note that hub patterns auto-harden with a supervisor while plain dag/pipeline don't. Step types were understated too. WorkflowStepType is 'agent' | 'deterministic' | 'worktree' | 'integration'. Added worktree + integration. Also reframed the top of the page to make two things explicit: 1. Workflows compose the primitives (channels, DMs, threads, files, scheduling) — they don't replace them. Links to each primitive page for discoverability. 2. Workflows are repeatable — the point is that you write them once and run them N times, share them, schedule them, resume from partial failure. Added this as a first-class 'why' bullet and a paragraph about orchestration at scale. --- web/content/docs/workflows-introduction.mdx | 50 +++++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/web/content/docs/workflows-introduction.mdx b/web/content/docs/workflows-introduction.mdx index c973f6d87..f31b4d552 100644 --- a/web/content/docs/workflows-introduction.mdx +++ b/web/content/docs/workflows-introduction.mdx @@ -3,27 +3,59 @@ title: 'Workflows' description: 'Orchestrate multi-step, multi-agent execution across Relay workers. Write once, run locally or in the cloud.' --- -Workflows are how Relay runs real work. A workflow is a DAG of steps — some are deterministic shell commands, others spawn agents that edit code, run tests, and push PRs. You write them in TypeScript, Python, or YAML; you run them with `agent-relay run`. +Workflows are how Relay runs real work. A workflow **composes the Relay primitives** — [channels](/docs/channels), [DMs](/docs/dms), [threads](/docs/threads), [file sharing](/docs/file-sharing), [scheduling](/docs/scheduling) — into a repeatable, multi-step, multi-agent execution plan. You write them in TypeScript, Python, or YAML; you run them with `agent-relay run`. -## When to use a workflow +## Why workflows (vs. just spawning an agent) -Use workflows when you need: +Spawning a single agent is fine for a one-off. Workflows earn their weight when you need: -- **Multiple agents coordinating** — a lead that plans, workers that implement, a reviewer that gates. +- **Multiple agents coordinating** — a lead that plans, workers that implement, a reviewer that gates. Wired together over channels + DMs. - **Cross-repo changes** — edit two repos as part of one unit of work, open linked PRs. - **Verification gates** — tests must pass before the commit. Build must pass before the push. Regressions must be zero before the PR opens. - **Parallelism with barriers** — fan out independent work, wait at the right moments, move on. +- **Repeatability** — this is the point. A workflow file is an artifact. Commit it, re-run it next week, share it with the team, let a scheduler fire it nightly, resume it from a partial failure. Same shape every time. -If you just want to spawn one agent and talk to it, [spawning an agent](/docs/spawning-an-agent) is simpler. Workflows earn their weight when you have steps that depend on each other. +Workflows turn ad-hoc "I typed prompts into three Claude windows" coordination into something you can automate, review, and repeat. That's the whole point. + +## How workflows use primitives + +A workflow doesn't replace the [primitives](/docs/sending-messages) — it composes them: + +- **Channels** are where agents in a workflow coordinate. Each workflow opens a dedicated channel (`.channel('wf-my-feature')`) so the team has a clean, isolated room. +- **DMs** let a lead address a specific worker without broadcasting. +- **Threads** keep sub-conversations organized when a workflow step has its own back-and-forth. +- **File sharing** is how agents hand artifacts to each other (diff payloads, review bundles, etc.) when they span processes. +- **Scheduling** turns a workflow into a cron job via [RelayCron](/docs/scheduling) — the same workflow file runs on a schedule, unchanged. + +If a primitive gives you atomic agent-to-agent capabilities, a workflow gives you the orchestration that makes them useful at scale. ## Mental model -A workflow is a typed DAG. Steps come in two flavors: +A workflow is a named, typed orchestration of **steps** run by the Relay runner. Step types: + +- **Agent** — spawn a CLI (`claude`, `codex`, `gemini`, etc.) with a task prompt. Agent reads the prompt, edits files, writes tests, posts on channels. +- **Deterministic** — a shell command. Reads, builds, tests, verifications, commits, pushes. No LLM. +- **Worktree** — create or clean up a git worktree for isolated work. +- **Integration** — call a registered external integration (e.g. a job runner) as a typed step. + +Steps declare dependencies (`dependsOn`). Steps with no shared dependency run in parallel up to `maxConcurrency`. + +### Patterns + +The default topology is a **DAG** (`.pattern('dag')`), but the SDK ships 25 swarm patterns. Pick the one that matches how work actually flows between agents: + +| Shape | Patterns | +|---|---| +| **DAG / trees** | `dag`, `pipeline`, `fan-out`, `scatter-gather`, `map-reduce`, `hierarchical`, `cascade` | +| **Hub + workers** | `hub-spoke`, `supervisor`, `handoff` | +| **Iterative loops** | `debate`, `reflection`, `review-loop`, `red-team`, `verifier` | +| **Non-fixed topology** | `mesh`, `swarm`, `blackboard` | +| **Selection / competition** | `auction`, `competitive`, `consensus` | +| **Error handling** | `circuit-breaker`, `saga`, `escalation` | -- **Agent steps** — spawn a CLI (`claude`, `codex`, `gemini`, etc.) with a task prompt. Agent reads the prompt and whatever context the workflow has injected, edits files, writes tests, commits. -- **Deterministic steps** — shell commands. Reads, builds, tests, verifications, commits, pushes. No LLM. +When you use a hub pattern (`supervisor`, `hub-spoke`, `fan-out`), the runner auto-hardens it — a supervisor gets spawned to monitor workers and issue `OWNER_DECISION` if they stall. Plain `dag` and `pipeline` don't auto-harden; you wire supervision yourself if you want it. -Steps declare dependencies (`dependsOn`). Steps with no shared dependency run in parallel. The runner enforces this topology, captures each step's stdout/stderr, and feeds one step's output into a downstream step's prompt via `{{steps.X.output}}`. +See [Patterns](/docs/workflows-patterns) for the canonical shapes most workflows actually use. ## A minimal example From a61c6411fe2f84079b94e3cbd8b71822fee175f0 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 14:10:20 +0200 Subject: [PATCH 09/12] docs: github-primitive as workflow-specific + add workflow-authoring skills block Two corrections + one addition: 1. GitHub primitive moved from top-level Primitives to Workflows. It's a workflow-specific primitive (integration step shaped for workflow(), not a general Relay primitive like channels/DMs), so it belongs alongside Patterns and Setup helpers under Workflows. 2. workflows-introduction 'how workflows use primitives' section separates top-level Relay primitives (channels/DMs/threads/files/ scheduling) from workflow-specific primitives (GitHub). One trailing paragraph introduces the latter category. 3. New 'Let an agent write your workflows' section at the bottom of workflows-introduction. Same install-box pattern as the headless orchestrator block in quickstart: - prpm (recommended, with --as claude[,codex] variants) - skills add (alternative) Mentions both writing-agent-relay-workflows (core authoring) and relay-80-100-workflow (validation-gate layer) with a one-line role description for each + two example prompts. --- web/content/docs/github-primitive.mdx | 120 ++++++++++++++++++++ web/content/docs/workflows-introduction.mdx | 42 ++++++- web/lib/docs-nav.ts | 1 + 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 web/content/docs/github-primitive.mdx diff --git a/web/content/docs/github-primitive.mdx b/web/content/docs/github-primitive.mdx new file mode 100644 index 000000000..f400855f1 --- /dev/null +++ b/web/content/docs/github-primitive.mdx @@ -0,0 +1,120 @@ +--- +title: 'GitHub primitive' +description: 'Workflow-specific primitive: a typed GitHub client + workflow integration step. Runs through the local `gh` CLI or a cloud proxy.' +--- + +The `@agent-relay/github-primitive` package is a **workflow-specific primitive** — it's not a general Relay primitive like channels or DMs, it's specifically shaped to drop into a `workflow()` as an integration step. It gives workflows a first-class GitHub surface — create issues, open PRs, read files, merge branches — with one call site that works the same locally via `gh` or in cloud via Nango / relay-cloud. + +Use this when your workflow needs to talk to GitHub, rather than having an agent shell out to `gh` and parse text. + +## Install + +```bash +npm install @agent-relay/github-primitive +``` + +## Runtime selection + +The primitive auto-picks the right backend for the environment it's running in: + +| Mode | Triggered when | +|---|---| +| `local` (via `gh` CLI) | `gh auth status` succeeds and no cloud creds are set | +| `cloud` (via Nango) | `NANGO_SECRET_KEY` + `NANGO_GITHUB_CONNECTION_ID` + `NANGO_GITHUB_PROVIDER_CONFIG_KEY` are present | +| `cloud` (via relay-cloud) | `RELAY_CLOUD_API_URL` + `RELAY_CLOUD_API_TOKEN` + `WORKSPACE_ID` are present (fallback when Nango absent) | + +Pick the default (`runtime: 'auto'`) unless you need to pin one for testing. + +## Client usage + +```ts +import { GitHubClient } from '@agent-relay/github-primitive'; + +const github = await GitHubClient.create({ runtime: 'auto' }); + +const repo = await github.getRepo('AgentWorkforce', 'relay'); +console.log(repo.defaultBranch); + +const pr = await github.createPR({ + repo: 'AgentWorkforce/relay', + head: 'feat/my-change', + base: 'main', + title: 'Ship the thing', + body: 'Closes #123', +}); +``` + +### Supported actions + +- **Repositories** — `listRepos`, `getRepo` +- **Issues** — `listIssues`, `createIssue`, `updateIssue`, `closeIssue` +- **Pull requests** — `listPRs`, `getPR`, `createPR`, `updatePR`, `mergePR` +- **Files** — `listFiles`, `readFile`, `createFile`, `updateFile`, `deleteFile` +- **Branches + commits** — `listBranches`, `createBranch`, `listCommits`, `createCommit` +- **Identity** — `getUser`, `listOrganizations` + +All methods return typed responses; errors are thrown rather than returned so you can `try/catch` normally. + +## Use inside a workflow + +The primitive ships a workflow integration step for declarative use inside a `workflow()`: + +```ts +import { workflow } from '@agent-relay/sdk/workflows'; +import { createGitHubStep } from '@agent-relay/github-primitive/workflow-step'; + +await workflow('ship-readme') + .agent('writer', { cli: 'claude' }) + + .step('read-readme', createGitHubStep({ + action: 'readFile', + repo: 'AgentWorkforce/relay', + params: { path: 'README.md' }, + output: { mode: 'data', format: 'text' }, + })) + + .step('edit', { + agent: 'writer', + dependsOn: ['read-readme'], + task: `Current README:\n{{steps.read-readme.output}}\n\nClean up the intro.`, + }) + + .step('open-pr', createGitHubStep({ + action: 'createPR', + repo: 'AgentWorkforce/relay', + params: { + head: 'docs/readme-cleanup', + base: 'main', + title: 'docs: clean up README intro', + body: 'Lightly edited for clarity.', + }, + })) + + .run({ cwd: process.cwd() }); +``` + +The step is `type: 'integration'` under the hood — the runner scheduling and verification applies the same way as any other step. + +## Multi-tenant cloud routing + +Every cloud workspace can have its own GitHub App install — one Nango connection per tenant. `createGitHubStep` accepts a per-step `config` field so a single workflow can route different actions through different connections. + +```ts +createGitHubStep({ + action: 'createPR', + repo: 'AgentWorkforce/cloud', + params: { title, head, base, body }, + config: await githubConfigForRepo({ + repo: 'AgentWorkforce/cloud', + workspaceId: process.env.RELAY_WORKSPACE_ID, + }), +}); +``` + +The primitive itself stays tenant-unaware — it takes a `GitHubRuntimeConfig` and does what it's told. Tenant lookup lives in your app (typically a `connection-resolver` helper). Adding a new GitHub App install is a config row, not a code change. + +## See also + +- [Workflows](/docs/workflows-introduction) — where this primitive shines. +- [File sharing](/docs/file-sharing) — pair with GitHub when agents pass large artifacts. +- [Authentication](/docs/authentication) — credentials model for the cloud runtime modes. diff --git a/web/content/docs/workflows-introduction.mdx b/web/content/docs/workflows-introduction.mdx index f31b4d552..87edf85c5 100644 --- a/web/content/docs/workflows-introduction.mdx +++ b/web/content/docs/workflows-introduction.mdx @@ -21,14 +21,16 @@ Workflows turn ad-hoc "I typed prompts into three Claude windows" coordination i A workflow doesn't replace the [primitives](/docs/sending-messages) — it composes them: -- **Channels** are where agents in a workflow coordinate. Each workflow opens a dedicated channel (`.channel('wf-my-feature')`) so the team has a clean, isolated room. -- **DMs** let a lead address a specific worker without broadcasting. -- **Threads** keep sub-conversations organized when a workflow step has its own back-and-forth. -- **File sharing** is how agents hand artifacts to each other (diff payloads, review bundles, etc.) when they span processes. -- **Scheduling** turns a workflow into a cron job via [RelayCron](/docs/scheduling) — the same workflow file runs on a schedule, unchanged. +- **[Channels](/docs/channels)** are where agents in a workflow coordinate. Each workflow opens a dedicated channel (`.channel('wf-my-feature')`) so the team has a clean, isolated room. +- **[DMs](/docs/dms)** let a lead address a specific worker without broadcasting. +- **[Threads](/docs/threads)** keep sub-conversations organized when a workflow step has its own back-and-forth. +- **[File sharing](/docs/file-sharing)** is how agents hand artifacts to each other (diff payloads, review bundles, etc.) when they span processes. +- **[Scheduling](/docs/scheduling)** turns a workflow into a cron job via RelayCron — the same workflow file runs on a schedule, unchanged. If a primitive gives you atomic agent-to-agent capabilities, a workflow gives you the orchestration that makes them useful at scale. +Workflows also ship their own **workflow-specific primitives** — e.g. the [GitHub primitive](/docs/github-primitive) — integration steps shaped specifically for `workflow()`. Most production workflows use it for PR creation, file reads, issue updates. + ## Mental model A workflow is a named, typed orchestration of **steps** run by the Relay runner. Step types: @@ -141,6 +143,36 @@ agent-relay cloud run workflows/my-workflow.ts --sync-code The tarball is built from `HEAD` — only committed files go. **Staged, unstaged, and untracked files are all excluded.** If you forget to commit a helper file, the cloud sandbox starts without it and the run fails "file not found." See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full flow. +## Let an agent write your workflows + +If you want a host agent (Claude, Codex, etc.) to autonomously author workflow files — follow the canonical patterns, avoid the common pitfalls, get features to 100% instead of 80% — install the workflow-authoring skills. The agent reads them alongside your prompt and writes workflows that match repo conventions. + + +```bash prpm (recommended) +# Install for a specific host +npx prpm install @agent-relay/writing-agent-relay-workflows --as claude +npx prpm install @agent-relay/relay-80-100-workflow --as claude + +# Or install both for multiple hosts at once +npx prpm install @agent-relay/writing-agent-relay-workflows @agent-relay/relay-80-100-workflow --as claude,codex +``` +```bash skills +npx skills add https://github.com/agentworkforce/skills --skill writing-agent-relay-workflows +npx skills add https://github.com/agentworkforce/skills --skill relay-80-100-workflow +``` + + +**The two skills:** + +- **`writing-agent-relay-workflows`** — the core authoring guide. Failure prevention rules, step sizing, pattern selection, parallelism/waves, common mistakes, multi-file edit patterns, DAG deadlock anti-patterns, lead+worker teams. +- **`relay-80-100-workflow`** — the validation-gate layer. Test-fix-rerun loop, verify gates after every edit, PGlite for in-memory Postgres testing, regression-check patterns. Takes features from "compiles" to "tested and proven-working before commit." + +Once installed, prompts like these just work: + +> "Write a workflow that adds a `pending` status to `src/types.ts` and the service layer, with tests. Use the 80-to-100 pattern so the commit only lands if tests pass." + +> "Split the migration work into two parallel waves. Cross-repo: sage + cloud. Open linked PRs at the end." + ## Next - Start with the [Quickstart](/docs/workflows-quickstart) for a working example you can run in 5 minutes. diff --git a/web/lib/docs-nav.ts b/web/lib/docs-nav.ts index 8d89395f1..cc8f2ae85 100644 --- a/web/lib/docs-nav.ts +++ b/web/lib/docs-nav.ts @@ -54,6 +54,7 @@ export const docsNav: NavGroup[] = [ { title: 'Builder API', slug: 'reference-workflows' }, { title: 'Patterns', slug: 'workflows-patterns' }, { title: 'Setup helpers', slug: 'workflows-setup-helpers' }, + { title: 'GitHub primitive', slug: 'github-primitive' }, { title: 'Common mistakes', slug: 'workflows-common-mistakes' }, { title: 'Run from CLI', slug: 'cli-workflows' }, ], From 4fbaa651a03eac11dc3603ce31acba278a2632d1 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 14:12:08 +0200 Subject: [PATCH 10/12] docs(github-primitive): focus on workflow usage, note SDK bundling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropped the standalone GitHubClient client-only usage block — the primitive is used WITHIN a workflow, not as a standalone imperative client. Kept the workflow integration step (createGitHubStep) as the one canonical usage example. Updated intro to state the primitive is bundled with the SDK (no separate install), and switched the import path to the bundled subpath `@agent-relay/sdk/github`. This is the target state — the bundling lands in a sibling SDK PR so this doc is accurate on merge. --- web/content/docs/github-primitive.mdx | 91 ++++++++++----------------- 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/web/content/docs/github-primitive.mdx b/web/content/docs/github-primitive.mdx index f400855f1..879657236 100644 --- a/web/content/docs/github-primitive.mdx +++ b/web/content/docs/github-primitive.mdx @@ -1,67 +1,19 @@ --- title: 'GitHub primitive' -description: 'Workflow-specific primitive: a typed GitHub client + workflow integration step. Runs through the local `gh` CLI or a cloud proxy.' +description: 'Workflow-specific primitive: a typed GitHub integration step. Runs through the local `gh` CLI or a cloud proxy.' --- -The `@agent-relay/github-primitive` package is a **workflow-specific primitive** — it's not a general Relay primitive like channels or DMs, it's specifically shaped to drop into a `workflow()` as an integration step. It gives workflows a first-class GitHub surface — create issues, open PRs, read files, merge branches — with one call site that works the same locally via `gh` or in cloud via Nango / relay-cloud. +The GitHub primitive is a **workflow-specific primitive** — an integration step shaped for `workflow()`. It gives a workflow a typed GitHub surface (create issues, open PRs, read files, merge branches) with one call site that works the same locally via `gh` or in cloud via Nango / relay-cloud. -Use this when your workflow needs to talk to GitHub, rather than having an agent shell out to `gh` and parse text. +It's **bundled with the SDK** — no separate install. If you've run `npm install @agent-relay/sdk`, you already have it. -## Install +## Usage inside a workflow -```bash -npm install @agent-relay/github-primitive -``` - -## Runtime selection - -The primitive auto-picks the right backend for the environment it's running in: - -| Mode | Triggered when | -|---|---| -| `local` (via `gh` CLI) | `gh auth status` succeeds and no cloud creds are set | -| `cloud` (via Nango) | `NANGO_SECRET_KEY` + `NANGO_GITHUB_CONNECTION_ID` + `NANGO_GITHUB_PROVIDER_CONFIG_KEY` are present | -| `cloud` (via relay-cloud) | `RELAY_CLOUD_API_URL` + `RELAY_CLOUD_API_TOKEN` + `WORKSPACE_ID` are present (fallback when Nango absent) | - -Pick the default (`runtime: 'auto'`) unless you need to pin one for testing. - -## Client usage - -```ts -import { GitHubClient } from '@agent-relay/github-primitive'; - -const github = await GitHubClient.create({ runtime: 'auto' }); - -const repo = await github.getRepo('AgentWorkforce', 'relay'); -console.log(repo.defaultBranch); - -const pr = await github.createPR({ - repo: 'AgentWorkforce/relay', - head: 'feat/my-change', - base: 'main', - title: 'Ship the thing', - body: 'Closes #123', -}); -``` - -### Supported actions - -- **Repositories** — `listRepos`, `getRepo` -- **Issues** — `listIssues`, `createIssue`, `updateIssue`, `closeIssue` -- **Pull requests** — `listPRs`, `getPR`, `createPR`, `updatePR`, `mergePR` -- **Files** — `listFiles`, `readFile`, `createFile`, `updateFile`, `deleteFile` -- **Branches + commits** — `listBranches`, `createBranch`, `listCommits`, `createCommit` -- **Identity** — `getUser`, `listOrganizations` - -All methods return typed responses; errors are thrown rather than returned so you can `try/catch` normally. - -## Use inside a workflow - -The primitive ships a workflow integration step for declarative use inside a `workflow()`: +Import `createGitHubStep` from the SDK's `/github` subpath and drop it in anywhere you'd use a regular `.step()`: ```ts import { workflow } from '@agent-relay/sdk/workflows'; -import { createGitHubStep } from '@agent-relay/github-primitive/workflow-step'; +import { createGitHubStep } from '@agent-relay/sdk/github'; await workflow('ship-readme') .agent('writer', { cli: 'claude' }) @@ -93,11 +45,34 @@ await workflow('ship-readme') .run({ cwd: process.cwd() }); ``` -The step is `type: 'integration'` under the hood — the runner scheduling and verification applies the same way as any other step. +Under the hood each `createGitHubStep(...)` call produces a `type: 'integration'` step — the runner schedules it, applies verification, and captures output the same way as any other step. + +## Supported actions + +- **Repositories** — `listRepos`, `getRepo` +- **Issues** — `listIssues`, `createIssue`, `updateIssue`, `closeIssue` +- **Pull requests** — `listPRs`, `getPR`, `createPR`, `updatePR`, `mergePR` +- **Files** — `listFiles`, `readFile`, `createFile`, `updateFile`, `deleteFile` +- **Branches + commits** — `listBranches`, `createBranch`, `listCommits`, `createCommit` +- **Identity** — `getUser`, `listOrganizations` + +All actions work through the `action` + `params` shape. Outputs are typed — downstream steps can use `{{steps..output}}` to chain values through the workflow. + +## Runtime selection + +The primitive auto-picks the right backend for the environment it's running in: + +| Mode | Triggered when | +|---|---| +| `local` (via `gh` CLI) | `gh auth status` succeeds and no cloud creds are set | +| `cloud` (via Nango) | `NANGO_SECRET_KEY` + `NANGO_GITHUB_CONNECTION_ID` + `NANGO_GITHUB_PROVIDER_CONFIG_KEY` are present | +| `cloud` (via relay-cloud) | `RELAY_CLOUD_API_URL` + `RELAY_CLOUD_API_TOKEN` + `WORKSPACE_ID` are present (fallback when Nango absent) | + +Pick the default (`runtime: 'auto'`) unless you need to pin one for testing. You can also set `runtime` per step via the `config` field — useful when the same workflow creates PRs across multiple tenants with different GitHub App installs. ## Multi-tenant cloud routing -Every cloud workspace can have its own GitHub App install — one Nango connection per tenant. `createGitHubStep` accepts a per-step `config` field so a single workflow can route different actions through different connections. +Every cloud workspace can have its own GitHub App install — one Nango connection per tenant. `createGitHubStep` accepts a per-step `config` field so a single workflow can route different actions through different connections: ```ts createGitHubStep({ @@ -115,6 +90,6 @@ The primitive itself stays tenant-unaware — it takes a `GitHubRuntimeConfig` a ## See also -- [Workflows](/docs/workflows-introduction) — where this primitive shines. -- [File sharing](/docs/file-sharing) — pair with GitHub when agents pass large artifacts. +- [Workflows introduction](/docs/workflows-introduction) — where this primitive shines. +- [Patterns](/docs/workflows-patterns) — canonical workflow shapes that commonly use GitHub steps (PR review loops, multi-repo shipping, etc.). - [Authentication](/docs/authentication) — credentials model for the cloud runtime modes. From 4e737f778e35325ce86cac90d9bf5c210dce8433 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 16:00:26 +0200 Subject: [PATCH 11/12] docs: correct --sync-code semantics (git ls-files + working tree, not HEAD) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous iterations overshot in both directions — first said staging was enough, then corrected to 'only committed.' Actual ground truth from reading relay's createTarball (packages/cloud/src/workflows.ts): 1. git ls-files -z -> tracked paths 2. tar.create() reads working-tree contents of those paths So: - Committed files -> yes - Committed + modified -> yes (working-tree contents) - Staged new file (added, not committed) -> yes - Untracked (never added) -> NO - Gitignored -> NO `git add` is enough. Commit not required. No `git clone` fallback if --sync-code is omitted — sandbox starts with empty $HOME. Affected pages: cli-cloud-commands (full explainer), workflows- introduction (cloud exec block), workflows-quickstart (Run-in-cloud section), workflows-common-mistakes (one-liner). All now consistent. --- web/content/docs/cli-cloud-commands.mdx | 33 ++++++++++--------- .../docs/workflows-common-mistakes.mdx | 2 +- web/content/docs/workflows-introduction.mdx | 12 +++---- web/content/docs/workflows-quickstart.mdx | 8 ++--- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/web/content/docs/cli-cloud-commands.mdx b/web/content/docs/cli-cloud-commands.mdx index c21349e15..ce4d24a9c 100644 --- a/web/content/docs/cli-cloud-commands.mdx +++ b/web/content/docs/cli-cloud-commands.mdx @@ -49,29 +49,33 @@ agent-relay cloud sync ## `--sync-code`: uploading your local repo -`--sync-code` tarballs your current working copy and ships it to the cloud sandbox as the starting point for the run. Without it, the sandbox pulls from the remote tracked branch (whatever's on `origin`) — which means local changes, uncommitted edits, and untracked helper files are missing. +`--sync-code` tarballs your current working copy and ships it to the cloud sandbox as the starting point for the run. Without it, the sandbox starts with **no code on disk** — it doesn't clone from `origin`, it just has an empty `$HOME`. Any workflow that reads your source needs `--sync-code`. -**In almost every case, you want `--sync-code`.** Running a workflow against whatever is on `origin/main` is rarely what you mean; you almost always want to test against the exact state of your worktree. +**In almost every case, you want `--sync-code`.** Running a workflow against an empty sandbox is rarely what you mean; you almost always want your local worktree state there. ### What gets uploaded -**Only committed files.** The tarball is built from `HEAD` — anything that isn't in a commit on your current branch is excluded: +The tarball is built from `git ls-files` — the **tracked** paths — and `tar` reads their current working-tree contents. No `git clone` anywhere. -- ✅ Files committed on the current branch (even if not pushed) -- ❌ **Staged-but-not-committed changes are excluded** -- ❌ Uncommitted working-tree edits are excluded -- ❌ Untracked files are excluded +| State | Synced? | +|---|---| +| Committed, unmodified | ✅ Working-tree version | +| Committed, then modified | ✅ Working-tree version (your edits go too) | +| Committed, then modified + `git add`ed | ✅ Working-tree version | +| New file + `git add`ed (not committed) | ✅ — once added, the file is tracked | +| New file, never added | ❌ Untracked → excluded | +| `.gitignore`d path | ❌ Excluded | -This catches new workflow authors regularly. If you just created `workflows/my-new.ts` or edited an existing file but haven't committed, the cloud sandbox sees the pre-edit state and the run fails with "file not found" or unexpected old behavior. +**Rule of thumb:** `git add` whatever the run needs. Commit is NOT required — staging is enough because `git ls-files` returns indexed paths. You don't need to push either. -**Rule of thumb:** `git commit` before `agent-relay cloud run --sync-code`. You don't need to push — local commits are enough, but staging isn't. +If you're not in a git repo at all, there's a fallback: the packer walks the filesystem and uses `.gitignore` as the exclude list. ### When NOT to use `--sync-code` Rare but real: -- You want to run exactly what's on `origin/main` (e.g. reproducing a CI failure against a known-good commit). -- You're running a workflow that explicitly checks out a branch in its `setup-branch` step and doesn't care about local state. +- You're running a cloud-managed workflow that doesn't touch local code (a fully `config`-typed workflow). +- You've set up a workflow whose `setup-branch` step explicitly `git clone`s something and doesn't care about local state. ### Typical flow @@ -79,12 +83,11 @@ Rare but real: # Edit your workflow locally vim workflows/fix-bug.ts -# Commit it (and anything else the workflow needs). --sync-code only -# picks up committed files, so staging alone is NOT enough. +# Stage everything the run needs — `git add` is enough; commit is optional. +# Untracked files would be silently excluded otherwise. git add workflows/fix-bug.ts -git commit -m "wip: new workflow" -# Ship HEAD to the cloud sandbox and run +# Ship your working tree to the cloud sandbox and run agent-relay cloud run workflows/fix-bug.ts --sync-code # note the run ID printed... diff --git a/web/content/docs/workflows-common-mistakes.mdx b/web/content/docs/workflows-common-mistakes.mdx index 8c9414baf..d081e56a6 100644 --- a/web/content/docs/workflows-common-mistakes.mdx +++ b/web/content/docs/workflows-common-mistakes.mdx @@ -105,7 +105,7 @@ Only these four verification types are valid: `exit_code`, `output_contains`, `f |---------|-----| | Relying on `agent-relay run` exit code for sub-workflow status | The runner exits 0 even when the inner workflow fails. Check `$?` AND grep `"Workflow status: failed"` from the log. | | Committing `.trajectories/`, `logs/`, or `package-lock.json` along with code | Stage only product files: `git add src/ test/` etc. Execution exhaust shouldn't be in PRs unless the user explicitly asks. | -| `--sync-code` leaves out changes I just made | Cloud tarball contains **committed** files only (HEAD). Staged and unstaged changes are both excluded. `git commit` before `agent-relay cloud --sync-code` — you don't need to push, but you do need to commit. | +| `--sync-code` leaves out a file I just created | Cloud tarball is built from `git ls-files` (tracked paths only) + working-tree contents. Untracked files are silently excluded. `git add` the file before `agent-relay cloud run --sync-code` — staging is enough, commit is optional. | ## See also diff --git a/web/content/docs/workflows-introduction.mdx b/web/content/docs/workflows-introduction.mdx index 87edf85c5..e60f6def2 100644 --- a/web/content/docs/workflows-introduction.mdx +++ b/web/content/docs/workflows-introduction.mdx @@ -132,16 +132,16 @@ See also: **Cloud execution.** Submit the same workflow file to `agent-relay cloud` to run it in a Daytona sandbox. Files get committed + pushed; PRs open automatically if your master workflow is set up for it. ```bash -# Almost always pass --sync-code — without it, the sandbox pulls from -# origin and any local commits you haven't pushed aren't there. -# Note: --sync-code uploads HEAD, so you must COMMIT (not just stage) -# the files the run needs. +# Almost always pass --sync-code — without it, the sandbox has NO +# code at all (there's no fallback clone from origin). The tarball +# is built from `git ls-files` + working-tree contents: tracked +# files (including staged-but-uncommitted) are synced; untracked +# files are NOT. `git add` is enough — commit is optional. git add workflows/my-workflow.ts -git commit -m "wip" agent-relay cloud run workflows/my-workflow.ts --sync-code ``` -The tarball is built from `HEAD` — only committed files go. **Staged, unstaged, and untracked files are all excluded.** If you forget to commit a helper file, the cloud sandbox starts without it and the run fails "file not found." See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full flow. +See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full sync semantics (what's in, what's out, edge cases). ## Let an agent write your workflows diff --git a/web/content/docs/workflows-quickstart.mdx b/web/content/docs/workflows-quickstart.mdx index 49decbef8..102e2c11c 100644 --- a/web/content/docs/workflows-quickstart.mdx +++ b/web/content/docs/workflows-quickstart.mdx @@ -139,10 +139,10 @@ You'll see the lead agent post a plan on the workflow channel, the worker implem The same workflow file runs unchanged in a Daytona sandbox: ```bash -# IMPORTANT: commit the workflow file first. `--sync-code` tarballs -# HEAD — staged-but-uncommitted and untracked files are BOTH excluded. +# IMPORTANT: `git add` the workflow file first. `--sync-code` tarballs +# git-tracked paths with working-tree contents. Untracked files are +# silently excluded. `git add` is enough — commit is optional. git add workflows/add-greeting.ts -git commit -m "wip: greet workflow" agent-relay cloud run workflows/add-greeting.ts --sync-code # ...note the run ID printed, then: @@ -150,7 +150,7 @@ agent-relay cloud logs --follow agent-relay cloud sync # pull the produced diff back locally ``` -Almost every cloud run should pass `--sync-code` — it uploads your current HEAD as the starting point. Without it, the sandbox starts from `origin/main` and your local commits (including the workflow file you just wrote) are missing. See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo). +Almost every cloud run should pass `--sync-code` — without it the sandbox has no code at all (there's no fallback clone from `origin`). See [Cloud commands → `--sync-code`](/docs/cli-cloud-commands#-sync-code-uploading-your-local-repo) for the full sync semantics. ## 6. Things to try next From 76b24bc278fabfe8513393cf2a0ca7ca65a79a46 Mon Sep 17 00:00:00 2001 From: Multi-Repo Pushback Bot Date: Fri, 24 Apr 2026 19:58:23 +0200 Subject: [PATCH 12/12] docs(quickstart): clarify headless-orchestrator example prompt The example prompt implied the skill would auto-invoke. Make the skill name explicit so users see how to reference it in their own prompts. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/content/docs/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/content/docs/quickstart.mdx b/web/content/docs/quickstart.mdx index 27e99a516..160c183a5 100644 --- a/web/content/docs/quickstart.mdx +++ b/web/content/docs/quickstart.mdx @@ -169,6 +169,6 @@ npx skills add https://github.com/agentworkforce/skills --skill running-headless Once installed, you can prompt the host like: -> "Spawn a claude agent called `reviewer`, DM it the diff, and wait for its verdict before you proceed." +> "Use the running-headless-orchestrator skill and spawn a claude agent called `reviewer`, DM it instructions, and wait for its verdict before you proceed." The host uses the skill's tools to run `agent-relay spawn reviewer claude ...`, send DMs via `agent-relay dm`, and read replies — all without you touching the shell.