# How the OpenHuman Approval Gate Mechanism Controls Interactive Turns

> Discover how OpenHuman's approval gate mechanism controls interactive turns. This runtime safety filter ensures agents only execute safe tool calls, preventing unwanted actions before they happen.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: deep-dive
- Published: 2026-08-26

---

**The OpenHuman approval gate mechanism functions as a runtime safety filter that evaluates every agent-generated tool call against configurable risk policies, returning either immediate execution, a human review request, or a denial before any external action occurs.**

The `tinyhumansai/openhuman` repository implements this approval gate mechanism to ensure autonomous agents remain safely within human-defined operational boundaries. This system intercepts tool calls at the critical moment between model generation and execution, creating a deterministic checkpoint for sensitive operations. By centralizing safety logic into a single `ToolHook` implementation, OpenHuman provides consistent governance across all agent skills without requiring individual tool modifications.

## Gate Installation and Tool Hook Registration

The approval gate mechanism installs itself during core initialization by registering as a **tool hook** within the turn-processing pipeline. In [`src/openhuman/security/approval/gate.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/approval/gate.rs), the `ApprovalGate` struct implements the `ToolHook` trait, which exposes a `decide` method that inspects every `ParsedToolCall` before execution.

```rust
// src/openhuman/security/approval/gate.rs
impl ToolHook for ApprovalGate {
    fn decide(&self, ctx: &ToolContext) -> ToolHookDecision {
        // … logic that examines the tool’s risk level and the config flag
    }
}

```

The `decide` method returns one of three variants from the `ToolHookDecision` enum:

- **Proceed** – Executes the tool call immediately without human interaction
- **Ask(reason)** – Parks the turn and persists an approval request for human review
- **Deny(reason)** – Rejects the tool call outright with a provided justification

This tri-state design ensures that every tool invocation receives an explicit safety verdict before accessing external systems.

## Risk Classification and Approval Modes

The approval gate mechanism classifies risk by examining each tool's **approval mode annotation** combined with runtime configuration flags. Each tool carries metadata specifying one of three modes: `required`, `sometimes`, or `null`.

The gate reads these annotations via internal metadata inspection (referenced as `redact_args` in the source) and evaluates them against the `require_approval_for_medium_risk` configuration flag. Medium-risk turns are defined as those lacking an explicit `required` mode but exceeding the configured sensitivity threshold. This dual-layer evaluation allows fine-grained control over which operations trigger human review without hardcoding behavior into individual tools.

## Parking Turns in the Approval Store

When the gate returns `Ask`, the approval gate mechanism immediately parks the turn and persists the request to durable storage. The `ApprovalStore::insert_pending` function in [`src/openhuman/security/approval/store.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/approval/store.rs) writes to an SQLite database (`approval.db` located in the workspace's `approval/` directory), recording the turn ID, thread ID, client ID, and a serialized snapshot of the model's original request.

```rust
// src/openhuman/security/approval/store.rs
pub fn insert_pending(cfg: &Config, pending: PendingApproval) -> Result<()> {
    // INSERT INTO pending_approvals …
}

```

This persistence guarantees that approval requests survive process restarts and enables asynchronous human review through external interfaces.

## Human-in-the-Loop Decision Flow

External clients interact with parked turns through JSON-RPC methods defined in [`src/openhuman/security/approval/schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/approval/schemas.rs). The `openhuman.approval_list_pending` method allows UIs to retrieve queued requests, presenting users with three resolution options: **Approve Once**, **Approve Always**, or **Deny**.

Upon user selection, the client calls `ApprovalStore::decide` to record the verdict:

```rust
// src/openhuman/security/approval/store.rs
pub fn decide(cfg: &Config, id: &str, decision: ApprovalDecision) -> Result<()> {
    // UPDATE pending_approvals → decisions table
}

```

This function atomically moves the record from the pending queue to the decisions audit table, ensuring no requests are lost during high-concurrency scenarios.

## Resuming Execution After Approval

After a decision is persisted, the gate's background task (demonstrated in [`tests/raw_coverage/worker_b_raw_coverage_e2e.rs`](https://github.com/tinyhumansai/openhuman/blob/main/tests/raw_coverage/worker_b_raw_coverage_e2e.rs)) wakes the parked turn and re-injects the original tool arguments into the execution pipeline.

```rust
// tests/raw_coverage/worker_b_raw_coverage_e2e.rs
let (outcome, audit_id) = approval_task.await.expect("approval task join");

```

**Approve Once** permits the single pending execution to complete before returning to normal gate evaluation. **Approve Always** modifies the tool's future evaluation path, automatically returning `Proceed` for subsequent invocations without further human interaction. **Deny** terminates the turn with a failure status propagated back to the agent.

## Audit Trails and Session Management

All approval decisions generate immutable audit records stored in the `decisions` table within `approval.db`. These records capture the decision type, timestamp, requesting agent, and human reviewer identity when applicable.

The gate periodically purges expired entries via `purge_session` to prevent database bloat while maintaining compliance history. Developers can query recent activity through the `openhuman.approval_list_recent_decisions` RPC method, enabling operational visibility into safety-critical agent behaviors.

## Implementing the Approval Gate in Code

To enable the approval gate mechanism in your OpenHuman deployment, register the `ApprovalGate` during harness initialization:

```rust
use openhuman_core::Harness;
use openhuman_core::openhuman::security::approval::gate::ApprovalGate;

// Build a harness with the approval gate enabled
let harness = Harness::builder()
    .access(openhuman_core::Access::full())
    .gate(ApprovalGate::default())   // registers the hook
    .build()
    .await?;

// Prompt that triggers a medium‑risk tool (e.g., sending an email)
let turn = harness.run("Send an email to alice@example.com").await?;

```

Once a turn is parked, list pending approvals via RPC:

```rust
let pending = core_rpc_client.call("openhuman.approval_list_pending", &json!({})).await?;
println!("Pending approvals: {:?}", pending);

```

Approve a specific request from CLI or custom UI:

```rust
let decision = json!({
    "id": "pending-1234",
    "decision": "ApproveOnce"
});
core_rpc_client.call("openhuman.approval_decide", &decision).await?;

```

Retrieve the audit trail for compliance monitoring:

```rust
let recent = core_rpc_client
    .call("openhuman.approval_list_recent_decisions", &json!({"limit": 10}))
    .await?;
println!("Recent approvals: {:?}", recent);

```

## Summary

- The approval gate mechanism inserts a `ToolHook` into the OpenHuman turn pipeline to evaluate every `ParsedToolCall` before execution
- Risk classification combines tool-level `approvalMode` annotations with runtime configuration flags like `require_approval_for_medium_risk`
- **Proceed**, **Ask**, and **Deny** decisions provide granular control over tool execution safety
- Parked turns persist to `approval.db` via `ApprovalStore::insert_pending`, enabling asynchronous human review through RPC methods
- Resume logic handles one-time approvals, permanent whitelisting, and denial scenarios through background task coordination
- Complete audit trails in the `decisions` table support operational visibility and compliance requirements

## Frequently Asked Questions

### What are the three possible decisions the approval gate mechanism can return?

The approval gate mechanism returns **Proceed** for immediate execution, **Ask(reason)** to pause the turn for human review, or **Deny(reason)** to reject the tool call entirely. These variants compose the `ToolHookDecision` enum returned by the `decide` method in [`src/openhuman/security/approval/gate.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/approval/gate.rs).

### How does OpenHuman store pending approval requests?

OpenHuman persists pending approvals to a local SQLite database named `approval.db` using the `ApprovalStore::insert_pending` function. This stores the turn ID, thread ID, client ID, and serialized model request snapshot, ensuring durability across process restarts.

### What happens to an agent turn while waiting for human approval?

While waiting for human input, the turn remains parked in a suspended state managed by the gate's background task. The original tool arguments are preserved in `approval.db`, and the execution thread awaits a signal from `ApprovalStore::decide` before resuming or terminating based on the human's selection.

### How can developers query the approval audit history?

Developers retrieve audit records by calling the `openhuman.approval_list_recent_decisions` JSON-RPC method, which queries the `decisions` table in `approval.db`. This returns historical data including decision types, timestamps, and review metadata for compliance and debugging purposes.