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,405 @@
1
+ """MemoryConsolidator — deduplicate and merge near-duplicate memories in Neo4j.
2
+
3
+ Runs as a background task (not in the hot path). Called by the autonomy loop
4
+ once per hour (configurable). Operates entirely on Memory nodes and their edges.
5
+
6
+ Algorithm:
7
+ 1. Retrieve all Memory nodes modified in the last N hours
8
+ 2. Compute pairwise cosine similarity using stored embeddings
9
+ 3. For pairs with similarity > threshold (default 0.92):
10
+ a. Keep the node with higher strength; add sources of both; update last_seen
11
+ b. Redirect all edges from the merged node to the survivor
12
+ c. Mark the merged node with MERGED_INTO edge and delete it
13
+ 4. Detect conflicting facts (same entity, contradictory attribute values):
14
+ create a CONFLICTS_WITH edge and flag for human review
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ import asyncio
21
+ import math
22
+ import os
23
+ import uuid
24
+ from dataclasses import dataclass, field
25
+ from datetime import datetime, timezone
26
+ from typing import Any, Dict, List, Optional, Tuple
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Result types
33
+ # ---------------------------------------------------------------------------
34
+
35
+ @dataclass
36
+ class ConflictPair:
37
+ """Two memories that assert contradictory facts."""
38
+ memory_id_a: str
39
+ memory_id_b: str
40
+ entity_name: str
41
+ reason: str
42
+
43
+
44
+ @dataclass
45
+ class ConsolidationResult:
46
+ """Outcome of one MemoryConsolidator.run() call."""
47
+ pairs_examined: int = 0
48
+ pairs_merged: int = 0
49
+ conflicts_detected: int = 0
50
+ errors: List[str] = field(default_factory=list)
51
+ duration_ms: float = 0.0
52
+
53
+ @property
54
+ def reduction_ratio(self) -> float:
55
+ """Fraction of examined pairs that were merged."""
56
+ if self.pairs_examined == 0:
57
+ return 0.0
58
+ return self.pairs_merged / self.pairs_examined
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Helpers
63
+ # ---------------------------------------------------------------------------
64
+
65
+ try:
66
+ import numpy as _np
67
+ except Exception: # numpy is present in practice; the pure
68
+ _np = None # python path remains as a fallback
69
+
70
+
71
+ def _cosine_similarity(a: List[float], b: List[float]) -> float:
72
+ """Cosine similarity between two equal-length vectors. Returns 0 on error.
73
+
74
+ Uses numpy when available: at embedding dimensions in the thousands the
75
+ pure-python path is ~100x slower and, run pairwise on the event loop, was
76
+ observed starving the whole API (accept-queue exhaustion)."""
77
+ if not a or not b or len(a) != len(b):
78
+ return 0.0
79
+ if _np is not None:
80
+ va = _np.asarray(a, dtype=_np.float32)
81
+ vb = _np.asarray(b, dtype=_np.float32)
82
+ na = float(_np.linalg.norm(va)); nb = float(_np.linalg.norm(vb))
83
+ if na == 0.0 or nb == 0.0:
84
+ return 0.0
85
+ return float(va.dot(vb) / (na * nb))
86
+ dot = sum(x * y for x, y in zip(a, b))
87
+ mag_a = math.sqrt(sum(x * x for x in a))
88
+ mag_b = math.sqrt(sum(y * y for y in b))
89
+ if mag_a == 0.0 or mag_b == 0.0:
90
+ return 0.0
91
+ return dot / (mag_a * mag_b)
92
+
93
+
94
+ def _jaccard_similarity(text_a: str, text_b: str) -> float:
95
+ """Token-level Jaccard similarity — fast fallback when embeddings are absent."""
96
+ tokens_a = set(text_a.lower().split())
97
+ tokens_b = set(text_b.lower().split())
98
+ if not tokens_a and not tokens_b:
99
+ return 1.0
100
+ if not tokens_a or not tokens_b:
101
+ return 0.0
102
+ return len(tokens_a & tokens_b) / len(tokens_a | tokens_b)
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Main class
107
+ # ---------------------------------------------------------------------------
108
+
109
+ class MemoryConsolidator:
110
+ """Deduplicate and merge near-duplicate memories in Neo4j.
111
+
112
+ Designed to be used with ColonyGraph but accepts any object that exposes
113
+ an ``async def execute(query, **params)`` method returning a list of
114
+ record dicts — this makes unit-testing straightforward without Neo4j.
115
+
116
+ Args:
117
+ graph_client: ColonyGraph instance (or compatible mock)
118
+ similarity_threshold: Cosine/Jaccard score above which two memories
119
+ are considered duplicates (default: 0.92)
120
+ lookback_hours: Only examine memories touched in the last N hours
121
+ (default: 24)
122
+ """
123
+
124
+ def __init__(
125
+ self,
126
+ graph_client: Any,
127
+ similarity_threshold: float = 0.92,
128
+ lookback_hours: int = 24,
129
+ max_merge_ratio: float = 0.5,
130
+ params: Any = None,
131
+ ) -> None:
132
+ self.graph = graph_client
133
+ self.similarity_threshold = similarity_threshold
134
+ self.lookback_hours = lookback_hours
135
+ self._max_merge_ratio = max_merge_ratio
136
+ # Optional AdaptiveParamStore: when wired, the merge threshold is
137
+ # re-resolved at the start of every run, so a meta-learning
138
+ # adjustment takes effect without a restart (bounds enforced by the
139
+ # store, hard floor 0.85).
140
+ self._params = params
141
+
142
+ # ------------------------------------------------------------------
143
+ # Public API
144
+ # ------------------------------------------------------------------
145
+
146
+ async def run(self) -> ConsolidationResult:
147
+ """Execute one consolidation pass. Never raises — errors are captured."""
148
+ import time
149
+ start = time.monotonic()
150
+ result = ConsolidationResult()
151
+
152
+ if self._params is not None:
153
+ try:
154
+ from apsimo.self_model.params import (
155
+ PARAM_CONSOLIDATION_THRESHOLD,
156
+ )
157
+ self.similarity_threshold = float(self._params.get(
158
+ PARAM_CONSOLIDATION_THRESHOLD,
159
+ default=self.similarity_threshold))
160
+ except Exception:
161
+ logger.debug("adaptive threshold resolve failed; keeping %s",
162
+ self.similarity_threshold, exc_info=True)
163
+
164
+ try:
165
+ candidates = await self._fetch_recent_memories()
166
+ except Exception as exc:
167
+ result.errors.append(f"fetch_recent_memories: {exc}")
168
+ result.duration_ms = (time.monotonic() - start) * 1000
169
+ return result
170
+
171
+ # Pairwise comparison (O(n²) — acceptable for typical batch sizes < 1000)
172
+ # Safety cap: merge at most max_merge_ratio of candidates per pass
173
+ # to prevent catastrophic information loss from runaway merges.
174
+ n = len(candidates)
175
+ max_merges = max(1, int(n * self._max_merge_ratio))
176
+ for i in range(n):
177
+ for j in range(i + 1, n):
178
+ a, b = candidates[i], candidates[j]
179
+ result.pairs_examined += 1
180
+ if result.pairs_examined % 512 == 0:
181
+ await asyncio.sleep(0) # breathe: never starve the event loop
182
+ sim = self._similarity(a, b)
183
+ if sim >= self.similarity_threshold:
184
+ if result.pairs_merged >= max_merges:
185
+ logger.warning(
186
+ "Consolidation safety cap reached: merged %d/%d (%.0f%% limit)",
187
+ result.pairs_merged, n, self._max_merge_ratio * 100,
188
+ )
189
+ break
190
+ try:
191
+ await self._merge_pair(a, b)
192
+ result.pairs_merged += 1
193
+ except Exception as exc:
194
+ result.errors.append(
195
+ f"merge_pair({a.get('id')}, {b.get('id')}): {exc}"
196
+ )
197
+ else:
198
+ continue
199
+ break # Break outer loop when safety cap reached
200
+
201
+ try:
202
+ conflicts = await self._detect_conflicts()
203
+ result.conflicts_detected = len(conflicts)
204
+ except Exception as exc:
205
+ result.errors.append(f"detect_conflicts: {exc}")
206
+
207
+ result.duration_ms = (time.monotonic() - start) * 1000
208
+ logger.info(
209
+ "MemoryConsolidator: examined=%d merged=%d conflicts=%d errors=%d (%.1fms)",
210
+ result.pairs_examined,
211
+ result.pairs_merged,
212
+ result.conflicts_detected,
213
+ len(result.errors),
214
+ result.duration_ms,
215
+ )
216
+ try:
217
+ from apsimo.events.broadcaster import emit as _emit
218
+ _emit("memory_consolidated", {
219
+ "examined": result.pairs_examined,
220
+ "merged": result.pairs_merged,
221
+ "conflicts": result.conflicts_detected,
222
+ "duration_ms": result.duration_ms,
223
+ })
224
+ except Exception:
225
+ logger.debug("memory_consolidated broadcast failed", exc_info=True)
226
+ return result
227
+
228
+ # ------------------------------------------------------------------
229
+ # Private helpers
230
+ # ------------------------------------------------------------------
231
+
232
+ async def _fetch_recent_memories(self) -> List[Dict[str, Any]]:
233
+ """Return Memory nodes touched within lookback_hours."""
234
+ query = (
235
+ "MATCH (m:Memory) "
236
+ "WHERE m.accessed_at >= datetime() - duration({hours: $hours}) "
237
+ " AND NOT m.epistemic_state IN ['stale', 'superseded', 'deprecated', 'archived'] "
238
+ "RETURN m.id AS id, m.content AS content, m.embedding AS embedding, "
239
+ "m.strength AS strength, m.effective_confidence AS effective_confidence, "
240
+ "m.type AS type, m.sources AS sources, m.epistemic_state AS epistemic_state "
241
+ "ORDER BY m.effective_confidence DESC, m.strength DESC "
242
+ "LIMIT $cap"
243
+ )
244
+ # O(n^2) pass: the candidate set MUST be bounded. A bulk import once
245
+ # produced thousands of same-day memories and the unbounded pass spun
246
+ # for hours at 100% CPU (COLONY_CONSOLIDATION_BATCH to tune).
247
+ try:
248
+ cap = max(50, int(os.environ.get("COLONY_CONSOLIDATION_BATCH", "400")))
249
+ except ValueError:
250
+ cap = 400
251
+ rows = await self._execute(query, hours=self.lookback_hours, cap=cap)
252
+ return rows if rows else []
253
+
254
+ def _similarity(self, a: Dict[str, Any], b: Dict[str, Any]) -> float:
255
+ """Return the best similarity score between two memory records."""
256
+ emb_a = a.get("embedding") or []
257
+ emb_b = b.get("embedding") or []
258
+ if emb_a and emb_b and len(emb_a) == len(emb_b):
259
+ return _cosine_similarity(emb_a, emb_b)
260
+ # Fall back to Jaccard on raw content
261
+ return _jaccard_similarity(
262
+ a.get("content", ""), b.get("content", "")
263
+ )
264
+
265
+ async def _merge_pair(self, keep_rec: Dict[str, Any], merge_rec: Dict[str, Any]) -> None:
266
+ """Merge *merge_rec* into *keep_rec*: highest-strength node survives."""
267
+ from apsimo.intelligence.graph.client import EpistemicState
268
+
269
+ # Determine survivor by effective_confidence → strength → recency
270
+ keep_conf = keep_rec.get("effective_confidence") or keep_rec.get("strength") or 0.0
271
+ merge_conf = merge_rec.get("effective_confidence") or merge_rec.get("strength") or 0.0
272
+ if merge_conf > keep_conf:
273
+ keep_rec, merge_rec = merge_rec, keep_rec
274
+ keep_conf, merge_conf = merge_conf, keep_conf
275
+
276
+ keep_id = keep_rec["id"]
277
+ merge_id = merge_rec["id"]
278
+
279
+ # 1. Re-point all outgoing MENTIONS relationships from merge → keep
280
+ await self._execute(
281
+ """
282
+ MATCH (m:Memory {id: $merge_id})-[r:MENTIONS]->(e:Entity)
283
+ MATCH (k:Memory {id: $keep_id})
284
+ MERGE (k)-[nr:MENTIONS]->(e)
285
+ SET nr.created_at = coalesce(r.created_at, datetime())
286
+ DELETE r
287
+ """,
288
+ keep_id=keep_id,
289
+ merge_id=merge_id,
290
+ )
291
+
292
+ # 3. Re-point ABOUT relationships
293
+ await self._execute(
294
+ """
295
+ MATCH (m:Memory {id: $merge_id})-[r:ABOUT]->(p:Person)
296
+ MATCH (k:Memory {id: $keep_id})
297
+ MERGE (k)-[nr:ABOUT]->(p)
298
+ SET nr.created_at = coalesce(r.created_at, datetime())
299
+ DELETE r
300
+ """,
301
+ keep_id=keep_id,
302
+ merge_id=merge_id,
303
+ )
304
+
305
+ # 4. Create MERGED_INTO edge
306
+ await self._execute(
307
+ """
308
+ MATCH (k:Memory {id: $keep_id}), (m:Memory {id: $merge_id})
309
+ MERGE (m)-[r:MERGED_INTO]->(k)
310
+ SET r.merged_at = datetime()
311
+ """,
312
+ keep_id=keep_id,
313
+ merge_id=merge_id,
314
+ )
315
+
316
+ # 5. Merge provenance and update survivor
317
+ merge_sources = merge_rec.get("sources") or []
318
+ merge_provenance = merge_rec.get("provenance") or []
319
+ await self._execute(
320
+ """
321
+ MATCH (k:Memory {id: $keep_id})
322
+ SET k.sources = CASE WHEN k.sources IS NULL THEN $sources
323
+ ELSE k.sources + [x IN $sources WHERE NOT x IN k.sources] END,
324
+ k.provenance = coalesce(k.provenance, []) + $merge_provenance + [$merged_id],
325
+ k.accessed_at = datetime(),
326
+ k.epistemic_state = CASE
327
+ WHEN k.epistemic_state = 'inferred' AND $merge_state IN ['observed', 'corroborated', 'verified']
328
+ THEN $merge_state
329
+ ELSE k.epistemic_state
330
+ END
331
+ """,
332
+ keep_id=keep_id,
333
+ sources=merge_sources,
334
+ merge_provenance=merge_provenance,
335
+ merged_id=merge_id,
336
+ merge_state=merge_rec.get("epistemic_state", "inferred"),
337
+ )
338
+
339
+ # 6. Remove :Memory label from merged node (preserves MERGED_INTO audit edge)
340
+ await self._execute(
341
+ "MATCH (m:Memory {id: $merge_id}) REMOVE m:Memory",
342
+ merge_id=merge_id,
343
+ )
344
+
345
+ logger.debug("Merged memory %s → %s (MERGED_INTO + provenance recorded)", merge_id, keep_id)
346
+
347
+ async def _detect_conflicts(self) -> List[ConflictPair]:
348
+ """Find Memory pairs that assert contradictory facts about the same entity."""
349
+ # Look for memories sharing an entity where content contradicts
350
+ # (simple heuristic: both contain the entity name + negation words)
351
+ query = (
352
+ "MATCH (m1:Memory)-[:MENTIONS]->(e:Entity)<-[:MENTIONS]-(m2:Memory) "
353
+ "WHERE id(m1) < id(m2) "
354
+ "AND NOT (m1)-[:MERGED_INTO]-() AND NOT (m2)-[:MERGED_INTO]-() "
355
+ "RETURN m1.id AS id_a, m2.id AS id_b, e.name AS entity, "
356
+ " m1.content AS content_a, m2.content AS content_b"
357
+ )
358
+ rows = await self._execute(query)
359
+ if not rows:
360
+ return []
361
+
362
+ conflicts: List[ConflictPair] = []
363
+ negation_words = {"not", "never", "no", "isn't", "wasn't", "doesn't", "can't", "won't"}
364
+
365
+ for row in rows:
366
+ ca = set((row.get("content_a") or "").lower().split())
367
+ cb = set((row.get("content_b") or "").lower().split())
368
+ # Conflict heuristic: one sentence has negation that the other lacks
369
+ neg_a = bool(ca & negation_words)
370
+ neg_b = bool(cb & negation_words)
371
+ if neg_a != neg_b:
372
+ conflict = ConflictPair(
373
+ memory_id_a=row["id_a"],
374
+ memory_id_b=row["id_b"],
375
+ entity_name=row.get("entity", ""),
376
+ reason="negation asymmetry",
377
+ )
378
+ conflicts.append(conflict)
379
+ # Flag with CONFLICTS_WITH edge
380
+ try:
381
+ await self._execute(
382
+ "MATCH (m1:Memory {id: $id_a}), (m2:Memory {id: $id_b}) "
383
+ "MERGE (m1)-[:CONFLICTS_WITH]->(m2)",
384
+ id_a=row["id_a"],
385
+ id_b=row["id_b"],
386
+ )
387
+ except Exception as exc:
388
+ logger.debug("Could not create CONFLICTS_WITH edge: %s", exc)
389
+
390
+ return conflicts
391
+
392
+ async def _execute(self, query: str, **params: Any) -> List[Dict[str, Any]]:
393
+ """Execute a Cypher query via the graph client.
394
+
395
+ Supports ColonyGraph (which exposes an AsyncDriver) or any mock that
396
+ implements ``async def execute(query, **params) -> list``.
397
+ """
398
+ if hasattr(self.graph, "execute"):
399
+ result = await self.graph.execute(query, **params)
400
+ return result if result is not None else []
401
+
402
+ # ColonyGraph path: use the driver session directly
403
+ async with self.graph.driver.session(database=self.graph.database) as session:
404
+ result = await session.run(query, **params)
405
+ return [dict(record) async for record in result]