diff --git a/src/pages/docs/ai-transport/messaging/human-in-the-loop.mdx b/src/pages/docs/ai-transport/messaging/human-in-the-loop.mdx index bcf5d1fb39..98207f1025 100644 --- a/src/pages/docs/ai-transport/messaging/human-in-the-loop.mdx +++ b/src/pages/docs/ai-transport/messaging/human-in-the-loop.mdx @@ -32,22 +32,30 @@ Human-in-the-loop authorization follows a request-approval pattern over Ably cha ## Request human approval -When an agent identifies an action requiring human oversight, it publishes a request to the channel. The request should include sufficient context for the approver to make an informed decision. The `requestId` enables correlation between requests and responses when handling multiple concurrent approval flows. +When an agent identifies an action requiring human oversight, it publishes a request to the channel. The request should include sufficient context for the approver to make an informed decision. The `toolCallId` in the message [extras](/docs/messages#properties) enables correlation between requests and responses when handling multiple concurrent approval flows. + +The agent stores each pending request in some local state before publishing. When an approval response arrives, the agent uses the `toolCallId` to retrieve the original tool call details, verify the approver's permissions for that specific action, execute the tool if approved, and resolve the pending approval. ```javascript -const channel = ably.channels.get('{{RANDOM_CHANNEL_NAME}}'); +const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); +const pendingApprovals = new Map(); async function requestHumanApproval(toolCall) { - const requestId = crypto.randomUUID(); - - await channel.publish('approval-request', { - requestId: requestId, - action: toolCall.name, - parameters: toolCall.parameters + pendingApprovals.set(toolCall.id, { toolCall }); + + await channel.publish({ + name: 'approval-request', + data: { + name: toolCall.name, + arguments: toolCall.arguments + }, + extras: { + headers: { + toolCallId: toolCall.id + } + } }); - - return requestId; } ``` @@ -58,7 +66,7 @@ Set [`echoMessages`](/docs/api/realtime-sdk/types#client-options) to `false` on ## Review and decide -Authorized humans subscribe to approval requests on the conversation channel and publish their decisions. The `requestId` correlates the response with the original request. +Authorized humans subscribe to approval requests on the conversation channel and publish their decisions. The `toolCallId` correlates the response with the original request. Use [identified clients](/docs/ai-transport/sessions-identity/identifying-users-and-agents#user-identity) or [user claims](/docs/ai-transport/sessions-identity/identifying-users-and-agents#user-claims) to establish a verified identity or role for the approver. For example, when a user [authenticates with Ably](/docs/ai-transport/sessions-identity/identifying-users-and-agents#authenticating), embed their identity and role in the JWT: @@ -79,25 +87,40 @@ For more information about establishing verified identities and roles, see [Iden ```javascript -const channel = ably.channels.get('{{RANDOM_CHANNEL_NAME}}'); +const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); await channel.subscribe('approval-request', (message) => { const request = message.data; + const toolCallId = message.extras?.headers?.toolCallId; // Display request for human review - displayApprovalUI(request); + displayApprovalUI(request, toolCallId); }); -async function approve(requestId) { - await channel.publish('approval-response', { - requestId: requestId, - decision: 'approved' +async function approve(toolCallId) { + await channel.publish({ + name: 'approval-response', + data: { + decision: 'approved' + }, + extras: { + headers: { + toolCallId: toolCallId + } + } }); } -async function reject(requestId) { - await channel.publish('approval-response', { - requestId: requestId, - decision: 'rejected' +async function reject(toolCallId) { + await channel.publish({ + name: 'approval-response', + data: { + decision: 'rejected' + }, + extras: { + headers: { + toolCallId: toolCallId + } + } }); } ``` @@ -109,7 +132,7 @@ Set [`echoMessages`](/docs/api/realtime-sdk/types#client-options) to `false` in ## Process the decision -The agent listens for human decisions and acts accordingly. When a response arrives, the agent retrieves the pending request using the `requestId`, verifies that the user is permitted to approve that specific action, and either executes the action or handles the rejection. +The agent listens for human decisions and acts accordingly. When a response arrives, the agent retrieves the pending request using the `toolCallId`, verifies that the user is permitted to approve that specific action, and either executes the action or handles the rejection.