You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A flow that opens a pull request can't stay with it. Keeping a PR alive means waking on review comments, CI results, and pushes to the base branch, fixing what they reveal, and stopping when the PR is merged, closed, or ready. Software Garden does this with a long-lived babysitter process. A relayflow should do it with the resident verb on: "the handler wakes, executes to its next await, parks" (SURFACE.md §1).
There is never a signal meaning "no more feedback". A flow has to end on provider state and use time only as a backstop, and the spec doesn't give a way to express either inside a running body.
What's missing today
Verified against main at 85e7e372 (v2.0.14):
Nothing produces wait.event.kernel/DESIGN.md §1.4 defines it and relayflowd-core/src/state.rs folds it into Waiting, but no step or verb ever appends one. Only wait.human is produced, for manual recovery.
timeout_at_ms is never enforced. It exists on WaitEventPayload and WaitHumanPayload and nothing reads it, so a wait with a timeout waits forever.
Events without an open wait are dropped.engine/remote.rsemit_event only closes waits that are already open. It returns matched: 0 and journals nothing, so an event that arrives while the body is running a fix step is lost.
Buffered subscriptions. A body-level on opens a subscription (subscription.opened / subscription.closed). Matching events are appended to a durable stream, deduplicated by provider delivery id, so nothing is lost while other steps run.
Stream-backed waits.next() lowers to an additive wait.event extension with stream, from_offset, settle_ms, idle_at_ms, and deadline_at_ms. Timeouts come back as timeout with result.timeout: "idle" | "deadline", so the reason enum doesn't change.
Required bounds.idle and deadline must both be set; flows check refuses an unbounded subscription with unbounded_subscription.
Wakes, not truth. The body re-reads provider state after every wake and before every wait.
Self-events filtered by default. Events caused by the run's own identity aren't delivered, so a babysitter's push doesn't wake itself.
Routing stays in Cloud. Cloud's event router routes frames to open subscriptions; the kernel stays tenant-unaware (decision 15).
Acceptance
Crash-injection tests prove:
An event emitted while a step is running is delivered by the next next().
kill -9 between stream.appended and wait completion delivers the event exactly once after resume.
An idle instant that passes during an outage wakes immediately on resume, without re-running earlier steps.
idle resets on each wake; deadline never moves.
Events arriving within settle produce one wake that carries all of them.
A duplicate delivery id appends nothing.
An event caused by the run's own identity is not delivered unless includeSelf is set.
After close() or done(), matching frames are refused.
A body-level on without idle or deadline fails flows check with unbounded_subscription.
wait.human with timeout_at_ms completes with timeout.
Motivation
This came from comparing the Cloud one-click Relayflow software factory with Software Garden. The Relayflow opens a PR and parks as needs_human: nothing watches CI or review feedback afterwards, and nothing reports back. Watching the PR from inside the run needs this primitive.
Problem
A flow that opens a pull request can't stay with it. Keeping a PR alive means waking on review comments, CI results, and pushes to the base branch, fixing what they reveal, and stopping when the PR is merged, closed, or ready. Software Garden does this with a long-lived babysitter process. A relayflow should do it with the resident verb
on: "the handler wakes, executes to its next await, parks" (SURFACE.md §1).There is never a signal meaning "no more feedback". A flow has to end on provider state and use time only as a backstop, and the spec doesn't give a way to express either inside a running body.
What's missing today
Verified against
mainat85e7e372(v2.0.14):wait.event.kernel/DESIGN.md§1.4 defines it andrelayflowd-core/src/state.rsfolds it intoWaiting, but no step or verb ever appends one. Onlywait.humanis produced, for manual recovery.timeout_at_msis never enforced. It exists onWaitEventPayloadandWaitHumanPayloadand nothing reads it, so a wait with a timeout waits forever.engine/remote.rsemit_eventonly closes waits that are already open. It returnsmatched: 0and journals nothing, so an event that arrives while the body is running a fix step is lost.on.packages/surface/src/context.tsexposeshuman,dispatch, anddone, and the first two throwunsupported_verb(Support durable human approval gates in authored TypeScript flows #400).wait.eventmatches an exactevent_key, while triggers already carry a recursive-subsetpattern.Proposal
Spec:
docs/EVENT-AWAIT.md(PR to follow). In short:onopens a subscription (subscription.opened/subscription.closed). Matching events are appended to a durable stream, deduplicated by provider delivery id, so nothing is lost while other steps run.next()lowers to an additivewait.eventextension withstream,from_offset,settle_ms,idle_at_ms, anddeadline_at_ms. Timeouts come back astimeoutwithresult.timeout: "idle" | "deadline", so the reason enum doesn't change.idleanddeadlinemust both be set;flows checkrefuses an unbounded subscription withunbounded_subscription.wait.human, which also unblocks Support durable human approval gates in authored TypeScript flows #400.Acceptance
Crash-injection tests prove:
next().kill -9betweenstream.appendedand wait completion delivers the event exactly once after resume.idleinstant that passes during an outage wakes immediately on resume, without re-running earlier steps.idleresets on each wake;deadlinenever moves.settleproduce one wake that carries all of them.includeSelfis set.close()ordone(), matching frames are refused.onwithoutidleordeadlinefailsflows checkwithunbounded_subscription.wait.humanwithtimeout_at_mscompletes withtimeout.Motivation
This came from comparing the Cloud one-click Relayflow software factory with Software Garden. The Relayflow opens a PR and parks as
needs_human: nothing watches CI or review feedback afterwards, and nothing reports back. Watching the PR from inside the run needs this primitive.Related: #400 (durable human approval gates).