Compare commits
10 Commits
71316b3090
...
5b0d00a799
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b0d00a799 | |||
| 461b36bc5d | |||
| 9c1794c58a | |||
| 8adab6fbc5 | |||
| d02faf5cac | |||
| c70448b61c | |||
| aef553bdc8 | |||
| 8277a00118 | |||
| 45e3b7663e | |||
| 7b1cf7315c |
@@ -10,8 +10,6 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import anthropic
|
||||
|
||||
from adapters.base.llm import LLMAdapter
|
||||
|
||||
|
||||
@@ -57,6 +55,14 @@ class AnthropicAdapter(LLMAdapter):
|
||||
ValueError
|
||||
If ANTHROPIC_API_KEY is not set in the environment.
|
||||
"""
|
||||
try:
|
||||
import anthropic as _anthropic
|
||||
except ModuleNotFoundError as exc:
|
||||
raise ImportError(
|
||||
"The 'anthropic' package is required for AnthropicAdapter. "
|
||||
"Install it with: pip install anthropic"
|
||||
) from exc
|
||||
|
||||
self._config = config
|
||||
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
||||
if not api_key:
|
||||
@@ -64,7 +70,7 @@ class AnthropicAdapter(LLMAdapter):
|
||||
"ANTHROPIC_API_KEY environment variable is not set. "
|
||||
"Export it before running the-agency."
|
||||
)
|
||||
self._client = anthropic.Anthropic(api_key=api_key)
|
||||
self._client = _anthropic.Anthropic(api_key=api_key)
|
||||
self._models_cfg: dict = config.get("models", {})
|
||||
self._default_max_tokens: int = self._models_cfg.get("default_max_tokens", 4096)
|
||||
self._default_temperature: float = self._models_cfg.get("default_temperature", 0)
|
||||
|
||||
2
agents
2
agents
Submodule agents updated: 5c669c28e6...72a144406c
@@ -2,28 +2,40 @@ t1:
|
||||
default: agents/strategy/nexus-strategy.md
|
||||
|
||||
t2:
|
||||
backend: agents/engineering/engineering-software-architect.md
|
||||
frontend: agents/engineering/engineering-software-architect.md
|
||||
backend: agents/engineering/engineering-backend-architect.md
|
||||
frontend: agents/engineering/engineering-frontend-architect.md
|
||||
infra: agents/engineering/engineering-devops-automator.md
|
||||
data: agents/engineering/engineering-data-engineer.md
|
||||
ai: agents/engineering/engineering-software-architect.md
|
||||
security: agents/engineering/engineering-security-engineer.md
|
||||
mobile: agents/engineering/engineering-software-architect.md
|
||||
default: agents/engineering/engineering-software-architect.md
|
||||
|
||||
t3:
|
||||
backend: agents/engineering/engineering-senior-developer.md
|
||||
frontend: agents/engineering/engineering-senior-developer.md
|
||||
backend: agents/engineering/engineering-senior-backend-developer.md
|
||||
frontend: agents/engineering/engineering-senior-frontend-developer.md
|
||||
infra: agents/engineering/engineering-sre.md
|
||||
default: agents/engineering/engineering-senior-developer.md
|
||||
data: agents/engineering/engineering-data-engineer.md
|
||||
ai: agents/engineering/engineering-ai-engineer.md
|
||||
security: agents/engineering/engineering-security-engineer.md
|
||||
mobile: agents/engineering/engineering-mobile-app-builder.md
|
||||
database: agents/engineering/engineering-database-optimizer.md
|
||||
devops: agents/engineering/engineering-sre.md
|
||||
docs: agents/engineering/engineering-technical-writer.md
|
||||
default: agents/engineering/engineering-backend-developer.md
|
||||
|
||||
t4:
|
||||
frontend: agents/engineering/engineering-frontend-developer.md
|
||||
backend: agents/engineering/engineering-backend-architect.md
|
||||
backend: agents/engineering/engineering-backend-developer.md
|
||||
database: agents/engineering/engineering-database-optimizer.md
|
||||
devops: agents/engineering/engineering-devops-automator.md
|
||||
mobile: agents/engineering/engineering-mobile-app-builder.md
|
||||
ai: agents/engineering/engineering-ai-engineer.md
|
||||
security: agents/engineering/engineering-security-engineer.md
|
||||
docs: agents/engineering/engineering-technical-writer.md
|
||||
default: agents/engineering/engineering-senior-developer.md
|
||||
data: agents/engineering/engineering-data-engineer.md
|
||||
embedded: agents/engineering/engineering-embedded-firmware-engineer.md
|
||||
default: agents/engineering/engineering-backend-developer.md
|
||||
|
||||
t5:
|
||||
code: agents/engineering/engineering-code-reviewer.md
|
||||
@@ -31,4 +43,8 @@ t5:
|
||||
api: agents/testing/testing-api-tester.md
|
||||
performance: agents/testing/testing-performance-benchmarker.md
|
||||
security: agents/engineering/engineering-security-engineer.md
|
||||
accessibility: agents/testing/testing-accessibility-auditor.md
|
||||
e2e: agents/testing/testing-evidence-collector.md
|
||||
frontend: agents/testing/testing-accessibility-auditor.md
|
||||
data: agents/testing/testing-reality-checker.md
|
||||
default: agents/engineering/engineering-code-reviewer.md
|
||||
|
||||
@@ -7,7 +7,17 @@ adapters:
|
||||
llm: anthropic
|
||||
vcs: github
|
||||
notify: openclaw
|
||||
runtime: openclaw
|
||||
|
||||
adapter_registry:
|
||||
llm:
|
||||
anthropic: "adapters.llm.anthropic:AnthropicAdapter"
|
||||
vcs:
|
||||
github: "adapters.vcs.github:GitHubAdapter"
|
||||
notify:
|
||||
openclaw: "adapters.notify.openclaw:OpenClawNotifyAdapter"
|
||||
runtime:
|
||||
openclaw: "adapters.runtime.openclaw:OpenClawRuntimeAdapter"
|
||||
claude_code: "adapters.runtime.claude_code:ClaudeCodeRuntimeAdapter"
|
||||
|
||||
models:
|
||||
default_max_tokens: 4096
|
||||
|
||||
@@ -5,6 +5,12 @@ Top-level orchestration entry point for the-agency pipeline.
|
||||
The TeamRunner loads team.yaml, builds the adapter registry, and drives the
|
||||
full T1 → T2 → T3 → T4 → T5 dispatch loop with escalation handling.
|
||||
|
||||
Runtime adapters are config-driven: every string-valued key in the top-level
|
||||
``runtime:`` section of team.yaml is instantiated as a RuntimeAdapter and
|
||||
stored in ``self._runtimes[name]``. Non-string values (e.g. ``native_teams:
|
||||
false``) are silently skipped. Dispatch routing uses
|
||||
``brief.preferred_runtime`` to look up the right adapter at call time.
|
||||
|
||||
CLI usage::
|
||||
|
||||
python -m core.team_runner --config config/team.yaml [--dry-run] [--verbose]
|
||||
@@ -52,7 +58,7 @@ _TIER_CAPABILITIES: dict[int, str] = {
|
||||
1: "reasoning-heavy",
|
||||
2: "reasoning-heavy",
|
||||
3: "capable",
|
||||
4: "capable",
|
||||
4: "fast-cheap",
|
||||
5: "fast-cheap",
|
||||
}
|
||||
|
||||
@@ -65,18 +71,16 @@ _TIER_CAPABILITIES: dict[int, str] = {
|
||||
# enabling third-party adapters without touching this file.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_LLM_ADAPTERS: dict[str, str] = {
|
||||
"anthropic": "adapters.llm.anthropic:AnthropicAdapter",
|
||||
}
|
||||
_VCS_ADAPTERS: dict[str, str] = {
|
||||
"github": "adapters.vcs.github:GitHubAdapter",
|
||||
}
|
||||
_NOTIFY_ADAPTERS: dict[str, str] = {
|
||||
"openclaw": "adapters.notify.openclaw:OpenClawNotifyAdapter",
|
||||
}
|
||||
_RUNTIME_ADAPTERS: dict[str, str] = {
|
||||
# Adapter registries are loaded from team.yaml at runtime (adapter_registry section).
|
||||
# Fallback built-ins are used only if team.yaml doesn't define adapter_registry.
|
||||
_BUILTIN_ADAPTER_REGISTRY: dict[str, dict[str, str]] = {
|
||||
"llm": {"anthropic": "adapters.llm.anthropic:AnthropicAdapter"},
|
||||
"vcs": {"github": "adapters.vcs.github:GitHubAdapter"},
|
||||
"notify": {"openclaw": "adapters.notify.openclaw:OpenClawNotifyAdapter"},
|
||||
"runtime": {
|
||||
"openclaw": "adapters.runtime.openclaw:OpenClawRuntimeAdapter",
|
||||
"claude_code": "adapters.runtime.claude_code:ClaudeCodeRuntimeAdapter",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -151,11 +155,23 @@ class TeamRunner:
|
||||
config_path : Path to team.yaml.
|
||||
dry_run : When True, skip LLM calls, VCS commits, and notifications.
|
||||
All planned actions are logged at INFO level.
|
||||
|
||||
Runtime adapters are built from the top-level ``runtime:`` section of
|
||||
team.yaml. Each string-valued key becomes an entry in
|
||||
``self._runtimes``; non-string values (e.g. ``native_teams: false``)
|
||||
are ignored. Adding a new runtime type requires only a new key in
|
||||
team.yaml — no changes to TeamRunner are needed.
|
||||
"""
|
||||
self._dry_run = dry_run
|
||||
|
||||
self._config = self._load_yaml(config_path)
|
||||
self._role_registry = self._load_yaml("config/role_registry.yaml")
|
||||
# Merge config-defined adapter_registry over built-in fallbacks.
|
||||
self._adapter_registry: dict[str, dict[str, str]] = {
|
||||
k: {**v} for k, v in _BUILTIN_ADAPTER_REGISTRY.items()
|
||||
}
|
||||
for kind, entries in self._config.get("adapter_registry", {}).items():
|
||||
self._adapter_registry.setdefault(kind, {}).update(entries)
|
||||
self._escalation = EscalationHandler()
|
||||
|
||||
run_id = str(uuid.uuid4())
|
||||
@@ -165,19 +181,27 @@ class TeamRunner:
|
||||
adapter_cfg: dict = self._config.get("adapters", {})
|
||||
runtime_cfg: dict = self._config.get("runtime", {})
|
||||
|
||||
if dry_run:
|
||||
# In dry-run mode the LLM adapter is never actually called, so we
|
||||
# tolerate missing dependencies (e.g. 'anthropic' SDK not installed).
|
||||
try:
|
||||
self._llm: LLMAdapter = self._build_llm(adapter_cfg.get("llm", "anthropic"))
|
||||
except (ImportError, ValueError) as exc:
|
||||
logger.warning(
|
||||
"LLM adapter unavailable in dry-run mode (%s) — continuing.", exc
|
||||
)
|
||||
self._llm = None # type: ignore[assignment]
|
||||
else:
|
||||
self._llm = self._build_llm(adapter_cfg.get("llm", "anthropic"))
|
||||
self._vcs: Optional[VCSAdapter] = self._build_optional( # type: ignore[assignment]
|
||||
_VCS_ADAPTERS, adapter_cfg.get("vcs"), "VCS"
|
||||
self._adapter_registry.get("vcs", {}), adapter_cfg.get("vcs"), "VCS"
|
||||
)
|
||||
self._notify: Optional[NotifyAdapter] = self._build_optional( # type: ignore[assignment]
|
||||
_NOTIFY_ADAPTERS, adapter_cfg.get("notify"), "notify"
|
||||
)
|
||||
self._default_runtime: RuntimeAdapter = self._build_runtime(
|
||||
runtime_cfg.get("default", "openclaw")
|
||||
)
|
||||
self._coding_runtime: RuntimeAdapter = self._build_runtime(
|
||||
runtime_cfg.get("coding_agent", "claude_code")
|
||||
self._adapter_registry.get("notify", {}), adapter_cfg.get("notify"), "notify"
|
||||
)
|
||||
# Runtime adapters are fully config-driven — one entry per string-valued
|
||||
# key in the top-level ``runtime:`` section of team.yaml.
|
||||
self._runtimes: dict[str, RuntimeAdapter] = self._build_runtimes(runtime_cfg)
|
||||
|
||||
logger.info(
|
||||
"TeamRunner initialised: run_id=%s dry_run=%s", run_id, dry_run
|
||||
@@ -198,7 +222,7 @@ class TeamRunner:
|
||||
return fh.read()
|
||||
|
||||
def _build_llm(self, key: str) -> LLMAdapter:
|
||||
cls = _load_adapter_class(key, _LLM_ADAPTERS, "LLM")
|
||||
cls = _load_adapter_class(key, self._adapter_registry.get("llm", {}), "LLM")
|
||||
return cls(self._config)
|
||||
|
||||
def _build_optional(
|
||||
@@ -226,9 +250,25 @@ class TeamRunner:
|
||||
return None
|
||||
|
||||
def _build_runtime(self, key: str) -> RuntimeAdapter:
|
||||
cls = _load_adapter_class(key, _RUNTIME_ADAPTERS, "runtime")
|
||||
cls = _load_adapter_class(key, self._adapter_registry.get("runtime", {}), "runtime")
|
||||
return cls(self._config)
|
||||
|
||||
def _build_runtimes(self, runtime_cfg: dict) -> dict[str, RuntimeAdapter]:
|
||||
"""
|
||||
Build a name → RuntimeAdapter mapping from the ``runtime:`` config block.
|
||||
|
||||
Every key whose value is a string is treated as a runtime adapter name
|
||||
and instantiated via ``_build_runtime``. Non-string values (e.g.
|
||||
``native_teams: false``) are skipped so that boolean/numeric control
|
||||
flags can coexist in the same config section.
|
||||
"""
|
||||
runtimes: dict[str, RuntimeAdapter] = {}
|
||||
for name, value in runtime_cfg.items():
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
runtimes[name] = self._build_runtime(value)
|
||||
return runtimes
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Role registry
|
||||
# ------------------------------------------------------------------
|
||||
@@ -298,8 +338,9 @@ class TeamRunner:
|
||||
|
||||
Routing
|
||||
-------
|
||||
preferred_runtime == "coding_agent" → coding runtime adapter
|
||||
preferred_runtime == "standard" → LLM adapter directly
|
||||
preferred_runtime == "standard" or empty/None → LLM adapter directly
|
||||
Otherwise → look up self._runtimes[preferred_runtime]; falls back to
|
||||
self._runtimes["default"] and then to LLM if no runtime is found.
|
||||
|
||||
Blackboard events emitted: spawned → completed | failed.
|
||||
"""
|
||||
@@ -320,10 +361,21 @@ class TeamRunner:
|
||||
)
|
||||
|
||||
try:
|
||||
if brief.preferred_runtime == "coding_agent":
|
||||
result = self._dispatch_via_runtime(brief)
|
||||
else:
|
||||
pref = brief.preferred_runtime
|
||||
if not pref or pref == "standard":
|
||||
result = self._dispatch_via_llm(brief)
|
||||
else:
|
||||
runtime = self._runtimes.get(pref) or self._runtimes.get("default")
|
||||
if runtime is None:
|
||||
logger.warning(
|
||||
"No runtime adapter found for %r (and no 'default') — "
|
||||
"falling back to LLM for brief %s",
|
||||
pref,
|
||||
brief.brief_id,
|
||||
)
|
||||
result = self._dispatch_via_llm(brief)
|
||||
else:
|
||||
result = self._dispatch_via_runtime(brief, runtime)
|
||||
|
||||
self._bb.update_brief_result(brief.brief_id, result)
|
||||
self._bb.log_event(
|
||||
@@ -359,22 +411,22 @@ class TeamRunner:
|
||||
)
|
||||
return self._extract_json(raw)
|
||||
|
||||
def _dispatch_via_runtime(self, brief: TaskBrief) -> dict:
|
||||
"""Spawn a coding agent via the runtime adapter and collect its result."""
|
||||
def _dispatch_via_runtime(self, brief: TaskBrief, runtime: RuntimeAdapter) -> dict:
|
||||
"""Spawn an agent via *runtime* and collect its result."""
|
||||
task_str = json.dumps(brief.to_dict(), indent=2)
|
||||
capability = _TIER_CAPABILITIES.get(brief.tier, "capable")
|
||||
timeout_s: int = brief.context.get("timeout_s", 300)
|
||||
|
||||
agent_id = self._coding_runtime.spawn(
|
||||
agent_id = runtime.spawn(
|
||||
task=task_str,
|
||||
capability=capability,
|
||||
context=brief.context,
|
||||
)
|
||||
logger.info(
|
||||
"Spawned coding agent %s for brief %s", agent_id, brief.brief_id
|
||||
"Spawned runtime agent %s for brief %s", agent_id, brief.brief_id
|
||||
)
|
||||
|
||||
result = self._coding_runtime.get_result(agent_id, timeout_s=timeout_s)
|
||||
result = runtime.get_result(agent_id, timeout_s=timeout_s)
|
||||
|
||||
# Attempt to parse JSON from the agent's text output.
|
||||
if isinstance(result.get("output"), str) and result["output"].strip():
|
||||
@@ -502,7 +554,9 @@ class TeamRunner:
|
||||
briefs: list[TaskBrief] = []
|
||||
for task in tasks:
|
||||
role = task.get("role", "default")
|
||||
pref_runtime = task.get("preferred_runtime", "standard")
|
||||
# T4 is the coding/implementation tier; default to coding_agent
|
||||
# so implementers use Claude Code unless T3 explicitly overrides.
|
||||
pref_runtime = task.get("preferred_runtime", "coding_agent")
|
||||
brief = parent.make_child_brief(
|
||||
tier=4,
|
||||
role=role,
|
||||
|
||||
Reference in New Issue
Block a user