Skip to content
Merged
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
102 changes: 52 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,74 @@
# relay(Flows)

**Step functions for coding agent workflows**

Agent Relay is building infrastructure for autonomous agents. A relayflow is a readable step function
that runs on the relay and produces a verifiable artifact or result that can be paused
for human input and resumed from any step wherever needed. It is an agentic pipeline
that can load in any model + harness along with deterministic gates to generate
reliable results.
**Turn a coding-agent task into steps you can inspect and verify.**

A flow combines shell commands and coding agents with a journal that records
what each step did and why it completed. Start with a small local flow; add
verification as the task grows.

```ts
import { flow } from "@relayflows/surface";

export default flow("fix-failing-tests", async (f) => {
const result = await f
.run("npm test 2>&1; echo EXIT:$?")
.gate((out) => !out.includes("EXIT:0"), "tests are already green, nothing to fix");

const fix = await f
.agent("fixer", {
task: `The test suite is failing. Diagnose and fix it:\n${result}`,
workspace: "src/**: readwrite",
})
.gate((r) => r.artifacts.length > 0, "the agent must actually change something");

f.done("success");
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.',
});
console.log(answer.summary);
f.done('success');
});
```

# Use Cases

Flows can be run locally or in production on our hosted cloud. We're built entire
applications using flows that are stacked to run in a sequence with review gates that
can run autonomously over days and weeks. Every agent session is observable and replayable.

- Cloud pipeline to use agents to generate a social media post. The pipeline coordinates agents who do research, verify the post, check for authenticity, generate graphics, and gate on a human approval — [`examples/social-post-pipeline/`](examples/social-post-pipeline/)
- Pull request review pipeline with different agents looking at the pull request from different angles (security, optimization etc) and agents communicate when needed to reach consensus — [`examples/pr-review-pipeline/`](examples/pr-review-pipeline/)
- Dependency upgrade bot: deterministic check flags a dependency out of date which fires an agent who does the upgrade in a sandbox. This upgrade is gated on another agent verifying the entire application with computer use in another sandbox. If completely verified a pull request is opened up — [`examples/dependency-upgrade-bot/`](examples/dependency-upgrade-bot/)

The new scaffolder in this branch creates the flow, `flows.json`, and an npm
project, then installs its dependencies:

# Get Started

Install the CLI, then the authoring package in your own project:
```sh
npm install -g relayflows
mkdir my-flow && cd my-flow && npm install @relayflows/surface
npx create-flow@latest my-flow
cd my-flow
npm start
```

