hyperagents 1.2.3rc5.post3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- ui/tui/app.py +214 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HyperClaw Agent Coordinator
|
|
3
|
+
Orchestrates all agents, routes tasks by domain, and manages execution.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Callable, Optional
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from .model_router import ModelRouter, ModelTier, get_model_router
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("hyperclaw.coordinator")
|
|
22
|
+
|
|
23
|
+
# Paths
|
|
24
|
+
ROOT_DIR = Path(__file__).parent.parent
|
|
25
|
+
CONFIG_DIR = ROOT_DIR / "config"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TaskStatus(Enum):
|
|
29
|
+
PENDING = "pending"
|
|
30
|
+
RUNNING = "running"
|
|
31
|
+
COMPLETED = "completed"
|
|
32
|
+
FAILED = "failed"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AgentConfig:
|
|
37
|
+
"""Configuration for an agent."""
|
|
38
|
+
id: str
|
|
39
|
+
name: str
|
|
40
|
+
domain: str
|
|
41
|
+
role: str
|
|
42
|
+
tier: int = 1
|
|
43
|
+
model_preference: str = "claude-sonnet"
|
|
44
|
+
task_types: list[str] = field(default_factory=list)
|
|
45
|
+
capabilities: list[str] = field(default_factory=list)
|
|
46
|
+
persona: str = ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class Task:
|
|
51
|
+
"""A task to be executed by an agent."""
|
|
52
|
+
id: str
|
|
53
|
+
goal: str
|
|
54
|
+
domain: str
|
|
55
|
+
task_type: str = "general"
|
|
56
|
+
priority: int = 5
|
|
57
|
+
assigned_to: Optional[str] = None
|
|
58
|
+
status: TaskStatus = TaskStatus.PENDING
|
|
59
|
+
result: Optional[str] = None
|
|
60
|
+
error: Optional[str] = None
|
|
61
|
+
parent_id: Optional[str] = None
|
|
62
|
+
metadata: dict = field(default_factory=dict)
|
|
63
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
64
|
+
completed_at: Optional[datetime] = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Domain keywords for routing
|
|
68
|
+
DOMAIN_KEYWORDS = {
|
|
69
|
+
"personal": ["schedule", "calendar", "reminder", "task", "todo", "health", "fitness", "meal", "travel", "home"],
|
|
70
|
+
"business": ["meeting", "email", "report", "strategy", "revenue", "sales", "client", "contract", "deal", "pipeline"],
|
|
71
|
+
"communications": ["message", "telegram", "slack", "email", "notification", "alert", "send", "reply"],
|
|
72
|
+
"scientific": ["research", "data", "analysis", "paper", "study", "experiment", "hypothesis"],
|
|
73
|
+
"creative": ["write", "design", "content", "story", "article", "blog", "video", "image"],
|
|
74
|
+
"technology": ["code", "debug", "deploy", "server", "api", "database", "security", "infrastructure"],
|
|
75
|
+
"talent": ["athlete", "talent", "roster", "brand", "sponsorship", "partnership"],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Task type patterns
|
|
79
|
+
TASK_TYPE_PATTERNS = {
|
|
80
|
+
"research": [r"research", r"find out", r"look up", r"investigate", r"what is", r"who is", r"learn about"],
|
|
81
|
+
"analysis": [r"analyze", r"evaluate", r"assess", r"compare", r"review"],
|
|
82
|
+
"synthesis": [r"summarize", r"combine", r"synthesize", r"consolidate", r"compile"],
|
|
83
|
+
"planning": [r"plan", r"strategy", r"schedule", r"organize", r"prioritize"],
|
|
84
|
+
"execution": [r"send", r"create", r"make", r"do", r"execute", r"perform", r"complete"],
|
|
85
|
+
"monitoring": [r"check", r"monitor", r"watch", r"track", r"status"],
|
|
86
|
+
"communication": [r"tell", r"inform", r"notify", r"message", r"email", r"reply"],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AgentCoordinator:
|
|
91
|
+
"""
|
|
92
|
+
Central coordinator for all HyperClaw agents.
|
|
93
|
+
Routes tasks to appropriate agents based on domain and capabilities.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, model_router: ModelRouter = None):
|
|
97
|
+
self.model_router = model_router or get_model_router()
|
|
98
|
+
self.agents: dict[str, AgentConfig] = {}
|
|
99
|
+
self.tasks: dict[str, Task] = {}
|
|
100
|
+
self.task_queue: asyncio.Queue = asyncio.Queue()
|
|
101
|
+
self._workers: list[asyncio.Task] = []
|
|
102
|
+
self._running = False
|
|
103
|
+
self._load_agents()
|
|
104
|
+
|
|
105
|
+
def _load_agents(self):
|
|
106
|
+
"""Load agent configurations from YAML."""
|
|
107
|
+
from hyperclaw.api_utils import find_config
|
|
108
|
+
agents_file = find_config("agents.yaml")
|
|
109
|
+
|
|
110
|
+
if not agents_file.exists():
|
|
111
|
+
logger.warning(f"Agents config not found: {agents_file}")
|
|
112
|
+
self._create_default_agents()
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
with open(agents_file) as f:
|
|
117
|
+
config = yaml.safe_load(f)
|
|
118
|
+
|
|
119
|
+
for agent_data in config.get("agents", []):
|
|
120
|
+
agent = AgentConfig(
|
|
121
|
+
id=agent_data.get("id", "unknown"),
|
|
122
|
+
name=agent_data.get("name", agent_data.get("id", "Unknown")),
|
|
123
|
+
domain=agent_data.get("domain", "general"),
|
|
124
|
+
role=agent_data.get("role", ""),
|
|
125
|
+
tier=agent_data.get("tier", 1),
|
|
126
|
+
model_preference=agent_data.get("model_preference", "claude-sonnet"),
|
|
127
|
+
task_types=agent_data.get("task_types", []),
|
|
128
|
+
capabilities=agent_data.get("capabilities", []),
|
|
129
|
+
persona=agent_data.get("persona", ""),
|
|
130
|
+
)
|
|
131
|
+
self.agents[agent.id] = agent
|
|
132
|
+
|
|
133
|
+
logger.info(f"Loaded {len(self.agents)} agents from config")
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"Failed to load agents config: {e}")
|
|
137
|
+
self._create_default_agents()
|
|
138
|
+
|
|
139
|
+
def _create_default_agents(self):
|
|
140
|
+
"""Create default agents if config not available."""
|
|
141
|
+
defaults = [
|
|
142
|
+
AgentConfig("ATLAS", "Atlas", "personal", "Personal productivity assistant", task_types=["planning", "execution"]),
|
|
143
|
+
AgentConfig("HERALD", "Herald", "communications", "Communications manager", task_types=["communication"]),
|
|
144
|
+
AgentConfig("STRATEGOS", "Strategos", "business", "Business strategist", task_types=["analysis", "planning"]),
|
|
145
|
+
AgentConfig("SCRIBE", "Scribe", "scientific", "Research assistant", task_types=["research", "synthesis"]),
|
|
146
|
+
AgentConfig("AUTHOR", "Author", "creative", "Creative writer", task_types=["synthesis", "execution"]),
|
|
147
|
+
AgentConfig("FORGE", "Forge", "technology", "Technical specialist", task_types=["execution", "analysis"]),
|
|
148
|
+
AgentConfig("NEXUS", "Nexus", "executive", "Executive coordinator", task_types=["planning", "monitoring"], tier=0),
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
for agent in defaults:
|
|
152
|
+
self.agents[agent.id] = agent
|
|
153
|
+
|
|
154
|
+
logger.info(f"Created {len(self.agents)} default agents")
|
|
155
|
+
|
|
156
|
+
# =========================================================================
|
|
157
|
+
# TASK ROUTING
|
|
158
|
+
# =========================================================================
|
|
159
|
+
|
|
160
|
+
def classify_domain(self, message: str) -> str:
|
|
161
|
+
"""Classify message into a domain."""
|
|
162
|
+
message_lower = message.lower()
|
|
163
|
+
|
|
164
|
+
domain_scores = {}
|
|
165
|
+
for domain, keywords in DOMAIN_KEYWORDS.items():
|
|
166
|
+
score = sum(1 for kw in keywords if kw in message_lower)
|
|
167
|
+
if score > 0:
|
|
168
|
+
domain_scores[domain] = score
|
|
169
|
+
|
|
170
|
+
if domain_scores:
|
|
171
|
+
return max(domain_scores, key=domain_scores.get)
|
|
172
|
+
|
|
173
|
+
return "general"
|
|
174
|
+
|
|
175
|
+
def classify_task_type(self, message: str) -> str:
|
|
176
|
+
"""Classify message into a task type."""
|
|
177
|
+
message_lower = message.lower()
|
|
178
|
+
|
|
179
|
+
for task_type, patterns in TASK_TYPE_PATTERNS.items():
|
|
180
|
+
for pattern in patterns:
|
|
181
|
+
if re.search(pattern, message_lower):
|
|
182
|
+
return task_type
|
|
183
|
+
|
|
184
|
+
return "general"
|
|
185
|
+
|
|
186
|
+
def select_agent(self, task: Task) -> Optional[AgentConfig]:
|
|
187
|
+
"""Select the best agent for a task."""
|
|
188
|
+
candidates = []
|
|
189
|
+
|
|
190
|
+
for agent_id, agent in self.agents.items():
|
|
191
|
+
score = 0
|
|
192
|
+
|
|
193
|
+
# Domain match
|
|
194
|
+
if agent.domain == task.domain:
|
|
195
|
+
score += 10
|
|
196
|
+
elif agent.domain == "executive":
|
|
197
|
+
score += 3 # Executive agents can handle anything
|
|
198
|
+
|
|
199
|
+
# Task type match
|
|
200
|
+
if task.task_type in agent.task_types:
|
|
201
|
+
score += 5
|
|
202
|
+
|
|
203
|
+
# Tier bonus (lower tier = more capable)
|
|
204
|
+
score += (3 - agent.tier)
|
|
205
|
+
|
|
206
|
+
if score > 0:
|
|
207
|
+
candidates.append((agent, score))
|
|
208
|
+
|
|
209
|
+
if not candidates:
|
|
210
|
+
# Fallback to NEXUS or first available
|
|
211
|
+
if "NEXUS" in self.agents:
|
|
212
|
+
return self.agents["NEXUS"]
|
|
213
|
+
return list(self.agents.values())[0] if self.agents else None
|
|
214
|
+
|
|
215
|
+
# Sort by score, return best
|
|
216
|
+
candidates.sort(key=lambda x: x[1], reverse=True)
|
|
217
|
+
return candidates[0][0]
|
|
218
|
+
|
|
219
|
+
def determine_model_tier(self, task: Task, agent: AgentConfig) -> ModelTier:
|
|
220
|
+
"""Determine which model tier to use for a task."""
|
|
221
|
+
# Simple tasks use fast model
|
|
222
|
+
if task.task_type in ["monitoring", "communication"]:
|
|
223
|
+
return ModelTier.FAST
|
|
224
|
+
|
|
225
|
+
# Complex tasks use standard/premium
|
|
226
|
+
if task.task_type in ["analysis", "research", "planning"]:
|
|
227
|
+
if len(task.goal) > 500 or task.priority >= 8:
|
|
228
|
+
return ModelTier.PREMIUM
|
|
229
|
+
return ModelTier.STANDARD
|
|
230
|
+
|
|
231
|
+
# Execution tasks depend on complexity
|
|
232
|
+
if len(task.goal) < 100:
|
|
233
|
+
return ModelTier.FAST
|
|
234
|
+
|
|
235
|
+
return ModelTier.STANDARD
|
|
236
|
+
|
|
237
|
+
# =========================================================================
|
|
238
|
+
# TASK EXECUTION
|
|
239
|
+
# =========================================================================
|
|
240
|
+
|
|
241
|
+
async def submit_task(
|
|
242
|
+
self,
|
|
243
|
+
goal: str,
|
|
244
|
+
domain: str = None,
|
|
245
|
+
task_type: str = None,
|
|
246
|
+
priority: int = 5,
|
|
247
|
+
agent_id: str = None,
|
|
248
|
+
metadata: dict = None
|
|
249
|
+
) -> Task:
|
|
250
|
+
"""Submit a new task for execution."""
|
|
251
|
+
task_id = str(uuid.uuid4())[:8]
|
|
252
|
+
|
|
253
|
+
# Auto-classify if not provided
|
|
254
|
+
if not domain:
|
|
255
|
+
domain = self.classify_domain(goal)
|
|
256
|
+
if not task_type:
|
|
257
|
+
task_type = self.classify_task_type(goal)
|
|
258
|
+
|
|
259
|
+
task = Task(
|
|
260
|
+
id=task_id,
|
|
261
|
+
goal=goal,
|
|
262
|
+
domain=domain,
|
|
263
|
+
task_type=task_type,
|
|
264
|
+
priority=priority,
|
|
265
|
+
metadata=metadata or {},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Select agent
|
|
269
|
+
if agent_id and agent_id in self.agents:
|
|
270
|
+
task.assigned_to = agent_id
|
|
271
|
+
else:
|
|
272
|
+
agent = self.select_agent(task)
|
|
273
|
+
if agent:
|
|
274
|
+
task.assigned_to = agent.id
|
|
275
|
+
|
|
276
|
+
self.tasks[task_id] = task
|
|
277
|
+
await self.task_queue.put(task)
|
|
278
|
+
|
|
279
|
+
logger.info(f"Task {task_id} submitted: {goal[:50]}... -> {task.assigned_to}")
|
|
280
|
+
|
|
281
|
+
return task
|
|
282
|
+
|
|
283
|
+
async def execute_task(self, task: Task) -> str:
|
|
284
|
+
"""Execute a single task."""
|
|
285
|
+
task.status = TaskStatus.RUNNING
|
|
286
|
+
|
|
287
|
+
agent = self.agents.get(task.assigned_to)
|
|
288
|
+
if not agent:
|
|
289
|
+
task.status = TaskStatus.FAILED
|
|
290
|
+
task.error = f"Agent {task.assigned_to} not found"
|
|
291
|
+
return task.error
|
|
292
|
+
|
|
293
|
+
# Determine model tier
|
|
294
|
+
model_tier = self.determine_model_tier(task, agent)
|
|
295
|
+
|
|
296
|
+
# Build agent prompt
|
|
297
|
+
system_prompt = self._build_agent_prompt(agent, task)
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
# Call model through router
|
|
301
|
+
response, metadata = await self.model_router.call(
|
|
302
|
+
message=task.goal,
|
|
303
|
+
system=system_prompt,
|
|
304
|
+
preferred_tier=model_tier,
|
|
305
|
+
context={"task": task, "agent": agent},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
task.result = response
|
|
309
|
+
task.status = TaskStatus.COMPLETED
|
|
310
|
+
task.completed_at = datetime.now()
|
|
311
|
+
|
|
312
|
+
logger.info(f"Task {task.id} completed by {agent.id} using {metadata.get('model_name')}")
|
|
313
|
+
|
|
314
|
+
return response
|
|
315
|
+
|
|
316
|
+
except Exception as e:
|
|
317
|
+
task.status = TaskStatus.FAILED
|
|
318
|
+
task.error = str(e)
|
|
319
|
+
logger.error(f"Task {task.id} failed: {e}")
|
|
320
|
+
return f"Error: {e}"
|
|
321
|
+
|
|
322
|
+
def _build_agent_prompt(self, agent: AgentConfig, task: Task) -> str:
|
|
323
|
+
"""Build system prompt for an agent."""
|
|
324
|
+
prompt_parts = [
|
|
325
|
+
f"You are {agent.name}, a specialized AI agent.",
|
|
326
|
+
f"Role: {agent.role}",
|
|
327
|
+
f"Domain: {agent.domain}",
|
|
328
|
+
"",
|
|
329
|
+
"## Behavioral Guidelines",
|
|
330
|
+
"- Execute tasks efficiently and completely",
|
|
331
|
+
"- Be concise but thorough",
|
|
332
|
+
"- If you cannot complete a task, explain why clearly",
|
|
333
|
+
"- Provide actionable outputs",
|
|
334
|
+
"",
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
if agent.persona:
|
|
338
|
+
prompt_parts.append(f"## Persona\n{agent.persona}\n")
|
|
339
|
+
|
|
340
|
+
if task.metadata:
|
|
341
|
+
prompt_parts.append(f"## Task Context\n{task.metadata}\n")
|
|
342
|
+
|
|
343
|
+
prompt_parts.append(f"Current time: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
|
344
|
+
|
|
345
|
+
return "\n".join(prompt_parts)
|
|
346
|
+
|
|
347
|
+
# =========================================================================
|
|
348
|
+
# MULTI-AGENT COORDINATION
|
|
349
|
+
# =========================================================================
|
|
350
|
+
|
|
351
|
+
async def coordinate(
|
|
352
|
+
self,
|
|
353
|
+
goal: str,
|
|
354
|
+
context: dict = None,
|
|
355
|
+
max_agents: int = 3
|
|
356
|
+
) -> dict:
|
|
357
|
+
"""
|
|
358
|
+
Coordinate multiple agents to accomplish a complex goal.
|
|
359
|
+
Breaks down the goal and routes sub-tasks to specialists.
|
|
360
|
+
"""
|
|
361
|
+
logger.info(f"Coordinating goal: {goal[:100]}...")
|
|
362
|
+
|
|
363
|
+
# First, use NEXUS (or primary agent) to plan
|
|
364
|
+
plan_result = await self._plan_goal(goal, context)
|
|
365
|
+
|
|
366
|
+
# Parse sub-tasks from plan
|
|
367
|
+
sub_tasks = self._parse_subtasks(plan_result)
|
|
368
|
+
|
|
369
|
+
if not sub_tasks:
|
|
370
|
+
# Simple task, execute directly
|
|
371
|
+
task = await self.submit_task(goal)
|
|
372
|
+
result = await self.execute_task(task)
|
|
373
|
+
return {
|
|
374
|
+
"goal": goal,
|
|
375
|
+
"approach": "direct",
|
|
376
|
+
"result": result,
|
|
377
|
+
"tasks": [{"id": task.id, "status": task.status.value}],
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# Execute sub-tasks (potentially in parallel)
|
|
381
|
+
results = []
|
|
382
|
+
parallel_tasks = []
|
|
383
|
+
|
|
384
|
+
for sub_task_goal in sub_tasks[:max_agents]:
|
|
385
|
+
task = await self.submit_task(sub_task_goal, metadata={"parent_goal": goal})
|
|
386
|
+
parallel_tasks.append(self.execute_task(task))
|
|
387
|
+
|
|
388
|
+
# Wait for all tasks
|
|
389
|
+
task_results = await asyncio.gather(*parallel_tasks, return_exceptions=True)
|
|
390
|
+
|
|
391
|
+
for i, result in enumerate(task_results):
|
|
392
|
+
if isinstance(result, Exception):
|
|
393
|
+
results.append({"error": str(result)})
|
|
394
|
+
else:
|
|
395
|
+
results.append({"result": result})
|
|
396
|
+
|
|
397
|
+
# Synthesize results
|
|
398
|
+
synthesis = await self._synthesize_results(goal, results)
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
"goal": goal,
|
|
402
|
+
"approach": "coordinated",
|
|
403
|
+
"sub_tasks": len(sub_tasks),
|
|
404
|
+
"results": results,
|
|
405
|
+
"synthesis": synthesis,
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async def _plan_goal(self, goal: str, context: dict = None) -> str:
|
|
409
|
+
"""Use NEXUS to plan how to accomplish a goal."""
|
|
410
|
+
planning_prompt = f"""Analyze this goal and break it into sub-tasks if needed:
|
|
411
|
+
|
|
412
|
+
Goal: {goal}
|
|
413
|
+
|
|
414
|
+
If this is a simple task, respond with: SIMPLE: [brief task description]
|
|
415
|
+
|
|
416
|
+
If this requires multiple steps or specialists, respond with:
|
|
417
|
+
SUBTASKS:
|
|
418
|
+
1. [First sub-task]
|
|
419
|
+
2. [Second sub-task]
|
|
420
|
+
...
|
|
421
|
+
|
|
422
|
+
Be specific and actionable. Maximum 5 sub-tasks."""
|
|
423
|
+
|
|
424
|
+
response, _ = await self.model_router.call(
|
|
425
|
+
message=planning_prompt,
|
|
426
|
+
system="You are a task planning specialist. Break down complex goals into actionable sub-tasks.",
|
|
427
|
+
preferred_tier=ModelTier.FAST, # Planning is fast
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
return response
|
|
431
|
+
|
|
432
|
+
def _parse_subtasks(self, plan_result: str) -> list[str]:
|
|
433
|
+
"""Parse sub-tasks from planning result."""
|
|
434
|
+
if "SIMPLE:" in plan_result:
|
|
435
|
+
return []
|
|
436
|
+
|
|
437
|
+
sub_tasks = []
|
|
438
|
+
lines = plan_result.split("\n")
|
|
439
|
+
|
|
440
|
+
for line in lines:
|
|
441
|
+
line = line.strip()
|
|
442
|
+
# Match numbered items
|
|
443
|
+
match = re.match(r"^\d+[\.\)]\s*(.+)$", line)
|
|
444
|
+
if match:
|
|
445
|
+
sub_tasks.append(match.group(1))
|
|
446
|
+
# Match bullet points
|
|
447
|
+
elif line.startswith("- "):
|
|
448
|
+
sub_tasks.append(line[2:])
|
|
449
|
+
|
|
450
|
+
return sub_tasks
|
|
451
|
+
|
|
452
|
+
async def _synthesize_results(self, goal: str, results: list[dict]) -> str:
|
|
453
|
+
"""Synthesize results from multiple sub-tasks."""
|
|
454
|
+
results_text = "\n\n".join(
|
|
455
|
+
f"Result {i+1}: {r.get('result', r.get('error', 'No result'))}"
|
|
456
|
+
for i, r in enumerate(results)
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
synthesis_prompt = f"""Original goal: {goal}
|
|
460
|
+
|
|
461
|
+
Sub-task results:
|
|
462
|
+
{results_text}
|
|
463
|
+
|
|
464
|
+
Synthesize these results into a coherent response that addresses the original goal.
|
|
465
|
+
Be concise but complete."""
|
|
466
|
+
|
|
467
|
+
response, _ = await self.model_router.call(
|
|
468
|
+
message=synthesis_prompt,
|
|
469
|
+
system="You synthesize multiple results into coherent responses.",
|
|
470
|
+
preferred_tier=ModelTier.STANDARD,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return response
|
|
474
|
+
|
|
475
|
+
# =========================================================================
|
|
476
|
+
# BACKGROUND WORKER
|
|
477
|
+
# =========================================================================
|
|
478
|
+
|
|
479
|
+
async def start_workers(self, num_workers: int = 3):
|
|
480
|
+
"""Start background task workers."""
|
|
481
|
+
if self._running:
|
|
482
|
+
return
|
|
483
|
+
|
|
484
|
+
self._running = True
|
|
485
|
+
|
|
486
|
+
for i in range(num_workers):
|
|
487
|
+
worker = asyncio.create_task(self._worker_loop(f"worker-{i}"))
|
|
488
|
+
self._workers.append(worker)
|
|
489
|
+
|
|
490
|
+
logger.info(f"Started {num_workers} task workers")
|
|
491
|
+
|
|
492
|
+
async def stop_workers(self):
|
|
493
|
+
"""Stop background workers."""
|
|
494
|
+
self._running = False
|
|
495
|
+
|
|
496
|
+
for worker in self._workers:
|
|
497
|
+
worker.cancel()
|
|
498
|
+
|
|
499
|
+
await asyncio.gather(*self._workers, return_exceptions=True)
|
|
500
|
+
self._workers = []
|
|
501
|
+
|
|
502
|
+
logger.info("Task workers stopped")
|
|
503
|
+
|
|
504
|
+
async def _worker_loop(self, worker_id: str):
|
|
505
|
+
"""Worker loop that processes tasks from queue."""
|
|
506
|
+
logger.debug(f"{worker_id} started")
|
|
507
|
+
|
|
508
|
+
while self._running:
|
|
509
|
+
try:
|
|
510
|
+
task = await asyncio.wait_for(self.task_queue.get(), timeout=1.0)
|
|
511
|
+
await self.execute_task(task)
|
|
512
|
+
self.task_queue.task_done()
|
|
513
|
+
except asyncio.TimeoutError:
|
|
514
|
+
continue
|
|
515
|
+
except asyncio.CancelledError:
|
|
516
|
+
break
|
|
517
|
+
except Exception as e:
|
|
518
|
+
logger.error(f"{worker_id} error: {e}")
|
|
519
|
+
|
|
520
|
+
logger.debug(f"{worker_id} stopped")
|
|
521
|
+
|
|
522
|
+
# =========================================================================
|
|
523
|
+
# STATUS & METRICS
|
|
524
|
+
# =========================================================================
|
|
525
|
+
|
|
526
|
+
def get_status(self) -> dict:
|
|
527
|
+
"""Get coordinator status."""
|
|
528
|
+
active_tasks = sum(1 for t in self.tasks.values() if t.status == TaskStatus.RUNNING)
|
|
529
|
+
pending_tasks = sum(1 for t in self.tasks.values() if t.status == TaskStatus.PENDING)
|
|
530
|
+
completed_tasks = sum(1 for t in self.tasks.values() if t.status == TaskStatus.COMPLETED)
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
"agents": len(self.agents),
|
|
534
|
+
"agents_by_domain": self._count_by_domain(),
|
|
535
|
+
"tasks": {
|
|
536
|
+
"total": len(self.tasks),
|
|
537
|
+
"active": active_tasks,
|
|
538
|
+
"pending": pending_tasks,
|
|
539
|
+
"completed": completed_tasks,
|
|
540
|
+
},
|
|
541
|
+
"workers": len(self._workers),
|
|
542
|
+
"running": self._running,
|
|
543
|
+
"model_stats": self.model_router.get_stats(),
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
def _count_by_domain(self) -> dict:
|
|
547
|
+
"""Count agents by domain."""
|
|
548
|
+
counts = {}
|
|
549
|
+
for agent in self.agents.values():
|
|
550
|
+
counts[agent.domain] = counts.get(agent.domain, 0) + 1
|
|
551
|
+
return counts
|
|
552
|
+
|
|
553
|
+
def list_agents(self) -> list[dict]:
|
|
554
|
+
"""List all agents."""
|
|
555
|
+
return [
|
|
556
|
+
{
|
|
557
|
+
"id": a.id,
|
|
558
|
+
"name": a.name,
|
|
559
|
+
"domain": a.domain,
|
|
560
|
+
"role": a.role,
|
|
561
|
+
"tier": a.tier,
|
|
562
|
+
"task_types": a.task_types,
|
|
563
|
+
}
|
|
564
|
+
for a in self.agents.values()
|
|
565
|
+
]
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
# Singleton
|
|
569
|
+
_coordinator: Optional[AgentCoordinator] = None
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
async def get_coordinator() -> AgentCoordinator:
|
|
573
|
+
"""Get or create coordinator singleton."""
|
|
574
|
+
global _coordinator
|
|
575
|
+
if _coordinator is None:
|
|
576
|
+
_coordinator = AgentCoordinator()
|
|
577
|
+
return _coordinator
|