feat: spawn_agent tool + host max_concurrent + docs
Adds a synchronous sub-agent spawning tool that lets the orchestrator delegate tasks to a specific role's model and tool set. - cortex/tools/agents.py: spawn_agent(task, role, tier, timeout, max_rounds) - Supports local_openai and gemini_api model types - Per-host asyncio semaphore (keyed by host_id or model type) - asyncio.wait_for() enforces timeout; admin-only tool - cortex/model_registry.py: max_concurrent field in host schema (default 3, clamped 1-20); backfilled on _normalize() for existing hosts - cortex/routers/local_llm.py + local_llm.html: "Max parallel" number input in host add/edit forms - cortex/tools/__init__.py: spawn_agent registered in TOOL_CATEGORIES["Agents"], _CALLABLES, TOOL_ROLES (admin), and _ALL_DECLARATIONS - Docs: TOOLS.md count 44→45, spawn_agent section; HELP.md tool table updated; ARCH__FUTURE.md Round 2 completed items; TODO__Agents.md spawn_agent checked; CLAUDE.md tool count and list updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -189,6 +189,7 @@ def _normalize(data: dict) -> dict:
|
||||
"""Back-fill missing fields introduced by schema additions."""
|
||||
for h in data.get("hosts", []):
|
||||
h.setdefault("host_type", "openwebui")
|
||||
h.setdefault("max_concurrent", 3)
|
||||
data.setdefault("providers", _default_providers())
|
||||
data["providers"].setdefault("anthropic", {"credentials": [{"id": "cli", "label": "Claude CLI (OAuth)", "type": "cli"}]})
|
||||
data["providers"].setdefault("google", {"accounts": []})
|
||||
@@ -605,17 +606,20 @@ def remove_google_account(username: str, account_id: str) -> bool:
|
||||
|
||||
def save_host(username: str, host_id: str | None,
|
||||
label: str, api_url: str, api_key: str,
|
||||
host_type: str = "openwebui") -> str:
|
||||
host_type: str = "openwebui",
|
||||
max_concurrent: int = 3) -> str:
|
||||
"""Create or update a host. Returns the host ID."""
|
||||
data = _load(username)
|
||||
host_type = host_type if host_type in ("openwebui", "openai") else "openwebui"
|
||||
max_concurrent = max(1, min(int(max_concurrent), 20))
|
||||
|
||||
if host_id:
|
||||
for h in data["hosts"]:
|
||||
if h["id"] == host_id:
|
||||
h["label"] = label.strip()
|
||||
h["api_url"] = api_url.strip()
|
||||
h["host_type"] = host_type
|
||||
h["label"] = label.strip()
|
||||
h["api_url"] = api_url.strip()
|
||||
h["host_type"] = host_type
|
||||
h["max_concurrent"] = max_concurrent
|
||||
if api_key.strip():
|
||||
h["api_key"] = api_key.strip()
|
||||
_save(username, data)
|
||||
@@ -624,11 +628,12 @@ def save_host(username: str, host_id: str | None,
|
||||
|
||||
host_id = secrets.token_hex(4)
|
||||
data["hosts"].append({
|
||||
"id": host_id,
|
||||
"label": label.strip(),
|
||||
"api_url": api_url.strip(),
|
||||
"api_key": api_key.strip(),
|
||||
"host_type": host_type,
|
||||
"id": host_id,
|
||||
"label": label.strip(),
|
||||
"api_url": api_url.strip(),
|
||||
"api_key": api_key.strip(),
|
||||
"host_type": host_type,
|
||||
"max_concurrent": max_concurrent,
|
||||
})
|
||||
_save(username, data)
|
||||
return host_id
|
||||
|
||||
Reference in New Issue
Block a user