-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Human-in-the-Loop approval workflows #12
Copy link
Copy link
Open
Description
Summary
Full implementation of the humanInTheLoop schema section. When approvalRequired: before-destructive-tool, the SDK intercepts tool calls and sends approval requests via webhook before proceeding.
The schema already declares this feature — webhookUrl, timeoutSeconds, timeoutAction are all in manifest.schema.ts. Users reading the docs expect it to work.
Schema (already exists, needs runtime implementation)
humanInTheLoop:
enabled: true
approvalRequired:
- before-destructive-tool
- before-external-call
webhookUrl: $secret:APPROVAL_WEBHOOK_URL
timeoutSeconds: 300
timeoutAction: reject # reject | proceed | escalateProposed implementation
AgentSpecReporter.approvalGate(toolName, input)
New public method on the reporter. Agents call this before executing any destructive tool:
const decision = await reporter.approvalGate('delete_files', { paths: ['/data'] })
if (decision === 'rejected') throw new Error('Action rejected by approver')Internally it:
- Checks whether
toolNamematches anyapprovalRequiredtrigger (e.g.destructiveHint: true→before-destructive-tool) - POSTs to the configured
webhookUrl - Awaits callback or times out → applies
timeoutAction
New module: packages/sdk/src/agent/approval/
| File | Purpose |
|---|---|
index.ts |
approvalGate() orchestrator — dispatches to backend, awaits result |
webhook.ts |
POST to spec.humanInTheLoop.webhookUrl, polls for signed callback token |
console.ts |
Logs to stdout (dev/test mode — prompts for manual input) |
types.ts |
ApprovalRequest, ApprovalDecision, ApprovalBackend interface |
The webhook contract is intentionally generic — callers can wire it to Slack, email, PagerDuty, or anything else on their end. The SDK stays dependency-free.
Webhook contract
Outbound POST to webhookUrl:
{
"requestId": "uuid",
"toolName": "delete_files",
"input": { "paths": ["/data"] },
"timeoutAt": "2025-01-01T00:05:00Z"
}Expected callback (POST back to SDK-provided callbackUrl):
{
"requestId": "uuid",
"decision": "approved" | "rejected",
"token": "<signed>"
}Timeout handling
After timeoutSeconds:
reject(default) — throws, tool is blockedproceed— allows the tool to run, logs the timeoutescalate— posts to a secondary escalation webhook
Files to modify
packages/sdk/src/agent/reporter.ts— addapprovalGate()public methodpackages/sdk/src/agent/approval/— new directory
Acceptance criteria
-
reporter.approvalGate(toolName, input)resolves'approved' | 'rejected' - Webhook backend POSTs request and polls for signed callback token
- Console backend works for local dev (no external deps)
- Timeout fires after
timeoutSecondsand appliestimeoutAction - All backends are zero-cost when
humanInTheLoop.enabled: false - Unit tests with mocked HTTP for the webhook backend
- Docs updated with usage example and sample webhook server
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels