Capability-set targeting + Claude Code headless provider + resilience fixes

Capability targeting (new feature):
- loop --capability <SET|ALL> scopes which capability library the loop reasons
  against. data/capability_sets/<name>.json (same schema as capabilities.json)
  holds one-off targeted products; drop a file in and pass its name, no code
  changes. First set: agentminder (Broadcom AgentMinder, agentic-AI identity).
- capabilities.set_active_set() swaps the active library; keyword pre-filter is
  bypassed for small targeted sets so every signal is evaluated. New
  'capability-sets' command lists them; run banner shows the active set.

LLM provider — the '429' was a disguised policy block:
- Anthropic rejects Claude Code OAuth tokens on /v1/messages for non-Claude-Code
  traffic with a fake rate_limit_error (no anthropic-ratelimit-* headers). New
  'claude-cli' provider runs inference through the sanctioned headless path
  (claude -p --append-system-prompt --model), now the default provider.
- OAuth keychain token auto-refresh via headless claude when expired.

Resilience:
- STATE_DIR is overridable (macOS endpoint-security locked output/state via
  com.apple.macl); load_memory tolerates PermissionError; exa usage counter
  tolerates malformed files; run.py handles Ctrl-C cleanly (no traceback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 09:00:02 -04:00
parent c2854ba163
commit fefda7481f
9 changed files with 234 additions and 26 deletions

View File

@@ -3,6 +3,12 @@
The design uses a vector ANN pre-filter to cut LLM calls ~60%. For the MVP we
approximate that with keyword overlap scoring (no embedding service required).
The interface is the same: given a signal, return candidate capabilities.
CAPABILITY SETS: the loop can target a subset of the portfolio at runtime.
- "ALL" (default): data/capabilities.json — the full portfolio.
- "<name>": data/capability_sets/<name>.json — a one-off targeted set
(same schema). Add new products by dropping another file in that folder and
passing `loop --capability <name>`.
"""
from __future__ import annotations
@@ -12,10 +18,43 @@ from functools import lru_cache
from . import config
# Selected via set_active_set() before the loop runs; "ALL" = full portfolio.
_active_set = "ALL"
def available_sets() -> list[str]:
sets_dir = config.DATA_DIR / "capability_sets"
names = sorted(p.stem for p in sets_dir.glob("*.json")) if sets_dir.exists() else []
return ["ALL"] + names
def set_active_set(name: str) -> None:
"""Select which capability library the loop reasons against."""
global _active_set
name = (name or "ALL").strip()
if name.upper() == "ALL":
_active_set = "ALL"
else:
path = config.DATA_DIR / "capability_sets" / f"{name}.json"
if not path.exists():
raise SystemExit(
f"Unknown capability set '{name}'. Available: {', '.join(available_sets())}"
)
_active_set = name
load_library.cache_clear()
def active_set() -> str:
return _active_set
@lru_cache(maxsize=1)
def load_library() -> dict:
return json.loads((config.DATA_DIR / "capabilities.json").read_text())
if _active_set == "ALL":
return json.loads((config.DATA_DIR / "capabilities.json").read_text())
return json.loads(
(config.DATA_DIR / "capability_sets" / f"{_active_set}.json").read_text()
)
def capability_by_id(cap_id: str) -> dict | None:
@@ -33,7 +72,13 @@ def prefilter(signal_content: str, top_k: int = 3) -> list[dict]:
"""Return capabilities whose keywords overlap the signal, best first.
Stand-in for the vector ANN pre-filter in the design (section 5.2).
In a targeted set (<=5 capabilities) the pre-filter is skipped: the whole
point of a targeted run is to evaluate EVERY signal against the chosen
product, and the LLM prompt stays small anyway.
"""
caps = load_library()["capabilities"]
if _active_set != "ALL" and len(caps) <= 5:
return caps
sig_tokens = _tokens(signal_content)
scored: list[tuple[float, dict]] = []
for cap in load_library()["capabilities"]: