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,379 @@
1
+ """Connection Discoverer — find non-obvious connections across domains.
2
+
3
+ Scans memories, signals, and relationships to find patterns that connect
4
+ seemingly unrelated information (e.g., "Mom always calls after doctor
5
+ appointments", "stress spikes correlate with late-night work sessions").
6
+
7
+ Uses graph queries + signal analysis to discover temporal, causal,
8
+ entity-based, and behavioral patterns across domain boundaries.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import textwrap
15
+ import uuid
16
+ from dataclasses import dataclass, field
17
+ from datetime import datetime
18
+ from enum import Enum
19
+ from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class ConnectionType(str, Enum):
25
+ """Categories of cross-domain connections.
26
+
27
+ TEMPORAL: A happens before/after B (time correlation)
28
+ CAUSAL: A causes B (inferred from repeated co-occurrence)
29
+ TOPIC: Same topic surfaces across different contexts
30
+ ENTITY: Same person/company appears in different domains
31
+ BEHAVIORAL: Repeating behavior pattern across contexts
32
+ """
33
+
34
+ TEMPORAL = "temporal"
35
+ CAUSAL = "causal"
36
+ TOPIC = "topic"
37
+ ENTITY = "entity"
38
+ BEHAVIORAL = "behavioral"
39
+
40
+
41
+ @dataclass
42
+ class Connection:
43
+ """A discovered connection between entities or concepts.
44
+
45
+ Represents a cross-domain relationship with evidence tracking
46
+ and confidence scoring.
47
+
48
+ Attributes:
49
+ id: Unique connection identifier
50
+ connection_type: Category of connection
51
+ source_domain: Origin domain ("health", "work", "relationships")
52
+ target_domain: Destination domain
53
+ entities: Related person IDs, memory IDs, etc.
54
+ description: Human-readable description of the connection
55
+ confidence: Confidence score between 0 and 1
56
+ evidence: List of evidence references (memory IDs, signal IDs)
57
+ first_observed: When connection was first detected
58
+ last_observed: Most recent observation
59
+ observation_count: How many times this connection has been seen
60
+ """
61
+
62
+ id: str
63
+ connection_type: ConnectionType
64
+ source_domain: str
65
+ target_domain: str
66
+ entities: List[str]
67
+ description: str
68
+ confidence: float
69
+ evidence: List[str] = field(default_factory=list)
70
+ first_observed: Optional[datetime] = None
71
+ last_observed: Optional[datetime] = None
72
+ observation_count: int = 1
73
+
74
+
75
+ @runtime_checkable
76
+ class GraphClient(Protocol):
77
+ """Protocol for graph database access.
78
+
79
+ Any object providing these methods can serve as the graph backend
80
+ for connection discovery.
81
+ """
82
+
83
+ async def recall(
84
+ self,
85
+ query: str,
86
+ limit: int = 10,
87
+ min_strength: float = 0.1,
88
+ ) -> List[Dict[str, Any]]: ...
89
+
90
+ async def traverse_memory_connections(
91
+ self,
92
+ memory_id: str,
93
+ max_depth: int = 3,
94
+ min_strength: float = 0.3,
95
+ limit: int = 20,
96
+ ) -> List[Dict[str, Any]]: ...
97
+
98
+ async def run_query(
99
+ self,
100
+ cypher: str,
101
+ params: Dict[str, Any],
102
+ ) -> List[Dict[str, Any]]: ...
103
+
104
+
105
+ class ConnectionDiscoverer:
106
+ """Discover cross-domain connections from graph data.
107
+
108
+ Analyzes memories, signals, and entity relationships to surface
109
+ patterns spanning multiple domains. Supports temporal correlation,
110
+ entity co-occurrence, and behavioral pattern detection.
111
+
112
+ Args:
113
+ graph_client: Graph database client implementing GraphClient protocol
114
+ signal_threshold: Minimum signal strength to consider (0-1)
115
+ """
116
+
117
+ def __init__(
118
+ self,
119
+ graph_client: GraphClient,
120
+ signal_threshold: float = 0.6,
121
+ ) -> None:
122
+ self.graph = graph_client
123
+ self.signal_threshold = signal_threshold
124
+
125
+ async def discover_connections(
126
+ self,
127
+ person_id: Optional[str] = None,
128
+ domain: Optional[str] = None,
129
+ min_confidence: float = 0.5,
130
+ ) -> List[Connection]:
131
+ """Find connections, optionally filtered by person or domain.
132
+
133
+ Runs temporal, entity, and behavioral pattern detectors in
134
+ sequence and returns results sorted by confidence descending.
135
+
136
+ Args:
137
+ person_id: Restrict search to this person's connections
138
+ domain: Restrict search to connections involving this domain
139
+ min_confidence: Minimum confidence threshold for results
140
+
141
+ Returns:
142
+ Connections sorted by confidence, highest first.
143
+ """
144
+ connections: List[Connection] = []
145
+
146
+ # Temporal patterns (e.g., "calls mom on Sundays")
147
+ temporal = await self._find_temporal_patterns(person_id)
148
+ connections.extend(temporal)
149
+
150
+ # Entity co-occurrence (same person in different contexts)
151
+ entity = await self._find_entity_patterns(person_id)
152
+ connections.extend(entity)
153
+
154
+ # Behavioral patterns (repeating actions)
155
+ behavioral = await self._find_behavioral_patterns(person_id)
156
+ connections.extend(behavioral)
157
+
158
+ # Filter by confidence and optional domain
159
+ filtered = [c for c in connections if c.confidence >= min_confidence]
160
+ if domain:
161
+ filtered = [
162
+ c
163
+ for c in filtered
164
+ if c.source_domain == domain or c.target_domain == domain
165
+ ]
166
+
167
+ return sorted(filtered, key=lambda c: c.confidence, reverse=True)
168
+
169
+ async def _find_temporal_patterns(
170
+ self,
171
+ person_id: Optional[str],
172
+ ) -> List[Connection]:
173
+ """Find time-based connections between events.
174
+
175
+ Queries graph for Memory node pairs that co-occur within a 4-hour
176
+ window and have been seen together at least 2 times in the past
177
+ 30 days. Confidence scales with daily co-occurrence rate.
178
+ """
179
+ if person_id is not None:
180
+ cypher = textwrap.dedent("""
181
+ MATCH (m1:Memory)-[:ABOUT]->(p:Person {id: $person_id})
182
+ MATCH (m2:Memory)-[:ABOUT]->(p)
183
+ WHERE m1.id < m2.id
184
+ AND abs(duration.between(m1.created_at, m2.created_at).hours) <= $window_hours
185
+ AND m1.created_at >= datetime() - duration({days: $lookback_days})
186
+ WITH m1, m2, count(*) AS co_occurrences
187
+ WHERE co_occurrences >= $min_count
188
+ RETURN m1.id AS source_id, m2.id AS target_id,
189
+ m1.type AS source_type, m2.type AS target_type,
190
+ m1.metadata AS source_meta, m2.metadata AS target_meta,
191
+ co_occurrences,
192
+ toFloat(co_occurrences) / $lookback_days AS daily_rate
193
+ ORDER BY daily_rate DESC
194
+ LIMIT 20
195
+ """).strip()
196
+ params: Dict[str, Any] = {
197
+ "person_id": person_id,
198
+ "window_hours": 4,
199
+ "lookback_days": 30,
200
+ "min_count": 2,
201
+ }
202
+ else:
203
+ cypher = textwrap.dedent("""
204
+ MATCH (m1:Memory), (m2:Memory)
205
+ WHERE m1.id < m2.id
206
+ AND abs(duration.between(m1.created_at, m2.created_at).hours) <= $window_hours
207
+ AND m1.created_at >= datetime() - duration({days: $lookback_days})
208
+ WITH m1, m2, count(*) AS co_occurrences
209
+ WHERE co_occurrences >= $min_count
210
+ RETURN m1.id AS source_id, m2.id AS target_id,
211
+ m1.type AS source_type, m2.type AS target_type,
212
+ m1.metadata AS source_meta, m2.metadata AS target_meta,
213
+ co_occurrences,
214
+ toFloat(co_occurrences) / $lookback_days AS daily_rate
215
+ ORDER BY daily_rate DESC
216
+ LIMIT 20
217
+ """).strip()
218
+ params = {
219
+ "window_hours": 4,
220
+ "lookback_days": 30,
221
+ "min_count": 2,
222
+ }
223
+
224
+ connections: List[Connection] = []
225
+ try:
226
+ rows = await self.graph.run_query(cypher, params)
227
+ except Exception:
228
+ logger.exception("_find_temporal_patterns query failed")
229
+ return connections
230
+
231
+ for row in rows:
232
+ source_type = row.get("source_type") or "unknown"
233
+ target_type = row.get("target_type") or "unknown"
234
+ daily_rate = float(row.get("daily_rate") or 0.0)
235
+ confidence = min(1.0, daily_rate * 7)
236
+ connections.append(
237
+ Connection(
238
+ id=self._make_id(),
239
+ connection_type=ConnectionType.TEMPORAL,
240
+ source_domain=source_type,
241
+ target_domain=target_type,
242
+ entities=[row["source_id"], row["target_id"]],
243
+ description=(
244
+ f"Memory type '{source_type}' co-occurs with"
245
+ f" '{target_type}' within 4 hours"
246
+ f" ({int(row['co_occurrences'])}x in 30 days)"
247
+ ),
248
+ confidence=confidence,
249
+ evidence=[row["source_id"], row["target_id"]],
250
+ )
251
+ )
252
+ return connections
253
+
254
+ async def _find_entity_patterns(
255
+ self,
256
+ person_id: Optional[str],
257
+ ) -> List[Connection]:
258
+ """Find entity co-occurrence across domains.
259
+
260
+ Detects when the same person, company, or concept appears in
261
+ multiple domain contexts (e.g., "Alex" in both health and work).
262
+ Requires an entity to appear in at least 2 distinct domains.
263
+ """
264
+ cypher = textwrap.dedent("""
265
+ MATCH (e:Entity)<-[:MENTIONS]-(m:Memory)
266
+ WHERE ($person_id IS NULL OR (m)-[:ABOUT]->(:Person {id: $person_id}))
267
+ WITH e, collect(DISTINCT m.id) AS mems
268
+ WITH e, mems, size(mems) AS mem_count
269
+ WHERE mem_count >= 2
270
+ RETURN e.name AS entity_name,
271
+ mem_count AS occurrence_count,
272
+ mems[0..5] AS evidence_sample
273
+ ORDER BY mem_count DESC
274
+ LIMIT 20
275
+ """).strip()
276
+ params = {
277
+ "person_id": person_id,
278
+ "min_domain_count": 2,
279
+ }
280
+
281
+ connections: List[Connection] = []
282
+ try:
283
+ rows = await self.graph.run_query(cypher, params)
284
+ except Exception:
285
+ logger.exception("_find_entity_patterns query failed")
286
+ return connections
287
+
288
+ for row in rows:
289
+ entity_name = row.get("entity_name") or "unknown"
290
+ domains: List[str] = list(row.get("domains") or [])
291
+ evidence_sample = row.get("evidence_sample") or []
292
+ # Flatten nested lists from collect(collect(...))
293
+ evidence: List[str] = []
294
+ for item in evidence_sample:
295
+ if isinstance(item, list):
296
+ evidence.extend(str(x) for x in item)
297
+ else:
298
+ evidence.append(str(item))
299
+ source_domain = domains[0] if domains else "unknown"
300
+ target_domain = domains[1] if len(domains) > 1 else "multiple"
301
+ confidence = min(1.0, (len(domains) - 1) * 0.3 + 0.4)
302
+ connections.append(
303
+ Connection(
304
+ id=self._make_id(),
305
+ connection_type=ConnectionType.ENTITY,
306
+ source_domain=source_domain,
307
+ target_domain=target_domain,
308
+ entities=[entity_name],
309
+ description=f"{entity_name} appears across {', '.join(domains)}",
310
+ confidence=confidence,
311
+ evidence=evidence[:5],
312
+ )
313
+ )
314
+ return connections
315
+
316
+ async def _find_behavioral_patterns(
317
+ self,
318
+ person_id: Optional[str],
319
+ ) -> List[Connection]:
320
+ """Find repeating behavior patterns across contexts.
321
+
322
+ Analyzes Signal sequences where signal type A is consistently
323
+ followed by signal type B within 24 hours, for a given person.
324
+ Requires person_id; returns empty list when person_id is None.
325
+ """
326
+ connections: List[Connection] = []
327
+ if person_id is None:
328
+ return connections
329
+
330
+ cypher = textwrap.dedent("""
331
+ MATCH (p:Person {id: $person_id})-[:EXHIBITED]->(s1:Signal)
332
+ MATCH (p)-[:EXHIBITED]->(s2:Signal)
333
+ WHERE s1.signal_type <> s2.signal_type
334
+ AND s2.timestamp > s1.timestamp
335
+ AND duration.between(s1.timestamp, s2.timestamp).hours <= $window_hours
336
+ WITH s1.signal_type AS type_a, s2.signal_type AS type_b,
337
+ count(*) AS occurrences,
338
+ avg(s2.normalized_value - s1.normalized_value) AS avg_delta,
339
+ collect(s1.id)[0..5] AS evidence
340
+ WHERE occurrences >= $min_occurrences
341
+ RETURN type_a, type_b, occurrences, avg_delta, evidence
342
+ ORDER BY occurrences DESC
343
+ LIMIT 15
344
+ """).strip()
345
+ params = {
346
+ "person_id": person_id,
347
+ "window_hours": 24,
348
+ "min_occurrences": 3,
349
+ }
350
+
351
+ try:
352
+ rows = await self.graph.run_query(cypher, params)
353
+ except Exception:
354
+ logger.exception("_find_behavioral_patterns query failed")
355
+ return connections
356
+
357
+ for row in rows:
358
+ type_a = row.get("type_a") or "unknown"
359
+ type_b = row.get("type_b") or "unknown"
360
+ occurrences = int(row.get("occurrences") or 0)
361
+ confidence = min(1.0, occurrences / 10.0)
362
+ connections.append(
363
+ Connection(
364
+ id=self._make_id(),
365
+ connection_type=ConnectionType.BEHAVIORAL,
366
+ source_domain="signals",
367
+ target_domain="signals",
368
+ entities=[],
369
+ description=f"Signal {type_a} consistently precedes {type_b}",
370
+ confidence=confidence,
371
+ evidence=list(row.get("evidence") or []),
372
+ )
373
+ )
374
+ return connections
375
+
376
+ @staticmethod
377
+ def _make_id() -> str:
378
+ """Generate a unique connection ID."""
379
+ return str(uuid.uuid4())
@@ -0,0 +1,287 @@
1
+ """Cross-Domain Analyzer — synthesize insights across domains.
2
+
3
+ Analyzes relationships between:
4
+ - Health data (sleep, stress → availability predictions)
5
+ - Work patterns (deadlines → communication shifts)
6
+ - Social patterns (relationship dynamics → scheduling impact)
7
+ - Financial patterns (spending → stress correlation)
8
+
9
+ Produces actionable DomainInsight objects that can be validated,
10
+ scored for novelty, and delivered through appropriate channels.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import math
17
+ import statistics
18
+ import uuid
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime, timezone
21
+ from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ @dataclass
27
+ class DomainInsight:
28
+ """Insight derived from cross-domain analysis.
29
+
30
+ Captures a pattern or prediction that spans multiple domains,
31
+ along with evidence, confidence, and an optional recommended action.
32
+
33
+ Attributes:
34
+ id: Unique insight identifier
35
+ domains: List of domains this insight spans (e.g. ["health", "work"])
36
+ insight_type: Category ("availability_prediction", "stress_pattern", etc.)
37
+ description: Human-readable description of the insight
38
+ confidence: Confidence score between 0 and 1
39
+ actionable: Whether this insight has a concrete recommended action
40
+ recommended_action: Suggested action if actionable
41
+ supporting_evidence: Memory IDs, signal IDs, or descriptions backing this insight
42
+ """
43
+
44
+ id: str
45
+ domains: List[str]
46
+ insight_type: str
47
+ description: str
48
+ confidence: float
49
+ actionable: bool
50
+ recommended_action: Optional[str] = None
51
+ supporting_evidence: List[str] = field(default_factory=list)
52
+
53
+
54
+ @runtime_checkable
55
+ class AnalyzerGraphClient(Protocol):
56
+ """Protocol for graph access needed by cross-domain analysis."""
57
+
58
+ async def recall(
59
+ self,
60
+ query: str,
61
+ limit: int = 10,
62
+ min_strength: float = 0.1,
63
+ ) -> List[Dict[str, Any]]: ...
64
+
65
+
66
+ @runtime_checkable
67
+ class AnalyzerEventBus(Protocol):
68
+ """Protocol for event emission from the analyzer."""
69
+
70
+ async def emit_async(self, event: Any) -> None: ...
71
+
72
+
73
+ class CrossDomainAnalyzer:
74
+ """Analyze patterns spanning multiple domains.
75
+
76
+ Runs targeted analyzers for domain pairs (health+work,
77
+ relationships+health, etc.) and produces scored insights.
78
+
79
+ Args:
80
+ graph_client: Graph database client for querying cross-domain data
81
+ event_bus: Event bus for emitting insight discovery events
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ graph_client: AnalyzerGraphClient,
87
+ event_bus: AnalyzerEventBus,
88
+ ) -> None:
89
+ self.graph = graph_client
90
+ self.events = event_bus
91
+
92
+ async def analyze(
93
+ self,
94
+ domains: Optional[List[str]] = None,
95
+ ) -> List[DomainInsight]:
96
+ """Run cross-domain analysis across specified domains.
97
+
98
+ If no domains are specified, all available analyzers run.
99
+ Results are sorted by confidence descending.
100
+
101
+ Args:
102
+ domains: Optional list of domains to include. None means all.
103
+
104
+ Returns:
105
+ List of DomainInsight objects, highest confidence first.
106
+ """
107
+ insights: List[DomainInsight] = []
108
+
109
+ # Health + Work: stress → availability
110
+ if not domains or "health" in domains or "work" in domains:
111
+ health_work = await self._analyze_health_work()
112
+ insights.extend(health_work)
113
+
114
+ # Relationships + Health: social stress patterns
115
+ if not domains or "relationships" in domains or "health" in domains:
116
+ relationships_health = await self._analyze_relationships_health()
117
+ insights.extend(relationships_health)
118
+
119
+ # Work + Relationships: deadline → social impact
120
+ if not domains or "work" in domains or "relationships" in domains:
121
+ work_relationships = await self._analyze_work_relationships()
122
+ insights.extend(work_relationships)
123
+
124
+ return sorted(insights, key=lambda i: i.confidence, reverse=True)
125
+
126
+ @staticmethod
127
+ def _pearson_correlation(xs: list, ys: list) -> float:
128
+ """Compute Pearson correlation coefficient between two lists."""
129
+ n = len(xs)
130
+ if n < 2:
131
+ return 0.0
132
+ try:
133
+ mean_x = sum(xs) / n
134
+ mean_y = sum(ys) / n
135
+ cov = sum((xs[i] - mean_x) * (ys[i] - mean_y) for i in range(n))
136
+ std_x = math.sqrt(sum((x - mean_x) ** 2 for x in xs))
137
+ std_y = math.sqrt(sum((y - mean_y) ** 2 for y in ys))
138
+ if std_x == 0.0 or std_y == 0.0:
139
+ return 0.0
140
+ return cov / (std_x * std_y)
141
+ except (ArithmeticError, ValueError, ZeroDivisionError) as exc:
142
+ logger.debug("Pearson correlation failed (treating as 0.0): %s", exc)
143
+ return 0.0
144
+
145
+ def _extract_date(self, mem: dict) -> Optional[datetime]:
146
+ """Extract datetime from a memory dict."""
147
+ created_at = mem.get("created_at")
148
+ if created_at is None:
149
+ return None
150
+ def _aware(dt):
151
+ # Mixed naive/aware datetimes make the later subtraction raise
152
+ # TypeError (swallowed upstream -> insights silently vanish).
153
+ if isinstance(dt, datetime) and dt.tzinfo is None:
154
+ return dt.replace(tzinfo=timezone.utc)
155
+ return dt
156
+ if isinstance(created_at, datetime):
157
+ return _aware(created_at)
158
+ # Neo4j DateTime object
159
+ if hasattr(created_at, 'to_native'):
160
+ return _aware(created_at.to_native())
161
+ # ISO string
162
+ try:
163
+ if isinstance(created_at, str):
164
+ return _aware(datetime.fromisoformat(created_at.replace("Z", "+00:00")))
165
+ except (ValueError, AttributeError) as exc:
166
+ logger.debug("_extract_date: unparseable created_at %r: %s", created_at, exc)
167
+ return None
168
+
169
+ def _correlate_domain_memories(
170
+ self,
171
+ mems_a: list,
172
+ mems_b: list,
173
+ window_days: float = 3.0,
174
+ ) -> tuple:
175
+ """Find temporally co-occurring pairs between two memory lists.
176
+
177
+ Returns (pairs, correlation) where pairs is list of (mem_a, mem_b) tuples
178
+ and correlation is the Pearson correlation of their strength values.
179
+ """
180
+ pairs = []
181
+ for ma in mems_a:
182
+ date_a = self._extract_date(ma)
183
+ if date_a is None:
184
+ continue
185
+ for mb in mems_b:
186
+ date_b = self._extract_date(mb)
187
+ if date_b is None:
188
+ continue
189
+ diff_days = abs((date_a - date_b).total_seconds()) / 86400
190
+ if diff_days <= window_days:
191
+ pairs.append((ma, mb))
192
+ if len(pairs) < 3:
193
+ return pairs, 0.0
194
+ strengths_a = [p[0].get("strength", 0.5) or 0.5 for p in pairs]
195
+ strengths_b = [p[1].get("strength", 0.5) or 0.5 for p in pairs]
196
+ corr = self._pearson_correlation(strengths_a, strengths_b)
197
+ return pairs, corr
198
+
199
+ async def _analyze_health_work(self) -> List[DomainInsight]:
200
+ """Analyze health -> work availability patterns."""
201
+ try:
202
+ health_mems = await self.graph.recall(
203
+ "sleep score stress energy fatigue exercise",
204
+ limit=50, min_strength=0.2,
205
+ )
206
+ work_mems = await self.graph.recall(
207
+ "deadline meeting cancelled productivity output",
208
+ limit=50, min_strength=0.2,
209
+ )
210
+ pairs, correlation = self._correlate_domain_memories(health_mems, work_mems, window_days=3.0)
211
+ if len(pairs) < 3 or abs(correlation) < 0.4:
212
+ return []
213
+ evidence = list({p[0].get("id", "") for p in pairs[:5]} | {p[1].get("id", "") for p in pairs[:5]})
214
+ evidence = [e for e in evidence if e][:5]
215
+ return [DomainInsight(
216
+ id=str(uuid.uuid4()),
217
+ domains=["health", "work"],
218
+ insight_type="availability_prediction",
219
+ description="Low sleep scores correlate with reduced work output in the following days",
220
+ confidence=min(1.0, abs(correlation)),
221
+ actionable=True,
222
+ recommended_action="Block focus time after poor sleep nights",
223
+ supporting_evidence=evidence,
224
+ )]
225
+ except Exception as e:
226
+ logger.debug("_analyze_health_work failed: %s", e)
227
+ return []
228
+
229
+ async def _analyze_relationships_health(self) -> List[DomainInsight]:
230
+ """Analyze relationship dynamics -> health impact."""
231
+ try:
232
+ relationship_mems = await self.graph.recall(
233
+ "conflict argument tension difficult person stressful interaction",
234
+ limit=50, min_strength=0.2,
235
+ )
236
+ health_mems = await self.graph.recall(
237
+ "stress anxiety sleep disruption fatigue mood",
238
+ limit=50, min_strength=0.2,
239
+ )
240
+ pairs, correlation = self._correlate_domain_memories(relationship_mems, health_mems, window_days=2.0)
241
+ if len(pairs) < 3 or abs(correlation) < 0.4:
242
+ return []
243
+ evidence = list({p[0].get("id", "") for p in pairs[:5]} | {p[1].get("id", "") for p in pairs[:5]})
244
+ evidence = [e for e in evidence if e][:5]
245
+ return [DomainInsight(
246
+ id=str(uuid.uuid4()),
247
+ domains=["relationships", "health"],
248
+ insight_type="stress_pattern",
249
+ description="Difficult relationship interactions correlate with health signal spikes within 48 hours",
250
+ confidence=min(1.0, abs(correlation)),
251
+ actionable=True,
252
+ recommended_action="Schedule recovery time after high-conflict interactions",
253
+ supporting_evidence=evidence,
254
+ )]
255
+ except Exception as e:
256
+ logger.debug("_analyze_relationships_health failed: %s", e)
257
+ return []
258
+
259
+ async def _analyze_work_relationships(self) -> List[DomainInsight]:
260
+ """Analyze work patterns -> relationship impact."""
261
+ try:
262
+ work_stress_mems = await self.graph.recall(
263
+ "deadline crunch sprint overloaded travel busy",
264
+ limit=50, min_strength=0.2,
265
+ )
266
+ social_mems = await self.graph.recall(
267
+ "cancelled plans late reply short message ignored social",
268
+ limit=50, min_strength=0.2,
269
+ )
270
+ pairs, correlation = self._correlate_domain_memories(work_stress_mems, social_mems, window_days=3.0)
271
+ if len(pairs) < 3 or abs(correlation) < 0.4:
272
+ return []
273
+ evidence = list({p[0].get("id", "") for p in pairs[:5]} | {p[1].get("id", "") for p in pairs[:5]})
274
+ evidence = [e for e in evidence if e][:5]
275
+ return [DomainInsight(
276
+ id=str(uuid.uuid4()),
277
+ domains=["work", "relationships"],
278
+ insight_type="communication_shift",
279
+ description="Heavy work stress periods correlate with reduced social responsiveness",
280
+ confidence=min(1.0, abs(correlation)),
281
+ actionable=True,
282
+ recommended_action="Proactively communicate reduced availability during high-load periods",
283
+ supporting_evidence=evidence,
284
+ )]
285
+ except Exception as e:
286
+ logger.debug("_analyze_work_relationships failed: %s", e)
287
+ return []