Metadata-Version: 2.4
Name: my-gate
Version: 0.5.0
Summary: 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.
Author: Robb Doering
Author-email: Robb Doering <robb@doering.ai>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Environment :: Console
Classifier: Typing :: Typed
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Project-URL: Homepage, https://gitlab.com/doering-ai/apps/gate
Project-URL: Source, https://gitlab.com/doering-ai/apps/gate
Project-URL: Issues, https://gitlab.com/doering-ai/apps/gate/-/issues
Project-URL: Documentation, https://gitlab.com/doering-ai/apps/gate/-/blob/main/docs/index.md
Description-Content-Type: text/markdown

# myGate

[![Pipeline status](https://img.shields.io/gitlab/pipeline-status/doering-ai/apps/gate?branch=main)](https://gitlab.com/doering-ai/apps/gate/-/pipelines)
[![Test coverage](https://img.shields.io/gitlab/pipeline-coverage/doering-ai/apps/gate?branch=main)](https://gitlab.com/doering-ai/apps/gate/-/graphs/main/charts)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](pyproject.toml)
[![MIT license](https://img.shields.io/badge/license-MIT-green)](LICENSE)

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:

```sh
uv tool install git+https://gitlab.com/doering-ai/apps/gate.git
gate register
gate doctor
```

`pipx` works too:

```sh
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:

```text
/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:

```sh
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:

```console
$ 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.

```text
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>`
```

<details>
<summary>See the captured incident</summary>

![A workflow fan-out denied until its nested agents were pinned to lighter models](assets/workflow-denial.png)

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.

</details>

## 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.

```sh
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`.

```toml
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.

```sh
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:

```sh
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:

```sh
gate unregister --harness all
uv tool uninstall my-gate
```

Plugin users can remove the plugin inside Claude Code:

```text
/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

- `SubagentStop` is 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_pattern` includes 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:

```zsh
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](docs/tint.md)).
Manual overrides: `gate tint set jade` / `show` / `clear`; `gate tint multiset` colors
every kitty window in the tab.

## Documentation and development

The [full documentation](docs/index.md) uses the same Sphinx + MyST + furo structure as the
other Python projects in this ecosystem, with executable examples and an autodoc API page.

```sh
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.

[workflow-gated semver]: https://gitlab.com/doering-ai/corpus/-/blob/main/docs/versioning.md

Licensed under the [MIT License](LICENSE).
