Compare commits

...

10 Commits

Author SHA1 Message Date
5b0d00a799 feat(config): T3 backend/frontend → senior devs; update agency-agents submodule
T3 squad leads now use dedicated senior roles instead of sharing
architects with T2:
- T3 backend: backend-architect → senior-backend-developer
- T3 frontend: frontend-architect → senior-frontend-developer

Clean tier separation:
  T2 = architects (design)
  T3 = senior devs (lead + implement-or-delegate)
  T4 = developers (pure implementation)

Depends on: coding-with-hans-heinemann/agency-agents#2
2026-03-16 11:31:32 -04:00
461b36bc5d fix(config): T2 backend → backend-architect (per review) 2026-03-16 11:13:19 -04:00
9c1794c58a fix(config): T3 backend → backend-architect; update agency-agents submodule
- T3 backend squad lead should be an architect, not a developer (per review)
- Submodule updated to include frontend-architect + backend-developer roles
2026-03-16 10:36:33 -04:00
8adab6fbc5 refactor(config): replace senior-developer with role-specific specialists
- T2 frontend: software-architect → frontend-architect
- T3 backend: senior-developer → backend-developer
- T3 frontend: senior-developer → frontend-architect
- T3 default: senior-developer → backend-developer
- T4 backend: backend-architect → backend-developer (implementation, not architecture)
- T4 default: senior-developer → backend-developer

Depends on: coding-with-hans-heinemann/agency-agents#1 (new agent roles)
2026-03-16 10:11:47 -04:00
d02faf5cac feat(config): expand role_registry + fix T4 default runtime
Role registry additions:
- T2: add ai, security, mobile domains
- T3: add data, ai, security, mobile, database, devops, docs domains
- T4: add data (data-engineer), embedded (firmware-engineer)
- T5: add accessibility, e2e, frontend, data verifier roles

TeamRunner consistency fix:
- T4 briefs now default to 'coding_agent' runtime (Claude Code)
  per build spec: 'Claude Code as default T4 runtime'
  T3 can still override preferred_runtime per task
2026-03-16 09:14:22 -04:00
c70448b61c chore: sync agency-agents submodule with upstream 2026-03-16 09:00:15 -04:00
aef553bdc8 refactor: T4 fast-cheap; move adapter registries from code to team.yaml 2026-03-16 01:14:35 -04:00
8277a00118 perf(team_runner): T4 capability fast-cheap — implementers produce structured JSON, don't need capable tier 2026-03-16 01:14:08 -04:00
45e3b7663e fix: lazy-import anthropic SDK; tolerate LLM adapter failure in dry-run mode
--dry-run was crashing with ModuleNotFoundError because:
1. adapters/llm/anthropic.py imported 'anthropic' at module level
2. TeamRunner.__init__ always built the LLM adapter regardless of dry_run flag

Fixes:
- Move 'import anthropic' inside AnthropicAdapter.__init__ (lazy import)
  so the module loads cleanly without the SDK installed
- In TeamRunner.__init__, wrap _build_llm in a try/except when dry_run=True
  so missing adapter deps are logged as warnings, not fatal errors

dry-run now works with no third-party packages installed.
2026-03-16 01:03:17 -04:00
7b1cf7315c refactor(team_runner): make runtimes config-driven — replace hardcoded slots with dict
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:50:17 -04:00
5 changed files with 139 additions and 53 deletions

View File

@@ -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

Submodule agents updated: 5c669c28e6...72a144406c

View File

@@ -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

View File

@@ -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

View File

@@ -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,