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 os
import anthropic
from adapters.base.llm import LLMAdapter from adapters.base.llm import LLMAdapter
@@ -57,6 +55,14 @@ class AnthropicAdapter(LLMAdapter):
ValueError ValueError
If ANTHROPIC_API_KEY is not set in the environment. 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 self._config = config
api_key = os.environ.get("ANTHROPIC_API_KEY") api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key: if not api_key:
@@ -64,7 +70,7 @@ class AnthropicAdapter(LLMAdapter):
"ANTHROPIC_API_KEY environment variable is not set. " "ANTHROPIC_API_KEY environment variable is not set. "
"Export it before running the-agency." "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._models_cfg: dict = config.get("models", {})
self._default_max_tokens: int = self._models_cfg.get("default_max_tokens", 4096) self._default_max_tokens: int = self._models_cfg.get("default_max_tokens", 4096)
self._default_temperature: float = self._models_cfg.get("default_temperature", 0) self._default_temperature: float = self._models_cfg.get("default_temperature", 0)

2
agents

Submodule agents updated: 5c669c28e6...72a144406c

View File

@@ -2,33 +2,49 @@ t1:
default: agents/strategy/nexus-strategy.md default: agents/strategy/nexus-strategy.md
t2: t2:
backend: agents/engineering/engineering-software-architect.md backend: agents/engineering/engineering-backend-architect.md
frontend: agents/engineering/engineering-software-architect.md frontend: agents/engineering/engineering-frontend-architect.md
infra: agents/engineering/engineering-devops-automator.md infra: agents/engineering/engineering-devops-automator.md
data: agents/engineering/engineering-data-engineer.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 default: agents/engineering/engineering-software-architect.md
t3: t3:
backend: agents/engineering/engineering-senior-developer.md backend: agents/engineering/engineering-senior-backend-developer.md
frontend: agents/engineering/engineering-senior-developer.md frontend: agents/engineering/engineering-senior-frontend-developer.md
infra: agents/engineering/engineering-sre.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: t4:
frontend: agents/engineering/engineering-frontend-developer.md 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 database: agents/engineering/engineering-database-optimizer.md
devops: agents/engineering/engineering-devops-automator.md devops: agents/engineering/engineering-devops-automator.md
mobile: agents/engineering/engineering-mobile-app-builder.md mobile: agents/engineering/engineering-mobile-app-builder.md
ai: agents/engineering/engineering-ai-engineer.md ai: agents/engineering/engineering-ai-engineer.md
security: agents/engineering/engineering-security-engineer.md security: agents/engineering/engineering-security-engineer.md
docs: agents/engineering/engineering-technical-writer.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: t5:
code: agents/engineering/engineering-code-reviewer.md code: agents/engineering/engineering-code-reviewer.md
integration: agents/testing/testing-reality-checker.md integration: agents/testing/testing-reality-checker.md
api: agents/testing/testing-api-tester.md api: agents/testing/testing-api-tester.md
performance: agents/testing/testing-performance-benchmarker.md performance: agents/testing/testing-performance-benchmarker.md
security: agents/engineering/engineering-security-engineer.md security: agents/engineering/engineering-security-engineer.md
default: agents/engineering/engineering-code-reviewer.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 llm: anthropic
vcs: github vcs: github
notify: openclaw 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: models:
default_max_tokens: 4096 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 The TeamRunner loads team.yaml, builds the adapter registry, and drives the
full T1 → T2 → T3 → T4 → T5 dispatch loop with escalation handling. 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:: CLI usage::
python -m core.team_runner --config config/team.yaml [--dry-run] [--verbose] python -m core.team_runner --config config/team.yaml [--dry-run] [--verbose]
@@ -52,7 +58,7 @@ _TIER_CAPABILITIES: dict[int, str] = {
1: "reasoning-heavy", 1: "reasoning-heavy",
2: "reasoning-heavy", 2: "reasoning-heavy",
3: "capable", 3: "capable",
4: "capable", 4: "fast-cheap",
5: "fast-cheap", 5: "fast-cheap",
} }
@@ -65,18 +71,16 @@ _TIER_CAPABILITIES: dict[int, str] = {
# enabling third-party adapters without touching this file. # enabling third-party adapters without touching this file.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
_LLM_ADAPTERS: dict[str, str] = { # Adapter registries are loaded from team.yaml at runtime (adapter_registry section).
"anthropic": "adapters.llm.anthropic:AnthropicAdapter", # Fallback built-ins are used only if team.yaml doesn't define adapter_registry.
} _BUILTIN_ADAPTER_REGISTRY: dict[str, dict[str, str]] = {
_VCS_ADAPTERS: dict[str, str] = { "llm": {"anthropic": "adapters.llm.anthropic:AnthropicAdapter"},
"github": "adapters.vcs.github:GitHubAdapter", "vcs": {"github": "adapters.vcs.github:GitHubAdapter"},
} "notify": {"openclaw": "adapters.notify.openclaw:OpenClawNotifyAdapter"},
_NOTIFY_ADAPTERS: dict[str, str] = { "runtime": {
"openclaw": "adapters.notify.openclaw:OpenClawNotifyAdapter", "openclaw": "adapters.runtime.openclaw:OpenClawRuntimeAdapter",
} "claude_code": "adapters.runtime.claude_code:ClaudeCodeRuntimeAdapter",
_RUNTIME_ADAPTERS: dict[str, str] = { },
"openclaw": "adapters.runtime.openclaw:OpenClawRuntimeAdapter",
"claude_code": "adapters.runtime.claude_code:ClaudeCodeRuntimeAdapter",
} }
@@ -151,11 +155,23 @@ class TeamRunner:
config_path : Path to team.yaml. config_path : Path to team.yaml.
dry_run : When True, skip LLM calls, VCS commits, and notifications. dry_run : When True, skip LLM calls, VCS commits, and notifications.
All planned actions are logged at INFO level. 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._dry_run = dry_run
self._config = self._load_yaml(config_path) self._config = self._load_yaml(config_path)
self._role_registry = self._load_yaml("config/role_registry.yaml") 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() self._escalation = EscalationHandler()
run_id = str(uuid.uuid4()) run_id = str(uuid.uuid4())
@@ -165,19 +181,27 @@ class TeamRunner:
adapter_cfg: dict = self._config.get("adapters", {}) adapter_cfg: dict = self._config.get("adapters", {})
runtime_cfg: dict = self._config.get("runtime", {}) runtime_cfg: dict = self._config.get("runtime", {})
self._llm: LLMAdapter = self._build_llm(adapter_cfg.get("llm", "anthropic")) 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] 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] self._notify: Optional[NotifyAdapter] = self._build_optional( # type: ignore[assignment]
_NOTIFY_ADAPTERS, adapter_cfg.get("notify"), "notify" self._adapter_registry.get("notify", {}), 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")
) )
# 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( logger.info(
"TeamRunner initialised: run_id=%s dry_run=%s", run_id, dry_run "TeamRunner initialised: run_id=%s dry_run=%s", run_id, dry_run
@@ -198,7 +222,7 @@ class TeamRunner:
return fh.read() return fh.read()
def _build_llm(self, key: str) -> LLMAdapter: 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) return cls(self._config)
def _build_optional( def _build_optional(
@@ -226,9 +250,25 @@ class TeamRunner:
return None return None
def _build_runtime(self, key: str) -> RuntimeAdapter: 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) 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 # Role registry
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -298,8 +338,9 @@ class TeamRunner:
Routing Routing
------- -------
preferred_runtime == "coding_agent" → coding runtime adapter preferred_runtime == "standard" or empty/None → LLM adapter directly
preferred_runtime == "standard" → 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. Blackboard events emitted: spawned → completed | failed.
""" """
@@ -320,10 +361,21 @@ class TeamRunner:
) )
try: try:
if brief.preferred_runtime == "coding_agent": pref = brief.preferred_runtime
result = self._dispatch_via_runtime(brief) if not pref or pref == "standard":
else:
result = self._dispatch_via_llm(brief) 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.update_brief_result(brief.brief_id, result)
self._bb.log_event( self._bb.log_event(
@@ -359,22 +411,22 @@ class TeamRunner:
) )
return self._extract_json(raw) return self._extract_json(raw)
def _dispatch_via_runtime(self, brief: TaskBrief) -> dict: def _dispatch_via_runtime(self, brief: TaskBrief, runtime: RuntimeAdapter) -> dict:
"""Spawn a coding agent via the runtime adapter and collect its result.""" """Spawn an agent via *runtime* and collect its result."""
task_str = json.dumps(brief.to_dict(), indent=2) task_str = json.dumps(brief.to_dict(), indent=2)
capability = _TIER_CAPABILITIES.get(brief.tier, "capable") capability = _TIER_CAPABILITIES.get(brief.tier, "capable")
timeout_s: int = brief.context.get("timeout_s", 300) timeout_s: int = brief.context.get("timeout_s", 300)
agent_id = self._coding_runtime.spawn( agent_id = runtime.spawn(
task=task_str, task=task_str,
capability=capability, capability=capability,
context=brief.context, context=brief.context,
) )
logger.info( 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. # Attempt to parse JSON from the agent's text output.
if isinstance(result.get("output"), str) and result["output"].strip(): if isinstance(result.get("output"), str) and result["output"].strip():
@@ -502,7 +554,9 @@ class TeamRunner:
briefs: list[TaskBrief] = [] briefs: list[TaskBrief] = []
for task in tasks: for task in tasks:
role = task.get("role", "default") 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( brief = parent.make_child_brief(
tier=4, tier=4,
role=role, role=role,