apsimo 1.3.0__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 (614) hide show
  1. apsimo/__init__.py +38 -0
  2. apsimo/__main__.py +6 -0
  3. apsimo/agent/__init__.py +6 -0
  4. apsimo/agent/client.py +276 -0
  5. apsimo/agent/models.py +46 -0
  6. apsimo/agents/__init__.py +20 -0
  7. apsimo/agents/models.py +264 -0
  8. apsimo/agents/store.py +861 -0
  9. apsimo/agents/websocket.py +522 -0
  10. apsimo/api/__init__.py +1 -0
  11. apsimo/api/auth_telemetry.py +287 -0
  12. apsimo/api/authority.py +1203 -0
  13. apsimo/api/contact_grants.py +347 -0
  14. apsimo/api/middleware.py +483 -0
  15. apsimo/api/routers/__init__.py +1 -0
  16. apsimo/api/routers/commitment_work.py +265 -0
  17. apsimo/api/routers/context_gate.py +123 -0
  18. apsimo/api/routers/executions.py +140 -0
  19. apsimo/api/routers/followup_plans.py +147 -0
  20. apsimo/api/routers/governed_actions.py +162 -0
  21. apsimo/api/routers/host.py +14473 -0
  22. apsimo/api/routers/initiative_work.py +115 -0
  23. apsimo/api/routers/mining.py +104 -0
  24. apsimo/api/routers/observations.py +110 -0
  25. apsimo/api/routers/social_state.py +225 -0
  26. apsimo/api/routers/task_queue.py +2715 -0
  27. apsimo/api/routers/temporal_followups.py +251 -0
  28. apsimo/api/routers/transport.py +110 -0
  29. apsimo/api/routers/transport_ingress_api.py +240 -0
  30. apsimo/api/schemas/__init__.py +1 -0
  31. apsimo/api/schemas/host.py +1949 -0
  32. apsimo/autonomy/cli.py +110 -0
  33. apsimo/autonomy/condition_worker.py +437 -0
  34. apsimo/autonomy/config.py +424 -0
  35. apsimo/autonomy/loop.py +4316 -0
  36. apsimo/autonomy/registry.py +339 -0
  37. apsimo/autonomy/scheduler.py +1822 -0
  38. apsimo/autonomy/synthesis.py +449 -0
  39. apsimo/backup.py +962 -0
  40. apsimo/beliefs/__init__.py +23 -0
  41. apsimo/beliefs/contradictions.py +109 -0
  42. apsimo/beliefs/decay.py +61 -0
  43. apsimo/beliefs/engine.py +479 -0
  44. apsimo/beliefs/models.py +67 -0
  45. apsimo/beliefs/promotion.py +41 -0
  46. apsimo/beliefs/resolve.py +58 -0
  47. apsimo/beliefs/source_claims.py +690 -0
  48. apsimo/beliefs/source_projection.py +883 -0
  49. apsimo/beliefs/source_time.py +208 -0
  50. apsimo/beliefs/store.py +133 -0
  51. apsimo/briefings/aggregators.py +824 -0
  52. apsimo/briefings/composer.py +420 -0
  53. apsimo/briefings/config.py +55 -0
  54. apsimo/briefings/delivery.py +439 -0
  55. apsimo/briefings/engagement.py +97 -0
  56. apsimo/briefings/engine.py +274 -0
  57. apsimo/briefings/enhancer.py +99 -0
  58. apsimo/briefings/models.py +183 -0
  59. apsimo/briefings/scheduler.py +382 -0
  60. apsimo/briefings/store.py +435 -0
  61. apsimo/chain/__init__.py +48 -0
  62. apsimo/chain/block.py +100 -0
  63. apsimo/chain/cli.py +704 -0
  64. apsimo/chain/genesis.py +443 -0
  65. apsimo/chain/identity.py +416 -0
  66. apsimo/chain/keys.py +1025 -0
  67. apsimo/chain/local_keys.py +187 -0
  68. apsimo/chain/manager.py +290 -0
  69. apsimo/chain/node.py +163 -0
  70. apsimo/chain/plugin_transactions.py +371 -0
  71. apsimo/chain/protocol.py +220 -0
  72. apsimo/chain/state_machine.py +676 -0
  73. apsimo/chain/storage.py +503 -0
  74. apsimo/chain/transactions.py +250 -0
  75. apsimo/chain/validation.py +397 -0
  76. apsimo/channels/__init__.py +1 -0
  77. apsimo/channels/manifest.py +31 -0
  78. apsimo/channels/migrations/001_channels_schema.sql +12 -0
  79. apsimo/channels/phone_gateways.py +42 -0
  80. apsimo/channels/presence.py +188 -0
  81. apsimo/channels/router.py +235 -0
  82. apsimo/channels/store.py +231 -0
  83. apsimo/cli.py +2688 -0
  84. apsimo/cognition/__init__.py +11 -0
  85. apsimo/cognition/charter.py +398 -0
  86. apsimo/cognition/drive_governance.py +3530 -0
  87. apsimo/cognition/evidence_pipeline.py +1627 -0
  88. apsimo/cognition/external_events.py +932 -0
  89. apsimo/cognition/goal_spine.py +3488 -0
  90. apsimo/cognition/introspection.py +214 -0
  91. apsimo/cognition/prompt.py +150 -0
  92. apsimo/cognition/runtime.py +108 -0
  93. apsimo/cognition/trigger.py +154 -0
  94. apsimo/commitments/__init__.py +18 -0
  95. apsimo/commitments/local_work.py +355 -0
  96. apsimo/commitments/store.py +1052 -0
  97. apsimo/commitments/work.py +91 -0
  98. apsimo/compat.py +53 -0
  99. apsimo/compression/__init__.py +467 -0
  100. apsimo/connectors/__init__.py +21 -0
  101. apsimo/connectors/base.py +152 -0
  102. apsimo/connectors/caldav_calendar.py +125 -0
  103. apsimo/connectors/fs_documents.py +85 -0
  104. apsimo/connectors/imap_email.py +138 -0
  105. apsimo/connectors/manager.py +218 -0
  106. apsimo/connectors/webhook_pull.py +88 -0
  107. apsimo/contacts/__init__.py +33 -0
  108. apsimo/contacts/comms.py +357 -0
  109. apsimo/contacts/config.py +79 -0
  110. apsimo/contacts/exporters/__init__.py +1 -0
  111. apsimo/contacts/exporters/vcard.py +71 -0
  112. apsimo/contacts/identity_links.py +251 -0
  113. apsimo/contacts/importer.py +280 -0
  114. apsimo/contacts/importers/__init__.py +1 -0
  115. apsimo/contacts/importers/batch.py +43 -0
  116. apsimo/contacts/importers/macos_contacts.py +101 -0
  117. apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
  118. apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
  119. apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
  120. apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
  121. apsimo/contacts/migrations/005_identity_links.sql +27 -0
  122. apsimo/contacts/models.py +308 -0
  123. apsimo/contacts/scoring.py +16 -0
  124. apsimo/contacts/store.py +1623 -0
  125. apsimo/contacts/transport_ingress.py +252 -0
  126. apsimo/contacts/world_bridge.py +314 -0
  127. apsimo/contextgate/__init__.py +69 -0
  128. apsimo/contextgate/chunker.py +169 -0
  129. apsimo/contextgate/estimate.py +54 -0
  130. apsimo/contextgate/gate.py +313 -0
  131. apsimo/contextgate/retrieve.py +115 -0
  132. apsimo/delivery/__init__.py +16 -0
  133. apsimo/delivery/bridge.py +1260 -0
  134. apsimo/delivery/channels.py +526 -0
  135. apsimo/delivery/classification.py +50 -0
  136. apsimo/delivery/rate_limiter.py +268 -0
  137. apsimo/delivery/reachout_policy.py +206 -0
  138. apsimo/directed/__init__.py +22 -0
  139. apsimo/directed/audit.py +167 -0
  140. apsimo/directed/intake.py +95 -0
  141. apsimo/directed/models.py +191 -0
  142. apsimo/directed/service.py +509 -0
  143. apsimo/directives/__init__.py +25 -0
  144. apsimo/directives/evidence.py +87 -0
  145. apsimo/directives/extractor.py +188 -0
  146. apsimo/directives/guard.py +364 -0
  147. apsimo/directives/models.py +206 -0
  148. apsimo/directives/service.py +372 -0
  149. apsimo/directives/store.py +167 -0
  150. apsimo/doctor.py +2173 -0
  151. apsimo/environment.py +43 -0
  152. apsimo/events/__init__.py +33 -0
  153. apsimo/events/broadcaster.py +98 -0
  154. apsimo/events/bus.py +217 -0
  155. apsimo/events/journal.py +863 -0
  156. apsimo/events/stream.py +131 -0
  157. apsimo/events/types.py +150 -0
  158. apsimo/execution_results.py +357 -0
  159. apsimo/feedback/__init__.py +5 -0
  160. apsimo/feedback/store.py +76 -0
  161. apsimo/feeds/__init__.py +19 -0
  162. apsimo/feeds/cli.py +84 -0
  163. apsimo/feeds/engine.py +437 -0
  164. apsimo/feeds/example-feed.yaml +77 -0
  165. apsimo/feeds/hermes_cron.py +126 -0
  166. apsimo/feeds/manager.py +235 -0
  167. apsimo/feeds/spec.py +250 -0
  168. apsimo/feeds/template.py +202 -0
  169. apsimo/gate/__init__.py +18 -0
  170. apsimo/gate/audit.py +61 -0
  171. apsimo/gate/communication_policy.py +166 -0
  172. apsimo/gate/config.py +72 -0
  173. apsimo/gate/context_provenance.py +170 -0
  174. apsimo/gate/env_risk.py +226 -0
  175. apsimo/gate/guard_audit.py +353 -0
  176. apsimo/gate/layers/__init__.py +1 -0
  177. apsimo/gate/layers/base.py +15 -0
  178. apsimo/gate/layers/l1_recipient.py +66 -0
  179. apsimo/gate/layers/l2_pii.py +134 -0
  180. apsimo/gate/layers/l3_cross_context.py +50 -0
  181. apsimo/gate/layers/l4_trust_tier.py +78 -0
  182. apsimo/gate/layers/l5_injection.py +199 -0
  183. apsimo/gate/layers/l6_review.py +86 -0
  184. apsimo/gate/layers/l7_delay.py +100 -0
  185. apsimo/gate/layers/tom2_epistemic.py +185 -0
  186. apsimo/gate/models.py +64 -0
  187. apsimo/gate/pending_dispatch.py +5 -0
  188. apsimo/gate/pipeline.py +206 -0
  189. apsimo/gate/rejection.py +259 -0
  190. apsimo/gate/response_guard.py +700 -0
  191. apsimo/gate/rulesets/injection_v1.yaml +51 -0
  192. apsimo/gate/surface_policy.py +189 -0
  193. apsimo/gate/taint.py +226 -0
  194. apsimo/genesis.json +9 -0
  195. apsimo/goals/__init__.py +100 -0
  196. apsimo/goals/config.py +38 -0
  197. apsimo/goals/decomposer.py +421 -0
  198. apsimo/goals/engine.py +617 -0
  199. apsimo/goals/inference.py +354 -0
  200. apsimo/goals/models.py +302 -0
  201. apsimo/goals/priority.py +270 -0
  202. apsimo/goals/queue_bridge.py +149 -0
  203. apsimo/goals/replan.py +450 -0
  204. apsimo/goals/schema.sql +89 -0
  205. apsimo/goals/store.py +692 -0
  206. apsimo/governed_actions.py +1708 -0
  207. apsimo/harness_integration/__init__.py +45 -0
  208. apsimo/harness_integration/context.py +41 -0
  209. apsimo/harness_integration/skills.py +231 -0
  210. apsimo/identity/__init__.py +26 -0
  211. apsimo/identity/participants.py +181 -0
  212. apsimo/identity/resolver.py +329 -0
  213. apsimo/identity_bootstrap/__init__.py +5 -0
  214. apsimo/identity_bootstrap/builder.py +208 -0
  215. apsimo/identity_bootstrap/corpus.py +443 -0
  216. apsimo/identity_bootstrap/models.py +54 -0
  217. apsimo/identity_bootstrap/runner.py +353 -0
  218. apsimo/identity_bootstrap/seeders/__init__.py +25 -0
  219. apsimo/identity_bootstrap/seeders/briefings.py +109 -0
  220. apsimo/identity_bootstrap/seeders/chain.py +57 -0
  221. apsimo/identity_bootstrap/seeders/goals.py +128 -0
  222. apsimo/identity_bootstrap/seeders/memory.py +191 -0
  223. apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
  224. apsimo/identity_bootstrap/seeders/relationship.py +152 -0
  225. apsimo/identity_bootstrap/seeders/sessions.py +67 -0
  226. apsimo/identity_bootstrap/seeders/skills.py +92 -0
  227. apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
  228. apsimo/identity_bootstrap/seeders/world_model.py +143 -0
  229. apsimo/identity_bootstrap/self_query.py +92 -0
  230. apsimo/identity_bootstrap/self_reflection.py +155 -0
  231. apsimo/identity_bootstrap/skill.py +37 -0
  232. apsimo/identity_bootstrap/verifier.py +436 -0
  233. apsimo/initiatives/__init__.py +20 -0
  234. apsimo/initiatives/action_registry.py +454 -0
  235. apsimo/initiatives/approval_authority.py +2105 -0
  236. apsimo/initiatives/approval_policy.py +123 -0
  237. apsimo/initiatives/assignment.py +263 -0
  238. apsimo/initiatives/backup_evidence.py +100 -0
  239. apsimo/initiatives/context_freshness.py +103 -0
  240. apsimo/initiatives/models.py +318 -0
  241. apsimo/initiatives/native_work.py +270 -0
  242. apsimo/initiatives/standing_approvals.py +232 -0
  243. apsimo/initiatives/store.py +1081 -0
  244. apsimo/initiatives/temporal_followup.py +410 -0
  245. apsimo/intelligence/__init__.py +1 -0
  246. apsimo/intelligence/cognition/__init__.py +24 -0
  247. apsimo/intelligence/cognition/gap_detector.py +148 -0
  248. apsimo/intelligence/cognition/metalearner.py +547 -0
  249. apsimo/intelligence/cognition/metrics_collector.py +217 -0
  250. apsimo/intelligence/cognition/performance_index.py +299 -0
  251. apsimo/intelligence/cognition/registry.py +192 -0
  252. apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
  253. apsimo/intelligence/cognition/types.py +16 -0
  254. apsimo/intelligence/components/__init__.py +66 -0
  255. apsimo/intelligence/components/anomaly_detector.py +413 -0
  256. apsimo/intelligence/components/initiative_engine.py +2643 -0
  257. apsimo/intelligence/components/preference_learner.py +521 -0
  258. apsimo/intelligence/components/research_orchestrator.py +358 -0
  259. apsimo/intelligence/components/self_directed_thinker.py +221 -0
  260. apsimo/intelligence/components/self_reflector.py +252 -0
  261. apsimo/intelligence/components/session_continuity.py +154 -0
  262. apsimo/intelligence/components/task_planner.py +320 -0
  263. apsimo/intelligence/components/tool_learner.py +217 -0
  264. apsimo/intelligence/graph/__init__.py +79 -0
  265. apsimo/intelligence/graph/client.py +2483 -0
  266. apsimo/intelligence/graph/consolidator.py +405 -0
  267. apsimo/intelligence/graph/distiller.py +312 -0
  268. apsimo/intelligence/graph/migrations.py +129 -0
  269. apsimo/intelligence/graph/queries.py +248 -0
  270. apsimo/intelligence/graph/recall.py +281 -0
  271. apsimo/intelligence/graph/reconciler.py +144 -0
  272. apsimo/intelligence/graph/schema.py +337 -0
  273. apsimo/intelligence/graph/selection.py +252 -0
  274. apsimo/intelligence/learning/__init__.py +17 -0
  275. apsimo/intelligence/learning/continuous_learner.py +245 -0
  276. apsimo/intelligence/learning/feedback_store.py +321 -0
  277. apsimo/intelligence/mind_model/__init__.py +1 -0
  278. apsimo/intelligence/mind_model/graph_baseline.py +136 -0
  279. apsimo/intelligence/mind_model/signal_collector.py +361 -0
  280. apsimo/intelligence/relationships/__init__.py +11 -0
  281. apsimo/intelligence/relationships/profiler.py +389 -0
  282. apsimo/intelligence/relationships/scorer.py +560 -0
  283. apsimo/intelligence/relationships/signal_floor.py +66 -0
  284. apsimo/intelligence/relationships/trust_tiers.py +300 -0
  285. apsimo/intelligence/synthesis/__init__.py +40 -0
  286. apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
  287. apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
  288. apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
  289. apsimo/intelligence/synthesis/insight_store.py +79 -0
  290. apsimo/intelligence/synthesis/insight_validator.py +183 -0
  291. apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
  292. apsimo/intelligence/turn_middleware/__init__.py +15 -0
  293. apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
  294. apsimo/mcp/__init__.py +41 -0
  295. apsimo/mcp/__main__.py +6 -0
  296. apsimo/mcp/config.py +287 -0
  297. apsimo/mcp/server.py +501 -0
  298. apsimo/migrations.py +187 -0
  299. apsimo/mining/__init__.py +27 -0
  300. apsimo/mining/corpus.py +239 -0
  301. apsimo/mining/escalations.py +289 -0
  302. apsimo/mining/models.py +169 -0
  303. apsimo/mining/store.py +210 -0
  304. apsimo/models/__init__.py +30 -0
  305. apsimo/models/memory.py +80 -0
  306. apsimo/models/mesh.py +72 -0
  307. apsimo/models/person.py +104 -0
  308. apsimo/models/signal.py +108 -0
  309. apsimo/observations/__init__.py +15 -0
  310. apsimo/observations/store.py +277 -0
  311. apsimo/patterns/__init__.py +6 -0
  312. apsimo/patterns/extract.py +187 -0
  313. apsimo/patterns/store.py +227 -0
  314. apsimo/persona/__init__.py +1 -0
  315. apsimo/persona/engine.py +611 -0
  316. apsimo/persona/manifest.py +140 -0
  317. apsimo/projects/__init__.py +28 -0
  318. apsimo/projects/engine.py +1681 -0
  319. apsimo/projects/event_outbox.py +188 -0
  320. apsimo/projects/models.py +216 -0
  321. apsimo/projects/planner.py +181 -0
  322. apsimo/projects/store.py +1446 -0
  323. apsimo/proposals/__init__.py +12 -0
  324. apsimo/proposals/engine.py +114 -0
  325. apsimo/proposals/models.py +207 -0
  326. apsimo/qualification/__init__.py +1 -0
  327. apsimo/qualification/cases.py +75 -0
  328. apsimo/qualification/cli.py +51 -0
  329. apsimo/qualification/memory_cases.py +209 -0
  330. apsimo/qualification/records.py +92 -0
  331. apsimo/qualification/report.py +87 -0
  332. apsimo/qualification/runner.py +311 -0
  333. apsimo/qualification/structured_cases.py +131 -0
  334. apsimo/reasoning/__init__.py +13 -0
  335. apsimo/reasoning/executor.py +506 -0
  336. apsimo/reasoning/loop.py +373 -0
  337. apsimo/reasoning/native_tools/__init__.py +16 -0
  338. apsimo/reasoning/native_tools/calculate.py +141 -0
  339. apsimo/reasoning/native_tools/file_ops.py +150 -0
  340. apsimo/reasoning/native_tools/web_search.py +49 -0
  341. apsimo/reasoning/tool_policy.py +182 -0
  342. apsimo/redact/__init__.py +176 -0
  343. apsimo/repos/__init__.py +5 -0
  344. apsimo/repos/mirrors.py +204 -0
  345. apsimo/research/__init__.py +41 -0
  346. apsimo/research/artifact.py +482 -0
  347. apsimo/research/gatherer.py +387 -0
  348. apsimo/research/pipeline.py +513 -0
  349. apsimo/research/search/__init__.py +7 -0
  350. apsimo/research/search/base.py +41 -0
  351. apsimo/research/search/brave.py +59 -0
  352. apsimo/research/search/cache.py +51 -0
  353. apsimo/research/search/duckduckgo.py +103 -0
  354. apsimo/research/search/orchestrator.py +119 -0
  355. apsimo/research/search/serpapi.py +59 -0
  356. apsimo/research/search/tavily.py +59 -0
  357. apsimo/research/synthesizer.py +309 -0
  358. apsimo/router/__init__.py +30 -0
  359. apsimo/router/complexity_scorer.py +148 -0
  360. apsimo/router/endpoints.py +153 -0
  361. apsimo/router/fallback.py +58 -0
  362. apsimo/router/functions.py +243 -0
  363. apsimo/router/native_policy.py +52 -0
  364. apsimo/router/router.py +762 -0
  365. apsimo/router/self_learning.py +174 -0
  366. apsimo/router/tiers.py +677 -0
  367. apsimo/sandbox/__init__.py +21 -0
  368. apsimo/sandbox/backend.py +195 -0
  369. apsimo/sandbox/manager.py +173 -0
  370. apsimo/scope_bounds.py +7 -0
  371. apsimo/secrets/__init__.py +6 -0
  372. apsimo/secrets/backends/__init__.py +8 -0
  373. apsimo/secrets/backends/base.py +42 -0
  374. apsimo/secrets/backends/env.py +110 -0
  375. apsimo/secrets/backends/keyring.py +72 -0
  376. apsimo/secrets/backends/onepassword.py +232 -0
  377. apsimo/secrets/cli.py +191 -0
  378. apsimo/secrets/manager.py +160 -0
  379. apsimo/secrets/migration.py +101 -0
  380. apsimo/secrets/types.py +98 -0
  381. apsimo/seed.py +41 -0
  382. apsimo/self_model/__init__.py +37 -0
  383. apsimo/self_model/appraisals.py +673 -0
  384. apsimo/self_model/benchmark.py +1314 -0
  385. apsimo/self_model/brief.py +40 -0
  386. apsimo/self_model/event_concerns.py +1128 -0
  387. apsimo/self_model/execution_forecasts.py +353 -0
  388. apsimo/self_model/expectations.py +1595 -0
  389. apsimo/self_model/experiments.py +1150 -0
  390. apsimo/self_model/journal.py +148 -0
  391. apsimo/self_model/judgments.py +705 -0
  392. apsimo/self_model/native_outcomes.py +55 -0
  393. apsimo/self_model/params.py +220 -0
  394. apsimo/self_model/perspective.py +246 -0
  395. apsimo/self_model/reconcile.py +183 -0
  396. apsimo/self_model/reply_forecasts.py +381 -0
  397. apsimo/self_model/runtime_forecasts.py +296 -0
  398. apsimo/self_model/runtime_models.py +67 -0
  399. apsimo/self_model/settlement.py +207 -0
  400. apsimo/self_model/situation.py +1731 -0
  401. apsimo/self_model/store.py +883 -0
  402. apsimo/self_model/supervised.py +137 -0
  403. apsimo/self_model/thinker.py +99 -0
  404. apsimo/self_model/trust.py +388 -0
  405. apsimo/self_model/workspace.py +2388 -0
  406. apsimo/server.py +4197 -0
  407. apsimo/services/__init__.py +1 -0
  408. apsimo/services/agent_bridge.py +474 -0
  409. apsimo/services/initiative_executor.py +914 -0
  410. apsimo/services/instance.py +297 -0
  411. apsimo/sessions/__init__.py +22 -0
  412. apsimo/sessions/config.py +13 -0
  413. apsimo/sessions/context_loader.py +88 -0
  414. apsimo/sessions/federation_session.py +75 -0
  415. apsimo/sessions/isolated_session.py +98 -0
  416. apsimo/sessions/reports.py +84 -0
  417. apsimo/sessions/store.py +148 -0
  418. apsimo/setup.py +2818 -0
  419. apsimo/setup_hermes.py +879 -0
  420. apsimo/setup_local_work.py +218 -0
  421. apsimo/setup_native_goals.py +134 -0
  422. apsimo/setup_native_reviews.py +115 -0
  423. apsimo/skills/__init__.py +10 -0
  424. apsimo/skills/base.py +108 -0
  425. apsimo/skills/budget.py +28 -0
  426. apsimo/skills/executor.py +493 -0
  427. apsimo/skills/executors/__init__.py +1 -0
  428. apsimo/skills/executors/behavioral_correction.py +75 -0
  429. apsimo/skills/executors/capability_gap.py +38 -0
  430. apsimo/skills/executors/data_quality.py +163 -0
  431. apsimo/skills/executors/knowledge_acquisition.py +41 -0
  432. apsimo/skills/executors/operational_hygiene.py +185 -0
  433. apsimo/skills/executors/subsystem_health.py +169 -0
  434. apsimo/skills/hermes_export.py +431 -0
  435. apsimo/skills/index.py +123 -0
  436. apsimo/skills/learning/__init__.py +21 -0
  437. apsimo/skills/learning/novelty_detector.py +206 -0
  438. apsimo/skills/learning/pattern_extractor.py +199 -0
  439. apsimo/skills/learning/triggers.py +159 -0
  440. apsimo/skills/loader.py +246 -0
  441. apsimo/skills/migrations/002_progressive_loading.sql +6 -0
  442. apsimo/skills/migrations/backfill_triggers.py +20 -0
  443. apsimo/skills/models.py +202 -0
  444. apsimo/skills/packager.py +128 -0
  445. apsimo/skills/protocols.py +70 -0
  446. apsimo/skills/registry.py +191 -0
  447. apsimo/skills/runtime.py +58 -0
  448. apsimo/skills/sandbox_runner.py +229 -0
  449. apsimo/skills/scheduler.py +129 -0
  450. apsimo/skills/schema.py +79 -0
  451. apsimo/skills/security/__init__.py +12 -0
  452. apsimo/skills/security/guards.py +53 -0
  453. apsimo/skills/security/scanner.py +223 -0
  454. apsimo/skills_memory/__init__.py +26 -0
  455. apsimo/skills_memory/distill.py +159 -0
  456. apsimo/skills_memory/models.py +85 -0
  457. apsimo/skills_memory/retrieve.py +62 -0
  458. apsimo/skills_memory/store.py +172 -0
  459. apsimo/surprise/__init__.py +6 -0
  460. apsimo/surprise/accumulation.py +57 -0
  461. apsimo/surprise/scorer.py +102 -0
  462. apsimo/surprise/store.py +203 -0
  463. apsimo/task_queue/__init__.py +69 -0
  464. apsimo/task_queue/action_receipts.py +148 -0
  465. apsimo/task_queue/approval_relay_canary.py +108 -0
  466. apsimo/task_queue/config.py +85 -0
  467. apsimo/task_queue/contract.py +361 -0
  468. apsimo/task_queue/events.py +130 -0
  469. apsimo/task_queue/governor.py +1031 -0
  470. apsimo/task_queue/handlers/__init__.py +16 -0
  471. apsimo/task_queue/handlers/base.py +37 -0
  472. apsimo/task_queue/handlers/inference.py +640 -0
  473. apsimo/task_queue/handlers/monitoring.py +116 -0
  474. apsimo/task_queue/handlers/registry.py +75 -0
  475. apsimo/task_queue/handlers/subtask_handler.py +173 -0
  476. apsimo/task_queue/handlers/system_maintenance.py +147 -0
  477. apsimo/task_queue/mesh_integration.py +111 -0
  478. apsimo/task_queue/models.py +317 -0
  479. apsimo/task_queue/queue_manager.py +8286 -0
  480. apsimo/task_queue/routing.py +287 -0
  481. apsimo/task_queue/scheduler.py +252 -0
  482. apsimo/task_queue/schema.sql +197 -0
  483. apsimo/task_queue/work_control.py +342 -0
  484. apsimo/task_queue/worker.py +993 -0
  485. apsimo/telemetry.py +145 -0
  486. apsimo/tom/__init__.py +6 -0
  487. apsimo/tom/affect.py +387 -0
  488. apsimo/tom/approvals.py +171 -0
  489. apsimo/tom/arcs.py +896 -0
  490. apsimo/tom/asymmetry.py +131 -0
  491. apsimo/tom/eligibility.py +248 -0
  492. apsimo/tom/engagement.py +214 -0
  493. apsimo/tom/exposure.py +214 -0
  494. apsimo/tom/extractor.py +306 -0
  495. apsimo/tom/fact_adapters.py +144 -0
  496. apsimo/tom/facts.py +326 -0
  497. apsimo/tom/integration.py +592 -0
  498. apsimo/tom/leveled.py +118 -0
  499. apsimo/tom/levels.py +247 -0
  500. apsimo/tom/recipient_audit.py +995 -0
  501. apsimo/tom/recipient_simulator.py +593 -0
  502. apsimo/tom/source_lineage.py +93 -0
  503. apsimo/tom/tom2.py +277 -0
  504. apsimo/tom/visibility.py +559 -0
  505. apsimo/tom/visibility_store.py +414 -0
  506. apsimo/tools/__init__.py +0 -0
  507. apsimo/tools/definitions.py +740 -0
  508. apsimo/tools/handlers.py +943 -0
  509. apsimo/toolsmith/__init__.py +26 -0
  510. apsimo/toolsmith/authority.py +166 -0
  511. apsimo/toolsmith/engine.py +559 -0
  512. apsimo/toolsmith/integrity.py +100 -0
  513. apsimo/toolsmith/miner.py +145 -0
  514. apsimo/toolsmith/policy.py +110 -0
  515. apsimo/toolsmith/registry.py +635 -0
  516. apsimo/turns/__init__.py +17 -0
  517. apsimo/turns/audio.py +134 -0
  518. apsimo/turns/documents.py +235 -0
  519. apsimo/turns/executions.py +486 -0
  520. apsimo/turns/hermes_history.py +245 -0
  521. apsimo/turns/hermes_kanban.py +268 -0
  522. apsimo/turns/hermes_work.py +96 -0
  523. apsimo/turns/idempotency.py +752 -0
  524. apsimo/turns/local_work.py +115 -0
  525. apsimo/turns/media.py +581 -0
  526. apsimo/turns/reported_workers.py +196 -0
  527. apsimo/turns/source_annotations.py +283 -0
  528. apsimo/turns/source_attribution.py +154 -0
  529. apsimo/turns/source_read.py +351 -0
  530. apsimo/turns/source_vectors.py +263 -0
  531. apsimo/turns/video.py +210 -0
  532. apsimo/util/autonomy_preset.py +220 -0
  533. apsimo/util/instance.py +92 -0
  534. apsimo/util/model_output.py +25 -0
  535. apsimo/util/quiet_hours.py +27 -0
  536. apsimo/util/session_safety.py +37 -0
  537. apsimo/util/temporal.py +343 -0
  538. apsimo/vector/__init__.py +75 -0
  539. apsimo/vector/backfill.py +171 -0
  540. apsimo/vector/caption.py +114 -0
  541. apsimo/vector/collections.py +51 -0
  542. apsimo/vector/config.py +102 -0
  543. apsimo/vector/embedder.py +670 -0
  544. apsimo/vector/image_preprocess.py +406 -0
  545. apsimo/vector/image_store.py +296 -0
  546. apsimo/vector/indexes.py +162 -0
  547. apsimo/vector/migrate.py +334 -0
  548. apsimo/vector/multimodal_provider.py +417 -0
  549. apsimo/vector/multimodal_types.py +87 -0
  550. apsimo/vector/openai_provider.py +119 -0
  551. apsimo/vector/query.py +49 -0
  552. apsimo/vector/reranker.py +565 -0
  553. apsimo/vector/safety_image.py +159 -0
  554. apsimo/vector/scanner.py +197 -0
  555. apsimo/vector/setup.py +289 -0
  556. apsimo/vector/store.py +533 -0
  557. apsimo/vector/tiers.py +263 -0
  558. apsimo/work_orders.py +925 -0
  559. apsimo/workers/__init__.py +21 -0
  560. apsimo/workers/agent_bridge.py +640 -0
  561. apsimo/workers/colony_worker.py +382 -0
  562. apsimo/workers/queue_worker.py +441 -0
  563. apsimo/workers/skills_sync.py +152 -0
  564. apsimo/world_model/__init__.py +71 -0
  565. apsimo/world_model/causal_maintenance.py +131 -0
  566. apsimo/world_model/causal_policy.py +43 -0
  567. apsimo/world_model/causal_query.py +125 -0
  568. apsimo/world_model/confidence.py +54 -0
  569. apsimo/world_model/config.py +64 -0
  570. apsimo/world_model/constants.py +97 -0
  571. apsimo/world_model/entities.py +145 -0
  572. apsimo/world_model/expectation_resolvers.py +177 -0
  573. apsimo/world_model/extraction/__init__.py +7 -0
  574. apsimo/world_model/extraction/base.py +62 -0
  575. apsimo/world_model/extraction/conversation_extractor.py +262 -0
  576. apsimo/world_model/extraction/detector.py +74 -0
  577. apsimo/world_model/extraction/document_extractor.py +78 -0
  578. apsimo/world_model/extraction/formats/__init__.py +24 -0
  579. apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
  580. apsimo/world_model/extraction/formats/html_fmt.py +72 -0
  581. apsimo/world_model/extraction/formats/json_fmt.py +68 -0
  582. apsimo/world_model/extraction/formats/pdf.py +43 -0
  583. apsimo/world_model/extraction/formats/text.py +27 -0
  584. apsimo/world_model/extraction/llm_extractor.py +164 -0
  585. apsimo/world_model/extraction/pipeline.py +73 -0
  586. apsimo/world_model/integrations/__init__.py +5 -0
  587. apsimo/world_model/integrations/mind_model_bridge.py +115 -0
  588. apsimo/world_model/integrations/social_intel_bridge.py +120 -0
  589. apsimo/world_model/jobs/__init__.py +4 -0
  590. apsimo/world_model/jobs/extraction_job.py +168 -0
  591. apsimo/world_model/llm_extract.py +572 -0
  592. apsimo/world_model/neo4j/__init__.py +5 -0
  593. apsimo/world_model/neo4j/backend.py +654 -0
  594. apsimo/world_model/observations.py +155 -0
  595. apsimo/world_model/populator.py +307 -0
  596. apsimo/world_model/postgres/__init__.py +1 -0
  597. apsimo/world_model/postgres/backend.py +683 -0
  598. apsimo/world_model/relationships.py +25 -0
  599. apsimo/world_model/resolution/__init__.py +13 -0
  600. apsimo/world_model/resolution/entity_resolver.py +232 -0
  601. apsimo/world_model/resolution/merge_audit.py +16 -0
  602. apsimo/world_model/resolution/merge_workflow.py +117 -0
  603. apsimo/world_model/source_reports.py +121 -0
  604. apsimo/world_model/sqlite/__init__.py +4 -0
  605. apsimo/world_model/sqlite/backend.py +855 -0
  606. apsimo/world_model/sqlite/schema.sql +132 -0
  607. apsimo/world_model/store.py +545 -0
  608. apsimo-1.3.0.dist-info/METADATA +78 -0
  609. apsimo-1.3.0.dist-info/RECORD +614 -0
  610. apsimo-1.3.0.dist-info/WHEEL +5 -0
  611. apsimo-1.3.0.dist-info/entry_points.txt +11 -0
  612. apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
  613. apsimo-1.3.0.dist-info/top_level.txt +2 -0
  614. colony_sidecar/__init__.py +4 -0
