@litfamily/lithermes 1.0.6 → 1.0.8

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,7 +1,7 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/cover-motion.webp" width="100%" alt="LitHermes cover art. A lime signal moves through orbital paths above the HERMES AGENT wordmark; a still frame from this motion cover appears when reduced motion is preferred." /></picture></p>
2
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/cover.webp" width="100%" alt="Five armored robots, one for each LitFamily product, beneath the words KEEP THE WORK LIT." /></p>
1
+ <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion.webp" width="100%" alt="LitHermes cover art. A lime signal moves through orbital paths above the HERMES AGENT wordmark; a still frame from this motion cover appears when reduced motion is preferred." /></picture></p>
2
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover.webp" width="100%" alt="Five armored robots, one for each LitFamily product, beneath the words KEEP THE WORK LIT." /></p>
3
3
 
4
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/ascii-readme.svg" width="480" alt="LIT ASCII B mark" /></p>
4
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ascii-readme.svg" width="480" alt="LIT ASCII B mark" /></p>
5
5
 
6
6
  <details>
7
7
  <summary>Copy ASCII logo</summary>
@@ -31,19 +31,19 @@
31
31
 
32
32
  </details>
33
33
 
34
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lithermes-wordmark.svg" width="480" alt="LITHERMES display type" /></p>
35
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lithermes-clay-icon.png" width="160" alt="LitHermes clay mark" /></p>
34
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-wordmark.svg" width="480" alt="LITHERMES display type" /></p>
35
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-clay-icon.png" width="160" alt="LitHermes clay mark" /></p>
36
36
 
37
37
  <p align="center">
38
- <a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/badge-version.svg" alt="1.0.6" /></a>
39
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/badge-license.svg" alt="MIT license" /></a>
38
+ <a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-version.svg" alt="1.0.8" /></a>
39
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-license.svg" alt="MIT license" /></a>
40
40
  </p>
41
41
 
42
42
  <p align="center">
43
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lucide-book-open.svg" width="16" alt="" /> Docs</a> &nbsp;
43
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-book-open.svg" width="16" alt="" /> Docs</a> &nbsp;
44
44
  <a href="#install">Install</a> &nbsp;
45
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition</a> &nbsp;
46
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
45
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition</a> &nbsp;
46
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
47
47
  </p>
48
48
 
49
49
  # LitHermes
@@ -52,11 +52,11 @@
52
52
 
53
53
  **What it is:** LitHermes connects planning, execution, review, and handoff inside **Hermes Agent**.
54
54
 
