CCOSIGNET

Blog · Guide

Human-in-the-loop for AI agents: a practical guide

Once an AI agent can send email, deploy to production, or move money, the dangerous part stops being what it generates and becomes what it executes. Human-in-the-loop is how you keep a person on the irreversible steps — but only if the approval is bound to the action, not just a click.

What human-in-the-loop actually means

“Human-in-the-loop” (HITL) means a person reviews and approves a step before the system commits to it. For an LLM that only writes text, the loop is editorial. For an agent with tools — GitHub, billing, cloud APIs, a database — the loop has to sit in front of the real side effect: the deploy, the transfer, the deletion, the secret rotation, the admin command.

The useful version of HITL has three properties:

Why a confirmation click is not a decision

The common first attempt is a Slack message with an Approve / Reject button. It feels like human-in-the-loop, but it records only that someone clicked a button. The click is not cryptographically tied to what the agent actually executes. Two failure modes follow:

A click is a UI event. For high-risk actions you want a decision bound to the operation.

Binding the approval to the action

The fix is to make the approval inseparable from the exact action. Cosignet does this with a standard WebAuthn passkey signature over a challenge built from the action itself:

challenge = nonce ‖ SHA-256(payload)

The approver confirms with a device passkey — Face ID, Touch ID, Windows Hello, or a security key — and the signature covers that exact payload. If the agent changes any field afterward, the signature no longer matches the operation, so the approval is real evidence about one specific action rather than reassurance about an intent. Keys never leave the approver’s device, and user verification (biometric or PIN) is required. The details are in the security model.

Where this helps — and where it doesn’t

Being honest about scope matters. Cosignet is an approval and evidence layer, not an executor or a policy engine. It does not run your action and it does not decide policy for you. It pauses a step, gets a real human signature bound to the payload, and hands you back a verifiable decision; you still execute.

It helps most when:

It is not a replacement for least-privilege scoping, input validation, or sandboxing. Human approval is the last gate on the actions you deliberately choose to gate — use it together with those controls, not instead of them.

How to add a human step to an agent

Call Cosignet right before the risky tool runs. It exposes an MCP server for agents and a REST API for scripts, CI/CD, and backends. Both long-poll for the human decision over your own outbound connection, so there is no inbound port to open.

npm install @cosignet/sdk
import { Cosignet } from '@cosignet/sdk';

const cosignet = new Cosignet({ apiKey: process.env.COSIGNET_API_KEY });

// Before the agent runs the dangerous tool:
const decision = await cosignet.requestApproval({
  username: 'alex',
  action: 'Deploy api-gateway to production',
  payload: { service: 'api-gateway', env: 'production', commit: 'a1b2c3d' },
  notify: 'telegram_or_email',
});

if (decision.status === 'approved') {
  // proceed — decision.rawAssertion is the signed, payload-bound proof
} else {
  // 'rejected' | 'expired' | 'pending' (timed out) → do NOT run the action
}

The agent proceeds only on an explicit approved status. Anything else — a rejection, an expiry, or a timeout — fails closed. For a framework-specific walkthrough, see adding human approval to a LangChain agent.

Evidence you can verify without trusting the vendor

Every real approval is appended to a public, append-only transparency log (an RFC-6962 Merkle tree with Ed25519-signed tree heads). Anyone can independently recompute the leaf, the inclusion proof, and the approver’s passkey signature with open, dependency-free verifiers — the audit trail does not depend on trusting Cosignet. You can check a live example on the verify page.

Takeaways