Skip to content

Platform-level relay reply: every agent converses over the relay by default #254

Closed

Description

@khaliqgant

Problem

Deployed agents can receive relay DMs today (persona relay.inbox: ['@self'] → cloud inbox_selectors → candidates → deliver → tick wakes the box), but they cannot reply over the relay. A relay DM from a local/peer agent wakes the target, but the answer goes to the wrong place:

  • @agentworkforce/delivery only knows 'slack' | 'telegram' targets (DeliveryClient.targets, resolveDeliveryTargets, onlyTargets, SlackRef/TelegramRef are all hardcoded to those two).
  • So an agent's relay Q&A branch (e.g. hn-monitor handleQaMessage(provider:'relay')) replies via the delivery package to Slack, never back to the relay sender. hn-monitor.parseRelayMessage even drops the sender.
  • There is no relaycast writeback provider in @relayfile/adapter-core (the catalog is external integrations only), and no handler-side relay-send primitive.

Net: "local agent ↔ deployed agent over the relay" doesn't round-trip. Patching each persona (hn-monitor, then inbox-buddy, then every future agent) is the wrong layer. Every agent should be on the relay and able to converse by default, with zero per-persona code.

Proposed design (workforce)

1. packages/delivery — add relaycast as a first-class target.

  • Widen the target union 'slack' | 'telegram' → 'slack' | 'telegram' | 'relaycast' across DeliveryClient.targets, resolveDeliveryTargets(ctx), createDelivery(ctx, transports?, onlyTargets?), and add RelaycastRef alongside SlackRef/TelegramRef.
  • The relaycast transport posts via the relaycast API using RELAY_API_KEY + RELAY_WORKSPACE_ID (already injected into every box by packages/core launcher in cloud) to the inbound relay event's channel/threadId (carried on RelaycastMessageEvent — @agent-relay/events).
  • resolveDeliveryTargets includes relaycast when the current run is handling a relay DM (origin-transport reply), so the existing "reply to origin transport" pattern in hn-monitor/inbox-buddy/joke-bot covers relay automatically — no persona changes.

2. packages/runtime — expose relay reply on ctx.

  • Add ctx.relay.reply(text) / ctx.relay.send(toAgent, text) sugar for agents that don't use the delivery package, backed by the same relaycast client.
  • Surface the inbound sender + channel/thread on the relay event so replies address the right DM/thread (today RelaycastMessageEvent carries channel/messageId but personas re-parse ad hoc and drop the sender).

3. (cross-repo) AgentWorkforce/cloud — relay-on by default.

  • Default inbox_selectors to ['@self'] for every deployed agent in translatePersonaRelayToInboxSelectors (packages/web/lib/proactive-runtime/inbox-selectors.ts), opt-out via relay.enabled:false, so addressability is the default rather than opt-in. (Receiving side; complements the reply primitive above.)

Migration / cleanup

  • Once delivery has the relaycast target, drop the per-persona Slack-fallback hack in agents repo hn-monitor relay branch; inbox-buddy/joke-bot inherit relay reply for free.

Acceptance

A local (broker-spawned) agent DMs a deployed agent (e.g. hn-monitor) over the relay and gets the answer back in the relay (full round-trip), with zero persona-specific relay code. Verified against a real workspace (rw_* on the cloud-configured relaycast gateway).

Context

