leos-agent 10.7.0 → 10.7.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # leos-agent
2
2
 
3
- Leo's portable agent operating policy, version **10.7.0**, installable on Claude
3
+ Leo's portable agent operating policy, version **10.7.1**, installable on Claude
4
4
  Code, Codex, Cursor, Hermes, Pi, and OpenCode through each harness's own plugin
5
5
  system.
6
6
 
@@ -119,7 +119,7 @@ their global instruction file.
119
119
  The payload has always said that a subagent dispatch must name a model. Prose
120
120
  alone did not hold: a forgotten dispatch inherits the parent's expensive model
121
121
  and pays a cold cache write per child, which is the single most expensive shape
122
- this policy has. From 10.7.0 that half of the rule is enforced by a hook instead,
122
+ this policy has. From 10.7.1 that half of the rule is enforced by a hook instead,
123
123
  and the prose it replaced came out of the always-loaded payload — enforcement in
124
124
  code costs **zero** context per turn, so the guard paid for itself in bytes
125
125
  before saving a cent.
@@ -134,6 +134,9 @@ can name one. Three ways to comply, all of them one word:
134
134
  | a generic agent, no model | `subagent_type: "leo-executor"` | an approved plan, a well-specified change |
135
135
  | a generic agent, no model | `model: "<name>"` | investigation and debugging — naming it *is* the stated reason |
136
136
 
137
+ A plugin install namespaces the type — dispatch `leos-agent:leo-runner` rather
138
+ than `leo-runner`; the guard accepts either.
139
+
137
140
  It never picks a model for you. It cannot force cheap work onto an expensive
138
141
  problem, so it cannot cause a quality regression — only an explicit choice. It
139
142
  is also deliberately narrow: a false block costs one re-dispatch, while a caught
@@ -158,7 +161,7 @@ LEOS_AGENT_DISPATCH_LOG_PROMPTS=1 debug only: keep 200 chars of brief text in t
158
161
  Hermes and OpenCode are wired with the same policy but their dispatch argument
159
162
  shapes are unconfirmed; there the guard is best-effort and no-ops rather than
160
163
  misfires. Pi has no hook surface and gets nothing. **Codex hash-pins hooks**, so
161
- upgrading to 10.7.0 — and every later edit to the guard — needs re-approval
164
+ upgrading to 10.7.1 — and every later edit to the guard — needs re-approval
162
165
  through `/hooks` there. Until you do, Codex silently enforces nothing; zero Codex
163
166
  rows in the report is the symptom.
164
167
 
@@ -187,7 +190,7 @@ gets it through its global instruction file, written by
187
190
  [`scripts/leo-install.py`](scripts/leo-install.py) into a marker block:
188
191
 
189
192
  ```
190
- <leos-agent version="10.7.0">
193
+ <leos-agent version="10.7.1">
191
194
  ...the payload...
192
195
  </leos-agent>
193
196
  ```
@@ -256,7 +259,7 @@ that it is already installed and changes nothing.
256
259
  Run the installer's uninstall first, while the script is still on disk:
257
260
 
258
261
  ```bash
259
- python3 ~/.claude/plugins/cache/leos-agent/leos-agent/10.7.0/scripts/leo-install.py claude --uninstall
262
+ python3 ~/.claude/plugins/cache/leos-agent/leos-agent/10.7.1/scripts/leo-install.py claude --uninstall
260
263
  ```
261
264
 
262
265
  ```bash
@@ -287,7 +290,7 @@ Then run the `install` skill in a Codex session (`$leos-agent`, then `install`),
287
290
  run the script directly:
288
291
 
289
292
  ```bash
290
- python3 ~/.codex/plugins/cache/leos-agent/leos-agent/10.7.0/scripts/leo-install.py codex
293
+ python3 ~/.codex/plugins/cache/leos-agent/leos-agent/10.7.1/scripts/leo-install.py codex
291
294
  ```
292
295
 
293
296
  This writes `~/.codex/AGENTS.md` and installs two economical agents:
@@ -312,7 +315,7 @@ threads only. Re-adding an already-installed plugin is idempotent.
312
315
  **Uninstall**
313
316
 
314
317
  ```bash
315
- python3 ~/.codex/plugins/cache/leos-agent/leos-agent/10.7.0/scripts/leo-install.py codex --uninstall
318
+ python3 ~/.codex/plugins/cache/leos-agent/leos-agent/10.7.1/scripts/leo-install.py codex --uninstall
316
319
  ```
317
320
 
318
321
  ```bash
@@ -461,7 +464,7 @@ Pinned refs are reconciled, never silently advanced — to move to a new tag,
461
464
  install it explicitly:
462
465
 
463
466
  ```bash
464
- pi install git:github.com/foxhatleo/leos-agent@v10.7.0
467
+ pi install git:github.com/foxhatleo/leos-agent@v10.7.1
465
468
  ```
466
469
 
467
470
  Re-run `/skill:install` afterwards.
