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,162 @@
1
+ """Embedding identity and rebuild metadata in the existing source ledger.
2
+
3
+ Lance files are projections. The small active pointer and deletion fences live
4
+ with canonical source state, so retaining an old index does not undo forgetting.
5
+ No identity field asserts that remotely served weights have been inspected.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import asdict, dataclass
10
+ import hashlib
11
+ import json
12
+ import uuid
13
+
14
+
15
+ class IncompatibleIndex(ValueError):
16
+ pass
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class EmbeddingIdentity:
21
+ requested_model: str
22
+ served_model: str
23
+ declared_revision: str
24
+ dimensions: int
25
+ document_format: str = "raw-text-v1"
26
+ query_format: str = "raw-text-v1"
27
+ normalization: str = "unspecified"
28
+ quantization: str = "unspecified"
29
+
30
+ @property
31
+ def fingerprint(self) -> str:
32
+ return hashlib.sha256(json.dumps(asdict(self), sort_keys=True,
33
+ separators=(",", ":")).encode()).hexdigest()
34
+
35
+ @classmethod
36
+ def from_pipeline(cls, pipeline):
37
+ provider = pipeline._provider
38
+ config = provider._config
39
+ return cls(
40
+ requested_model=config.model_id,
41
+ served_model=getattr(provider, "_served_model", "") or "unknown",
42
+ declared_revision=getattr(config, "revision", None) or "unknown",
43
+ dimensions=pipeline.dimensions,
44
+ query_format='prefix-v1:' + pipeline.query_instruction,
45
+ normalization="provider-l2" if config.provider in {"cpu", "cuda", "mlx"} else "unspecified",
46
+ quantization=config.quantization or "unspecified",
47
+ )
48
+
49
+
50
+ class IndexCatalog:
51
+ """One deployment's primary vector index, backed by its source ledger."""
52
+
53
+ def __init__(self, ledger):
54
+ self.ledger = ledger
55
+ with ledger._connect() as conn:
56
+ conn.executescript('''
57
+ CREATE TABLE IF NOT EXISTS vector_generations (
58
+ id TEXT PRIMARY KEY, fingerprint TEXT, identity_json TEXT,
59
+ status TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
60
+ error TEXT);
61
+ CREATE TABLE IF NOT EXISTS vector_active (
62
+ slot INTEGER PRIMARY KEY CHECK(slot=1), generation_id TEXT NOT NULL);
63
+ CREATE TABLE IF NOT EXISTS vector_projection_deletions (
64
+ collection TEXT NOT NULL, entry_id TEXT NOT NULL,
65
+ PRIMARY KEY(collection, entry_id));
66
+ ''')
67
+
68
+ @staticmethod
69
+ def _record(row):
70
+ if row is None:
71
+ return None
72
+ result = dict(row)
73
+ result['identity'] = json.loads(result.pop('identity_json') or 'null')
74
+ return result
75
+
76
+ def active(self):
77
+ with self.ledger._connect() as conn:
78
+ return self._record(conn.execute('''SELECT g.* FROM vector_generations g
79
+ JOIN vector_active a ON a.generation_id=g.id WHERE a.slot=1''').fetchone())
80
+
81
+ def generations(self):
82
+ with self.ledger._connect() as conn:
83
+ return [self._record(row) for row in conn.execute('SELECT * FROM vector_generations ORDER BY created_at,id')]
84
+
85
+ def initialize(self, identity: EmbeddingIdentity, *, legacy_exists: bool):
86
+ with self.ledger._connect() as conn:
87
+ conn.execute('BEGIN IMMEDIATE')
88
+ if conn.execute('SELECT 1 FROM vector_active').fetchone():
89
+ return
90
+ if legacy_exists:
91
+ generation, fingerprint, payload, status = 'legacy', None, None, 'unverified'
92
+ else:
93
+ generation = uuid.uuid4().hex
94
+ fingerprint, payload, status = identity.fingerprint, json.dumps(asdict(identity)), 'ready'
95
+ conn.execute('INSERT INTO vector_generations(id,fingerprint,identity_json,status) VALUES(?,?,?,?)',
96
+ (generation, fingerprint, payload, status))
97
+ conn.execute('INSERT INTO vector_active(slot,generation_id) VALUES(1,?)', (generation,))
98
+
99
+ def begin(self, identity: EmbeddingIdentity):
100
+ with self.ledger._connect() as conn:
101
+ conn.execute('BEGIN IMMEDIATE')
102
+ row = conn.execute("SELECT * FROM vector_generations WHERE fingerprint=? AND status='building'",
103
+ (identity.fingerprint,)).fetchone()
104
+ if row is not None:
105
+ return self._record(row)
106
+ generation = uuid.uuid4().hex
107
+ conn.execute('INSERT INTO vector_generations(id,fingerprint,identity_json,status) VALUES(?,?,?,?)',
108
+ (generation, identity.fingerprint, json.dumps(asdict(identity)), 'building'))
109
+ return self._record(conn.execute('SELECT * FROM vector_generations WHERE id=?', (generation,)).fetchone())
110
+
111
+ def write_generation(self, identity: EmbeddingIdentity):
112
+ # New writes follow a matching rebuild, including a same-model rebuild.
113
+ # This keeps new arrivals in the generation that will be promoted.
114
+ with self.ledger._connect() as conn:
115
+ row = conn.execute("SELECT * FROM vector_generations WHERE fingerprint=? AND status='building'",
116
+ (identity.fingerprint,)).fetchone()
117
+ if row is not None:
118
+ return self._record(row)
119
+ active = self.active()
120
+ if active and active['fingerprint'] == identity.fingerprint:
121
+ return active
122
+ raise IncompatibleIndex('Embedding identity has no compatible index; rebuild before semantic use')
123
+
124
+ def read_generation(self, identity: EmbeddingIdentity):
125
+ active = self.active()
126
+ if not active or active['fingerprint'] != identity.fingerprint:
127
+ raise IncompatibleIndex('Active embedding index is unverified or incompatible; lexical recall remains available')
128
+ return active
129
+
130
+ def finish(self, generation_id: str, *, error: str):
131
+ with self.ledger._connect() as conn:
132
+ conn.execute("UPDATE vector_generations SET error=? WHERE id=? AND status='building'",
133
+ (error, generation_id))
134
+
135
+ def promote(self, generation_id: str, identity: EmbeddingIdentity):
136
+ with self.ledger._connect() as conn:
137
+ conn.execute('BEGIN IMMEDIATE')
138
+ row = conn.execute('SELECT * FROM vector_generations WHERE id=?', (generation_id,)).fetchone()
139
+ if row is None or row['status'] != 'building' or row['fingerprint'] != identity.fingerprint:
140
+ raise IncompatibleIndex('Only a complete generation matching the selected embedding identity can be promoted')
141
+ # The caller has finished writing all collections. Completion and
142
+ # pointer movement are one commit: interruption cannot strand a
143
+ # ready generation containing arrivals absent from the old index.
144
+ conn.execute("UPDATE vector_generations SET status='ready',error=NULL WHERE id=?", (generation_id,))
145
+ conn.execute("UPDATE vector_generations SET status='retained' WHERE id=(SELECT generation_id FROM vector_active WHERE slot=1) AND id<>?", (generation_id,))
146
+ conn.execute('UPDATE vector_active SET generation_id=? WHERE slot=1', (generation_id,))
147
+
148
+ def delete(self, collection: str, entry_id: str):
149
+ with self.ledger._connect() as conn:
150
+ conn.execute('INSERT OR IGNORE INTO vector_projection_deletions VALUES(?,?)', (collection, entry_id))
151
+
152
+ def deleted(self, collection: str, entry_id: str) -> bool:
153
+ with self.ledger._connect() as conn:
154
+ return conn.execute('SELECT 1 FROM vector_projection_deletions WHERE collection=? AND entry_id=?',
155
+ (collection, entry_id)).fetchone() is not None
156
+
157
+ def source_erased(self, metadata) -> bool:
158
+ if (metadata or {}).get('source_projection'):
159
+ from apsimo.turns.source_vectors import hydrate
160
+ return hydrate(self.ledger, metadata) is None
161
+ source = (metadata or {}).get('source_uri', '')
162
+ return isinstance(source, str) and source.startswith('turn:') and self.ledger.is_projection_erased(source[5:])
@@ -0,0 +1,334 @@
1
+ """Colony Vector Store — tier migration.
2
+
3
+ Migrates all vectors from an old embedding model to a new one.
4
+ Re-embeds every vector with the current pipeline, updates metadata,
5
+ and optionally updates the .env file with the new configuration.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import logging
12
+ import os
13
+ import time
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class MigrationResult:
23
+ """Result of a tier migration run."""
24
+
25
+ collections_migrated: int = 0
26
+ vectors_migrated: int = 0
27
+ vectors_failed: int = 0
28
+ duration_s: float = 0.0
29
+ errors: list[str] = field(default_factory=list)
30
+ generation_id: str = ""
31
+ fingerprint: str = ""
32
+
33
+
34
+ @dataclass
35
+ class MigrationState:
36
+ """Persisted state of an in-progress or completed migration."""
37
+
38
+ from_model: str = ""
39
+ to_model: str = ""
40
+ collections_done: list[str] = field(default_factory=list)
41
+ collections_remaining: list[str] = field(default_factory=list)
42
+ started_at: float = 0.0
43
+ completed_at: Optional[float] = None
44
+
45
+ def to_dict(self) -> dict:
46
+ return {
47
+ "from_model": self.from_model,
48
+ "to_model": self.to_model,
49
+ "collections_done": self.collections_done,
50
+ "collections_remaining": self.collections_remaining,
51
+ "started_at": self.started_at,
52
+ "completed_at": self.completed_at,
53
+ }
54
+
55
+ @classmethod
56
+ def from_dict(cls, d: dict) -> MigrationState:
57
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
58
+
59
+
60
+ def _migration_state_path() -> Path:
61
+ state_dir = os.environ.get("COLONY_STATE_DIR", ".")
62
+ return Path(state_dir) / "migration_state.json"
63
+
64
+
65
+ def load_migration_state() -> Optional[MigrationState]:
66
+ """Load persisted migration state, or None if no migration in progress."""
67
+ path = _migration_state_path()
68
+ if not path.exists():
69
+ return None
70
+ try:
71
+ data = json.loads(path.read_text())
72
+ return MigrationState.from_dict(data)
73
+ except Exception as exc:
74
+ logger.warning("Failed to load migration state: %s", exc)
75
+ return None
76
+
77
+
78
+ def save_migration_state(state: MigrationState) -> None:
79
+ """Persist migration state to disk."""
80
+ path = _migration_state_path()
81
+ try:
82
+ path.write_text(json.dumps(state.to_dict(), indent=2))
83
+ except Exception as exc:
84
+ logger.warning("Failed to save migration state: %s", exc)
85
+
86
+
87
+ def clear_migration_state() -> None:
88
+ """Remove migration state file."""
89
+ path = _migration_state_path()
90
+ try:
91
+ path.unlink(missing_ok=True)
92
+ except Exception:
93
+ pass
94
+
95
+
96
+ async def migrate_tier(
97
+ store,
98
+ pipeline,
99
+ old_model_id: Optional[str] = None,
100
+ batch_size: int = 64,
101
+ graph=None,
102
+ ) -> MigrationResult:
103
+ """Migrate all vectors to the current embedding model.
104
+
105
+ Parameters
106
+ ----------
107
+ store : VectorStore
108
+ The LanceDB vector store.
109
+ pipeline : EmbeddingPipeline
110
+ The new embedding pipeline to use.
111
+ old_model_id : str, optional
112
+ The old model ID to migrate from. If None, re-embeds all vectors
113
+ regardless of their current model_id.
114
+ batch_size : int
115
+ Number of rows to embed per batch.
116
+
117
+ Returns
118
+ -------
119
+ MigrationResult
120
+ """
121
+ if getattr(store, 'catalog', None) is not None:
122
+ return await _migrate_generation(store, pipeline, graph=graph,
123
+ old_model_id=old_model_id, batch_size=batch_size)
124
+ from apsimo.vector.collections import Collection
125
+ from apsimo.vector.backfill import _backfill_collection
126
+
127
+ start = time.monotonic()
128
+ result = MigrationResult()
129
+
130
+ # Determine current model
131
+ current_model_id = ""
132
+ if hasattr(pipeline, "_provider") and hasattr(pipeline._provider, "_config"):
133
+ current_model_id = pipeline._provider._config.model_id
134
+
135
+ # Set up migration state
136
+ state = MigrationState(
137
+ from_model=old_model_id or "unknown",
138
+ to_model=current_model_id,
139
+ collections_remaining=[c.value for c in Collection],
140
+ started_at=start,
141
+ )
142
+ save_migration_state(state)
143
+
144
+ for col in Collection:
145
+ try:
146
+ # Use backfill logic with forced re-embedding
147
+ rows = await store.scan_all(col)
148
+ to_embed = []
149
+ for row in rows:
150
+ meta_str = row.get("metadata", "{}")
151
+ try:
152
+ meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
153
+ except (json.JSONDecodeError, TypeError):
154
+ meta = {}
155
+ # If old_model_id specified, only migrate vectors from that model
156
+ if old_model_id and meta.get("model_id") != old_model_id:
157
+ continue
158
+ to_embed.append(row)
159
+
160
+ # Re-embed in batches
161
+ now = time.time()
162
+ for i in range(0, len(to_embed), batch_size):
163
+ batch = to_embed[i : i + batch_size]
164
+ texts = [row.get("text", "") for row in batch]
165
+
166
+ try:
167
+ vectors = await pipeline.embed_batch(texts)
168
+ except Exception as exc:
169
+ result.vectors_failed += len(batch)
170
+ result.errors.append(f"embed batch failed: {exc}")
171
+ continue
172
+
173
+ for row, vector in zip(batch, vectors):
174
+ try:
175
+ meta_str = row.get("metadata", "{}")
176
+ try:
177
+ meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
178
+ except (json.JSONDecodeError, TypeError):
179
+ meta = {}
180
+ meta["model_id"] = current_model_id
181
+ meta["embedded_at"] = now
182
+
183
+ await store.update(col, id=row["id"], text=row.get("text", ""), vector=vector, metadata=meta)
184
+ result.vectors_migrated += 1
185
+ except Exception as exc:
186
+ result.vectors_failed += 1
187
+ result.errors.append(f"update {row.get('id', '?')} failed: {exc}")
188
+
189
+ result.collections_migrated += 1
190
+ state.collections_done.append(col.value)
191
+ state.collections_remaining.remove(col.value)
192
+ save_migration_state(state)
193
+
194
+ except Exception as exc:
195
+ msg = f"Collection {col.value} migration failed: {exc}"
196
+ logger.error(msg)
197
+ result.errors.append(msg)
198
+
199
+ state.completed_at = time.monotonic()
200
+ save_migration_state(state)
201
+
202
+ # Update .env if migration succeeded
203
+ if result.vectors_migrated > 0 and result.errors == []:
204
+ _update_env(current_model_id, pipeline)
205
+
206
+ result.duration_s = time.monotonic() - start
207
+ return result
208
+
209
+
210
+ async def _migrate_generation(store, pipeline, *, graph, old_model_id, batch_size):
211
+ """Rebuild from retained evidence; the selected generation is never rewritten."""
212
+ from apsimo.vector.collections import Collection
213
+ from apsimo.vector.indexes import IncompatibleIndex
214
+ if store.identity != pipeline.index_identity:
215
+ raise IncompatibleIndex('The embedding pipeline and index identity do not match')
216
+ if old_model_id:
217
+ raise ValueError('A generation rebuild must include all retained rows, not one model subset')
218
+ if not 1 <= batch_size <= 128:
219
+ raise ValueError('Rebuild batch size must be between 1 and 128')
220
+ started = time.monotonic()
221
+ generation = store.catalog.begin(store.identity)
222
+ result = MigrationResult(generation_id=generation['id'], fingerprint=store.identity.fingerprint)
223
+ source_generation = store.catalog.active()
224
+
225
+ async def source_rows(collection):
226
+ if collection == Collection.MEMORIES and graph is not None:
227
+ # The graph is authoritative for memories, including rows missed by
228
+ # the old index during embedding outages. This is not a restore.
229
+ async for row in graph.iter_indexable_memories():
230
+ yield row
231
+ else:
232
+ db = await store._generation_db(source_generation)
233
+ if collection.value in await db.table_names():
234
+ table = await db.open_table(collection.value)
235
+ for row in (await table.query().select(['id', 'text', 'metadata']).to_pandas()).to_dict('records'):
236
+ yield row
237
+
238
+ async def write_batch(collection, rows):
239
+ vectors = await pipeline.embed_batch([row['text'] for row in rows])
240
+ if len(vectors) != len(rows):
241
+ raise ValueError('Embedding batch response does not match retained inputs')
242
+ for vector in vectors:
243
+ store._validate_vector(vector)
244
+ table = await store._table(collection, write=True, generation=generation)
245
+ projected_rows, eligible, projected_ids = [], [], set()
246
+ for row, vector in zip(rows, vectors):
247
+ meta = row['metadata']
248
+ if row['id'] in projected_ids or not store._eligible(collection, row['id'], meta):
249
+ continue
250
+ now = time.time()
251
+ meta = {**meta, 'embedding_fingerprint': store.identity.fingerprint,
252
+ 'model_id': store.identity.requested_model, 'served_model': store.identity.served_model,
253
+ 'declared_revision': store.identity.declared_revision, 'embedded_at': now}
254
+ projected = {'id': row['id'], 'text': row['text'], 'vector': vector,
255
+ 'metadata': json.dumps(meta, default=str),
256
+ 'scope_key': meta.get('scope_key'),
257
+ 'modality': meta.get('modality', 'text'), 'image_hash': meta.get('image_hash', ''),
258
+ 'image_ref': meta.get('image_ref', ''), 'thumbnail_ref': meta.get('thumbnail_ref', ''),
259
+ 'caption': meta.get('caption', ''), 'created_at': now, 'updated_at': now}
260
+ projected_rows.append(projected)
261
+ eligible.append((row['id'], meta))
262
+ # Legacy Lance collections can contain repeated IDs. Preserve the
263
+ # first eligible row, as the earlier per-row insert-only merge did.
264
+ projected_ids.add(row['id'])
265
+ if not projected_rows:
266
+ return
267
+ # One Lance commit per embedding batch. Existing staged rows include
268
+ # concurrent live writes and survive the insert-only merge unchanged.
269
+ await table.merge_insert('id').when_not_matched_insert_all().execute(projected_rows)
270
+ for entry_id, meta in eligible:
271
+ # A source can be erased while the batch commit is in flight.
272
+ if not store._eligible(collection, entry_id, meta):
273
+ await table.delete('id = ' + store._quoted(entry_id))
274
+ else:
275
+ result.vectors_migrated += 1
276
+
277
+ try:
278
+ for collection in Collection:
279
+ table = await store._table(collection, write=True, generation=generation)
280
+ present = {str(row['id']) for row in await table.query().select(['id']).to_list()}
281
+ batch = []
282
+ async for row in source_rows(collection):
283
+ meta = row.get('metadata') or {}
284
+ if isinstance(meta, str):
285
+ meta = json.loads(meta)
286
+ if row['id'] in present or not row.get('text') or not store._eligible(collection, row['id'], meta):
287
+ continue
288
+ # Text re-embedding cannot recreate an image vector space.
289
+ # Preserve the old generation; require a qualified multimodal
290
+ # rebuild rather than silently encoding an empty/caption text.
291
+ if meta.get('modality') == 'image':
292
+ raise ValueError('Retained image vectors require a qualified multimodal reindex')
293
+ batch.append({**row, 'metadata': meta})
294
+ if len(batch) >= batch_size:
295
+ await write_batch(collection, batch)
296
+ batch = []
297
+ if batch:
298
+ await write_batch(collection, batch)
299
+ result.collections_migrated += 1
300
+ store.catalog.promote(generation['id'], store.identity)
301
+ except Exception as exc:
302
+ # A retry resumes this generation. No deleted row is restored, and the
303
+ # active pointer has not moved on a partial rebuild.
304
+ message = f'{type(exc).__name__}: {exc}'
305
+ result.errors.append(message)
306
+ result.vectors_failed += len(locals().get('batch', []))
307
+ store.catalog.finish(generation['id'], error=message)
308
+ result.duration_s = time.monotonic() - started
309
+ return result
310
+
311
+
312
+ def _update_env(model_id: str, pipeline) -> None:
313
+ """Update .env file with the new model configuration."""
314
+ env_path = Path(os.environ.get("COLONY_STATE_DIR", ".")) / ".env"
315
+ if not env_path.exists():
316
+ return
317
+
318
+ try:
319
+ lines = env_path.read_text().splitlines()
320
+ updated = {}
321
+ for i, line in enumerate(lines):
322
+ if line.startswith("COLONY_EMBED_MODEL="):
323
+ lines[i] = f"COLONY_EMBED_MODEL={model_id}"
324
+ updated["model"] = True
325
+ elif line.startswith("COLONY_EMBED_DIMS="):
326
+ dims = pipeline.dimensions if hasattr(pipeline, "dimensions") else ""
327
+ lines[i] = f"COLONY_EMBED_DIMS={dims}"
328
+ updated["dims"] = True
329
+
330
+ env_path.write_text("\n".join(lines) + "\n")
331
+ if updated:
332
+ logger.info("Updated .env with new model config: %s", updated)
333
+ except Exception as exc:
334
+ logger.warning("Failed to update .env: %s", exc)