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,252 @@
1
+ """Metadata receipts for a transport-owned durable inbox, in the communications DB.
2
+
3
+ No message bodies or attachment bytes belong here. A claimed handoff is deliberately
4
+ not retried: only an actual canonical turn receipt can settle its processing.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ import json
10
+ import sqlite3
11
+ import time
12
+
13
+
14
+ def ensure_schema(conn: sqlite3.Connection) -> None:
15
+ conn.executescript('''
16
+ CREATE TABLE IF NOT EXISTS transport_ingress (
17
+ receipt_id TEXT PRIMARY KEY, producer TEXT NOT NULL,
18
+ account_id TEXT NOT NULL, epoch TEXT NOT NULL, sequence INTEGER NOT NULL,
19
+ event_id TEXT NOT NULL, contact_id TEXT, occurred_at REAL NOT NULL,
20
+ journal_ref TEXT NOT NULL, payload_digest TEXT NOT NULL,
21
+ media_available INTEGER NOT NULL, metadata_json TEXT NOT NULL,
22
+ state TEXT NOT NULL DEFAULT 'admitted', batch_id TEXT,
23
+ native_turn_json TEXT, source_versions_json TEXT,
24
+ outcome TEXT, created_at REAL NOT NULL, completed_at REAL,
25
+ UNIQUE(producer,account_id,epoch,sequence));
26
+ CREATE INDEX IF NOT EXISTS transport_ingress_native
27
+ ON transport_ingress(native_turn_json,state);
28
+ CREATE TABLE IF NOT EXISTS transport_ingress_coverage (
29
+ producer TEXT NOT NULL, account_id TEXT NOT NULL, epoch TEXT NOT NULL,
30
+ connected_since REAL, observed_at REAL NOT NULL, watermark INTEGER NOT NULL,
31
+ connected INTEGER NOT NULL, unavailable INTEGER NOT NULL,
32
+ sequence_floor INTEGER NOT NULL DEFAULT 0,
33
+ PRIMARY KEY(producer,account_id));
34
+ ''')
35
+ columns = {row[1] for row in conn.execute('PRAGMA table_info(transport_ingress_coverage)')}
36
+ if 'sequence_floor' not in columns:
37
+ conn.execute('ALTER TABLE transport_ingress_coverage ADD COLUMN sequence_floor INTEGER NOT NULL DEFAULT 0')
38
+ conn.commit()
39
+
40
+
41
+ def _json(value):
42
+ return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)
43
+
44
+
45
+ def _text(value, name, limit=512):
46
+ if not isinstance(value, str) or not value or len(value) > limit:
47
+ raise ValueError('invalid_ingress_' + name)
48
+ return value
49
+
50
+
51
+ class TransportIngress:
52
+ """Use the caller's existing CommsLog connection and transaction lifetime."""
53
+
54
+ def __init__(self, conn: sqlite3.Connection):
55
+ self.conn = conn
56
+
57
+ def get(self, receipt_id):
58
+ row = self.conn.execute('SELECT * FROM transport_ingress WHERE receipt_id=?',
59
+ (receipt_id,)).fetchone()
60
+ return dict(row) if row else None
61
+
62
+ def admit(self, *, producer, account_id, epoch, sequence, event_id, contact_id,
63
+ occurred_at, journal_ref, payload_digest, media_available, metadata=None,
64
+ now=None):
65
+ now = time.time() if now is None else float(now)
66
+ for name, value in [('producer', producer), ('account_id', account_id),
67
+ ('epoch', epoch), ('event_id', event_id), ('journal_ref', journal_ref)]:
68
+ _text(value, name)
69
+ if (type(sequence) is not int or sequence < 1 or type(media_available) is not bool
70
+ or not 0 < float(occurred_at) <= now + 1
71
+ or len(payload_digest) != 64 or any(c not in '0123456789abcdef' for c in payload_digest)):
72
+ raise ValueError('invalid_ingress_envelope')
73
+ if contact_id is not None:
74
+ _text(contact_id, 'contact_id', 128)
75
+ metadata = dict(metadata or {})
76
+ if set(metadata) - {'channel', 'sender_ref', 'reply_to_ref', 'from_owner', 'is_group'}:
77
+ raise ValueError('ingress_metadata_only')
78
+ if len(_json(metadata)) > 2048:
79
+ raise ValueError('ingress_metadata_too_large')
80
+ receipt_id = 'ingress:' + hashlib.sha256(_json([producer, account_id, epoch, sequence]).encode()).hexdigest()
81
+ fields = dict(producer=producer, account_id=account_id, epoch=epoch, sequence=sequence,
82
+ event_id=event_id, contact_id=contact_id, occurred_at=float(occurred_at),
83
+ journal_ref=journal_ref, payload_digest=payload_digest,
84
+ media_available=int(media_available), metadata_json=_json(metadata))
85
+ with self.conn:
86
+ old = self.get(receipt_id)
87
+ if old:
88
+ if old['state'] == 'erased':
89
+ if any(old[key] != fields[key] for key in ('event_id', 'journal_ref', 'payload_digest')):
90
+ raise ValueError('ingress_event_conflict')
91
+ return self.view(old)
92
+ # A retained provider event can acquire its missing attachment later.
93
+ # No claimed turn or identity/timestamp may be rewritten by repair.
94
+ repair = (old['state'] == 'admitted' and not old['media_available']
95
+ and fields['media_available'] == 1
96
+ and all(old[key] == val for key, val in fields.items()
97
+ if key not in {'payload_digest', 'media_available'}))
98
+ if repair:
99
+ self.conn.execute('UPDATE transport_ingress SET payload_digest=?,media_available=1 '
100
+ 'WHERE receipt_id=?', (payload_digest, receipt_id))
101
+ return self.view(self.get(receipt_id))
102
+ if any(old[key] != val for key, val in fields.items()):
103
+ raise ValueError('ingress_event_conflict')
104
+ return self.view(old)
105
+ columns = ','.join(fields)
106
+ self.conn.execute(f'INSERT INTO transport_ingress (receipt_id,{columns},created_at) '
107
+ f'VALUES ({",".join("?" for _ in range(len(fields)+2))})',
108
+ (receipt_id, *fields.values(), now))
109
+ return self.view(self.get(receipt_id))
110
+
111
+ @staticmethod
112
+ def view(row):
113
+ return {key: row[key] for key in ('receipt_id', 'state', 'batch_id', 'journal_ref',
114
+ 'media_available', 'source_versions_json', 'outcome')}
115
+
116
+ def handoff(self, *, producer, receipt_ids, batch_id, native_turn=None):
117
+ _text(batch_id, 'batch_id', 128)
118
+ ids = sorted(set(receipt_ids))
119
+ if not ids or len(ids) > 100:
120
+ raise ValueError('invalid_ingress_batch')
121
+ native = None
122
+ if native_turn is not None:
123
+ if set(native_turn) != {'session_id', 'task_id', 'turn_id'}:
124
+ raise ValueError('invalid_ingress_native_turn')
125
+ native = _json({key: _text(value, key, 256) for key, value in native_turn.items()})
126
+ with self.conn:
127
+ self.conn.execute('BEGIN IMMEDIATE')
128
+ rows = [self.get(receipt) for receipt in ids]
129
+ if any(not row or row['producer'] != producer for row in rows):
130
+ raise ValueError('ingress_receipt_scope_mismatch')
131
+ if any(row['state'] not in {'admitted', 'handed_off'} or not row['media_available'] for row in rows):
132
+ raise ValueError('ingress_not_ready')
133
+ if any(row['batch_id'] not in (None, batch_id) for row in rows):
134
+ raise ValueError('ingress_handoff_conflict')
135
+ if native and any(row['native_turn_json'] not in (None, native) for row in rows):
136
+ raise ValueError('ingress_native_turn_conflict')
137
+ may_dispatch = all(row['state'] == 'admitted' for row in rows)
138
+ if native and may_dispatch:
139
+ raise ValueError('ingress_handoff_required_before_native_binding')
140
+ for row in rows:
141
+ self.conn.execute('UPDATE transport_ingress SET state=?,batch_id=?, '
142
+ 'native_turn_json=COALESCE(native_turn_json,?) WHERE receipt_id=?',
143
+ ('handed_off', batch_id, native, row['receipt_id']))
144
+ return {'receipt_ids': ids, 'batch_id': batch_id, 'may_dispatch': may_dispatch,
145
+ 'native_bound': native is not None, 'processing_complete': False}
146
+
147
+ def complete(self, *, native_turn, source_versions, outcome, now=None):
148
+ """Call only after canonical source admission; not exposed to a model tool."""
149
+ if not source_versions or outcome not in {'captured', 'interrupted', 'failed'}:
150
+ raise ValueError('canonical_ingress_completion_required')
151
+ for key, value in source_versions.items():
152
+ _text(key, 'source_id'); _text(value, 'source_version')
153
+ stamp = time.time() if now is None else now
154
+ rows = self.conn.execute('SELECT * FROM transport_ingress WHERE native_turn_json=?',
155
+ (_json(native_turn),)).fetchall()
156
+ with self.conn:
157
+ for row in rows:
158
+ if row['state'] == 'erased':
159
+ continue
160
+ if row['state'] == 'completed':
161
+ if row['source_versions_json'] != _json(source_versions) or row['outcome'] != outcome:
162
+ raise ValueError('ingress_completion_conflict')
163
+ continue
164
+ self.conn.execute('UPDATE transport_ingress SET state=?,source_versions_json=?, '
165
+ 'outcome=?,completed_at=? WHERE receipt_id=?',
166
+ ('completed', _json(source_versions), outcome, stamp, row['receipt_id']))
167
+ return [self.view(self.get(row['receipt_id'])) for row in rows]
168
+
169
+ def for_canonical_turn(self, *, turn_id, contact_id):
170
+ """Unique native tuple bound by the transport producer, never a text match."""
171
+ rows = self.conn.execute("SELECT * FROM transport_ingress WHERE contact_id=? "
172
+ "AND native_turn_json IS NOT NULL AND state!='erased'", (contact_id,)).fetchall()
173
+ rows = [row for row in rows if json.loads(row['native_turn_json']).get('turn_id') == turn_id]
174
+ tuples = {row['native_turn_json'] for row in rows}
175
+ return ([dict(row) for row in rows] if len(tuples) == 1 else [])
176
+
177
+ def receipts(self, *, producer, receipt_ids):
178
+ if len(receipt_ids) > 100:
179
+ raise ValueError('invalid_ingress_batch')
180
+ rows = [self.get(receipt) for receipt in receipt_ids]
181
+ if any(not row or row['producer'] != producer for row in rows):
182
+ raise ValueError('ingress_receipt_scope_mismatch')
183
+ return [self.view(row) for row in rows]
184
+
185
+ def erase_sources(self, source_ids):
186
+ """Return locators root must erase at the sole payload owner, idempotently."""
187
+ wanted = set(source_ids)
188
+ rows = self.conn.execute('SELECT * FROM transport_ingress WHERE source_versions_json IS NOT NULL '
189
+ 'OR native_turn_json IS NOT NULL').fetchall()
190
+ selected = [row for row in rows if wanted.intersection(json.loads(row['source_versions_json'] or '{}'))
191
+ or json.loads(row['native_turn_json'] or '{}').get('turn_id') in wanted]
192
+ with self.conn:
193
+ for row in selected:
194
+ self.conn.execute("UPDATE transport_ingress SET state='erased',metadata_json='{}',"
195
+ "contact_id=NULL WHERE receipt_id=?", (row['receipt_id'],))
196
+ return [{'receipt_id': row['receipt_id'], 'journal_ref': row['journal_ref']} for row in selected]
197
+
198
+ def observe_coverage(self, *, producer, account_id, epoch, connected_since, observed_at,
199
+ watermark, connected, unavailable, sequence_floor=0, now=None):
200
+ stamp = time.time() if now is None else float(now)
201
+ if (type(watermark) is not int or watermark < 0 or type(connected) is not bool
202
+ or type(sequence_floor) is not int or not 0 <= sequence_floor <= watermark
203
+ or type(unavailable) is not int or unavailable < 0
204
+ or not 0 < float(observed_at) <= stamp + 1
205
+ or (connected and (connected_since is None or not 0 < connected_since <= observed_at))):
206
+ raise ValueError('invalid_ingress_coverage')
207
+ old = self.conn.execute('SELECT * FROM transport_ingress_coverage WHERE producer=? AND account_id=?',
208
+ (producer, account_id)).fetchone()
209
+ if old and (observed_at < old['observed_at'] or (epoch == old['epoch'] and watermark < old['watermark'])):
210
+ raise ValueError('ingress_coverage_regression')
211
+ with self.conn:
212
+ self.conn.execute('INSERT OR REPLACE INTO transport_ingress_coverage VALUES (?,?,?,?,?,?,?,?,?)',
213
+ (producer, account_id, epoch, connected_since, observed_at,
214
+ watermark, int(connected), unavailable, sequence_floor))
215
+
216
+ def coverage(self, *, producer, account_id, contact_id, since, now=None, max_age=5,
217
+ until=None, observation=None):
218
+ """Read current coverage or recheck a retained internal observation.
219
+
220
+ A fixed horizon excludes later activity from that earlier interval.
221
+ Rechecking a stored observation still queries current intake/erasure
222
+ records; it never treats a retained coverage hash as timeless proof.
223
+ """
224
+ now = time.time() if now is None else now
225
+ row = observation if observation is not None else self.conn.execute(
226
+ 'SELECT * FROM transport_ingress_coverage WHERE producer=? AND account_id=?',
227
+ (producer, account_id)).fetchone()
228
+ if observation is not None and (row['producer'] != producer or row['account_id'] != account_id):
229
+ raise ValueError('coverage_observation_scope_mismatch')
230
+ if until is not None and not since <= until <= now:
231
+ raise ValueError('invalid_coverage_interval')
232
+ reasons = []
233
+ if not row or not row['connected'] or row['connected_since'] is None or row['connected_since'] > since:
234
+ reasons.append('connection_interval_unknown')
235
+ if not row or now - row['observed_at'] > max_age:
236
+ reasons.append('intake_observation_stale')
237
+ if row:
238
+ admitted = self.conn.execute('SELECT COUNT(*) FROM transport_ingress WHERE producer=? '
239
+ 'AND account_id=? AND epoch=? AND sequence>? AND sequence<=?',
240
+ (producer, account_id, row['epoch'], row['sequence_floor'], row['watermark'])).fetchone()[0]
241
+ if admitted != row['watermark'] - row['sequence_floor'] or row['unavailable']:
242
+ reasons.append('intake_gap')
243
+ activity = self.conn.execute('SELECT receipt_id,state,metadata_json FROM transport_ingress '
244
+ 'WHERE producer=? AND account_id=? AND occurred_at>=? AND '
245
+ '(contact_id=? OR contact_id IS NULL)'+(' AND occurred_at<=?' if until is not None else ''),
246
+ (producer, account_id, since, contact_id, *([until] if until is not None else []))).fetchall()
247
+ activity = [item for item in activity if not json.loads(item['metadata_json']).get('from_owner')]
248
+ if activity:
249
+ reasons.append('recipient_activity_requires_review')
250
+ return {'observed': not reasons, 'reasons': reasons, 'observed_through': row['observed_at'] if row else None,
251
+ 'watermark': row['watermark'] if row else None, 'activity_receipts': [r['receipt_id'] for r in activity],
252
+ 'effect_authorized': False}
@@ -0,0 +1,314 @@
1
+ """Colony Contacts — World Model Bridge.
2
+
3
+ Synchronises Neo4j :Person nodes with the SQLite ContactStore so that
4
+ discovered people become visible to the contacts API while preserving
5
+ the architectural separation between curated and discovered contacts.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from .models import Contact
14
+ from .store import SQLiteContactStore, _name_similarity, _normalize_email, _normalize_phone
15
+
16
+ logger = logging.getLogger("colony.contacts.world_bridge")
17
+
18
+
19
+ def _person_handle_gaps(person: Dict[str, Any]) -> List[tuple[str, str]]:
20
+ """Extract (gateway, address) pairs from a Person node's properties."""
21
+ handles: List[tuple[str, str]] = []
22
+ phone = person.get("phone")
23
+ if phone:
24
+ norm = _normalize_phone(str(phone))
25
+ if norm:
26
+ handles.append(("imessage", norm))
27
+ email = person.get("email")
28
+ if email:
29
+ norm = _normalize_email(str(email))
30
+ if norm:
31
+ handles.append(("email", norm))
32
+ return handles
33
+
34
+
35
+ class WorldModelContactBridge:
36
+ """Creates and maintains shadow contacts from Neo4j Person nodes.
37
+
38
+ Shadow contacts:
39
+ - trust_tier = "acquaintance"
40
+ - interaction_allowed = False
41
+ - import_source = "world_model"
42
+ - person_node_id = Person.id
43
+
44
+ The bridge deduplicates against existing curated contacts by
45
+ person_node_id, overlapping handles, or high name similarity.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ graph, # ColonyGraph instance
51
+ store: SQLiteContactStore,
52
+ min_signals: int = 2,
53
+ min_memories: int = 2,
54
+ name_match_threshold: float = 0.85,
55
+ ) -> None:
56
+ self._graph = graph
57
+ self._store = store
58
+ self._min_signals = min_signals
59
+ self._min_memories = min_memories
60
+ self._name_match_threshold = name_match_threshold
61
+
62
+ # ── Public API ─────────────────────────────────────────────────────────────
63
+
64
+ async def sync_person_to_contact(self, person_id: str) -> Optional[Contact]:
65
+ """Ensure a single Person node is reflected as a contact.
66
+
67
+ Returns the existing or newly-created contact, or None if the
68
+ person does not meet the substance threshold.
69
+ """
70
+ person = await self._graph.get_person(person_id)
71
+ if not person:
72
+ logger.debug("sync_person_to_contact: Person %s not found in graph", person_id)
73
+ return None
74
+
75
+ if not person.get("name"):
76
+ logger.debug("sync_person_to_contact: Person %s has no name, skipping", person_id)
77
+ return None
78
+
79
+ # Check substance threshold
80
+ handles = _person_handle_gaps(person)
81
+ has_handle = bool(handles)
82
+
83
+ # We also accept persons with signals/memories — graph query already
84
+ # filters, but for single-person sync we do a lightweight check.
85
+ if not has_handle:
86
+ # Need to verify signals/memories via graph
87
+ people = await self._graph.get_people_with_substance(
88
+ min_signals=self._min_signals,
89
+ min_memories=self._min_memories,
90
+ )
91
+ if not any(p["id"] == person_id for p in people):
92
+ logger.debug(
93
+ "sync_person_to_contact: Person %s lacks substance (no handle, < %d signals/memories)",
94
+ person_id, self._min_signals,
95
+ )
96
+ return None
97
+
98
+ # Deduplication: check for existing contact
99
+ existing = await self._find_existing_contact(person_id, person.get("name"), handles)
100
+ if existing:
101
+ # Ensure person_node_id is linked
102
+ if not existing.person_node_id:
103
+ await self._store.update(existing.contact_id, person_node_id=person_id)
104
+ existing.person_node_id = person_id
105
+ logger.debug(
106
+ "sync_person_to_contact: Person %s matched existing contact %s",
107
+ person_id, existing.contact_id,
108
+ )
109
+ return existing
110
+
111
+ # Create shadow contact
112
+ contact = await self._store.create(
113
+ display_name=person.get("name"),
114
+ trust_tier="acquaintance",
115
+ interaction_allowed=False,
116
+ import_source="world_model",
117
+ notes=person.get("notes") or None,
118
+ )
119
+ await self._store.update(contact.contact_id, person_node_id=person_id)
120
+ contact.person_node_id = person_id
121
+
122
+ # Attach handles from Person properties
123
+ for gateway, address in handles:
124
+ try:
125
+ await self._store.add_handle(
126
+ contact.contact_id,
127
+ gateway,
128
+ address,
129
+ is_primary=True,
130
+ source="world_model",
131
+ confidence=0.7,
132
+ )
133
+ except ValueError:
134
+ # Handle already owned by another contact (possible race or
135
+ # overlap with curated contact created since dedup check)
136
+ logger.debug(
137
+ "sync_person_to_contact: handle conflict for %s %s on person %s",
138
+ gateway, address, person_id,
139
+ )
140
+
141
+ logger.info(
142
+ "Created shadow contact %s for Person %s (%s)",
143
+ contact.contact_id, person_id, person.get("name"),
144
+ )
145
+ return contact
146
+
147
+ async def backfill_all_people(self) -> Dict[str, int]:
148
+ """One-shot backfill of all substantive Person nodes into contacts.
149
+
150
+ Returns counters: {"created": int, "linked": int, "skipped": int}
151
+ """
152
+ people = await self._graph.get_people_with_substance(
153
+ min_signals=self._min_signals,
154
+ min_memories=self._min_memories,
155
+ )
156
+ stats = {"created": 0, "linked": 0, "skipped": 0}
157
+ for person in people:
158
+ person_id = person["id"]
159
+ name = person.get("name")
160
+ handles = _person_handle_gaps(person)
161
+
162
+ existing = await self._find_existing_contact(person_id, name, handles)
163
+ if existing:
164
+ if not existing.person_node_id:
165
+ await self._store.update(existing.contact_id, person_node_id=person_id)
166
+ stats["linked"] += 1
167
+ else:
168
+ stats["skipped"] += 1
169
+ continue
170
+
171
+ contact = await self._store.create(
172
+ display_name=name,
173
+ trust_tier="acquaintance",
174
+ interaction_allowed=False,
175
+ import_source="world_model",
176
+ notes=person.get("notes") or None,
177
+ )
178
+ await self._store.update(contact.contact_id, person_node_id=person_id)
179
+
180
+ for gateway, address in handles:
181
+ try:
182
+ await self._store.add_handle(
183
+ contact.contact_id, gateway, address,
184
+ is_primary=True, source="world_model", confidence=0.7,
185
+ )
186
+ except ValueError:
187
+ pass
188
+
189
+ stats["created"] += 1
190
+ logger.info(
191
+ "Backfill: created shadow contact %s for Person %s (%s)",
192
+ contact.contact_id, person_id, name,
193
+ )
194
+
195
+ logger.info(
196
+ "Backfill complete: created=%d linked=%d skipped=%d total_people=%d",
197
+ stats["created"], stats["linked"], stats["skipped"], len(people),
198
+ )
199
+ return stats
200
+
201
+ async def absorb_discovered_contact(
202
+ self,
203
+ curated_contact_id: str,
204
+ discovered_contact: Contact,
205
+ ) -> bool:
206
+ """Absorb a discovered contact into a curated one.
207
+
208
+ Copies person_node_id and handles, then soft-deletes the discovered
209
+ contact. Returns True if anything was absorbed.
210
+ """
211
+ if discovered_contact.import_source != "world_model":
212
+ return False
213
+
214
+ updates: Dict[str, Any] = {}
215
+ if discovered_contact.person_node_id and not (
216
+ await self._store.find_by_person_node_id(discovered_contact.person_node_id)
217
+ ):
218
+ updates["person_node_id"] = discovered_contact.person_node_id
219
+
220
+ if updates:
221
+ await self._store.update(curated_contact_id, **updates)
222
+
223
+ # Migrate handles
224
+ handles = await self._store.get_handles(discovered_contact.contact_id)
225
+ for h in handles:
226
+ try:
227
+ await self._store.add_handle(
228
+ curated_contact_id,
229
+ h.gateway,
230
+ h.address,
231
+ is_primary=h.is_primary,
232
+ source=f"absorbed:{h.source}",
233
+ confidence=h.confidence,
234
+ )
235
+ except ValueError:
236
+ # Handle already on curated contact — fine
237
+ pass
238
+
239
+ await self._store.soft_delete(
240
+ discovered_contact.contact_id,
241
+ reason=f"absorbed_into:{curated_contact_id}",
242
+ performed_by="system",
243
+ )
244
+ await self._store.record_audit(
245
+ curated_contact_id,
246
+ "absorbed_discovered",
247
+ {
248
+ "absorbed_contact_id": discovered_contact.contact_id,
249
+ "person_node_id": discovered_contact.person_node_id,
250
+ },
251
+ )
252
+ logger.info(
253
+ "Absorbed discovered contact %s into curated %s",
254
+ discovered_contact.contact_id, curated_contact_id,
255
+ )
256
+ return True
257
+
258
+ async def prune_orphaned_shadows(self) -> int:
259
+ """Soft-delete shadow contacts whose Person node no longer exists.
260
+
261
+ Returns the number of contacts pruned.
262
+ """
263
+ # Fetch all shadow contacts with person_node_id
264
+ shadow_contacts = await self._store.list(
265
+ trust_tier="acquaintance", limit=10000,
266
+ )
267
+ if not shadow_contacts:
268
+ return 0
269
+
270
+ alive_ids = set(await self._graph.list_person_ids())
271
+ pruned = 0
272
+ for contact in shadow_contacts:
273
+ if not contact.person_node_id:
274
+ continue
275
+ if contact.person_node_id not in alive_ids:
276
+ await self._store.soft_delete(
277
+ contact.contact_id,
278
+ reason="orphaned_person_node",
279
+ performed_by="system",
280
+ )
281
+ pruned += 1
282
+ logger.info(
283
+ "Pruned orphaned shadow contact %s (Person %s deleted from graph)",
284
+ contact.contact_id, contact.person_node_id,
285
+ )
286
+ return pruned
287
+
288
+ # ── Internal helpers ─────────────────────────────────────────────────────────────
289
+
290
+ async def _find_existing_contact(
291
+ self,
292
+ person_id: str,
293
+ name: Optional[str],
294
+ handles: List[tuple[str, str]],
295
+ ) -> Optional[Contact]:
296
+ """Check if a Person already has a matching contact (curated or discovered)."""
297
+ # 1. Exact person_node_id match
298
+ contact = await self._store.find_by_person_node_id(person_id)
299
+ if contact:
300
+ return contact
301
+
302
+ # 2. Handle overlap
303
+ for gateway, address in handles:
304
+ contact = await self._store.resolve_handle(gateway, address)
305
+ if contact:
306
+ return contact
307
+
308
+ # 3. Name similarity
309
+ if name:
310
+ matches = await self._store.find_by_name(name, threshold=self._name_match_threshold)
311
+ if matches:
312
+ return matches[0]
313
+
314
+ return None
@@ -0,0 +1,69 @@
1
+ """Context gate — decide per-call whether context fits a model whole,
2
+ or needs chunking + retrieval (RAG) / map-reduce summarization first.
3
+
4
+ Motivation: a model's *useful* context window (the range over which exact
5
+ retrieval stays reliable) is often far below its advertised maximum.
6
+ Stuffing a 200K document into such a model silently degrades recall.
7
+ The gate measures the input against a configurable per-model budget
8
+ (``TierConfig.useful_context_tokens`` or an explicit budget) and picks
9
+ one of three strategies:
10
+
11
+ - ``PASS_THROUGH`` — fits; send whole.
12
+ - ``RETRIEVE`` — query-focused task; chunk, rank chunks against the
13
+ query (embeddings when available, lexical TF-IDF
14
+ otherwise), and pack the best chunks into budget.
15
+ - ``MAP_REDUCE`` — holistic task (summarize/review the whole thing);
16
+ summarize chunks via a caller-provided LLM callable,
17
+ or fall back to evenly-spaced coverage sampling.
18
+
19
+ Everything is host-agnostic and dependency-free (pure stdlib); embedding
20
+ and summarization are injected as optional callables so any agent can use
21
+ it with whatever LLM/embedding stack it already has.
22
+
23
+ Usage::
24
+
25
+ from apsimo.contextgate import prepare_context
26
+
27
+ prepared = await prepare_context(
28
+ content=big_document,
29
+ query="when did the outage start?",
30
+ budget_tokens=65536,
31
+ )
32
+ messages = [{"role": "user", "content": prepared.text}]
33
+
34
+ Configuration (env, all optional):
35
+
36
+ - ``COLONY_CONTEXT_GATE`` — ``auto`` (default) | ``on`` | ``off``
37
+ - ``COLONY_CONTEXT_GATE_HEADROOM`` — fraction of budget that triggers
38
+ gating (default 0.8)
39
+ - ``COLONY_CONTEXT_GATE_BUDGET`` — default token budget when neither
40
+ the caller nor the model tier provides one (default 0 = no gating)
41
+ - ``COLONY_CONTEXT_CHUNK_TOKENS`` — target chunk size (default 1024)
42
+ - ``COLONY_CONTEXT_OVERLAP_TOKENS`` — chunk overlap (default 128)
43
+ - ``COLONY_CONTEXT_CHARS_PER_TOKEN`` — token estimator ratio (default 4.0)
44
+ """
45
+
46
+ from apsimo.contextgate.estimate import estimate_tokens
47
+ from apsimo.contextgate.chunker import Chunk, chunk_text
48
+ from apsimo.contextgate.retrieve import rank_chunks
49
+ from apsimo.contextgate.gate import (
50
+ GateConfig,
51
+ GateDecision,
52
+ PreparedContext,
53
+ classify_task,
54
+ decide,
55
+ prepare_context,
56
+ )
57
+
58
+ __all__ = [
59
+ "Chunk",
60
+ "GateConfig",
61
+ "GateDecision",
62
+ "PreparedContext",
63
+ "chunk_text",
64
+ "classify_task",
65
+ "decide",
66
+ "estimate_tokens",
67
+ "prepare_context",
68
+ "rank_chunks",
69
+ ]