From 0141dc50a037327d22974b73e75308b9b5be1e79 Mon Sep 17 00:00:00 2001 From: agentrelaybot Date: Sat, 19 Sep 2026 14:35:25 -0700 Subject: [PATCH 1/4] docs: document node-claim guard and provider subscriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover two behaviors that shipped without docs: - agent-relay 12.3.0's machine-local enrollment claim: `node up` now refuses a second broker for an already-claimed node id (with `--force` as the deliberate takeover), documented on the broker lifecycle page and cross-linked from Nodes → Enrollment And Identity. - `agent-relay integration subscribe` as a user-facing recipe: bind a provider resource to an agent/channel, events materialize through Relayfile and wake the recipient, deliveries queue and replay across reconnects. New page under Automation with a pointer from Webhooks. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- web/content/docs/cli-broker-lifecycle.mdx | 15 +++++ web/content/docs/nodes.mdx | 2 + web/content/docs/provider-subscriptions.mdx | 63 +++++++++++++++++++++ web/content/docs/webhooks.mdx | 3 +- web/lib/docs-nav.ts | 1 + 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 web/content/docs/provider-subscriptions.mdx diff --git a/web/content/docs/cli-broker-lifecycle.mdx b/web/content/docs/cli-broker-lifecycle.mdx index b97f16e5..be470bb4 100644 --- a/web/content/docs/cli-broker-lifecycle.mdx +++ b/web/content/docs/cli-broker-lifecycle.mdx @@ -38,6 +38,7 @@ Flags: | `--workspace-key ` | Join a pre-existing Relay workspace. | | `--state-dir ` | Write runtime state outside `.agentworkforce/relay/`. | | `--broker-name ` | Override the broker identity. Defaults to the project directory basename. | +| `--force` | Take the enrolled node over from another live broker on this machine, evicting that broker's delivery socket. See the claim guard below. | | `--verbose` | Enable verbose startup logging (raises the node log level to `debug`). | | `--log-file ` | Write structured node logs — each capability registered and every action invoked/completed — to a file. | | `--log-level ` | Node log verbosity: `debug` \| `info` \| `warn` \| `error` (default `info`). | @@ -53,6 +54,20 @@ agent-relay node up --background --workspace-key "$RELAY_WORKSPACE_KEY" The broker listens on a local API port starting from `3888` (override with `AGENT_RELAY_BROKER_PORT`). If this machine was enrolled as a Cloud-managed node with `agent-relay cloud enroll`, `node up` picks up the persisted enrollment automatically and serves under the enrolled node name. +### One broker per enrolled node + +An enrolled node has exactly one Cloud delivery socket, so two local brokers must never serve the same node id at once — the second registration would evict the first broker's socket and it would silently stop receiving messages. `node up` prevents this with a machine-local claim under `~/.agentworkforce/relay/node-claims/`: the first broker to start claims the enrolled node id, and a later `node up` for the same node — including one pinned with `RELAY_NODE_ID` — refuses and names the holding broker's pid and state directory. + +When the refusal is wrong or the takeover is intended, you have three options: + +```bash +agent-relay node down --state-dir # stop the running broker +agent-relay node up --workspace-key # serve a different enrolled node +agent-relay node up --force # take the node over anyway +``` + +`--force` intentionally evicts the incumbent's delivery socket — the old broker stays up but no longer receives realtime delivery. A claim left by a crashed or rebooted broker never blocks a restart, and `--local-only` claims nothing at all. `node down` releases the claim when the broker exits cleanly. + ## Check Status ```bash diff --git a/web/content/docs/nodes.mdx b/web/content/docs/nodes.mdx index abf6ca6c..6b0f01e4 100644 --- a/web/content/docs/nodes.mdx +++ b/web/content/docs/nodes.mdx @@ -215,6 +215,8 @@ A node enrolls with `POST /v1/nodes` using the workspace key. The request carrie A node id supplied or pinned by an operator (`node_id` in the enroll request, used with its node token) is taken as-is. Otherwise the id derives from the machine identity, the working directory, and the workspace, so several nodes on one host — for example one per project directory — do not collide. +The agent runtime additionally guards enrolled ids machine-locally: a running broker records a claim for its enrolled node id, and a second broker starting for the same node on that host is refused rather than allowed to evict the incumbent's delivery socket. See [Broker lifecycle → One broker per enrolled node](/docs/cli-broker-lifecycle#one-broker-per-enrolled-node). + ## Presence And Context Workspace observers see node presence events as `node.online`, `node.heartbeat`, and `node.offline`. Each event carries a node payload matching the roster entry. diff --git a/web/content/docs/provider-subscriptions.mdx b/web/content/docs/provider-subscriptions.mdx new file mode 100644 index 00000000..e4fa8fb2 --- /dev/null +++ b/web/content/docs/provider-subscriptions.mdx @@ -0,0 +1,63 @@ +--- +title: 'Provider Subscriptions' +description: 'Bind a provider resource — a GitHub repo, a Linear team, a Slack channel — to an agent or channel so provider events wake it directly.' +--- + +Provider subscriptions wire an external resource to a Relay recipient. When an event fires on the provider — an issue opened, a comment posted — it materializes through Relayfile as mounted files and arrives as a Relay message that wakes the bound agent. + +```bash +agent-relay integration subscribe github \ + --to @watcher \ + --resource AgentWorkforce/software-garden \ + --events issues,issue_comment +``` + +The recipient can be an agent (`@watcher`) or a channel (`#triage`). `@watcher` does not need to exist yet — see `--spawn` below. + +## What the agent sees + +Each event arrives as a message from the provider identity (for example `github`) naming the event type and the Relayfile path that holds the payload: + +``` +Relay message from github: Github issue_comment.created +Relayfile path: /github/repos/AgentWorkforce/software-garden/issues/531/comments/5744794683/meta.json +``` + +The agent reads the mounted file for the full payload — the same tree documented in [Relayfile integrations](/docs/file/integrations). It can answer in-channel, which posts back to the issue or thread when the provider supports writeback. + +Events that arrive while the node's delivery socket is down are queued durably and replayed to the session on reconnect, so a subscription survives broker restarts without losing events. See [Delivery](/docs/delivery). + +## Spawning a recipient + +`--spawn ` launches a fresh agent and confirms it is live before subscribing: + +```bash +agent-relay integration subscribe github \ + --resource AgentWorkforce/software-garden \ + --events issues,issue_comment \ + --spawn claude --task "Triage new issue comments and reply with a summary." +``` + +## Managing subscriptions + +```bash +agent-relay integration subscribe --list # active bindings +agent-relay integration unsubscribe github --resource AgentWorkforce/software-garden +``` + +Unsubscribing removes the resource binding and its inbound webhook; agents and channels are untouched. + + + + The event vocabulary subscriptions and listeners share. + + + How provider resources become files an agent can read and watch. + + + Sequencing, acks, and reconnect replay for node-delivered events. + + + The full `integration` command surface. + + diff --git a/web/content/docs/webhooks.mdx b/web/content/docs/webhooks.mdx index 043def82..d2f0cede 100644 --- a/web/content/docs/webhooks.mdx +++ b/web/content/docs/webhooks.mdx @@ -10,7 +10,8 @@ directions, both under the `relay.webhooks` namespace: - **Outbound:** subscribe your service to Relay events. Relay POSTs HMAC-signed event payloads to your URL. Provider connections (Slack, GitHub App installs, and similar) live under the separate `relay.integrations` -namespace — don't conflate it with webhooks. +namespace — don't conflate it with webhooks. To wake an agent on provider events like GitHub issues and +comments, use [Provider subscriptions](/docs/provider-subscriptions). ## Inbound: external services into Relay diff --git a/web/lib/docs-nav.ts b/web/lib/docs-nav.ts index f10ac396..351861cf 100644 --- a/web/lib/docs-nav.ts +++ b/web/lib/docs-nav.ts @@ -41,6 +41,7 @@ export const docsNav: NavGroup[] = [ { title: 'Events', slug: 'events' }, { title: 'Event handlers', slug: 'event-handlers' }, { title: 'Webhooks', slug: 'webhooks' }, + { title: 'Provider subscriptions', slug: 'provider-subscriptions' }, ], }, { From aa9bc7490b5611e7d725d912f75f596aaed3ef1a Mon Sep 17 00:00:00 2001 From: agentrelaybot Date: Sat, 19 Sep 2026 14:45:06 -0700 Subject: [PATCH 2/4] docs: tighten node-claim and provider-subscription prose Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- web/content/docs/cli-broker-lifecycle.mdx | 8 ++++---- web/content/docs/nodes.mdx | 2 +- web/content/docs/provider-subscriptions.mdx | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/web/content/docs/cli-broker-lifecycle.mdx b/web/content/docs/cli-broker-lifecycle.mdx index be470bb4..407273ec 100644 --- a/web/content/docs/cli-broker-lifecycle.mdx +++ b/web/content/docs/cli-broker-lifecycle.mdx @@ -38,7 +38,7 @@ Flags: | `--workspace-key ` | Join a pre-existing Relay workspace. | | `--state-dir ` | Write runtime state outside `.agentworkforce/relay/`. | | `--broker-name ` | Override the broker identity. Defaults to the project directory basename. | -| `--force` | Take the enrolled node over from another live broker on this machine, evicting that broker's delivery socket. See the claim guard below. | +| `--force` | Take the enrolled node over from a live broker on this machine; evicts that broker's delivery socket. | | `--verbose` | Enable verbose startup logging (raises the node log level to `debug`). | | `--log-file ` | Write structured node logs — each capability registered and every action invoked/completed — to a file. | | `--log-level ` | Node log verbosity: `debug` \| `info` \| `warn` \| `error` (default `info`). | @@ -56,9 +56,9 @@ The broker listens on a local API port starting from `3888` (override with `AGEN ### One broker per enrolled node -An enrolled node has exactly one Cloud delivery socket, so two local brokers must never serve the same node id at once — the second registration would evict the first broker's socket and it would silently stop receiving messages. `node up` prevents this with a machine-local claim under `~/.agentworkforce/relay/node-claims/`: the first broker to start claims the enrolled node id, and a later `node up` for the same node — including one pinned with `RELAY_NODE_ID` — refuses and names the holding broker's pid and state directory. +An enrolled node has one Cloud delivery socket. If two brokers served the same node id, the second registration would evict the first broker's socket and it would silently stop receiving messages. `node up` prevents this with a machine-local claim in `~/.agentworkforce/relay/node-claims/`: the first broker claims its enrolled node id, and a later `node up` for that node — including one pinned with `RELAY_NODE_ID` — refuses and names the holding broker's pid and state directory. -When the refusal is wrong or the takeover is intended, you have three options: +If the refusal is wrong, or you want the takeover: ```bash agent-relay node down --state-dir # stop the running broker @@ -66,7 +66,7 @@ agent-relay node up --workspace-key # serve a different en agent-relay node up --force # take the node over anyway ``` -`--force` intentionally evicts the incumbent's delivery socket — the old broker stays up but no longer receives realtime delivery. A claim left by a crashed or rebooted broker never blocks a restart, and `--local-only` claims nothing at all. `node down` releases the claim when the broker exits cleanly. +`--force` evicts the incumbent's delivery socket: the old broker keeps running but stops receiving realtime delivery. A crashed broker's stale claim never blocks a restart, `--local-only` claims nothing, and `node down` releases the claim on clean exit. ## Check Status diff --git a/web/content/docs/nodes.mdx b/web/content/docs/nodes.mdx index 6b0f01e4..83499e99 100644 --- a/web/content/docs/nodes.mdx +++ b/web/content/docs/nodes.mdx @@ -215,7 +215,7 @@ A node enrolls with `POST /v1/nodes` using the workspace key. The request carrie A node id supplied or pinned by an operator (`node_id` in the enroll request, used with its node token) is taken as-is. Otherwise the id derives from the machine identity, the working directory, and the workspace, so several nodes on one host — for example one per project directory — do not collide. -The agent runtime additionally guards enrolled ids machine-locally: a running broker records a claim for its enrolled node id, and a second broker starting for the same node on that host is refused rather than allowed to evict the incumbent's delivery socket. See [Broker lifecycle → One broker per enrolled node](/docs/cli-broker-lifecycle#one-broker-per-enrolled-node). +The agent runtime also claims the enrolled id locally: a running broker records a claim, and a second broker for the same node on that host is refused instead of evicting the incumbent's delivery socket. See [Broker lifecycle → One broker per enrolled node](/docs/cli-broker-lifecycle#one-broker-per-enrolled-node). ## Presence And Context diff --git a/web/content/docs/provider-subscriptions.mdx b/web/content/docs/provider-subscriptions.mdx index e4fa8fb2..16780d2a 100644 --- a/web/content/docs/provider-subscriptions.mdx +++ b/web/content/docs/provider-subscriptions.mdx @@ -1,9 +1,9 @@ --- title: 'Provider Subscriptions' -description: 'Bind a provider resource — a GitHub repo, a Linear team, a Slack channel — to an agent or channel so provider events wake it directly.' +description: 'Bind a provider resource — a repo, a team, a channel — to an agent so provider events wake it.' --- -Provider subscriptions wire an external resource to a Relay recipient. When an event fires on the provider — an issue opened, a comment posted — it materializes through Relayfile as mounted files and arrives as a Relay message that wakes the bound agent. +Provider subscriptions bind an external resource to a Relay recipient. When an event fires — an issue opened, a comment posted — Relayfile writes it to the mounted tree and Relay delivers it as a message that wakes the recipient. ```bash agent-relay integration subscribe github \ @@ -12,24 +12,24 @@ agent-relay integration subscribe github \ --events issues,issue_comment ``` -The recipient can be an agent (`@watcher`) or a channel (`#triage`). `@watcher` does not need to exist yet — see `--spawn` below. +The recipient is an agent (`@watcher`) or a channel (`#triage`). The agent does not need to exist yet; `--spawn` creates it. ## What the agent sees -Each event arrives as a message from the provider identity (for example `github`) naming the event type and the Relayfile path that holds the payload: +Each event is a message from the provider identity (`github`) with the event type and the Relayfile path holding the payload: ``` Relay message from github: Github issue_comment.created Relayfile path: /github/repos/AgentWorkforce/software-garden/issues/531/comments/5744794683/meta.json ``` -The agent reads the mounted file for the full payload — the same tree documented in [Relayfile integrations](/docs/file/integrations). It can answer in-channel, which posts back to the issue or thread when the provider supports writeback. +The mounted file holds the full payload — the same tree as [Relayfile integrations](/docs/file/integrations). An in-channel reply posts back to the issue or thread when the provider supports writeback. -Events that arrive while the node's delivery socket is down are queued durably and replayed to the session on reconnect, so a subscription survives broker restarts without losing events. See [Delivery](/docs/delivery). +Events that arrive while the node is disconnected queue durably and replay on reconnect, so a subscription survives broker restarts. See [Delivery](/docs/delivery). ## Spawning a recipient -`--spawn ` launches a fresh agent and confirms it is live before subscribing: +`--spawn ` launches a new agent and confirms it is live before subscribing: ```bash agent-relay integration subscribe github \ @@ -45,7 +45,7 @@ agent-relay integration subscribe --list # act agent-relay integration unsubscribe github --resource AgentWorkforce/software-garden ``` -Unsubscribing removes the resource binding and its inbound webhook; agents and channels are untouched. +Unsubscribing removes the binding and its inbound webhook; agents and channels are untouched. From 72217b39e69cbf3e78f10d365b1f1b3fd709c51b Mon Sep 17 00:00:00 2001 From: khaliqgant Date: Thu, 24 Sep 2026 16:35:11 -0700 Subject: [PATCH 3/4] feat(onboarding): let Linear sources wake on assignment, not just creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assigning a ticket to the agent's connected app user is the delegation gesture — Cloud listens for it as AppUserNotification.issueAssignedToYou (linear:events=assigned|all) — but onboarding's Linear source only offered team/project/labels, so the choice was unreachable from the form. - Linear gains a "Wake on" select: New issues (default) / Issues assigned to the agent / New or assigned issues - SourcePicker renders a select for fields that carry options - validSourcePreferences constrains choice fields to their options (or blank-as-default), so a stale stored value fails validation the same way an unknown field does - sourceSummary names the chosen option, not its storage value - events is excluded from local filter rules and the prefilled local ticket: it is what wakes the Cloud listener, never a field an issue carries — a local run has no dispatcher to interpret it Verified: 209 web tests pass (incl. new pins for the select options, validation, summary label, and no events in local code or input), tsc --noEmit clean. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- web/app/flows/onboarding/SourcePicker.tsx | 15 ++++++--- .../flows/onboarding/onboarding.module.css | 4 +-- web/lib/flow-local.ts | 4 ++- web/lib/flow-sources.ts | 28 +++++++++++++--- web/lib/test/flow-sources.test.ts | 32 ++++++++++++++++++- 5 files changed, 71 insertions(+), 12 deletions(-) diff --git a/web/app/flows/onboarding/SourcePicker.tsx b/web/app/flows/onboarding/SourcePicker.tsx index 4a13946e..9ddf1fad 100644 --- a/web/app/flows/onboarding/SourcePicker.tsx +++ b/web/app/flows/onboarding/SourcePicker.tsx @@ -63,10 +63,17 @@ export function SourcePicker({ draft, onChange, onTrack }: { draft: FactoryDraft
{source.fields.map(field =>
- update({ [field.key]: event.target.value })} - aria-describedby={field.key === 'labels' ? 'source-labels-help' : undefined} /> - {field.key === 'labels' && Separate with commas. Every label must match.} + {'options' in field + ? + : <> + update({ [field.key]: event.target.value })} + aria-describedby={field.key === 'labels' ? 'source-labels-help' : undefined} /> + {field.key === 'labels' && Separate with commas. Every label must match.} + }
)}
{source.id === 'slack' &&