workshops/2025-05/sections/10-human-approval/README.md
Add support for human approval of operations.
for this section, we'll disable the baml logs. You can optionally enable them if you want to see more details.
export BAML_LOG=off
update the server to handle human approvals
handleNextStep to execute approved actionssrc/server.ts
import express from 'express';
-import { Thread, agentLoop } from '../src/agent';
+import { Thread, agentLoop, handleNextStep } from '../src/agent';
import { ThreadStore } from '../src/state';
});
+
+type ApprovalPayload = {
+ type: "approval";
+ approved: boolean;
+ comment?: string;
+}
+
+type ResponsePayload = {
+ type: "response";
+ response: string;
+}
+
+type Payload = ApprovalPayload | ResponsePayload;
+
// POST /thread/:id/response - Handle clarification response
app.post('/thread/:id/response', async (req, res) => {
return res.status(404).json({ error: "Thread not found" });
}
+
+ const body: Payload = req.body;
+
+ let lastEvent = thread.events[thread.events.length - 1];
+
+ if (thread.awaitingHumanResponse() && body.type === 'response') {
+ thread.events.push({
+ type: "human_response",
+ data: body.response
+ });
+ } else if (thread.awaitingHumanApproval() && body.type === 'approval' && !body.approved) {
+ // push feedback onto the thread
+ thread.events.push({
+ type: "tool_response",
+ data: `user denied the operation with feedback: "${body.comment}"`
+ });
+ } else if (thread.awaitingHumanApproval() && body.type === 'approval' && body.approved) {
+ // approved, run the tool, pushing results onto the thread
+ await handleNextStep(lastEvent.data, thread);
+ } else {
+ res.status(400).json({
+ error: "Invalid request: " + body.type,
+ awaitingHumanResponse: thread.awaitingHumanResponse(),
+ awaitingHumanApproval: thread.awaitingHumanApproval()
+ });
+ return;
+ }
+
- thread.events.push({
- type: "human_response",
- data: req.body.message
- });
-
// loop until stop event
const newThread = await agentLoop(thread);
store.update(req.params.id, newThread);
- const lastEvent = newThread.events[newThread.events.length - 1];
+ lastEvent = newThread.events[newThread.events.length - 1];
lastEvent.data.response_url = `/thread/${req.params.id}/response`;
cp ./walkthrough/10-server.ts src/server.ts
Add a few methods to the agent to handle approvals and responses
src/agent.ts
`)
}
+
+ awaitingHumanResponse(): boolean {
+ const lastEvent = this.events[this.events.length - 1];
+ return ['request_more_information', 'done_for_now'].includes(lastEvent.data.intent);
+ }
+
+ awaitingHumanApproval(): boolean {
+ const lastEvent = this.events[this.events.length - 1];
+ return lastEvent.data.intent === 'divide';
+ }
}
// response to human, return the thread
return thread;
+ case "divide":
+ // divide is scary, return it for human approval
+ return thread;
case "add":
case "subtract":
case "multiply":
- case "divide":
thread = await handleNextStep(nextStep, thread);
}
cp ./walkthrough/10-agent.ts src/agent.ts
Start the server
npx tsx src/server.ts
Test division with approval
curl -X POST http://localhost:3000/thread \
-H "Content-Type: application/json"
-d '{"message":"can you divide 3 by 4"}'
You should see:
{
"thread_id": "2b243b66-215a-4f37-8bc6-9ace3849043b", "events": [ { "type": "user_input", "data": "can you divide 3 by 4" }, { "type": "tool_call", "data": { "intent": "divide", "a": 3, "b": 4, "response_url": "/thread/2b243b66-215a-4f37-8bc6-9ace3849043b/response" } } ] }
reject the request with another curl call, changing the thread ID
curl -X POST 'http://localhost:3000/thread/{thread_id}/response' \
-H "Content-Type: application/json"
-d '{"type": "approval", "approved": false, "comment": "I dont think thats right, use 5 instead of 4"}'
You should see: the last tool call is now "intent":"divide","a":3,"b":5
{
"events": [ { "type": "user_input", "data": "can you divide 3 by 4" }, { "type": "tool_call", "data": { "intent": "divide", "a": 3, "b": 4, "response_url": "/thread/2b243b66-215a-4f37-8bc6-9ace3849043b/response" } }, { "type": "tool_response", "data": "user denied the operation with feedback: "I dont think thats right, use 5 instead of 4"" }, { "type": "tool_call", "data": { "intent": "divide", "a": 3, "b": 5, "response_url": "/thread/1f1f5ff5-20d7-4114-97b4-3fc52d5e0816/response" } } ] }
now you can approve the operation
curl -X POST 'http://localhost:3000/thread/{thread_id}/response' \
-H "Content-Type: application/json"
-d '{"type": "approval", "approved": true}'
you should see the final message includes the tool response and final result!
...
{ "type": "tool_response", "data": 0.5 }, { "type": "done_for_now", "message": "I divided 3 by 6 and the result is 0.5. If you have any more operations or queries, feel free to ask!", "response_url": "/thread/2b469403-c497-4797-b253-043aae830209/response" }