From 537b772755264f8fc197cf11f75e122a222750a0 Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Thu, 24 Sep 2026 21:02:44 -0700 Subject: [PATCH 1/2] feat(writing-relayflows): document --cloud-mirror, verified on 2.0.32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `flows run --cloud-mirror` (2.0.32) puts a local run on the Cloud dashboard. The skill covered neither the flag nor the surface it opens up: the three hosted read verbs — `flows status --cloud`, `flows logs`, `flows runs` — now answer for local runs, where until 2.0.32 they only answered for runs Cloud had launched. The new section explains the split that matters and is easy to get backwards: the **observer link is the default** (free, workspace key, step projection), and `--cloud-mirror` is the **opt-in** richer hosted view that also *stores* the flow source, every agent transcript and the CLI's own stderr — which is why it is asked for rather than turned on by the presence of a login. It also names the trap: the report's `runId` is the journal's ULID and every hosted read verb takes Cloud's UUID, so `--json` gained `cloudRunId`/`dashboardUrl`. Verified the way this skill requires rather than by reading a diff. `2.0.32` was installed from npm into a scratch project — not run from a source tree — and two real local runs were mirrored to production: a deterministic flow read back through all three verbs, and a two-agent flow (`cli: claude` + `cli: codex`, $0.01836 of real spend) whose per-step transcripts were fetched back out of Cloud storage. An echo-only flow exercises none of the transcript path, which is the claim worth having evidence for. Two findings from that session are recorded because they are the kind of thing this skill exists to save someone: **flows drives exactly two agent CLIs** — `adapters/index.ts` registers `claude` and `codex`, everything else needs the `relayflows-wrapper-v1` token contract, and a real `devin` on PATH rejects the probe outright, so it cannot be a `cli:` no matter what is installed. And the **observer projection has no retry** — one error sets `failed` and every later publish is a no-op, so a transient `429 workspace_busy` (hit for real during verification) permanently loses the observer view, while the dashboard mirror survives the same window because it treats 429 as transient. 1.4.0 -> 1.5.0: new surface, nothing removed. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 2 +- prpm.json | 2 +- skills/writing-relayflows/SKILL.md | 60 +++++++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e452cc8..2e9dea5 100644 --- a/README.md +++ b/README.md @@ -11,7 +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) | 2.0.0 | **DEPRECATED** - the superseded v1 `@relayflows/core` WorkflowBuilder engine. Use [`writing-relayflows`](skills/writing-relayflows/SKILL.md) for v2; keep this only to maintain an existing v1 workflow. | | [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.4.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. | +| [writing-relayflows](skills/writing-relayflows/SKILL.md) | 1.5.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, putting a local run on the Cloud dashboard with `--cloud-mirror`, 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.4.0 | 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, including GitHub PR-owner integration subscriptions. | | [relay-80-100-workflow](skills/relay-80-100-workflow/SKILL.md) | 2.1.0 | Close the 80-to-100 validation gap in a Relayflows v2 flow: the evidence recorder that turns a red check into work for a repair agent, repairable gates on the critical path, edit and hazard gates, when *not* to add a repair step, and the fresh-eyes review rounds that catch what green gates miss. | diff --git a/prpm.json b/prpm.json index ed84069..e96c249 100644 --- a/prpm.json +++ b/prpm.json @@ -95,7 +95,7 @@ }, { "name": "writing-relayflows", - "version": "1.4.0", + "version": "1.5.0", "description": "Use when authoring a Relayflows flow (@relayflows/surface / @relayflows/sdk, the v2 journal-based engine, CLI `flows`) in TypeScript or YAML/JSON. Covers the run/llm/agent ladder, human/dispatch/done, verification gates, cli/model resolution, flows.json, and flows check/run/resume refusal shapes. Not for the older @relayflows/core WorkflowBuilder (chained .pattern('dag')/.agent()/.step() calls) — see writing-agent-relay-workflows / migrating-persona-to-relayflow instead.", "format": "claude", "subtype": "skill", diff --git a/skills/writing-relayflows/SKILL.md b/skills/writing-relayflows/SKILL.md index 77671e6..e08d36c 100644 --- a/skills/writing-relayflows/SKILL.md +++ b/skills/writing-relayflows/SKILL.md @@ -1,6 +1,6 @@ --- 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 (including which ones actually run today), per-step cli/model selection, flows.json, parallel agents, the agent-relay dispatch transport, and `flows check`/`run`/`deploy`/`schedule` 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. +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 (including which ones actually run today), per-step cli/model selection, flows.json, parallel agents, the agent-relay dispatch transport, putting a local run on the Cloud dashboard with `--cloud-mirror`, and `flows check`/`run`/`deploy`/`schedule` 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 @@ -360,9 +360,9 @@ It isn't the `: readonly`/`: readwrite` annotation syntax that's rejected — it ``` flows check [--json] [--watch] -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 [--allow-human-influenced] [--json] [--no-spawn] [--no-observer-link] [--data-dir ] [--local-agent] +flows run [--json] [--no-spawn] [--no-observer-link] [--cloud-mirror] [--data-dir ] [--local-agent] +flows run [--json] [--no-spawn] [--no-observer-link] [--cloud-mirror] [--data-dir ] [--local-agent] --input +flows resume [--allow-human-influenced] [--json] [--no-spawn] [--no-observer-link] [--cloud-mirror] [--data-dir ] [--local-agent] flows deploy --repo --on [:key=value,...] [--on ...] --approver [--agents claude[,codex]] [--name ] [--draft] [--json] flows deployments [--json] flows undeploy [--json] @@ -392,6 +392,47 @@ flows run pr-reviewer.flow.ts --local-agent --data-dir /somewhere/outside/the/re **Now fixed, was broken through `2.0.16`** ([flows#461](https://github.com/AgentWorkforce/flows/issues/461), closed): the one-shot `flows run --cloud [--sync-code] --input ` form used to misroute authored TypeScript flows into the declarative-spec loader (`invalid_input: Cannot read or compile the declarative flow`) or refuse with a bare `http_error: HTTP 400`. Root cause: Cloud pinned an older `@relayflows/surface` than the CLI authored against, and refused the version mismatch with those opaque errors instead of naming it. Fixed in `2.0.17` (CLI side) — verified for real: `flows run --cloud --wait --input '{}'` now submits successfully and returns a real run ID instead of refusing immediately. If you still hit a bare `HTTP 400`/`invalid_input` on this path, you're likely on a CLI older than `2.0.17` — update first before assuming something else is wrong. (Cloud-side reporting of the exact version mismatch, when one still exists, was tracked as a separate follow-up PR at the time of writing — the CLI's own refusal is fixed either way.) +### Watching a local run: observer link vs `--cloud-mirror` + +New in **`2.0.32`**. A local run has two ways to be watched, and they are not the same thing: + +- **The observer link is the default.** Every `flows run` mints a read-only `ot_live_` link scoped to that run's own `wf-` channel and prints `Observer: `. It is free, needs only a workspace key, and carries a step projection. `--no-observer-link` suppresses it. +- **`--cloud-mirror` additionally puts the run on the Cloud dashboard** — the richer hosted view: the flow source, **every agent step's transcript**, the run graph, the run's own log, and the run sitting in the same history as your hosted ones. `FLOWS_CLOUD_MIRROR=1` turns it on for a whole shell. + +```bash +flows run review.flow.ts --local-agent --input '{}' # observer link only +flows run --cloud-mirror review.flow.ts --local-agent --input '{}' # ...and the dashboard +``` + +The run prints both, and the dashboard line names Cloud's run id as well as the page: + +``` +Observer: https://agentrelay.com/observer?key=ot_live_... +Dashboard: https://.../dashboard/workflow//runner · flows status --cloud --watch +``` + +**That second id matters and is easy to get wrong.** The report's own `runId` is the *journal's* ULID (`01M3B9...`); every hosted read verb — `flows status --cloud`, `flows logs`, `flows runs` — takes Cloud's UUID. Under `--json` both ride in the report as `cloudRunId` and `dashboardUrl`, beside `observerUrl`. Do not pass a journal id to `--cloud`. + +**Why it is opt-in, not on by default.** Because it is the richer view, it is also the one that *stores* all of that: source, step metadata, agent transcripts, and the CLI's own stderr. Transcripts are whatever the agent printed, including file contents and command output. Everything goes through the same redactor `flows status` uses — but redaction is pattern matching, and pattern matching has a false-negative rate. So the trigger is an explicit request and never the mere presence of a Cloud login. Only an affirmative counts for the env var (`1`/`true`/`on`/`yes`); `0`, empty, and anything nobody meant as a switch all leave the run local. + +**What it buys you, concretely:** the three read verbs start answering for *local* runs, which until `2.0.32` only worked for runs Cloud had launched. + +``` +$ flows status --cloud +RUN 27ab5702-... local-mirror-agent-confirmation completed spend 13,088 in / 61 out / $0.01836 + ✓ ask-claude agent 1 attempt 3.3s claude-haiku-4-5-20251001 · 1 turn · $0.01836 + ✓ ask-codex agent 1 attempt 7.9s +$ flows logs --step ask-claude # that step's transcript, out of Cloud storage +``` + +Three behaviours worth knowing before you rely on it: + +- **It cannot fail a run.** Every push collapses to a boolean; each poll and the whole finish are bounded. A Cloud outage costs the run its dashboard page and nothing else. If the mirror is refused you get one line naming which switch asked for it, and the run's exit code is untouched. +- **The dashboard says the run was local.** The row is `dispatchType: "local"`, and the run page shows **Ran on: Your machine** instead of a sandbox tile. Cancel is refused for it — Cloud mirrors a local run, it does not control it, so stop it where it is running. +- **A `--cloud-mirror` resume is a second dashboard row, linked to the first.** Cloud refuses to move a terminal run back to `running` (its own hosted resume mints a fresh id too), so the resumed attempt carries `resumedFromRunId` and the run page links the two — the parked "Needs review" row shows what continued it. The mapping lives in `/cloud-runs/`: the Cloud run id and the deployment, no credential, mode 0600, ageing out at 30 days. + +**Requires a Cloud login** (`agent-relay cloud login`, or `FLOWS_CLOUD_TOKEN`) — the same credential every other hosted verb uses. Without one, `--cloud-mirror` prints one line saying the run stays local and the run proceeds normally. A local run that joins no workspace is not a defect (RFC-0001 settled decision 7: the journal is the record, the workspace is one view onto it). + ### 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: @@ -440,11 +481,20 @@ Both are real, both are exit 2 — the same underlying problem can print a diffe | `flows run --local-agent [--input ...]` | CLI | actually executes; `.flow.ts` needs `--input`, agent/llm steps need `--local-agent` | | `flows deploy --repo ... --on ...` | CLI | persistent trigger-based listener; needs `agent-relay cloud login` plus a connected GitHub App + `--on` provider first, or `flow_repository_not_connected` | | `flows run --cloud --input ...` | CLI | fixed in `2.0.17` ([flows#461](https://github.com/AgentWorkforce/flows/issues/461)); update if you still see `http_error`/`invalid_input` | +| `flows run --cloud-mirror --local-agent` | CLI | `2.0.32`+; local run also on the Cloud dashboard, transcripts included. Opt-in; `FLOWS_CLOUD_MIRROR=1` for a shell. `--json` gains `cloudRunId`/`dashboardUrl` | +| `flows status --cloud` / `logs` / `runs` | CLI | take Cloud's **UUID**, not the journal ULID. Answer for mirrored local runs since `2.0.32` | | `flows schedule --cron ...` | CLI | cron-based cloud run | ## Verified against -**`@relayflows/surface@2.0.22` and `@relayflows/sdk@2.0.22`** — the current npm `latest` (there is no `2.0.23`). Every type, union and option in this skill was read directly from those packages' shipped `.d.ts` and `dist/*.js`, not from documentation or memory. +**`@relayflows/surface@2.0.22` and `@relayflows/sdk@2.0.22`** for everything except the `--cloud-mirror` section, which is **`2.0.32`** (see below). Every type, union and option in this skill was read directly from those packages' shipped `.d.ts` and `dist/*.js`, not from documentation or memory. + +**The `--cloud-mirror` section was verified on `2.0.32`, against production, with the published artifact.** `relayflows@2.0.32` was installed from npm into a scratch project — not run from a source tree — and two real local runs were mirrored to `agentrelay.com`: + +- a two-step deterministic flow, read back with `flows status --cloud`, `flows logs` (the `runner.log` round-trip) and `flows runs` (the run appearing in history beside hosted ones); +- a two-agent flow, one `cli: claude` step and one `cli: codex` step, spending a real `$0.01836` — whose **per-step transcripts were fetched back out of Cloud storage** and rendered in each provider's own frame vocabulary. That is the claim worth having evidence for: an echo-only flow exercises none of the transcript path. + +Two things that verification established and that are easy to assume otherwise. **Flows drives exactly two agent CLIs**: `adapters/index.ts` registers `claude` and `codex`, and anything else resolves to `relayflows-wrapper-v1`, which requires the executable to answer `--relayflows-adapter-v1` with a flows-specific token. A real `devin` CLI on `PATH` rejects that flag outright (`error: unexpected argument`), so it cannot be used as a `cli:` for an agent step no matter what is installed — adding one is a new `adapters/.ts` plus a registry entry. And the **observer projection has no retry**: `run-projection.ts` sets `failed = true` on the first error and every later publish is a no-op, so a transient `429 workspace_busy` — observed for real during this verification, while another run was launching in the same workspace — permanently loses the observer view for that run. The dashboard mirror survived the same window because it classifies `429` as transient and retries on its next poll. If an observer link opens an empty channel, that asymmetry is the first thing to check. This revision resolved a set of contradictions left by an earlier refresh that was verified at `2.0.16` and then merged with notes taken at `2.0.22`. Each was settled against the published package, and the losing side was deleted rather than hedged: `AgentOptions.permissions` exists on the TypeScript call site (added in `2.0.17`, validated and journaled, not enforced — preflight warns `permissions_unenforced`); `run(command, options?)` takes a second `{timeout}` argument; `f.done` takes the six `FLOW_COMPLETION_REASONS`; there is no `budget.maxWallclockMs` (`FlowHeader.budget` is `string | {tokens?, dollars?, wallclock?}`); predicate `.gate()` and the `artifact_exists` gate both ship; and `f.human` executes while `f.dispatch` still does not. Beyond static type-checking, the claims in this refresh that carry a **real run**, not just `flows check`, are backed by the recipes in [`AgentWorkforce/flows-cookbook`](https://github.com/AgentWorkforce/flows-cookbook) — each recipe's own README states exactly what was run, when, and what the result was (a real local run, a real Cloud deploy, or both), rather than duplicating that evidence here where it will go stale. If a claim in this skill and a cookbook recipe's README disagree, trust whichever was verified more recently — check the README's date. From bcfce30228a30cf18d76a8df1f45eb0268e7e13b Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Thu, 24 Sep 2026 21:10:56 -0700 Subject: [PATCH 2/2] fix(writing-relayflows): flows runs takes no run id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex and CodeRabbit both caught the same thing, and they are right: the section grouped `flows runs` with `flows status --cloud` and `flows logs` under "every hosted read verb takes Cloud's UUID". It does not take an id at all. Confirmed against 2.0.32's own usage: flows status --cloud [--json] [--watch] flows logs [--step ] [--raw] [--json] [--follow] flows runs [--limit ] [--json] Grouping it there pointed a reader at the wrong command shape. Worse, it buried the useful fact: `flows runs` is the way *out* of the id problem, because it lists runs newest-first with their Cloud UUIDs — which is how you find the id the other two want once you no longer have the terminal that printed it. The section and the quick-reference row now say that. Co-Authored-By: Claude Opus 5 (1M context) --- .agentworkforce/relay/pending-cleanups.json | 1 + .agentworkforce/relay/pending-cleanups.json.lock | 1 + skills/writing-relayflows/SKILL.md | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .agentworkforce/relay/pending-cleanups.json create mode 100644 .agentworkforce/relay/pending-cleanups.json.lock diff --git a/.agentworkforce/relay/pending-cleanups.json b/.agentworkforce/relay/pending-cleanups.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/.agentworkforce/relay/pending-cleanups.json @@ -0,0 +1 @@ +[] diff --git a/.agentworkforce/relay/pending-cleanups.json.lock b/.agentworkforce/relay/pending-cleanups.json.lock new file mode 100644 index 0000000..67c2e8e --- /dev/null +++ b/.agentworkforce/relay/pending-cleanups.json.lock @@ -0,0 +1 @@ +agent-relay-cleanup-lock v2 diff --git a/skills/writing-relayflows/SKILL.md b/skills/writing-relayflows/SKILL.md index e08d36c..ff19c01 100644 --- a/skills/writing-relayflows/SKILL.md +++ b/skills/writing-relayflows/SKILL.md @@ -411,7 +411,9 @@ Observer: https://agentrelay.com/observer?key=ot_live_... Dashboard: https://.../dashboard/workflow//runner · flows status --cloud --watch ``` -**That second id matters and is easy to get wrong.** The report's own `runId` is the *journal's* ULID (`01M3B9...`); every hosted read verb — `flows status --cloud`, `flows logs`, `flows runs` — takes Cloud's UUID. Under `--json` both ride in the report as `cloudRunId` and `dashboardUrl`, beside `observerUrl`. Do not pass a journal id to `--cloud`. +**That second id matters and is easy to get wrong.** The report's own `runId` is the *journal's* ULID (`01M3B9...`), while the two hosted verbs that take an id — `flows status --cloud ` and `flows logs ` — want Cloud's UUID. Do not pass a journal id to either. Under `--json` both ride in the report as `cloudRunId` and `dashboardUrl`, beside `observerUrl`. + +`flows runs` is the odd one out and the way *out* of this problem: it takes no id at all (`flows runs [--limit ] [--json]`) and lists the runs the credential can see, newest first, with each one's Cloud UUID — so it is how you find the id the other two want when you no longer have the terminal that printed it. **Why it is opt-in, not on by default.** Because it is the richer view, it is also the one that *stores* all of that: source, step metadata, agent transcripts, and the CLI's own stderr. Transcripts are whatever the agent printed, including file contents and command output. Everything goes through the same redactor `flows status` uses — but redaction is pattern matching, and pattern matching has a false-negative rate. So the trigger is an explicit request and never the mere presence of a Cloud login. Only an affirmative counts for the env var (`1`/`true`/`on`/`yes`); `0`, empty, and anything nobody meant as a switch all leave the run local. @@ -482,7 +484,8 @@ Both are real, both are exit 2 — the same underlying problem can print a diffe | `flows deploy --repo ... --on ...` | CLI | persistent trigger-based listener; needs `agent-relay cloud login` plus a connected GitHub App + `--on` provider first, or `flow_repository_not_connected` | | `flows run --cloud --input ...` | CLI | fixed in `2.0.17` ([flows#461](https://github.com/AgentWorkforce/flows/issues/461)); update if you still see `http_error`/`invalid_input` | | `flows run --cloud-mirror --local-agent` | CLI | `2.0.32`+; local run also on the Cloud dashboard, transcripts included. Opt-in; `FLOWS_CLOUD_MIRROR=1` for a shell. `--json` gains `cloudRunId`/`dashboardUrl` | -| `flows status --cloud` / `logs` / `runs` | CLI | take Cloud's **UUID**, not the journal ULID. Answer for mirrored local runs since `2.0.32` | +| `flows status --cloud ` / `flows logs ` | CLI | take Cloud's **UUID**, not the journal ULID. Answer for mirrored local runs since `2.0.32` | +| `flows runs [--limit ]` | CLI | takes **no id** — lists runs newest-first, which is how you find the UUID the two above want | | `flows schedule --cron ...` | CLI | cron-based cloud run | ## Verified against