Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions a0/adapters/subagents.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 127:32 0:0 4:0
# 136:37 0:0 4:0
"""PTCA subagent definitions for the claude-agent-sdk.

Each AgentDefinition maps to a PTCA architectural role.
Expand All @@ -15,7 +15,22 @@
"""
from __future__ import annotations

from claude_agent_sdk import AgentDefinition
try:
from claude_agent_sdk import AgentDefinition
except ImportError:
# claude_agent_sdk is an optional runtime dependency (see
# ClaudeAgentAdapter._SDK_AVAILABLE). When it is absent the adapter
# degrades gracefully, so these definitions must remain importable too.
# This stand-in carries the same fields the SDK's AgentDefinition does,
# keeping ALL_SUBAGENTS / MODE_SUBAGENTS populated for inspection.
from dataclasses import dataclass, field

@dataclass
class AgentDefinition: # type: ignore[no-redef]
description: str = ""
prompt: str = ""
tools: list[str] = field(default_factory=list)
model: str = ""

# ---------------------------------------------------------------------------
# Private cognitive cores
Expand Down Expand Up @@ -175,4 +190,4 @@
"route": ROUTE_SUBAGENTS,
"act": ACT_SUBAGENTS,
}
# 127:32 0:0 4:0
# 136:37 0:0 4:0
9 changes: 6 additions & 3 deletions python/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 164:17 0:0 0:31
# 163:21 0:0 0:31
from .chat import router as chat_router
from .agents import router as agents_router
from .memory import router as memory_router
Expand Down Expand Up @@ -88,7 +88,10 @@ def collect_ui_meta() -> list[dict]:
"python.routes.cli",
"python.routes.liminals",
"python.routes.artifacts",
"python.routes.transcripts",
# transcripts is a standalone page (routed at /transcripts via the top
# nav), not a metadata-driven console tab. Its UI_META is a page-nav
# descriptor (path, no tab_id/sections) and must not be aggregated here,
# or the console renders an unrenderable placeholder tab for it.
"python.routes.fleet",
]
tabs = []
Expand Down Expand Up @@ -195,4 +198,4 @@ def collect_doc_meta() -> list[dict]:
# === END CONTRACTS ===
# 171:16

# 164:17 0:0 0:31
# 163:21 0:0 0:31
81 changes: 81 additions & 0 deletions tests/test_a0_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 53:10 0:0 0:0
# DOC module: tests.test_a0_package
# DOC label: a0 package import + CLI smoke
# DOC description: Imports every module under the a0/ package to catch
# import-time breaks (e.g. an unguarded optional dependency), verifies the
# subagent registry is populated without the optional claude_agent_sdk, and
# runs the a0.a0 CLI end-to-end through stdin/stdout.
import importlib
import json
import pkgutil
import subprocess
import sys

import pytest

import a0


A0_MODULES = sorted(
m.name for m in pkgutil.walk_packages(a0.__path__, prefix="a0.")
)


def test_module_discovery_nonempty():
# Guards against the walk silently finding nothing (which would make the
# parametrized import test below vacuously pass).
assert len(A0_MODULES) > 20, f"only discovered {len(A0_MODULES)} a0 modules"


@pytest.mark.parametrize("modname", A0_MODULES)
def test_a0_submodule_imports_clean(modname):
importlib.import_module(modname)


def test_subagent_registry_populated_without_sdk():
# claude_agent_sdk is an optional dependency; the definitions must still
# load and carry their data when it is absent.
from a0.adapters import ALL_SUBAGENTS, MODE_SUBAGENTS

assert set(ALL_SUBAGENTS) == {"phi", "psi", "omega", "jury", "bandit"}
for mode in ("analyze", "route", "act"):
assert mode in MODE_SUBAGENTS
assert len(MODE_SUBAGENTS[mode]) > 0
bandit = ALL_SUBAGENTS["bandit"]
assert bandit.tools and bandit.model


def test_handle_round_trips_in_process():
from a0.contract import A0Request, normalize_hmmm
from a0.router import handle

req = A0Request(
task_id="unit-1",
input={"text": "hello", "files": [], "metadata": {}},
tools_allowed=["none"],
mode="analyze",
hmmm=normalize_hmmm(["hmm"]),
)
resp = handle(req)
assert resp.task_id == "unit-1"
assert resp.result is not None


def test_a0_cli_smoke():
payload = {
"task_id": "smoke1",
"input": {"text": "hello a0", "files": [], "metadata": {}},
"tools_allowed": ["none"],
"mode": "analyze",
"hmmm": ["hmm"],
}
proc = subprocess.run(
[sys.executable, "-m", "a0.a0"],
input=json.dumps(payload).encode("utf-8"),
stdout=subprocess.PIPE,
check=True,
)
out = json.loads(proc.stdout.decode("utf-8"))
assert out["task_id"] == "smoke1"
assert "result" in out
# 53:10 0:0 0:0
102 changes: 0 additions & 102 deletions tests/test_energy_registry_persistence.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/test_inference_modes_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_aggregate_round_trips_through_cache_breakdown():
energy_registry.cache_breakdown — which the chat route calls on the
final usage — reads back the same numbers without subtracting
cache_read twice."""
from python.services.energy_registry import energy_registry
from python.services import energy_registry
serialized = [
{"model": "claude", "content": "x", "error": None,
"usage": {"input_tokens": 200, "output_tokens": 50,
Expand Down
Loading