Surfaced while wiring local-agent ↔ cloud-agent chat for the AgentWorkforce/agents personas (inbox-buddy/joke-bot/spotify/hn-monitor dual-transport unification). Relates to cloud inbox delivery (#2212) and the optional-integrations work (#252).

Activity

  1. khaliqgant commented on Jun 25, 2026

    @khaliqgant
    MemberAuthor

    pt.1 — delivery relaycast target: #255 (merged-pending). Adds relaycast as a first-class delivery target so the existing origin-reply pattern covers agent-to-agent relay replies with no per-persona code.

    Gateway URL decision: default is now https://cast.agentrelay.com (DEFAULT_RELAYCAST_URL, single source of truth in packages/delivery/src/relaycast.ts), overridable per-env via RELAYCAST_URL > RELAY_BASE_URL.

    Remaining:

    • runtime ctx.relay.reply() + surface inbound sender on the relay event (so callers populate to ergonomically)
    • cloud: default inbox_selectors to ['@self'] (relay-on by default)
    • converge the box's injected relay base URL + workspace key onto one gateway (key currently validates on api.relaycast.dev, infra injects gateway.relaycast.dev, target is cast.agentrelay.com)
    • publish @agentworkforce/delivery, bump in agents repo, drop hn-monitor's slack-only relay fallback
  2. khaliqgant commented on Jun 25, 2026

    @khaliqgant
    MemberAuthor

    Update — review pass + a newly-surfaced blocker.

    • delivery relaycast target (feat(delivery): relaycast target — agents reply over the relay (#254 pt.1) #255): review feedback addressed — fetchWithTimeout, agent-token auth for /v1/dm (not the workspace key), unwrap {ok,data} for the message id, missing-id = failure, and relaycast excluded from non-blocking publish(). 8/8 green.
    • cloud relay-on-default (#2491): precedence fix landed, but converted to draft — Codex P1 confirmed: deliverRelaycastEventForDeployments(ws, "@self", …) fans a DM to every @self deployment, not the recipient. Defaulting @self everywhere would leak private DMs across agents.

    New required item (predecessor to relay-on-by-default):

    • Scope @self deployment delivery to the DM recipient in services/agent-gateway/src/durable-object.ts (deliverRelaycastEventForDeployments) — deliver only to the addressed recipient deployment; keep channel-selector fan-out. Un-drafts #2491.

    Also still open: runtime ctx.relay.reply() + surface inbound sender; converge box base-URL+key onto cast.agentrelay.com; publish delivery + consume in agents.

  3. khaliqgant commented on Jun 25, 2026

    @khaliqgant
    MemberAuthor

    Comprehensive status

    Shipped / in review

    B — @self recipient-scoping (HARD blocker for un-drafting #2491). Root cause confirmed:
    services/agent-gateway/src/inbox-subscriber.ts RelaycastInboxMessageEvent has from but no recipient/to. durable-object.ts handleRelaycastSelfEvent → deliverRelaycastEventForDeployments(ws, "@self", …) fans to all @self deployment candidates (only excluding the sender) because, for cold deployments, it has no recipient identity to match against. Live agents get their own scoped @self connection; cold deployments are woken by the over-broad candidate fan-out. Fix needs the recipient identity on the relay event (relaycast engine + gateway) so cold-deployment @self delivery can match the addressed agent. Not a safe surgical edit — needs design + live testing. Until then, defaulting @self everywhere would leak DMs, so #2491 stays drafted.

    C — gateway URL → cast.agentrelay.com

    • Done where safe: @agentworkforce/delivery + @agentworkforce/runtime now default to cast.agentrelay.com (single-const source of truth, env-overridable).
    • Remaining is a deploy/migration step, not a blind code change: flip infra/relaycast.ts hostname (line ~21) to cast.agentrelay.com + restore the prod domain binding (line ~158, currently released to the legacy router), provision the Cloudflare custom domain/cert, and re-mint existing workspace keys against cast (today's rk_live_* validate on api.relaycast.dev, are rejected by cast — the "Invalid API key" we saw).

    D — agents consumption (gated on publish)
    After #255 + #256 merge and @agentworkforce/{delivery,runtime} publish: bump them in the agents repo, drop hn-monitor's Slack-only relay fallback, and have its relay Q&A reply via ctx.relay.dm(<sender>, …). Needs the sender on the event (see B) for a true reply-to-sender; until then it can reply to a known peer.

    Merge order

    1. feat(delivery): relaycast target — agents reply over the relay (#254 pt.1) #255 (delivery) → publish @agentworkforce/delivery
    2. feat(runtime): ctx.relay — agent-to-agent messaging over the relay (#254 pt.2) #256 (runtime) → publish @agentworkforce/runtime
    3. B (@self recipient-scoping) — unblocks #2491
    4. cloud#2491 (un-draft after B)
    5. C infra flip + key migration (coordinated deploy)
    6. D agents consumption
  4. khaliqgant commented on Jun 25, 2026

    @khaliqgant
    MemberAuthor

    Blocker B now has a detailed design issue: AgentWorkforce/cloud#2494 (recipient-scope @self relay DM delivery). It's the gate for un-drafting cloud#2491 and for true reply-to-sender via ctx.relay.

  5. kjgbot commented on Jun 25, 2026

    @kjgbot
    Contributor

    Closed by workforce#255 (delivery relaycast target, merged) and workforce#256 (ctx.relay, merged).

  6. khaliqgant commented on Jun 26, 2026

    @khaliqgant
    MemberAuthor

    Correction — canonical gateway + repo scoping.

    The relaycast gateway's going-forward home is the AgentWorkforce/relaycast-cloud repo, and it serves cast.agentrelay.com (prod) on the agentrelay.com zone (relaycast-cloud/infra/relaycast.ts + README). gateway.relaycast.dev / api.relaycast.dev are legacy hosts, fronted by relaycast-cloud's strangler router during cutover. So:

    • The package defaults I set (@agentworkforce/{delivery,runtime} → cast.agentrelay.com) were correct.
    • Engine-side changes (DM event shape / messaging router) belong in relaycast-cloud, NOT the standalone relaycast repo or cloud/packages/relaycast (legacy).
    • The agent-gateway @self recipient-scoping (cloud#2494) correctly stays in cloud (agent infra).

    C re-scoped + done in code: the cast gateway itself already exists (relaycast-cloud). The remaining cloud-side change — point resolveRelaycastUrl() (key minting + every agent box's RELAY_BASE_URL) at cast.agentrelay.com so all proactive agents go on the canonical gateway — is cloud#2501 (draft). Its only deploy prerequisite is the key migration: existing workspace keys minted on api.relaycast.dev must be re-minted against the cast backend.

    Updated merge order:

    1. workforce#255 ✅ merged + published 4.1.14
    2. workforce#256 ✅ merged + published 4.1.14
    3. cloud#2494 ✅ merged (@self recipient-scoping)
    4. cloud#2491 (relay-on default) — un-draft (unblocked by #2494) → merge
    5. cloud#2501 (all agents → cast) + key re-mint/backfill → merge + deploy
    6. agents: drop hn-monitor Slack-only relay fallback, reply via ctx.relay (now buildable — 4.1.14 published)
  7. khaliqgant commented on Jun 26, 2026

    @khaliqgant
    MemberAuthor

    All pieces now have landed code. Status:

    # piece repo state
    #255 delivery relaycast target workforce ✅ merged + published 4.1.14
    #256 runtime ctx.relay workforce ✅ merged + published 4.1.14
    #2494 @self recipient-scoping (no-leak) cloud ✅ merged
    #2491 relay-on by default (@self) cloud ✅ merged
    #2501 all agents → cast.agentrelay.com gateway cloud 🟡 draft — gated on key re-mint
    agents#92 hn-monitor replies over the relay (consume) agents 🟢 open, mergeable (graceful Slack fallback)

    Remaining to actually round-trip in prod:

    1. Merge agents#92 (hn-monitor → ctx.relay).
    2. Merge + deploy cloud#2501 AND re-mint existing workspace keys against the cast (relaycast-cloud) backend — the one ops/migration step. New workspaces are self-consistent on cast immediately.
    3. (Optional) confirm the summary.actor sender field path against the live relay; the hn-monitor resolver degrades to Slack if it differs.

    The platform itself (send/reply + relay-on + no-leak + canonical gateway in code) is complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions