Skip to content

Commit eadbd50

Browse files
authored
docs: clarify workflow PR ownership (#29)
1 parent de689a2 commit eadbd50

2 files changed

Lines changed: 41 additions & 30 deletions

File tree

prpm.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agent-workforce-skills",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Skills for multi-agent coordination - swarm patterns, workflow building, relay usage, and headless orchestration",
55
"author": "khaliqgant",
66
"organization": "agent-relay",
@@ -28,7 +28,7 @@
2828
},
2929
{
3030
"name": "writing-agent-relay-workflows",
31-
"version": "1.6.1",
31+
"version": "1.6.2",
3232
"description": "Use when building multi-agent workflows with the relay broker-sdk - covers conversation-shape vs pipeline-shape coordination, WorkflowBuilder API, DAG step dependencies, agent definitions, output chaining via {{steps.X.output}}, verification gates, evidence-based completion, channels, swarm patterns, chat-native coordination recipes (Q/A, broadcast-ack, peer review, standup, hand-off), error handling, event listeners, step sizing, lead+workers team pattern, and parallel wave planning",
3333
"format": "claude",
3434
"subtype": "skill",

skills/writing-agent-relay-workflows/SKILL.md

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,25 @@ runWorkflow().catch((error) => {
280280

281281
Do not end workflow files with bare top-level `await workflow(...).run(...)`.
282282

283+
### 1b. Make commit and PR boundaries explicit
284+
285+
Workflows do **not** get a PR for free just because they pass validation. If the intended deliverable is a branch, commit, push, or GitHub PR, the workflow itself must own that boundary explicitly and document the expected file scope.
286+
287+
Use this pattern only when the workflow is supposed to own repository delivery:
288+
289+
1. Preflight the git state and fail on unexpected staged changes.
290+
2. Create or verify the intended branch.
291+
3. Run implementation, review, soft validation, fix, and hard validation gates.
292+
4. Stage only the declared target files and review/signoff artifacts.
293+
5. Commit with a deterministic message.
294+
6. Push the branch.
295+
7. Use `createGitHubStep({ action: 'createPR', ... })` from `@agent-relay/sdk/github` to open the PR.
296+
8. Verify the PR URL/state deterministically and write it into the final signoff artifact.
297+
298+
Do not hide commit/PR work in agent prose. Model it as deterministic steps whenever possible. For PR creation, issue updates, file reads, or any GitHub operation, prefer `createGitHubStep` over shelling out to `gh`; it is bundled with `@agent-relay/sdk`. The downstream hard gate must still verify the PR exists before signoff.
299+
300+
If commit or PR creation is intentionally outside the workflow, say that directly in the workflow description and signoff so the operator knows to do it after completion.
301+
283302
### 2. Avoid raw fenced code blocks inside workflow task template literals
284303

285304
Raw triple-backtick code fences inside large inline `task: \`...\`` template strings are fragile and can break outer TypeScript parsing, especially when they contain language tags like `swift` or `diff`.
@@ -331,7 +350,7 @@ The battle-tested template:
331350
- **Use `grep -vE "^(...)$"` for full-line match.** Substring matches bleed across unrelated files (e.g., `setup.ts` would also match `packages/core/src/bootstrap/setup.ts`).
332351
- **Append `|| true` to the grep.** Without it, an empty result triggers `set -e` and the whole preflight fails before the `if` can even run.
333352
- **Check the staging area separately.** A dirty index is different from a dirty working tree and both must be clean (modulo allow-list).
334-
- **Check `gh auth status` early** if any downstream step uses `gh pr create` or similar. Failing on auth at the end of a long DAG is painful.
353+
- **Check `gh auth status` early** if downstream GitHub operations will use the local transport. Failing on auth at the end of a long DAG is painful.
335354

336355
**Never use `git diff --quiet` alone as your "clean tree" check.** It fails on any dirty file, including the ones the workflow is expected to rewrite, which causes false failures on every resume / re-run.
337356

@@ -374,7 +393,7 @@ command: [
374393

375394
Results in `set -e && cat > /tmp/f <<EOF && line 1 && line 2 && EOF && next-command` — a shell syntax error because `&&` cannot appear inside a heredoc body. Use `.join('\n')` or (better) sidestep the heredoc entirely.
376395

377-
**The `printf` + `mktemp` alternative — use this for commit messages, PR bodies, and any other multi-line file content.** It avoids heredocs altogether and composes with `.join(' && ')`:
396+
**The `printf` + `mktemp` alternative — use this for commit messages, raw-CLI fallback PR bodies, and any other multi-line file content.** It avoids heredocs altogether and composes with `.join(' && ')`:
378397

379398
```ts
380399
command: [
@@ -388,7 +407,7 @@ command: [
388407
].join(' && '),
389408
```
390409

391-
This pattern is specifically recommended over `git commit -m "$(cat <<'EOF' ... EOF)"` and `gh pr create --body "$(cat <<'BODY' ... BODY)"`. Nesting a heredoc inside `$(...)` forces the shell to match a closing paren across many lines of unparsed body text, and any stray parenthesis in the body text can silently break the match. `--body-file` + `mktemp` + `printf` is immune to that entire class of bug.
410+
This pattern is specifically recommended over `git commit -m "$(cat <<'EOF' ... EOF)"` and raw `gh pr create --body "$(cat <<'BODY' ... BODY)"`. Nesting a heredoc inside `$(...)` forces the shell to match a closing paren across many lines of unparsed body text, and any stray parenthesis in the body text can silently break the match. `--body-file` + `mktemp` + `printf` is immune to that entire class of bug. For workflow-owned PR creation, prefer `createGitHubStep` over raw `gh`; this shell pattern is for raw CLI fallback cases.
392411

393412
### 2d. Template-literal escape sequences are processed once before the string is rendered
394413

@@ -579,8 +598,8 @@ For bug-fix or reliability workflows, do **not** stop at unit or integration tes
579598
8. **Record residual risks**
580599
- Call out what was not covered
581600
9. **Ship the result as a PR**
582-
- Open the pull request from the workflow itself with the GitHub primitive
583-
- See [Shipping the Result — Open a PR via the GitHub Primitive](#shipping-the-result--open-a-pr-via-the-github-primitive) below
601+
- Open the pull request from the workflow itself with `createGitHubStep`
602+
- See [Shipping the Result — Open a PR via `createGitHubStep`](#shipping-the-result--open-a-pr-via-creategithubstep) below
584603
- A workflow that fixes a bug and stops short of the PR has only done half the loop
585604

586605
### Clean-environment validation guidance
@@ -602,38 +621,33 @@ If the right proving environment is unclear, first write a **meta-workflow** tha
602621

603622
This is often better than jumping straight to implementation.
604623

605-
## Shipping the Result — Open a PR via the GitHub Primitive
624+
## Shipping the Result — Open a PR via `createGitHubStep`
606625

607-
A workflow whose final artifact is "a clean working tree on a sandbox you'll throw away" has not shipped anything. **End every code-changing workflow by opening a pull request, and do it from inside the workflow** using the `@agent-relay/github-primitive`. Don't tell the operator to follow up with `gh pr create` — make the workflow's own last step the PR.
626+
A workflow whose final artifact is "a clean working tree on a sandbox you'll throw away" has not shipped anything. **End every code-changing workflow by opening a pull request, and do it from inside the workflow** using `createGitHubStep` from `@agent-relay/sdk/github`. Don't tell the operator to follow up with `gh pr create` — make the workflow's own last step the PR.
608627

609-
### Why the primitive (and not raw `gh` / `octokit`)
628+
### Why `createGitHubStep` (and not raw `gh` / `octokit`)
610629

611630
The primitive picks the right transport at runtime:
612631

613-
| Where the workflow runs | Transport the primitive uses | What you provide |
632+
| Where the workflow runs | Transport `createGitHubStep` uses | What you provide |
614633
|---|---|---|
615634
| Local (`agent-relay run`) | `gh` CLI | `gh auth status` works |
616635
| Cloud (`agent-relay cloud run`) — tenant-scoped | Nango → workspace's GitHub App installation | Nothing — cloud injects credentials |
617636
| Cloud — fallback | Relay-cloud GitHub proxy | Nothing — cloud injects credentials |
618637

619638
You write **one** workflow. The same `createPR` step opens a PR via your local `gh` when you iterate on it on a laptop, and via the workspace's GitHub App when the same file runs in `agent-relay cloud run`. No branching by environment, no env-var sniffing in your task strings, no "this part only works in cloud" caveats. That's the whole point of the adapter.
620639

621-
> **Phase C interaction (cloud only):** `agent-relay cloud run` already auto-pushes per-`paths[]` diffs as separate PRs after the workflow callback when the repos are allowlisted (see `pushedTo` in the run record). Phase C is the *catch-all* — if your workflow does nothing else, you still get one PR per declared path. Use the github primitive **on top of** that when you need PRs the catch-all can't produce: cross-cutting issues, follow-up tracking issues, opening one PR that spans multiple paths, draft PRs you want labeled/assigned in specific ways, or PRs against a repo you didn't `paths[]` in.
640+
> **Phase C interaction (cloud only):** `agent-relay cloud run` already auto-pushes per-`paths[]` diffs as separate PRs after the workflow callback when the repos are allowlisted (see `pushedTo` in the run record). Phase C is the *catch-all* — if your workflow does nothing else, you still get one PR per declared path. Use `createGitHubStep` **on top of** that when you need PRs the catch-all can't produce: cross-cutting issues, follow-up tracking issues, opening one PR that spans multiple paths, draft PRs you want labeled/assigned in specific ways, or PRs against a repo you didn't `paths[]` in.
622641
623642
### The minimal "open a PR" recipe
624643

625644
```typescript
626645
import { workflow } from '@agent-relay/sdk/workflows';
627-
import { GitHubStepExecutor, createGitHubStep } from '@agent-relay/github-primitive';
646+
import { createGitHubStep } from '@agent-relay/sdk/github';
628647

629648
const REPO = 'AgentWorkforce/cloud';
630649
const BRANCH = `agent-relay/run-${Date.now()}`;
631650

632-
// Auto-detect runtime: gh CLI locally, Nango/relay-cloud in cloud.
633-
// You don't need to wire any of the cloud config yourself when running
634-
// in `agent-relay cloud run` — the cloud bootstrap injects it.
635-
const github = new GitHubStepExecutor({ runtime: 'auto' });
636-
637651
await workflow('feature-x')
638652
// ... your real steps that produce code changes ...
639653
.step('write-marker', {
@@ -642,17 +656,15 @@ await workflow('feature-x')
642656
})
643657

644658
// Branch off main on the remote.
645-
.step(createGitHubStep({
646-
name: 'create-branch',
659+
.step('create-branch', createGitHubStep({
647660
dependsOn: ['write-marker'],
648661
action: 'createBranch',
649662
repo: REPO,
650663
params: { branch: BRANCH, source: 'main' },
651-
}), { executor: github })
664+
}))
652665

653666
// Commit the change to the branch via Contents API.
654-
.step(createGitHubStep({
655-
name: 'commit-change',
667+
.step('commit-change', createGitHubStep({
656668
dependsOn: ['create-branch'],
657669
action: 'createFile',
658670
repo: REPO,
@@ -662,11 +674,10 @@ await workflow('feature-x')
662674
content: '<file body here>',
663675
message: 'chore: changelog entry',
664676
},
665-
}), { executor: github })
677+
}))
666678

667679
// Open the PR. This is the load-bearing step.
668-
.step(createGitHubStep({
669-
name: 'open-pr',
680+
.step('open-pr', createGitHubStep({
670681
dependsOn: ['commit-change'],
671682
action: 'createPR',
672683
repo: REPO,
@@ -678,12 +689,12 @@ await workflow('feature-x')
678689
draft: false,
679690
},
680691
output: { mode: 'data', format: 'json', path: 'html_url' },
681-
}), { executor: github })
692+
}))
682693

683694
.run({ cwd: process.cwd() });
684695
```
685696

686-
The primitive's actions are stable across runtimes: `getRepo`, `createBranch`, `createFile`, `updateFile`, `createPR`, `updatePR`, `getPR`, `listPRs`, `mergePR`, `createIssue`, etc. See `packages/github-primitive/src/types.ts` for the full enum.
697+
`createGitHubStep` is bundled with `@agent-relay/sdk`; do not add a separate install. Its actions are stable across runtimes: `getRepo`, `createBranch`, `createFile`, `updateFile`, `createPR`, `updatePR`, `getPR`, `listPRs`, `mergePR`, `createIssue`, etc. See the SDK GitHub primitive docs for the full enum.
687698

688699
### Authoring rules for PR-shipping workflows
689700

@@ -692,7 +703,7 @@ The primitive's actions are stable across runtimes: `getRepo`, `createBranch`, `
692703
3. **Branch name encodes the run.** `agent-relay/run-${runId}` or `agent-relay/${workflow-name}-${timestamp}` so reviewers can tell the PR apart from other automation, and so reruns don't clash.
693704
4. **`draft: true` while iterating.** Once the workflow is stable end-to-end, flip to `draft: false`.
694705
5. **Body is a real PR description.** Summary + Test plan, generated from the workflow's own evidence (verification step output, diff stats, test run output). If you find yourself writing a placeholder body, the workflow isn't done — capture the real evidence in an earlier step and template it in.
695-
6. **Don't use the primitive to substitute for `paths[]` push-back in cloud.** If the diff lives in a tarballed `paths[]` mount, let cloud's Phase C push-back open that PR (it handles the patch generation, branch lifecycle, and per-repo allowlist). Use the primitive when you need a PR against a repo or branch outside the `paths[]` set, or when you want to add an extra PR (e.g. a tracking issue, a follow-up against a sibling repo, a docs-only PR).
706+
6. **Don't use `createGitHubStep` to substitute for `paths[]` push-back in cloud.** If the diff lives in a tarballed `paths[]` mount, let cloud's Phase C push-back open that PR (it handles the patch generation, branch lifecycle, and per-repo allowlist). Use `createGitHubStep` when you need a PR against a repo or branch outside the `paths[]` set, or when you want to add an extra PR (e.g. a tracking issue, a follow-up against a sibling repo, a docs-only PR).
696707
7. **Failure is a real failure.** If `createPR` errors (auth, permissions, branch conflict), the workflow should fail the step, not warn-and-continue. A "successful" workflow that silently failed to open the PR is the worst-case outcome — the human thinks the work shipped.
697708

698709
### Where this fits in the bug-fix phases
@@ -1305,7 +1316,7 @@ When you set `.pattern('supervisor')` (or `hub-spoke`, `fan-out`), the runner au
13051316
| Hardcoding all channels at spawn time | Use `agent.subscribe()` / `agent.unsubscribe()` for dynamic channel membership post-spawn |
13061317
| Using `preset: 'worker'` for Codex in *interactive team* patterns when coordination is needed | Codex interactive mode works fine with PTY channel injection. Drop the preset for interactive team patterns (keep it for one-shot DAG workers where clean stdout matters) |
13071318
| Separate reviewer agent from lead in interactive team | Merge lead + reviewer into one interactive Claude agent — reviews between rounds, fewer agents |
1308-
| 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 |
1319+
| Not printing PR URL after `createGitHubStep({ action: 'createPR' })` | Capture `html_url` with `output: { mode: 'data', format: 'json', path: 'html_url' }` and echo or write it in a final deterministic step |
13091320
| Workflow ending without worktree + PR for cross-repo changes | Add `setup-worktree` at start and `push-and-pr` + `cleanup-worktree` at end |
13101321

13111322
## YAML Alternative

0 commit comments

Comments
 (0)