my-gate 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- my_gate-0.5.0/LICENSE +21 -0
- my_gate-0.5.0/PKG-INFO +319 -0
- my_gate-0.5.0/README.md +297 -0
- my_gate-0.5.0/gate/README.md +15 -0
- my_gate-0.5.0/gate/__init__.py +5 -0
- my_gate-0.5.0/gate/__main__.py +8 -0
- my_gate-0.5.0/gate/cli.py +834 -0
- my_gate-0.5.0/gate/hook.py +1099 -0
- my_gate-0.5.0/gate/paths.py +49 -0
- my_gate-0.5.0/gate/py.typed +0 -0
- my_gate-0.5.0/gate/telemetry.py +163 -0
- my_gate-0.5.0/gate/tint.py +595 -0
- my_gate-0.5.0/pyproject.toml +220 -0
my_gate-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robb Doering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
my_gate-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: my-gate
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
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.
|
|
5
|
+
Author: Robb Doering
|
|
6
|
+
Author-email: Robb Doering <robb@doering.ai>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Project-URL: Homepage, https://gitlab.com/doering-ai/apps/gate
|
|
18
|
+
Project-URL: Source, https://gitlab.com/doering-ai/apps/gate
|
|
19
|
+
Project-URL: Issues, https://gitlab.com/doering-ai/apps/gate/-/issues
|
|
20
|
+
Project-URL: Documentation, https://gitlab.com/doering-ai/apps/gate/-/blob/main/docs/index.md
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# myGate
|
|
24
|
+
|
|
25
|
+
[](https://gitlab.com/doering-ai/apps/gate/-/pipelines)
|
|
26
|
+
[](https://gitlab.com/doering-ai/apps/gate/-/graphs/main/charts)
|
|
27
|
+
[](pyproject.toml)
|
|
28
|
+
[](LICENSE)
|
|
29
|
+
|
|
30
|
+
myGate prevents expensive nested-agent fan-outs from happening silently.
|
|
31
|
+
Known superheavy launches are denied before they start; launches visible only at runtime
|
|
32
|
+
raise a desktop notification.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
Requires Python 3.11 or newer.
|
|
37
|
+
Choose **one** route—installing both duplicates the hooks.
|
|
38
|
+
|
|
39
|
+
### Universal CLI
|
|
40
|
+
|
|
41
|
+
The source repository is public; the PyPI project is not published yet.
|
|
42
|
+
Install the CLI persistently from Git:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
uv tool install git+https://gitlab.com/doering-ai/apps/gate.git
|
|
46
|
+
gate register
|
|
47
|
+
gate doctor
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`pipx` works too:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pipx install git+https://gitlab.com/doering-ai/apps/gate.git
|
|
54
|
+
gate register
|
|
55
|
+
gate doctor
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The default registers all three Claude Code layers in your user settings.
|
|
59
|
+
For Factory Droid's pre-flight layer, use `gate register --harness droid`;
|
|
60
|
+
use `--harness all` to register both.
|
|
61
|
+
|
|
62
|
+
### Claude Code plugin
|
|
63
|
+
|
|
64
|
+
Run these inside Claude Code:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
/plugin marketplace add https://gitlab.com/doering-ai/apps/gate.git
|
|
68
|
+
/plugin install my-gate@my-gate
|
|
69
|
+
/reload-plugins
|
|
70
|
+
/plugin
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The bundled `hooks/hooks.json` registers all three layers without editing settings.
|
|
74
|
+
The plugin route requires `python3` on `PATH`; on Windows without that alias, use the CLI
|
|
75
|
+
route, which records the installed interpreter's absolute path.
|
|
76
|
+
|
|
77
|
+
> **Important:** Use the plugin or the CLI registration, not both.
|
|
78
|
+
> Duplicate hooks double notifications and can consume a one-off grant without
|
|
79
|
+
> allowing the launch.
|
|
80
|
+
|
|
81
|
+
## Verify
|
|
82
|
+
|
|
83
|
+
For the CLI route:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
gate doctor
|
|
87
|
+
gate status
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A healthy persistent install ends `doctor` with `0 failure(s).`.
|
|
91
|
+
`status` checks settings-file registrations; plugin users should inspect Claude Code's
|
|
92
|
+
`/plugin` **Installed** and **Errors** tabs instead.
|
|
93
|
+
|
|
94
|
+
To test the decision logic safely:
|
|
95
|
+
|
|
96
|
+
```console
|
|
97
|
+
$ gate selftest
|
|
98
|
+
[ok] unpinned workflow under heavy session denied
|
|
99
|
+
[ok] fully light-pinned workflow allowed
|
|
100
|
+
[ok] explicit heavy pin denied even under light session
|
|
101
|
+
[ok] unpinned workflow under light session allowed
|
|
102
|
+
[ok] explicit heavy Agent override denied
|
|
103
|
+
[ok] garbage payload ignored
|
|
104
|
+
[ok] grant honored (heavy Agent allowed)
|
|
105
|
+
[ok] grant exhausted (heavy Agent denied)
|
|
106
|
+
[ok] expired grant ignored (heavy Agent denied)
|
|
107
|
+
|
|
108
|
+
0 failure(s).
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`selftest` feeds synthetic hook payloads through the real gate.
|
|
112
|
+
It launches no agents and suppresses notifications.
|
|
113
|
+
Its output and the package's docstring examples are exercised in the test suite.
|
|
114
|
+
|
|
115
|
+
## What happens
|
|
116
|
+
|
|
117
|
+
| Situation | Outcome |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| Explicit `opus` or `fable` nested-agent pin | Denied before launch |
|
|
120
|
+
| Unpinned workflow under a resolvable superheavy session | Denied before fan-out |
|
|
121
|
+
| Explicit `sonnet` or `haiku` pin | Allowed |
|
|
122
|
+
| Allowlisted or one-off-granted launch | Allowed and normally notified |
|
|
123
|
+
| Launch visible only after it starts | Start notification, then stop-time ground truth |
|
|
124
|
+
|
|
125
|
+
When a launch is denied, the calling agent gets the exact reason and a sanctioned next action:
|
|
126
|
+
pin a light model, switch the parent session to one, or ask the human to mint a bounded grant.
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
Nested agent launch DENIED by myGate: subagent_type='general-purpose'
|
|
130
|
+
resolves to model='opus' via explicit override. ... ask the user to run:
|
|
131
|
+
`gate grant general-purpose --uses 1 --ttl 15m --session <session_id>`
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
<details>
|
|
135
|
+
<summary>See the captured incident</summary>
|
|
136
|
+
|
|
137
|
+

|
|
138
|
+
|
|
139
|
+
The captured conversation preserves a count mismatch: the gate reported six candidate
|
|
140
|
+
launches, while the agent later found five explicit `agent()` calls. Treat a reported count
|
|
141
|
+
as a prompt to inspect the workflow; the deny itself does not depend on that estimate.
|
|
142
|
+
|
|
143
|
+
</details>
|
|
144
|
+
|
|
145
|
+
## Why
|
|
146
|
+
|
|
147
|
+
One orchestrator session on Claude Opus or Fable can be a deliberate choice.
|
|
148
|
+
Twenty nested agents silently inheriting that model from a workflow fan-out is a budget
|
|
149
|
+
incident.
|
|
150
|
+
The dominant leak is **inheritance, not explicit pins**: subagents and workflow-internal
|
|
151
|
+
agents use the parent session's model unless each call pins a lighter one.
|
|
152
|
+
|
|
153
|
+
myGate makes that fan-out impossible to trigger silently.
|
|
154
|
+
It denies what can be established before launch and makes runtime-only evidence visible.
|
|
155
|
+
|
|
156
|
+
## The three layers
|
|
157
|
+
|
|
158
|
+
| Layer | Hook event | Action | Covers |
|
|
159
|
+
| --- | --- | --- | --- |
|
|
160
|
+
| Pre-flight | `PreToolUse` (`Agent`, `Task`, `Workflow`) | **deny + notify** | Explicit heavy pins and inherited workflow fan-outs |
|
|
161
|
+
| Start-time | `SubagentStart` | notify | Built-in or inherited launches invisible pre-flight |
|
|
162
|
+
| Ground truth | `SubagentStop` | notify | The model recorded in the agent's own transcript |
|
|
163
|
+
|
|
164
|
+
For `Workflow` scripts, pre-flight analysis denies an explicit superheavy pin.
|
|
165
|
+
Under a superheavy session, a workflow is allowed only when every recognized `agent()` call
|
|
166
|
+
explicitly pins a light model.
|
|
167
|
+
The start and stop layers cover what static inspection cannot prove.
|
|
168
|
+
|
|
169
|
+
## Configuration
|
|
170
|
+
|
|
171
|
+
Configuration is optional.
|
|
172
|
+
The defaults gate `opus|fable`, recognize `sonnet|haiku` as light, notify on confirmed
|
|
173
|
+
events, and allow no standing exceptions.
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
gate init-config
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
That writes `~/.config/gate/config.toml` on Linux and macOS or
|
|
180
|
+
`%APPDATA%\gate\config.toml` on Windows.
|
|
181
|
+
Override the location with `GATE_CONFIG`.
|
|
182
|
+
|
|
183
|
+
```toml
|
|
184
|
+
superheavy_pattern = "opus|fable"
|
|
185
|
+
light_pattern = "sonnet|haiku"
|
|
186
|
+
allow = [
|
|
187
|
+
"adversarial-critic",
|
|
188
|
+
"workflow:deep-research",
|
|
189
|
+
]
|
|
190
|
+
notify = true
|
|
191
|
+
notify_command = []
|
|
192
|
+
throttle_seconds = 60
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Allowlisted launches remain visible: they are allowed and notify at normal urgency.
|
|
196
|
+
`notify_command` is executed directly as an argument vector, without a shell. Keep it that way:
|
|
197
|
+
`{title}` and `{body}` include hook-payload text, so do not embed them in `sh -c`,
|
|
198
|
+
PowerShell source, or another shell-evaluated string.
|
|
199
|
+
|
|
200
|
+
## One-off grants
|
|
201
|
+
|
|
202
|
+
A standing allowlist entry is permanent and broad.
|
|
203
|
+
A grant is single-use by default, always expires, and is minted only from the human's terminal.
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
gate grant general-purpose --uses 1 --ttl 15m
|
|
207
|
+
gate grants
|
|
208
|
+
gate revoke general-purpose
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Use `--session <session_id>` to bind a grant to the exact session named in a deny message.
|
|
212
|
+
Every successful consumption sends a normal-urgency notification and decrements the
|
|
213
|
+
remaining uses.
|
|
214
|
+
|
|
215
|
+
Grants live at `~/.config/gate/grants.json` by default; override the path with
|
|
216
|
+
`GATE_GRANTS`.
|
|
217
|
+
If the CLI is not installed, a one-off diagnostic or grant command can run directly from
|
|
218
|
+
the public source:
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
uvx --from git+https://gitlab.com/doering-ai/apps/gate.git \
|
|
222
|
+
gate grant general-purpose --uses 1 --ttl 15m
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Uninstall
|
|
226
|
+
|
|
227
|
+
Settings-file registration is reversible:
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
gate unregister --harness all
|
|
231
|
+
uv tool uninstall my-gate
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Plugin users can remove the plugin inside Claude Code:
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
/plugin uninstall my-gate@my-gate
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Platform support
|
|
241
|
+
|
|
242
|
+
| Platform | Pre-flight deny | Start/stop detection | Desktop notification |
|
|
243
|
+
| --- | --- | --- | --- |
|
|
244
|
+
| Linux | yes | yes | `notify-send`, `kitten notify` |
|
|
245
|
+
| macOS | yes | yes | `terminal-notifier`, `osascript` (best-effort, untested) |
|
|
246
|
+
| Windows | yes | yes | PowerShell toast (best-effort, untested) |
|
|
247
|
+
|
|
248
|
+
Claude Code's three layers have been exercised end to end on Linux.
|
|
249
|
+
Factory Droid registration covers its documented `Task` hook; live deny enforcement remains
|
|
250
|
+
advisory until observed.
|
|
251
|
+
Other harnesses currently expose no equivalent hookable nested-launch event.
|
|
252
|
+
|
|
253
|
+
## Design notes
|
|
254
|
+
|
|
255
|
+
- **Fail open on uncertainty.** Unrecognized or unparseable payloads exit normally in silence.
|
|
256
|
+
A deny is emitted only for a confirmed match, plus documented conservative workflow cases.
|
|
257
|
+
- **Resolve the session model.** Hook payloads omit it, but the transcript records it on every
|
|
258
|
+
assistant message, including mid-session model switches.
|
|
259
|
+
- **State facts, not guesses.** Confirmed superheavy launches notify plainly; unresolved cases
|
|
260
|
+
stay silent until stop-time ground truth is available.
|
|
261
|
+
- **Back prevention with detection.** Dynamic model construction can evade static analysis but
|
|
262
|
+
not the model recorded in the completed agent's transcript.
|
|
263
|
+
- **Keep exceptions visible.** Allowlisted and granted launches still notify.
|
|
264
|
+
|
|
265
|
+
## Known limitations
|
|
266
|
+
|
|
267
|
+
- `SubagentStop` is detection, not prevention; that agent's tokens have already been spent.
|
|
268
|
+
- Dynamically built model options can escape workflow static analysis, then surface at stop-time.
|
|
269
|
+
- Built-in agent defaults without an on-disk definition are invisible before launch.
|
|
270
|
+
- A missing hook script blocks the matched Claude Code call; uninstall through the supported
|
|
271
|
+
command instead of deleting files by hand.
|
|
272
|
+
- New model families remain ungated until `superheavy_pattern` includes them.
|
|
273
|
+
- Grant locking is advisory on network filesystems.
|
|
274
|
+
- Notification throttling groups repeated detections by subject and session.
|
|
275
|
+
|
|
276
|
+
## Terminal tinting: the gate you can see
|
|
277
|
+
|
|
278
|
+
`gate tint` turns gate state into your terminal's background color (OSC 11) — a recent
|
|
279
|
+
denial, a heavy subagent or workflow in flight, a live grant — plus ambient colors for
|
|
280
|
+
directories and commands.
|
|
281
|
+
Wire it into your prompt:
|
|
282
|
+
|
|
283
|
+
```zsh
|
|
284
|
+
precmd() { gate tint -d "$PWD" -s "$?" &! }
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The escape goes to `/dev/tty`, so it survives redirection; tmux needs
|
|
288
|
+
`set -g allow-passthrough on`.
|
|
289
|
+
Configure events, aliases, and triggers in `~/.config/gate/tint.toml`
|
|
290
|
+
(`gate tint init` writes a commented starter; see the
|
|
291
|
+
[tint documentation](docs/tint.md)).
|
|
292
|
+
Manual overrides: `gate tint set jade` / `show` / `clear`; `gate tint multiset` colors
|
|
293
|
+
every kitty window in the tab.
|
|
294
|
+
|
|
295
|
+
## Documentation and development
|
|
296
|
+
|
|
297
|
+
The [full documentation](docs/index.md) uses the same Sphinx + MyST + furo structure as the
|
|
298
|
+
other Python projects in this ecosystem, with executable examples and an autodoc API page.
|
|
299
|
+
|
|
300
|
+
```sh
|
|
301
|
+
task docs
|
|
302
|
+
task eval
|
|
303
|
+
task test
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The release build is dependency-free at runtime.
|
|
307
|
+
Source metadata, plugin metadata, the runtime version, the typed-package marker, and license
|
|
308
|
+
claims are checked by the test suite.
|
|
309
|
+
|
|
310
|
+
## Versioning
|
|
311
|
+
|
|
312
|
+
This project follows [workflow-gated semver]: the minor version records the highest completed
|
|
313
|
+
`py-workflow` step, with `0.9.0` gated on at least nine external users and `1.0.0` on a
|
|
314
|
+
maintainer social guarantee.
|
|
315
|
+
Version `0.5.0` means implementation gate 5 is the highest cleared release.
|
|
316
|
+
|
|
317
|
+
[workflow-gated semver]: https://gitlab.com/doering-ai/corpus/-/blob/main/docs/versioning.md
|
|
318
|
+
|
|
319
|
+
Licensed under the [MIT License](LICENSE).
|
my_gate-0.5.0/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# myGate
|
|
2
|
+
|
|
3
|
+
[](https://gitlab.com/doering-ai/apps/gate/-/pipelines)
|
|
4
|
+
[](https://gitlab.com/doering-ai/apps/gate/-/graphs/main/charts)
|
|
5
|
+
[](pyproject.toml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
myGate prevents expensive nested-agent fan-outs from happening silently.
|
|
9
|
+
Known superheavy launches are denied before they start; launches visible only at runtime
|
|
10
|
+
raise a desktop notification.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Requires Python 3.11 or newer.
|
|
15
|
+
Choose **one** route—installing both duplicates the hooks.
|
|
16
|
+
|
|
17
|
+
### Universal CLI
|
|
18
|
+
|
|
19
|
+
The source repository is public; the PyPI project is not published yet.
|
|
20
|
+
Install the CLI persistently from Git:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
uv tool install git+https://gitlab.com/doering-ai/apps/gate.git
|
|
24
|
+
gate register
|
|
25
|
+
gate doctor
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`pipx` works too:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pipx install git+https://gitlab.com/doering-ai/apps/gate.git
|
|
32
|
+
gate register
|
|
33
|
+
gate doctor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The default registers all three Claude Code layers in your user settings.
|
|
37
|
+
For Factory Droid's pre-flight layer, use `gate register --harness droid`;
|
|
38
|
+
use `--harness all` to register both.
|
|
39
|
+
|
|
40
|
+
### Claude Code plugin
|
|
41
|
+
|
|
42
|
+
Run these inside Claude Code:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
/plugin marketplace add https://gitlab.com/doering-ai/apps/gate.git
|
|
46
|
+
/plugin install my-gate@my-gate
|
|
47
|
+
/reload-plugins
|
|
48
|
+
/plugin
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The bundled `hooks/hooks.json` registers all three layers without editing settings.
|
|
52
|
+
The plugin route requires `python3` on `PATH`; on Windows without that alias, use the CLI
|
|
53
|
+
route, which records the installed interpreter's absolute path.
|
|
54
|
+
|
|
55
|
+
> **Important:** Use the plugin or the CLI registration, not both.
|
|
56
|
+
> Duplicate hooks double notifications and can consume a one-off grant without
|
|
57
|
+
> allowing the launch.
|
|
58
|
+
|
|
59
|
+
## Verify
|
|
60
|
+
|
|
61
|
+
For the CLI route:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
gate doctor
|
|
65
|
+
gate status
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A healthy persistent install ends `doctor` with `0 failure(s).`.
|
|
69
|
+
`status` checks settings-file registrations; plugin users should inspect Claude Code's
|
|
70
|
+
`/plugin` **Installed** and **Errors** tabs instead.
|
|
71
|
+
|
|
72
|
+
To test the decision logic safely:
|
|
73
|
+
|
|
74
|
+
```console
|
|
75
|
+
$ gate selftest
|
|
76
|
+
[ok] unpinned workflow under heavy session denied
|
|
77
|
+
[ok] fully light-pinned workflow allowed
|
|
78
|
+
[ok] explicit heavy pin denied even under light session
|
|
79
|
+
[ok] unpinned workflow under light session allowed
|
|
80
|
+
[ok] explicit heavy Agent override denied
|
|
81
|
+
[ok] garbage payload ignored
|
|
82
|
+
[ok] grant honored (heavy Agent allowed)
|
|
83
|
+
[ok] grant exhausted (heavy Agent denied)
|
|
84
|
+
[ok] expired grant ignored (heavy Agent denied)
|
|
85
|
+
|
|
86
|
+
0 failure(s).
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`selftest` feeds synthetic hook payloads through the real gate.
|
|
90
|
+
It launches no agents and suppresses notifications.
|
|
91
|
+
Its output and the package's docstring examples are exercised in the test suite.
|
|
92
|
+
|
|
93
|
+
## What happens
|
|
94
|
+
|
|
95
|
+
| Situation | Outcome |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| Explicit `opus` or `fable` nested-agent pin | Denied before launch |
|
|
98
|
+
| Unpinned workflow under a resolvable superheavy session | Denied before fan-out |
|
|
99
|
+
| Explicit `sonnet` or `haiku` pin | Allowed |
|
|
100
|
+
| Allowlisted or one-off-granted launch | Allowed and normally notified |
|
|
101
|
+
| Launch visible only after it starts | Start notification, then stop-time ground truth |
|
|
102
|
+
|
|
103
|
+
When a launch is denied, the calling agent gets the exact reason and a sanctioned next action:
|
|
104
|
+
pin a light model, switch the parent session to one, or ask the human to mint a bounded grant.
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
Nested agent launch DENIED by myGate: subagent_type='general-purpose'
|
|
108
|
+
resolves to model='opus' via explicit override. ... ask the user to run:
|
|
109
|
+
`gate grant general-purpose --uses 1 --ttl 15m --session <session_id>`
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary>See the captured incident</summary>
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
The captured conversation preserves a count mismatch: the gate reported six candidate
|
|
118
|
+
launches, while the agent later found five explicit `agent()` calls. Treat a reported count
|
|
119
|
+
as a prompt to inspect the workflow; the deny itself does not depend on that estimate.
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
## Why
|
|
124
|
+
|
|
125
|
+
One orchestrator session on Claude Opus or Fable can be a deliberate choice.
|
|
126
|
+
Twenty nested agents silently inheriting that model from a workflow fan-out is a budget
|
|
127
|
+
incident.
|
|
128
|
+
The dominant leak is **inheritance, not explicit pins**: subagents and workflow-internal
|
|
129
|
+
agents use the parent session's model unless each call pins a lighter one.
|
|
130
|
+
|
|
131
|
+
myGate makes that fan-out impossible to trigger silently.
|
|
132
|
+
It denies what can be established before launch and makes runtime-only evidence visible.
|
|
133
|
+
|
|
134
|
+
## The three layers
|
|
135
|
+
|
|
136
|
+
| Layer | Hook event | Action | Covers |
|
|
137
|
+
| --- | --- | --- | --- |
|
|
138
|
+
| Pre-flight | `PreToolUse` (`Agent`, `Task`, `Workflow`) | **deny + notify** | Explicit heavy pins and inherited workflow fan-outs |
|
|
139
|
+
| Start-time | `SubagentStart` | notify | Built-in or inherited launches invisible pre-flight |
|
|
140
|
+
| Ground truth | `SubagentStop` | notify | The model recorded in the agent's own transcript |
|
|
141
|
+
|
|
142
|
+
For `Workflow` scripts, pre-flight analysis denies an explicit superheavy pin.
|
|
143
|
+
Under a superheavy session, a workflow is allowed only when every recognized `agent()` call
|
|
144
|
+
explicitly pins a light model.
|
|
145
|
+
The start and stop layers cover what static inspection cannot prove.
|
|
146
|
+
|
|
147
|
+
## Configuration
|
|
148
|
+
|
|
149
|
+
Configuration is optional.
|
|
150
|
+
The defaults gate `opus|fable`, recognize `sonnet|haiku` as light, notify on confirmed
|
|
151
|
+
events, and allow no standing exceptions.
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
gate init-config
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
That writes `~/.config/gate/config.toml` on Linux and macOS or
|
|
158
|
+
`%APPDATA%\gate\config.toml` on Windows.
|
|
159
|
+
Override the location with `GATE_CONFIG`.
|
|
160
|
+
|
|
161
|
+
```toml
|
|
162
|
+
superheavy_pattern = "opus|fable"
|
|
163
|
+
light_pattern = "sonnet|haiku"
|
|
164
|
+
allow = [
|
|
165
|
+
"adversarial-critic",
|
|
166
|
+
"workflow:deep-research",
|
|
167
|
+
]
|
|
168
|
+
notify = true
|
|
169
|
+
notify_command = []
|
|
170
|
+
throttle_seconds = 60
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Allowlisted launches remain visible: they are allowed and notify at normal urgency.
|
|
174
|
+
`notify_command` is executed directly as an argument vector, without a shell. Keep it that way:
|
|
175
|
+
`{title}` and `{body}` include hook-payload text, so do not embed them in `sh -c`,
|
|
176
|
+
PowerShell source, or another shell-evaluated string.
|
|
177
|
+
|
|
178
|
+
## One-off grants
|
|
179
|
+
|
|
180
|
+
A standing allowlist entry is permanent and broad.
|
|
181
|
+
A grant is single-use by default, always expires, and is minted only from the human's terminal.
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
gate grant general-purpose --uses 1 --ttl 15m
|
|
185
|
+
gate grants
|
|
186
|
+
gate revoke general-purpose
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Use `--session <session_id>` to bind a grant to the exact session named in a deny message.
|
|
190
|
+
Every successful consumption sends a normal-urgency notification and decrements the
|
|
191
|
+
remaining uses.
|
|
192
|
+
|
|
193
|
+
Grants live at `~/.config/gate/grants.json` by default; override the path with
|
|
194
|
+
`GATE_GRANTS`.
|
|
195
|
+
If the CLI is not installed, a one-off diagnostic or grant command can run directly from
|
|
196
|
+
the public source:
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
uvx --from git+https://gitlab.com/doering-ai/apps/gate.git \
|
|
200
|
+
gate grant general-purpose --uses 1 --ttl 15m
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Uninstall
|
|
204
|
+
|
|
205
|
+
Settings-file registration is reversible:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
gate unregister --harness all
|
|
209
|
+
uv tool uninstall my-gate
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Plugin users can remove the plugin inside Claude Code:
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
/plugin uninstall my-gate@my-gate
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Platform support
|
|
219
|
+
|
|
220
|
+
| Platform | Pre-flight deny | Start/stop detection | Desktop notification |
|
|
221
|
+
| --- | --- | --- | --- |
|
|
222
|
+
| Linux | yes | yes | `notify-send`, `kitten notify` |
|
|
223
|
+
| macOS | yes | yes | `terminal-notifier`, `osascript` (best-effort, untested) |
|
|
224
|
+
| Windows | yes | yes | PowerShell toast (best-effort, untested) |
|
|
225
|
+
|
|
226
|
+
Claude Code's three layers have been exercised end to end on Linux.
|
|
227
|
+
Factory Droid registration covers its documented `Task` hook; live deny enforcement remains
|
|
228
|
+
advisory until observed.
|
|
229
|
+
Other harnesses currently expose no equivalent hookable nested-launch event.
|
|
230
|
+
|
|
231
|
+
## Design notes
|
|
232
|
+
|
|
233
|
+
- **Fail open on uncertainty.** Unrecognized or unparseable payloads exit normally in silence.
|
|
234
|
+
A deny is emitted only for a confirmed match, plus documented conservative workflow cases.
|
|
235
|
+
- **Resolve the session model.** Hook payloads omit it, but the transcript records it on every
|
|
236
|
+
assistant message, including mid-session model switches.
|
|
237
|
+
- **State facts, not guesses.** Confirmed superheavy launches notify plainly; unresolved cases
|
|
238
|
+
stay silent until stop-time ground truth is available.
|
|
239
|
+
- **Back prevention with detection.** Dynamic model construction can evade static analysis but
|
|
240
|
+
not the model recorded in the completed agent's transcript.
|
|
241
|
+
- **Keep exceptions visible.** Allowlisted and granted launches still notify.
|
|
242
|
+
|
|
243
|
+
## Known limitations
|
|
244
|
+
|
|
245
|
+
- `SubagentStop` is detection, not prevention; that agent's tokens have already been spent.
|
|
246
|
+
- Dynamically built model options can escape workflow static analysis, then surface at stop-time.
|
|
247
|
+
- Built-in agent defaults without an on-disk definition are invisible before launch.
|
|
248
|
+
- A missing hook script blocks the matched Claude Code call; uninstall through the supported
|
|
249
|
+
command instead of deleting files by hand.
|
|
250
|
+
- New model families remain ungated until `superheavy_pattern` includes them.
|
|
251
|
+
- Grant locking is advisory on network filesystems.
|
|
252
|
+
- Notification throttling groups repeated detections by subject and session.
|
|
253
|
+
|
|
254
|
+
## Terminal tinting: the gate you can see
|
|
255
|
+
|
|
256
|
+
`gate tint` turns gate state into your terminal's background color (OSC 11) — a recent
|
|
257
|
+
denial, a heavy subagent or workflow in flight, a live grant — plus ambient colors for
|
|
258
|
+
directories and commands.
|
|
259
|
+
Wire it into your prompt:
|
|
260
|
+
|
|
261
|
+
```zsh
|
|
262
|
+
precmd() { gate tint -d "$PWD" -s "$?" &! }
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The escape goes to `/dev/tty`, so it survives redirection; tmux needs
|
|
266
|
+
`set -g allow-passthrough on`.
|
|
267
|
+
Configure events, aliases, and triggers in `~/.config/gate/tint.toml`
|
|
268
|
+
(`gate tint init` writes a commented starter; see the
|
|
269
|
+
[tint documentation](docs/tint.md)).
|
|
270
|
+
Manual overrides: `gate tint set jade` / `show` / `clear`; `gate tint multiset` colors
|
|
271
|
+
every kitty window in the tab.
|
|
272
|
+
|
|
273
|
+
## Documentation and development
|
|
274
|
+
|
|
275
|
+
The [full documentation](docs/index.md) uses the same Sphinx + MyST + furo structure as the
|
|
276
|
+
other Python projects in this ecosystem, with executable examples and an autodoc API page.
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
task docs
|
|
280
|
+
task eval
|
|
281
|
+
task test
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The release build is dependency-free at runtime.
|
|
285
|
+
Source metadata, plugin metadata, the runtime version, the typed-package marker, and license
|
|
286
|
+
claims are checked by the test suite.
|
|
287
|
+
|
|
288
|
+
## Versioning
|
|
289
|
+
|
|
290
|
+
This project follows [workflow-gated semver]: the minor version records the highest completed
|
|
291
|
+
`py-workflow` step, with `0.9.0` gated on at least nine external users and `1.0.0` on a
|
|
292
|
+
maintainer social guarantee.
|
|
293
|
+
Version `0.5.0` means implementation gate 5 is the highest cleared release.
|
|
294
|
+
|
|
295
|
+
[workflow-gated semver]: https://gitlab.com/doering-ai/corpus/-/blob/main/docs/versioning.md
|
|
296
|
+
|
|
297
|
+
Licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# `gate`: Hook and CLI package
|
|
2
|
+
|
|
3
|
+
The package has five deliberately small modules:
|
|
4
|
+
|
|
5
|
+
- `hook` contains the stdlib-only decision engine and hook entrypoint.
|
|
6
|
+
- `cli` registers the hook, diagnoses installations, runs self-tests, manages grants,
|
|
7
|
+
and drives terminal tinting.
|
|
8
|
+
- `telemetry` records content-free local counters and event state without network I/O.
|
|
9
|
+
- `paths` migrates pre-rename (`superheavy-gate`) state locations once, best-effort.
|
|
10
|
+
- `tint` decides and emits OSC 11 terminal background colors from triggers and gate
|
|
11
|
+
events (a stdlib port of irix's `colorcode`).
|
|
12
|
+
|
|
13
|
+
Start with the [project README](../README.md), follow the
|
|
14
|
+
[quickstart](../docs/quickstart.md), or browse the rendered
|
|
15
|
+
[Python API](../docs/api.md).
|