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,155 @@
1
+ """Qualified property observations in the existing world observation journal.
2
+
3
+ Stable entities describe what a thing is. This projection describes what was
4
+ observed, reported or hypothesized about it, when, and for which audience.
5
+ Confidence is deliberately absent: repetition is not independent evidence.
6
+ The host authenticates observation producers and resolves canonical references.
7
+ """
8
+ from datetime import datetime, timezone
9
+ import json
10
+ import re
11
+
12
+ from apsimo.self_model.situation import _safe_id, _validate_scope
13
+
14
+ KINDS = ('observed', 'reported', 'hypothesis')
15
+ SOURCE_PREFIX = 'world-property-v1:'
16
+
17
+
18
+ def canonical(value):
19
+ return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)
20
+
21
+
22
+ def epoch(value):
23
+ if isinstance(value, bool):
24
+ raise ValueError('invalid_world_observation_time')
25
+ if isinstance(value, (int, float)):
26
+ parsed = datetime.fromtimestamp(value, timezone.utc)
27
+ else:
28
+ parsed = datetime.fromisoformat(str(value).replace('Z', '+00:00'))
29
+ if parsed.tzinfo is None:
30
+ raise ValueError('world_observation_time_requires_timezone')
31
+ return parsed.timestamp()
32
+
33
+
34
+ def iso(value):
35
+ return datetime.fromtimestamp(epoch(value), timezone.utc).isoformat()
36
+
37
+
38
+ def observation(*, observation_id, entity_id, property_key, value, kind, producer,
39
+ evidence_refs, observed_at, fresh_until, subject_person_id,
40
+ viewer_scope, shareability, valid_from=None, valid_to=None, source_refs=()):
41
+ for key, item in [('observation_id', observation_id), ('entity_id', entity_id), ('producer', producer)]:
42
+ _safe_id(item, key)
43
+ if not isinstance(property_key, str) or not re.fullmatch('[a-z][a-z0-9_.-]{0,63}', property_key):
44
+ raise ValueError('invalid_world_property_key')
45
+ if kind not in KINDS:
46
+ raise ValueError('invalid_world_observation_kind')
47
+ if not isinstance(evidence_refs, (tuple, list)) or not 1 <= len(evidence_refs) <= 40:
48
+ raise ValueError('world_observation_requires_evidence')
49
+ refs = sorted({_safe_id(ref, 'evidence_ref') for ref in evidence_refs})
50
+ if not isinstance(source_refs, (list, tuple)) or len(source_refs) > 40:
51
+ raise ValueError('invalid_world_source_refs')
52
+ sources = []
53
+ for source in source_refs:
54
+ if (not isinstance(source, dict) or set(source) != {'source_id', 'source_version'}
55
+ or not isinstance(source['source_version'], str)
56
+ or not re.fullmatch('[0-9a-f]{64}', source['source_version'])):
57
+ raise ValueError('invalid_world_source_refs')
58
+ _safe_id(source['source_id'], 'source_id')
59
+ if 'source:' + source['source_id'] not in refs:
60
+ raise ValueError('world_source_ref_requires_evidence')
61
+ sources.append(dict(source))
62
+ # Preserve existing source/receipt/media identifiers; do not invent text
63
+ # claims or upload referenced media into this journal.
64
+ subject, viewer, sharing = _validate_scope(subject_person_id, viewer_scope, shareability)
65
+ observed, fresh = iso(observed_at), iso(fresh_until)
66
+ start, end = iso(valid_from if valid_from is not None else observed_at), iso(valid_to) if valid_to is not None else None
67
+ if epoch(fresh) < epoch(observed) or epoch(fresh) - epoch(observed) > 366 * 86400:
68
+ raise ValueError('world_observation_freshness_must_be_bounded')
69
+ if end is not None and epoch(end) <= epoch(start):
70
+ raise ValueError('invalid_world_observation_validity')
71
+ if len(canonical(value).encode()) > 8192:
72
+ raise ValueError('world_observation_value_too_large')
73
+ return dict(schema='WorldPropertyObservationV1', observation_id=observation_id,
74
+ entity_id=entity_id, property_key=property_key, value=value, kind=kind, producer=producer,
75
+ evidence_refs=refs, source_refs=sorted(sources, key=lambda s: s['source_id']),
76
+ observed_at=observed, fresh_until=fresh, valid_from=start, valid_to=end,
77
+ subject_person_id=subject, viewer_scope=viewer, shareability=sharing)
78
+
79
+
80
+ def project(records, *, entity_id, property_key, subject_person_id, viewer_scope,
81
+ shareability, as_of=None, coverage_limited=False):
82
+ """Observed-time view. Delayed arrival cannot win over a newer observation.
83
+
84
+ Each producer's latest applicable report is considered separately. Fresh
85
+ observations outrank reports, which outrank hypotheses, but disagreement
86
+ remains explicit. A stale latest observation cannot revive its older value.
87
+ ``as_of`` answers what held then using available evidence, not what the
88
+ system had already ingested then.
89
+ """
90
+ _validate_scope(subject_person_id, viewer_scope, shareability)
91
+ now = epoch(as_of if as_of is not None else datetime.now(timezone.utc).isoformat())
92
+ visible = []
93
+ for row in records:
94
+ if (row.get('schema') != 'WorldPropertyObservationV1' or row.get('entity_id') != entity_id
95
+ or row.get('property_key') != property_key):
96
+ continue
97
+ if (row['subject_person_id'], row['viewer_scope'], row['shareability']) != (subject_person_id, viewer_scope, shareability):
98
+ continue
99
+ if epoch(row['observed_at']) > now or epoch(row['valid_from']) > now:
100
+ continue
101
+ visible.append(row)
102
+ # Repackaging one receipt under fresh IDs cannot renew freshness or create
103
+ # independent corroboration. Keep its first observed occurrence per value.
104
+ dedup = {}
105
+ for row in sorted(visible, key=lambda r: (epoch(r['observed_at']), r['observation_id'])):
106
+ key = (row['producer'], row['kind'], tuple(row['evidence_refs']), canonical(row['value']))
107
+ dedup.setdefault(key, row)
108
+ latest = {}
109
+ for row in dedup.values():
110
+ key = (row['producer'], row['kind'])
111
+ moment = epoch(row['observed_at'])
112
+ if key not in latest or moment > epoch(latest[key][0]['observed_at']):
113
+ latest[key] = [row]
114
+ elif moment == epoch(latest[key][0]['observed_at']):
115
+ latest[key].append(row)
116
+ candidates = [dict(row, freshness='invalidated' if row.get('invalidated') else 'fresh' if epoch(row['fresh_until']) > now and
117
+ (row['valid_to'] is None or epoch(row['valid_to']) > now) else 'stale')
118
+ for rows in latest.values() for row in rows]
119
+ current = [row for row in candidates if row['freshness'] == 'fresh']
120
+ best = next(([r for r in current if r['kind'] == kind] for kind in KINDS
121
+ if any(r['kind'] == kind for r in current)), [])
122
+ values = {canonical(row['value']) for row in best}
123
+ state = 'conflicted' if len(values) > 1 else 'current' if best else 'stale' if any(
124
+ r['freshness'] == 'stale' for r in candidates) else 'unknown'
125
+ if coverage_limited:
126
+ state = 'unknown'
127
+ selected = best if state == 'current' else []
128
+ value = selected[0]['value'] if selected else None
129
+ refs = sorted({ref for row in best for ref in row['evidence_refs']})
130
+ return dict(schema='WorldPropertyStateV1', entity_id=entity_id, property_key=property_key,
131
+ subject_person_id=subject_person_id, viewer_scope=viewer_scope, shareability=shareability,
132
+ state=state, value=value, kind=selected[0]['kind'] if selected else None,
133
+ as_of=datetime.fromtimestamp(now, timezone.utc).isoformat(), time_basis='observed_at',
134
+ evidence_refs=refs, observations=sorted(candidates, key=lambda r: (r['producer'], r['kind'], r['observation_id'])),
135
+ has_disagreement=len({canonical(r['value']) for r in current}) > 1,
136
+ coverage_limited=coverage_limited, authority_granted=False)
137
+
138
+
139
+ def compact_situation(snapshot, *, limit=12):
140
+ """Scoped present-tense facts for context; stale values never become current."""
141
+ if snapshot is None:
142
+ return dict(schema='WorldSituationViewV1', state='unknown', facts=[], stale=[], authority_granted=False)
143
+ limit = max(1, min(int(limit), 32))
144
+ def compact(fact):
145
+ return {key: fact.public()[key] for key in ('observation_id', 'category', 'entity_id', 'state', 'active',
146
+ 'observed_at', 'fresh_until', 'freshness', 'evidence_refs', 'attributes')}
147
+ facts = [compact(f) for f in snapshot.facts if f.freshness == 'fresh']
148
+ stale = [dict(entity_id=f.entity_id, category=f.category, state='unknown', last_observed_state=f.state,
149
+ observed_at=f.observed_at, fresh_until=f.fresh_until, evidence_refs=list(f.evidence_refs))
150
+ for f in snapshot.facts if f.freshness != 'fresh']
151
+ return dict(schema='WorldSituationViewV1', snapshot_id=snapshot.snapshot_id, as_of=snapshot.as_of,
152
+ subject_person_id=snapshot.subject_person_id, viewer_scope=snapshot.viewer_scope,
153
+ shareability=snapshot.shareability, state='current' if facts else 'stale' if stale else 'unknown',
154
+ facts=facts[:limit], stale=stale[:limit], omitted=max(0, len(facts)-limit)+max(0, len(stale)-limit),
155
+ authority_granted=False)
@@ -0,0 +1,307 @@
1
+ """WorldModelPopulator -- turn conversation into structured world knowledge.
2
+
3
+ Wires the (already-existing, rule-based, zero-LLM) ConversationExtractor to the
4
+ world-model store so Colony actually learns the owner's people, companies,
5
+ projects and products from what he says. This was the missing link that left
6
+ the world model empty.
7
+
8
+ Observability-first and reversible:
9
+ * mode "off" -- do nothing.
10
+ * mode "shadow" -- extract + resolve + log EXACTLY what it WOULD populate,
11
+ write nothing (default).
12
+ * mode "live" -- upsert entities (dedup via EntityResolver) + light
13
+ relationships into the local world-model store only.
14
+
15
+ Every candidate is boundary-checked: if the owner set a directive to leave a
16
+ subject alone, it is never added to the world model.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import os
23
+ import re
24
+ from dataclasses import dataclass, field
25
+ from typing import Any, Dict, List, Optional, Tuple
26
+
27
+ from apsimo.world_model.extraction.conversation_extractor import (
28
+ ConversationExtractor, ExtractionCandidate,
29
+ )
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ def populate_mode() -> str:
35
+ from apsimo.util.autonomy_preset import resolve
36
+ return resolve("COLONY_WORLD_POPULATE_MODE",
37
+ ("off", "shadow", "live"), "shadow")
38
+
39
+
40
+ @dataclass
41
+ class PopulationReport:
42
+ source_id: str = ""
43
+ mode: str = "shadow"
44
+ created: List[Dict[str, Any]] = field(default_factory=list) # {name,type,confidence}
45
+ merged: List[Dict[str, Any]] = field(default_factory=list) # {name,type,into}
46
+ proposed: List[Dict[str, Any]] = field(default_factory=list)
47
+ relationships: List[Dict[str, Any]] = field(default_factory=list) # {source,rel,target}
48
+ skipped_boundary: List[str] = field(default_factory=list)
49
+
50
+ def total(self) -> int:
51
+ return len(self.created) + len(self.merged) + len(self.proposed)
52
+
53
+
54
+ # Person <-> Company work link cues (person <cue> company).
55
+ _WORK_CUE = re.compile(
56
+ r"\b(?:works?\s+(?:at|for)|employed\s+at|ceo\s+of|cto\s+of|founder\s+of|"
57
+ r"co-?founder\s+of|runs?|leads?|heads?|at)\b", re.IGNORECASE,
58
+ )
59
+
60
+ # Lowercase connectives that only appear inside a sentence, never in a real
61
+ # entity NAME. Their presence marks a candidate as a sentence fragment.
62
+ _FRAGMENT_WORDS = frozenset({
63
+ "the", "a", "an", "who", "whom", "that", "which", "and", "or", "but",
64
+ "at", "in", "on", "of", "for", "to", "with", "about", "met", "meet",
65
+ "works", "work", "working", "is", "are", "was", "were", "said", "says",
66
+ "from", "by", "joined", "join", "called", "told", "asked",
67
+ })
68
+
69
+
70
+ def _looks_like_fragment(name: str) -> bool:
71
+ """A real entity name is a few proper tokens, not a clause."""
72
+ toks = name.split()
73
+ if len(toks) > 5:
74
+ return True
75
+ lows = {t.lower().strip(".,") for t in toks}
76
+ return bool(lows & _FRAGMENT_WORDS)
77
+
78
+
79
+ # Generic role/system words + calendar words that rule-based NER mis-tags as
80
+ # people/places. Deployment-specific role names (the assistant persona, host
81
+ # agent, etc.) are added from config/env at runtime -- never hardcoded here.
82
+ _GENERIC_NOISE = frozenset({
83
+ "agent", "assistant", "system", "user", "bot", "ai",
84
+ "gpt", "model", "human", "owner",
85
+ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
86
+ "january", "february", "march", "april", "may", "june", "july", "august",
87
+ "september", "october", "november", "december", "today", "tomorrow",
88
+ "yesterday", "morning", "evening", "tonight", "week", "month", "year",
89
+ })
90
+
91
+
92
+ def _noise_names() -> frozenset:
93
+ """Generic role/calendar noise + any deployment-specific role names from
94
+ env (persona/agent name, plus COLONY_WORLD_NOISE_NAMES)."""
95
+ extra = set()
96
+ for var in ("COLONY_PERSONA_NAME", "COLONY_AGENT_NAME"):
97
+ v = os.environ.get(var, "").strip().lower()
98
+ if v:
99
+ extra.add(v)
100
+ for t in os.environ.get("COLONY_WORLD_NOISE_NAMES", "").split(","):
101
+ t = t.strip().lower()
102
+ if t:
103
+ extra.add(t)
104
+ return _GENERIC_NOISE | extra
105
+
106
+
107
+ # Common English nouns/verbs that never form part of a real person's name.
108
+ # Title-cased operational phrases ("Root Cause", "Orphan Messages") are the
109
+ # dominant person-noise class observed in live data.
110
+ _NON_NAME_WORDS = frozenset({
111
+ "cause", "root", "message", "messages", "session", "sessions",
112
+ "initiative", "initiatives", "operation", "operations", "orphan",
113
+ "fresh", "spawn", "error", "errors", "task", "tasks", "queue", "memory",
114
+ "review", "reviews", "summary", "report", "reports", "update", "updates",
115
+ "status", "issue", "issues", "problem", "problems", "fix", "fixes",
116
+ "bug", "bugs", "test", "tests", "daily", "weekly", "briefing", "digest",
117
+ "reminder", "alert", "alerts", "notification", "delivery", "channel",
118
+ "gateway", "bridge", "worker", "workers", "pending", "stale", "self",
119
+ "improvement", "cleanup", "backlog", "audit", "check", "checks",
120
+ })
121
+
122
+
123
+ def _is_low_quality(name: str, etype: str) -> bool:
124
+ """High-precision gate for world-model population.
125
+
126
+ Rule-based NER is noisy on conversation; drop the dominant noise classes so
127
+ the world model is trustworthy rather than full of sentence-initial words.
128
+ """
129
+ low = name.strip().lower()
130
+ _noise = _noise_names()
131
+ if low in _noise:
132
+ return True
133
+ # URLs, paths, emails, and placeholder fragments are never entity NAMES
134
+ # (they belong in external_ids/properties, not as canonical names).
135
+ if ("://" in low or low.startswith(("http", "www."))
136
+ or low.endswith(("...", "/")) or "/" in name.strip()):
137
+ return True
138
+ toks = name.split()
139
+ # Any token being a role/system/greeting word taints the whole name
140
+ # (e.g. a greeting + the assistant's own name, or "Agent ...").
141
+ tok_lows = {t.lower().strip(".,") for t in toks}
142
+ if tok_lows & (_noise | {"hey", "hi", "hello", "yo", "ok", "okay", "thanks"}):
143
+ return True
144
+ # Persons/locations must be multi-token proper names (single capitalized
145
+ # words at sentence start are the main noise source). Companies/products
146
+ # come from structured signals (org-suffix, domain, url) and are kept.
147
+ if etype in ("person", "location") and len(toks) < 2:
148
+ return True
149
+ # A "person" whose name contains a common operational noun is a
150
+ # title-cased phrase, not a human ("Root Cause", "Orphan Messages").
151
+ if etype == "person" and tok_lows & _NON_NAME_WORDS:
152
+ return True
153
+ return False
154
+
155
+
156
+ class WorldModelPopulator:
157
+ def __init__(
158
+ self,
159
+ store: Any,
160
+ directive_manager: Any = None,
161
+ mode: Optional[str] = None,
162
+ min_confidence: float = 0.30,
163
+ ) -> None:
164
+ self._store = store
165
+ self._directives = directive_manager
166
+ self._mode = mode or populate_mode()
167
+ self._min_conf = min_confidence
168
+ self._extractor = ConversationExtractor()
169
+ self._resolver = None
170
+ if store is not None:
171
+ try:
172
+ from apsimo.world_model.resolution.entity_resolver import EntityResolver
173
+ self._resolver = EntityResolver(store)
174
+ except Exception as exc:
175
+ logger.debug("EntityResolver unavailable: %s", exc)
176
+
177
+ @property
178
+ def mode(self) -> str:
179
+ return self._mode
180
+
181
+ def _boundary_ok(self, name: str) -> bool:
182
+ if self._directives is None:
183
+ # No directive manager configured: nothing to check against
184
+ # (distinct from "checked and allowed" — see log line).
185
+ logger.debug("world populate boundary UNCHECKED for %r "
186
+ "(no directive manager)", name)
187
+ return True
188
+ try:
189
+ from apsimo.directives import Action
190
+ return self._directives.check(Action(kind="populate", text=name, target=name)).allowed
191
+ except Exception:
192
+ # Fail closed, matching directives.guard.boundary_fail_closed():
193
+ # an error while evaluating an owner boundary must refuse.
194
+ logger.warning("world populate boundary check errored for %r "
195
+ "— failing closed", name, exc_info=True)
196
+ return False
197
+
198
+ async def populate_from_text(self, text: str, source_id: str) -> PopulationReport:
199
+ report = PopulationReport(source_id=source_id, mode=self._mode)
200
+ if self._mode == "off" or self._store is None or not text:
201
+ return report
202
+
203
+ try:
204
+ extraction = await self._extractor.extract(text, source_id)
205
+ except Exception as exc:
206
+ logger.debug("world populate extract failed: %s", exc)
207
+ return report
208
+
209
+ # Dedup within the message + confidence + boundary filter.
210
+ candidates: List[ExtractionCandidate] = []
211
+ seen = set()
212
+ for c in extraction.entities:
213
+ key = (c.text.strip().lower(), c.entity_type)
214
+ if not c.text.strip() or key in seen or c.confidence < self._min_conf:
215
+ continue
216
+ seen.add(key)
217
+ # Reject sentence-fragment "names" and low-quality/noise candidates
218
+ # that slip through rule-based NER (high-precision population).
219
+ if _looks_like_fragment(c.text) or _is_low_quality(c.text, c.entity_type):
220
+ continue
221
+ if not self._boundary_ok(c.text):
222
+ report.skipped_boundary.append(c.text)
223
+ continue
224
+ candidates.append(c)
225
+
226
+ name_to_type: Dict[str, str] = {}
227
+ for c in candidates:
228
+ name_to_type[c.text] = c.entity_type
229
+ await self._handle_candidate(c, report)
230
+
231
+ self._infer_relationships(text, candidates, report)
232
+ return report
233
+
234
+ async def _handle_candidate(self, c: ExtractionCandidate, report: PopulationReport) -> None:
235
+ action = "create"
236
+ matched = None
237
+ if self._resolver is not None:
238
+ try:
239
+ res = await self._resolver.resolve(c, c.entity_type)
240
+ action = getattr(res.action, "value", str(res.action))
241
+ matched = res.matched_entity_id
242
+ except Exception as exc:
243
+ logger.debug("resolve failed for %r: %s", c.text, exc)
244
+
245
+ rec = {"name": c.text, "type": c.entity_type, "confidence": round(c.confidence, 2)}
246
+ if action == "merge":
247
+ rec["into"] = matched
248
+ report.merged.append(rec)
249
+ if self._mode == "live" and matched:
250
+ try:
251
+ await self._store.add_entity_alias(matched, c.text)
252
+ except Exception:
253
+ pass
254
+ # Repeat mention (covers exact-match, external-id and alias
255
+ # resolves — they all arrive here as MERGE): the entity was
256
+ # just seen again, so touch last_seen / mention_count /
257
+ # confidence. Reinforcement only — a repeat-mention must never
258
+ # make an entity MORE prunable than a single mention.
259
+ try:
260
+ await self._store.reinforce_entity(matched)
261
+ except Exception as exc:
262
+ logger.debug("reinforce_entity failed for %r: %s", matched, exc)
263
+ return
264
+ if action == "propose":
265
+ rec["near"] = matched
266
+ report.proposed.append(rec)
267
+ # in live mode a proposal still creates the candidate + queues review,
268
+ # but to stay conservative we only create on a clean CREATE.
269
+ return
270
+ # CREATE
271
+ report.created.append(rec)
272
+ if self._mode == "live":
273
+ try:
274
+ from apsimo.world_model.entities import ENTITY_CLASS_MAP, BaseEntity
275
+ from apsimo.world_model.sqlite.backend import _generate_id
276
+ cls = ENTITY_CLASS_MAP.get(c.entity_type, BaseEntity)
277
+ ent = cls(id=_generate_id("we"), name=c.text,
278
+ entity_type=c.entity_type, confidence=c.confidence)
279
+ await self._store.upsert_entity(ent)
280
+ except Exception as exc:
281
+ logger.debug("upsert_entity failed for %r: %s", c.text, exc)
282
+
283
+ def _infer_relationships(self, text: str, candidates: List[ExtractionCandidate],
284
+ report: PopulationReport) -> None:
285
+ """Light, conservative relationship inference (name-level in the report).
286
+
287
+ Only person<->company WORKS_AT when a clear work cue links them, and
288
+ person<->person KNOWS co-occurrence at low confidence. Written in live
289
+ mode is deferred to LLM-assist; here we surface them for observability.
290
+ """
291
+ persons = [c for c in candidates if c.entity_type == "person"]
292
+ companies = [c for c in candidates if c.entity_type == "company"]
293
+ for p in persons:
294
+ for co in companies:
295
+ lo, hi = sorted([p.start_char, co.start_char])
296
+ between = text[lo:hi]
297
+ if _WORK_CUE.search(between) or _WORK_CUE.search(
298
+ text[max(0, p.start_char - 20):p.start_char]
299
+ ):
300
+ report.relationships.append(
301
+ {"source": p.text, "rel": "WM_WORKS_AT", "target": co.text,
302
+ "confidence": 0.5})
303
+ for i in range(len(persons)):
304
+ for j in range(i + 1, len(persons)):
305
+ report.relationships.append(
306
+ {"source": persons[i].text, "rel": "WM_KNOWS",
307
+ "target": persons[j].text, "confidence": 0.3})
@@ -0,0 +1 @@
1
+ # Postgres backend for World Model