OAuth2 flow with all 11 Google Fitness scopes (activity, body, sleep, heart rate, nutrition, location, blood glucose/pressure/temperature, oxygen saturation, reproductive health). Stores access + refresh tokens; auto-refreshes on expiry. GoogleHealthSignalSource fetches steps, sleep sessions, active minutes, calories, and heart rate from the Fit aggregate + sessions APIs. Signals flow into both the tip orchestrator and the health-vitals pre-compute agent, which generates prompt snippets about step progress, sleep deficit, sedentary time, and elevated heart rate. Signal.kind extended with 'health'; IntegrationProvider extended with 'google-health'. Agent compute signal mapping enriched to include source, kind, and all features so health-vitals can filter its own signals. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
2.3 KiB
Python
61 lines
2.3 KiB
Python
"""Agent registry — single point of registration for sub-agents (ADR-0014).
|
|
|
|
Each agent module contributes:
|
|
- a `BaseAgent` subclass instance
|
|
- a module-level `MANIFEST: AgentManifest`
|
|
|
|
The orchestrator, registry endpoint, and inference framework all read from
|
|
here. Adding an agent is: add a module, register it once below.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from .base import BaseAgent
|
|
from .manifest import AgentManifest
|
|
from .overdue_task import OverdueTaskAgent, MANIFEST as OVERDUE_TASK_MANIFEST
|
|
from .momentum import MomentumAgent, MANIFEST as MOMENTUM_MANIFEST
|
|
from .time_of_day import TimeOfDayAgent, MANIFEST as TIME_OF_DAY_MANIFEST
|
|
from .recent_patterns import RecentPatternsAgent, MANIFEST as RECENT_PATTERNS_MANIFEST
|
|
from .focus_area import FocusAreaAgent, MANIFEST as FOCUS_AREA_MANIFEST
|
|
from .health_vitals import HealthVitalsAgent, MANIFEST as HEALTH_VITALS_MANIFEST
|
|
|
|
_REGISTERED: list[tuple[BaseAgent, AgentManifest]] = [
|
|
(OverdueTaskAgent(), OVERDUE_TASK_MANIFEST),
|
|
(MomentumAgent(), MOMENTUM_MANIFEST),
|
|
(TimeOfDayAgent(), TIME_OF_DAY_MANIFEST),
|
|
(RecentPatternsAgent(), RECENT_PATTERNS_MANIFEST),
|
|
(FocusAreaAgent(), FOCUS_AREA_MANIFEST),
|
|
(HealthVitalsAgent(), HEALTH_VITALS_MANIFEST),
|
|
]
|
|
|
|
# Sanity check — agent_id and manifest.id must agree, otherwise the registry
|
|
# becomes inconsistent across endpoints.
|
|
for _agent, _manifest in _REGISTERED:
|
|
if _agent.agent_id != _manifest.id:
|
|
raise RuntimeError(
|
|
f"Manifest mismatch: {_agent.__class__.__name__}.agent_id={_agent.agent_id!r} "
|
|
f"≠ MANIFEST.id={_manifest.id!r}"
|
|
)
|
|
|
|
_AGENTS: dict[str, BaseAgent] = {a.agent_id: a for a, _ in _REGISTERED}
|
|
_MANIFESTS: dict[str, AgentManifest] = {m.id: m for _, m in _REGISTERED}
|
|
|
|
|
|
def get_agent(agent_id: str) -> BaseAgent:
|
|
if agent_id not in _AGENTS:
|
|
raise KeyError(f"Unknown agent: {agent_id!r}. Known: {sorted(_AGENTS)}")
|
|
return _AGENTS[agent_id]
|
|
|
|
|
|
def all_agents() -> list[BaseAgent]:
|
|
return list(_AGENTS.values())
|
|
|
|
|
|
def get_manifest(agent_id: str) -> AgentManifest:
|
|
if agent_id not in _MANIFESTS:
|
|
raise KeyError(f"Unknown agent: {agent_id!r}. Known: {sorted(_MANIFESTS)}")
|
|
return _MANIFESTS[agent_id]
|
|
|
|
|
|
def all_manifests() -> list[AgentManifest]:
|
|
return list(_MANIFESTS.values())
|