leos-agent 10.7.1 → 11.202609070.0
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.
- package/README.md +258 -117
- package/hooks/hooks.json +12 -0
- package/package.json +5 -1
- package/pi-extension.js +90 -0
- package/scripts/bump.py +186 -0
- package/scripts/check.py +74 -4
- package/scripts/dispatch_guard.py +45 -13
- package/scripts/emit_payload.py +99 -0
- package/scripts/leo-install.py +142 -107
- package/scripts/measure_context.py +12 -10
- package/scripts/payload.py +95 -0
- package/skills/doctor/SKILL.md +64 -41
- package/skills/install/SKILL.md +59 -33
- package/skills/tune-routing/SKILL.md +21 -12
- package/skills/tune-routing/reference/harnesses.md +14 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# leos-agent
|
|
2
2
|
|
|
3
|
-
Leo's portable agent operating policy, version **
|
|
3
|
+
Leo's portable agent operating policy, version **11.202609070.0**, installable on Claude
|
|
4
4
|
Code, Codex, Cursor, Hermes, Pi, and OpenCode through each harness's own plugin
|
|
5
5
|
system.
|
|
6
6
|
|
|
@@ -82,21 +82,42 @@ accepts goes in verbatim. A misspelled *key*, though, is a hard error, because a
|
|
|
82
82
|
typo that silently left a harness on the expensive model is the one failure this
|
|
83
83
|
is here to prevent.
|
|
84
84
|
|
|
85
|
-
**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
**Read once per session now, not never.** Most harnesses render the routing
|
|
86
|
+
stanza live: `emit_payload.py` calls `routing.load()` every time it runs — on
|
|
87
|
+
a `SessionStart` hook (Claude Code, Codex), inside Hermes's frozen
|
|
88
|
+
session-scoped prompt section, and on every *turn* for Pi, since
|
|
89
|
+
`before_agent_start` has no cheaper hook to sit on (see `pi-extension.js`).
|
|
90
|
+
Codex's and Cursor's per-machine halves are still baked at install time —
|
|
91
|
+
into the profile TOMLs and the `.mdc` rule respectively — because neither is
|
|
92
|
+
otherwise re-rendered. Either way this costs one read of a small local JSON
|
|
93
|
+
file, never a network call or a re-render of the whole payload. The bytes it
|
|
94
|
+
adds are unchanged from before this delivery mechanism moved: each machine
|
|
95
|
+
still carries only its own harness's dispatch line rather than all of them,
|
|
96
|
+
which is what keeps the rendered payload at 4222–4251 bytes depending on the
|
|
97
|
+
harness (measured with no routing configured; a configured harness costs a
|
|
98
|
+
little more, bounded by the model names chosen) against the pre-split figure
|
|
99
|
+
of 4497. `scripts/measure_context.py --check` enforces the ceiling and prints
|
|
100
|
+
the per-harness figure.
|
|
92
101
|
|
|
93
102
|
Edit the file — by hand, or with `routing.py set --harness <h> --runner
|
|
94
|
-
<model>`, which [`/tune-routing`](#what-it-ships) drives end to end —
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
<model>`, which [`/tune-routing`](#what-it-ships) drives end to end — and it
|
|
104
|
+
takes effect at the next session with nothing else to run on Claude Code,
|
|
105
|
+
Hermes, and Pi, all of which read it live. Codex, Cursor and OpenCode still
|
|
106
|
+
need the installer re-run afterwards, because their halves are baked into
|
|
107
|
+
files at install time; `leo-install.py <harness> --check` reports "out of
|
|
108
|
+
date" for those three until you do, and `/doctor` surfaces it. Installing is
|
|
97
109
|
idempotent: same config, same version, same bytes, so a second run reports
|
|
98
110
|
`unchanged`.
|
|
99
111
|
|
|
112
|
+
OpenCode needs a second file for a reason worth knowing: its `instructions`
|
|
113
|
+
list reads `rules/preferences.md` straight off disk, **un-rendered**, so the
|
|
114
|
+
routing region in that file keeps its shipped default no matter what is
|
|
115
|
+
configured. The per-machine half therefore ships as its own
|
|
116
|
+
`~/.config/opencode/leos-agent-routing.md`, written only when routing is
|
|
117
|
+
actually configured for OpenCode, and added to `instructions` alongside the
|
|
118
|
+
payload — exactly the shape Cursor has had all along, and for exactly the same
|
|
119
|
+
reason.
|
|
120
|
+
|
|
100
121
|
**The config is yours, never the installer's.** `leo-install.py` only ever reads
|
|
101
122
|
it, and never creates, migrates, rewrites, or removes it, including under
|
|
102
123
|
`--uninstall`; it lives outside the plugin so an upgrade cannot take it. The one
|
|
@@ -109,17 +130,22 @@ default and behaviour is exactly what it was before this existed.
|
|
|
109
130
|
|
|
110
131
|
Delivery differs by harness only in the last mile: Claude Code gets a `model:`
|
|
111
132
|
override alongside `subagent_type:` (the plugin-owned `agents/*.md` are never
|
|
112
|
-
rewritten), Codex gets the models substituted
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
rewritten), rendered live at session start. Codex gets the models substituted
|
|
134
|
+
into its installed profile TOMLs by the installer, because Codex plugins
|
|
135
|
+
cannot ship agent definitions and nothing re-renders them between installer
|
|
136
|
+
runs. Cursor gets its own `~/.cursor/rules/leos-agent-routing.mdc`, also
|
|
137
|
+
written by the installer, because its rules come straight out of the plugin
|
|
138
|
+
directory and are not otherwise re-rendered per session. Hermes and Pi get
|
|
139
|
+
the rendered dispatch line inside the payload itself, live, at whatever
|
|
140
|
+
cadence that harness re-renders it (see the delivery table under
|
|
141
|
+
[How it works](#how-it-works)). OpenCode gets nothing — see above.
|
|
116
142
|
|
|
117
143
|
## The dispatch guard
|
|
118
144
|
|
|
119
145
|
The payload has always said that a subagent dispatch must name a model. Prose
|
|
120
146
|
alone did not hold: a forgotten dispatch inherits the parent's expensive model
|
|
121
147
|
and pays a cold cache write per child, which is the single most expensive shape
|
|
122
|
-
this policy has. From
|
|
148
|
+
this policy has. From 11.202609070.0 that half of the rule is enforced by a hook instead,
|
|
123
149
|
and the prose it replaced came out of the always-loaded payload — enforcement in
|
|
124
150
|
code costs **zero** context per turn, so the guard paid for itself in bytes
|
|
125
151
|
before saving a cent.
|
|
@@ -143,11 +169,12 @@ is also deliberately narrow: a false block costs one re-dispatch, while a caught
|
|
|
143
169
|
inherited fan-out saves the cold prefix of every child, so the margin only holds
|
|
144
170
|
while the rule refuses to make judgment calls.
|
|
145
171
|
|
|
146
|
-
Detection is by **argument shape**, not tool name
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
guard allows the call and records
|
|
150
|
-
decision
|
|
172
|
+
Detection is by **argument shape**, not tool name: a dispatch is a call that
|
|
173
|
+
selects an agent and carries a brief. A shape the guard does not recognise is
|
|
174
|
+
left alone, so a harness it has never met keeps working. MCP tools are never
|
|
175
|
+
guarded. Anything that goes wrong inside the guard allows the call and records
|
|
176
|
+
`decision: "error"`, kept distinct from a decision to allow, because a guard
|
|
177
|
+
that dies quietly is worse than no guard.
|
|
151
178
|
|
|
152
179
|
```
|
|
153
180
|
LEOS_AGENT_DISPATCH_GUARD=on block (default)
|
|
@@ -157,13 +184,17 @@ LEOS_AGENT_DISPATCH_GUARD=on block (default)
|
|
|
157
184
|
LEOS_AGENT_DISPATCH_LOG_PROMPTS=1 debug only: keep 200 chars of brief text in the log
|
|
158
185
|
```
|
|
159
186
|
|
|
160
|
-
**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
**Per harness.** Claude Code dispatches `Agent` with `subagent_type`, and a
|
|
188
|
+
plugin install namespaces it — both forms are tiers. Codex dispatches
|
|
189
|
+
`spawn_agent`, which selects behaviour by `model` and `reasoning_effort` rather
|
|
190
|
+
than by naming an agent, so there the guard requires `model`; its `message` is
|
|
191
|
+
encrypted, so the over-delegation heuristic and block-conversion tracking do not
|
|
192
|
+
apply on Codex. Hermes and OpenCode run the same policy in-process, blocking by
|
|
193
|
+
directive and by throw respectively. Pi has no hook surface.
|
|
194
|
+
|
|
195
|
+
**Codex hash-pins hooks.** A new version of the guard enforces nothing there
|
|
196
|
+
until it is re-approved through `/hooks`, and the symptom is silence — zero
|
|
197
|
+
Codex rows in `dispatch_log.py report`.
|
|
167
198
|
|
|
168
199
|
### What it records
|
|
169
200
|
|
|
@@ -184,45 +215,82 @@ python3 scripts/usage_scan.py --since 7d
|
|
|
184
215
|
## How it works
|
|
185
216
|
|
|
186
217
|
The payload lives in exactly one file: [`rules/preferences.md`](rules/preferences.md).
|
|
218
|
+
Every harness now reads it live, out of the plugin directory, at or near session
|
|
219
|
+
start — upgrading the plugin *is* upgrading the policy, with no render-to-disk
|
|
220
|
+
step in between.
|
|
187
221
|
|
|
188
|
-
Cursor reads that file
|
|
189
|
-
|
|
190
|
-
[`scripts/
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<leos-agent version="10.7.1">
|
|
194
|
-
...the payload...
|
|
195
|
-
</leos-agent>
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Updating replaces that block and nothing else, so anything you wrote in those
|
|
199
|
-
files by hand survives an upgrade untouched. The script writes only when the
|
|
200
|
-
bytes actually differ, so running it twice is a no-op, and it writes through a
|
|
201
|
-
temporary file and an atomic rename, so an interrupted run cannot leave a
|
|
202
|
-
half-written instruction file behind.
|
|
203
|
-
|
|
204
|
-
If it ever finds markers it cannot pair — an opener with no closer, a stray
|
|
205
|
-
closer, two blocks — it refuses to touch that file and tells you what to fix.
|
|
206
|
-
Guessing there would mean deleting whatever sits between the markers, which is
|
|
207
|
-
exactly the content it exists to protect.
|
|
208
|
-
|
|
209
|
-
| Harness | Global file the installer writes |
|
|
210
|
-
|---|---|
|
|
211
|
-
| Claude Code | `~/.claude/CLAUDE.md` (the `leo-runner` / `leo-executor` agents need no installer step — the plugin's `agents/` directory delivers them) |
|
|
212
|
-
| Codex | `~/.codex/AGENTS.md` (plus `~/.codex/agents/leo-runner.toml` and `leo-executor.toml`) |
|
|
213
|
-
| Cursor | none — the plugin's always-apply rule delivers it |
|
|
214
|
-
| Hermes | `~/.hermes/SOUL.md` (edited only if it already exists) |
|
|
215
|
-
| Pi | `~/.pi/agent/AGENTS.md` |
|
|
216
|
-
| OpenCode | `~/.config/opencode/AGENTS.md` (plus copied skills and commands) |
|
|
222
|
+
Cursor reads that file directly as an always-apply rule; it was the model for
|
|
223
|
+
everything that follows. Every other harness runs
|
|
224
|
+
[`scripts/emit_payload.py`](scripts/emit_payload.py), which strips the
|
|
225
|
+
frontmatter, renders this machine's [routing](#per-machine-model-routing)
|
|
226
|
+
stanza, and prints the result — the trigger and the plumbing differ by harness:
|
|
217
227
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
| Harness | How the payload arrives | What the installer still does |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| Claude Code | a `SessionStart` hook (`hooks/hooks.json`, auto-discovered) runs `emit_payload.py`; its stdout becomes session context | nothing for the payload — `agents/` ships `leo-runner`/`leo-executor` directly; only cleans up a `<leos-agent>` block a pre-11.0 install left in `~/.claude/CLAUDE.md` |
|
|
231
|
+
| Codex | the same `hooks/hooks.json` entry — Codex auto-discovers it too, aliasing `${CLAUDE_PLUGIN_ROOT}` to its own plugin root | writes `~/.codex/agents/leo-runner.toml` and `leo-executor.toml`, since Codex plugins cannot ship agent definitions; cleans up a legacy `~/.codex/AGENTS.md` block |
|
|
232
|
+
| Cursor | native — `rules/preferences.md` read straight off the plugin directory as an always-apply rule | writes `~/.cursor/rules/leos-agent-routing.mdc`, only when routing is configured for Cursor |
|
|
233
|
+
| Hermes | `register(ctx)` in `__init__.py` calls `ctx.register_system_prompt_section("leos-agent", ..., position="after_memory")` — rendered once per session and frozen, so it never invalidates the cache mid-session | cleans up a legacy `~/.hermes/SOUL.md` block; `SOUL.md` itself is never written any more |
|
|
234
|
+
| Pi | `pi-extension.js` appends `emit_payload.py`'s output to the system prompt on `before_agent_start`, which fires every *turn*, not once per session — the one harness that pays a spawn per turn rather than per session — and contributes `skills/` via `resources_discover` | cleans up a legacy `~/.pi/agent/AGENTS.md` block |
|
|
235
|
+
| OpenCode | a one-time `"instructions": ["<plugin-root>/rules/preferences.md"]` line in `~/.config/opencode/opencode.json`, read at startup | copies skills and commands into `~/.config/opencode/skills/` and `commands/` (OpenCode's JS-only plugin API cannot register those); prints the `instructions` line and reports it outstanding until it's present — the file is JSONC with your comments in it, so the installer never edits it itself; cleans up a legacy `~/.config/opencode/AGENTS.md` block |
|
|
236
|
+
|
|
237
|
+
**The `<leos-agent version="...">` marker block still exists**, but only for
|
|
238
|
+
migration and uninstall now — nothing writes it on a fresh install. Malformed
|
|
239
|
+
markers are still refused rather than guessed at: an opener with no closer, a
|
|
240
|
+
stray closer, two blocks all stop the run and tell you what to fix, because
|
|
241
|
+
guessing would mean deleting whatever sits between them. Where a pre-11.0
|
|
242
|
+
install left a block behind, a normal run strips it and reports `migrated`,
|
|
243
|
+
preserving whatever surrounding text you wrote by hand; `--uninstall` does the
|
|
244
|
+
same cleanup, so either command clears the leftover.
|
|
245
|
+
|
|
246
|
+
Determinism is part of the contract, not an implementation detail: two runs of
|
|
247
|
+
`emit_payload.py` must produce byte-identical output, or the session's cached
|
|
248
|
+
prompt prefix stops being cacheable and every session pays a full cold write —
|
|
249
|
+
so nothing in the render path may read the clock, an absolute path, or git
|
|
250
|
+
state. It also fails open, silently, on stdout: any error prints nothing, exits
|
|
251
|
+
0, and leaves a breadcrumb in `$LEOS_AGENT_LOCAL_PATH/emit-payload.log` instead
|
|
252
|
+
of injecting a traceback into the session as context.
|
|
222
253
|
|
|
223
254
|
Requires Python 3.9+ and macOS, Linux, or WSL. No symlinks are used anywhere —
|
|
224
255
|
installs are real clones and copies.
|
|
225
256
|
|
|
257
|
+
## Multi-surface and cloud reach
|
|
258
|
+
|
|
259
|
+
"One install per harness" undersells how many surfaces a single install
|
|
260
|
+
reaches — and where it doesn't:
|
|
261
|
+
|
|
262
|
+
- **Claude Code's CLI, VS Code extension, JetBrains plugin, and desktop local
|
|
263
|
+
sessions share `~/.claude`.** The VS Code docs describe plugin management as
|
|
264
|
+
using "the same CLI commands under the hood," and the JetBrains plugin runs
|
|
265
|
+
the `claude` CLI rather than bundling its own — one `claude plugin install`
|
|
266
|
+
reaches all four.
|
|
267
|
+
- **Claude Code cloud/web sessions do not** — `~/.claude/CLAUDE.md` is
|
|
268
|
+
documented as not carried into them, so the old block-injection design never
|
|
269
|
+
reached the web at all. The plugin route does: declare `leos-agent` under
|
|
270
|
+
`enabledPlugins` in the repo's `.claude/settings.json` and it installs at
|
|
271
|
+
session start. That is a real gain of this design — the policy reaches cloud
|
|
272
|
+
for the first time.
|
|
273
|
+
- **Desktop WSL sessions have no plugin support**; SSH sessions read the
|
|
274
|
+
*remote* host's `~/.claude`, so the plugin needs installing on each SSH
|
|
275
|
+
target separately — a local install does not follow you there.
|
|
276
|
+
- **The desktop Cowork tab is a separate, account-synced config surface** and
|
|
277
|
+
will not see a CLI install.
|
|
278
|
+
- **Codex's IDE extension does not support plugins at all**, though it does
|
|
279
|
+
read `~/.codex/AGENTS.md` and the installed agent TOMLs — so a migrated
|
|
280
|
+
instruction file and the agent profiles still reach it; the live hook
|
|
281
|
+
delivery does not.
|
|
282
|
+
- **Cursor Cloud Agents do not see plugin-shipped rules.** User Rules are
|
|
283
|
+
account-synced and do reach them; a plugin's rules live on local disk and
|
|
284
|
+
stop there.
|
|
285
|
+
- **Hermes profiles and OpenCode's `OPENCODE_CONFIG_DIR` each create a second,
|
|
286
|
+
invisible config scope** — a plugin installed under one is invisible under
|
|
287
|
+
the other.
|
|
288
|
+
|
|
289
|
+
Upgrades need a new session almost everywhere: Claude Code's
|
|
290
|
+
`/reload-plugins` or a VS Code restart banner, Codex "start a new session,"
|
|
291
|
+
Cursor's Reload Window, Hermes a restart or `/restart`, OpenCode only rereads
|
|
292
|
+
its config at startup.
|
|
293
|
+
|
|
226
294
|
---
|
|
227
295
|
|
|
228
296
|
## Claude Code
|
|
@@ -237,11 +305,19 @@ claude plugin marketplace add foxhatleo/leos-agent
|
|
|
237
305
|
claude plugin install leos-agent@leos-agent --scope user
|
|
238
306
|
```
|
|
239
307
|
|
|
240
|
-
|
|
241
|
-
|
|
308
|
+
Nothing else to install. `hooks/hooks.json`'s `SessionStart` hook is
|
|
309
|
+
auto-discovered, and the payload starts arriving at the next session — no
|
|
310
|
+
`/install` run needed. If this checkout has a `<leos-agent>` block in
|
|
311
|
+
`~/.claude/CLAUDE.md` left by a version before 11.0, see Upgrade below to clear
|
|
312
|
+
it.
|
|
242
313
|
|
|
243
314
|
**Upgrade**
|
|
244
315
|
|
|
316
|
+
Claude Code auto-updates an installed plugin in the background roughly once
|
|
317
|
+
per session, so most of the time this is zero commands — start a new session
|
|
318
|
+
(`/reload-plugins`, or restart in VS Code) and the new payload is already
|
|
319
|
+
live. To force it immediately:
|
|
320
|
+
|
|
245
321
|
```bash
|
|
246
322
|
claude plugin marketplace update leos-agent
|
|
247
323
|
```
|
|
@@ -250,16 +326,24 @@ claude plugin marketplace update leos-agent
|
|
|
250
326
|
claude plugin install leos-agent@leos-agent --scope user
|
|
251
327
|
```
|
|
252
328
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
that
|
|
329
|
+
Both commands are safe to repeat; installing an already-current version
|
|
330
|
+
reports that it is already installed and changes nothing. If you're
|
|
331
|
+
upgrading a checkout that still carries a `<leos-agent>` block from before
|
|
332
|
+
this delivery mechanism, run the installer once to strip it — it reports
|
|
333
|
+
`migrated` and leaves the rest of `~/.claude/CLAUDE.md` exactly as you wrote
|
|
334
|
+
it:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
python3 ~/.claude/plugins/cache/leos-agent/leos-agent/11.202609070.0/scripts/leo-install.py claude
|
|
338
|
+
```
|
|
256
339
|
|
|
257
340
|
**Uninstall**
|
|
258
341
|
|
|
259
|
-
Run the installer's uninstall first, while the script is still on disk
|
|
342
|
+
Run the installer's uninstall first, while the script is still on disk — it
|
|
343
|
+
only has a legacy block to clean up, but do it before the plugin cache is gone:
|
|
260
344
|
|
|
261
345
|
```bash
|
|
262
|
-
python3 ~/.claude/plugins/cache/leos-agent/leos-agent/
|
|
346
|
+
python3 ~/.claude/plugins/cache/leos-agent/leos-agent/11.202609070.0/scripts/leo-install.py claude --uninstall
|
|
263
347
|
```
|
|
264
348
|
|
|
265
349
|
```bash
|
|
@@ -286,18 +370,22 @@ codex plugin marketplace add foxhatleo/leos-agent
|
|
|
286
370
|
codex plugin add leos-agent@leos-agent
|
|
287
371
|
```
|
|
288
372
|
|
|
289
|
-
|
|
290
|
-
|
|
373
|
+
The payload arrives the same way it does on Claude Code: `hooks/hooks.json`'s
|
|
374
|
+
`SessionStart` hook is auto-discovered, and Codex aliases
|
|
375
|
+
`${CLAUDE_PLUGIN_ROOT}` to its own plugin root, so no Codex-specific hook file
|
|
376
|
+
is needed. The installer is still required for what Codex plugins cannot ship
|
|
377
|
+
on their own — the two economical agent profiles:
|
|
291
378
|
|
|
292
379
|
```bash
|
|
293
|
-
python3 ~/.codex/plugins/cache/leos-agent/leos-agent/
|
|
380
|
+
python3 ~/.codex/plugins/cache/leos-agent/leos-agent/11.202609070.0/scripts/leo-install.py codex
|
|
294
381
|
```
|
|
295
382
|
|
|
296
|
-
This writes `~/.codex/
|
|
297
|
-
`leo-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
is
|
|
383
|
+
This writes `~/.codex/agents/leo-runner.toml` (`gpt-5.6-luna`, low effort) and
|
|
384
|
+
`leo-executor.toml` (`gpt-5.6-terra`, medium effort), and cleans up a
|
|
385
|
+
`<leos-agent>` block a pre-11.0 install left in `~/.codex/AGENTS.md`. Codex
|
|
386
|
+
trusts a hook by the hash of its command, so the command string in
|
|
387
|
+
`hooks/hooks.json` is deliberately constant — a payload edit alone never
|
|
388
|
+
requires re-approving the hook through `/hooks`.
|
|
301
389
|
|
|
302
390
|
**Upgrade**
|
|
303
391
|
|
|
@@ -309,13 +397,15 @@ codex plugin marketplace upgrade leos-agent
|
|
|
309
397
|
codex plugin add leos-agent@leos-agent
|
|
310
398
|
```
|
|
311
399
|
|
|
312
|
-
Re-run the installer
|
|
313
|
-
|
|
400
|
+
Re-run the installer to refresh the agent TOMLs (a no-op unless your routing
|
|
401
|
+
config changed), then start a new thread — Codex picks up plugin changes on
|
|
402
|
+
new threads only. Re-adding an already-installed plugin is idempotent, and the
|
|
403
|
+
hook needs no re-approval since its command string never changes.
|
|
314
404
|
|
|
315
405
|
**Uninstall**
|
|
316
406
|
|
|
317
407
|
```bash
|
|
318
|
-
python3 ~/.codex/plugins/cache/leos-agent/leos-agent/
|
|
408
|
+
python3 ~/.codex/plugins/cache/leos-agent/leos-agent/11.202609070.0/scripts/leo-install.py codex --uninstall
|
|
319
409
|
```
|
|
320
410
|
|
|
321
411
|
```bash
|
|
@@ -331,9 +421,12 @@ codex plugin marketplace remove leos-agent
|
|
|
331
421
|
## Cursor
|
|
332
422
|
|
|
333
423
|
Cursor has no on-disk global rules file — its User Rules live in your synced
|
|
334
|
-
Cursor account — so there
|
|
335
|
-
|
|
336
|
-
plugin
|
|
424
|
+
Cursor account — so there was never anything for the installer to write here;
|
|
425
|
+
this is the harness the rest of leos-agent's live delivery was modeled on. The
|
|
426
|
+
plugin ships the payload as an always-apply rule instead, which takes effect
|
|
427
|
+
as soon as the plugin is installed. The installer still has one job for
|
|
428
|
+
Cursor: writing `~/.cursor/rules/leos-agent-routing.mdc`, and only when
|
|
429
|
+
[routing](#per-machine-model-routing) is actually configured for it.
|
|
337
430
|
|
|
338
431
|
**Install** — either through the UI, or as a local clone.
|
|
339
432
|
|
|
@@ -394,14 +487,17 @@ plugins:
|
|
|
394
487
|
- leos-agent
|
|
395
488
|
```
|
|
396
489
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
490
|
+
Nothing else to install. `register(ctx)` in `__init__.py` calls
|
|
491
|
+
`ctx.register_system_prompt_section("leos-agent", ..., position="after_memory")`
|
|
492
|
+
— the cache-safe path: it renders once per session and freezes, rather than
|
|
493
|
+
re-rendering on every turn. `~/.hermes/SOUL.md` is not written by this plugin
|
|
494
|
+
at all, so there is no "run Hermes once first" step any more. On a Hermes build
|
|
495
|
+
old enough to lack `register_system_prompt_section`, the plugin degrades
|
|
496
|
+
silently and delivers nothing — upgrade Hermes.
|
|
401
497
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
498
|
+
`/leo-install` still exists, mainly for `--dry-run` and `--uninstall`; a plain
|
|
499
|
+
run is a no-op unless a pre-11.0 install left a `<leos-agent>` block in
|
|
500
|
+
`SOUL.md`, in which case it strips it and reports `migrated`.
|
|
405
501
|
|
|
406
502
|
**Upgrade**
|
|
407
503
|
|
|
@@ -415,7 +511,9 @@ or, for a clone:
|
|
|
415
511
|
git -C ~/.hermes/plugins/leos-agent pull
|
|
416
512
|
```
|
|
417
513
|
|
|
418
|
-
|
|
514
|
+
Restart Hermes (or `/restart`) for a fresh session to pick up the new payload.
|
|
515
|
+
If this checkout still carries a legacy `<leos-agent>` block in `SOUL.md`, run
|
|
516
|
+
`/leo-install` once to strip it.
|
|
419
517
|
|
|
420
518
|
**Uninstall**
|
|
421
519
|
|
|
@@ -428,7 +526,8 @@ hermes plugins remove leos-agent
|
|
|
428
526
|
```
|
|
429
527
|
|
|
430
528
|
Remove the `leos-agent` entry from `plugins.enabled`, and delete the clone if
|
|
431
|
-
you made one. Your own `SOUL.md` content is left intact — only
|
|
529
|
+
you made one. Your own `SOUL.md` content is left intact — only a legacy block,
|
|
530
|
+
if one is present, goes.
|
|
432
531
|
|
|
433
532
|
**Note on model routing:** Hermes applies a single `delegation.model` to every
|
|
434
533
|
child of a `delegate_task` call, so it cannot vary the model per spawn. A
|
|
@@ -445,14 +544,22 @@ say so where a per-spawn model is not available.
|
|
|
445
544
|
pi install git:github.com/foxhatleo/leos-agent
|
|
446
545
|
```
|
|
447
546
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
547
|
+
Nothing else to install. `pi-extension.js` registers `before_agent_start`,
|
|
548
|
+
which runs `scripts/emit_payload.py` and appends its output to the system
|
|
549
|
+
prompt, and `resources_discover`, which contributes `skills/` directly — no
|
|
550
|
+
`/skill:install`, no `~/.pi/agent/AGENTS.md` write. Unlike Claude Code's and
|
|
551
|
+
Codex's session-scoped hook, `before_agent_start` fires on every turn, not
|
|
552
|
+
once per session, so Pi pays one `python3` spawn per turn rather than per
|
|
553
|
+
session.
|
|
453
554
|
|
|
454
555
|
Pi pins the git ref it installed and records the package in
|
|
455
|
-
`~/.pi/agent/settings.json`; re-running install is idempotent.
|
|
556
|
+
`~/.pi/agent/settings.json`; re-running install is idempotent. If a legacy
|
|
557
|
+
`<leos-agent>` block exists in `~/.pi/agent/AGENTS.md` from a pre-11.0 install,
|
|
558
|
+
strip it with the installer:
|
|
559
|
+
|
|
560
|
+
```bash
|
|
561
|
+
python3 ~/.pi/agent/git/github.com/foxhatleo/leos-agent/scripts/leo-install.py pi
|
|
562
|
+
```
|
|
456
563
|
|
|
457
564
|
**Upgrade**
|
|
458
565
|
|
|
@@ -464,10 +571,10 @@ Pinned refs are reconciled, never silently advanced — to move to a new tag,
|
|
|
464
571
|
install it explicitly:
|
|
465
572
|
|
|
466
573
|
```bash
|
|
467
|
-
pi install git:github.com/foxhatleo/leos-agent@
|
|
574
|
+
pi install git:github.com/foxhatleo/leos-agent@v11.202609070.0
|
|
468
575
|
```
|
|
469
576
|
|
|
470
|
-
|
|
577
|
+
Start a new session afterwards; there is nothing else to re-run.
|
|
471
578
|
|
|
472
579
|
**Uninstall**
|
|
473
580
|
|
|
@@ -494,19 +601,32 @@ opencode plugin leos-agent -g
|
|
|
494
601
|
|
|
495
602
|
That adds the package to the `plugin` array in `~/.config/opencode/opencode.json`
|
|
496
603
|
(or `.jsonc`) and caches it. Bootstrap the installer once by running the script from
|
|
497
|
-
the cache — OpenCode's plugin API cannot register skills
|
|
498
|
-
first run has to come from the package itself:
|
|
604
|
+
the cache — OpenCode's JS-only plugin API cannot register skills, commands, or a
|
|
605
|
+
payload source, so the first run has to come from the package itself:
|
|
499
606
|
|
|
500
607
|
```bash
|
|
501
608
|
python3 ~/.cache/opencode/packages/leos-agent@latest/node_modules/leos-agent/scripts/leo-install.py opencode
|
|
502
609
|
```
|
|
503
610
|
|
|
504
|
-
That
|
|
505
|
-
`~/.config/opencode/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
611
|
+
That copies the skills and commands into `~/.config/opencode/skills/` and
|
|
612
|
+
`~/.config/opencode/commands/`, cleans up a `<leos-agent>` block a pre-11.0
|
|
613
|
+
install left in `~/.config/opencode/AGENTS.md`, and — since the payload itself
|
|
614
|
+
now arrives through a one-time line in `opencode.json` rather than a written
|
|
615
|
+
file — prints the exact line to add and reports it outstanding until it's
|
|
616
|
+
there:
|
|
617
|
+
|
|
618
|
+
```jsonc
|
|
619
|
+
"instructions": ["<plugin-root>/rules/preferences.md"]
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
The installer never adds that line for you: `opencode.json` is JSONC, with
|
|
623
|
+
your comments in it, and rewriting it would destroy them. Add it by hand,
|
|
624
|
+
once — OpenCode reads it at startup from then on. From then on `/leo-install`
|
|
625
|
+
works inside OpenCode for re-copying the skills and commands. Those copies are
|
|
626
|
+
installed with the plugin root already resolved to an absolute path —
|
|
627
|
+
OpenCode sets no resolution env var, and the copies live apart from the
|
|
628
|
+
scripts they invoke — so re-run the installer after clearing or moving the
|
|
629
|
+
package cache to point them at the new location.
|
|
510
630
|
|
|
511
631
|
**Upgrade**
|
|
512
632
|
|
|
@@ -520,7 +640,10 @@ If the cache holds a stale copy, clear it and let OpenCode refetch:
|
|
|
520
640
|
rm -rf ~/.cache/opencode/packages/leos-agent@*
|
|
521
641
|
```
|
|
522
642
|
|
|
523
|
-
Re-run the bootstrap install command above to refresh the copied
|
|
643
|
+
Re-run the bootstrap install command above to refresh the copied skills and
|
|
644
|
+
commands — the `instructions` line does not need touching, since it just
|
|
645
|
+
points at the plugin root and the payload behind it updates live. OpenCode
|
|
646
|
+
only rereads its config at startup, so restart it to pick up either change.
|
|
524
647
|
|
|
525
648
|
**Uninstall**
|
|
526
649
|
|
|
@@ -528,10 +651,11 @@ Re-run the bootstrap install command above to refresh the copied files.
|
|
|
528
651
|
python3 ~/.cache/opencode/packages/leos-agent@latest/node_modules/leos-agent/scripts/leo-install.py opencode --uninstall
|
|
529
652
|
```
|
|
530
653
|
|
|
531
|
-
OpenCode has no plugin-remove command, so delete the
|
|
532
|
-
the `plugin` array
|
|
533
|
-
|
|
534
|
-
|
|
654
|
+
OpenCode has no plugin-remove command, so **by hand**: delete the
|
|
655
|
+
`"leos-agent"` entry from the `plugin` array, and remove the `"instructions"`
|
|
656
|
+
line pointing at this plugin's `rules/preferences.md`, in
|
|
657
|
+
`~/.config/opencode/opencode.json`. The installer edits neither — same JSONC
|
|
658
|
+
reason as above. Then clear the cache:
|
|
535
659
|
|
|
536
660
|
```bash
|
|
537
661
|
rm -rf ~/.cache/opencode/packages/leos-agent@*
|
|
@@ -638,6 +762,14 @@ JSON.
|
|
|
638
762
|
|
|
639
763
|
## Development
|
|
640
764
|
|
|
765
|
+
**One-time setup:** `git config core.hooksPath .githooks` activates
|
|
766
|
+
`.githooks/pre-commit`, which stamps today's version with `scripts/bump.py`
|
|
767
|
+
and runs `scripts/check.py` on every commit — so a normal commit already
|
|
768
|
+
carries a canonical version and a green structural check, and you should not
|
|
769
|
+
need to run either by hand for a routine change. The scheme is
|
|
770
|
+
`11.YYYYMMDDX.0`: major pinned at 11, minor the UTC calendar date with a
|
|
771
|
+
same-day serial digit appended, patch always 0.
|
|
772
|
+
|
|
641
773
|
Run the checks:
|
|
642
774
|
|
|
643
775
|
```bash
|
|
@@ -684,16 +816,25 @@ claude plugin uninstall leos-agent@leos-agent && claude plugin install leos-agen
|
|
|
684
816
|
```
|
|
685
817
|
|
|
686
818
|
or replace the cachebuster suffix in the Codex manifest with one in the form
|
|
687
|
-
`
|
|
819
|
+
`11.202609070.0+codex.local-YYYYMMDD-HHMMSS` and re-add. Either way, plugin changes only
|
|
688
820
|
reach a **new** session or thread.
|
|
689
821
|
|
|
690
822
|
`--check` exits non-zero when a file is out of date, and `--force` replaces a
|
|
691
823
|
copied file that something else has since overwritten.
|
|
692
824
|
|
|
693
|
-
To release:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
825
|
+
To release: the pre-commit hook has already stamped the version via
|
|
826
|
+
`scripts/bump.py` on your latest commit if `core.hooksPath` is set up as
|
|
827
|
+
above. Otherwise run it by hand:
|
|
828
|
+
|
|
829
|
+
```bash
|
|
830
|
+
python3 scripts/bump.py
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
That rewrites every `major.minor.patch` string it owns — `package.json`, the
|
|
834
|
+
three `plugin.json` files, `.claude-plugin/marketplace.json`, `plugin.yaml`,
|
|
835
|
+
and every mention in this README, including the uninstall commands' cache
|
|
836
|
+
paths — in one pass; `scripts/check.py` still fails the build on any stale
|
|
837
|
+
one it finds. Then push a `v`-prefixed tag matching that version.
|
|
697
838
|
|
|
698
839
|
Pushing that tag is the whole release. `.github/workflows/release.yml` runs the
|
|
699
840
|
tests and both checks, refuses a tag that disagrees with `package.json`,
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume|clear|compact",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/emit_payload.py\"",
|
|
10
|
+
"timeout": 10
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
3
15
|
"PreToolUse": [
|
|
4
16
|
{
|
|
5
17
|
"matcher": "[Tt]ask|[Aa]gent|[Ss]ubagent|[Dd]ispatch|[Dd]elegate|[Ss]pawn",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leos-agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.202609070.0",
|
|
4
4
|
"description": "Leo's portable agent operating policy: orchestrator main thread, subagent-first execution, cost-tiered model routing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"pi": {
|
|
24
24
|
"skills": [
|
|
25
25
|
"./skills"
|
|
26
|
+
],
|
|
27
|
+
"extensions": [
|
|
28
|
+
"./pi-extension.js"
|
|
26
29
|
]
|
|
27
30
|
},
|
|
28
31
|
"files": [
|
|
@@ -31,6 +34,7 @@
|
|
|
31
34
|
"hooks/",
|
|
32
35
|
"index.js",
|
|
33
36
|
"payload/",
|
|
37
|
+
"pi-extension.js",
|
|
34
38
|
"rules/",
|
|
35
39
|
"scripts/",
|
|
36
40
|
"skills-claude/",
|