A relay that turns one ARCP human.input.request into a fan-out
across phone, email, and Slack — and resolves on the first valid
response, cancelling the rest.
Two patterns in the wild: (a) the agent embeds Slack/Twilio/SES clients directly and reinvents response parsing for each; (b) the agent posts to a single channel and dies waiting if nobody's watching. Neither lets a runtime block a job until a human answers without writing a custom dispatcher.
$client->subscribe(['types' => ['human.input.request']],
fn (Envelope $env) => async(fn () => fanOut($client, $env)));
// inside fanOut:
[$winnerDest, $value] = awaitFirstWithDest($futures, $timeout);
$client->session->transport->send(new Envelope(
payload: new HumanInputResponse(value: $value, respondedBy: $winnerDest, ...),
correlationId: $request->id,
...
));The runtime treats the answer as a typed reply to the original request and unblocks whichever job was waiting (RFC §12.4).
human.input.request/human.input.response/human.input.cancelled— RFC §12.1, §12.4.- Multi-channel resolution rule (resolve on first; cancel the rest) — §12.3.
expires_atdeadline →DEADLINE_EXCEEDEDcancellation — §12.4.
main.php— opens session, dispatches each inbound HITL request.fanOutis the file: first-wins resolution, loser-channel cancellation, deadline handling.channels.php— per-destination adapter registry; stubbed.
- Replace first-wins with a quorum policy (negotiated as an
extension on
human.input.request.payload). - Honor
default(§12.4): synthesize a response when the deadline expires instead of cancelling. - Use
human.choice.requestfor multi-option pickers; the relay pattern is identical.