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,177 @@
1
+ """World-model expectation resolvers (Mind M3a x world model).
2
+
3
+ The expectation engine keeps its store generic and lets subsystems register
4
+ how a class of predictions gets checked against reality. This module supplies
5
+ the two world-model classes:
6
+
7
+ - ``world-relationship:`` -- "this relationship is still active at the
8
+ horizon". ``detail`` carries ``source_id``/``target_id`` and optionally
9
+ ``relationship_type``.
10
+ - ``world-property:`` -- "this entity property still holds this value at the
11
+ horizon". ``detail`` carries ``entity_id``/``key``/``value``.
12
+
13
+ Resolvers return True (hit), False (miss), or None when the world model
14
+ cannot see the subject -- the prediction then stays pending and eventually
15
+ goes ``unresolved``, which is excluded from calibration (never a fabricated
16
+ miss). The world store is fetched lazily at resolve time so boot order does
17
+ not matter, and the async store API is bridged the same way the briefing
18
+ aggregators do it (``asyncio.run`` off-loop, a worker thread on-loop).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ import concurrent.futures
25
+ import logging
26
+ from typing import Any, Optional
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ RELATIONSHIP_PREFIX = "world-relationship:"
31
+ PROPERTY_PREFIX = "world-property:"
32
+
33
+ # Historical edge-survival bookkeeping is self-consistency, not an
34
+ # independently observed causal outcome. Retain old rows/functions for audit.
35
+ CAUSAL_PREFIX = "world-causal:"
36
+
37
+
38
+ def _world_store() -> Any:
39
+ try:
40
+ from apsimo.api.routers.host import _world_store as ws
41
+ return ws
42
+ except Exception:
43
+ return None
44
+
45
+
46
+ def _run_async(coro: Any) -> Any:
47
+ """Run a store coroutine from the engine's synchronous resolver path."""
48
+ try:
49
+ asyncio.get_running_loop()
50
+ except RuntimeError:
51
+ return asyncio.run(coro)
52
+ # Already on an event loop (the autonomy phase) — blocking on this loop
53
+ # would deadlock, so complete the coroutine on its own loop in a worker.
54
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
55
+ return ex.submit(asyncio.run, coro).result(timeout=10.0)
56
+
57
+
58
+ def _norm(v: Any) -> str:
59
+ return str(v if v is not None else "").strip().lower()
60
+
61
+
62
+ def resolve_relationship_still_active(prediction: Any) -> Optional[bool]:
63
+ """Hit when at least one matching relationship is still active.
64
+
65
+ Causal-typed predictions are NOT resolved here (query-only guard,
66
+ H2.5): a causal edge must never be scored true/false through the
67
+ generic relationship machinery — its falsifiability path is the causal
68
+ subsystem's own. Returning None leaves the prediction unresolved,
69
+ which calibration excludes (never a fabricated hit or miss).
70
+ """
71
+ store = _world_store()
72
+ detail = getattr(prediction, "detail", None) or {}
73
+ source_id = detail.get("source_id")
74
+ target_id = detail.get("target_id")
75
+ if store is None or not source_id or not target_id:
76
+ return None
77
+ try:
78
+ from apsimo.world_model.causal_policy import is_causal
79
+ if is_causal(detail.get("relationship_type") or ""):
80
+ return None
81
+ except Exception:
82
+ return None
83
+ rels = _run_async(store.query_relationships(
84
+ source_id=source_id, target_id=target_id,
85
+ relationship_type=detail.get("relationship_type") or None,
86
+ active_only=False, min_confidence=0.0, limit=50))
87
+ if not rels:
88
+ # never observed -> the world model cannot score this prediction
89
+ return None
90
+ return any(getattr(r, "is_active", False) for r in rels)
91
+
92
+
93
+ def resolve_property_unchanged(prediction: Any) -> Optional[bool]:
94
+ """Hit when the entity property still equals the predicted value."""
95
+ store = _world_store()
96
+ detail = getattr(prediction, "detail", None) or {}
97
+ entity_id = detail.get("entity_id")
98
+ key = detail.get("key")
99
+ if store is None or not entity_id or not key or "value" not in detail:
100
+ return None
101
+ entity = _run_async(store.get_entity(entity_id, min_confidence=0.0))
102
+ if entity is None:
103
+ return None
104
+ props = getattr(entity, "properties", None) or {}
105
+ if key not in props:
106
+ # the property is no longer tracked at all — that is a visibility
107
+ # loss, not evidence the value changed; leave it unresolvable
108
+ return None
109
+ return _norm(props.get(key)) == _norm(detail.get("value"))
110
+
111
+
112
+ def resolve_causal_edge(prediction: Any) -> Optional[bool]:
113
+ """Historical self-consistency only; never predictive calibration.
114
+
115
+ HIT: the edge still exists, is active, holds at least the confidence it
116
+ had when the prediction was made, and no opposing edge (H2.3 polarity)
117
+ exists on the same ordered pair.
118
+ MISS: the edge decayed below its creation confidence, was deleted or
119
+ deactivated, or acquired an active opposing edge.
120
+ None: the world store cannot be seen at all (never a fabricated miss).
121
+ """
122
+ store = _world_store()
123
+ detail = getattr(prediction, "detail", None) or {}
124
+ edge_id = detail.get("edge_id")
125
+ source_id = detail.get("source_id")
126
+ target_id = detail.get("target_id")
127
+ rel_type = str(detail.get("relationship_type") or "").upper()
128
+ try:
129
+ conf_floor = float(detail.get("confidence_at_creation"))
130
+ except (TypeError, ValueError):
131
+ return None
132
+ if store is None or not edge_id or not source_id or not target_id\
133
+ or not rel_type:
134
+ return None
135
+ try:
136
+ rels = _run_async(store.query_relationships(
137
+ source_id=source_id, target_id=target_id,
138
+ relationship_type=rel_type, min_confidence=0.0, limit=50))
139
+ except Exception:
140
+ return None # store unreachable -> unseen, not a miss
141
+ edge = next((r for r in rels or [] if r.id == edge_id), None)
142
+ if edge is None:
143
+ # fall back to same pair + type (the claim, if not the row)
144
+ edge = (rels or [None])[0]
145
+ if edge is None:
146
+ return False # deleted — the claim did not survive
147
+ if getattr(edge, "valid_to", None) is not None:
148
+ return False # deactivated
149
+ if float(getattr(edge, "confidence", 0.0) or 0.0) < conf_floor:
150
+ return False # decayed below its creation confidence
151
+ # Opposing edge on the same ordered pair (H2.3 polarity sets)?
152
+ try:
153
+ from apsimo.world_model.causal_maintenance import (
154
+ NEGATIVE_CAUSAL, POSITIVE_CAUSAL,
155
+ )
156
+ opposing = (NEGATIVE_CAUSAL if rel_type in POSITIVE_CAUSAL
157
+ else POSITIVE_CAUSAL if rel_type in NEGATIVE_CAUSAL
158
+ else frozenset())
159
+ for opp_type in sorted(opposing):
160
+ opp = _run_async(store.query_relationships(
161
+ source_id=source_id, target_id=target_id,
162
+ relationship_type=opp_type, min_confidence=0.0, limit=10))
163
+ if any(getattr(o, "valid_to", None) is None for o in opp or []):
164
+ return False # opposed
165
+ except Exception:
166
+ return None # cannot verify the no-opposition clause -> unseen
167
+ return True
168
+
169
+
170
+ def register_world_resolvers(engine: Any) -> None:
171
+ """Attach the world-model resolvers to an ExpectationEngine."""
172
+ if engine is None:
173
+ return
174
+ engine.register_resolver(RELATIONSHIP_PREFIX,
175
+ resolve_relationship_still_active)
176
+ engine.register_resolver(PROPERTY_PREFIX, resolve_property_unchanged)
177
+ # Causal edge survival is deliberately not registered as outcome truth.
@@ -0,0 +1,7 @@
1
+ """World model entity extraction from multiple document formats."""
2
+
3
+ from apsimo.world_model.extraction.base import FormatExtractor
4
+ from apsimo.world_model.extraction.detector import FormatDetector
5
+ from apsimo.world_model.extraction.pipeline import ExtractionPipeline
6
+
7
+ __all__ = ["FormatExtractor", "FormatDetector", "ExtractionPipeline"]
@@ -0,0 +1,62 @@
1
+ """Base class for format extractors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class ExtractedEntity:
13
+ """An entity extracted from a document."""
14
+
15
+ def __init__(
16
+ self,
17
+ name: str,
18
+ entity_type: str,
19
+ attributes: Optional[Dict[str, Any]] = None,
20
+ source_offset: Optional[int] = None,
21
+ confidence: float = 1.0,
22
+ ):
23
+ self.name = name
24
+ self.entity_type = entity_type
25
+ self.attributes = attributes or {}
26
+ self.source_offset = source_offset
27
+ self.confidence = confidence
28
+
29
+ def to_dict(self) -> dict:
30
+ return {
31
+ "name": self.name,
32
+ "type": self.entity_type,
33
+ "attributes": self.attributes,
34
+ "confidence": self.confidence,
35
+ }
36
+
37
+
38
+ class FormatExtractor(ABC):
39
+ """Base class for document format extractors.
40
+
41
+ Each extractor handles one or more MIME types and can:
42
+ 1. Extract raw text from the document
43
+ 2. Extract structured entities directly (without LLM)
44
+ """
45
+
46
+ @abstractmethod
47
+ def supported_formats(self) -> List[str]:
48
+ """Return list of MIME types this extractor handles."""
49
+ ...
50
+
51
+ @abstractmethod
52
+ async def extract_text(self, content: bytes, metadata: Dict[str, Any]) -> str:
53
+ """Extract raw text from content. Returns empty string if extraction fails."""
54
+ ...
55
+
56
+ async def extract_entities(self, content: bytes, metadata: Dict[str, Any]) -> List[ExtractedEntity]:
57
+ """Extract structured entities directly from content (no LLM).
58
+
59
+ Default implementation returns empty list. Override for formats
60
+ that have structured data (JSON, CSV).
61
+ """
62
+ return []
@@ -0,0 +1,262 @@
1
+ """Lightweight post-message entity extractor.
2
+
3
+ Uses rule-based NER and regex patterns. Zero LLM calls.
4
+ MUST complete within 50ms for single-message inputs.
5
+ """
6
+
7
+ import re
8
+ import time
9
+ from dataclasses import dataclass, field
10
+ from typing import List, Optional
11
+
12
+ from ..confidence import CONFIDENCE_BY_SOURCE, apply_extraction_adjustments
13
+
14
+
15
+ # ── Regex patterns for structured entity signals ──────────────────────────────
16
+
17
+ _EMAIL_RE = re.compile(
18
+ r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b"
19
+ )
20
+ _DOMAIN_RE = re.compile(
21
+ r"\b(?:https?://)?([A-Za-z0-9\-]+\.(?:com|org|net|io|ai|co|app|dev|tech))\b",
22
+ re.IGNORECASE,
23
+ )
24
+ _TICKER_RE = re.compile(r"\b([A-Z]{2,5})\b(?=\s*(?:stock|shares|ticker|NYSE|NASDAQ|$))")
25
+ _PHONE_RE = re.compile(
26
+ r"\+?\d[\d\s\-\(\)]{7,}\d"
27
+ )
28
+ _URL_RE = re.compile(
29
+ r"https?://[^\s<>\"']+", re.IGNORECASE
30
+ )
31
+
32
+ # Capitalized-word person/org heuristic (1–4 consecutive title-cased tokens).
33
+ # Single-word matches are filtered against _NAME_STOPWORDS before use.
34
+ _CAPITALIZED_NAME_RE = re.compile(
35
+ r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3})\b"
36
+ )
37
+
38
+ # Common English words that appear capitalized but are not proper names.
39
+ # Filtered out when the regex produces a single-word match.
40
+ _NAME_STOPWORDS = frozenset({
41
+ "The", "A", "An", "This", "That", "These", "Those",
42
+ "He", "She", "It", "We", "They", "You", "I",
43
+ "His", "Her", "Its", "Our", "Their", "My", "Your",
44
+ "Is", "Are", "Was", "Were", "Be", "Been", "Being",
45
+ "Has", "Have", "Had", "Do", "Does", "Did",
46
+ "Will", "Would", "Could", "Should", "May", "Might", "Must",
47
+ "In", "At", "To", "By", "For", "Of", "On", "Off",
48
+ "With", "From", "About", "Into", "Through", "During",
49
+ "And", "Or", "But", "If", "As", "So", "Yet", "Nor",
50
+ "Not", "No", "Yes", "Ok", "Please", "Thanks", "Thank",
51
+ "Here", "There", "Now", "Then", "When", "Where", "How", "Why",
52
+ "What", "Which", "Who", "Whose",
53
+ "Also", "Just", "Very", "More", "Most", "Some", "Any",
54
+ "All", "Both", "Each", "Few", "Many", "Much", "Other",
55
+ "After", "Before", "Over", "Under", "Above", "Below",
56
+ "Between", "Among", "Against", "Along", "Around",
57
+ "Can", "Cannot", "Well", "Even", "Still", "Already",
58
+ "Never", "Always", "Often", "Again", "Since", "Once",
59
+ "Hi", "Hey", "Hello", "Okay", "Sure", "Yeah",
60
+ })
61
+
62
+ # Common org suffixes. The name is 1-4 consecutive Title-case tokens immediately
63
+ # before the suffix -- NOT a greedy run across lowercase words (which used to
64
+ # swallow whole sentences, e.g. "I met Alice at Acme Corp" -> one "company").
65
+ _ORG_SUFFIX_RE = re.compile(
66
+ r"\b((?:[A-Z][A-Za-z&.\-]*\s+){0,3}[A-Z][A-Za-z&.\-]*"
67
+ r"\s+(?:Inc\.?|Corp\.?|LLC|Ltd\.?|Co\.?|Group|Foundation|Institute|University|"
68
+ r"School|Hospital|Agency|Association|Society|Department))\b"
69
+ )
70
+
71
+ # Location indicators
72
+ _LOCATION_INDICATORS = frozenset({
73
+ "in", "at", "from", "to", "near", "visit", "located", "based", "headquartered",
74
+ "office", "city", "country", "region", "state", "province",
75
+ })
76
+
77
+
78
+ @dataclass
79
+ class ExtractionCandidate:
80
+ """A candidate entity or relationship found in text."""
81
+ text: str # the extracted text span
82
+ entity_type: str # tentative entity type
83
+ start_char: int
84
+ end_char: int
85
+ confidence: float
86
+ context_window: str # surrounding text for disambiguation
87
+ linked_entity_id: Optional[str] = None # if resolved to existing entity
88
+
89
+
90
+ @dataclass
91
+ class ExtractionResult:
92
+ entities: List[ExtractionCandidate] = field(default_factory=list)
93
+ relationships: List[dict] = field(default_factory=list)
94
+ extraction_ms: float = 0.0
95
+ source_id: str = "" # message ID that was processed
96
+
97
+
98
+ class ConversationExtractor:
99
+ """Lightweight post-message entity extractor.
100
+
101
+ Uses rule-based NER and regex patterns. Zero LLM calls.
102
+ MUST complete within 50ms for single-message inputs.
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ min_message_length: int = 20,
108
+ existing_entities: Optional[List[str]] = None,
109
+ ) -> None:
110
+ self._min_length = min_message_length
111
+ self._known_names: List[str] = list(existing_entities or [])
112
+
113
+ def _context(self, text: str, start: int, end: int, window: int = 40) -> str:
114
+ return text[max(0, start - window): min(len(text), end + window)]
115
+
116
+ async def extract(
117
+ self,
118
+ message_text: str,
119
+ source_id: str,
120
+ existing_entities: Optional[List[str]] = None,
121
+ ) -> ExtractionResult:
122
+ """Extract entity candidates from a single message.
123
+
124
+ Args:
125
+ message_text: Raw message content.
126
+ source_id: The message ID for provenance tracking.
127
+ existing_entities: List of known entity names for fast linking.
128
+
129
+ Returns:
130
+ ExtractionResult with candidates and elapsed time.
131
+ """
132
+ t0 = time.monotonic()
133
+ result = ExtractionResult(source_id=source_id)
134
+
135
+ if len(message_text) < self._min_length:
136
+ result.extraction_ms = (time.monotonic() - t0) * 1000
137
+ return result
138
+
139
+ known = set(existing_entities or []) | set(self._known_names)
140
+ seen_spans: List[tuple] = [] # (start, end) to avoid duplicates
141
+
142
+ def _overlaps(s: int, e: int) -> bool:
143
+ for a, b in seen_spans:
144
+ if s < b and e > a:
145
+ return True
146
+ return False
147
+
148
+ # ── Emails → person (high confidence) ────────────────────────────────
149
+ for m in _EMAIL_RE.finditer(message_text):
150
+ if _overlaps(m.start(), m.end()):
151
+ continue
152
+ adj = [0.15] # email pattern bonus
153
+ if m.group() in known:
154
+ adj.append(0.20)
155
+ conf = apply_extraction_adjustments(
156
+ CONFIDENCE_BY_SOURCE["conversation_ner"], adj
157
+ )
158
+ result.entities.append(ExtractionCandidate(
159
+ text=m.group(),
160
+ entity_type="person",
161
+ start_char=m.start(),
162
+ end_char=m.end(),
163
+ confidence=conf,
164
+ context_window=self._context(message_text, m.start(), m.end()),
165
+ ))
166
+ seen_spans.append((m.start(), m.end()))
167
+
168
+ # ── URLs → product/company ─────────────────────────────────────────
169
+ for m in _URL_RE.finditer(message_text):
170
+ if _overlaps(m.start(), m.end()):
171
+ continue
172
+ conf = apply_extraction_adjustments(
173
+ CONFIDENCE_BY_SOURCE["conversation_ner"], [0.05]
174
+ )
175
+ result.entities.append(ExtractionCandidate(
176
+ text=m.group(),
177
+ entity_type="product",
178
+ start_char=m.start(),
179
+ end_char=m.end(),
180
+ confidence=conf,
181
+ context_window=self._context(message_text, m.start(), m.end()),
182
+ ))
183
+ seen_spans.append((m.start(), m.end()))
184
+
185
+ # ── Org suffix → company ──────────────────────────────────────────
186
+ for m in _ORG_SUFFIX_RE.finditer(message_text):
187
+ full = m.group().strip()
188
+ if _overlaps(m.start(), m.end()):
189
+ continue
190
+ adj = []
191
+ if full in known:
192
+ adj.append(0.20)
193
+ conf = apply_extraction_adjustments(
194
+ CONFIDENCE_BY_SOURCE["conversation_ner"], adj
195
+ )
196
+ result.entities.append(ExtractionCandidate(
197
+ text=full,
198
+ entity_type="company",
199
+ start_char=m.start(),
200
+ end_char=m.end(),
201
+ confidence=conf,
202
+ context_window=self._context(message_text, m.start(), m.end()),
203
+ ))
204
+ seen_spans.append((m.start(), m.end()))
205
+
206
+ # ── Capitalized names → person ────────────────────────────────────
207
+ for m in _CAPITALIZED_NAME_RE.finditer(message_text):
208
+ name = m.group().strip()
209
+ if _overlaps(m.start(), m.end()):
210
+ continue
211
+ # Skip single-word all-caps (likely acronym/ticker)
212
+ if name.isupper():
213
+ continue
214
+ is_single_word = " " not in name
215
+ # Skip single-word matches that are common English stopwords
216
+ if is_single_word and name in _NAME_STOPWORDS:
217
+ continue
218
+ adj = []
219
+ if name in known:
220
+ adj.append(0.20)
221
+ # Single-word proper names get lower confidence than multi-word names
222
+ if is_single_word:
223
+ adj.append(-0.10)
224
+ conf = apply_extraction_adjustments(
225
+ CONFIDENCE_BY_SOURCE["conversation_ner"], adj
226
+ )
227
+ # Heuristic: check if preceded by location indicator
228
+ prefix = message_text[max(0, m.start() - 30):m.start()].lower()
229
+ etype = "person"
230
+ for indicator in _LOCATION_INDICATORS:
231
+ if indicator in prefix.split():
232
+ etype = "location"
233
+ break
234
+ result.entities.append(ExtractionCandidate(
235
+ text=name,
236
+ entity_type=etype,
237
+ start_char=m.start(),
238
+ end_char=m.end(),
239
+ confidence=conf,
240
+ context_window=self._context(message_text, m.start(), m.end()),
241
+ ))
242
+ seen_spans.append((m.start(), m.end()))
243
+
244
+ # ── Domain names → company ────────────────────────────────────────
245
+ for m in _DOMAIN_RE.finditer(message_text):
246
+ if _overlaps(m.start(), m.end()):
247
+ continue
248
+ conf = apply_extraction_adjustments(
249
+ CONFIDENCE_BY_SOURCE["conversation_ner"], [0.05]
250
+ )
251
+ result.entities.append(ExtractionCandidate(
252
+ text=m.group(1),
253
+ entity_type="company",
254
+ start_char=m.start(),
255
+ end_char=m.end(),
256
+ confidence=conf,
257
+ context_window=self._context(message_text, m.start(), m.end()),
258
+ ))
259
+ seen_spans.append((m.start(), m.end()))
260
+
261
+ result.extraction_ms = (time.monotonic() - t0) * 1000
262
+ return result
@@ -0,0 +1,74 @@
1
+ """Format detection for document content."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Common extension to MIME type mapping
12
+ EXTENSION_MAP = {
13
+ ".pdf": "application/pdf",
14
+ ".html": "text/html",
15
+ ".htm": "text/html",
16
+ ".json": "application/json",
17
+ ".csv": "text/csv",
18
+ ".txt": "text/plain",
19
+ ".md": "text/markdown",
20
+ ".xml": "application/xml",
21
+ ".yaml": "application/x-yaml",
22
+ ".yml": "application/x-yaml",
23
+ }
24
+
25
+ # Magic byte signatures
26
+ MAGIC_SIGNATURES = {
27
+ b"%PDF": "application/pdf",
28
+ b"<!DOCTYPE": "text/html",
29
+ b"<html": "text/html",
30
+ b"<?xml": "application/xml",
31
+ }
32
+
33
+
34
+ class FormatDetector:
35
+ """Detect document format from content, filename, or explicit MIME type."""
36
+
37
+ def detect(
38
+ self,
39
+ content: bytes,
40
+ filename: str = "",
41
+ mime_type: str = "",
42
+ ) -> str:
43
+ """Detect content format.
44
+
45
+ Priority: explicit mime_type > filename extension > magic bytes > fallback.
46
+ """
47
+ # 1. Explicit MIME type wins
48
+ if mime_type:
49
+ return mime_type
50
+
51
+ # 2. Filename extension
52
+ if filename:
53
+ ext = Path(filename).suffix.lower()
54
+ if ext in EXTENSION_MAP:
55
+ return EXTENSION_MAP[ext]
56
+
57
+ # 3. Magic bytes
58
+ if content:
59
+ for sig, fmt in MAGIC_SIGNATURES.items():
60
+ if content[:len(sig)].startswith(sig):
61
+ return fmt
62
+
63
+ # JSON detection
64
+ stripped = content[:512].strip()
65
+ if stripped and stripped[0:1] in (b"{", b"["):
66
+ return "application/json"
67
+
68
+ # CSV detection (simple heuristic)
69
+ first_line = content[:512].split(b"\n")[0]
70
+ if b"," in first_line and first_line.count(b",") >= 2:
71
+ return "text/csv"
72
+
73
+ # 4. Fallback
74
+ return "text/plain"
@@ -0,0 +1,78 @@
1
+ """Background document entity extractor.
2
+
3
+ Invoked via task queue. MAY use heavier NER models.
4
+ Processes structured documents and returns typed entity candidates.
5
+ """
6
+
7
+ import base64
8
+ from enum import Enum
9
+ from typing import Optional
10
+
11
+ from .conversation_extractor import ExtractionResult, ConversationExtractor
12
+
13
+
14
+ class DocumentType(str, Enum):
15
+ PDF = "pdf"
16
+ HTML = "html"
17
+ EMAIL_BODY = "email_body"
18
+ PLAIN_TEXT = "plain_text"
19
+ CALENDAR_ICS = "calendar_ics"
20
+
21
+
22
+ class DocumentExtractor:
23
+ """Background document entity extractor.
24
+
25
+ Invoked via task queue. MAY use heavier NER models.
26
+ Processes structured documents and returns typed entity candidates.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ self._conv_extractor = ConversationExtractor(min_message_length=0)
31
+
32
+ async def extract_from_document(
33
+ self,
34
+ content: bytes,
35
+ document_type: DocumentType,
36
+ source_reference: str,
37
+ ) -> ExtractionResult:
38
+ """Extract entities from a document.
39
+
40
+ Args:
41
+ content: Raw document bytes.
42
+ document_type: The type of document being processed.
43
+ source_reference: Email ID, URL, or file path for provenance.
44
+
45
+ Returns:
46
+ ExtractionResult with full entity and relationship candidates.
47
+ """
48
+ text = self._decode_content(content, document_type)
49
+ return await self._conv_extractor.extract(
50
+ message_text=text,
51
+ source_id=source_reference,
52
+ )
53
+
54
+ def _decode_content(self, content: bytes, document_type: DocumentType) -> str:
55
+ """Decode raw bytes to text based on document type."""
56
+ if document_type == DocumentType.HTML:
57
+ return self._strip_html(content.decode("utf-8", errors="replace"))
58
+ elif document_type == DocumentType.CALENDAR_ICS:
59
+ return self._parse_ics(content.decode("utf-8", errors="replace"))
60
+ else:
61
+ return content.decode("utf-8", errors="replace")
62
+
63
+ def _strip_html(self, html: str) -> str:
64
+ """Minimal HTML tag stripper."""
65
+ import re
66
+ text = re.sub(r"<[^>]+>", " ", html)
67
+ text = re.sub(r"&[a-z]+;", " ", text)
68
+ return re.sub(r"\s+", " ", text).strip()
69
+
70
+ def _parse_ics(self, ics: str) -> str:
71
+ """Extract displayable text from iCal content."""
72
+ lines = []
73
+ for line in ics.splitlines():
74
+ for prefix in ("SUMMARY:", "DESCRIPTION:", "LOCATION:", "ORGANIZER:"):
75
+ if line.startswith(prefix):
76
+ lines.append(line[len(prefix):].strip())
77
+ break
78
+ return " ".join(lines)
@@ -0,0 +1,24 @@
1
+ """Extraction formats package."""
2
+
3
+ from apsimo.world_model.extraction.formats.text import TextExtractor
4
+ from apsimo.world_model.extraction.formats.json_fmt import JSONExtractor
5
+ from apsimo.world_model.extraction.formats.csv_fmt import CSVExtractor
6
+
7
+ # Optional extractors — load if dependencies are available
8
+ try:
9
+ from apsimo.world_model.extraction.formats.pdf import PDFExtractor
10
+ except ImportError:
11
+ PDFExtractor = None
12
+
13
+ try:
14
+ from apsimo.world_model.extraction.formats.html_fmt import HTMLExtractor
15
+ except ImportError:
16
+ HTMLExtractor = None
17
+
18
+ __all__ = [
19
+ "TextExtractor",
20
+ "JSONExtractor",
21
+ "CSVExtractor",
22
+ "PDFExtractor",
23
+ "HTMLExtractor",
24
+ ]