agentgate
An agent-hook safety gate for AI-written code. Wire it into Claude Code or Codex as a hook; it runs a battery of deterministic checks on the text an agent is about to write (PreToolUse) or just wrote (PostToolUse) and either blocks the write or hands the model a structured feedback blob so the loop self-corrects without a human round-trip.
Problem
AI coding agents write files directly. They introduce defect classes that traditional linters and CI were not built to catch:
- Valid-but-wrong CJK -- a real but wrong kanji/hanzi/hangul (mojihen's domain; grep and unit tests pass it as false-green).
- Bidi / invisible Unicode -- Trojan-Source bidi overrides and invisible characters smuggled into source identifiers.
Both have standalone CLIs that run in CI. What is missing is a single hook at the agent write boundary that (a) speaks the agents' hook protocols, (b) applies one severity-to-action policy across checks, and (c) returns a model-readable feedback blob so the agent rewrites itself.
Positioning (honest)
agentgate is not a new category -- Claude Code and Codex already expose hook frameworks. agentgate is the packaged, cross-agent gate that bundles AI-write-specific checks, normalizes the two agents' payloads, centralizes block/warn policy, and ships an open check registry so the set grows without forking. The ecosystem value is the gate + registry standard, plus composing the sibling engine mojihen, not any single linter.
Coverage limits
agentgate only sees writes that flow through a hooked tool call. It does NOT see, and makes no claim about:
- Files written by shell commands the agent runs (
echo >,sed -i, codegen). - Pre-existing files, generated artifacts, or out-of-band edits.
- Editor/IDE agents that do not emit the supported hook payloads.
- Codex tool paths other than
apply_patch(best-effort; fail-open otherwise). - Anything a PostToolUse hook is asked to undo -- it cannot roll back a completed write, only report it back to the model.
M1 built-in checks
unicode (stdlib, always available)
- AG-BIDI (high, always block): bidi control characters U+202A-U+202E, U+2066-U+2069. These are the Trojan-Source vectors with essentially no legitimate use in source files.
- AG-INVIS (high, code context only): invisible chars (U+200B zero-width
space, U+2060 word joiner, U+FEFF stray BOM, U+00AD soft hyphen) flagged only
when the file has a code extension AND the char is inside an identifier/string
run. U+200C/U+200D (ZWNJ/ZWJ) and U+200E/U+200F (LRM/RLM) are NOT flagged
by default -- they are legitimate in Arabic/Persian/Indic text and emoji ZWJ
sequences. Strict mode (
unicode.strict_zerowidth = true) adds ZWNJ/ZWJ only inside ASCII-identifier runs. - AG-HOMO (medium, opt-in): Latin-looking Cyrillic/Greek codepoints inside an otherwise-ASCII identifier. Off by default.
cjk (requires mojihen extra)
Embeds mojihen.detect.run_detectors to catch known LLM CJK corruption: a
real but wrong kanji/hanzi that grep and unit tests pass as false-green (rule
MH001 and others). Disabled by default so the stdlib-only core installs and runs
out of the box.
Install
Core (stdlib-only, unicode check):
pip install agent-write-gate
With CJK check (requires mojihen):
pip install agent-write-gate[cjk]
Then enable in config:
# agentgate.toml
[checks]
cjk = true
CLI
# Primary: agent hook entrypoint (pipe JSON from agent hook system)
agentgate hook --stdin
# Scan files (CI / manual audit)
agentgate scan src/ --format tty|json|sarif
# List checks and their status
agentgate checks
# Version
agentgate --version
Exit codes:
hook: 0 = allow; 2 = block (deny in Pre / feedback in Post) or error.scan: 0 = no blocking findings; 1 = blocking findings; 2 = error.
Hook setup: Claude Code
Add to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{"type": "command", "command": "agentgate hook --stdin"}]
}
]
}
}
PreToolUse (exit 2) denies the tool call -- the write never happens.
PostToolUse (exit 2) surfaces feedback to the model after the write.
Only PreToolUse provides true prevention. See hooks/claude-code.md.
Hook setup: Codex
Only apply_patch is supported (Codex's primary write tool):
{
"hooks": {
"PreToolUse": [
{
"matcher": "apply_patch",
"hooks": [{"type": "command", "command": "agentgate hook --stdin"}]
}
]
}
}
See hooks/codex.md for PostToolUse setup and coverage limits.
pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/hryoma1217/agentgate
rev: v0.1.0
hooks:
- id: agentgate
The bidi / invisible-Unicode checks work out of the box. The CJK corruption
check is off by default; if you enable it ([checks.cjk] enabled = true), the
hook also needs mojihen in pre-commit's isolated env — add it via
additional_dependencies:
- id: agentgate
additional_dependencies: ["agent-write-gate[cjk]"]
Configuration
agentgate.toml or [tool.agentgate] in pyproject.toml. Falls back to
defaults when no config file is found.
# Unicode checks are ON by default; the CJK check is OFF by default.
# Enable CJK only if you installed the `agent-write-gate[cjk]` extra.
[checks.cjk]
enabled = false
min_confidence = "high"
[checks.unicode]
enabled = true
homoglyph = false
strict_zerowidth = false
allow_bidi_suppression = false
code_extensions = [
".py", ".js", ".ts", ".go", ".rs", ".java",
".c", ".cpp", ".rb", ".php", ".sh", ".sql"
]
[policy]
high = "block"
medium = "warn"
low = "ignore"
Suppression
Rule-specific only: agentgate: ignore[AG-INVIS] on the offending line.
There is no bare agentgate: ignore -- that would let a model launder
violations. AG-BIDI is not suppressible unless allow_bidi_suppression = true.
Model-readable block report
When a write is blocked, stderr looks like:
agentgate: BLOCKED -- 2 issue(s) to fix before this write
app.py:3:18 cjk/MH001 HIGH '闾' -> likely: 閾
'闾' is a known LLM corruption (likely intended: 閾) ...
app.py:5:1 unicode/AG-BIDI HIGH U+202E RIGHT-TO-LEFT OVERRIDE
Remove the bidi control char; it visually reorders source.
Fix these and re-emit.
The model sees this as a structured remediation signal and issues a corrective write without human intervention.
Relationship to mojihen
mojihen is the CJK engine (its own PyPI package, independently useful).
agentgate is the cross-agent gate that composes it (optional extra
agent-write-gate[cjk]) with the stdlib Unicode-safety check, under one policy and
one model-readable feedback contract. Two focused packages; the gate + open
registry is the ecosystem layer.
Open registry
Third-party checks can be registered:
from agentgate.registry import register
def my_check(event, cfg):
# event: WriteEvent, cfg: GateConfig
# return List[Issue]
return []
register("my-check", my_check)
License
MIT
Release files for agent-write-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 | |
|---|---|---|---|
| agent_write_gate-0.1.0.tar.gz | 31.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agent_write_gate-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.3 kB
Release files / agent_write_gate-0.1.0.tar.gz
| Download URL | agent_write_gate-0.1.0.tar.gz |
|---|---|
| Size | 31.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7320c18435e6cc4ad44e55987b04743450c9a00b9663853aa7b2b9f6cc7dcd21
|
|
BLAKE2b-256 checksum How to use checksums |
25291ea4e94fbab43477e0500654fca47c937a27b27c6277f1d07ef36dd57faa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.11
|
Release files / agent_write_gate-0.1.0-py3-none-any.whl
| Download URL | agent_write_gate-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5df8dec0d93e1453550537804dc173bc633088332270054565cc7d0141109d13
|
|
BLAKE2b-256 checksum How to use checksums |
6b68329b7b04eddcbcc4bab68924c177e34c74b48e5b22840a92692ebbd26c96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.11
|