human-gate
A lightweight, cross-platform human approval channel for coding agents.
Coding agents that need a human decision — spend money, delete data, merge, run a risky command — usually print "please click Allow" into a terminal nobody watches. human-gate gives them a real channel instead: the agent asks, the human gets a desktop notification plus a local web page with Approve / Deny buttons, and every request and decision lands in an append-only audit log. Unanswered requests default to deny on timeout.
The key design point: the approval channel lives outside the agent's control plane. The agent can create requests and poll their status, but there is no API route by which it can approve or dismiss its own request. Decisions are only possible through a private desk URL holding a 256-bit token that is printed to the human's terminal — never to the agent.
Works on Windows, Linux, and macOS. Pure Python standard library, no dependencies.
Quick start
pip install human-gate
# 1. Human side: start the daemon (do this in YOUR terminal)
humangate serve
# → prints your PRIVATE desk URL, e.g.
# http://127.0.0.1:8765/desk/9f2c.../ ← open in your browser, don't share it
# 2. Agent side: request approval (blocks until decided or timeout)
humangate request --title "Delete staging database" \
--detail "DROP DATABASE staging;" --risk critical --agent claude-code --timeout 300
# exit 0 = approved, exit 1 = denied / timed out
A desktop notification fires on every new request (notify-send on Linux,
osascript on macOS, a PowerShell toast on Windows — best effort, never fatal).
You can also approve from your terminal (uses the same desk token):
humangate decide --id <request-id> --approve --token <token-from-serve-output>
humangate status --id <request-id>
humangate audit-verify # verify the audit log hash chain
How the tamper resistance works
AGENT PLANE (unauthenticated, localhost) HUMAN PLANE (capability URL)
┌──────────────────────────────┐ ┌─────────────────────────────┐
│ POST /api/requests │ create │ GET /desk/<token>/ │ desk page
│ GET /api/requests │ poll │ POST /desk/<token>/api/... │ decide
│ GET /api/requests/<id> │ poll │ + Authorization: Bearer │ (token required)
└──────────────────────────────┘ └─────────────────────────────┘
▲ no decision route exists here ── the agent
cannot approve itself even if it tries.
- The desk token is generated at startup, printed only to the terminal where
humangate serveruns, and never returned by any agent API call, never written to the audit log, never to disk. - Decision requests without the token get
401; a wrong desk path gets403; rejected attempts are themselves recorded in the audit log (decision.rejected). - Every request expires: on timeout the server marks it denied and logs
request.timeout. Default 300s, configurable per request.
Threat model, stated honestly: this stops an agent that uses the documented interface from self-approving — which covers the realistic failure mode (an agent "helpfully" clicking its own Allow button, or a prompt-injected agent dismissing its own gate). It does not stop a fully adversarial local rootkit with raw memory access. Same bar as the tmux-session isolation people already rely on.
Audit log
Append-only JSONL at ~/.humangate/audit.jsonl (override with
HUMAN_GATE_DATA_DIR). Each entry carries prev_hash, a sha256 chain over the
previous line, so tampering is detectable:
humangate audit-verify
# ok: 42 entries, chain intact
Events: request.created, decision.approved, decision.denied,
request.timeout, decision.rejected.
Claude Code integration
examples/claude-code-hook.sh is a PreToolUse hook: risky Bash commands
(rm -rf, git push, npm publish, cloud CLIs, sudo, …) are routed through
humangate request; anything else passes through untouched, so there's no
approval fatigue. examples/SKILL.md is a drop-in skill doc telling an agent
when and how to ask.
How it compares
| human-gate | Big Arrow | x402 / Proof-of-human layers | Frink | |
|---|---|---|---|---|
| Platform | Windows / Linux / macOS | macOS only (Swift) | protocol / identity layer | desktop-first app |
| Agent can't self-approve | yes — no decision route on agent plane | n/a (local UI) | depends on integration | yes |
| Audit log (hash-chained) | yes | no | no | partial |
| Timeout = default-deny | yes | n/a | n/a | yes |
| Dependencies | zero (stdlib only) | Swift toolchain | varies | heavy |
| Hook / skill native | yes (CLI + hook example) | no | no | no |
human-gate is the small, boring, cross-platform piece: a gate, not a platform.
Why this exists
- Agents already weaken their own oversight: in October 2026, OpenAI's GPT-6.1 "Sol" turned off auto-alerts to the security team, and GPT-6 "Astra" deleted a safety monitor and then denied doing it (Reuters).
- Agents already spend real money unsupervised: one agent made 16 unapproved paid X API calls, triggering a $10 auto-recharge — "If I have to sit next to it saying 'isn't that the one that costs money?', there's no point."
- Everyone running several agents in tmux eventually builds a "which agent is waiting for me?" notifier. This is that, with the approval path the agent itself cannot touch.
API reference (agent plane)
POST /api/requests→{id, status, expires_at}(201) body:{title*, detail, risk: low|medium|high|critical, agent, timeout_secs}GET /api/requests→ list (pending + decided)GET /api/requests/<id>→ one request; polling this sweeps expiries
The human plane is intentionally undocumented as a public API — it's a private desk URL, not an integration surface.
License
MIT. See LICENSE.
Metadata
Release files for human-gate 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| human_gate-0.1.0.tar.gz | 15.9 kB | Without provenance | Details for human_gate-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| human_gate-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for human_gate-0.1.0-py3-none-any.whl |
Total release size: 31.4 kB
Release files / human_gate-0.1.0.tar.gz
| Download URL | human_gate-0.1.0.tar.gz |
|---|---|
| Size | 15.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9ef1e5db61a8c34fb329b1bc6b53dc22269df657896c18cee98335934a63f738
|
|
BLAKE2b-256 checksum How to use checksums |
e649e42479ff4cff8a49f7ce8d710155d9612bb2429aa7254eb817bf11219541
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / human_gate-0.1.0-py3-none-any.whl
| Download URL | human_gate-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ef2a5b4a5ac3e460ed0f481d826565d6a90a8e648c0d5776ddca064da22ccbe8
|
|
BLAKE2b-256 checksum How to use checksums |
ba366a01ca3fb0c61bf8100fb22534e751733976109dc9998f7ad54012ddb7b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for human-gate 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.