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,343 @@
1
+ """Temporal reference frame for Colony (v0.21.0).
2
+
3
+ The single source of truth for "now" and timezone handling across the sidecar.
4
+ Everything that needs to reason about wall-clock time — context assembly, the
5
+ timeline tool, briefings, autonomy triggers — should go through this module so
6
+ the agent gets a consistent, editable sense of time.
7
+
8
+ Three independent, editable timezones (per the design):
9
+ * AGENT timezone — where the Colony agent "lives" (its home reference frame).
10
+ Editable; defaults to COLONY_AGENT_TIMEZONE, then the host
11
+ system tz, then UTC.
12
+ * CONTACT timezone — a per-contact field (contacts.timezone). Editable per
13
+ contact. Lets the agent reason "it's 9am my time / 3pm
14
+ theirs".
15
+ * COMMUNICATION tz — resolved per turn: an explicit override on the turn, else
16
+ the contact's tz, else the agent tz.
17
+
18
+ Stdlib only (datetime + zoneinfo). No third-party deps.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import logging
25
+ import os
26
+ import re
27
+ from datetime import datetime, timezone, timedelta
28
+ from pathlib import Path
29
+ from typing import Optional
30
+
31
+ try: # py3.9+
32
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
33
+ except Exception: # pragma: no cover - very old python
34
+ ZoneInfo = None # type: ignore
35
+ class ZoneInfoNotFoundError(Exception): # type: ignore
36
+ pass
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ UTC = timezone.utc
41
+
42
+
43
+ # --------------------------------------------------------------------------- #
44
+ # State (editable agent / default-contact timezone) #
45
+ # --------------------------------------------------------------------------- #
46
+
47
+ def _state_dir() -> Path:
48
+ return Path(os.environ.get("COLONY_STATE_DIR", os.path.expanduser("~/.colony")))
49
+
50
+
51
+ def _state_path() -> Path:
52
+ return _state_dir() / "temporal.json"
53
+
54
+
55
+ def _load_state() -> dict:
56
+ try:
57
+ with open(_state_path()) as f:
58
+ return json.load(f) or {}
59
+ except Exception:
60
+ return {}
61
+
62
+
63
+ def _save_state(state: dict) -> None:
64
+ try:
65
+ p = _state_path()
66
+ p.parent.mkdir(parents=True, exist_ok=True)
67
+ tmp = p.with_suffix(".json.tmp")
68
+ with open(tmp, "w") as f:
69
+ json.dump(state, f, indent=2)
70
+ os.replace(tmp, p)
71
+ except Exception as exc: # pragma: no cover
72
+ logger.warning("Failed to persist temporal state: %s", exc)
73
+
74
+
75
+ def is_valid_timezone(tz: Optional[str]) -> bool:
76
+ if not tz:
77
+ return False
78
+ if tz.upper() == "UTC":
79
+ return True
80
+ if ZoneInfo is None:
81
+ return False
82
+ try:
83
+ ZoneInfo(tz)
84
+ return True
85
+ except (ZoneInfoNotFoundError, ValueError, KeyError):
86
+ return False
87
+
88
+
89
+ def _system_timezone() -> Optional[str]:
90
+ """Best-effort IANA name of the host's local timezone."""
91
+ # /etc/localtime symlink → .../zoneinfo/<Area>/<City>
92
+ try:
93
+ p = Path("/etc/localtime")
94
+ if p.is_symlink():
95
+ target = os.readlink(p)
96
+ if "zoneinfo/" in target:
97
+ name = target.split("zoneinfo/", 1)[1]
98
+ if is_valid_timezone(name):
99
+ return name
100
+ except Exception:
101
+ pass
102
+ tz = os.environ.get("TZ")
103
+ if is_valid_timezone(tz):
104
+ return tz
105
+ return None
106
+
107
+
108
+ def agent_timezone() -> str:
109
+ """The agent's home timezone (IANA name). Always returns a usable value."""
110
+ env = os.environ.get("COLONY_AGENT_TIMEZONE")
111
+ if is_valid_timezone(env):
112
+ return env # type: ignore[return-value]
113
+ stored = _load_state().get("agent_timezone")
114
+ if is_valid_timezone(stored):
115
+ return stored
116
+ sys_tz = _system_timezone()
117
+ if sys_tz:
118
+ return sys_tz
119
+ return "UTC"
120
+
121
+
122
+ def set_agent_timezone(tz: str) -> str:
123
+ """Persist the agent home timezone. Raises ValueError if invalid."""
124
+ if not is_valid_timezone(tz):
125
+ raise ValueError(f"Invalid IANA timezone: {tz!r}")
126
+ state = _load_state()
127
+ state["agent_timezone"] = tz
128
+ _save_state(state)
129
+ return tz
130
+
131
+
132
+ def default_contact_timezone() -> Optional[str]:
133
+ """Fallback tz for contacts that have none set (optional)."""
134
+ env = os.environ.get("COLONY_DEFAULT_CONTACT_TIMEZONE")
135
+ if is_valid_timezone(env):
136
+ return env # type: ignore[return-value]
137
+ stored = _load_state().get("default_contact_timezone")
138
+ return stored if is_valid_timezone(stored) else None
139
+
140
+
141
+ def set_default_contact_timezone(tz: Optional[str]) -> Optional[str]:
142
+ if tz is not None and not is_valid_timezone(tz):
143
+ raise ValueError(f"Invalid IANA timezone: {tz!r}")
144
+ state = _load_state()
145
+ state["default_contact_timezone"] = tz
146
+ _save_state(state)
147
+ return tz
148
+
149
+
150
+ def resolve_communication_timezone(
151
+ contact_tz: Optional[str] = None,
152
+ override_tz: Optional[str] = None,
153
+ ) -> str:
154
+ """Per-communication tz: explicit override → contact → default → agent."""
155
+ for cand in (override_tz, contact_tz, default_contact_timezone()):
156
+ if is_valid_timezone(cand):
157
+ return cand # type: ignore[return-value]
158
+ return agent_timezone()
159
+
160
+
161
+ # --------------------------------------------------------------------------- #
162
+ # Now / conversion #
163
+ # --------------------------------------------------------------------------- #
164
+
165
+ def _zone(tz: str):
166
+ if tz.upper() == "UTC" or ZoneInfo is None:
167
+ return UTC
168
+ try:
169
+ return ZoneInfo(tz)
170
+ except Exception:
171
+ return UTC
172
+
173
+
174
+ def now_utc() -> datetime:
175
+ return datetime.now(UTC)
176
+
177
+
178
+ def now_in(tz: str) -> datetime:
179
+ return now_utc().astimezone(_zone(tz))
180
+
181
+
182
+ def to_zone(dt: datetime, tz: str) -> datetime:
183
+ if dt.tzinfo is None:
184
+ dt = dt.replace(tzinfo=UTC)
185
+ return dt.astimezone(_zone(tz))
186
+
187
+
188
+ def parse_iso(value) -> Optional[datetime]:
189
+ """Parse an ISO-8601 string (or pass through a datetime) → aware UTC dt."""
190
+ if value is None:
191
+ return None
192
+ if isinstance(value, datetime):
193
+ return value if value.tzinfo else value.replace(tzinfo=UTC)
194
+ try:
195
+ s = str(value).strip().replace("Z", "+00:00")
196
+ dt = datetime.fromisoformat(s)
197
+ return dt if dt.tzinfo else dt.replace(tzinfo=UTC)
198
+ except Exception:
199
+ return None
200
+
201
+
202
+ # --------------------------------------------------------------------------- #
203
+ # Human-friendly formatting #
204
+ # --------------------------------------------------------------------------- #
205
+
206
+ def part_of_day(dt: datetime) -> str:
207
+ h = dt.hour
208
+ if h < 5:
209
+ return "the middle of the night"
210
+ if h < 9:
211
+ return "early morning"
212
+ if h < 12:
213
+ return "morning"
214
+ if h < 14:
215
+ return "midday"
216
+ if h < 18:
217
+ return "afternoon"
218
+ if h < 22:
219
+ return "evening"
220
+ return "night"
221
+
222
+
223
+ def format_clock(dt: datetime, tz: Optional[str] = None) -> str:
224
+ """e.g. 'Thu, Jun 12, 12:37 AM EDT'."""
225
+ if tz:
226
+ dt = to_zone(dt, tz)
227
+ elif dt.tzinfo is None:
228
+ dt = dt.replace(tzinfo=UTC)
229
+ label = dt.strftime("%Z") or "UTC"
230
+ # %-I is platform-specific; strip a leading zero portably.
231
+ hm = dt.strftime("%I:%M %p").lstrip("0")
232
+ return f"{dt.strftime('%a, %b %d')}, {hm} {label}"
233
+
234
+
235
+ def humanize_delta(ts, ref: Optional[datetime] = None) -> str:
236
+ """'6h ago', 'in 2d', 'just now'. Accepts datetime or ISO string."""
237
+ dt = parse_iso(ts)
238
+ if dt is None:
239
+ return "unknown"
240
+ ref = ref or now_utc()
241
+ if ref.tzinfo is None:
242
+ ref = ref.replace(tzinfo=UTC)
243
+ delta = ref - dt
244
+ secs = delta.total_seconds()
245
+ future = secs < 0
246
+ secs = abs(secs)
247
+
248
+ if secs < 45:
249
+ return "just now"
250
+ if secs < 90 * 60:
251
+ val, unit = round(secs / 60), "m"
252
+ elif secs < 36 * 3600:
253
+ val, unit = round(secs / 3600), "h"
254
+ elif secs < 14 * 86400:
255
+ val, unit = round(secs / 86400), "d"
256
+ elif secs < 8 * 7 * 86400:
257
+ val, unit = round(secs / (7 * 86400)), "w"
258
+ elif secs < 365 * 86400:
259
+ val, unit = round(secs / (30 * 86400)), "mo"
260
+ else:
261
+ val, unit = round(secs / (365 * 86400)), "y"
262
+ return f"in {val}{unit}" if future else f"{val}{unit} ago"
263
+
264
+
265
+ def bucket(ts, tz: Optional[str] = None) -> str:
266
+ """Coarse calendar bucket in the given tz: today / yesterday / this week..."""
267
+ dt = parse_iso(ts)
268
+ if dt is None:
269
+ return "unknown"
270
+ tz = tz or agent_timezone()
271
+ local = to_zone(dt, tz)
272
+ today = now_in(tz).date()
273
+ d = (today - local.date()).days
274
+ if d < 0:
275
+ return "upcoming"
276
+ if d == 0:
277
+ return "today"
278
+ if d == 1:
279
+ return "yesterday"
280
+ if d < 7:
281
+ return "earlier this week"
282
+ if d < 14:
283
+ return "last week"
284
+ if d < 31:
285
+ return f"{d // 7}w ago"
286
+ if d < 365:
287
+ return f"{d // 30}mo ago"
288
+ return f"{d // 365}y ago"
289
+
290
+
291
+ _REL_RE = re.compile(r"^\s*(\d+)\s*([smhdw])\s*$", re.IGNORECASE)
292
+
293
+
294
+ def parse_relative_since(value, default_hours: float = 24.0) -> str:
295
+ """Turn a window spec into an ISO 'since' timestamp (UTC).
296
+
297
+ Accepts relative durations ('30m', '24h', '7d', '2w'), the words
298
+ 'today'/'yesterday', or an absolute ISO date/time. Falls back to
299
+ ``default_hours`` ago when empty/unparseable.
300
+ """
301
+ if not value:
302
+ return (now_utc() - timedelta(hours=default_hours)).isoformat()
303
+ s = str(value).strip()
304
+ m = _REL_RE.match(s)
305
+ if m:
306
+ n = int(m.group(1))
307
+ mult = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}[m.group(2).lower()]
308
+ return (now_utc() - timedelta(seconds=n * mult)).isoformat()
309
+ low = s.lower()
310
+ if low in ("today", "yesterday"):
311
+ local = now_in(agent_timezone())
312
+ if low == "yesterday":
313
+ local = local - timedelta(days=1)
314
+ start = local.replace(hour=0, minute=0, second=0, microsecond=0)
315
+ return start.astimezone(UTC).isoformat()
316
+ dt = parse_iso(s)
317
+ if dt is not None:
318
+ return dt.isoformat()
319
+ return (now_utc() - timedelta(hours=default_hours)).isoformat()
320
+
321
+
322
+ def hours_since(ts, ref: Optional[datetime] = None) -> Optional[float]:
323
+ dt = parse_iso(ts)
324
+ if dt is None:
325
+ return None
326
+ ref = ref or now_utc()
327
+ if ref.tzinfo is None:
328
+ ref = ref.replace(tzinfo=UTC)
329
+ return (ref - dt).total_seconds() / 3600.0
330
+
331
+
332
+ def describe_now(agent_tz: Optional[str] = None,
333
+ contact_tz: Optional[str] = None,
334
+ contact_label: str = "their") -> str:
335
+ """One-line 'now' anchor, optionally with the contact's local time too."""
336
+ atz = agent_tz or agent_timezone()
337
+ a_local = now_in(atz)
338
+ line = f"{format_clock(a_local)} — {part_of_day(a_local)} (your local time, {atz})"
339
+ if contact_tz and is_valid_timezone(contact_tz) and contact_tz != atz:
340
+ c_local = now_in(contact_tz)
341
+ poss = contact_label if contact_label.endswith("s") else f"{contact_label}'s"
342
+ line += f"\nFor {poss} side it is {format_clock(c_local)} — {part_of_day(c_local)} ({contact_tz})."
343
+ return line
@@ -0,0 +1,75 @@
1
+ """Colony Vector Store — local LanceDB-backed vector search.
2
+
3
+ Public API:
4
+ - VectorStore — LanceDB wrapper, CRUD + ANN search
5
+ - EmbeddingPipeline — provider + caching + latency monitoring
6
+ - Collection — enum of named vector collections
7
+ - get_store() — retrieve the singleton VectorStore instance
8
+ - get_pipeline() — retrieve the singleton EmbeddingPipeline instance
9
+ """
10
+
11
+ from apsimo.vector.collections import Collection
12
+ from apsimo.vector.config import EmbeddingConfig, HardwareProfile, VectorStoreConfig
13
+ from apsimo.vector.embedder import (
14
+ CPUEmbeddingProvider,
15
+ CUDAEmbeddingProvider,
16
+ EmbeddingPipeline,
17
+ EmbeddingProvider,
18
+ MLXEmbeddingProvider,
19
+ make_provider,
20
+ )
21
+ from apsimo.vector.query import HybridQuery, VectorItem, VectorQuery, VectorResult
22
+
23
+ # Lazy import — VectorStore depends on pyarrow/lancedb which may not be installed
24
+ from typing import Any, Optional
25
+ _store: Any = None
26
+ _pipeline: Optional[EmbeddingPipeline] = None
27
+
28
+
29
+ def _VectorStore():
30
+ """Lazy import for VectorStore."""
31
+ from apsimo.vector.store import VectorStore
32
+ return VectorStore
33
+
34
+
35
+ def set_store(store: Any) -> None:
36
+ global _store
37
+ _store = store
38
+
39
+
40
+ def set_pipeline(pipeline: EmbeddingPipeline) -> None:
41
+ global _pipeline
42
+ _pipeline = pipeline
43
+
44
+
45
+ def get_store() -> Any:
46
+ """Retrieve the singleton VectorStore, or None if not configured."""
47
+ return _store
48
+
49
+
50
+ def get_pipeline() -> Optional[EmbeddingPipeline]:
51
+ """Retrieve the singleton EmbeddingPipeline, or None if not configured."""
52
+ return _pipeline
53
+
54
+
55
+ __all__ = [
56
+ "Collection",
57
+ "EmbeddingConfig",
58
+ "EmbeddingPipeline",
59
+ "EmbeddingProvider",
60
+ "CUDAEmbeddingProvider",
61
+ "CPUEmbeddingProvider",
62
+ "MLXEmbeddingProvider",
63
+ "HardwareProfile",
64
+ "HybridQuery",
65
+ "VectorItem",
66
+ "VectorQuery",
67
+ "VectorResult",
68
+ "VectorStore",
69
+ "VectorStoreConfig",
70
+ "get_pipeline",
71
+ "get_store",
72
+ "make_provider",
73
+ "set_pipeline",
74
+ "set_store",
75
+ ]
@@ -0,0 +1,171 @@
1
+ """Colony Vector Store — backfill and re-embedding.
2
+
3
+ Re-embeds all vectors in one or more collections using the current
4
+ embedding pipeline. Skips rows already tagged with the current model
5
+ (idempotent). Marks every re-embedded vector with model_id and
6
+ embedded_at metadata.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import json
13
+ import logging
14
+ import time
15
+ from dataclasses import dataclass, field
16
+ from typing import Optional
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class BackfillResult:
23
+ """Result of a backfill run."""
24
+
25
+ total: int = 0
26
+ processed: int = 0
27
+ failed: int = 0
28
+ skipped: int = 0 # already had current model_id
29
+ duration_s: float = 0.0
30
+ errors: list[str] = field(default_factory=list)
31
+
32
+
33
+ async def backfill(
34
+ store,
35
+ pipeline,
36
+ collection: Optional[str] = None,
37
+ batch_size: int = 64,
38
+ current_model_id: Optional[str] = None,
39
+ ) -> BackfillResult:
40
+ """Re-embed vectors in the store using the current pipeline.
41
+
42
+ Parameters
43
+ ----------
44
+ store : VectorStore
45
+ The LanceDB vector store.
46
+ pipeline : EmbeddingPipeline
47
+ The current embedding pipeline.
48
+ collection : str, optional
49
+ Specific collection to backfill. If None, backfill all.
50
+ batch_size : int
51
+ Number of rows to embed per batch.
52
+ current_model_id : str, optional
53
+ The model_id of the current embedder. Rows with this model_id
54
+ in their metadata are skipped (idempotent).
55
+
56
+ Returns
57
+ -------
58
+ BackfillResult
59
+ """
60
+ from apsimo.vector.collections import Collection
61
+
62
+ start = time.monotonic()
63
+ result = BackfillResult()
64
+
65
+ # Resolve current model_id from pipeline if not provided
66
+ if current_model_id is None:
67
+ if hasattr(pipeline, "_provider") and hasattr(pipeline._provider, "_config"):
68
+ current_model_id = pipeline._provider._config.model_id
69
+ else:
70
+ current_model_id = ""
71
+
72
+ # Determine collections to process
73
+ if collection is not None:
74
+ try:
75
+ collections = [Collection(collection)]
76
+ except ValueError:
77
+ result.errors.append(f"Unknown collection: {collection}")
78
+ result.duration_s = time.monotonic() - start
79
+ return result
80
+ else:
81
+ collections = list(Collection)
82
+
83
+ for col in collections:
84
+ try:
85
+ col_result = await _backfill_collection(
86
+ store, pipeline, col, batch_size, current_model_id
87
+ )
88
+ result.total += col_result.total
89
+ result.processed += col_result.processed
90
+ result.failed += col_result.failed
91
+ result.skipped += col_result.skipped
92
+ result.errors.extend(col_result.errors)
93
+ except Exception as exc:
94
+ msg = f"Collection {col.value} backfill failed: {exc}"
95
+ logger.error(msg)
96
+ result.errors.append(msg)
97
+
98
+ result.duration_s = time.monotonic() - start
99
+ return result
100
+
101
+
102
+ async def _backfill_collection(
103
+ store,
104
+ pipeline,
105
+ collection,
106
+ batch_size: int,
107
+ current_model_id: str,
108
+ ) -> BackfillResult:
109
+ """Backfill a single collection."""
110
+ result = BackfillResult()
111
+
112
+ # Scan all rows
113
+ try:
114
+ rows = await store.scan_all(collection)
115
+ except Exception as exc:
116
+ result.errors.append(f"scan failed: {exc}")
117
+ return result
118
+
119
+ result.total = len(rows)
120
+
121
+ # Separate into skip vs re-embed
122
+ to_embed: list[dict] = []
123
+ for row in rows:
124
+ meta_str = row.get("metadata", "{}")
125
+ try:
126
+ meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
127
+ except (json.JSONDecodeError, TypeError):
128
+ meta = {}
129
+
130
+ if current_model_id and meta.get("model_id") == current_model_id:
131
+ result.skipped += 1
132
+ continue
133
+ to_embed.append(row)
134
+
135
+ # Batch re-embed
136
+ now = time.time()
137
+ for i in range(0, len(to_embed), batch_size):
138
+ batch = to_embed[i : i + batch_size]
139
+ texts = [row.get("text", "") for row in batch]
140
+
141
+ try:
142
+ vectors = await pipeline.embed_batch(texts)
143
+ except Exception as exc:
144
+ result.failed += len(batch)
145
+ result.errors.append(f"embed batch failed: {exc}")
146
+ continue
147
+
148
+ # Upsert each row with new vector + metadata
149
+ for row, vector in zip(batch, vectors):
150
+ try:
151
+ meta_str = row.get("metadata", "{}")
152
+ try:
153
+ meta = json.loads(meta_str) if isinstance(meta_str, str) else (meta_str or {})
154
+ except (json.JSONDecodeError, TypeError):
155
+ meta = {}
156
+ meta["model_id"] = current_model_id
157
+ meta["embedded_at"] = now
158
+
159
+ await store.update(
160
+ collection,
161
+ id=row["id"],
162
+ text=row.get("text", ""),
163
+ vector=vector,
164
+ metadata=meta,
165
+ )
166
+ result.processed += 1
167
+ except Exception as exc:
168
+ result.failed += 1
169
+ result.errors.append(f"update {row.get('id', '?')} failed: {exc}")
170
+
171
+ return result
@@ -0,0 +1,114 @@
1
+ """Colony Vector — auto-captioning for images.
2
+
3
+ Generates text captions for images using the host's LLM (if configured)
4
+ or falls back to EXIF/filename-based descriptions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import Any, Optional
11
+
12
+ from apsimo.vector.multimodal_types import ImageInput
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ async def caption_image(
18
+ image: ImageInput,
19
+ llm_config: Optional[dict[str, Any]] = None,
20
+ max_tokens: int = 50,
21
+ ) -> str:
22
+ """Generate a caption for an image.
23
+
24
+ Tries LLM-based captioning first (if credentials available),
25
+ falls back to EXIF/metadata-based caption.
26
+ """
27
+ # Try LLM captioning
28
+ if llm_config and llm_config.get("api_key"):
29
+ try:
30
+ caption = await _llm_caption(image, llm_config, max_tokens)
31
+ if caption:
32
+ return caption
33
+ except Exception as exc:
34
+ logger.warning("LLM captioning failed: %s — falling back to metadata", exc)
35
+
36
+ # Fallback: metadata-based caption
37
+ return _metadata_caption(image)
38
+
39
+
40
+ async def _llm_caption(
41
+ image: ImageInput,
42
+ llm_config: dict[str, Any],
43
+ max_tokens: int,
44
+ ) -> str:
45
+ """Use a vision LLM to generate a caption."""
46
+ import base64
47
+ import httpx
48
+
49
+ api_key = llm_config.get("api_key", "")
50
+ base_url = llm_config.get("base_url", "https://open.bigmodel.cn/api/paas/v4")
51
+ model = llm_config.get("vision_model", llm_config.get("model", "glm-4v-flash"))
52
+
53
+ b64 = base64.b64encode(image.data).decode()
54
+
55
+ async with httpx.AsyncClient(timeout=30) as client:
56
+ resp = await client.post(
57
+ f"{base_url}/chat/completions",
58
+ headers={"Authorization": f"Bearer {api_key}"},
59
+ json={
60
+ "model": model,
61
+ "messages": [
62
+ {
63
+ "role": "user",
64
+ "content": [
65
+ {"type": "text", "text": f"Describe this image in {max_tokens} characters or less. Be concise and factual."},
66
+ {"type": "image_url", "image_url": {"url": f"data:{image.mime_type};base64,{b64}"}},
67
+ ],
68
+ }
69
+ ],
70
+ "max_tokens": max_tokens,
71
+ },
72
+ )
73
+ resp.raise_for_status()
74
+ data = resp.json()
75
+ return data["choices"][0]["message"]["content"].strip()
76
+
77
+
78
+ def _metadata_caption(image: ImageInput) -> str:
79
+ """Generate a basic caption from image metadata."""
80
+ parts: list[str] = []
81
+
82
+ # Use user-provided caption if available
83
+ if image.caption:
84
+ return image.caption
85
+
86
+ # Dimensions
87
+ if image.width and image.height:
88
+ parts.append(f"{image.width}x{image.height}")
89
+
90
+ # MIME type
91
+ fmt = image.mime_type.split("/")[-1].upper() if image.mime_type else "Image"
92
+ parts.insert(0, fmt)
93
+
94
+ # EXIF data
95
+ exif = image.exif
96
+ if exif.get("captured_at"):
97
+ parts.append(f"taken {exif['captured_at']}")
98
+ if exif.get("camera_make") or exif.get("camera_model"):
99
+ camera = f"{exif.get('camera_make', '')} {exif.get('camera_model', '')}".strip()
100
+ parts.append(f"camera: {camera}")
101
+
102
+ # GPS
103
+ if exif.get("gps_lat") and exif.get("gps_lon"):
104
+ parts.append(f"location: {exif['gps_lat']},{exif['gps_lon']}")
105
+
106
+ # File path hint
107
+ if image.original_path:
108
+ from pathlib import Path
109
+ name = Path(image.original_path).stem
110
+ if name and not name.startswith("data:"):
111
+ parts.insert(0, name.replace("-", " ").replace("_", " "))
112
+
113
+ caption = " — ".join(parts) if parts else "Image"
114
+ return caption[:200] # Cap length