Skip to main content
Some actions are too important to leave to the model alone — sending an email, executing a transaction, modifying production data. Human-in-the-loop (HITL) lets you mark those actions and require human approval before they run. Cognis offers two paths to HITL, depending on whether you’re inside an agent loop or driving a graph directly.

Two layers

Cognis ships two distinct HITL surfaces — they pause at different points and answer different questions. For “ask before running tool X” — almost always the right answer — use tool approval. For “let a reviewer steer the model on every reply” — rare — use the middleware. For workflow checkpoints, use graph interrupts.

Quick example — tool approval

AgentBuilder::with_approver wraps every registered tool in an ApprovalGatedTool. The approver decides per call.
Tools registered after .with_approver(...) are NOT auto-wrapped. Call with_approver last.
The trait:
Decision covers the common shapes:
  • Decision::Approve — let the call run as-is.
  • Decision::Reject { reason } — refuse; the model sees reason as the tool’s output and can apologize or try something else. Use Decision::reject("nope") as a shortcut.
  • Decision::Edit { args } — approve, but rewrite the args (e.g., cap a transfer amount).
Built-in approvers under cognis::tools: AutoApprove, RejectAll, AllowList — handy for tests or as a base to compose your own.

Quick example — graph interrupts

For workflows where you want to pause before (or after) a specific node, regardless of what the model decided:
Source: examples/graphs/graph_interrupts_demo.rs.

Transcript-level pause

For the rarer case where you want a human to potentially intervene on every model call (or a subset gated by a predicate), wrap the LLM client with the HumanInTheLoop middleware. It pauses before each call, asks a HumanResponder, and acts on a HumanDecision.
To run middleware inside an AgentBuilder-built agent (where the loop drives the LLM), wrap a PipelinedClient in a custom LLMProvider and feed that into Client::new(...). See Middleware → Wiring middleware into an agent. Built-in HumanResponder impls under cognis::middleware: AlwaysSkip (no-op for testing). Closures that return HumanDecision work too via a blanket impl.

How it works

  • The agent loop is graph-shaped. When a tool call is about to dispatch, control hits the approver before the dispatcher node runs.
  • The graph engine treats interrupts as errors with structure. CognisError::GraphInterrupted is not a failure — it’s a pause. Your code matches it, decides, and calls resume.
  • Checkpointers are required for resume. The engine needs persisted state to restart from. See Graph workflows → Checkpointing.
  • Edits during pause are first-class. graph.update_state(run_id, step, &edited) writes a new snapshot at the same step. Subsequent resume reads from it.

When to combine

Many production agents use both: an Approver for tool-call-level gating, and graph interrupts for higher-level workflow checkpoints (e.g., “pause before generating the final report so a reviewer sees the draft”). They don’t conflict; the approver runs inside the agent loop, the interrupt fires at the graph boundary you mark.

See also

Patterns → HITL approval

A complete approval flow with a CLI prompt.

Graph workflows → Checkpointing

What resume reads from.

Middleware

HumanInTheLoop, ApprovalGate, and friends.