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,329 @@
1
+ """IdentityResolver — normalize person identifiers across Colony subsystems.
2
+
3
+ A person is referenced by different identifier formats depending on the
4
+ subsystem:
5
+
6
+ - Contact store: CID (``cid-<ts>-<rand>``), display name, platform handles
7
+ - Neo4j graph: Person node ID (UUID), ``Person.name``
8
+ - Affect store: ``contact_id`` (whatever the caller passed — historically
9
+ display names or CIDs)
10
+
11
+ The contact store is the source of truth (Step 0 finding, v0.16.0 spec):
12
+ a Contact record carries the CID, names, the linked Neo4j node via
13
+ ``person_node_id``, and all platform handles via the ``contact_handles``
14
+ table. The resolver is an index over it.
15
+
16
+ Owner identity rules:
17
+ - The owner is resolved once from ``COLONY_OWNER_CONTACT_ID`` and cached.
18
+ - ``COLONY_HOST_CONTACT_ID`` is accepted as a deprecated alias.
19
+ - A configured-but-unresolvable owner raises :class:`OwnerIdentityError`.
20
+ There is NO fallback to a default string — the silent ``"owner"``
21
+ default was the bug this module replaces. Callers that filter the owner
22
+ out of generated work must fail closed (generate nothing) rather than
23
+ fall through.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import logging
29
+ import os
30
+ import re
31
+ from typing import Any, Optional, Set
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ _PHONE_RE = re.compile(r"^\+?[\d\s().-]{7,}$")
36
+
37
+ from apsimo.channels.phone_gateways import get_phone_gateways as _get_phone_gateways
38
+
39
+
40
+ class OwnerIdentityError(RuntimeError):
41
+ """The owner's identity is missing or cannot be resolved."""
42
+
43
+
44
+ def get_owner_contact_id() -> Optional[str]:
45
+ """Return the configured owner contact ID, honouring the legacy alias.
46
+
47
+ ``COLONY_OWNER_CONTACT_ID`` is canonical. ``COLONY_HOST_CONTACT_ID``
48
+ is accepted with a deprecation warning so running deployments keep
49
+ their config. Returns None when neither is set — callers must treat
50
+ that as "owner unknown", never as a default string.
51
+ """
52
+ owner = os.environ.get("COLONY_OWNER_CONTACT_ID")
53
+ if owner:
54
+ return owner
55
+ legacy = os.environ.get("COLONY_HOST_CONTACT_ID")
56
+ if legacy:
57
+ logger.warning(
58
+ "COLONY_HOST_CONTACT_ID is deprecated, use COLONY_OWNER_CONTACT_ID"
59
+ )
60
+ return legacy
61
+ return None
62
+
63
+
64
+ def get_owner_name(default: str = "the owner") -> str:
65
+ """Human-readable owner name for prompts/messages.
66
+
67
+ Deployment-agnostic: the generic build must never hardcode a person's name.
68
+ Set ``COLONY_OWNER_NAME`` in the deployment env; otherwise a neutral term
69
+ ("the owner") is used so the public code carries no personal identity.
70
+ """
71
+ return os.environ.get("COLONY_OWNER_NAME") or default
72
+
73
+
74
+ def get_persona_name(default: str = "the assistant") -> str:
75
+ """Human-readable persona/agent name for tool descriptions and prompts.
76
+
77
+ Same rule: the deployment sets ``COLONY_PERSONA_NAME``; the generic default
78
+ is neutral so the public code names no specific product.
79
+ """
80
+ return os.environ.get("COLONY_PERSONA_NAME") or default
81
+
82
+
83
+ class IdentityResolver:
84
+ """Resolve any identifier for a person to the set of all known forms.
85
+
86
+ Backed by the contact store (async API). When the contact store is
87
+ unavailable the resolver degrades to exact-match on the configured
88
+ value only — it never invents identifiers.
89
+ """
90
+
91
+ def __init__(
92
+ self,
93
+ contact_store: Any = None,
94
+ owner_id: Optional[str] = None,
95
+ ) -> None:
96
+ self._contact_store = contact_store
97
+ # Capture explicitly so tests can construct without env mutation.
98
+ self._owner_id = owner_id if owner_id is not None else get_owner_contact_id()
99
+ self._owner_set: Optional[frozenset] = None
100
+ self._identity_revision = None
101
+ # Small bounded cache of is_owner verdicts (one tick scans ~100
102
+ # contacts; name resolution is a full-table similarity scan).
103
+ self._is_owner_cache: dict = {}
104
+
105
+ # ------------------------------------------------------------------
106
+ # Public API
107
+ # ------------------------------------------------------------------
108
+
109
+ async def resolve(self, any_id: str) -> Set[str]:
110
+ """Given any identifier for a person, return all known forms.
111
+
112
+ Returns an empty set when the identifier is unknown or ambiguous
113
+ (display names are not unique — an ambiguous name must not merge
114
+ two people into one).
115
+ """
116
+ if not any_id or not isinstance(any_id, str):
117
+ return set()
118
+ contact = await self._lookup(any_id.strip())
119
+ if contact is None:
120
+ return set()
121
+ return await self._identity_set(contact)
122
+
123
+ async def is_owner(self, any_id: Any) -> bool:
124
+ """True if ``any_id`` resolves into the owner's identity set.
125
+
126
+ Raises :class:`OwnerIdentityError` when the owner identity itself
127
+ cannot be established (missing config, or config that resolves to
128
+ nothing). Callers in generation paths must fail closed on that.
129
+ """
130
+ if not any_id:
131
+ return False
132
+ owners = await self.owner_identities()
133
+ key = str(any_id).strip()
134
+ cached = self._is_owner_cache.get(key)
135
+ if cached is not None:
136
+ return cached
137
+
138
+ verdict = key in owners or key.lower() in owners
139
+ if not verdict:
140
+ # Cross-format check: resolve the candidate and intersect.
141
+ try:
142
+ forms = await self.resolve(key)
143
+ except Exception as exc: # resolution must never break filtering
144
+ logger.warning("Identity resolution failed for %r: %s", key, exc)
145
+ forms = set()
146
+ verdict = bool(forms & owners)
147
+
148
+ if len(self._is_owner_cache) > 512:
149
+ self._is_owner_cache.clear()
150
+ self._is_owner_cache[key] = verdict
151
+ return verdict
152
+
153
+ async def owner_identities(self) -> frozenset:
154
+ """Resolve and cache the owner's full identity set.
155
+
156
+ Raises :class:`OwnerIdentityError` if no owner is configured, or if
157
+ the configured value resolves to nothing in the contact store.
158
+ """
159
+ revision_reader = getattr(self._contact_store, 'identity_revision', None)
160
+ if revision_reader is not None:
161
+ revision = await revision_reader()
162
+ if revision != self._identity_revision:
163
+ self._owner_set = None
164
+ self._is_owner_cache.clear()
165
+ self._identity_revision = revision
166
+ if self._owner_set is not None:
167
+ return self._owner_set
168
+
169
+ owner_id = self._owner_id
170
+ if not owner_id:
171
+ raise OwnerIdentityError(
172
+ "COLONY_OWNER_CONTACT_ID is not set. Owner-exclusion filters "
173
+ "cannot run; set it to the owner's contact CID, Neo4j Person "
174
+ "ID, or an unambiguous display name."
175
+ )
176
+
177
+ if self._contact_store is None:
178
+ # No identity index available — exact-match on the configured
179
+ # value (plus case folding). This is the operator's own value,
180
+ # not a made-up default, so it is safe to use directly.
181
+ logger.warning(
182
+ "Contact store unavailable — owner exclusion limited to "
183
+ "exact matches on %r", owner_id,
184
+ )
185
+ self._owner_set = frozenset({owner_id, owner_id.lower()})
186
+ return self._owner_set
187
+
188
+ forms = await self.resolve(owner_id)
189
+ if not forms:
190
+ raise OwnerIdentityError(
191
+ f"COLONY_OWNER_CONTACT_ID={owner_id!r} does not resolve to "
192
+ "any contact. Fix the config — refusing to guess."
193
+ )
194
+ # Always include the configured literal so equality checks against
195
+ # raw config values keep working.
196
+ forms |= {owner_id, owner_id.lower()}
197
+ self._owner_set = frozenset(forms)
198
+ logger.info(
199
+ "Owner identity resolved: %d identifier forms cached", len(forms)
200
+ )
201
+ return self._owner_set
202
+
203
+ def invalidate(self) -> None:
204
+ """Drop cached owner identity and verdicts (e.g. after a contact merge)."""
205
+ self._owner_set = None
206
+ self._is_owner_cache.clear()
207
+
208
+ # ------------------------------------------------------------------
209
+ # Internals
210
+ # ------------------------------------------------------------------
211
+
212
+ async def _lookup(self, any_id: str) -> Any:
213
+ """Find the Contact record for an identifier of any format."""
214
+ store = self._contact_store
215
+ if store is None:
216
+ return None
217
+
218
+ # CID — primary key lookup.
219
+ if any_id.startswith("cid-"):
220
+ return await store.get(any_id)
221
+
222
+ # Email handle.
223
+ if "@" in any_id:
224
+ return await store.resolve_handle("email", any_id)
225
+
226
+ # Phone-like handle.
227
+ if _PHONE_RE.match(any_id):
228
+ for gateway in _get_phone_gateways():
229
+ contact = await store.resolve_handle(gateway, any_id)
230
+ if contact is not None:
231
+ return contact
232
+ return None
233
+
234
+ # Neo4j Person node ID (UUID) — forward link lives on the contact.
235
+ contact = await store.find_by_person_node_id(any_id)
236
+ if contact is not None:
237
+ return contact
238
+
239
+ # Display name — only when unambiguous. Names are not unique.
240
+ try:
241
+ matches = await store.find_by_name(any_id, threshold=0.9)
242
+ except Exception as exc:
243
+ logger.warning("find_by_name failed for %r: %s", any_id, exc)
244
+ return None
245
+ wanted = any_id.lower()
246
+ exact = [
247
+ m for m in matches
248
+ if (m.display_name or "").strip().lower() == wanted
249
+ ]
250
+ candidates = exact or matches
251
+ if len(candidates) == 1:
252
+ return candidates[0]
253
+ if len(candidates) > 1:
254
+ logger.warning(
255
+ "Ambiguous identity %r: %d contacts match — returning no "
256
+ "identity rather than merging people", any_id, len(candidates),
257
+ )
258
+ return None
259
+
260
+ async def _identity_set(self, contact: Any) -> Set[str]:
261
+ """All known identifier forms for a resolved contact."""
262
+ forms: Set[str] = {contact.contact_id}
263
+
264
+ display_name = getattr(contact, "display_name", None)
265
+ if display_name:
266
+ forms.add(display_name)
267
+ given = getattr(contact, "given_name", None)
268
+ family = getattr(contact, "family_name", None)
269
+ if given and family:
270
+ forms.add(f"{given} {family}")
271
+ person_node_id = getattr(contact, "person_node_id", None)
272
+ if person_node_id:
273
+ forms.add(person_node_id)
274
+
275
+ if self._contact_store is not None:
276
+ try:
277
+ handles = await self._contact_store.get_handles(contact.contact_id)
278
+ for handle in handles:
279
+ from apsimo.contacts.identity_links import usable_handle
280
+ if not usable_handle(handle):
281
+ continue
282
+ address = getattr(handle, "address", None)
283
+ if address:
284
+ forms.add(address)
285
+ except Exception as exc:
286
+ logger.debug(
287
+ "Handle lookup failed for %s: %s", contact.contact_id, exc
288
+ )
289
+
290
+ # Case-folded variants for case-insensitive membership checks.
291
+ forms |= {f.lower() for f in list(forms)}
292
+ return forms
293
+
294
+
295
+ # ---------------------------------------------------------------------------
296
+ # Module-level singleton
297
+ # ---------------------------------------------------------------------------
298
+
299
+ _resolver: Optional[IdentityResolver] = None
300
+
301
+
302
+ def get_identity_resolver(contact_store: Any = None) -> IdentityResolver:
303
+ """Return the process-wide resolver, creating it on first use.
304
+
305
+ When ``contact_store`` is not supplied, the contact store registered
306
+ with the host router is used (same late-binding pattern as
307
+ ``SubsystemRegistry``).
308
+ """
309
+ global _resolver
310
+ if contact_store is None:
311
+ try:
312
+ from apsimo.api.routers import host as _host_mod
313
+ contact_store = getattr(_host_mod, "_contacts_store", None)
314
+ except Exception:
315
+ contact_store = None
316
+
317
+ if _resolver is None:
318
+ _resolver = IdentityResolver(contact_store=contact_store)
319
+ elif contact_store is not None and _resolver._contact_store is None:
320
+ # The contact store comes up after the resolver in some boot orders.
321
+ _resolver._contact_store = contact_store
322
+ _resolver.invalidate()
323
+ return _resolver
324
+
325
+
326
+ def reset_identity_resolver() -> None:
327
+ """Reset the singleton (tests and reconfiguration)."""
328
+ global _resolver
329
+ _resolver = None
@@ -0,0 +1,5 @@
1
+ """Colony Identity Bootstrap — seeds every data system with self-knowledge at startup."""
2
+
3
+ from apsimo.identity_bootstrap.runner import IdentityBootstrap
4
+
5
+ __all__ = ["IdentityBootstrap"]
@@ -0,0 +1,208 @@
1
+ """Colony Identity Bootstrap — IdentityBootstrapBuilder.
2
+
3
+ Assembles a SelfKnowledgeCorpus at runtime by reading colony_id, version,
4
+ network info, and public key from the running system.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import os
11
+ from typing import Any, Optional
12
+
13
+ from apsimo.identity_bootstrap.corpus import (
14
+ CORPUS_VERSION,
15
+ GATE_LAYERS,
16
+ INFERENCE_TIERS,
17
+ API_ENDPOINTS,
18
+ COGNITION_PHASES,
19
+ LAYERS,
20
+ InferenceTier,
21
+ SelfKnowledgeCorpus,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def _colony_version() -> str:
28
+ """Return Colony version from canonical source."""
29
+ # Canonical: colony_cli.__init__ (full_version_string returns "0.3.0-h6")
30
+ try:
31
+ from colony_cli import full_version_string
32
+ return full_version_string()
33
+ except Exception:
34
+ pass
35
+ # Fallback: importlib metadata (reads pyproject.toml version)
36
+ try:
37
+ from importlib.metadata import version
38
+ return version("colony")
39
+ except Exception:
40
+ pass
41
+ try:
42
+ from importlib.metadata import version
43
+ return version("colony-ai")
44
+ except Exception:
45
+ pass
46
+ return "0.3.0-h6"
47
+
48
+
49
+ def _read_colony_id(chain_manager: Optional[Any]) -> str:
50
+ """Read colony_id from ChainManager, chain.db, or environment.
51
+
52
+ Priority order:
53
+ 1. ChainManager.colony_id (if ChainManager is initialized)
54
+ 2. COLONY_ID environment variable
55
+ 3. Genesis block genesis_colony_id from chain.db (crypto identity)
56
+ 4. Hostname-based fallback (last resort)
57
+ """
58
+ if chain_manager is not None:
59
+ try:
60
+ cid = chain_manager.colony_id
61
+ if cid and cid != "local":
62
+ return str(cid)
63
+ except Exception as exc:
64
+ logger.debug("ChainManager.colony_id unavailable: %s", exc)
65
+
66
+ env_id = os.environ.get("COLONY_ID", "")
67
+ if env_id:
68
+ return env_id
69
+
70
+ # Read genesis_colony_id directly from chain.db — this is the
71
+ # cryptographic identity (sha256 of Ed25519 pubkey) that proves
72
+ # genesis admin authority. Avoids the hostname fallback when the
73
+ # ChainManager singleton hasn't been constructed yet.
74
+ import json
75
+ import sqlite3
76
+ colony_home = os.environ.get(
77
+ "COLONY_HOME",
78
+ os.path.join(os.path.expanduser("~"), ".colony"),
79
+ )
80
+ chain_db = os.path.join(colony_home, "chain.db")
81
+ if os.path.exists(chain_db):
82
+ try:
83
+ conn = sqlite3.connect(chain_db)
84
+ row = conn.execute(
85
+ "SELECT raw_json FROM chain_blocks WHERE block_index = 0"
86
+ ).fetchone()
87
+ conn.close()
88
+ if row:
89
+ genesis_data = json.loads(row[0])
90
+ cid = genesis_data.get("metadata", {}).get("genesis_colony_id")
91
+ if cid:
92
+ return cid
93
+ except Exception as exc:
94
+ logger.debug("_read_colony_id chain.db fallback failed: %s", exc)
95
+
96
+ # Last resort: hostname-based stable ID
97
+ import socket
98
+ import hashlib
99
+ raw = f"{socket.gethostname()}-colony"
100
+ return "col-" + hashlib.sha256(raw.encode()).hexdigest()[:16]
101
+
102
+
103
+ def _read_network_id(chain_manager: Optional[Any]) -> str:
104
+ try:
105
+ if chain_manager is not None:
106
+ state = getattr(chain_manager, "state", None)
107
+ if state is not None:
108
+ nid = getattr(state, "network_id", None) or getattr(state, "genesis_hash", None)
109
+ if nid:
110
+ return str(nid)
111
+ except Exception:
112
+ pass
113
+ return os.environ.get("COLONY_NETWORK_ID", "local")
114
+
115
+
116
+ def _read_public_key(chain_manager: Optional[Any]) -> Optional[str]:
117
+ try:
118
+ if chain_manager is not None:
119
+ keys = getattr(chain_manager, "keys", None)
120
+ if keys is not None:
121
+ pub = getattr(keys, "public_key_hex", None) or getattr(keys, "public_key", None)
122
+ if pub:
123
+ return str(pub)
124
+ except Exception:
125
+ pass
126
+ return None
127
+
128
+
129
+ def _resolve_inference_tiers() -> list[InferenceTier]:
130
+ """Return INFERENCE_TIERS with model names populated from the live LLMRouter config.
131
+
132
+ The static corpus stores no provider-specific model names. This function
133
+ reads them from ``colony.router.tiers.DEFAULT_TIERS`` at runtime so the
134
+ corpus always reflects whichever models are actually configured.
135
+ Falls back to the static definitions (empty model lists) if the router
136
+ module is unavailable.
137
+ """
138
+ # Tier index → ModelTier enum value in the router
139
+ try:
140
+ from apsimo.router.tiers import DEFAULT_TIERS, ModelTier # type: ignore[import]
141
+
142
+ tier_index_to_router: dict[int, ModelTier] = {
143
+ 0: ModelTier.SMALL,
144
+ 1: ModelTier.MEDIUM,
145
+ 2: ModelTier.LARGE,
146
+ }
147
+ resolved: list[InferenceTier] = []
148
+ for tier in INFERENCE_TIERS:
149
+ router_tier = tier_index_to_router.get(tier.tier_index)
150
+ if router_tier is not None and router_tier in DEFAULT_TIERS:
151
+ model_id = DEFAULT_TIERS[router_tier].model_id
152
+ resolved.append(
153
+ InferenceTier(
154
+ name=tier.name,
155
+ description=tier.description,
156
+ complexity_range=tier.complexity_range,
157
+ models=[model_id],
158
+ tier_index=tier.tier_index,
159
+ )
160
+ )
161
+ else:
162
+ resolved.append(tier)
163
+ return resolved
164
+ except Exception as exc:
165
+ logger.debug("Could not resolve inference tiers from router config: %s", exc)
166
+ return list(INFERENCE_TIERS)
167
+
168
+
169
+ class IdentityBootstrapBuilder:
170
+ """Builds a SelfKnowledgeCorpus by introspecting the running Colony instance."""
171
+
172
+ def __init__(self, chain_manager: Optional[Any] = None) -> None:
173
+ self._chain_manager = chain_manager
174
+
175
+ def build(self) -> SelfKnowledgeCorpus:
176
+ colony_id = _read_colony_id(self._chain_manager)
177
+ colony_name = os.environ.get("COLONY_NAME", f"colony-{colony_id[:8]}")
178
+ colony_version = _colony_version()
179
+ network_id = _read_network_id(self._chain_manager)
180
+ public_key_hex = _read_public_key(self._chain_manager)
181
+
182
+ corpus = SelfKnowledgeCorpus(
183
+ colony_id=colony_id,
184
+ colony_name=colony_name,
185
+ colony_version=colony_version,
186
+ network_id=network_id,
187
+ public_key_hex=public_key_hex,
188
+ layers=list(LAYERS),
189
+ api_endpoints=list(API_ENDPOINTS),
190
+ cognition_phases=list(COGNITION_PHASES),
191
+ gate_layers=list(GATE_LAYERS),
192
+ inference_tiers=_resolve_inference_tiers(),
193
+ corpus_version=CORPUS_VERSION,
194
+ properties={
195
+ "colony_home": os.environ.get(
196
+ "COLONY_HOME",
197
+ os.path.join(os.path.expanduser("~"), ".colony"),
198
+ ),
199
+ "python_version": _python_version(),
200
+ },
201
+ )
202
+ return corpus
203
+
204
+
205
+ def _python_version() -> str:
206
+ import sys
207
+ v = sys.version_info
208
+ return f"{v.major}.{v.minor}.{v.micro}"