Hook-pack that denies or flags nested agent launches on top-tier ("superheavy") models -- pre-flight deny, start-time notify, ground-truth detection. Ships as both a PyPI CLI (Claude Code + Factory Droid registration) and a Claude Code plugin marketplace in one repo. Also ships `gate tint`: trigger-driven terminal background tinting for gate events (denials, grants, heavy subagents, workflows), directories, and commands.
Project description
myGate
myGate prevents expensive nested-agent fan-outs from happening silently. Known superheavy launches are denied before they start; launches visible only at runtime raise a desktop notification.
Install
Requires Python 3.11 or newer. Choose one route—installing both duplicates the hooks.
Universal CLI
The source repository is public; the PyPI project is not published yet. Install the CLI persistently from Git:
uv tool install git+https://gitlab.com/doering-ai/apps/gate.git
gate register
gate doctor
pipx works too:
pipx install git+https://gitlab.com/doering-ai/apps/gate.git
gate register
gate doctor
The default registers all three Claude Code layers in your user settings.
For Factory Droid's pre-flight layer, use gate register --harness droid;
use --harness all to register both.
Claude Code plugin
Run these inside Claude Code:
/plugin marketplace add https://gitlab.com/doering-ai/apps/gate.git
/plugin install my-gate@my-gate
/reload-plugins
/plugin
The bundled hooks/hooks.json registers all three layers without editing settings.
The plugin route requires python3 on PATH; on Windows without that alias, use the CLI
route, which records the installed interpreter's absolute path.
Important: Use the plugin or the CLI registration, not both. Duplicate hooks double notifications and can consume a one-off grant without allowing the launch.
Verify
For the CLI route:
gate doctor
gate status
A healthy persistent install ends doctor with 0 failure(s)..
status checks settings-file registrations; plugin users should inspect Claude Code's
/plugin Installed and Errors tabs instead.
To test the decision logic safely:
$ gate selftest
[ok] unpinned workflow under heavy session denied
[ok] fully light-pinned workflow allowed
[ok] explicit heavy pin denied even under light session
[ok] unpinned workflow under light session allowed
[ok] explicit heavy Agent override denied
[ok] garbage payload ignored
[ok] grant honored (heavy Agent allowed)
[ok] grant exhausted (heavy Agent denied)
[ok] expired grant ignored (heavy Agent denied)
0 failure(s).
selftest feeds synthetic hook payloads through the real gate.
It launches no agents and suppresses notifications.
Its output and the package's docstring examples are exercised in the test suite.
What happens
| Situation | Outcome |
|---|---|
Explicit opus or fable nested-agent pin |
Denied before launch |
| Unpinned workflow under a resolvable superheavy session | Denied before fan-out |
Explicit sonnet or haiku pin |
Allowed |
| Allowlisted or one-off-granted launch | Allowed and normally notified |
| Launch visible only after it starts | Start notification, then stop-time ground truth |
When a launch is denied, the calling agent gets the exact reason and a sanctioned next action: pin a light model, switch the parent session to one, or ask the human to mint a bounded grant.
Nested agent launch DENIED by myGate: subagent_type='general-purpose'
resolves to model='opus' via explicit override. ... ask the user to run:
`gate grant general-purpose --uses 1 --ttl 15m --session <session_id>`
See the captured incident
The captured conversation preserves a count mismatch: the gate reported six candidate
launches, while the agent later found five explicit agent() calls. Treat a reported count
as a prompt to inspect the workflow; the deny itself does not depend on that estimate.
Why
One orchestrator session on Claude Opus or Fable can be a deliberate choice. Twenty nested agents silently inheriting that model from a workflow fan-out is a budget incident. The dominant leak is inheritance, not explicit pins: subagents and workflow-internal agents use the parent session's model unless each call pins a lighter one.
myGate makes that fan-out impossible to trigger silently. It denies what can be established before launch and makes runtime-only evidence visible.
The three layers
| Layer | Hook event | Action | Covers |
|---|---|---|---|
| Pre-flight | PreToolUse (Agent, Task, Workflow) |
deny + notify | Explicit heavy pins and inherited workflow fan-outs |
| Start-time | SubagentStart |
notify | Built-in or inherited launches invisible pre-flight |
| Ground truth | SubagentStop |
notify | The model recorded in the agent's own transcript |
For Workflow scripts, pre-flight analysis denies an explicit superheavy pin.
Under a superheavy session, a workflow is allowed only when every recognized agent() call
explicitly pins a light model.
The start and stop layers cover what static inspection cannot prove.
Configuration
Configuration is optional.
The defaults gate opus|fable, recognize sonnet|haiku as light, notify on confirmed
events, and allow no standing exceptions.
gate init-config
That writes ~/.config/gate/config.toml on Linux and macOS or
%APPDATA%\gate\config.toml on Windows.
Override the location with GATE_CONFIG.
superheavy_pattern = "opus|fable"
light_pattern = "sonnet|haiku"
allow = [
"adversarial-critic",
"workflow:deep-research",
]
notify = true
notify_command = []
throttle_seconds = 60
Allowlisted launches remain visible: they are allowed and notify at normal urgency.
notify_command is executed directly as an argument vector, without a shell. Keep it that way:
{title} and {body} include hook-payload text, so do not embed them in sh -c,
PowerShell source, or another shell-evaluated string.
One-off grants
A standing allowlist entry is permanent and broad. A grant is single-use by default, always expires, and is minted only from the human's terminal.
gate grant general-purpose --uses 1 --ttl 15m
gate grants
gate revoke general-purpose
Use --session <session_id> to bind a grant to the exact session named in a deny message.
Every successful consumption sends a normal-urgency notification and decrements the
remaining uses.
Grants live at ~/.config/gate/grants.json by default; override the path with
GATE_GRANTS.
If the CLI is not installed, a one-off diagnostic or grant command can run directly from
the public source:
uvx --from git+https://gitlab.com/doering-ai/apps/gate.git \
gate grant general-purpose --uses 1 --ttl 15m
Uninstall
Settings-file registration is reversible:
gate unregister --harness all
uv tool uninstall my-gate
Plugin users can remove the plugin inside Claude Code:
/plugin uninstall my-gate@my-gate
Platform support
| Platform | Pre-flight deny | Start/stop detection | Desktop notification |
|---|---|---|---|
| Linux | yes | yes | notify-send, kitten notify |
| macOS | yes | yes | terminal-notifier, osascript (best-effort, untested) |
| Windows | yes | yes | PowerShell toast (best-effort, untested) |
Claude Code's three layers have been exercised end to end on Linux.
Factory Droid registration covers its documented Task hook; live deny enforcement remains
advisory until observed.
Other harnesses currently expose no equivalent hookable nested-launch event.
Design notes
- Fail open on uncertainty. Unrecognized or unparseable payloads exit normally in silence. A deny is emitted only for a confirmed match, plus documented conservative workflow cases.
- Resolve the session model. Hook payloads omit it, but the transcript records it on every assistant message, including mid-session model switches.
- State facts, not guesses. Confirmed superheavy launches notify plainly; unresolved cases stay silent until stop-time ground truth is available.
- Back prevention with detection. Dynamic model construction can evade static analysis but not the model recorded in the completed agent's transcript.
- Keep exceptions visible. Allowlisted and granted launches still notify.
Known limitations
SubagentStopis detection, not prevention; that agent's tokens have already been spent.- Dynamically built model options can escape workflow static analysis, then surface at stop-time.
- Built-in agent defaults without an on-disk definition are invisible before launch.
- A missing hook script blocks the matched Claude Code call; uninstall through the supported command instead of deleting files by hand.
- New model families remain ungated until
superheavy_patternincludes them. - Grant locking is advisory on network filesystems.
- Notification throttling groups repeated detections by subject and session.
Terminal tinting: the gate you can see
gate tint turns gate state into your terminal's background color (OSC 11) — a recent
denial, a heavy subagent or workflow in flight, a live grant — plus ambient colors for
directories and commands.
Wire it into your prompt:
precmd() { gate tint -d "$PWD" -s "$?" &! }
The escape goes to /dev/tty, so it survives redirection; tmux needs
set -g allow-passthrough on.
Configure events, aliases, and triggers in ~/.config/gate/tint.toml
(gate tint init writes a commented starter; see the
tint documentation).
Manual overrides: gate tint set jade / show / clear; gate tint multiset colors
every kitty window in the tab.
Documentation and development
The full documentation uses the same Sphinx + MyST + furo structure as the other Python projects in this ecosystem, with executable examples and an autodoc API page.
task docs
task eval
task test
The release build is dependency-free at runtime. Source metadata, plugin metadata, the runtime version, the typed-package marker, and license claims are checked by the test suite.
Versioning
This project follows workflow-gated semver: the minor version records the highest completed
py-workflow step, with 0.9.0 gated on at least nine external users and 1.0.0 on a
maintainer social guarantee.
Version 0.5.0 means implementation gate 5 is the highest cleared release.
Licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file my_gate-0.5.0.tar.gz.
File metadata
- Download URL: my_gate-0.5.0.tar.gz
- Upload date:
- Size: 40.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d06eda951740c2090d47756c6ca025e0a8667190b8ef6bdf8add4feb03c9a182
|
|
| MD5 |
5b74b2c0fe644f9b0c7dc8ca86967690
|
|
| BLAKE2b-256 |
d64712ca6412408301578df767986b8cc62299ee47afccaa42fced68a5627597
|
File details
Details for the file my_gate-0.5.0-py3-none-any.whl.
File metadata
- Download URL: my_gate-0.5.0-py3-none-any.whl
- Upload date:
- Size: 43.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5fdb377d049bfe5104ec19ae8acb9252d3da5d1fe0f92e5f5e216380d8d50ec0
|
|
| MD5 |
19324650f25cb40d542c4dba4bd93a14
|
|
| BLAKE2b-256 |
9f59a77232b1487b36355534389f67fa3efdda3353f7d90bf1630e91199c3910
|