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,45 @@
1
+ """Harness integration - writes context files and skills to each harness.
2
+
3
+ This module provides automatic integration with:
4
+ - Crush (MCP + skill)
5
+ - Codex (MCP + skill)
6
+ - Claude Code (MCP + skill)
7
+ - OpenCode (MCP + skill)
8
+ - Hermes (plugin only)
9
+ """
10
+
11
+ from .context import (
12
+ write_apsimo_context,
13
+ APSIMO_CONTEXT_TEMPLATE,
14
+ write_colony_context,
15
+ COLONY_CONTEXT_TEMPLATE,
16
+ )
17
+ from .skills import (
18
+ write_apsimo_skill,
19
+ write_apsimo_check_skill,
20
+ remove_apsimo_skill,
21
+ APSIMO_DIAGNOSTIC_SKILL,
22
+ APSIMO_CHECK_SKILL,
23
+ write_colony_skill,
24
+ write_colony_check_skill,
25
+ remove_colony_skill,
26
+ get_skill_path,
27
+ get_skill_config_path,
28
+ COLONY_DIAGNOSTIC_SKILL,
29
+ COLONY_CHECK_SKILL,
30
+ )
31
+
32
+ __all__ = [
33
+ "write_apsimo_context", "write_apsimo_skill", "write_apsimo_check_skill",
34
+ "remove_apsimo_skill", "APSIMO_CONTEXT_TEMPLATE",
35
+ "APSIMO_DIAGNOSTIC_SKILL", "APSIMO_CHECK_SKILL",
36
+ "write_colony_context",
37
+ "write_colony_skill",
38
+ "write_colony_check_skill",
39
+ "remove_colony_skill",
40
+ "get_skill_path",
41
+ "get_skill_config_path",
42
+ "COLONY_CONTEXT_TEMPLATE",
43
+ "COLONY_DIAGNOSTIC_SKILL",
44
+ "COLONY_CHECK_SKILL",
45
+ ]
@@ -0,0 +1,41 @@
1
+ """Write a short, model-independent Apsimo reference into a workspace."""
2
+ from pathlib import Path
3
+
4
+ APSIMO_CONTEXT_TEMPLATE = """# Apsimo integration
5
+
6
+ Apsimo is the cognition and continuity layer, formerly ColonyAI. Your agent's
7
+ private identity is separate from the platform name. Old-name searches refer to
8
+ the same platform and history.
9
+
10
+ Use `apsimo_health` to check connectivity and `apsimo_get_context` for relevant,
11
+ authenticated context. Check `apsimo_check_commitments` before promising work.
12
+ Recall useful evidence with `apsimo_lookup_facts`; cite the returned source and
13
+ ask about contradictions. Save durable facts with `apsimo_remember_fact` only
14
+ when their source and usefulness are clear. Corrections and forgetting govern
15
+ source-backed recall across sessions; do not create diagnostic junk memories.
16
+
17
+ Use `apsimo doctor` and `apsimo mcp detect` for installation checks. Hermes uses
18
+ its native Apsimo plugin and memory provider. Coding harnesses may use the MCP
19
+ server. Both connect to the same selected instance over its authenticated API;
20
+ sharing an instance does not grant every caller access to every private source.
21
+
22
+ APSIMO_* configuration names are preferred; existing COLONY_* names remain
23
+ accepted. Use the selected instance's actual configuration and credential
24
+ reference. Never guess a key or print credentials into diagnostic transcripts.
25
+ """
26
+
27
+ # Public compatibility aliases point to the same template and writer.
28
+ COLONY_CONTEXT_TEMPLATE = APSIMO_CONTEXT_TEMPLATE
29
+
30
+
31
+ def write_apsimo_context(workspace_dir: Path) -> bool:
32
+ if not workspace_dir.exists():
33
+ return False
34
+ try:
35
+ (workspace_dir / "APSIMO.md").write_text(APSIMO_CONTEXT_TEMPLATE)
36
+ return True
37
+ except OSError:
38
+ return False
39
+
40
+
41
+ write_colony_context = write_apsimo_context
@@ -0,0 +1,231 @@
1
+ """Write Apsimo diagnostic skill to harness skills directories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+
10
+ # Skill directory paths for each harness
11
+ SKILL_PATHS = {
12
+ "openclaw": None, # Special case - uses workspace_dir
13
+ "crush": "~/.config/crush/skills/apsimo-diagnose",
14
+ "codex": "~/.codex/skills/apsimo-diagnose",
15
+ "claude-code": "~/.codex/skills/apsimo-diagnose", # Shares with Codex
16
+ "opencode": "~/.config/opencode/skills/apsimo-diagnose",
17
+ "hermes": None, # Plugin-based, no skill directory
18
+ }
19
+
20
+ # Config paths for skills_paths updates (Crush only)
21
+ CONFIG_PATHS = {
22
+ "crush": ["~/.crush.json", "~/.config/crush/crush.json"],
23
+ }
24
+
25
+
26
+ APSIMO_DIAGNOSTIC_SKILL = """---
27
+ name: apsimo-diagnose
28
+ description: Diagnose Apsimo or legacy ColonyAI connection failures and missing recalled context using the selected instance.
29
+ ---
30
+
31
+ # Apsimo diagnostics
32
+
33
+ 1. Run `apsimo status` and `apsimo doctor`. Read the reported endpoint and instance
34
+ instead of assuming a directory, machine, port, or database backend.
35
+ 2. For coding harnesses, run `apsimo mcp detect`. Confirm one `apsimo` MCP server
36
+ is configured. Legacy `colony` entries are migrated by `apsimo mcp setup --harness <name>`.
37
+ 3. Call `apsimo_health`. If it fails, report the actual transport or authentication
38
+ error. Use the selected instance's credential reference; never guess a key,
39
+ print its value, or dump a whole configuration into a transcript.
40
+ 4. If health passes, use `apsimo_get_context` for the authenticated contact and
41
+ `apsimo_lookup_facts` for a known source-backed fact. Report what was actually
42
+ retrieved. Do not insert test facts into ordinary memory.
43
+ 5. For Hermes, inspect its selected profile's plugin and memory-provider status.
44
+ Correct the indicated connection or configuration, then repeat the failed
45
+ probe once. A successful HTTP request alone does not prove useful recollection.
46
+
47
+ Apsimo is the platform's new name; Aeva or another private agent keeps its own
48
+ identity. Existing state paths and COLONY_* variables remain supported. Conflicting
49
+ explicit APSIMO_* and COLONY_* settings must be resolved at configuration load.
50
+ """
51
+ COLONY_DIAGNOSTIC_SKILL = APSIMO_DIAGNOSTIC_SKILL
52
+
53
+
54
+ def get_skill_path(harness_id: str) -> Path | None:
55
+ """Get the skill directory path for a harness.
56
+
57
+ Args:
58
+ harness_id: Harness identifier (e.g., 'crush', 'openclaw')
59
+
60
+ Returns:
61
+ Path to skill directory, or None if harness doesn't support skills
62
+ """
63
+ path_str = SKILL_PATHS.get(harness_id)
64
+ if path_str:
65
+ canonical = Path(path_str).expanduser()
66
+ legacy = canonical.with_name("colony-diagnose")
67
+ # Reuse an installed legacy directory, so setup does not discover two
68
+ # copies of one skill. New installations use the canonical directory.
69
+ return legacy if legacy.exists() and not canonical.exists() else canonical
70
+ return None
71
+
72
+
73
+ def get_skill_config_path(harness_id: str) -> Path | None:
74
+ """Get the harness config path for skills_paths update.
75
+
76
+ Args:
77
+ harness_id: Harness identifier
78
+
79
+ Returns:
80
+ Path to config file, or None if not applicable
81
+ """
82
+ config_paths = CONFIG_PATHS.get(harness_id, [])
83
+ for path_str in config_paths:
84
+ path = Path(path_str).expanduser()
85
+ if path.exists():
86
+ return path
87
+ return None
88
+
89
+
90
+ def write_apsimo_skill(harness_id: str, workspace_dir: Path | None = None) -> bool:
91
+ """Write Apsimo diagnostic skill to harness skills directory.
92
+
93
+ For Crush: also updates options.skills_paths in config.
94
+
95
+ Args:
96
+ harness_id: Harness identifier (e.g., 'crush', 'openclaw')
97
+ workspace_dir: Optional workspace dir (used for OpenClaw to override)
98
+
99
+ Returns:
100
+ True if written successfully, False otherwise (including if harness doesn't support skills)
101
+ """
102
+ # Special handling for OpenClaw - use workspace skills dir
103
+ if harness_id == "openclaw" and workspace_dir:
104
+ skill_dir = workspace_dir / "skills" / "apsimo-diagnose"
105
+ else:
106
+ skill_dir = get_skill_path(harness_id)
107
+
108
+ # Harness doesn't support skills (e.g., Hermes)
109
+ if not skill_dir:
110
+ return False
111
+
112
+ try:
113
+ skill_dir.mkdir(parents=True, exist_ok=True)
114
+ skill_md = skill_dir / "SKILL.md"
115
+ skill_md.write_text(COLONY_DIAGNOSTIC_SKILL)
116
+
117
+ # For Crush, update skills_paths in config
118
+ if harness_id == "crush":
119
+ _update_crush_skills_paths()
120
+
121
+ return True
122
+ except Exception:
123
+ return False
124
+
125
+
126
+ def remove_apsimo_skill(harness_id: str) -> bool:
127
+ """Remove Apsimo diagnostic skill from harness skills directory.
128
+
129
+ Args:
130
+ harness_id: Harness identifier
131
+
132
+ Returns:
133
+ True if removed successfully, False if not found or error
134
+ """
135
+ skill_dir = get_skill_path(harness_id)
136
+
137
+ if not skill_dir or not skill_dir.exists():
138
+ return False
139
+
140
+ try:
141
+ shutil.rmtree(skill_dir)
142
+ return True
143
+ except Exception:
144
+ return False
145
+
146
+
147
+ def _update_crush_skills_paths() -> bool:
148
+ """Internal: Add ~/.config/crush/skills to Crush's skills_paths if not present.
149
+
150
+ Crush config can be at:
151
+ 1. ~/.crush.json (checked first by Crush)
152
+ 2. ~/.config/crush/crush.json (checked second)
153
+
154
+ We check both and update whichever exists, preferring ~/.crush.json.
155
+ If neither exists, we don't create one just for skills_paths.
156
+ """
157
+ skill_path_to_add = "~/.config/crush/skills"
158
+
159
+ # Find existing config
160
+ config_path = get_skill_config_path("crush")
161
+ if not config_path:
162
+ return False # No config exists, don't create one
163
+
164
+ try:
165
+ data = json.loads(config_path.read_text())
166
+ except (json.JSONDecodeError, FileNotFoundError):
167
+ return False
168
+
169
+ # Check if skills_paths already includes our path
170
+ options = data.get("options", {})
171
+ skills_paths = options.get("skills_paths", [])
172
+
173
+ # Normalize paths for comparison
174
+ normalized_new = str(Path(skill_path_to_add).expanduser())
175
+ normalized_existing = [str(Path(p).expanduser()) for p in skills_paths]
176
+
177
+ if normalized_new in normalized_existing:
178
+ return True # Already present
179
+
180
+ # Add the path
181
+ skills_paths.append(skill_path_to_add)
182
+ options["skills_paths"] = skills_paths
183
+ data["options"] = options
184
+
185
+ try:
186
+ config_path.write_text(json.dumps(data, indent=2))
187
+ return True
188
+ except Exception:
189
+ return False
190
+
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # Apsimo Check Skill (for on-demand initiative checking)
194
+ # ---------------------------------------------------------------------------
195
+
196
+ APSIMO_CHECK_SKILL = """---
197
+ name: apsimo-check
198
+ description: Review current commitments and useful work through the selected Apsimo instance.
199
+ ---
200
+
201
+ Use `apsimo_get_context` and `apsimo_check_commitments` for the authenticated
202
+ contact. Summarize overdue work, current commitments, and source-backed surprises.
203
+ Distinguish observations from proposed actions. Respect existing task ownership
204
+ and execution limits; do not create duplicate work or claim that a task ran
205
+ without its execution evidence. Do not write a memory merely because this check
206
+ was performed. If a tool fails, report its actual blocker and change strategy.
207
+ """
208
+ COLONY_CHECK_SKILL = APSIMO_CHECK_SKILL
209
+
210
+
211
+ def write_apsimo_check_skill(workspace_dir: Path) -> bool:
212
+ """Write apsimo-check skill to OpenClaw workspace skills directory.
213
+
214
+ Args:
215
+ workspace_dir: OpenClaw workspace directory
216
+
217
+ Returns:
218
+ True if written successfully, False otherwise
219
+ """
220
+ skill_dir = workspace_dir / "skills" / "apsimo-check"
221
+ try:
222
+ skill_dir.mkdir(parents=True, exist_ok=True)
223
+ (skill_dir / "SKILL.md").write_text(COLONY_CHECK_SKILL)
224
+ return True
225
+ except Exception:
226
+ return False
227
+
228
+
229
+ write_colony_skill = write_apsimo_skill
230
+ write_colony_check_skill = write_apsimo_check_skill
231
+ remove_colony_skill = remove_apsimo_skill
@@ -0,0 +1,26 @@
1
+ """Identity normalization service (v0.16.0).
2
+
3
+ Maps between the identifier formats Colony uses for a person:
4
+ contact-store CID, Neo4j Person node ID, display name, and platform
5
+ handles (email, phone, etc.).
6
+ """
7
+
8
+ from apsimo.identity.resolver import (
9
+ IdentityResolver,
10
+ OwnerIdentityError,
11
+ get_identity_resolver,
12
+ get_owner_contact_id,
13
+ get_owner_name,
14
+ get_persona_name,
15
+ reset_identity_resolver,
16
+ )
17
+
18
+ __all__ = [
19
+ "IdentityResolver",
20
+ "OwnerIdentityError",
21
+ "get_identity_resolver",
22
+ "get_owner_contact_id",
23
+ "get_owner_name",
24
+ "get_persona_name",
25
+ "reset_identity_resolver",
26
+ ]
@@ -0,0 +1,181 @@
1
+ """ParticipantResolver -- per-message sender -> contact attribution.
2
+
3
+ The attribution chokepoint for relationship intelligence (docs/RELATIONSHIPS.md):
4
+ every synced turn that carries a ``sender`` resolves to a real contact here,
5
+ server-side, regardless of client caching. Unknown people become shadow
6
+ contacts so history accrues from first contact; machines resolve to the
7
+ reserved ``system`` sentinel and never touch relationship stores.
8
+
9
+ Resolution ladder (first hit wins):
10
+ 1. exact / cross-gateway messaging handle (phones unify across sms/rcs/
11
+ whatsapp/signal/imessage via phone_key; emails normalize)
12
+ 2. scoped display-name: propose a candidate association, without attributing
13
+ the sender to that person's identity or private history
14
+ 3. shadow contact (tier unknown, interaction_allowed=false, provenance
15
+ recorded), when COLONY_IDENTITY_SHADOW_CONTACTS (default true)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import os
22
+ from dataclasses import dataclass
23
+ from typing import Any, Optional
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ #: Reserved contact id for machine-origin turns. Comms may record it for ops
28
+ #: visibility; ToM/interactions/relationship surfaces must always exclude it.
29
+ SYSTEM_CONTACT_ID = "system"
30
+
31
+
32
+ def shadow_contacts_enabled() -> bool:
33
+ return os.environ.get(
34
+ "COLONY_IDENTITY_SHADOW_CONTACTS", "true").strip().lower() != "false"
35
+
36
+
37
+ def machine_channel_prefixes() -> tuple:
38
+ raw = os.environ.get("COLONY_IDENTITY_MACHINE_CHANNELS", "cron,api,internal")
39
+ return tuple(p.strip().lower() for p in raw.split(",") if p.strip())
40
+
41
+
42
+ def is_machine_turn(channel_id: str, user_text: str, has_sender: bool) -> bool:
43
+ """A turn is machine-origin when it has no human sender AND either rides
44
+ a machine channel or carries system-origin text markers. A turn WITH a
45
+ resolved human sender is never reclassified (a human talking on an api
46
+ channel is still a human)."""
47
+ if has_sender:
48
+ return False
49
+ ch = (channel_id or "").strip().lower()
50
+ for prefix in machine_channel_prefixes():
51
+ if ch == prefix or ch.startswith(prefix + ":"):
52
+ return True
53
+ try:
54
+ from apsimo.delivery.reachout_policy import is_system_origin
55
+ if is_system_origin(user_text):
56
+ return True
57
+ except ImportError:
58
+ pass
59
+ return False
60
+
61
+
62
+ @dataclass
63
+ class Resolution:
64
+ contact_id: Optional[str]
65
+ method: str # handle | scoped_name | shadow | none
66
+ created: bool = False
67
+ proposal_filed: bool = False
68
+ candidate_contact_id: Optional[str] = None
69
+ proposal_id: Optional[str] = None
70
+
71
+
72
+ class ParticipantResolver:
73
+ def __init__(self, contacts_store: Any) -> None:
74
+ self._store = contacts_store
75
+
76
+ async def resolve(self, *, platform: str, user_id: str,
77
+ display_name: str = "", group_id: str = "",
78
+ channel_id: str = "") -> Resolution:
79
+ """Resolve a sender to a contact id (see module doc for the ladder)."""
80
+ platform = (platform or "").strip().lower()
81
+ user_id = (user_id or "").strip()
82
+ if not user_id:
83
+ return Resolution(None, "none")
84
+
85
+ # 0. Already-canonical contact id (e.g. the voice channel, where the
86
+ # speaker was resolved to a contact upstream and passed as user_id).
87
+ # Confirm it exists rather than minting a shadow for it.
88
+ if user_id.startswith("cid-"):
89
+ try:
90
+ if await self._store.get(user_id) is not None:
91
+ return Resolution(user_id, "contact_id")
92
+ except Exception:
93
+ pass
94
+
95
+ # 1. Handle match (exact + cross-gateway phone + email normalization).
96
+ try:
97
+ c = await self._store.resolve_messaging_handle(platform, user_id)
98
+ except Exception:
99
+ logger.debug("participant handle resolve failed", exc_info=True)
100
+ c = None
101
+ if c is not None:
102
+ return Resolution(c.contact_id, "handle")
103
+
104
+ # 2. A name can suggest a link; it cannot establish attribution.
105
+ candidate, proposal = None, None
106
+ if display_name and group_id:
107
+ match = await self._scoped_name_match(platform, group_id, display_name)
108
+ if match is not None:
109
+ proposal = await self._file_handle_proposal(
110
+ match, platform, user_id, display_name)
111
+ candidate = match
112
+
113
+ # 3. Shadow contact.
114
+ if not shadow_contacts_enabled():
115
+ return Resolution(None, "none", proposal_filed=bool(proposal),
116
+ candidate_contact_id=candidate, proposal_id=proposal)
117
+ try:
118
+ contact = await self._store.create(
119
+ display_name=display_name or user_id,
120
+ trust_tier="unknown",
121
+ interaction_allowed=False,
122
+ import_source="auto:sender",
123
+ notes=(f"Auto-created from first contact on {channel_id or platform}"
124
+ + (f" (group {group_id})" if group_id else "")),
125
+ )
126
+ await self._store.add_handle(
127
+ contact.contact_id, platform, user_id,
128
+ is_primary=True, confidence=0.9, source="auto:sender")
129
+ logger.info("Shadow contact %s created for %s:%s (%r)",
130
+ contact.contact_id, platform, user_id,
131
+ display_name or "?")
132
+ return Resolution(contact.contact_id, "shadow", created=True,
133
+ proposal_filed=bool(proposal), candidate_contact_id=candidate,
134
+ proposal_id=proposal)
135
+ except Exception:
136
+ logger.warning("shadow contact creation failed for %s:%s",
137
+ platform, user_id, exc_info=True)
138
+ return Resolution(None, "none")
139
+
140
+ # -- rung 2 helpers ----------------------------------------------------
141
+ async def _scoped_name_match(self, platform: str, group_id: str,
142
+ display_name: str) -> Optional[str]:
143
+ """The display name matches exactly ONE member of this group's scope."""
144
+ try:
145
+ scope = await self._store.get_scope(
146
+ platform=platform, external_id=str(group_id))
147
+ if scope is None:
148
+ return None
149
+ members = await self._store.scope_members(scope.scope_id)
150
+ except Exception:
151
+ return None
152
+ hits = []
153
+ want = display_name.strip().lower()
154
+ for m in members or []:
155
+ cid = getattr(m, "contact_id", None) or (
156
+ m.get("contact_id") if isinstance(m, dict) else None)
157
+ if not cid:
158
+ continue
159
+ try:
160
+ c = await self._store.get(cid)
161
+ except Exception:
162
+ continue
163
+ if c is None:
164
+ continue
165
+ names = {str(c.display_name or "").strip().lower(),
166
+ str(getattr(c, "given_name", "") or "").strip().lower()}
167
+ if want and (want in names
168
+ or any(n and want.split()[0] == n.split()[0]
169
+ for n in names if n)):
170
+ hits.append(c.contact_id)
171
+ return hits[0] if len(hits) == 1 else None
172
+
173
+ async def _file_handle_proposal(self, contact_id: str, platform: str,
174
+ user_id: str, display_name: str) -> Optional[str]:
175
+ """Keep the hypothesis outside the handle index used for attribution."""
176
+ try:
177
+ result = await self._store.propose_handle_link(contact_id, platform, user_id)
178
+ return result['candidate_id'] if result['status'] == 'pending' else None
179
+ except Exception:
180
+ logger.debug("handle proposal failed", exc_info=True)
181
+ return None