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).
Problem
Deployed agents can receive relay DMs today (persona
relay.inbox: ['@self']→ cloudinbox_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/deliveryonly knows'slack' | 'telegram'targets (DeliveryClient.targets,resolveDeliveryTargets,onlyTargets,SlackRef/TelegramRefare all hardcoded to those two).hn-monitorhandleQaMessage(provider:'relay')) replies via the delivery package to Slack, never back to the relay sender.hn-monitor.parseRelayMessageeven drops the sender.relaycastwriteback 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— addrelaycastas a first-class target.'slack' | 'telegram'→'slack' | 'telegram' | 'relaycast'acrossDeliveryClient.targets,resolveDeliveryTargets(ctx),createDelivery(ctx, transports?, onlyTargets?), and addRelaycastRefalongsideSlackRef/TelegramRef.RELAY_API_KEY+RELAY_WORKSPACE_ID(already injected into every box bypackages/corelauncher in cloud) to the inbound relay event'schannel/threadId(carried onRelaycastMessageEvent—@agent-relay/events).resolveDeliveryTargetsincludesrelaycastwhen 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 onctx.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.channel/threadon the relay event so replies address the right DM/thread (todayRelaycastMessageEventcarrieschannel/messageIdbut personas re-parse ad hoc and drop the sender).3. (cross-repo)
AgentWorkforce/cloud— relay-on by default.inbox_selectorsto['@self']for every deployed agent intranslatePersonaRelayToInboxSelectors(packages/web/lib/proactive-runtime/inbox-selectors.ts), opt-out viarelay.enabled:false, so addressability is the default rather than opt-in. (Receiving side; complements the reply primitive above.)Migration / cleanup
relaycasttarget, drop the per-persona Slack-fallback hack inagentsrepohn-monitorrelay 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).