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:
Scott Idem
2026-05-08 22:48:21 -04:00
parent 6ad7597db8
commit 09d775b47b
10 changed files with 275 additions and 26 deletions

View File

@@ -70,6 +70,7 @@ from tools.agent_notes import (
agent_notes_append as _agent_notes_append,
agent_notes_clear as _agent_notes_clear,
)
from tools.agents import spawn_agent as _spawn_agent
# ── Declaration imports ───────────────────────────────────────────────────────
@@ -84,6 +85,7 @@ import tools.reminders as _mod_reminders
import tools.scratch as _mod_scratch
import tools.notify as _mod_notify
import tools.agent_notes as _mod_agent_notes
import tools.agents as _mod_agents
# ── Tool categories — used by the Model Registry UI for grouped checkboxes ───
@@ -106,6 +108,7 @@ TOOL_CATEGORIES: dict[str, list[str]] = {
],
"Aether Tasks": ["ae_task_list"],
"Agent Notes": ["agent_notes_read", "agent_notes_write", "agent_notes_append", "agent_notes_clear"],
"Agents": ["spawn_agent"],
}
# ── Callable registry ─────────────────────────────────────────────────────────
@@ -156,6 +159,7 @@ _CALLABLES: dict[str, callable] = {
"agent_notes_write": _agent_notes_write,
"agent_notes_append": _agent_notes_append,
"agent_notes_clear": _agent_notes_clear,
"spawn_agent": _spawn_agent,
}
# ── Role-based access control ─────────────────────────────────────────────────
@@ -172,6 +176,7 @@ TOOL_ROLES: dict[str, str] = {
"file_list": "admin",
"file_write": "admin",
"ae_task_list": "admin",
"spawn_agent": "admin",
"email_send": "admin",
"nc_talk_send": "admin",
}
@@ -208,6 +213,7 @@ _ALL_DECLARATIONS: list[types.FunctionDeclaration] = (
+ _mod_ae_knowledge.DECLARATIONS
+ _mod_ae_tasks.DECLARATIONS
+ _mod_agent_notes.DECLARATIONS
+ _mod_agents.DECLARATIONS
)
# Full Gemini Tool object (all tools — use get_tools_for_role() in production)