@@ -0,0 +1,522 @@
1
+ """WebSocket manager for remote agent connections.
2
+
3
+ Provides:
4
+ - Challenge-response authentication
5
+ - Connection management
6
+ - Initiative delivery
7
+ - Ping/pong timeout
8
+ - Circuit breaker integration
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import logging
14
+ import time
15
+ from collections import defaultdict
16
+ from datetime import datetime, timedelta, timezone
17
+ from typing import Any, Callable, Dict, List, Optional, Set
18
+
19
+ from fastapi import WebSocket, WebSocketDisconnect
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class WebSocketManager:
25
+ """Manages WebSocket connections for remote agents."""
26
+
27
+ # Timeouts
28
+ AUTH_TIMEOUT = 30 # Seconds to complete auth
29
+ SESSION_TIMEOUT = timedelta(hours=24) # Re-auth after 24h
30
+ PING_INTERVAL = 30 # Seconds between pings
31
+ PONG_TIMEOUT = 10 # Seconds to wait for pong
32
+ ACK_TIMEOUT = 30 # Seconds to wait for initiative ACK
33
+ MAX_TIMESTAMP_SKEW = 300 # 5 minutes
34
+
35
+ # Limits
36
+ MAX_CONNECTIONS = 100
37
+ MAX_MESSAGE_SIZE = 1024 * 1024 # 1 MB
38
+ MAX_CONNECT_ATTEMPTS = 5
39
+ ATTEMPT_WINDOW = timedelta(minutes=1)
40
+
41
+ def __init__(
42
+ self,
43
+ agent_store: Any,
44
+ initiative_store: Any,
45
+ circuit_breaker: Optional[Any] = None,
46
+ dead_letter_queue: Optional[Any] = None,
47
+ ):
48
+ self._agent_store = agent_store
49
+ self._initiative_store = initiative_store
50
+ self._circuit_breaker = circuit_breaker
51
+ self._dlq = dead_letter_queue
52
+
53
+ # Active connections
54
+ self._active_connections: Dict[str, WebSocket] = {}
55
+ self._connection_info: Dict[str, Dict[str, Any]] = {}
56
+
57
+ # Rate limiting
58
+ self._connect_attempts: Dict[str, List[float]] = defaultdict(list)
59
+
60
+ # Pending auth challenges
61
+ self._pending_challenges: Dict[str, str] = {}
62
+
63
+ # Pending ACKs
64
+ self._pending_acks: Dict[str, asyncio.Future] = {}
65
+
66
+ # Message sequencing
67
+ self._seq: Dict[str, int] = defaultdict(int)
68
+
69
+ # Running tasks
70
+ self._tasks: Set[asyncio.Task] = set()
71
+
72
+ # ------------------------------------------------------------------
73
+ # Connection Handling
74
+ # ------------------------------------------------------------------
75
+
76
+ async def handle_connection(
77
+ self,
78
+ websocket: WebSocket,
79
+ agent_id: str,
80
+ client_ip: str,
81
+ ) -> None:
82
+ """Handle WebSocket connection from remote agent."""
83
+ # Check max connections
84
+ if len(self._active_connections) >= self.MAX_CONNECTIONS:
85
+ await websocket.close(code=1013, reason="Max connections reached")
86
+ return
87
+
88
+ # Rate limit check
89
+ if not await self._check_rate_limit(client_ip):
90
+ await websocket.close(code=4003, reason="Rate limited")
91
+ return
92
+
93
+ # Get agent
94
+ agent = await self._agent_store.get(agent_id)
95
+ if not agent:
96
+ await websocket.close(code=4004, reason="Agent not found")
97
+ return
98
+
99
+ # Check if revoked
100
+ if self._agent_store.is_revoked(agent.node_id):
101
+ await websocket.close(code=4003, reason="Agent revoked")
102
+ return
103
+
104
+ # Check if can reconnect
105
+ if not agent.status.can_reconnect():
106
+ await websocket.close(code=4003, reason=f"Agent status: {agent.status}")
107
+ return
108
+
109
+ # Accept connection
110
+ await websocket.accept()
111
+
112
+ try:
113
+ # Authenticate
114
+ if not await self._authenticate(websocket, agent, client_ip):
115
+ return
116
+
117
+ # Register connection
118
+ self._active_connections[agent_id] = websocket
119
+ self._connection_info[agent_id] = {
120
+ "connected_at": datetime.now(timezone.utc),
121
+ "client_ip": client_ip,
122
+ "last_pong": time.time(),
123
+ }
124
+
125
+ # Mark agent online
126
+ await self._agent_store.set_online(
127
+ agent_id,
128
+ websocket_connected=True,
129
+ metadata={"last_connection_ip": client_ip},
130
+ )
131
+
132
+ logger.info("Agent %s connected from %s", agent_id, client_ip)
133
+
134
+ # Start ping task
135
+ ping_task = asyncio.create_task(
136
+ self._ping_task(agent_id, websocket)
137
+ )
138
+ self._tasks.add(ping_task)
139
+
140
+ # Message loop
141
+ await self._message_loop(agent_id, websocket)
142
+
143
+ except WebSocketDisconnect:
144
+ logger.info("Agent %s disconnected", agent_id)
145
+ except Exception as e:
146
+ logger.error("Agent %s WebSocket error: %s", agent_id, e)
147
+ finally:
148
+ await self._cleanup_connection(agent_id)
149
+
150
+ async def _check_rate_limit(self, ip: str) -> bool:
151
+ """Check if IP is rate limited."""
152
+ now = time.time()
153
+ window_start = now - self.ATTEMPT_WINDOW.total_seconds()
154
+
155
+ # Clean old attempts
156
+ self._connect_attempts[ip] = [
157
+ t for t in self._connect_attempts[ip] if t > window_start
158
+ ]
159
+
160
+ # Check limit
161
+ if len(self._connect_attempts[ip]) >= self.MAX_CONNECT_ATTEMPTS:
162
+ return False
163
+
164
+ # Record attempt
165
+ self._connect_attempts[ip].append(now)
166
+ return True
167
+
168
+ # ------------------------------------------------------------------
169
+ # Authentication
170
+ # ------------------------------------------------------------------
171
+
172
+ async def _authenticate(
173
+ self,
174
+ websocket: WebSocket,
175
+ agent: Any,
176
+ client_ip: str,
177
+ ) -> bool:
178
+ """Challenge-response authentication."""
179
+ import secrets
180
+
181
+ # Generate challenge
182
+ nonce = secrets.token_hex(16)
183
+ timestamp = int(time.time())
184
+
185
+ self._pending_challenges[agent.agent_id] = nonce
186
+
187
+ # Send challenge
188
+ await websocket.send_json({
189
+ "type": "auth_challenge",
190
+ "nonce": nonce,
191
+ "timestamp": timestamp,
192
+ })
193
+
194
+ # Wait for response
195
+ try:
196
+ data = await asyncio.wait_for(
197
+ websocket.receive_text(),
198
+ timeout=self.AUTH_TIMEOUT,
199
+ )
200
+ response = json.loads(data)
201
+ except asyncio.TimeoutError:
202
+ await websocket.close(code=4001, reason="Auth timeout")
203
+ return False
204
+ except json.JSONDecodeError:
205
+ await websocket.close(code=4001, reason="Invalid JSON")
206
+ return False
207
+
208
+ # Verify response
209
+ if response.get("type") != "auth_response":
210
+ await websocket.close(code=4001, reason="Expected auth_response")
211
+ return False
212
+
213
+ if response.get("nonce") != nonce:
214
+ await websocket.close(code=4001, reason="Nonce mismatch")
215
+ return False
216
+
217
+ # Verify timestamp (replay prevention)
218
+ response_ts = response.get("timestamp", 0)
219
+ if abs(time.time() - response_ts) > self.MAX_TIMESTAMP_SKEW:
220
+ await websocket.close(code=4001, reason="Timestamp skew too large")
221
+ return False
222
+
223
+ # Verify signature
224
+ signed_payload = f"{nonce}:{response_ts}:{agent.agent_id}".encode()
225
+ signature = response.get("signature", "")
226
+
227
+ if not self._verify_signature(agent.node_cert, signed_payload, signature):
228
+ await websocket.close(code=4003, reason="Invalid signature")
229
+ return False
230
+
231
+ # Clean up challenge
232
+ self._pending_challenges.pop(agent.agent_id, None)
233
+
234
+ # Send success
235
+ await websocket.send_json({"type": "connected"})
236
+
237
+ return True
238
+
239
+ def _verify_signature(
240
+ self,
241
+ cert: Optional[Dict[str, Any]],
242
+ message: bytes,
243
+ signature_hex: str,
244
+ ) -> bool:
245
+ """Verify Ed25519 signature."""
246
+ if not cert:
247
+ return False
248
+
249
+ try:
250
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
251
+
252
+ pubkey_hex = cert.get("node_public_key_ed25519")
253
+ if not pubkey_hex:
254
+ return False
255
+
256
+ pub_bytes = bytes.fromhex(pubkey_hex)
257
+ pub_key = Ed25519PublicKey.from_public_bytes(pub_bytes)
258
+ sig_bytes = bytes.fromhex(signature_hex)
259
+ pub_key.verify(sig_bytes, message)
260
+ return True
261
+ except Exception as e:
262
+ logger.warning("Signature verification failed: %s", e)
263
+ return False
264
+
265
+ # ------------------------------------------------------------------
266
+ # Message Handling
267
+ # ------------------------------------------------------------------
268
+
269
+ async def _message_loop(
270
+ self,
271
+ agent_id: str,
272
+ websocket: WebSocket,
273
+ ) -> None:
274
+ """Handle incoming WebSocket messages."""
275
+ while agent_id in self._active_connections:
276
+ try:
277
+ data = await websocket.receive_text()
278
+
279
+ # Check message size
280
+ if len(data) > self.MAX_MESSAGE_SIZE:
281
+ await websocket.send_json({
282
+ "type": "error",
283
+ "error": "message_too_large",
284
+ "message": f"Message exceeds {self.MAX_MESSAGE_SIZE} bytes",
285
+ })
286
+ continue
287
+
288
+ # Parse message
289
+ try:
290
+ message = json.loads(data)
291
+ except json.JSONDecodeError as e:
292
+ await websocket.send_json({
293
+ "type": "error",
294
+ "error": "invalid_json",
295
+ "message": str(e),
296
+ })
297
+ continue
298
+
299
+ # Validate structure
300
+ if not isinstance(message, dict):
301
+ await websocket.send_json({
302
+ "type": "error",
303
+ "error": "invalid_message",
304
+ "message": "Message must be a JSON object",
305
+ })
306
+ continue
307
+
308
+ msg_type = message.get("type")
309
+ if not msg_type:
310
+ await websocket.send_json({
311
+ "type": "error",
312
+ "error": "missing_type",
313
+ "message": "Message must have 'type' field",
314
+ })
315
+ continue
316
+
317
+ # Handle message
318
+ await self._handle_message(agent_id, websocket, message)
319
+
320
+ except WebSocketDisconnect:
321
+ break
322
+ except Exception as e:
323
+ logger.error("Message handling error for agent %s: %s", agent_id, e)
324
+
325
+ async def _handle_message(
326
+ self,
327
+ agent_id: str,
328
+ websocket: WebSocket,
329
+ message: Dict[str, Any],
330
+ ) -> None:
331
+ """Handle specific message type."""
332
+ msg_type = message.get("type")
333
+
334
+ if msg_type == "pong":
335
+ # Update last pong time
336
+ if agent_id in self._connection_info:
337
+ self._connection_info[agent_id]["last_pong"] = time.time()
338
+
339
+ elif msg_type == "acknowledge":
340
+ # Initiative acknowledgment
341
+ initiative_id = message.get("initiative_id")
342
+ if initiative_id and initiative_id in self._pending_acks:
343
+ future = self._pending_acks.pop(initiative_id)
344
+ if not future.done():
345
+ future.set_result(True)
346
+
347
+ # Update store
348
+ await self._initiative_store.acknowledge(initiative_id, agent_id)
349
+
350
+ elif msg_type == "complete":
351
+ # Initiative completed
352
+ initiative_id = message.get("initiative_id")
353
+ result = message.get("result")
354
+ metadata = message.get("metadata", {})
355
+
356
+ await self._initiative_store.complete(
357
+ initiative_id,
358
+ agent_id,
359
+ result=result,
360
+ result_metadata=metadata,
361
+ )
362
+ await self._agent_store.decrement_assignments(agent_id)
363
+
364
+ elif msg_type == "fail":
365
+ # Initiative failed
366
+ initiative_id = message.get("initiative_id")
367
+ reason = message.get("reason", "Unknown")
368
+ retry = message.get("retry", False)
369
+
370
+ await self._initiative_store.fail(
371
+ initiative_id,
372
+ agent_id,
373
+ reason=reason,
374
+ retry=retry,
375
+ )
376
+ if not retry:
377
+ await self._agent_store.decrement_assignments(agent_id)
378
+
379
+ elif msg_type == "heartbeat":
380
+ # Heartbeat response
381
+ await self._agent_store.update(
382
+ agent_id,
383
+ last_seen_at=datetime.now(timezone.utc),
384
+ )
385
+
386
+ else:
387
+ logger.warning("Unknown message type from agent %s: %s", agent_id, msg_type)
388
+
389
+ # ------------------------------------------------------------------
390
+ # Ping/Pong
391
+ # ------------------------------------------------------------------
392
+
393
+ async def _ping_task(self, agent_id: str, websocket: WebSocket) -> None:
394
+ """Send periodic pings and check for timeout."""
395
+ while agent_id in self._active_connections:
396
+ await asyncio.sleep(self.PING_INTERVAL)
397
+
398
+ if agent_id not in self._connection_info:
399
+ break
400
+
401
+ info = self._connection_info[agent_id]
402
+ last_activity = info.get("last_pong", time.time())
403
+
404
+ # Check for timeout
405
+ if time.time() - last_activity > self.PING_INTERVAL + self.PONG_TIMEOUT:
406
+ logger.warning("Agent %s ping timeout", agent_id)
407
+ try:
408
+ await websocket.close(code=4001, reason="Ping timeout")
409
+ except Exception:
410
+ pass
411
+ break
412
+
413
+ # Send ping
414
+ try:
415
+ seq = self._next_seq(agent_id)
416
+ await websocket.send_json({
417
+ "type": "ping",
418
+ "seq": seq,
419
+ "timestamp": datetime.now(timezone.utc).isoformat(),
420
+ })
421
+ except Exception as e:
422
+ logger.error("Failed to send ping to agent %s: %s", agent_id, e)
423
+ break
424
+
425
+ def _next_seq(self, agent_id: str) -> int:
426
+ """Get next sequence number for agent."""
427
+ self._seq[agent_id] += 1
428
+ return self._seq[agent_id]
429
+
430
+ # ------------------------------------------------------------------
431
+ # Initiative Delivery
432
+ # ------------------------------------------------------------------
433
+
434
+ async def send_initiative(
435
+ self,
436
+ agent_id: str,
437
+ initiative: Dict[str, Any],
438
+ ) -> bool:
439
+ """Send initiative to agent and wait for ACK."""
440
+ # Check circuit breaker
441
+ if self._circuit_breaker and self._circuit_breaker.is_open(agent_id):
442
+ priority = initiative.get("priority", 1)
443
+ if priority < 2: # Not urgent
444
+ logger.warning("Circuit breaker open for agent %s", agent_id)
445
+ return False
446
+
447
+ websocket = self._active_connections.get(agent_id)
448
+ if not websocket:
449
+ return False
450
+
451
+ seq = self._next_seq(agent_id)
452
+
453
+ # Send initiative
454
+ try:
455
+ await websocket.send_json({
456
+ "type": "initiative",
457
+ "seq": seq,
458
+ "initiative": initiative,
459
+ })
460
+ except Exception as e:
461
+ logger.error("Failed to send initiative to agent %s: %s", agent_id, e)
462
+ if self._circuit_breaker:
463
+ self._circuit_breaker.record_failure(agent_id)
464
+ return False
465
+
466
+ # Wait for ACK
467
+ ack_future = asyncio.Future()
468
+ self._pending_acks[initiative["id"]] = ack_future
469
+
470
+ try:
471
+ await asyncio.wait_for(ack_future, timeout=self.ACK_TIMEOUT)
472
+ if self._circuit_breaker:
473
+ self._circuit_breaker.record_success(agent_id)
474
+ return True
475
+ except asyncio.TimeoutError:
476
+ logger.warning("No ACK from agent %s for initiative %s", agent_id, initiative["id"])
477
+ self._pending_acks.pop(initiative["id"], None)
478
+
479
+ if self._circuit_breaker:
480
+ self._circuit_breaker.record_failure(agent_id)
481
+
482
+ return False
483
+
484
+ # ------------------------------------------------------------------
485
+ # Cleanup
486
+ # ------------------------------------------------------------------
487
+
488
+ async def _cleanup_connection(self, agent_id: str) -> None:
489
+ """Clean up after agent disconnects."""
490
+ # Remove from active
491
+ self._active_connections.pop(agent_id, None)
492
+ self._connection_info.pop(agent_id, None)
493
+
494
+ # Mark agent offline
495
+ await self._agent_store.set_offline(agent_id)
496
+
497
+ # Reassign pending initiatives
498
+ await self._initiative_store.reassign_from_agent(agent_id, only_pending=True)
499
+
500
+ # Cancel pending ACKs
501
+ for init_id, future in list(self._pending_acks.items()):
502
+ # Note: We'd need to track which agent each ACK belongs to
503
+ pass
504
+
505
+ def get_connection_count(self) -> int:
506
+ """Get number of active connections."""
507
+ return len(self._active_connections)
508
+
509
+ def get_connected_agents(self) -> List[str]:
510
+ """Get list of connected agent IDs."""
511
+ return list(self._active_connections.keys())
512
+
513
+ async def close_all(self) -> None:
514
+ """Close all connections."""
515
+ for agent_id, websocket in list(self._active_connections.items()):
516
+ try:
517
+ await websocket.close(code=1001, reason="Server shutdown")
518
+ except Exception:
519
+ pass
520
+
521
+ self._active_connections.clear()
522
+ self._connection_info.clear()
apsimo/api/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # api