Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 57 additions & 46 deletions .agents/skills/writing-agent-relay-workflows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,68 +619,79 @@ export function applyCloudRepoSetup<T>(wf: T, opts: CloudRepoSetupOptions): T {
- chooses the best swarm pattern
- then authors the final fix/validation workflow

### Shipping the Result — Open a PR via `createGitHubStep`
### Shipping the Result — Open a PR

#### The minimal "open a PR" recipe
#### Canonical pattern: `gh pr create` in a deterministic step

This is the preferred approach. It works on all SDK versions and avoids the
`createGitHubStep` API pitfalls documented below.

```typescript
import { workflow } from '@agent-relay/sdk/workflows';
import { createGitHubStep } from '@agent-relay/sdk';
// Commit all staged changes.
.step('commit', {
type: 'deterministic',
command: 'git add -A && git commit -m "feat(program): implement spec XYZ" 2>&1 || echo "NOTHING_TO_COMMIT"',
captureOutput: true,
failOnError: false,
})

const REPO = 'AgentWorkforce/cloud';
const BRANCH = `agent-relay/run-${Date.now()}`;
// Push branch to remote.
.step('push-branch', {
type: 'deterministic',
dependsOn: ['commit'],
command: 'BRANCH=$(git rev-parse --abbrev-ref HEAD) && git push -u origin "$BRANCH" 2>&1 && echo "PUSH_OK: $BRANCH"',
captureOutput: true,
failOnError: false,
})

async function runWorkflow() {
await workflow('feature-x')
// ... your real implementation, repair, review loops, and final acceptance ...
.step('write-marker', {
// Open a draft PR (idempotent — skips if PR already exists).
.step('ship-pr', {
type: 'deterministic',
command: `echo "fix landed at $(date -u)" >> CHANGELOG.md`,
dependsOn: ['push-branch'],
command: [
'BRANCH=$(git rev-parse --abbrev-ref HEAD)',
'EXISTING=$(gh pr list --head "$BRANCH" --json number --jq ".[0].number" 2>/dev/null || echo "")',
'if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then',
' echo "PR_ALREADY_EXISTS: #$EXISTING"',
'else',
' gh pr create --base main --head "$BRANCH" --draft --title "feat: ship X" --body "## Summary\\n\\n- ..." 2>&1',
' echo "PR_CREATED"',
'fi',
].join('\n'),
captureOutput: true,
failOnError: false,
})
```

// Branch off main on the remote.
.step('create-branch', createGitHubStep({
dependsOn: ['write-marker'],
action: 'createBranch',
repo: REPO,
params: { branch: BRANCH, source: 'main' },
}))
#### Alternative: `createGitHubStep` (requires SDK ≥ 6.0.9)

// Commit the change to the branch via Contents API.
.step('commit-change', createGitHubStep({
dependsOn: ['create-branch'],
action: 'createFile',
repo: REPO,
params: {
path: 'CHANGELOG.md',
branch: BRANCH,
content: '<file body here>',
message: 'chore: changelog entry',
},
}))
**Critical field names** — getting any of these wrong causes a startup parse error:
- **`name`** is NOT a field inside `createGitHubStep({...})`; the step name comes from the first arg of `.step('step-name', createGitHubStep({...}))`
- **`action: 'createPR'`** — NOT `createPullRequest`. Valid values: `createPR`, `createBranch`, `createFile`, `updateFile`, `getPR`, `listPRs`, `mergePR`, etc.
- **`repo`** MUST be `'owner/repo'` format (e.g. `'AgentWorkforce/nightcto'`) — NOT separate `owner`/`repo` fields
- **NO `id` field** — `createGitHubStep` has no `id` parameter

```typescript
import { workflow } from '@agent-relay/sdk/workflows';
import { createGitHubStep } from '@agent-relay/sdk'; // NOT from '@agent-relay/github-primitive'

// Open the PR. This is the load-bearing step.
const REPO = 'AgentWorkforce/cloud'; // 'owner/repo' format — required
const BRANCH = 'results/my-feature';

// Open the PR. name comes from .step('open-pr', ...) NOT from createGitHubStep.
.step('open-pr', createGitHubStep({
dependsOn: ['commit-change'],
action: 'createPR',
repo: REPO,
action: 'createPR', // NOT 'createPullRequest'
repo: REPO, // 'owner/repo' string — NOT separate owner/repo
params: {
title: 'feat: ship feature X',
head: BRANCH,
base: 'main',
body: '## Summary\n\n- ...\n\n## Test plan\n\n- [x] ...',
draft: false,
body: '## Summary\n\n- ...',
draft: true,
},
output: { mode: 'data', format: 'json', path: 'html_url' },
dependsOn: ['push-branch'],
}))

.run({ cwd: process.cwd() });
}

runWorkflow().catch((error) => {
console.error(error);
process.exit(1);
});
```


Expand Down Expand Up @@ -747,11 +758,11 @@ relay.onChannelUnmuted = (agent, channel) => { /* ... */ };
#### Model Constants

```typescript
import { ClaudeModels, CodexModels, GeminiModels } from '@agent-relay/config';
import { ClaudeModels } from '@agent-relay/config';

.agent('planner', { cli: 'claude', model: ClaudeModels.OPUS }) // not 'opus'
.agent('worker', { cli: 'claude', model: ClaudeModels.SONNET }) // not 'sonnet'
.agent('coder', { cli: 'codex', model: CodexModels.GPT_5_4 }) // not 'gpt-5.4'
.agent('coder', { cli: 'claude', model: ClaudeModels.SONNET }) // default to claude; other CLIs (codex, gemini) are supported but not the default
```


Expand Down
83 changes: 40 additions & 43 deletions src/product/generation/master-workflow-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ function renderMasterSource(input: {
? listedValidationCommands
: [TYPECHECK_COMMAND, deriveTestCommand(input.spec)]),
'git diff --name-only',
`grep -F RICKY_MASTER_REVIEW_READY ${shellQuote(`${input.artifactsDir}/review-codex.md`)}`,
`grep -F RICKY_MASTER_REVIEW_READY ${shellQuote(`${input.artifactsDir}/review-master.md`)}`,
'echo RICKY_MASTER_FINAL_VALIDATION_READY',
];

Expand All @@ -373,7 +373,7 @@ function renderMasterSource(input: {
` .onError(${repairAwareOnError('master-lead')})`,
'',
' .agent("master-lead", { cli: "claude", interactive: false, role: "Repairs master executor failures and final integration evidence.", retries: 1 })',
' .agent("master-reviewer", { cli: "codex", preset: "reviewer", role: "Reviews child signoff evidence and master executor readiness.", retries: 1 })',
' .agent("master-reviewer", { cli: "claude", preset: "reviewer", role: "Reviews child signoff evidence and master executor readiness.", retries: 1 })',
'',
' .step("prepare-context", {',
' type: "deterministic",',
Expand Down Expand Up @@ -440,9 +440,9 @@ function renderMasterSource(input: {
` task: ${templateLiteral([
'Review child workflow signoffs and deterministic gates for the master executor run.',
`Read ${input.artifactsDir}/master-plan.json and each child signoff path.`,
`Write ${input.artifactsDir}/review-codex.md ending with RICKY_MASTER_REVIEW_READY.`,
`Write ${input.artifactsDir}/review-master.md ending with RICKY_MASTER_REVIEW_READY.`,
].join('\n'))},`,
` verification: { type: "file_exists", value: ${literal(`${input.artifactsDir}/review-codex.md`)} },`,
` verification: { type: "file_exists", value: ${literal(`${input.artifactsDir}/review-master.md`)} },`,
' })',
'',
' .step("final-hard-validation", {',
Expand Down Expand Up @@ -531,11 +531,10 @@ export function childWorkflowSource(child: ChildWorkflowPlan, spec: NormalizedWo
' .timeout(3600000)',
` .onError(${repairAwareOnError('validator-claude')})`,
' .agent("lead-claude", { cli: "claude", interactive: false, role: "Plans this bounded child workflow slice.", retries: 1 })',
' .agent("impl-codex", { cli: "codex", role: "Implements only this child workflow slice and its declared file scope.", retries: 2 })',
' .agent("impl-claude", { cli: "claude", role: "Implements only this child workflow slice and its declared file scope.", retries: 2 })',
' .agent("reviewer-claude", { cli: "claude", preset: "reviewer", role: "First-pass fresh-eyes reviewer for scope, evidence, and product fit.", retries: 1 })',
' .agent("reviewer-codex", { cli: "codex", preset: "reviewer", role: "Reviews code, tests, deterministic gates, and PR/result evidence.", retries: 1 })',
' .agent("validator-claude", { cli: "claude", preset: "worker", role: "Runs the 80-to-100 fix loop and writes final signoff.", retries: 2 })',
' .agent("validator-codex", { cli: "codex", preset: "worker", role: "Runs the Codex review-fix loop and verifies final readiness.", retries: 2 })',
' .agent("reviewer-claude-2", { cli: "claude", preset: "reviewer", role: "Second-pass fresh-eyes reviewer for code, tests, deterministic gates, and PR/result evidence.", retries: 1 })',
' .agent("validator-claude", { cli: "claude", preset: "worker", role: "Runs the 80-to-100 fix loop, second-pass fix loop, and writes final signoff.", retries: 2 })',
' .step("prepare-context", {',
' type: "deterministic",',
` command: ${literal([
Expand Down Expand Up @@ -589,7 +588,7 @@ export function childWorkflowSource(child: ChildWorkflowPlan, spec: NormalizedWo
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/lead-plan.md`)} },`,
' })',
' .step("implement-slice", {',
' agent: "impl-codex",',
' agent: "impl-claude",',
' dependsOn: ["lead-plan"],',
` task: ${templateLiteral([
`Implement the bounded child slice: ${child.title}.`,
Expand Down Expand Up @@ -660,62 +659,62 @@ export function childWorkflowSource(child: ChildWorkflowPlan, spec: NormalizedWo
].join('\n'))},`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/claude-final-fix.md`)} },`,
' })',
' .step("review-codex", {',
' agent: "reviewer-codex",',
' .step("review-claude-2", {',
' agent: "reviewer-claude-2",',
' dependsOn: ["final-fix-claude"],',
` task: ${templateLiteral([
'Second-pass fresh-eyes review after the Claude loop. Read the actual files, diff, review artifacts, and validation evidence.',
'Second-pass fresh-eyes review after the first fix loop. Read the actual files, diff, review artifacts, and validation evidence.',
sharedWorktreeScopeRule,
'Use verdict: FINDINGS | NO_ISSUES_FOUND | BLOCKED and include fix_required plus test_required for each finding.',
`Write ${artifactsDir}/review-codex.md ending with RICKY_CHILD_CODEX_REVIEW_READY.`,
`Write ${artifactsDir}/review-claude-2.md ending with RICKY_CHILD_CLAUDE_2_REVIEW_READY.`,
].join('\n'))},`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/review-codex.md`)} },`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/review-claude-2.md`)} },`,
' })',
' .step("fix-loop-codex", {',
' agent: "validator-codex",',
' dependsOn: ["review-codex"],',
' .step("fix-loop-claude-2", {',
' agent: "validator-claude",',
' dependsOn: ["review-claude-2"],',
` task: ${templateLiteral([
`Read ${artifactsDir}/review-codex.md.`,
'Fix every valid Codex finding and add or update tests/proofs for testable findings.',
`Read ${artifactsDir}/review-claude-2.md.`,
'Fix every valid finding and add or update tests/proofs for testable findings.',
sharedWorktreeScopeRule,
`If blocked, write ${artifactsDir}/BLOCKED_NO_COMMIT.md with exact evidence.`,
`Write ${artifactsDir}/codex-fix-loop-report.md ending with RICKY_CHILD_CODEX_FIX_LOOP_READY.`,
`Write ${artifactsDir}/claude-2-fix-loop-report.md ending with RICKY_CHILD_CLAUDE_2_FIX_LOOP_READY.`,
].join('\n'))},`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/codex-fix-loop-report.md`)} },`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/claude-2-fix-loop-report.md`)} },`,
' })',
' .step("post-codex-fix-validation", {',
' .step("post-claude-2-fix-validation", {',
' type: "deterministic",',
' dependsOn: ["fix-loop-codex"],',
' dependsOn: ["fix-loop-claude-2"],',
` command: ${literal(`${validationCommand} 2>&1 | tail -160`)},`,
' captureOutput: true,',
' failOnError: false,',
' })',
' .step("final-review-codex", {',
' agent: "reviewer-codex",',
' dependsOn: ["post-codex-fix-validation"],',
' .step("final-review-claude-2", {',
' agent: "reviewer-claude-2",',
' dependsOn: ["post-claude-2-fix-validation"],',
` task: ${templateLiteral([
'Final Codex fresh-eyes review after Codex fixes.',
'Final second-pass fresh-eyes review after second fix loop.',
sharedWorktreeScopeRule,
'Use verdict: FINDINGS | NO_ISSUES_FOUND | BLOCKED and include fix_required plus test_required for each finding.',
`Write ${artifactsDir}/final-review-codex.md ending with RICKY_CHILD_CODEX_FINAL_REVIEW_READY.`,
`Write ${artifactsDir}/final-review-claude-2.md ending with RICKY_CHILD_CLAUDE_2_FINAL_REVIEW_READY.`,
].join('\n'))},`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/final-review-codex.md`)} },`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/final-review-claude-2.md`)} },`,
' })',
' .step("final-fix-codex", {',
' agent: "validator-codex",',
' dependsOn: ["final-review-codex"],',
' .step("final-fix-claude-2", {',
' agent: "validator-claude",',
' dependsOn: ["final-review-claude-2"],',
` task: ${templateLiteral([
`Read ${artifactsDir}/final-review-codex.md.`,
`Read ${artifactsDir}/final-review-claude-2.md.`,
'If it says NO_ISSUES_FOUND, record that no fix was needed. Otherwise fix every valid finding and harden tests/proofs.',
sharedWorktreeScopeRule,
`If blocked, write ${artifactsDir}/BLOCKED_NO_COMMIT.md with exact evidence.`,
`Write ${artifactsDir}/codex-final-fix.md ending with RICKY_CHILD_CODEX_FINAL_FIX_READY.`,
`Write ${artifactsDir}/claude-2-final-fix.md ending with RICKY_CHILD_CLAUDE_2_FINAL_FIX_READY.`,
].join('\n'))},`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/codex-final-fix.md`)} },`,
` verification: { type: "file_exists", value: ${literal(`${artifactsDir}/claude-2-final-fix.md`)} },`,
' })',
' .step("final-review-pass-gate", {',
' type: "deterministic",',
' dependsOn: ["final-fix-codex"],',
' dependsOn: ["final-fix-claude-2"],',
` command: ${literal(buildFinalReviewPassGateCommand({
artifactsDir,
checks: [
Expand All @@ -724,8 +723,8 @@ export function childWorkflowSource(child: ChildWorkflowPlan, spec: NormalizedWo
missingDetail: `${artifactsDir}/claude-final-fix.md is missing RICKY_CHILD_CLAUDE_FINAL_FIX_READY`,
},
{
presenceTest: `grep -qF RICKY_CHILD_CODEX_FINAL_FIX_READY ${shellQuote(`${artifactsDir}/codex-final-fix.md`)}`,
missingDetail: `${artifactsDir}/codex-final-fix.md is missing RICKY_CHILD_CODEX_FINAL_FIX_READY`,
presenceTest: `grep -qF RICKY_CHILD_CLAUDE_2_FINAL_FIX_READY ${shellQuote(`${artifactsDir}/claude-2-final-fix.md`)}`,
missingDetail: `${artifactsDir}/claude-2-final-fix.md is missing RICKY_CHILD_CLAUDE_2_FINAL_FIX_READY`,
},
],
successMarker: 'RICKY_CHILD_FRESH_EYES_LOOP_READY',
Expand All @@ -740,10 +739,8 @@ export function childWorkflowSource(child: ChildWorkflowPlan, spec: NormalizedWo
'set -e',
validationCommand,
'git diff --name-only',
// Quiet greps with explicit diagnostics — a missing marker here must
// not be hidden behind the previous grep's matched-line output.
`if ! grep -qF RICKY_CHILD_CLAUDE_FINAL_FIX_READY ${shellQuote(`${artifactsDir}/claude-final-fix.md`)}; then echo ${shellQuote(`RICKY_CHILD_GATE_MISSING_MARKER: ${artifactsDir}/claude-final-fix.md is missing RICKY_CHILD_CLAUDE_FINAL_FIX_READY`)} >&2; exit 1; fi`,
`if ! grep -qF RICKY_CHILD_CODEX_FINAL_FIX_READY ${shellQuote(`${artifactsDir}/codex-final-fix.md`)}; then echo ${shellQuote(`RICKY_CHILD_GATE_MISSING_MARKER: ${artifactsDir}/codex-final-fix.md is missing RICKY_CHILD_CODEX_FINAL_FIX_READY`)} >&2; exit 1; fi`,
`if ! grep -qF RICKY_CHILD_CLAUDE_2_FINAL_FIX_READY ${shellQuote(`${artifactsDir}/claude-2-final-fix.md`)}; then echo ${shellQuote(`RICKY_CHILD_GATE_MISSING_MARKER: ${artifactsDir}/claude-2-final-fix.md is missing RICKY_CHILD_CLAUDE_2_FINAL_FIX_READY`)} >&2; exit 1; fi`,
'echo RICKY_CHILD_FINAL_VALIDATION_READY',
].join('\n'))},`,
' captureOutput: true,',
Expand Down Expand Up @@ -807,7 +804,7 @@ function buildMasterGates(artifactsDir: string, plan: MasterExecutionPlan, spec:
gate('skill-boundary-metadata-gate', `test -f ${artifactsDir}/skill-application-boundary.json`, 'file_exists', true, ['prepare-context'], 'pre_review'),
gate('child-workflow-file-gate', plan.children.map((child) => `test -f ${child.workflowFilePath}`).join(' && '), 'file_exists', true, ['materialize-child-workflows'], 'pre_review'),
gate('initial-soft-validation', listedValidationOnly ? safeFinalValidationCommand : `{ ${TYPECHECK_COMMAND}; } 2>&1 | tail -160`, 'output_contains', false, ['child-workflow-file-gate'], 'pre_review'),
gate('final-review-pass-gate', `grep -F RICKY_MASTER_REVIEW_READY ${artifactsDir}/review-codex.md`, 'output_contains', true, ['review-child-evidence'], 'final'),
gate('final-review-pass-gate', `grep -F RICKY_MASTER_REVIEW_READY ${artifactsDir}/review-master.md`, 'output_contains', true, ['review-child-evidence'], 'final'),
gate('final-hard-validation', safeFinalValidationCommand, 'exit_code', true, ['final-review-pass-gate'], 'final'),
gate('git-diff-gate', 'git diff --name-only', 'output_contains', true, ['final-hard-validation'], 'final'),
gate('regression-gate', listedValidationOnly ? safeFinalValidationCommand : testCommand, 'exit_code', true, ['git-diff-gate'], 'regression'),
Expand Down Expand Up @@ -838,7 +835,7 @@ function buildMasterToolSelections(plan: MasterExecutionPlan): ToolSelection[] {
concurrency: child.parallelizable ? 2 : 1,
rule: 'master executor runs child workflow through ricky run',
})),
{ stepId: 'review-child-evidence', agent: 'master-reviewer', runner: 'codex', concurrency: 1, rule: 'master executor evidence review' },
{ stepId: 'review-child-evidence', agent: 'master-reviewer', runner: '@agent-relay/sdk', concurrency: 1, rule: 'master executor evidence review' },
];
}

Expand Down
Loading
Loading