55
- [한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/README_Ko-KR.md) · [npm](https://www.npmjs.com/package/@litfamily/lithermes) · [GitHub](https://github.com/wjgoarxiv/lithermes)
55
+ [한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/README_Ko-KR.md) · [npm](https://www.npmjs.com/package/@litfamily/lithermes) · [GitHub](https://github.com/wjgoarxiv/lithermes)
56
56
 
57
57
  ## Install
58
58
 
59
- Source version: `@litfamily/lithermes@1.0.6`. Have Hermes Agent and Node.js 18+ installed, with write access to your Hermes home (normally `~/.hermes`).
59
+ Source version: `@litfamily/lithermes@1.0.8`. Have Hermes Agent and Node.js 18+ installed, with write access to your Hermes home (normally `~/.hermes`).
60
60
 
61
61
  The installer targets your Hermes home. For an isolated trial, set `HERMES_HOME` to a new empty directory before installation and use that same value when starting Hermes. Keep your existing home and settings. `--no-patch-installed-hermes` also prevents compatibility edits to the detected host installation outside that profile.
62
62
 
package/README_Ko-KR.md CHANGED
@@ -1,7 +1,7 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/cover-motion.webp" width="100%" alt="LitHermes 표지. 라임색 신호가 궤도를 지나 HERMES AGENT 워드마크로 이어집니다. 모션을 줄이도록 설정하면 이 모션 표지에서 추출한 정지 프레임을 표시합니다." /></picture></p>
2
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/cover.webp" width="100%" alt="LitFamily 다섯 제품을 나타내는 장갑 로봇 패널과 KEEP THE WORK LIT. 문구" /></p>
1
+ <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion.webp" width="100%" alt="LitHermes 표지. 라임색 신호가 궤도를 지나 HERMES AGENT 워드마크로 이어집니다. 모션을 줄이도록 설정하면 이 모션 표지에서 추출한 정지 프레임을 표시합니다." /></picture></p>
2
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover.webp" width="100%" alt="LitFamily 다섯 제품을 나타내는 장갑 로봇 패널과 KEEP THE WORK LIT. 문구" /></p>
3
3
 
4
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/ascii-readme.svg" width="480" alt="LIT ASCII B 마크" /></p>
4
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ascii-readme.svg" width="480" alt="LIT ASCII B 마크" /></p>
5
5
 
6
6
  <details>
7
7
  <summary>ASCII 로고 복사</summary>
@@ -31,19 +31,19 @@
31
31
 
32
32
  </details>
33
33
 
34
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lithermes-wordmark.svg" width="480" alt="LITHERMES 디스플레이 서체" /></p>
35
- <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lithermes-clay-icon.png" width="160" alt="LitHermes 클레이 마크" /></p>
34
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-wordmark.svg" width="480" alt="LITHERMES 디스플레이 서체" /></p>
35
+ <p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-clay-icon.png" width="160" alt="LitHermes 클레이 마크" /></p>
36
36
 
37
37
  <p align="center">
38
- <a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/badge-version.svg" alt="1.0.6" /></a>
39
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/badge-license.svg" alt="MIT license" /></a>
38
+ <a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-version.svg" alt="1.0.8" /></a>
39
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-license.svg" alt="MIT license" /></a>
40
40
  </p>
41
41
 
42
42
  <p align="center">
43
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lucide-book-open.svg" width="16" alt="" /> 문서</a> &nbsp;
43
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-book-open.svg" width="16" alt="" /> 문서</a> &nbsp;
44
44
  <a href="#설치">설치</a> &nbsp;
45
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition</a> &nbsp;
46
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
45
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition</a> &nbsp;
46
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
47
47
  </p>
48
48
 
49
49
  # LitHermes
@@ -52,11 +52,11 @@
52
52
 
53
53
  **무엇인가요:** LitHermes는 **Hermes Agent**에서 계획, 실행, 검토, 인계를 이어갑니다.
54
54
 
55
- [English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.6/README.md) · [npm](https://www.npmjs.com/package/@litfamily/lithermes) · [GitHub](https://github.com/wjgoarxiv/lithermes)
55
+ [English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/README.md) · [npm](https://www.npmjs.com/package/@litfamily/lithermes) · [GitHub](https://github.com/wjgoarxiv/lithermes)
56
56
 
57
57
  ## 설치
58
58
 
59
- 소스 기준 버전은 `@litfamily/lithermes@1.0.6`입니다. Hermes Agent와 Node.js 18+가 필요하며, Hermes 홈(기본 `~/.hermes`)에 쓰기 권한이 있어야 합니다.
59
+ 소스 기준 버전은 `@litfamily/lithermes@1.0.8`입니다. Hermes Agent와 Node.js 18+가 필요하며, Hermes 홈(기본 `~/.hermes`)에 쓰기 권한이 있어야 합니다.
60
60
 
61
61
  설치기는 Hermes 홈을 대상으로 합니다. 별도 체험을 하려면 설치 전에 `HERMES_HOME`을 새 빈 디렉터리로 지정하고 Hermes를 시작할 때도 같은 값을 사용하세요. 기존 홈과 설정은 보관합니다. `--no-patch-installed-hermes`는 해당 프로필 밖에서 감지된 호스트 설치본에 호환성 패치를 적용하지 못하게 합니다.
62
62
 
@@ -84,6 +84,27 @@ _OPENAI_CATALOG_EFFORTS = {
84
84
  }
85
85
  _REVIEWER_ROUTE_NAME = "".join(chr(code) for code in (109, 111, 109, 117, 115))
86
86
  SUPPORTED_HERMES_HOST_VERSIONS = frozenset(("0.17.0", "0.19.0"))
87
+ _MINIMUM_HERMES_HOST_VERSION = (0, 17, 0)
88
+
89
+
90
+ def _classify_hermes_host_version(host_version: str) -> str:
91
+ if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", host_version) is None:
92
+ return "unsupported"
93
+ version = tuple(int(part) for part in host_version.split("."))
94
+ if version < _MINIMUM_HERMES_HOST_VERSION:
95
+ return "unsupported"
96
+ if host_version in SUPPORTED_HERMES_HOST_VERSIONS:
97
+ return "verified"
98
+ return "beyond-verified"
99
+
100
+
101
+ def _beyond_verified_host_note(host_version: str) -> str | None:
102
+ if _classify_hermes_host_version(host_version) != "beyond-verified":
103
+ return None
104
+ return (
105
+ f"[NOTE] Hermes host {host_version} is beyond the verified matrix "
106
+ "(0.17.0, 0.19.0); diagnostics are read-only"
107
+ )
87
108
 
88
109
 
89
110
  @dataclass(frozen=True, slots=True)
@@ -94,6 +115,7 @@ class ModelCapabilities:
94
115
  child_effort: str
95
116
  concurrency: int
96
117
  async_batches: int
118
+ shared_async_cap: bool
97
119
  spawn_depth: int
98
120
  flat: bool
99
121
  approved: bool
@@ -149,14 +171,31 @@ def _host_config_path() -> Path:
149
171
  return home / "config.yaml"
150
172
 
151
173
 
174
+ def _is_secret_looking_scalar(value: object) -> bool:
175
+ if not isinstance(value, str):
176
+ return False
177
+ trimmed = value.strip()
178
+ if not trimmed or re.fullmatch(r"(?:true|false|null)", trimmed, re.IGNORECASE):
179
+ return False
180
+ if re.fullmatch(r"[A-Z][A-Z0-9_]*", trimmed) and "_" in trimmed:
181
+ return False
182
+ if re.fullmatch(r"\$\{[A-Z][A-Z0-9_]*\}", trimmed):
183
+ return False
184
+ return not re.search(r"(?:^|[-_])(redacted|placeholder)(?:[-_]|$)", trimmed, re.IGNORECASE)
185
+
186
+
152
187
  def _has_credential_key(value: object) -> bool:
153
188
  if isinstance(value, dict):
154
- return any(
155
- _CREDENTIAL_KEY_RE.search(
156
- re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", str(key)).replace("-", "_")
157
- ) or _has_credential_key(child)
158
- for key, child in value.items()
159
- )
189
+ for key, child in value.items():
190
+ normalized = re.sub(
191
+ r"([a-z0-9])([A-Z])", r"\1_\2", str(key or "")
192
+ ).replace("-", "_")
193
+ if (
194
+ _CREDENTIAL_KEY_RE.search(normalized)
195
+ and _is_secret_looking_scalar(child)
196
+ ) or _has_credential_key(child):
197
+ return True
198
+ return False
160
199
  if isinstance(value, list):
161
200
  return any(_has_credential_key(child) for child in value)
162
201
  return False
@@ -203,7 +242,7 @@ def _cataloged_route(model: object, effort: object) -> bool:
203
242
 
204
243
 
205
244
  def _parse_capabilities(host_version: str) -> CapabilityResult:
206
- if host_version not in SUPPORTED_HERMES_HOST_VERSIONS:
245
+ if _classify_hermes_host_version(host_version) == "unsupported":
207
246
  return UnavailableCapabilities("unsupported Hermes host version")
208
247
  try:
209
248
  text = _host_config_path().read_text(encoding="utf-8")
@@ -221,7 +260,8 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
221
260
  return UnavailableCapabilities("malformed host config")
222
261
  if _has_credential_key(config):
223
262
  return UnavailableCapabilities("credential-risk host config")
224
- if config.get("_config_version") != 30:
263
+ schema_version = config.get("_config_version")
264
+ if type(schema_version) is not int or schema_version < 30:
225
265
  return UnavailableCapabilities("unknown host config schema")
226
266
  for key in _SAFE_CHILDREN:
227
267
  if key in config and not isinstance(config[key], dict):
@@ -256,7 +296,8 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
256
296
  child_effort = effort if child_effort_raw is None or child_effort_raw == "" else child_effort_raw
257
297
  child_provider = provider if child_provider_raw is None or child_provider_raw == "" else child_provider_raw
258
298
  concurrency_raw = delegation_config.get("max_concurrent_children", "")
259
- async_raw = delegation_config.get("max_async_children", 3)
299
+ shared_async_cap = schema_version >= 33
300
+ async_raw = 1 if shared_async_cap else delegation_config.get("max_async_children", 3)
260
301
  depth_raw = delegation_config.get("max_spawn_depth", 1)
261
302
  orchestrator_enabled = delegation_config.get("orchestrator_enabled", True)
262
303
  delegation_provider = delegation_config.get("provider") or provider
@@ -355,6 +396,7 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
355
396
  child_effort=child_effort,
356
397
  concurrency=concurrency,
357
398
  async_batches=async_batches,
399
+ shared_async_cap=shared_async_cap,
358
400
  spawn_depth=spawn_depth,
359
401
  flat=flat,
360
402
  approved=approved,
@@ -369,16 +411,22 @@ def _recursion_detail(spawn_depth: int, flat: bool) -> str:
369
411
  return f"hard (configured depth {spawn_depth}, flat via orchestrator kill switch)"
370
412
 
371
413
 
414
+ def _concurrency_detail(concurrency: int, async_batches: int, shared_async_cap: bool) -> str:
415
+ if shared_async_cap:
416
+ return f"per batch {concurrency}; background cap {concurrency}; potential children {concurrency}"
417
+ return f"per batch {concurrency}; async batches {async_batches}; potential children {concurrency * async_batches}"
418
+
419
+
372
420
  def _capability_status_lines(host_version: str) -> list[str]:
373
421
  result = _parse_capabilities(host_version)
374
422
  match result:
375
- case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, spawn_depth=spawn_depth, flat=flat):
423
+ case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, shared_async_cap=shared_async_cap, spawn_depth=spawn_depth, flat=flat):
376
424
  lines = [
377
425
  f"parent model: {parent_model}",
378
426
  f"parent effort: {parent_effort}",
379
427
  f"global child route: configured ({child_model}, effort {child_effort}, provider inherited)",
380
428
  "child execution proof: unavailable (delegate_task receipt required)",
381
- f"concurrency: hard (per batch {concurrency}; async batches {async_batches}; potential children {concurrency * async_batches})",
429
+ f"concurrency: hard ({_concurrency_detail(concurrency, async_batches, shared_async_cap)})",
382
430
  f"recursion: {_recursion_detail(spawn_depth, flat)}",
383
431
  "auto-compaction: unavailable (ratio-only compression; no exact 650K schema)",
384
432
  f"runtime: hard ({_OPENAI_PROVIDER} {_RESPONSES_MODE})",
@@ -408,7 +456,7 @@ def _capability_status_lines(host_version: str) -> list[str]:
408
456
  def status_report(host_version: str | None = None) -> str:
409
457
  version = host_version or hermes_host_version()
410
458
  skills = _skill_names()
411
- return "\n".join([
459
+ lines = [
412
460
  f"LitHermes plugin {plugin_version()} (Hermes host {version})",
413
461
  f"plugin dir: {PLUGIN_ROOT}",
414
462
  f"hooks ({len(HOOKS)}): {', '.join(HOOKS)}",
@@ -424,7 +472,11 @@ def status_report(host_version: str | None = None) -> str:
424
472
  f"litgoal evidence kinds: {', '.join(_evidence_kinds())}",
425
473
  f"scientific visualization: {_science.dependency_summary()}",
426
474
  *_capability_status_lines(version),
427
- ])
475
+ ]
476
+ host_note = _beyond_verified_host_note(version)
477
+ if host_note:
478
+ lines.insert(1, host_note)
479
+ return "\n".join(lines)
428
480
 
429
481
 
430
482
  def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
@@ -463,14 +515,17 @@ def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
463
515
  lines.append(f"[OK] Hermes host detected (v{version})")
464
516
  else:
465
517
  lines.append("[NOTE] Hermes host version unknown (running outside Hermes?)")
518
+ host_note = _beyond_verified_host_note(version)
519
+ if host_note:
520
+ lines.append(host_note)
466
521
  capabilities = _parse_capabilities(version)
467
522
  match capabilities:
468
- case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, spawn_depth=spawn_depth, flat=flat):
523
+ case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, shared_async_cap=shared_async_cap, spawn_depth=spawn_depth, flat=flat):
469
524
  lines.extend([
470
525
  f"[OK] parent model {parent_model} / effort {parent_effort}",
471
526
  f"[NOTE] global child route configured ({child_model}, effort {child_effort}, provider inherited)",
472
527
  "[NOTE] child execution proof unavailable (delegate_task receipt required)",
473
- f"[OK] concurrency hard (per batch {concurrency}; async batches {async_batches}; potential children {concurrency * async_batches})",
528
+ f"[OK] concurrency hard ({_concurrency_detail(concurrency, async_batches, shared_async_cap)})",
474
529
  f"[{'OK' if flat else 'WARN'}] recursion {_recursion_detail(spawn_depth, flat)}",
475
530
  "[OK] auto-compaction unavailable (ratio-only compression; no exact 650K schema)",
476
531
  f"[OK] runtime hard ({_OPENAI_PROVIDER} {_RESPONSES_MODE})",
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-09-24T07:38:36.208Z",
2
+ "syncedAt": "2026-09-24T16:39:30.023Z",
3
3
  "source": "bundled-payload",
4
- "sourceHash": "ae1d45a687de0c01415820ff467295f77a175cc393e4923b940729668f58c49b",
4
+ "sourceHash": "95a5f8d9b4057baaa3d73a74a48bc99d953dd8a31403b7b80126c1e98340c091",
5
5
  "files": [
6
6
  {
7
7
  "path": "NOTICE.md",
@@ -85,7 +85,7 @@
85
85
  },
86
86
  {
87
87
  "path": "diagnostics.py",
88
- "sha256": "8d8196c1c50a6dfa44a68f8681a22b3c84428776bc7b0af1847d80d30346f0d0"
88
+ "sha256": "fc4c902009704378e31fe511e7d85ad48474fc3198706417f90ac6eb2956b9e8"
89
89
  },
90
90
  {
91
91
  "path": "handoff.py",
@@ -157,7 +157,7 @@
157
157
  },
158
158
  {
159
159
  "path": "plugin.yaml",
160
- "sha256": "9a82f3005c45dab04f48b1bdd4ef79937bdbf7dae7e0f2fcd9a5a391a64c3c96"
160
+ "sha256": "200f3ae31a7b4623a2a09f3a9fcb1af87e7638db16ddb19a6f64ef03e0864d04"
161
161
  },
162
162
  {
163
163
  "path": "provider_metrics.py",
@@ -1245,7 +1245,7 @@
1245
1245
  },
1246
1246
  {
1247
1247
  "path": "skills/frontend-ui-ux/references/complete-contract.md",
1248
- "sha256": "1a7d471fc7a12ec4eb74d7a78110e5104564a64417f2687e90df89dd80c3b249"
1248
+ "sha256": "50973cdab59eeea6bc699d1da32856ce20b810c56fcb3ceb14d09b387f4782d2"
1249
1249
  },
1250
1250
  {
1251
1251
  "path": "skills/frontend-ui-ux/references/composition.md",
@@ -1969,7 +1969,7 @@
1969
1969
  },
1970
1970
  {
1971
1971
  "path": "skills/visual-qa/references/complete-contract.md",
1972
- "sha256": "fa3842123069e693ee20784e40f08e79a9e0f6854bf62af89d44f9faf13c39f8"
1972
+ "sha256": "6df5e613de62f49e3aa7d78251bcd916d2dea419a679598a0099a761d5f68fcd"
1973
1973
  },
1974
1974
  {
1975
1975
  "path": "skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json",
@@ -1,5 +1,5 @@
1
1
  name: lithermes
2
- version: 1.0.6
2
+ version: 1.0.8
3
3
  description: "Hermes-native workflow toolkit: litgoal durable runtime, 5-lane review orchestrator, Litwork commands, skills, and prompt steering."
4
4
  author: "LitHermes contributors"
5
5
  kind: standalone
@@ -431,7 +431,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
431
431
 
432
432
  ```text
433
433
  $ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
434
- Installed LitHermes 1.0.6
434
+ Installed LitHermes 1.0.8
435
435
  plugin: $ISOLATED/plugins/lithermes
436
436
  model config: updated
437
437
 
@@ -448,7 +448,7 @@ installed payload: PASS
448
448
  enabled config: PASS
449
449
 
450
450
  $ hermes lithermes doctor
451
- [OK] plugin.yaml readable (version 1.0.6)
451
+ [OK] plugin.yaml readable (version 1.0.8)
452
452
  [OK] skills bundled: 32
453
453
  [OK] litgoal durable runtime importable
454
454
  ```
@@ -485,7 +485,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
485
485
 
486
486
  ```text
487
487
  $ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
488
- Installed LitHermes 1.0.6
488
+ Installed LitHermes 1.0.8
489
489
  plugin: $ISOLATED/plugins/lithermes
490
490
  model config: updated
491
491
 
@@ -502,7 +502,7 @@ installed payload: PASS
502
502
  enabled config: PASS
503
503
 
504
504
  $ hermes lithermes doctor
505
- [OK] plugin.yaml readable (version 1.0.6)
505
+ [OK] plugin.yaml readable (version 1.0.8)
506
506
  [OK] skills bundled: 32
507
507
  [OK] litgoal durable runtime importable
508
508
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/lithermes",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "npx/bunx installer for the LitHermes Hermes plugin",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.6"><title>release: 1.0.6</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.6</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.8"><title>release: 1.0.8</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.8</text></g></svg>
package/src/lib/check.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require("node:fs");
2
2
  const path = require("node:path");
3
3
  const { spawnSync } = require("node:child_process");
4
- const { configHasLitHermes, inspectHermesCapabilities, readConfig, readExternalSkillDirsConfig } = require("./config");
4
+ const { configHasLitHermes, formatConcurrencyCapability, inspectHermesCapabilities, readConfig, readExternalSkillDirsConfig } = require("./config");
5
5
  const { detectHermesRuntimeRepo, detectHermesVersion, ensureHermesHome, expandHome, inspectHermesHostCapabilities, LitHermesError } = require("./hermesDiscovery");
6
6
  const { formatManagedRoute } = require("./modelRoutePolicy");
7
7
  const { inspectPythonCache } = require("./files");
@@ -271,7 +271,7 @@ function doctorLitHermes(flags = {}) {
271
271
  `skill shadow check: ${skillShadows.length ? "WARNING" : "PASS"}`,
272
272
  ...skillShadows.map(formatSkillShadowWarning),
273
273
  `auto-compaction: ${auto.status}${auto.reason ? ` (${auto.reason})` : ""}`,
274
- `concurrency: ${concurrency.status}${concurrency.status === "hard" ? ` (per batch ${concurrency.limit}; async batches ${concurrency.asyncBatchLimit}; potential children ${concurrency.potentialChildCeiling})` : ` (${concurrency.reason})`}`,
274
+ `concurrency: ${formatConcurrencyCapability(concurrency)}`,
275
275
  `recursion: ${recursion.status}${recursion.status === "hard" ? recursion.flatBy === "depth" ? ` (depth ${recursion.limit}, flat)` : ` (configured depth ${recursion.limit}, flat via orchestrator kill switch)` : ` (${recursion.reason})`}`,
276
276
  `model route safety: ${modelSafety.status}${modelSafety.code ? ` (${modelSafety.code}; ${modelSafety.reason})` : ` (${modelSafety.reason})`}`,
277
277
  `runtime: ${runtime.status}${runtime.status === "hard" ? ` (${runtime.provider} ${runtime.apiMode})` : ` (${runtime.reason})`}`,
@@ -5,6 +5,7 @@ const {
5
5
  configHasLitHermes,
6
6
  disableLitHermesConfig,
7
7
  enableLitHermesConfig,
8
+ formatConcurrencyCapability,
8
9
  inspectHermesCapabilities,
9
10
  planModelConfig,
10
11
  readConfig,
@@ -245,9 +246,7 @@ function capabilityLines(plan, { dryRun = false } = {}) {
245
246
  const auto = autoCompaction.status === "hard"
246
247
  ? `hard (${autoCompaction.limit} tokens)`
247
248
  : `unavailable (${autoCompaction.reason.replace(/^Hermes 0\.17\.0 exposes /, "")})`;
248
- const sync = concurrency.status === "hard"
249
- ? `hard (per batch ${concurrency.limit}; async batches ${concurrency.asyncBatchLimit}; potential children ${concurrency.potentialChildCeiling})`
250
- : `${concurrency.status} (${concurrency.reason})`;
249
+ const sync = formatConcurrencyCapability(concurrency);
251
250
  const nesting = recursion.status === "hard"
252
251
  ? recursion.flatBy === "depth"
253
252
  ? `hard (depth ${recursion.limit}, flat)`
@@ -1,7 +1,7 @@
1
1
  const fs = require("node:fs");
2
2
  const path = require("node:path");
3
3
  const { writeFileAtomic } = require("./files");
4
- const { OPENAI_PROVIDER, RESPONSES_MODE, classifyHostVersion,
4
+ const { OPENAI_PROVIDER, RESPONSES_MODE, VERIFIED_CONFIG_VERSION, classifyHostVersion,
5
5
  exactHostVersion, parseBoundary, sourceHash } = require("./modelConfigBoundary");
6
6
  const {
7
7
  ASTRA_MODEL,
@@ -138,6 +138,14 @@ function unavailableCapabilities(reason) {
138
138
  };
139
139
  }
140
140
 
141
+ function formatConcurrencyCapability(concurrency) {
142
+ if (concurrency.status !== "hard") return `${concurrency.status} (${concurrency.reason})`;
143
+ if (concurrency.sharedAsyncCap) {
144
+ return `hard (per batch ${concurrency.limit}; background cap ${concurrency.backgroundCap}; potential children ${concurrency.potentialChildCeiling})`;
145
+ }
146
+ return `hard (per batch ${concurrency.limit}; async batches ${concurrency.asyncBatchLimit}; potential children ${concurrency.potentialChildCeiling})`;
147
+ }
148
+
141
149
  function safeOriginalDispatchId(dispatch) {
142
150
  if (typeof dispatch !== "string" || !/^[A-Za-z0-9._:/-]{1,128}$/.test(dispatch)) return null;
143
151
  return dispatch;
@@ -199,9 +207,11 @@ function inspectHermesCapabilities(text, options = {}) {
199
207
  }
200
208
  const concurrency = boundary.document.getIn(["delegation", "max_concurrent_children"]);
201
209
  const asyncBatches = boundary.document.getIn(["delegation", "max_async_children"]);
210
+ const configVersion = boundary.document.get("_config_version");
211
+ const sharedAsyncCap = configVersion >= 33;
202
212
  const spawnDepth = boundary.document.getIn(["delegation", "max_spawn_depth"]);
203
213
  const orchestratorEnabled = boundary.document.getIn(["delegation", "orchestrator_enabled"]);
204
- const effectiveAsyncBatches = Number.isInteger(asyncBatches) ? asyncBatches : 3;
214
+ const effectiveAsyncBatches = sharedAsyncCap ? 1 : Number.isInteger(asyncBatches) ? asyncBatches : 3;
205
215
  const effectiveSpawnDepth = Number.isInteger(spawnDepth) ? spawnDepth : 1;
206
216
  const flatDelegation = effectiveSpawnDepth === 1 || orchestratorEnabled === false;
207
217
  const contextLength = boundary.document.getIn(["model", "context_length"]);
@@ -234,12 +244,15 @@ function inspectHermesCapabilities(text, options = {}) {
234
244
  concurrency: concurrency === 20
235
245
  ? {
236
246
  status: "hard",
237
- reason: "effective synchronous concurrency is configured to 20",
247
+ reason: sharedAsyncCap
248
+ ? "effective concurrency and background work share the configured cap of 20"
249
+ : "effective synchronous concurrency is configured to 20",
238
250
  observedSource: "delegation.max_concurrent_children",
239
251
  limit: 20,
240
252
  scope: "per-batch",
241
- asyncBatchLimit: effectiveAsyncBatches,
242
- potentialChildCeiling: 20 * effectiveAsyncBatches,
253
+ ...(sharedAsyncCap
254
+ ? { sharedAsyncCap: true, backgroundCap: 20, potentialChildCeiling: 20 }
255
+ : { asyncBatchLimit: effectiveAsyncBatches, potentialChildCeiling: 20 * effectiveAsyncBatches }),
243
256
  }
244
257
  : {
245
258
  status: "unavailable",
@@ -379,15 +392,22 @@ function planModelConfig(text, options = {}) {
379
392
  document.setIn(["delegation", "max_spawn_depth"], 1);
380
393
  document.setIn(["delegation", "orchestrator_enabled"], false);
381
394
  const plannedText = document.toString({ lineWidth: 0 });
395
+ const beyondVerifiedReasons = [];
396
+ if (classifyHostVersion(options.hostVersion).status === "beyond-verified") {
397
+ beyondVerifiedReasons.push(`Hermes ${request.hostVersion} is beyond the verified matrix; source and runtime markers verified, model schema written`);
398
+ }
399
+ const configVersion = document.get("_config_version");
400
+ if (configVersion > VERIFIED_CONFIG_VERSION) {
401
+ beyondVerifiedReasons.push(`config schema ${configVersion} is beyond the verified ceiling ${VERIFIED_CONFIG_VERSION}`);
402
+ }
382
403
  return {
383
404
  action: "write",
384
405
  backupAllowed: true,
385
406
  capabilities: inspectHermesCapabilities(plannedText, { ...options, ...route }),
386
407
  classification,
387
- reason:
388
- classifyHostVersion(options.hostVersion).status === "beyond-verified"
389
- ? `Hermes ${request.hostVersion} is beyond the verified matrix; source and runtime markers verified, model schema written`
390
- : `verified Hermes ${request.hostVersion} model schema`,
408
+ reason: beyondVerifiedReasons.length
409
+ ? beyondVerifiedReasons.join("; ")
410
+ : `verified Hermes ${request.hostVersion} model schema`,
391
411
  request,
392
412
  sourceHash: sourceHash(String(text || "")),
393
413
  text: plannedText,
@@ -429,6 +449,7 @@ function applyModelConfigPlan(hermesHome, plan) {
429
449
  module.exports = {
430
450
  applyModelConfigPlan,
431
451
  classifyModelConfig,
452
+ formatConcurrencyCapability,
432
453
  inspectHermesCapabilities,
433
454
  planModelConfig,
434
455
  revalidateModelConfigPlan,
@@ -17,6 +17,7 @@ const MINIMUM_HOST_VERSION = "0.17.0";
17
17
  // malformed, which must still be reported as unsupported.
18
18
  const NOT_PROBED_HOST_VERSION = "__lithermes_dry_run_not_probed__";
19
19
  const CONFIG_VERSION = 30;
20
+ const VERIFIED_CONFIG_VERSION = 45;
20
21
  const CREDENTIAL_KEY = /(^|[_-])(api[_-]?key|client[_-]?secret|auth[_-]?token|(?:aws[_-]?)?access[_-]?key(?:[_-]?id)?|(?:ssh[_-]?)?private[_-]?key|token|password|passphrase|secret|credential|authorization)([_-]|$)/i;
21
22
  const MAP_PATHS = ["model", "agent", "delegation", "compression"];
22
23
  const SCALAR_PATHS = [
@@ -165,8 +166,9 @@ function parseBoundary(text, options = {}) {
165
166
  return { error: "malformed host config" };
166
167
  }
167
168
  const credentialKey = findCredentialRiskKey(document);
168
- if (document.get("_config_version") !== CONFIG_VERSION) {
169
- return { error: `unknown host config schema; expected version ${CONFIG_VERSION}` };
169
+ const configVersion = document.get("_config_version");
170
+ if (!Number.isInteger(configVersion) || configVersion < CONFIG_VERSION) {
171
+ return { error: `unknown host config schema; expected integer version >= ${CONFIG_VERSION}` };
170
172
  }
171
173
  for (const key of MAP_PATHS) {
172
174
  const node = document.get(key, true);
@@ -208,6 +210,7 @@ module.exports = {
208
210
  NOT_PROBED_HOST_VERSION,
209
211
  OPENAI_PROVIDER,
210
212
  RESPONSES_MODE,
213
+ VERIFIED_CONFIG_VERSION,
211
214
  classifyHostVersion,
212
215
  exactHostVersion,
213
216
  parseBoundary,