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.
Files changed (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,23 @@
1
+ from .bandit import (
2
+ HyperRouter,
3
+ ucb1_score,
4
+ select_agent,
5
+ select_model,
6
+ update_scores,
7
+ MODEL_COSTS,
8
+ MODEL_LATENCY_MS,
9
+ CHATJIMMY_TASK_TYPES,
10
+ )
11
+ from .fast_loop import FastLoop
12
+
13
+ __all__ = [
14
+ "HyperRouter",
15
+ "ucb1_score",
16
+ "select_agent",
17
+ "select_model",
18
+ "update_scores",
19
+ "MODEL_COSTS",
20
+ "MODEL_LATENCY_MS",
21
+ "CHATJIMMY_TASK_TYPES",
22
+ "FastLoop",
23
+ ]
@@ -0,0 +1,218 @@
1
+ """
2
+ HyperRouter UCB1 Bandit — multi-armed bandit model and agent routing.
3
+ Uses Upper Confidence Bound 1 (UCB1) algorithm for exploration/exploitation.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import copy
8
+ import math
9
+ from dataclasses import dataclass, field
10
+ from typing import Optional
11
+
12
+ from core.hyperstate.schema import AgentScore, ModelScore
13
+
14
+
15
+ # ── Cost and latency tables ───────────────────────────────────────────────────
16
+
17
+ MODEL_COSTS: dict[str, float] = {
18
+ "chatjimmy": 0.000001,
19
+ "claude-opus-5": 0.005,
20
+ "claude-sonnet-5": 0.002,
21
+ "claude-code": 0.003,
22
+ "nim-local": 0.0,
23
+ }
24
+
25
+ MODEL_LATENCY_MS: dict[str, float] = {
26
+ "chatjimmy": 50.0,
27
+ "claude-opus-5": 4000.0,
28
+ "claude-sonnet-5": 2000.0,
29
+ "claude-code": 3000.0,
30
+ "nim-local": 500.0,
31
+ }
32
+
33
+ # Tasks ChatJimmy handles well (fast, cheap, low-stakes)
34
+ CHATJIMMY_TASK_TYPES: frozenset[str] = frozenset([
35
+ "routing", "classification", "quick_lookup",
36
+ "summarization", "draft", "triage", "status_check",
37
+ ])
38
+
39
+
40
+ # ── Core UCB1 functions ───────────────────────────────────────────────────────
41
+
42
+ def ucb1_score(
43
+ successes: int,
44
+ attempts: int,
45
+ total_attempts: int,
46
+ c: float = 2.0,
47
+ ) -> float:
48
+ """
49
+ Compute UCB1 score for a single arm.
50
+
51
+ Returns float('inf') if attempts == 0 (unexplored arm always gets tried first).
52
+ Formula: mean_reward + c * sqrt(ln(total_attempts) / attempts)
53
+ """
54
+ if attempts == 0:
55
+ return float("inf")
56
+ mean_reward = successes / attempts
57
+ exploration = c * math.sqrt(math.log(max(total_attempts, 1)) / attempts)
58
+ return mean_reward + exploration
59
+
60
+
61
+ def select_agent(
62
+ agents: dict[str, AgentScore],
63
+ task_type: str,
64
+ total_attempts: int,
65
+ ) -> str:
66
+ """
67
+ Select the best agent for a task_type using UCB1.
68
+ Prefers agents whose task_type matches, falls back to all agents.
69
+ Returns the agent_id with the highest UCB1 score.
70
+ """
71
+ if not agents:
72
+ return "default_agent"
73
+
74
+ # Filter to agents matching task_type if possible
75
+ matching = {
76
+ aid: score for aid, score in agents.items()
77
+ if score.task_type == task_type
78
+ }
79
+ pool = matching if matching else agents
80
+
81
+ best_id = max(
82
+ pool,
83
+ key=lambda aid: ucb1_score(
84
+ pool[aid].successes,
85
+ pool[aid].attempts,
86
+ total_attempts,
87
+ ),
88
+ )
89
+ return best_id
90
+
91
+
92
+ def select_model(
93
+ model_scores: dict[str, dict[str, ModelScore]],
94
+ task_type: str,
95
+ total_attempts: int,
96
+ cost_budget_usd: Optional[float] = None,
97
+ latency_budget_ms: Optional[float] = None,
98
+ ) -> str:
99
+ """
100
+ Select the best model for a task_type using UCB1, with optional budget filters.
101
+
102
+ ChatJimmy is the default for CHATJIMMY_TASK_TYPES if it passes budget checks.
103
+ Budget filters are applied before scoring — models exceeding either budget are excluded.
104
+ Falls back to 'claude-opus-5' if no scored models available.
105
+ """
106
+ # ChatJimmy fast-path for cheap/quick tasks
107
+ if task_type in CHATJIMMY_TASK_TYPES:
108
+ cj_cost = MODEL_COSTS.get("chatjimmy", 0.0)
109
+ cj_latency = MODEL_LATENCY_MS.get("chatjimmy", 50.0)
110
+ cost_ok = cost_budget_usd is None or cj_cost <= cost_budget_usd
111
+ latency_ok = latency_budget_ms is None or cj_latency <= latency_budget_ms
112
+ if cost_ok and latency_ok:
113
+ return "chatjimmy"
114
+
115
+ # Build candidate pool with budget filtering
116
+ candidates: dict[str, ModelScore] = {}
117
+ for model_id, task_map in model_scores.items():
118
+ score = task_map.get(task_type, ModelScore())
119
+
120
+ cost = MODEL_COSTS.get(model_id, 0.001)
121
+ latency = MODEL_LATENCY_MS.get(model_id, 2000.0)
122
+
123
+ if cost_budget_usd is not None and cost > cost_budget_usd:
124
+ continue
125
+ if latency_budget_ms is not None and latency > latency_budget_ms:
126
+ continue
127
+
128
+ candidates[model_id] = score
129
+
130
+ if not candidates:
131
+ # Fallback: try any model within budget ignoring task_type filter
132
+ for model_id in MODEL_COSTS:
133
+ cost = MODEL_COSTS.get(model_id, 0.001)
134
+ latency = MODEL_LATENCY_MS.get(model_id, 2000.0)
135
+ if cost_budget_usd is not None and cost > cost_budget_usd:
136
+ continue
137
+ if latency_budget_ms is not None and latency > latency_budget_ms:
138
+ continue
139
+ candidates[model_id] = ModelScore()
140
+
141
+ if not candidates:
142
+ return "claude-opus-5"
143
+
144
+ best_model = max(
145
+ candidates,
146
+ key=lambda mid: ucb1_score(
147
+ candidates[mid].successes,
148
+ candidates[mid].attempts,
149
+ total_attempts,
150
+ ),
151
+ )
152
+ return best_model
153
+
154
+
155
+ def update_scores(
156
+ scores: dict[str, dict[str, ModelScore]],
157
+ model_id: str,
158
+ task_type: str,
159
+ success: bool,
160
+ ) -> dict[str, dict[str, ModelScore]]:
161
+ """
162
+ Immutably update model scores. Returns a new dict — original is unchanged.
163
+ """
164
+ new_scores = copy.deepcopy(scores)
165
+ if model_id not in new_scores:
166
+ new_scores[model_id] = {}
167
+ if task_type not in new_scores[model_id]:
168
+ new_scores[model_id][task_type] = ModelScore()
169
+
170
+ entry = new_scores[model_id][task_type]
171
+ entry.attempts += 1
172
+ if success:
173
+ entry.successes += 1
174
+
175
+ return new_scores
176
+
177
+
178
+ # ── HyperRouter dataclass ──────────────────────────────────────────────────────
179
+
180
+ @dataclass
181
+ class HyperRouter:
182
+ """
183
+ Stateful router that holds live UCB1 scores and routes tasks to
184
+ (agent_id, model_id) pairs.
185
+ """
186
+ agent_scores: dict[str, AgentScore] = field(default_factory=dict)
187
+ model_scores: dict[str, dict[str, ModelScore]] = field(default_factory=dict)
188
+ total_attempts: int = 0
189
+
190
+ def route(
191
+ self,
192
+ task_type: str,
193
+ cost_budget: Optional[float] = None,
194
+ latency_budget: Optional[float] = None,
195
+ ) -> tuple[str, str]:
196
+ """
197
+ Route a task to the best (agent_id, model_id) pair.
198
+ Uses UCB1 for both selections.
199
+ """
200
+ agent_id = select_agent(self.agent_scores, task_type, self.total_attempts)
201
+ model_id = select_model(
202
+ self.model_scores,
203
+ task_type,
204
+ self.total_attempts,
205
+ cost_budget_usd=cost_budget,
206
+ latency_budget_ms=latency_budget,
207
+ )
208
+ return agent_id, model_id
209
+
210
+ def record(self, agent_id: str, model_id: str, task_type: str, success: bool) -> None:
211
+ """Record the outcome of a routing decision and update scores."""
212
+ self.total_attempts += 1
213
+ self.model_scores = update_scores(self.model_scores, model_id, task_type, success)
214
+ if agent_id not in self.agent_scores:
215
+ self.agent_scores[agent_id] = AgentScore(task_type=task_type)
216
+ self.agent_scores[agent_id].attempts += 1
217
+ if success:
218
+ self.agent_scores[agent_id].successes += 1
@@ -0,0 +1,68 @@
1
+ """
2
+ FastLoop — high-frequency routing loop. Routes tasks in <100ms using cached UCB1 scores.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from datetime import datetime, timezone
7
+ from typing import Optional
8
+
9
+ from core.hyperstate.schema import ExperimentEntry, HyperState
10
+ from .bandit import HyperRouter
11
+
12
+
13
+ class FastLoop:
14
+ """
15
+ Fast routing path. Reads live UCB1 scores from HyperState.model_scores
16
+ and routes to the optimal (agent_id, model_id) pair in microseconds.
17
+ """
18
+
19
+ def __init__(self) -> None:
20
+ self._router = HyperRouter()
21
+
22
+ async def route(
23
+ self,
24
+ state: HyperState,
25
+ task_type: str,
26
+ cost_budget: Optional[float] = None,
27
+ latency_budget: Optional[float] = None,
28
+ ) -> tuple[str, str]:
29
+ """
30
+ Route a task using live UCB1 scores from the HyperState.
31
+
32
+ Syncs the router's model_scores from state, routes the task,
33
+ then appends a routing entry to state.experiment_log.
34
+
35
+ Returns:
36
+ (agent_id, model_id)
37
+ """
38
+ # Sync router from state's live scores
39
+ self._router.agent_scores = dict(state.agent_scores)
40
+ self._router.model_scores = {
41
+ model_id: dict(task_map)
42
+ for model_id, task_map in state.model_scores.items()
43
+ }
44
+ self._router.total_attempts = sum(
45
+ score.attempts for score in state.agent_scores.values()
46
+ ) or 1
47
+
48
+ agent_id, model_id = self._router.route(
49
+ task_type=task_type,
50
+ cost_budget=cost_budget,
51
+ latency_budget=latency_budget,
52
+ )
53
+
54
+ # Log routing decision to state experiment log
55
+ routing_entry = ExperimentEntry(
56
+ method=f"fast_loop_route:{task_type}",
57
+ model_used=model_id,
58
+ result=f"routed to agent={agent_id} model={model_id}",
59
+ certified=False,
60
+ test_trace="",
61
+ cost_usd=0.0,
62
+ latency_ms=0.0,
63
+ timestamp=datetime.now(timezone.utc),
64
+ )
65
+ state.experiment_log.append(routing_entry)
66
+ state._bump_version()
67
+
68
+ return agent_id, model_id
@@ -0,0 +1,143 @@
1
+ """
2
+ SlowLoop — background optimization loop. Periodically re-reads SwarmMessage history
3
+ from the DB, rebalances UCB1 scores, and writes routing performance summaries to CausalGraph.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ from datetime import datetime, timezone
11
+ from typing import TYPE_CHECKING
12
+
13
+ import asyncpg
14
+
15
+ from .bandit import HyperRouter, update_scores
16
+ from core.hyperstate.schema import AgentScore, ModelScore
17
+
18
+ if TYPE_CHECKING:
19
+ from memory.causal_graph import CausalGraph
20
+
21
+ log = logging.getLogger("hyperclaw.slow_loop")
22
+
23
+
24
+ class SlowLoop:
25
+ """
26
+ Background optimization loop.
27
+
28
+ Every interval_hours:
29
+ 1. Reads all SwarmMessages from DB since last run
30
+ 2. Recalculates UCB1 scores per agent/model per task_type
31
+ 3. Updates HyperRouter in-place
32
+ 4. Writes routing performance summary to CausalGraph (node_type='outcome')
33
+ 5. Returns summary dict
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ hyper_router: HyperRouter,
39
+ pool: asyncpg.Pool,
40
+ causal_graph: "CausalGraph",
41
+ ) -> None:
42
+ self._router = hyper_router
43
+ self._pool = pool
44
+ self._causal_graph = causal_graph
45
+ self._last_run: datetime | None = None
46
+
47
+ async def run_once(self) -> dict:
48
+ """
49
+ Process SwarmMessages since last run, update router scores.
50
+ Returns summary: {messages_processed, agents_updated, models_updated, timestamp}
51
+ """
52
+ since = self._last_run or datetime(2020, 1, 1, tzinfo=timezone.utc)
53
+
54
+ async with self._pool.acquire() as conn:
55
+ rows = await conn.fetch(
56
+ """
57
+ SELECT agent_id, model_used, task_type, certified
58
+ FROM swarm_messages
59
+ WHERE created_at > $1
60
+ ORDER BY created_at ASC
61
+ """,
62
+ since,
63
+ )
64
+
65
+ agents_seen: set[str] = set()
66
+ models_seen: set[str] = set()
67
+ messages_processed = 0
68
+
69
+ new_agent_scores: dict = dict(self._router.agent_scores)
70
+ new_model_scores: dict = dict(self._router.model_scores)
71
+ new_total = self._router.total_attempts
72
+
73
+ for row in rows:
74
+ agent_id = row["agent_id"]
75
+ model_id = row["model_used"]
76
+ task_type = row["task_type"]
77
+ # Treat certified=True as success; False as failure
78
+ success = bool(row["certified"])
79
+
80
+ # Update agent scores
81
+ if agent_id not in new_agent_scores:
82
+ new_agent_scores[agent_id] = AgentScore(task_type=task_type)
83
+ new_agent_scores[agent_id].attempts += 1
84
+ if success:
85
+ new_agent_scores[agent_id].successes += 1
86
+ agents_seen.add(agent_id)
87
+
88
+ # Update model scores (immutable pattern)
89
+ new_model_scores = update_scores(new_model_scores, model_id, task_type, success)
90
+ models_seen.add(model_id)
91
+ new_total += 1
92
+ messages_processed += 1
93
+
94
+ # Apply updated scores to router
95
+ self._router.agent_scores = new_agent_scores
96
+ self._router.model_scores = new_model_scores
97
+ self._router.total_attempts = new_total
98
+
99
+ now = datetime.now(timezone.utc)
100
+ self._last_run = now
101
+
102
+ summary = {
103
+ "messages_processed": messages_processed,
104
+ "agents_updated": len(agents_seen),
105
+ "models_updated": len(models_seen),
106
+ "timestamp": now.isoformat(),
107
+ }
108
+
109
+ # Write routing performance summary to CausalGraph
110
+ if messages_processed > 0:
111
+ try:
112
+ await self._causal_graph.add_node(
113
+ label=f"routing_optimization: processed {messages_processed} messages",
114
+ node_type="outcome",
115
+ domain="recursive",
116
+ metadata={
117
+ "slow_loop_summary": summary,
118
+ "agents_updated": list(agents_seen),
119
+ "models_updated": list(models_seen),
120
+ },
121
+ )
122
+ except Exception as e:
123
+ log.warning(f"SlowLoop: failed to write CausalGraph summary: {e}")
124
+
125
+ log.info(
126
+ f"SlowLoop run_once: {messages_processed} messages, "
127
+ f"{len(agents_seen)} agents, {len(models_seen)} models updated"
128
+ )
129
+ return summary
130
+
131
+ async def run_forever(self, interval_hours: float = 6.0) -> None:
132
+ """
133
+ Infinite loop — runs run_once() then sleeps interval_hours.
134
+ Catches all exceptions, logs, continues.
135
+ """
136
+ log.info(f"SlowLoop started — interval={interval_hours}h")
137
+ while True:
138
+ try:
139
+ summary = await self.run_once()
140
+ log.info(f"SlowLoop cycle complete: {summary}")
141
+ except Exception as e:
142
+ log.error(f"SlowLoop error (continuing): {e}")
143
+ await asyncio.sleep(interval_hours * 3600)
@@ -0,0 +1,24 @@
1
+ from .schema import (
2
+ HyperState,
3
+ Task,
4
+ Hypothesis,
5
+ ExperimentEntry,
6
+ AgentScore,
7
+ ModelScore,
8
+ CertifiedMethod,
9
+ RecursiveResearch,
10
+ Domain,
11
+ TaskType,
12
+ )
13
+ from .certifier import Certifier, CertificationError
14
+ from .store import HyperStateStore
15
+ from .state_manager import StateManager
16
+
17
+ __all__ = [
18
+ "HyperState", "Task", "Hypothesis", "ExperimentEntry",
19
+ "AgentScore", "ModelScore", "CertifiedMethod", "RecursiveResearch",
20
+ "Domain", "TaskType",
21
+ "Certifier", "CertificationError",
22
+ "HyperStateStore",
23
+ "StateManager",
24
+ ]
@@ -0,0 +1,154 @@
1
+ """
2
+ HyperClaw Certifier — validates ExperimentEntries before promoting to CertifiedMethod.
3
+ When a CausalGraph is provided, writes cause→effect relationships on every successful certification.
4
+ ChatJimmy outputs must always pass through Claude verification before reaching this stage.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import uuid
9
+ from typing import TYPE_CHECKING
10
+ from uuid import UUID
11
+
12
+ from .schema import CertifiedMethod, ExperimentEntry
13
+
14
+ if TYPE_CHECKING:
15
+ from memory.causal_graph import CausalGraph
16
+
17
+
18
+ class CertificationError(Exception):
19
+ """Raised when an ExperimentEntry fails certification criteria."""
20
+ pass
21
+
22
+
23
+ class Certifier:
24
+ """
25
+ Validates ExperimentEntry objects and produces CertifiedMethod records.
26
+ Optionally wires to CausalGraph — every certified method auto-writes a causal edge.
27
+
28
+ Certification rules:
29
+ 1. test_trace must be non-empty
30
+ 2. result must be non-empty
31
+ 3. certified flag must be True on the entry
32
+ """
33
+
34
+ def __init__(self, causal_graph: "CausalGraph | None" = None) -> None:
35
+ self._causal_graph = causal_graph
36
+
37
+ def certify(
38
+ self,
39
+ entry: ExperimentEntry,
40
+ domain: str = "business",
41
+ ) -> CertifiedMethod:
42
+ """
43
+ Validate an ExperimentEntry and return a CertifiedMethod.
44
+ If causal_graph was provided at init, schedules a causal edge write.
45
+
46
+ Raises:
47
+ CertificationError: if test_trace is empty, result is empty,
48
+ or certified flag is not set.
49
+ """
50
+ if not entry.test_trace or not entry.test_trace.strip():
51
+ raise CertificationError(
52
+ f"Certification failed for method '{entry.method}': "
53
+ "test_trace is empty. A non-empty test trace is required."
54
+ )
55
+ if not entry.result or not entry.result.strip():
56
+ raise CertificationError(
57
+ f"Certification failed for method '{entry.method}': "
58
+ "result is empty. A non-empty result is required."
59
+ )
60
+ if not entry.certified:
61
+ raise CertificationError(
62
+ f"Certification failed for method '{entry.method}': "
63
+ "entry.certified is False. Entry must be verified before certification."
64
+ )
65
+
66
+ certified_method = CertifiedMethod(
67
+ method_id=entry.method,
68
+ model_used=entry.model_used,
69
+ performance={
70
+ "cost_usd": entry.cost_usd,
71
+ "latency_ms": entry.latency_ms,
72
+ "test_trace_length": len(entry.test_trace),
73
+ "result_length": len(entry.result),
74
+ },
75
+ )
76
+
77
+ # Write causal edge if CausalGraph is wired in
78
+ if self._causal_graph is not None:
79
+ self._schedule_causal_write(entry, domain, certified_method)
80
+
81
+ return certified_method
82
+
83
+ def _schedule_causal_write(
84
+ self,
85
+ entry: ExperimentEntry,
86
+ domain: str,
87
+ certified_method: CertifiedMethod,
88
+ ) -> None:
89
+ """
90
+ Fire-and-forget coroutine to write causal edge.
91
+ Uses asyncio.ensure_future if an event loop is running,
92
+ otherwise stores for deferred execution.
93
+ """
94
+ import asyncio
95
+
96
+ coro = self._write_causal_edge(entry, domain, certified_method)
97
+ try:
98
+ loop = asyncio.get_running_loop()
99
+ loop.create_task(coro)
100
+ except RuntimeError:
101
+ # No running event loop — skip (sync context, e.g. unit tests)
102
+ pass
103
+
104
+ async def _write_causal_edge(
105
+ self,
106
+ entry: ExperimentEntry,
107
+ domain: str,
108
+ certified_method: CertifiedMethod,
109
+ ) -> None:
110
+ """Async: write cause→effect to CausalGraph."""
111
+ try:
112
+ method_uuid = UUID(certified_method.method_id) if _is_uuid(certified_method.method_id) else uuid.uuid4()
113
+ await self._causal_graph.write_certified_method( # type: ignore[union-attr]
114
+ method_description=entry.method,
115
+ cause_description=f"method: {entry.method}",
116
+ effect_description=f"result: {entry.result}",
117
+ domain=domain,
118
+ confidence=1.0,
119
+ method_id=method_uuid,
120
+ )
121
+ except Exception as e:
122
+ import logging
123
+ logging.getLogger("hyperclaw.certifier").warning(
124
+ f"CausalGraph write failed for method '{entry.method}': {e}"
125
+ )
126
+
127
+ def validate_batch(
128
+ self,
129
+ entries: list[ExperimentEntry],
130
+ domain: str = "business",
131
+ ) -> tuple[list[CertifiedMethod], list[CertificationError]]:
132
+ """
133
+ Attempt to certify a batch of entries. Returns (certified, errors).
134
+ Does not raise — all errors are collected and returned.
135
+ """
136
+ certified: list[CertifiedMethod] = []
137
+ errors: list[CertificationError] = []
138
+
139
+ for entry in entries:
140
+ try:
141
+ cm = self.certify(entry, domain=domain)
142
+ certified.append(cm)
143
+ except CertificationError as e:
144
+ errors.append(e)
145
+
146
+ return certified, errors
147
+
148
+
149
+ def _is_uuid(value: str) -> bool:
150
+ try:
151
+ UUID(value)
152
+ return True
153
+ except (ValueError, AttributeError):
154
+ return False