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,103 @@
1
+ """DuckDuckGo HTML search provider — zero-config fallback.
2
+
3
+ Uses the lite HTML endpoint (https://html.duckduckgo.com/html/) because
4
+ it requires no API key and no JS rendering. This is best-effort — DDG
5
+ can change their markup at any time — so any failure degrades to an
6
+ empty result set rather than raising.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import html as _html
12
+ import logging
13
+ import os
14
+ import re
15
+ from typing import List
16
+
17
+ import httpx
18
+
19
+ from apsimo.research.search.base import SearchProvider, SearchResult
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ _RESULT_RE = re.compile(
25
+ r'<a rel="nofollow" class="result__a" href="(?P<url>[^"]+)"[^>]*>'
26
+ r"(?P<title>.*?)</a>"
27
+ r'.*?<a class="result__snippet"[^>]*>(?P<snippet>.*?)</a>',
28
+ re.DOTALL,
29
+ )
30
+
31
+ _TAG_RE = re.compile(r"<[^>]+>")
32
+
33
+
34
+ def _strip_tags(s: str) -> str:
35
+ return _html.unescape(_TAG_RE.sub("", s)).strip()
36
+
37
+
38
+ class DuckDuckGoProvider(SearchProvider):
39
+ """HTML-endpoint DuckDuckGo provider.
40
+
41
+ No API key required. Fragile against markup changes — callers
42
+ MUST tolerate empty results silently.
43
+ """
44
+
45
+ _ENDPOINT = "https://html.duckduckgo.com/html/"
46
+
47
+ def __init__(self, timeout_secs: float = 8.0) -> None:
48
+ self._timeout = timeout_secs
49
+
50
+ @property
51
+ def name(self) -> str:
52
+ return "duckduckgo"
53
+
54
+ @property
55
+ def rate_limit_per_minute(self) -> int:
56
+ # DuckDuckGo has no documented limit; stay polite.
57
+ return 20
58
+
59
+ async def search(self, query: str, max_results: int = 5) -> List[SearchResult]:
60
+ params = {"q": query}
61
+ try:
62
+ # UA is deployment-overridable (COLONY_SEARCH_USER_AGENT); the
63
+ # default is a neutral project identifier with no deployment URL.
64
+ _ua = os.environ.get(
65
+ "COLONY_SEARCH_USER_AGENT",
66
+ "Mozilla/5.0 (compatible; ColonyAI research crawler)")
67
+ async with httpx.AsyncClient(
68
+ timeout=self._timeout,
69
+ headers={
70
+ "User-Agent": _ua,
71
+ "Accept": "text/html,application/xhtml+xml",
72
+ },
73
+ follow_redirects=True,
74
+ ) as client:
75
+ resp = await client.post(self._ENDPOINT, data=params)
76
+ resp.raise_for_status()
77
+ body = resp.text
78
+ except Exception as exc:
79
+ logger.warning("DuckDuckGo request failed: %s", exc)
80
+ return []
81
+
82
+ results: List[SearchResult] = []
83
+ for i, m in enumerate(_RESULT_RE.finditer(body)):
84
+ if i >= max_results:
85
+ break
86
+ url = m.group("url")
87
+ # DDG wraps external URLs in a redirect; strip it when present.
88
+ if url.startswith("//duckduckgo.com/l/?uddg="):
89
+ from urllib.parse import parse_qs, unquote, urlparse
90
+ qs = parse_qs(urlparse("https:" + url).query)
91
+ uddg = qs.get("uddg", [""])[0]
92
+ if uddg:
93
+ url = unquote(uddg)
94
+ results.append(
95
+ SearchResult(
96
+ title=_strip_tags(m.group("title")),
97
+ url=url,
98
+ snippet=_strip_tags(m.group("snippet")),
99
+ source=self.name,
100
+ rank=i + 1,
101
+ )
102
+ )
103
+ return results
@@ -0,0 +1,119 @@
1
+ """Search orchestrator — routes queries, handles rate limits, caches results."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+ from typing import Dict, List, Optional
8
+
9
+ from apsimo.research.search.base import SearchProvider, SearchResult
10
+ from apsimo.research.search.cache import SearchCache
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SearchOrchestrator:
16
+ """Routes search queries to providers, handles rate limits and caching.
17
+
18
+ If no providers are configured, returns empty results (graceful degradation).
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ providers: Optional[List[SearchProvider]] = None,
24
+ cache_ttl: int = 3600,
25
+ max_cache_entries: int = 500,
26
+ ):
27
+ self._providers: Dict[str, SearchProvider] = {}
28
+ if providers:
29
+ for p in providers:
30
+ self._providers[p.name] = p
31
+ self._cache = SearchCache(ttl_seconds=cache_ttl, max_entries=max_cache_entries)
32
+ self._rate_tracker: Dict[str, List[float]] = {name: [] for name in self._providers}
33
+ self._default_provider = next(iter(self._providers), None)
34
+
35
+ @property
36
+ def has_providers(self) -> bool:
37
+ return len(self._providers) > 0
38
+
39
+ def add_provider(self, provider: SearchProvider) -> None:
40
+ """Add a search provider."""
41
+ self._providers[provider.name] = provider
42
+ self._rate_tracker[provider.name] = []
43
+ if not self._default_provider:
44
+ self._default_provider = provider
45
+
46
+ async def search(
47
+ self,
48
+ query: str,
49
+ max_results: int = 5,
50
+ provider: str = "",
51
+ ) -> List[SearchResult]:
52
+ """Execute a search query.
53
+
54
+ Args:
55
+ query: Search query string
56
+ max_results: Maximum number of results
57
+ provider: Preferred provider name (empty = auto-select)
58
+
59
+ Returns:
60
+ List of search results, empty if no providers available.
61
+ """
62
+ if not self._providers:
63
+ return []
64
+
65
+ # Check cache
66
+ cached = self._cache.get(query)
67
+ if cached is not None:
68
+ logger.debug("Search cache hit for '%s'", query[:50])
69
+ return cached[:max_results]
70
+
71
+ # Select provider
72
+ prov = self._select_provider(provider)
73
+ if not prov:
74
+ return []
75
+
76
+ # Rate limit check
77
+ if not self._check_rate_limit(prov.name):
78
+ prov = self._fallback_provider(prov.name)
79
+ if not prov:
80
+ logger.warning("All search providers rate-limited")
81
+ return []
82
+
83
+ # Execute search
84
+ try:
85
+ results = await prov.search(query, max_results)
86
+ self._cache.put(query, results)
87
+ logger.info("Search via %s: '%s' → %d results", prov.name, query[:50], len(results))
88
+ return results
89
+ except Exception as e:
90
+ logger.warning("Search via %s failed: %s", prov.name, e)
91
+ return []
92
+
93
+ def _select_provider(self, preferred: str) -> Optional[SearchProvider]:
94
+ if preferred and preferred in self._providers:
95
+ return self._providers[preferred]
96
+ return self._default_provider
97
+
98
+ def _fallback_provider(self, exclude: str) -> Optional[SearchProvider]:
99
+ for name, prov in self._providers.items():
100
+ if name != exclude and self._check_rate_limit(name):
101
+ return prov
102
+ return None
103
+
104
+ def _check_rate_limit(self, name: str) -> bool:
105
+ if name not in self._rate_tracker:
106
+ return False
107
+ now = time.time()
108
+ # Clean up entries older than 60 seconds
109
+ self._rate_tracker[name] = [t for t in self._rate_tracker[name] if now - t < 60]
110
+ prov = self._providers.get(name)
111
+ if not prov:
112
+ return False
113
+ return len(self._rate_tracker[name]) < prov.rate_limit_per_minute
114
+
115
+ def clear_cache(self) -> None:
116
+ self._cache.clear()
117
+
118
+ def list_providers(self) -> List[str]:
119
+ return list(self._providers.keys())
@@ -0,0 +1,59 @@
1
+ """SerpAPI search provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Optional
7
+
8
+ from apsimo.research.search.base import SearchProvider, SearchResult
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ try:
13
+ import httpx
14
+ HAS_HTTPX = True
15
+ except ImportError:
16
+ HAS_HTTPX = False
17
+
18
+
19
+ class SerpAPIProvider(SearchProvider):
20
+ """Google search via SerpAPI."""
21
+
22
+ def __init__(self, api_key: str):
23
+ self._api_key = api_key
24
+ self._base_url = "https://serpapi.com/search"
25
+
26
+ @property
27
+ def name(self) -> str:
28
+ return "serpapi"
29
+
30
+ async def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
31
+ if not HAS_HTTPX:
32
+ logger.warning("httpx not installed — cannot use SerpAPI")
33
+ return []
34
+
35
+ try:
36
+ async with httpx.AsyncClient(timeout=15) as client:
37
+ resp = await client.get(self._base_url, params={
38
+ "q": query,
39
+ "api_key": self._api_key,
40
+ "engine": "google",
41
+ "num": max_results,
42
+ "output": "json",
43
+ })
44
+ resp.raise_for_status()
45
+ data = resp.json()
46
+ results = []
47
+ for i, r in enumerate(data.get("organic_results", [])[:max_results]):
48
+ results.append(SearchResult(
49
+ title=r.get("title", ""),
50
+ url=r.get("link", ""),
51
+ snippet=r.get("snippet", ""),
52
+ content=r.get("snippet"), # SerpAPI doesn't return full content
53
+ source="serpapi",
54
+ rank=i,
55
+ ))
56
+ return results
57
+ except Exception as e:
58
+ logger.warning("SerpAPI search failed: %s", e)
59
+ return []
@@ -0,0 +1,59 @@
1
+ """Tavily search provider — designed for AI agents, returns clean content."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Optional
7
+
8
+ from apsimo.research.search.base import SearchProvider, SearchResult
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ try:
13
+ import httpx
14
+ HAS_HTTPX = True
15
+ except ImportError:
16
+ HAS_HTTPX = False
17
+
18
+
19
+ class TavilyProvider(SearchProvider):
20
+ """Tavily search API — optimized for AI agent use cases."""
21
+
22
+ def __init__(self, api_key: str):
23
+ self._api_key = api_key
24
+ self._base_url = "https://api.tavily.com/search"
25
+
26
+ @property
27
+ def name(self) -> str:
28
+ return "tavily"
29
+
30
+ async def search(self, query: str, max_results: int = 5) -> list[SearchResult]:
31
+ if not HAS_HTTPX:
32
+ logger.warning("httpx not installed — cannot use Tavily")
33
+ return []
34
+
35
+ try:
36
+ async with httpx.AsyncClient(timeout=15) as client:
37
+ resp = await client.post(self._base_url, json={
38
+ "api_key": self._api_key,
39
+ "query": query,
40
+ "max_results": max_results,
41
+ "include_raw_content": True,
42
+ "search_depth": "advanced",
43
+ })
44
+ resp.raise_for_status()
45
+ data = resp.json()
46
+ return [
47
+ SearchResult(
48
+ title=r.get("title", ""),
49
+ url=r.get("url", ""),
50
+ snippet=r.get("content", "")[:500],
51
+ content=r.get("raw_content"),
52
+ source="tavily",
53
+ rank=i,
54
+ )
55
+ for i, r in enumerate(data.get("results", []))
56
+ ]
57
+ except Exception as e:
58
+ logger.warning("Tavily search failed: %s", e)
59
+ return []
@@ -0,0 +1,309 @@
1
+ """Research synthesizer — Stage 3 of the Research-to-Artifact Pipeline.
2
+
3
+ Runs gathered evidence through Colony's intelligence synthesis layer to
4
+ produce a structured ``SynthesisReport`` containing validated, novelty-ranked
5
+ insights and annotated contradictions.
6
+
7
+ Steps:
8
+ 1. Connection discovery — temporal, causal, entity, behavioural patterns
9
+ 2. Cross-domain analysis — groups evidence by domain, extracts insights
10
+ 3. Novelty scoring — rank by formula: 0.4*(1/freq) + 0.3*domain_dist + 0.3*conf_delta
11
+ 4. Quality validation — min evidence count, confidence thresholds, recency
12
+ 5. Contradiction — conflicting evidence annotated with provenance
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from dataclasses import dataclass, field
19
+ from datetime import datetime, timezone
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ from apsimo.research.gatherer import EvidenceItem, EvidencePackage, SourceType
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Synthesis models
29
+ # ---------------------------------------------------------------------------
30
+
31
+
32
+ @dataclass
33
+ class DomainInsight:
34
+ """A validated, novelty-ranked insight derived from evidence."""
35
+
36
+ id: str
37
+ title: str
38
+ summary: str
39
+ domains: List[str]
40
+ supporting_citations: List[str] # Evidence item citations
41
+ confidence: float = 0.0 # 0.0–1.0
42
+ novelty_score: float = 0.0 # 0.0–1.0
43
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
44
+ metadata: Dict[str, Any] = field(default_factory=dict)
45
+
46
+
47
+ @dataclass
48
+ class Contradiction:
49
+ """Two evidence items with conflicting claims."""
50
+
51
+ id: str
52
+ claim_a: str
53
+ claim_b: str
54
+ citation_a: str
55
+ citation_b: str
56
+ topic: str
57
+ confidence_a: float = 0.5
58
+ confidence_b: float = 0.5
59
+
60
+
61
+ @dataclass
62
+ class SynthesisReport:
63
+ """Output of the synthesis stage."""
64
+
65
+ goal_id: str
66
+ insights: List[DomainInsight]
67
+ contradictions: List[Contradiction]
68
+ evidence_count: int
69
+ source_breakdown: Dict[str, int] # SourceType.value -> count
70
+ synthesis_confidence: float # 0.0–1.0, aggregate
71
+ graph_entities_referenced: List[str] # Neo4j node IDs
72
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Synthesis configuration
77
+ # ---------------------------------------------------------------------------
78
+
79
+
80
+ @dataclass
81
+ class SynthesisConfig:
82
+ min_evidence_for_insight: int = 2
83
+ min_confidence: float = 0.55
84
+ min_novelty: float = 0.30
85
+ max_insights: int = 50
86
+ timeout_seconds: float = 90.0
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Synthesizer
91
+ # ---------------------------------------------------------------------------
92
+
93
+
94
+ class ResearchSynthesizer:
95
+ """Synthesize an EvidencePackage into a SynthesisReport.
96
+
97
+ Tries to use Colony's intelligence synthesis layer when available;
98
+ falls back to a lightweight built-in implementation for offline/test use.
99
+ """
100
+
101
+ def __init__(self, config: Optional[SynthesisConfig] = None) -> None:
102
+ self.config = config or SynthesisConfig()
103
+
104
+ async def synthesize(
105
+ self,
106
+ evidence: EvidencePackage,
107
+ goal_id: str,
108
+ goal_text: str = "",
109
+ ) -> SynthesisReport:
110
+ """Run evidence through the synthesis pipeline.
111
+
112
+ Args:
113
+ evidence: List of EvidenceItem objects (already deduplicated).
114
+ goal_id: Unique ID of the parent research run.
115
+ goal_text: Original research goal text (used for context).
116
+
117
+ Returns:
118
+ A ``SynthesisReport`` with ranked insights and contradictions.
119
+ """
120
+ # Filter out flagged items before any LLM exposure
121
+ safe_evidence = [e for e in evidence if e.is_safe()]
122
+ flagged_count = len(evidence) - len(safe_evidence)
123
+ if flagged_count:
124
+ logger.warning(
125
+ "Synthesizer: excluded %d flagged evidence items (pii/injection)",
126
+ flagged_count,
127
+ )
128
+
129
+ source_breakdown: Dict[str, int] = {}
130
+ for item in safe_evidence:
131
+ source_breakdown[item.source_type.value] = (
132
+ source_breakdown.get(item.source_type.value, 0) + 1
133
+ )
134
+
135
+ insights = await self._extract_insights(safe_evidence, goal_text)
136
+ contradictions = self._detect_contradictions(safe_evidence)
137
+
138
+ # Enforce limits
139
+ insights = insights[: self.config.max_insights]
140
+
141
+ aggregate_confidence = (
142
+ sum(i.confidence for i in insights) / len(insights) if insights else 0.0
143
+ )
144
+
145
+ graph_entities: List[str] = []
146
+ for item in safe_evidence:
147
+ if item.source_type == SourceType.GRAPH:
148
+ node_id = item.citation.replace("graph://", "")
149
+ if node_id and node_id not in graph_entities:
150
+ graph_entities.append(node_id)
151
+
152
+ return SynthesisReport(
153
+ goal_id=goal_id,
154
+ insights=insights,
155
+ contradictions=contradictions,
156
+ evidence_count=len(safe_evidence),
157
+ source_breakdown=source_breakdown,
158
+ synthesis_confidence=aggregate_confidence,
159
+ graph_entities_referenced=graph_entities,
160
+ )
161
+
162
+ async def _extract_insights(
163
+ self,
164
+ evidence: EvidencePackage,
165
+ goal_text: str,
166
+ ) -> List[DomainInsight]:
167
+ """Extract and rank insights from evidence."""
168
+ # Attempt to use Colony's CrossDomainAnalyzer
169
+ try:
170
+ return await self._run_colony_synthesis(evidence, goal_text)
171
+ except Exception as exc:
172
+ logger.debug("CrossDomainAnalyzer unavailable (%s), using built-in", exc)
173
+ return self._builtin_extract(evidence, goal_text)
174
+
175
+ async def _run_colony_synthesis(
176
+ self,
177
+ evidence: EvidencePackage,
178
+ goal_text: str,
179
+ ) -> List[DomainInsight]:
180
+ """Run Colony intelligence synthesis layer."""
181
+ from apsimo.intelligence.synthesis.cross_domain_analyzer import CrossDomainAnalyzer
182
+ from apsimo.intelligence.synthesis.novelty_scorer import NoveltyScorer
183
+ from apsimo.intelligence.synthesis.insight_validator import InsightValidator
184
+
185
+ # Build a minimal data structure the analyzer expects
186
+ signals = [
187
+ {
188
+ "domain": item.source_type.value,
189
+ "content": item.content,
190
+ "timestamp": item.retrieved_at.isoformat(),
191
+ "confidence": item.relevance_score,
192
+ "source": item.citation,
193
+ }
194
+ for item in evidence
195
+ ]
196
+
197
+ analyzer = CrossDomainAnalyzer()
198
+ raw_insights = await analyzer.analyze(signals)
199
+
200
+ validator = InsightValidator(
201
+ min_confidence=self.config.min_confidence,
202
+ min_evidence_count=self.config.min_evidence_for_insight,
203
+ )
204
+ scorer = NoveltyScorer()
205
+
206
+ results: List[DomainInsight] = []
207
+ import secrets as _secrets
208
+
209
+ for raw in raw_insights:
210
+ validation = validator.validate(raw)
211
+ if not validation.valid:
212
+ continue
213
+ novelty = scorer.score(raw)
214
+ if novelty.score < self.config.min_novelty:
215
+ continue
216
+ results.append(
217
+ DomainInsight(
218
+ id=_secrets.token_hex(6),
219
+ title=getattr(raw, "title", str(raw)[:80]),
220
+ summary=getattr(raw, "summary", str(raw)),
221
+ domains=getattr(raw, "domains", []),
222
+ supporting_citations=[],
223
+ confidence=getattr(raw, "confidence", 0.6),
224
+ novelty_score=novelty.score,
225
+ )
226
+ )
227
+
228
+ return sorted(results, key=lambda x: x.novelty_score, reverse=True)
229
+
230
+ def _builtin_extract(
231
+ self,
232
+ evidence: EvidencePackage,
233
+ goal_text: str,
234
+ ) -> List[DomainInsight]:
235
+ """Lightweight built-in insight extractor (no external deps)."""
236
+ import secrets as _secrets
237
+
238
+ cfg = self.config
239
+ insights: List[DomainInsight] = []
240
+
241
+ # Group evidence by source type
242
+ by_source: Dict[str, List[EvidenceItem]] = {}
243
+ for item in evidence:
244
+ by_source.setdefault(item.source_type.value, []).append(item)
245
+
246
+ for source_type, items in by_source.items():
247
+ if len(items) < cfg.min_evidence_for_insight:
248
+ continue
249
+ # One insight per source type, summarizing top items
250
+ top_items = sorted(items, key=lambda x: x.relevance_score, reverse=True)[:5]
251
+ combined = " ".join(i.content[:200] for i in top_items)
252
+ avg_confidence = sum(i.relevance_score for i in top_items) / len(top_items)
253
+
254
+ if avg_confidence < cfg.min_confidence:
255
+ continue
256
+
257
+ # Compute a simple novelty score from source diversity
258
+ novelty = min(1.0, 0.3 + 0.1 * len(items))
259
+ if novelty < cfg.min_novelty:
260
+ continue
261
+
262
+ insights.append(
263
+ DomainInsight(
264
+ id=_secrets.token_hex(6),
265
+ title=f"Findings from {source_type} sources",
266
+ summary=combined[:500],
267
+ domains=[source_type],
268
+ supporting_citations=[i.citation for i in top_items],
269
+ confidence=avg_confidence,
270
+ novelty_score=novelty,
271
+ )
272
+ )
273
+
274
+ return sorted(insights, key=lambda x: x.novelty_score, reverse=True)
275
+
276
+ def _detect_contradictions(
277
+ self,
278
+ evidence: EvidencePackage,
279
+ ) -> List[Contradiction]:
280
+ """Detect pairs of evidence items with potentially conflicting claims.
281
+
282
+ Uses a heuristic: items from different sources with similar topics
283
+ but significantly different relevance scores are flagged.
284
+ """
285
+ import secrets as _secrets
286
+
287
+ contradictions: List[Contradiction] = []
288
+ # Simple heuristic — compare web vs graph items on same topic keywords
289
+ web_items = [e for e in evidence if e.source_type == SourceType.WEB]
290
+ graph_items = [e for e in evidence if e.source_type == SourceType.GRAPH]
291
+
292
+ for w in web_items[:5]:
293
+ for g in graph_items[:5]:
294
+ # If relevance scores differ significantly, flag as potential contradiction
295
+ if abs(w.relevance_score - g.relevance_score) > 0.4:
296
+ contradictions.append(
297
+ Contradiction(
298
+ id=_secrets.token_hex(6),
299
+ claim_a=w.content[:200],
300
+ claim_b=g.content[:200],
301
+ citation_a=w.citation,
302
+ citation_b=g.citation,
303
+ topic="relevance discrepancy",
304
+ confidence_a=w.relevance_score,
305
+ confidence_b=g.relevance_score,
306
+ )
307
+ )
308
+
309
+ return contradictions[:10] # Cap at 10 contradictions
@@ -0,0 +1,30 @@
1
+ """Colony LLM Cost Router — intelligent model tier selection.
2
+
3
+ Route LLM requests to the cheapest capable model based on prompt complexity.
4
+ Expected cost savings: 30–40% vs always using the largest model.
5
+
6
+ Usage::
7
+
8
+ from apsimo.router import LLMRouter, ModelTier
9
+
10
+ router = LLMRouter()
11
+ response = await router.complete(messages, context={"task": "summarise"})
12
+ """
13
+
14
+ from apsimo.router.tiers import ModelTier, TierConfig, DEFAULT_TIERS
15
+ from apsimo.router.complexity_scorer import ComplexityScorer, ComplexitySignals
16
+ from apsimo.router.router import LLMRouter, LLMResponse
17
+ from apsimo.router.self_learning import RouterSelfLearner
18
+ from apsimo.router.fallback import FallbackHandler
19
+
20
+ __all__ = [
21
+ "LLMRouter",
22
+ "LLMResponse",
23
+ "ModelTier",
24
+ "TierConfig",
25
+ "DEFAULT_TIERS",
26
+ "ComplexityScorer",
27
+ "ComplexitySignals",
28
+ "RouterSelfLearner",
29
+ "FallbackHandler",
30
+ ]