Write a flow — save this as `hello.flow.ts`:
```ts
import { flow } from "@relayflows/surface";
**Release status:** `create-flow` is not published yet. The commands above are
the intended released entry point; use the [candidate artifact procedure](docs/evidence/ws13/README.md)
to try this branch. The [clone + deterministic starter measurement](docs/evidence/ws13/cold-clone-direct.txt)
completed in **49.975 seconds** in a fresh Linux container with Node and Git
provisioned before the timer. The [real Claude command](docs/evidence/ws13/agent-run.txt)
completed in **132.637 seconds** on an authenticated development host; its
agent step took 28.95 seconds, including the provider round trip. The total
also includes CLI startup and preflight, whose costs were not separately
measured.

The agent starter requires Node 22.18+ and an installed, authenticated Claude
CLI. Use `--cli codex` to select Codex, or `--template deterministic` for a
starter that needs no model credentials. The generated command is
`flows run my-flow.flow.ts --local-agent --input '{}'`.

`--local-agent` attaches the existing SDK agent worker to the local daemon.
It accepts stream-only agent steps and runs the chosen CLI with its existing
local access. Workspace revision pins and isolation require a worker that
provides those capabilities. Authored TypeScript bodies are not yet durably
resumable as a whole; each lowered step has its own journal run.

For SDK callers, the CLI is optional:

export default flow("hello", async (f) => {
await f.run('echo "hello from a relayflow"');
f.done("success");
});
```
```ts
import { createFlow } from '@relayflows/sdk/create-flow';
import { renderProgress } from '@relayflows/sdk/progress';

Run it:
```sh
flows run hello.flow.ts --input '{}'
await createFlow('./my-flow', { cli: 'claude' });
// renderProgress(events) returns terminal lines; callers own event delivery.
```

That's the whole loop — `flows run` spins up the local kernel itself on first use, no separate daemon step. You should see a completed run report.
See the [example gallery and individual run results](examples/README.md).
[Watch the captured agent run](docs/evidence/ws13/agent-run.cast)
([text transcript](docs/evidence/ws13/agent-run.txt)).

`f.run` and `f.agent` both actually dispatch today. `f.agent` runs a real coding-agent CLI the same way a declarative `type: agent` step does — it needs a `flows.json` in your project declaring which CLI to use (see `docs/SURFACE.md` §5 and `packages/sdk/src/cli/check.ts`'s `readProjectConfig`); without one, `flows run` refuses with a clear `agent_cli_unresolved` diagnostic rather than hanging. `f.llm`, `f.human`, `f.dispatch`, and `f.cloud` are still `docs/SURFACE.md`'s design surface, not yet runnable — see [`examples/`](examples/) for what the full shape looks like, and each example's own README for exactly what runs today versus what's still landing.
The gallery reports each requested example as PASS or BLOCKED, with its
command, output, timing, and any capability or provider requirement still missing.

Give your agent a skill to write a flow:

```sh
npx skills add https://github.com/agentworkforce/skills --skill writing-relayflows
```
148 changes: 148 additions & 0 deletions docs/evidence/ws13/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# WS-13 local development evidence

**Timing is accepted by Khaliq's ruling, not a blocker.** The measured cold
deterministic loop is 49.975s; the existing-host real Claude command is
132.637s. Its agent step is 28.95s including the provider round trip; the
remaining startup/preflight time was not separately measured, so the evidence
does not attribute most of the total to the provider.

**Named handoffs:** the release-gate owner must register `create-flow` in
versioning/packaging/publishing. The review-swarm/CI owner must restore fresh
maintainability, history and structure transcripts; all three are missing and
there is no approving independent signoff. After the PR left draft, Codex
and Cubic produced review findings; the lease-renewal P1 is addressed
in this branch, with [captured verification](followup/README.md). Those findings do not replace the missing swarm transcripts. Neither handoff is a reason to keep
the PR in draft once the gallery results are reported. No publishing work or
Cloud run-publication API is part of this follow-up.

The [current three-entry gallery](../../../examples/README.md) is **1 PASS,
2 BLOCKED** and supersedes the initial invocation results below. Research
completed with the default budget in [690.935s](followup/default-budget/gallery-research.txt);
the SDK flows still refuse unsupported budget headers in the
[corrected, verified launcher runs](review/README.md). The prior 0.138s/0.143s
captures were stale-launcher invocation refusals and had been misclassified. Research now reports each provider probe
and timeout on stderr. [Research regression tests](followup/research-tests.txt)
and [typecheck](followup/research-typecheck.txt) contain the commands/output.

## Initial captured results

| Check | Result | Evidence |
|---|---|---|
| SDK, API type tests, and test-source typechecks | Exit 0 | [Commands and output](typechecks.txt) |
| Clone + empty-cache scaffold + direct deterministic run on fresh Debian Trixie | Completed in 49.975s; Node/Git provisioning excluded; no agent | [Command and output](cold-clone-direct.txt) |
| Earlier clone-inclusive run using npx for the final invocation | Completed in 60.063s; misses the timing target | [Command and output](cold-clone-npx.txt) |
| Focused scaffolding/authored-flow/CLI tests | 121 passed | [Command and output](focused-tests.txt) |
| Built CLI + real daemon + scripted agent wrapper | 4 passed with isolated Node 22.22.2 | [Final command and output](local-agent-tests-final.txt) |
| First live-worker test attempt | 3 process timeouts, 1 passed | [Command and output](local-agent-tests-first-attempt.txt) |
| Real Claude invocation in the generated project | Completed, 132.637s for the command; existing authenticated macOS host | [Transcript](agent-run.txt), [asciicast v2 recording](agent-run.cast) |
| Earlier recording attempts | Auth probe timeout; then a broken host Node shared-library dependency | [Auth timeout](agent-probe-timeout.txt), [host failure](agent-host-node-failure.txt) |
| Empty-cache install + deterministic run in fresh Debian Trixie container | Completed in 43.374s; Node/image provisioning excluded, empty npm cache, deterministic template, no source clone or agent | [Command and output](cold-trixie.txt) |
| Empty-cache install + deterministic run in fresh Debian Bookworm container | Refused: published Linux daemon requires GLIBC_2.39; 55.223s | [Command and output](cold-container.txt) |
| Linux container test runner | esbuild Go runtime crashed under amd64 emulation before collecting tests | [Command, script and full output](container-tests.txt) |
| Research typecheck after correcting its compiler path | Superseded by the complete follow-up capture | [Command and output](followup/research-typecheck.txt) |

The [gallery table](../../../examples/README.md) reports the three requested
entries individually. Unsupported budget headers remain a capability-owner
handoff. The initial research attempt reached an outer 150-second limit with
no captured output; the follow-up now exposes preflight progress and captures
the shim's own failure or success result. No gallery declaration was weakened.

The cold-container transcripts include provisioning output followed by the
inner command’s elapsed value; `record.py` was used for the separate PTY
agent recordings, not to time the cold Docker commands. Node/image/Git
provisioning is excluded from those cold command timings.

The recording uses the initial packed implementation plus the npm bin fix.
Its agent step invokes the real installed Claude CLI. The host already had
Node, provider authentication, and dependencies; this is **not** a cold-machine
measurement. The recorded command does not include a clone or installation.
Text transcripts normalize terminal CRLF to LF and trim trailing whitespace; the `.cast` files retain the
captured terminal bytes and elapsed timestamps.

The functional CLI fixture has a 90-second cleanup ceiling. Its original
30-second process limit terminated a request while the worker still held a
live lease ([captured failure](local-agent-tests-30s-ceiling.txt)); startup and
preflight happen before that lease begins. Kernel lease behavior and the
separate 60-second cold-start criterion were not changed. Test-source types
were [checked again](test-types-final.txt) after fixing fixture binary discovery
to ask the existing Cargo wrapper for this worktree's target directory.

The local worker is stream-only. Each invocation declares a fresh stream at
offset zero and uses the existing `AgentWorker` and journal protocol. It
refuses workspace declarations rather than inventing revision pins. It is a
worker attached to the selected local daemon, not an OS sandbox. The executor
still lowers each authored step to a separate kernel run; whole-body durable
resume is not introduced by this change.

## Candidate artifacts, not a published release

`create-flow` is not published. The new CLI imports the SDK's lightweight
`/create-flow` export; `relayflows` imports `/cli`, so package lookup works with
both nested and hoisted npm dependencies. Runtime packages stop registering
their legacy bundled executable as the competing npm `flows` command.
[The original artifact failure](launcher-before-fix.txt) is retained.

Build and pack from this branch with Node 22.18+:

```sh
npm --prefix packages/sdk ci --ignore-scripts
npm --prefix packages/sdk run build
mkdir -p /tmp/ws13-artifacts
npm pack --ignore-scripts --pack-destination /tmp/ws13-artifacts ./packages/sdk
npm pack --ignore-scripts --pack-destination /tmp/ws13-artifacts ./packages/create-flow
npm pack --ignore-scripts --pack-destination /tmp/ws13-artifacts ./packages/relayflows
```

For full installation testing, stage each runtime package's `bin/` from the
published 2.0.8 package before packing the updated runtime manifest. This
session reused those published binaries; it did not rebuild or change Rust.
The runtime tarballs retain legacy `bin/flows` because the existing release
gate requires it, while their npm `bin` maps now export only `relayflowd`.
The Debian failure above belongs to that published binary's libc requirement.

Serve all candidate tarballs locally:

```sh
node docs/evidence/ws13/stage-registry.mjs /tmp/ws13-artifacts 48734
```

The registry binds to loopback by default. For Docker access, explicitly
add the bind host: `node docs/evidence/ws13/stage-registry.mjs /tmp/ws13-artifacts 48734 0.0.0.0`.

In a separate terminal, point npm at that registry; dependencies outside this
branch redirect to the public npm registry:

```sh
npm_config_registry=http://127.0.0.1:48734 npx --yes create-flow@latest /tmp/my-flow
cd /tmp/my-flow
npm start
```

The final served tarballs match the SHA-256 values in [artifacts.json](artifacts.json).
[Packed-file hash check](artifact-check.txt). Restart the registry after repacking; it freezes package metadata and tarball bytes
at startup. Earlier cold recordings used the SDK-root launcher; the final
clone-inclusive recording uses the lightweight SDK/cli launcher.

`cold-start.sh` uses the same registry with the deterministic template and an
empty cache. `record.py` captures real process output as an asciicast and text
transcript. Neither script silently converts a refusal into a successful run.

## Scope and release blockers

Base: `origin/main` at `f0a3b3b` (2.0.8), isolated branch
`feat/flows-local-dev-ux`. Both #243 and #244 diffs were inspected before SDK
edits. #243 is now merged; #244 remains open. Overlap with #243 is README.md,
`packages/sdk/src/authored-flow-executor.ts`, and `packages/sdk/src/cli/direct-run.ts`.
There is no file overlap with #244's inspected diff. Existing executor error,
output, gate, and lifecycle behavior is reused; its pre-existing size was not
expanded into an unrelated refactor.

The independent release-gate owner must add `create-flow` to package versioning
and publishing, and to `scripts/pack-release.mjs`, which currently refuses that
package name. That script also requires the legacy runtime executable.
Those gates were not edited. No package was published and no merge is allowed. PR #247 is ready for review,
not in draft; publishing and review are named handoffs.

Veto tools were not exposed. Relay queue receipts did not establish delivery;
the coordinator confirmed the original handoff never arrived. The PR and this
evidence directory are the durable handoff.
3 changes: 3 additions & 0 deletions docs/evidence/ws13/agent-host-node-failure.cast
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"version": 2, "width": 120, "height": 30, "timestamp": 1788873468, "title": "Relayflows local development", "command": "npx --no-install flows run hello.flow.ts --local-agent --input '{}'", "env": {"TERM": "xterm-256color"}}
[0.052747, "o", "dyld[9200]: Library not loaded: /opt/homebrew/opt/ada-url/lib/libada.3.dylib\r\n Referenced from: <87FBC746-7D47-3FD8-B0A3-97018CBF954B> /opt/homebrew/Cellar/node/26.5.0/bin/node\r\n Reason: tried: '/opt/homebrew/opt/ada-url/lib/libada.3.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/opt/ada-url/lib/libada.3.dylib' (no such file), '/opt/homebrew/opt/ada-url/lib/libada.3.dylib' (no such file), '/opt/homebrew/Cellar/ada-url/4.0.0/lib/libada.3.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/Cellar/ada-url/4.0.0/lib/libada.3.dylib' (no such file), '/opt/homebrew/Cellar/ada-url/4.0.0/lib/libada.3.dylib' (no such file)\r\n"]
[0.052983, "o", "\r\nEXIT_CODE=-6\r\nELAPSED_SECONDS=0.053\r\nTIMED_OUT=False\r\n"]
9 changes: 9 additions & 0 deletions docs/evidence/ws13/agent-host-node-failure.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$ cd /tmp/ws13-consumer/hello
$ npx --no-install flows run hello.flow.ts --local-agent --input '{}'
dyld[9200]: Library not loaded: /opt/homebrew/opt/ada-url/lib/libada.3.dylib
Referenced from: <87FBC746-7D47-3FD8-B0A3-97018CBF954B> /opt/homebrew/Cellar/node/26.5.0/bin/node
Reason: tried: '/opt/homebrew/opt/ada-url/lib/libada.3.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/opt/ada-url/lib/libada.3.dylib' (no such file), '/opt/homebrew/opt/ada-url/lib/libada.3.dylib' (no such file), '/opt/homebrew/Cellar/ada-url/4.0.0/lib/libada.3.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/Cellar/ada-url/4.0.0/lib/libada.3.dylib' (no such file), '/opt/homebrew/Cellar/ada-url/4.0.0/lib/libada.3.dylib' (no such file)

EXIT_CODE=-6
ELAPSED_SECONDS=0.053
TIMED_OUT=False
10 changes: 10 additions & 0 deletions docs/evidence/ws13/agent-probe-timeout.cast
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{"version": 2, "width": 120, "height": 30, "timestamp": 1788873089, "title": "Relayflows local development", "command": "npx --no-install flows run hello.flow.ts --local-agent --input '{}'", "env": {"TERM": "xterm-256color"}}
[5.045106, "o", "npm notice run npx\r\n"]
[5.045358, "o", "npm notice run 'flows' run hello.flow.ts --local-agent --input {}\r\n"]
[7.742345, "o", "\u25cb run-1 (deterministic) 0.00s\r\n"]
[7.953248, "o", "\u2713 run-1 (deterministic) 0.21s completionReason: success\r\n"]
[7.955388, "o", "Hello from Relayflows\r\n"]
[7.955632, "o", "\u25cb agent-2 (agent) [agent: preparing] 0.00s\r\n"]
[18.136722, "o", "\u2717 agent-2 (agent) [agent: failed] 10.18s\r\n"]
[18.16956, "o", "REFUSED [invalid_spec] agent_cli_unresolved: Could not verify CLI \"claude\" for step \"agent-2\": the probe timed out after 10000ms.\r\n"]
[18.173621, "o", "\r\nEXIT_CODE=2\r\nELAPSED_SECONDS=18.174\r\nTIMED_OUT=False\r\n"]
14 changes: 14 additions & 0 deletions docs/evidence/ws13/agent-probe-timeout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
$ cd /tmp/ws13-consumer/hello
$ npx --no-install flows run hello.flow.ts --local-agent --input '{}'
npm notice run npx
npm notice run 'flows' run hello.flow.ts --local-agent --input {}
○ run-1 (deterministic) 0.00s
✓ run-1 (deterministic) 0.21s completionReason: success
Hello from Relayflows
○ agent-2 (agent) [agent: preparing] 0.00s
✗ agent-2 (agent) [agent: failed] 10.18s
REFUSED [invalid_spec] agent_cli_unresolved: Could not verify CLI "claude" for step "agent-2": the probe timed out after 10000ms.

EXIT_CODE=2
ELAPSED_SECONDS=18.174
TIMED_OUT=False
Loading
Loading