@@ -681,7 +684,7 @@ claude plugin uninstall leos-agent@leos-agent && claude plugin install leos-agen
681
684
  ```
682
685
 
683
686
  or replace the cachebuster suffix in the Codex manifest with one in the form
684
- `10.7.0+codex.local-YYYYMMDD-HHMMSS` and re-add. Either way, plugin changes only
687
+ `10.7.1+codex.local-YYYYMMDD-HHMMSS` and re-add. Either way, plugin changes only
685
688
  reach a **new** session or thread.
686
689
 
687
690
  `--check` exits non-zero when a file is out of date, and `--force` replaces a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leos-agent",
3
- "version": "10.7.0",
3
+ "version": "10.7.1",
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",
@@ -192,11 +192,21 @@ def routable(name):
192
192
  return False
193
193
 
194
194
 
195
+ def _is_tier(agent):
196
+ try:
197
+ from dispatch_log import is_tier
198
+ return is_tier(agent)
199
+ except Exception:
200
+ # Never let a missing sibling module turn a compliant dispatch into a
201
+ # refusal: fall back to allowing anything that looks like a tier.
202
+ return bool(agent) and agent.rsplit(":", 1)[-1].startswith("leo-")
203
+
204
+
195
205
  def decide(dispatch, name, is_routable):
196
206
  """(action, reason). The entire policy, and deliberately four lines of it."""
197
207
  if dispatch is None:
198
208
  return ALLOW, "not-a-dispatch"
199
- if dispatch.agent.startswith("leo-"):
209
+ if _is_tier(dispatch.agent):
200
210
  return ALLOW, "leo-tier" # the agent definition carries the model
201
211
  if dispatch.model:
202
212
  return ALLOW, "explicit-model" # a choice was typed; cheap or not
@@ -212,6 +222,7 @@ def render_block(dispatch):
212
222
  "silently inherit the parent's. Re-dispatch with one of:\n"
213
223
  ' subagent_type "leo-runner" reading, search, tests, logs, codemods, fan-out\n'
214
224
  ' subagent_type "leo-executor" an approved plan or a specified change\n'
225
+ ' (a plugin install namespaces these: "leos-agent:leo-runner")\n'
215
226
  ' model: "<name>" investigation/debugging - naming it IS the reason\n'
216
227
  "Set LEOS_AGENT_DISPATCH_GUARD=off to disable, =warn to log only."
217
228
  ) % dispatch.agent
@@ -47,6 +47,23 @@ MAX_BYTES = 1 << 20
47
47
  RECORD_VERSION = 1
48
48
 
49
49
 
50
+ TIER_PREFIX = "leo-"
51
+
52
+
53
+ def is_tier(agent):
54
+ """Is this one of the economical-tier agents?
55
+
56
+ Match the bare name after any namespace. A plugin install namespaces the
57
+ type -- Claude Code dispatches `leos-agent:leo-runner`, not `leo-runner` --
58
+ and a raw prefix test rejects that, which blocked the exact path the refusal
59
+ message recommends. That is the worst false positive this guard can have, so
60
+ the one place that decides it is shared rather than repeated.
61
+ """
62
+ if not agent:
63
+ return False
64
+ return agent.rsplit(":", 1)[-1].rsplit("/", 1)[-1].startswith(TIER_PREFIX)
65
+
66
+
50
67
  def path():
51
68
  return os.path.join(_data_root(), LOG_NAME)
52
69
 
@@ -149,7 +166,7 @@ def summarise(entries):
149
166
  tiers = collections.Counter()
150
167
  for entry in entries:
151
168
  agent = entry.get("agent") or ""
152
- if agent.startswith("leo-"):
169
+ if is_tier(agent):
153
170
  tiers[agent] += 1
154
171
  elif entry.get("model"):
155
172
  tiers["explicit model"] += 1
@@ -336,6 +336,16 @@ def scan_guard():
336
336
  return {"error": "%s: %s" % (type(exc).__name__, exc)}
337
337
 
338
338
 
339
+ def _is_tier(agent):
340
+ """Shared with the guard, so the report cannot disagree with enforcement
341
+ about what counts as a tier -- namespaced plugin types included."""
342
+ try:
343
+ import dispatch_log
344
+ return dispatch_log.is_tier(agent)
345
+ except Exception:
346
+ return bool(agent) and agent.rsplit(":", 1)[-1].startswith("leo-")
347
+
348
+
339
349
  def routing_compliance(dispatches):
340
350
  """Problem (c), quantified: how many dispatches named a tier, and how many
341
351
  let the parent's model ride along."""
@@ -343,7 +353,7 @@ def routing_compliance(dispatches):
343
353
  inherited_bytes = 0
344
354
  for d in dispatches:
345
355
  agent = d.get("agent") or "-"
346
- if agent.startswith("leo-"):
356
+ if _is_tier(agent):
347
357
  tiers[agent] += 1
348
358
  elif d.get("model"):
349
359
  tiers["explicit model"] += 1