From 8eb02f7fd5dce5c9ad52d6e3802448020c3467b9 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Fri, 11 Sep 2026 12:04:28 +0200 Subject: [PATCH 1/2] feat: writing-relayflows skill Adds the skill flows repo's own README.md already links to (`npx skills add https://github.com/agentworkforce/skills --skill writing-relayflows`, README.md:76) but which didn't exist yet. Covers the v2, journal-based @relayflows/surface / @relayflows/sdk engine (the `flows` CLI, package versions 2.0.x): the run/llm/agent ladder, human/dispatch/done, verification gates, TypeScript vs YAML authoring, per-step cli/model and its resolution order (step > named agent > flow > project flows.json), flows.json's real schema, and flows check/run/resume with their actual refusal shapes and exit codes. Explicitly scoped away from this repo's existing, unrelated older engine that's also casually called "Relayflow" -- @relayflows/core's chained WorkflowBuilder (.pattern('dag')/.agent()/.step()), covered by writing-agent-relay-workflows and migrating-persona-to-relayflow. Flagged prominently in both the skill's description and its own body so an agent mid-task notices the collision before mixing the two APIs. Verification (manual, cited -- not automated): - Built packages/surface and packages/sdk from source in a clean worktree off AgentWorkforce/flows@86a2ec2 (origin/main). Published npm @relayflows/surface@2.0.8 is stale -- predates flows#310, no cli/model on AgentOptions -- so the SDK's node_modules/@relayflows/surface was symlinked to the local build instead of using the published package. - Actually ran the real `flows` CLI against every YAML/TS example quoted in the skill: $ flows check hello.flow.yaml (this skill's YAML example) -> CHECK PASSED, exit 0 $ flows check hello.flow.ts (this skill's TS example) -> CHECK PASSED, exit 0 $ flows check extract.flow.yaml (output_contains example) -> CHECK PASSED, exit 0 $ flows check hello.flow.yaml (same YAML, no flows.json) -> REFUSED [cli_unresolved], exit 2 $ flows check hello.flow.yaml (model not in flows.json's models[]) -> REFUSED [model_unknown], exit 2 - Every type/field/interface quoted (Ctx, AgentOptions, FlowHeader, StepSpec variants, FlowSpec, failure-kinds, cli.ts USAGE) is a literal read of current origin/main source, cited by file path (and file:line where the claim is narrow enough to pin). - The f.human/f.dispatch/f.slack snippet and the TS `REFUSED [invalid_spec]` wrapping claim are source-cited only (direct-run.ts:97-119 for the latter) -- not independently re-run, since both need a live relayflowd daemon (plus a Slack mount for the former), which this pass didn't build. Said so explicitly in the skill's own "Verified against" section rather than implying full live coverage. Registered in prpm.json (agent-workforce-skills 1.1.5 -> 1.1.6, writing-relayflows 1.0.0) and README.md's Published Skills table, matching the existing per-skill entry format. Co-authored-by: Cursor --- README.md | 1 + prpm.json | 25 ++- skills/writing-relayflows/SKILL.md | 320 +++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 skills/writing-relayflows/SKILL.md diff --git a/README.md b/README.md index 8d8c735..6b926d3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Package metadata lives in [prpm.json](prpm.json). The repo currently publishes ` | [choosing-swarm-patterns](skills/choosing-swarm-patterns/SKILL.md) | 1.1.5 | Pick the right Agent Relay orchestration pattern across the 10 core swarm patterns plus specialized patterns. | | [writing-agent-relay-workflows](skills/writing-agent-relay-workflows/SKILL.md) | 1.6.24 | Build multi-agent workflows with WorkflowBuilder, DAG dependencies, Relayfile-backed Slack human assistance, integration subscriptions, waitFor gates, review-depth review/fix loops, channels, and chat-native coordination recipes. | | [setting-up-relayfile](skills/setting-up-relayfile/SKILL.md) | 1.1.1 | Set up Relayfile mounts and writeback for provider files through local filesystem access. | +| [writing-relayflows](skills/writing-relayflows/SKILL.md) | 1.0.0 | Author a Relayflows v2 flow (`@relayflows/surface`/`@relayflows/sdk`, the `flows` CLI) in TypeScript or YAML/JSON — the run/llm/agent ladder, verification gates, cli/model resolution, flows.json, and real `flows check`/`run` refusal shapes. Not the older `@relayflows/core` WorkflowBuilder engine. | | [using-agent-relay](skills/using-agent-relay/SKILL.md) | 1.4.0 | Participant-side MCP reference for a **registered** relay agent (spawned worker / registered lead): messaging, channels, threads, reactions, search, inbox, actions, and worker spawn/release. Counterpart to `orchestrating-agent-relay`. | | [orchestrating-agent-relay](skills/orchestrating-agent-relay/SKILL.md) | 2.3.1 | The canonical way to run agent-relay: self-bootstrap the broker (`agent-relay node up`) and autonomously spawn, monitor, and coordinate a worker team over the relay MCP without human intervention. | | [relay-80-100-workflow](skills/relay-80-100-workflow/SKILL.md) | 1.0.9 | Author workflows that close the 80-to-100 validation gap with repair-aware test, verify, review-depth review/fix with test hardening, and commit gates. | diff --git a/prpm.json b/prpm.json index fef7b4f..2961cc7 100644 --- a/prpm.json +++ b/prpm.json @@ -1,6 +1,6 @@ { "name": "agent-workforce-skills", - "version": "1.1.5", + "version": "1.1.6", "description": "Skills for multi-agent coordination - swarm patterns, workflow building, relay usage, and orchestrating agent-relay", "author": "khaliqgant", "organization": "agent-relay", @@ -93,6 +93,29 @@ "skills/setting-up-relayfile/SKILL.md" ] }, + { + "name": "writing-relayflows", + "version": "1.0.0", + "description": "Use when authoring a Relayflows flow (@relayflows/surface / @relayflows/sdk, the journal-based v2 engine \u2014 the CLI is `flows`, package versions 2.0.x) in TypeScript or YAML/JSON. Covers the three-rung ladder (run/llm/agent), the resident verbs (human/dispatch/done), verification gates, TypeScript vs YAML authoring, per-step cli/model selection and its resolution order, flows.json, and `flows check`/`run`/`resume` with their real refusal shapes and exit codes. Not for the older, unrelated `@relayflows/core` WorkflowBuilder engine (`.pattern('dag')`/.agent()/.step() chains) that `writing-agent-relay-workflows` and `migrating-persona-to-relayflow` cover \u2014 that's a different product despite the similar name.", + "format": "claude", + "subtype": "skill", + "languages": [ + "typescript", + "yaml" + ], + "tags": [ + "relayflows", + "flows", + "journal", + "workflow", + "agent", + "typescript", + "yaml" + ], + "files": [ + "skills/writing-relayflows/SKILL.md" + ] + }, { "name": "using-agent-relay", "version": "1.4.0", diff --git a/skills/writing-relayflows/SKILL.md b/skills/writing-relayflows/SKILL.md new file mode 100644 index 0000000..ddba448 --- /dev/null +++ b/skills/writing-relayflows/SKILL.md @@ -0,0 +1,320 @@ +--- +name: writing-relayflows +description: Use when authoring a Relayflows flow (@relayflows/surface / @relayflows/sdk, the journal-based v2 engine — the CLI is `flows`, package versions 2.0.x) in TypeScript or YAML/JSON. Covers the three-rung ladder (run/llm/agent), the resident verbs (human/dispatch/done), verification gates, TypeScript vs YAML authoring, per-step cli/model selection and its resolution order, flows.json, and `flows check`/`run`/`resume` with their real refusal shapes and exit codes. Not for the older, unrelated `@relayflows/core` WorkflowBuilder engine (`.pattern('dag')`/.agent()/.step() chains) that `writing-agent-relay-workflows` and `migrating-persona-to-relayflow` cover — that's a different product despite the similar name. +--- + +# Writing Relayflows + +## Overview + +Relayflows turns a coding-agent task into steps a journal can inspect, verify, and resume. A flow is data (YAML/JSON) or code (TypeScript) that compiles to the same journal-backed kernel spec. Every effect is journaled before it's treated as real — a journal write that fails fails the step, with no silent fallback. + +**Name collision warning.** This repo also has skills for an older, unrelated engine that is *also* casually called "Relayflow" (singular) — `@relayflows/core`'s `WorkflowBuilder`, a chained builder (`workflow('name').pattern('dag').agent(...).step(...).run()`). That's `writing-agent-relay-workflows` and `migrating-persona-to-relayflow`'s territory. This skill is the **v2** engine: `@relayflows/surface`'s `flow()` function and the YAML/JSON dialect compiled by `@relayflows/sdk`. If you see `.pattern(`, `.agent(` as a chained builder call, or `ctx.workflow.run()`, you're in the other engine — stop and use one of those skills instead. + +## When to use this skill + +- Writing a new `.flow.ts` or `.flow.yaml`/`.flow.json` for the `flows` CLI (package `@relayflows/sdk`, binary name `flows`). +- Deciding whether a step needs `run` (shell), `llm` (bare model call), or `agent` (harnessed coding agent in a workspace). +- Wiring up `cli`/`model` for an `agent` or `llm` step, in either language. +- Debugging a `REFUSED [...]` message from `flows check` or `flows run`. +- Choosing between TypeScript and YAML for a given flow. + +## The ladder + +Three step verbs, one per rung — never more (`packages/sdk/src/spec.ts`, `export type StepType = 'deterministic' | 'llm' | 'agent';`): + +1. **`run` / `deterministic`** — a shell command. No model. Implicit gate is `exit_code == 0`. +2. **`llm` / `llm`** — a bare model call. Prompt in, verified output out. No workspace, no tool use. +3. **`agent` / `agent`** — a harnessed coding agent in a workspace. Returns `{ summary, artifacts }`, not raw text. + +Plus four resident verbs that aren't ladder rungs: `human` (durable approval), `dispatch` (hand off to a child flow), `done` (typed finish), and in YAML, `on`/triggers (event entry points — out of scope for this skill). + +Most flows only need `run` and `llm`. Climb to `agent` once a step needs hands on a real workspace. + +## Two ways to author the same thing + +**YAML/JSON** is data: `flows check` or CI can validate it without running anything. **TypeScript** calls the same primitives imperatively as ordinary async code. Both compile to the same journal. + +### TypeScript + +```ts +import { flow } from '@relayflows/surface'; + +export default flow('hello', async (f) => { + const greeting = await f.run('echo "Hello from Relayflows"'); + console.log(greeting.trim()); + + const answer = await f.agent('greeter', { + task: 'Reply with one short hello sentence. Do not use tools or modify files.', + cli: 'claude', + model: 'claude-sonnet-4-6', + }); + console.log(answer.summary); + + f.done('success'); +}); +``` + +### YAML + +```yaml +version: '0.1.0' +name: hello +steps: + - id: greeting + type: deterministic + command: 'echo "Hello from Relayflows"' + - id: greeter + type: agent + dependsOn: [greeting] + instruction: 'Reply with one short hello sentence. Do not use tools or modify files.' + cli: claude + model: claude-sonnet-4-6 +``` + +Both examples above were run for real against a built `2.0.8`-line checkout (`flows check`, exit 0 both) — see **Verified against**. + +## The real `Ctx` contract (TypeScript) + +`packages/surface/src/context.ts`, current as of `origin/main@86a2ec2`: + +```ts +export interface AgentResult { + summary: string; + artifacts: string[]; +} + +export interface AgentOptions { + task: string; + workspace?: string; + cli?: string; + model?: string; +} + +export interface Ctx { + run(command: string): Step; + llm(strings: TemplateStringsArray, ...values: unknown[]): Step; + llm(prompt: string, options: { output: Record; cli?: string; model?: string }): Step; + agent(name: string, options: AgentOptions): Step; + human(question: string, options: { to: string }): Promise; + dispatch(flow: string, input: unknown): Promise; + done(reason: RunCompletionReason): void; + cloud: CloudHelper; + slack: SlackHelper; +} +``` + +Do not add fields to `AgentOptions`/`Ctx` that aren't in this list — they don't exist in the shipped SDK. In particular: **no flow-level `cli`, no named-agent map, no `recoveryMode`/`permissions`/`surfaces`/`budget`** on the TypeScript side. Those are YAML/JSON-only today (see **What this skill does NOT cover**). `FlowHeader` (`packages/surface/src/flow.ts:4-9`) only allows `identity`, `memory`, `budget`, `tools`, `workspace` — no `agents` key. Passing an unknown header field or an unknown `AgentOptions` field throws a `TypeError` at authoring time, before anything is journaled. + +`Step` (`packages/surface/src/step.ts`) is a `PromiseLike` with one extra method: `.gate(predicate, because?)`, which fails the step `verification_failed` when the predicate returns false. Every awaited step must actually be awaited — an unawaited or manually-`.then()`-chained step is refused (`unawaited_step` / `unsupported_verb`), not silently dropped. + +`done(reason)` takes exactly one of the closed run-completion set (`packages/surface/src/completion.ts`): `'success' | 'step_failed' | 'canceled' | 'budget_exceeded'`. There's no fifth option — don't invent one (e.g. `'partial'`, `'skipped'`). + +## The real step shapes (YAML/JSON, `packages/sdk/src/spec.ts`) + +```ts +interface DeterministicStepSpec { + type: 'deterministic'; + id: string; + command: string; + dependsOn?: string[]; + timeoutMs?: number; + verification?: VerificationSpec; // omit for implicit exit_code +} + +interface LlmStepSpec { + type: 'llm'; + id: string; + prompt: string; + dependsOn?: string[]; + verification?: OutputVerificationSpec; + model?: string; + cli?: string; +} + +interface AgentStepSpec { + type: 'agent'; + id: string; + instruction: string; + dependsOn?: string[]; + verification?: OutputVerificationSpec; + agent?: string; // selects a named FlowSpec.agents entry + cli?: string; + model?: string; + surfaces?: { workspace?: { surface: string }[]; streams?: { stream: string }[]; external?: string[] }; + recoveryMode?: 'reset' | 'inspect' | 'manual'; // default 'reset' + permissions?: { fileGlobs?: string[]; networkAllowlist?: string[]; accessPreset?: 'readonly' | 'readwrite' }; +} + +interface FlowSpec { + version: string; // required, e.g. '0.1.0' — not optional + name?: string; + cli?: string; // flow-level CLI default + agents?: Record; // both fields required + steps: StepSpec[]; + budget?: { maxTokensIn?: number; maxTokensOut?: number; maxDollars?: string }; +} +``` + +`version` is required on every `FlowSpec` — omitting it is a real, common mistake; `flows check` refuses it. + +## Verification gates + +Verification is control flow, not decoration — a gate decides whether a step actually completed, not just whether the process exited cleanly (`packages/sdk/src/spec.ts`, `VerificationGateType`): + +- `exit_code` — implicit default for `deterministic` steps. `exit_code == 0`. Not configurable in v0. +- `output_contains` — step output (stdout tail or LLM value, stringified) contains `value`. +- `json_schema` — step output validates against a JSON Schema (`boolean | Record`). Used for structured LLM/agent output. + +```yaml +- id: classify + type: llm + prompt: 'Classify this ticket as bug, feature, or question: "the export button does nothing"' + cli: claude + model: claude-sonnet-4-6 + verification: + type: output_contains + value: bug +``` + +Verified for real: `flows check` on exactly this step passed (`CHECK PASSED`, exit 0) — see **Verified against**. + +An agent step rarely fails by crashing; it fails by returning something plausible and wrong, which a plain retry-on-error never catches. Always give an `agent`/`llm` step a real `verification`, not just the default. + +## `cli` / `model`: what a step actually runs on + +Both YAML and TypeScript agent/llm steps can set `cli` and `model` directly (TypeScript since flows#310, `AgentOptions.cli?`/`.model?`). Resolution order for `cli` — checked once per step by `preflight.ts`'s `resolveCli` (`packages/sdk/src/preflight.ts:265-282`), identical regardless of authoring language because both compile to the same `StepSpec`: + +1. **step** — `step.cli` (or TS's `options.cli`) +2. **named** — the flow's `agents[step.agent]` entry, if the step selects one via `agent: ` (YAML/JSON only — TypeScript has no named-agent map yet, see below) +3. **flow** — `FlowSpec.cli` +4. **project** — nearest `flows.json`'s `cli`, found by walking up from the flow file's directory + +No resolution found at any level → `REFUSED [cli_unresolved]`, before anything is journaled. Verified for real: + +``` +$ flows check hello.flow.yaml # agent step, no cli anywhere +REFUSED [cli_unresolved] Step "greeter" has no CLI at step, flow, or project level. No flows.json was found from "..." to the filesystem root. +``` +(exit 2 — see **Verified against**) + +`model` has **no** flow or project default — only step or named-agent. Omitting it just runs whatever model the resolved CLI defaults to. + +### `flows.json` + +Nearest-wins project config, walking from the flow file's directory to the filesystem root (`packages/sdk/src/cli/check.ts`). Exact schema — unknown keys fail closed as `config_invalid`: + +```json +{ "cli": "claude", "executors": ["cron"], "models": ["claude-sonnet-4-6"] } +``` + +- `cli` (optional, non-empty string) — the project-wide CLI default (resolution rung 4 above). +- `executors` (optional, string array) — trigger executors this project has registered. A trigger whose `executor` isn't in this list is `no_executor`. +- `models` (optional, string array) — **an allowlist, not a default.** Any model declared on any step or named agent anywhere in the flow must appear here, or `flows check` refuses `model_unknown`. Setting `models` does not select a model for anything; there's still no flow/project model default. + +``` +$ flows check hello.flow.yaml # model set on the step, but not in flows.json's models[] +REFUSED [model_unknown] Step "greeter" declares model "claude-sonnet-4-6" for CLI "claude", but it is not listed in project model registry "..."; add the exact model only after verifying that project is allowed to use it. +``` +(Verified for real — see **Verified against**.) + +## Human approval, dispatch, and Slack (TypeScript resident verbs) + +```ts +import { flow } from '@relayflows/surface'; + +export default flow('ship-feature', async (f) => { + const plan = await f.agent('planner', { + task: 'Research and plan: add OAuth2 support', + workspace: 'acme/api: readonly', // compiles to relayauth path scopes + }); + + const ok = await f.human(`Ship this?\n${plan.summary}`, { to: 'khaliq' }); + if (!ok) return f.done('canceled'); + + const pr = await f.dispatch('garden/implement', plan); // hands off to a child flow + await f.slack.reply(event, `Shipped: ${pr.url}`); +}); +``` + +`f.human` is a durable, journaled approval gate — the run parks until the human answers, and resumes exactly where it left off. `f.dispatch` hands input to a named child flow and returns its typed result; the parent doesn't inline the child's steps. (Source: `docs/SURFACE.md` §2 rule 6 region, lines ~40-53 as of `origin/main@86a2ec2` — this snippet is cited, not independently re-run, since it needs a live daemon + Slack mount.) + +## Running it: `flows check` / `run` / `resume` + +Real usage (`packages/sdk/src/cli.ts`): + +``` +flows check [--json] +flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir ] [--local-agent] +flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir ] [--local-agent] --input +flows resume [--json] [--no-spawn] [--no-observer-link] [--data-dir ] +``` + +`check` is a pure compile-and-preflight — no daemon, no socket, no data dir. It's the fast, safe way to validate a flow before ever running it. `--json` works on `check`/`run`/`resume`; it does **not** exist on `tick start`, `hn-monitor start`, or `observer`. + +A `.flow.ts` run via `flows run` requires `--input ` even when the flow body ignores its input argument — `flows run hello.flow.ts` alone refuses `REFUSED [input_missing]`. + +### Exit codes and refusal shapes + +Every refusal before a journal write is exit **2**, printed as `REFUSED [] `. The `` differs by *how* the flow was checked, not just *what* was wrong — don't assume one canonical string for "no CLI": + +- `flows check` on a YAML/JSON spec, or the declarative-compiler path in general → `preflight.ts`'s own kind directly: `cli_unresolved`, `model_unknown`, `cli_missing`, `cli_unauthenticated`, `no_executor`, etc. (`packages/sdk/src/failure-kinds.ts`, `PREFLIGHT_FAILURE_KINDS`). +- `flows run` on a **TypeScript** `.flow.ts` whose authored `f.agent`/`f.llm` call can't resolve a CLI at runtime → the executor's internal `agent_cli_unresolved` gets wrapped and printed as `REFUSED [invalid_spec] ` (`packages/sdk/src/cli/direct-run.ts:97-119`). The internal `agent_cli_unresolved` string is never itself the printed kind a user sees. + +Both are real, both are exit 2 — just from different code paths, so don't be surprised if the same underlying problem prints a different `[kind]` depending on whether you hit it via `flows check` on YAML or `flows run` on TypeScript. + +## Common mistakes + +- **Forgetting `version` in a YAML/JSON `FlowSpec`.** It's required, not optional — `flows check` refuses a spec without it. +- **Adding `agents:` to a TypeScript `flow()` header.** `FlowHeader` has no such field; it throws `TypeError: flow header has unknown fields: agents` at authoring time. Named-agent maps + `agent:` selector are YAML/JSON-only (flows#300 tracks TypeScript composition via `use:`, not yet shipped). +- **Assuming `flows.json`'s `models` sets a default model.** It only validates models already declared elsewhere; it never selects one. +- **Not awaiting a step, or manually `.then()`-chaining one.** Both are refused (`unawaited_step` / `unsupported_verb`) rather than silently ignored — the executor closes every root operation's lifecycle explicitly. +- **Running a `.flow.ts` without `--input`.** Required even for flows that don't use their input argument. +- **Expecting a fifth `done()` reason.** The set is closed: `success | step_failed | canceled | budget_exceeded`. Don't invent `partial` or `skipped`. + +## What this skill does NOT cover + +- **Named-agent maps in TypeScript** (`agents: { reviewer: { cli, model } }` + reuse across steps by name) — YAML/JSON only today. Tracked for TS composition via `use:` at [flows#300](https://github.com/AgentWorkforce/flows/issues/300). +- **`recoveryMode`, `permissions`, `surfaces`, `budget`, `memory`** on agent steps — real YAML/JSON fields with no TypeScript equivalent. Author that step in YAML and reach it from TypeScript with `f.dispatch` if you need them. +- **Cloud execution** (`flows run --cloud`), **triggers/webhooks**, **memory retrieval**, and the **`f.mcp`**/**`f.slack`** helper namespaces — each is its own surface with its own gotchas; see the [Relayflows product docs](https://agentrelay.com/docs/relayflows) for what's shipped versus designed-but-not-yet-implemented. +- The **older `@relayflows/core` `WorkflowBuilder`** engine — see `writing-agent-relay-workflows` and `migrating-persona-to-relayflow` in this repo. + +## Quick reference + +| Verb / field | Language | Notes | +|---|---|---| +| `f.run(command)` / `type: deterministic` | both | shell command, implicit `exit_code` gate | +| `f.llm(...)` / `type: llm` | both | bare model call, no workspace | +| `f.agent(name, opts)` / `type: agent` | both | harnessed coding agent, returns `{summary, artifacts}` | +| `f.human(question, {to})` | TS only | durable approval; YAML has no equivalent yet | +| `f.dispatch(flow, input)` | TS only | hand off to a named child flow | +| `f.done(reason)` / — | TS / kernel | one of `success \| step_failed \| canceled \| budget_exceeded` | +| `options.cli` / `step.cli` | both | per-call/step CLI override (TS: flows#310) | +| `options.model` / `step.model` | both | per-call/step model; no flow/project default | +| `agent: ` + `agents: {...}` | YAML/JSON only | named cli/model pair, reused by selector | +| `flows check ` | CLI | pure validate + preflight, no daemon | +| `flows run [--input ...]` | CLI | actually executes; `.flow.ts` needs `--input` | +| `flows resume ` | CLI | resume a parked/crashed run | + +## Verified against + +`AgentWorkforce/flows@86a2ec2` (origin/main). Built `packages/surface` and `packages/sdk` from source in a clean worktree (published npm `@relayflows/surface@2.0.8` is stale — it predates flows#310 and lacks `cli`/`model` on `AgentOptions`; local build was symlinked in instead), then ran the real CLI: + +``` +$ flows check hello.flow.yaml # this skill's YAML example, cli/model added, flows.json models allowlist set +CHECK PASSED hello.flow.yaml # exit 0 + +$ flows check hello.flow.ts # this skill's TypeScript example +CHECK PASSED hello.flow.ts # exit 0 + +$ flows check extract.flow.yaml # this skill's output_contains example +CHECK PASSED extract.flow.yaml # exit 0 + +$ flows check hello.flow.yaml # same YAML, no flows.json anywhere +REFUSED [cli_unresolved] Step "greeter" has no CLI at step, flow, or project level. ... # exit 2 + +$ flows check hello.flow.yaml # step model not in flows.json's models[] +REFUSED [model_unknown] Step "greeter" declares model "claude-sonnet-4-6" ... not listed in project model registry ... # exit 2 +``` + +The `f.human`/`f.dispatch`/`f.slack` snippet and the TypeScript `REFUSED [invalid_spec]` wrapping claim are source-cited (exact file:line above), not independently re-run — both need a live `relayflowd` daemon (and Slack mount, for the former), which this pass didn't build. From 20474c37b9468133f43d3c14bce0357c768b04f8 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Fri, 11 Sep 2026 12:52:20 +0200 Subject: [PATCH 2/2] fix(writing-relayflows): address CodeRabbit/Codex review feedback - README.md: fix stale 1.1.5 -> 1.1.6 to match prpm.json (CodeRabbit) - SKILL.md TS example: note that published @relayflows/surface@2.0.8 predates cli/model on AgentOptions and requires flows@86a2ec2+ (CodeRabbit) - SKILL.md 'Running it': note flows check's real usage text omits .flow.ts even though it accepts it (verified in this skill's own Verified-against transcripts) (CodeRabbit) - SKILL.md human/dispatch example: removed the undeclared `event` reference in f.slack.reply(event, ...) -- that call only exists inside a .on(trigger, async (f, event) => ...) handler in the real source (docs/SURFACE.md), which this skill explicitly doesn't cover. Retitled the section and added an explicit warning against copying that call into a plain flow() body (CodeRabbit + Codex, same finding) Co-authored-by: Cursor --- README.md | 2 +- skills/writing-relayflows/SKILL.md | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6b926d3..82abf45 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Skills, slash commands, and a Claude Code plugin for building multi-agent systems with Agent Relay. -Package metadata lives in [prpm.json](prpm.json). The repo currently publishes `agent-workforce-skills` version `1.1.5`. +Package metadata lives in [prpm.json](prpm.json). The repo currently publishes `agent-workforce-skills` version `1.1.6`. ## Published Skills diff --git a/skills/writing-relayflows/SKILL.md b/skills/writing-relayflows/SKILL.md index ddba448..55dba6d 100644 --- a/skills/writing-relayflows/SKILL.md +++ b/skills/writing-relayflows/SKILL.md @@ -72,7 +72,7 @@ steps: model: claude-sonnet-4-6 ``` -Both examples above were run for real against a built `2.0.8`-line checkout (`flows check`, exit 0 both) — see **Verified against**. +**Version note:** the published `@relayflows/surface@2.0.8` on npm predates `AgentOptions.cli`/`.model` (flows#310) — it does not define them, and using them against that installed version throws at authoring time. These examples require `AgentWorkforce/flows@86a2ec2` or a later/compatible published release; if your installed `@relayflows/surface` still reads `2.0.8` and doesn't export these fields, omit `cli`/`model` from `f.agent`/`f.llm` calls until it's updated. Both examples above were run for real against a source build of that commit (`flows check`, exit 0 both) — see **Verified against**. ## The real `Ctx` contract (TypeScript) @@ -218,7 +218,7 @@ REFUSED [model_unknown] Step "greeter" declares model "claude-sonnet-4-6" for CL ``` (Verified for real — see **Verified against**.) -## Human approval, dispatch, and Slack (TypeScript resident verbs) +## Human approval and dispatch (TypeScript resident verbs) ```ts import { flow } from '@relayflows/surface'; @@ -233,11 +233,13 @@ export default flow('ship-feature', async (f) => { if (!ok) return f.done('canceled'); const pr = await f.dispatch('garden/implement', plan); // hands off to a child flow - await f.slack.reply(event, `Shipped: ${pr.url}`); + f.done('success'); }); ``` -`f.human` is a durable, journaled approval gate — the run parks until the human answers, and resumes exactly where it left off. `f.dispatch` hands input to a named child flow and returns its typed result; the parent doesn't inline the child's steps. (Source: `docs/SURFACE.md` §2 rule 6 region, lines ~40-53 as of `origin/main@86a2ec2` — this snippet is cited, not independently re-run, since it needs a live daemon + Slack mount.) +`f.human` is a durable, journaled approval gate — the run parks until the human answers, and resumes exactly where it left off. `f.dispatch` hands input to a named child flow and returns its typed result; the parent doesn't inline the child's steps. (Source: `docs/SURFACE.md` §2 rule 6 region, lines ~40-53 as of `origin/main@86a2ec2` — this snippet is cited, not independently re-run, since it needs a live daemon.) + +`f.slack` is a separate helper namespace, and its real calling convention doesn't fit a plain `flow(name, async (f) => ...)` body: in the real source, `f.slack.reply(event, ...)` only appears inside a trigger handler registered via `.on(slack.mention('#exec'), async (f, event) => { ... })`, where `event` is the trigger's second callback argument — not something a step-based flow like the one above ever has in scope. Triggers and `f.slack` are out of this skill's scope (see **What this skill does NOT cover**); don't copy a bare `f.slack.reply(event, ...)` call into a `flow()` body like the one above, it will throw `event is not defined`. ## Running it: `flows check` / `run` / `resume` @@ -250,7 +252,7 @@ flows run [--json] [--no-spawn] [--no-observer-link] [--data-dir ] [--local flows resume [--json] [--no-spawn] [--no-observer-link] [--data-dir ] ``` -`check` is a pure compile-and-preflight — no daemon, no socket, no data dir. It's the fast, safe way to validate a flow before ever running it. `--json` works on `check`/`run`/`resume`; it does **not** exist on `tick start`, `hn-monitor start`, or `observer`. +`check` is a pure compile-and-preflight — no daemon, no socket, no data dir. It's the fast, safe way to validate a flow before ever running it. `--json` works on `check`/`run`/`resume`; it does **not** exist on `tick start`, `hn-monitor start`, or `observer`. The printed usage above only lists `` for `check`, but it accepts `.flow.ts` too — verified in this skill's own **Verified against** section (`flows check hello.flow.ts` passes); the tool's own `--help` text is just incomplete on this point. A `.flow.ts` run via `flows run` requires `--input ` even when the flow body ignores its input argument — `flows run hello.flow.ts` alone refuses `REFUSED [input_missing]`.