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,76 @@
1
+ """TypeFeedbackStore -- crude outcome-driven priority feedback (item 3b / 4).
2
+
3
+ Records how the owner responded to each class of proactive work (proposals /
4
+ reach-outs / initiatives) and turns that into a per-type priority multiplier:
5
+ classes the owner acts on are boosted; classes he dismisses or ignores decay.
6
+ This closes the loop the CPI actuator never did, standalone and simple.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import sqlite3
13
+ import threading
14
+ import time
15
+ from typing import Dict, List, Optional
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Multiplicative nudges per outcome, with clamps so a type never fully dies or
20
+ # runs away.
21
+ _NUDGE = {"actioned": 1.12, "acted": 1.12, "dismissed": 0.85, "ignored": 0.9,
22
+ "acknowledged": 1.0, "snoozed": 0.97}
23
+ _MIN, _MAX = 0.5, 1.5
24
+
25
+
26
+ class TypeFeedbackStore:
27
+ def __init__(self, db_path: Optional[str] = None) -> None:
28
+ self._lock = threading.RLock()
29
+ self._conn = sqlite3.connect(str(db_path) if db_path else ":memory:",
30
+ check_same_thread=False)
31
+ self._conn.row_factory = sqlite3.Row
32
+ with self._lock:
33
+ self._conn.execute(
34
+ """CREATE TABLE IF NOT EXISTS type_feedback (
35
+ itype TEXT PRIMARY KEY, multiplier REAL DEFAULT 1.0,
36
+ actioned INTEGER DEFAULT 0, dismissed INTEGER DEFAULT 0,
37
+ ignored INTEGER DEFAULT 0, other INTEGER DEFAULT 0,
38
+ updated_at REAL
39
+ )""")
40
+ self._conn.commit()
41
+
42
+ def record(self, itype: str, outcome: str) -> float:
43
+ """Record an outcome for an initiative/proposal type; return new multiplier."""
44
+ itype = (itype or "unknown").strip().lower()
45
+ outcome = (outcome or "").strip().lower()
46
+ nudge = _NUDGE.get(outcome, 1.0)
47
+ col = ("actioned" if nudge > 1.0 else
48
+ "dismissed" if outcome == "dismissed" else
49
+ "ignored" if outcome == "ignored" else "other")
50
+ with self._lock:
51
+ row = self._conn.execute(
52
+ "SELECT multiplier FROM type_feedback WHERE itype=?", (itype,)).fetchone()
53
+ mult = float(row["multiplier"]) if row else 1.0
54
+ mult = max(_MIN, min(_MAX, mult * nudge))
55
+ self._conn.execute(
56
+ f"""INSERT INTO type_feedback (itype, multiplier, {col}, updated_at)
57
+ VALUES (?, ?, 1, ?)
58
+ ON CONFLICT(itype) DO UPDATE SET
59
+ multiplier=?, {col}={col}+1, updated_at=?""",
60
+ (itype, mult, time.time(), mult, time.time()))
61
+ self._conn.commit()
62
+ logger.info("feedback[%s] outcome=%s -> multiplier=%.3f", itype, outcome, mult)
63
+ return mult
64
+
65
+ def multiplier(self, itype: str) -> float:
66
+ itype = (itype or "unknown").strip().lower()
67
+ with self._lock:
68
+ row = self._conn.execute(
69
+ "SELECT multiplier FROM type_feedback WHERE itype=?", (itype,)).fetchone()
70
+ return float(row["multiplier"]) if row else 1.0
71
+
72
+ def snapshot(self) -> List[Dict]:
73
+ with self._lock:
74
+ rows = self._conn.execute(
75
+ "SELECT * FROM type_feedback ORDER BY updated_at DESC").fetchall()
76
+ return [dict(r) for r in rows]
@@ -0,0 +1,19 @@
1
+ """Colony Feeds — spec-driven intelligence feed pipelines.
2
+
3
+ A *feed* is a reproducible pipeline instantiated from a single YAML/JSON spec:
4
+
5
+ collect -> scriptable source adapters (X, GitHub, Hugging Face, Discourse
6
+ forums, arXiv, RSS) score + dedup items into a queue
7
+ distill -> an agent turns the queue into an actionable brief and delivers
8
+ it to the feed's destination (group chat, DM, or file archive)
9
+ digest -> optional daily rollup
10
+ alerts -> optional top-priority items pushed between briefs
11
+ discovery-> optional agent run that researches and proposes new sources
12
+
13
+ Instances are created/managed with ``colony feeds`` (see cli.py) or
14
+ conversationally through the ``feeds-manage`` harness plugin. All state is
15
+ namespaced per instance under the harness data dir so any number of feeds on
16
+ any topics can coexist.
17
+ """
18
+
19
+ from .spec import FeedSpec, SpecError # noqa: F401
apsimo/feeds/cli.py ADDED
@@ -0,0 +1,84 @@
1
+ """``colony feeds`` — manage spec-driven feed instances.
2
+
3
+ Also runnable without an installed package (any python3 with PyYAML):
4
+ PYTHONPATH=<repo>/sidecar python3 -m apsimo.feeds.cli <cmd> ...
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import sys
12
+
13
+ from . import manager
14
+ from .spec import FeedSpec, SpecError
15
+
16
+
17
+ def build_parser(prog: str = "colony feeds") -> argparse.ArgumentParser:
18
+ p = argparse.ArgumentParser(prog=prog, description="Spec-driven intelligence feeds")
19
+ sub = p.add_subparsers(dest="cmd", required=True)
20
+
21
+ c = sub.add_parser("create", help="Instantiate a feed from a spec file")
22
+ c.add_argument("spec", help="Path to the feed spec (.yaml or .json)")
23
+ c.add_argument("--dry-run", action="store_true",
24
+ help="Validate and print rendered prompts without creating anything")
25
+
26
+ v = sub.add_parser("validate", help="Validate a spec file and exit")
27
+ v.add_argument("spec")
28
+
29
+ sub.add_parser("list", help="List feed instances")
30
+
31
+ for name, hlp in (("status", "Show one instance's queue/brief/job state"),
32
+ ("pause", "Pause all of an instance's jobs"),
33
+ ("resume", "Resume all of an instance's jobs")):
34
+ s = sub.add_parser(name, help=hlp)
35
+ s.add_argument("name")
36
+
37
+ r = sub.add_parser("run", help="Run one stage of an instance now")
38
+ r.add_argument("name")
39
+ r.add_argument("stage", choices=["collect", "distill", "digest", "alert", "discovery"])
40
+
41
+ d = sub.add_parser("delete", help="Remove an instance's jobs and shims")
42
+ d.add_argument("name")
43
+ d.add_argument("--purge", action="store_true", help="Also delete the instance data dir")
44
+ return p
45
+
46
+
47
+ def main(argv: list[str] | None = None, prog: str = "colony feeds") -> None:
48
+ args = build_parser(prog).parse_args(argv)
49
+ try:
50
+ if args.cmd == "validate":
51
+ spec = FeedSpec.load(args.spec)
52
+ print(f"OK: {spec.name} ({spec.title})")
53
+ elif args.cmd == "create":
54
+ result = manager.create(args.spec, dry_run=args.dry_run)
55
+ if args.dry_run:
56
+ for stage, prompt in result["prompts"].items():
57
+ print(f"===== {stage} prompt =====\n{prompt}\n")
58
+ print(f"dry-run OK: {result['name']} -> {result['root']}")
59
+ else:
60
+ print(json.dumps(result, indent=2))
61
+ elif args.cmd == "list":
62
+ for inst in manager.list_instances():
63
+ state = "paused" if inst.get("paused") else "active"
64
+ print(f"{inst['name']:24} {state:7} jobs={','.join(inst['jobs'])}")
65
+ elif args.cmd == "status":
66
+ print(json.dumps(manager.status(args.name), indent=2))
67
+ elif args.cmd == "pause":
68
+ manager.pause(args.name)
69
+ print(f"paused {args.name}")
70
+ elif args.cmd == "resume":
71
+ manager.resume(args.name)
72
+ print(f"resumed {args.name}")
73
+ elif args.cmd == "run":
74
+ manager.run(args.name, args.stage)
75
+ elif args.cmd == "delete":
76
+ manager.delete(args.name, purge=args.purge)
77
+ print(f"deleted {args.name}")
78
+ except SpecError as e:
79
+ print(str(e), file=sys.stderr)
80
+ sys.exit(2)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main(prog="python3 -m apsimo.feeds.cli")
apsimo/feeds/engine.py ADDED
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env python3
2
+ """Colony feed collector engine — self-contained, stdlib-only.
3
+
4
+ This file is BOTH a package module and a deployable script: the feeds manager
5
+ copies it verbatim into the harness scripts dir (as ``colony-feed-engine.py``)
6
+ where per-instance shims execute it under any python3. Keep it stdlib-only.
7
+
8
+ Stages (selected via $COLONY_FEED_STAGE, default ``collect``):
9
+ collect -> run every configured source adapter, score + dedup new items,
10
+ overwrite the instance queue with never-seen items, write the
11
+ alert file when top-priority items appear
12
+ alert -> print pending alert text (empty stdout = silent), optionally
13
+ piping it through the instance send_command
14
+
15
+ Config file (path in $COLONY_FEED_CONFIG) is either the spec-rendered shape
16
+ produced by ``FeedSpec.engine_config()`` or a legacy hand-written shape
17
+ (detected by the absence of ``feed_name``) kept for pre-framework installs.
18
+ """
19
+
20
+ import hashlib
21
+ import json
22
+ import os
23
+ import re
24
+ import sqlite3
25
+ import subprocess
26
+ import sys
27
+ import time
28
+ import urllib.parse
29
+ import urllib.request
30
+ from datetime import datetime, timezone
31
+
32
+ UA = "Colony-Feed-Collector/1.0"
33
+
34
+
35
+ # --------------------------------------------------------------------- config
36
+ def load_config():
37
+ path = os.environ.get("COLONY_FEED_CONFIG")
38
+ if not path:
39
+ raise SystemExit("COLONY_FEED_CONFIG not set")
40
+ with open(os.path.expanduser(path), encoding="utf-8") as f:
41
+ cfg = json.load(f)
42
+ if "feed_name" not in cfg:
43
+ cfg = _normalize_legacy(cfg)
44
+ cfg.setdefault("benchmark_regex", r"\d+\s*(tok/s|tokens?/?s|t/s|tflops?|ms|latency|throughput)")
45
+ return cfg
46
+
47
+
48
+ def _normalize_legacy(cfg):
49
+ """Map the pre-framework single-feed config shape onto the engine shape."""
50
+ home = os.path.expanduser("~")
51
+ sc = cfg.get("scoring", {})
52
+ cats = []
53
+ for key, points_key in (("hardware_keywords", "hardware_match"),
54
+ ("model_keywords", "model_match"),
55
+ ("inference_keywords", "inference_technique")):
56
+ kws = cfg.get(key) or []
57
+ if kws:
58
+ cats.append({"keywords": kws, "points": sc.get(points_key, 10)})
59
+ # boosts that were hardcoded in the original collector
60
+ if sc.get("agent_infra"):
61
+ cats.append({"keywords": ["agent", "harness", "router", "orchestrat", "mcp",
62
+ "tool use", "sub-agent"],
63
+ "points": sc["agent_infra"]})
64
+ if sc.get("training_relevant"):
65
+ cats.append({"keywords": ["training", "fine-tune", "fine tune", "lora", "qlora",
66
+ "distributed", "fsdp", "deepspeed"],
67
+ "points": sc["training_relevant"]})
68
+ return {
69
+ "feed_name": cfg.get("name", "legacy"),
70
+ "queue_path": cfg.get("queue_path", f"{home}/.hermes/data/feed_queue.json"),
71
+ "seen_db": cfg.get("seen_db", f"{home}/.hermes/data/feed_seen.db"),
72
+ "alert_path": cfg.get("alert_path", f"{home}/.hermes/data/p0_alert.json"),
73
+ "x_searches": cfg.get("x_searches", []),
74
+ "x_results_per_query": 15,
75
+ "key_accounts": cfg.get("key_accounts", []),
76
+ "hf_keywords": cfg.get("hf_keywords", []),
77
+ "github_search": cfg.get("github_search",
78
+ "inference OR vllm OR sglang OR speculative OR llm serving"),
79
+ "github_keywords": cfg.get("github_keywords", []),
80
+ "forum_urls": cfg.get("forum_urls", []),
81
+ "forum_title_keywords": cfg.get("forum_title_keywords", []),
82
+ "arxiv_categories": cfg.get("arxiv_categories", []),
83
+ "arxiv_query_terms": cfg.get("arxiv_query_terms",
84
+ ["speculative", "inference", "quantization", "serving",
85
+ "efficient", "throughput", "KV cache", "mixture of experts"]),
86
+ "arxiv_keywords": cfg.get("arxiv_keywords", []),
87
+ "rss": cfg.get("rss", []),
88
+ "keyword_categories": cats,
89
+ "scoring": {
90
+ "account_whitelist": sc.get("account_whitelist", 10),
91
+ "engagement_per_100_likes": sc.get("engagement_per_100_likes", 1),
92
+ "engagement_per_1000_likes": sc.get("engagement_per_1000_likes", 2),
93
+ "engagement_max": sc.get("engagement_max", 10),
94
+ "human_curated": sc.get("human_curated", 20),
95
+ "benchmark_numbers": sc.get("benchmark_numbers", 15),
96
+ "code_release": sc.get("code_release", 10),
97
+ "open_weights": sc.get("open_weights", 10),
98
+ },
99
+ "priority_thresholds": cfg.get("priority_thresholds", {"p0": 50, "p1": 35, "p2": 20}),
100
+ "send_command": cfg.get("send_command", ""),
101
+ "alert_min_priority": cfg.get("alert_min_priority", "P0"),
102
+ }
103
+
104
+
105
+ # ----------------------------------------------------------------- seen store
106
+ def get_db(cfg):
107
+ path = os.path.expanduser(cfg["seen_db"])
108
+ os.makedirs(os.path.dirname(path), exist_ok=True)
109
+ conn = sqlite3.connect(path)
110
+ conn.execute("PRAGMA journal_mode=WAL")
111
+ conn.execute("""CREATE TABLE IF NOT EXISTS seen_items (
112
+ id TEXT PRIMARY KEY, source TEXT, url TEXT, score INTEGER,
113
+ priority TEXT, raw_json TEXT, processed_at TEXT)""")
114
+ return conn
115
+
116
+
117
+ def is_seen(conn, item_id):
118
+ return conn.execute("SELECT 1 FROM seen_items WHERE id=?", (item_id,)).fetchone() is not None
119
+
120
+
121
+ def mark_seen(conn, item_id, source, url, score, priority, raw=""):
122
+ conn.execute(
123
+ "INSERT OR REPLACE INTO seen_items (id, source, url, score, priority, raw_json, processed_at)"
124
+ " VALUES (?,?,?,?,?,?,datetime('now'))",
125
+ (item_id, source, url, score, priority, json.dumps(raw)[:5000]))
126
+ conn.commit()
127
+
128
+
129
+ # -------------------------------------------------------------------- scoring
130
+ def score_item(text, cfg, account=None, likes=0, has_code=False,
131
+ has_weights=False, human_curated=False, has_benchmarks=False):
132
+ sc = cfg["scoring"]
133
+ tl = text.lower()
134
+ score = 0
135
+ for cat in cfg.get("keyword_categories", []):
136
+ if any(kw.lower() in tl for kw in cat.get("keywords", [])):
137
+ score += cat.get("points", 10)
138
+ if account and account.lower() in [a.lower() for a in cfg.get("key_accounts", [])]:
139
+ score += sc["account_whitelist"]
140
+ if likes > 0:
141
+ score += min(int(likes / 100) * sc["engagement_per_100_likes"]
142
+ + int(likes / 1000) * sc["engagement_per_1000_likes"],
143
+ sc["engagement_max"])
144
+ if human_curated:
145
+ score += sc["human_curated"]
146
+ if has_benchmarks or re.search(cfg["benchmark_regex"], tl):
147
+ score += sc["benchmark_numbers"]
148
+ if has_code:
149
+ score += sc["code_release"]
150
+ if has_weights or "open weights" in tl or ("weights" in tl and "release" in tl):
151
+ score += sc["open_weights"]
152
+ th = cfg["priority_thresholds"]
153
+ for pri in ("p0", "p1", "p2"):
154
+ if score >= th.get(pri, 10**9):
155
+ return score, pri.upper()
156
+ return score, "P3"
157
+
158
+
159
+ def _fetch_json(url, timeout=15, headers=None):
160
+ req = urllib.request.Request(url)
161
+ req.add_header("User-Agent", UA)
162
+ for k, v in (headers or {}).items():
163
+ req.add_header(k, v)
164
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
165
+ return json.loads(resp.read().decode())
166
+
167
+
168
+ def _fetch_text(url, timeout=20):
169
+ req = urllib.request.Request(url)
170
+ req.add_header("User-Agent", UA)
171
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
172
+ return resp.read().decode(errors="replace")
173
+
174
+
175
+ # ------------------------------------------------------------------- adapters
176
+ def collect_x(cfg, conn):
177
+ items = []
178
+ for query in cfg.get("x_searches", []):
179
+ try:
180
+ result = subprocess.run(["xurl", "search", query, "-n",
181
+ str(cfg.get("x_results_per_query", 15))],
182
+ capture_output=True, text=True, timeout=30)
183
+ if result.returncode != 0:
184
+ continue
185
+ data = json.loads(result.stdout)
186
+ users = {u.get("id"): u.get("username", "")
187
+ for u in data.get("includes", {}).get("users", [])}
188
+ for tweet in data.get("data", []):
189
+ tid = tweet.get("id", "")
190
+ if not tid or is_seen(conn, f"x:{tid}"):
191
+ continue
192
+ text = tweet.get("text", "")
193
+ author = users.get(tweet.get("author_id"), "")
194
+ likes = tweet.get("public_metrics", {}).get("like_count", 0)
195
+ has_code = "github.com" in text or "huggingface.co" in text
196
+ sc, pri = score_item(text, cfg, account=author, likes=likes, has_code=has_code)
197
+ url = (f"https://x.com/{author}/status/{tid}" if author
198
+ else f"https://x.com/i/web/status/{tid}")
199
+ mark_seen(conn, f"x:{tid}", "x", url, sc, pri,
200
+ {"text": text[:500], "author": author})
201
+ items.append({"source": "x", "id": tid, "author": author,
202
+ "text": text[:500], "url": url, "score": sc,
203
+ "priority": pri, "likes": likes,
204
+ "created_at": tweet.get("created_at", "")})
205
+ except Exception as e:
206
+ print(f"X error for '{query[:40]}': {e}", file=sys.stderr)
207
+ time.sleep(1)
208
+ return items
209
+
210
+
211
+ def collect_hf(cfg, conn):
212
+ kws = cfg.get("hf_keywords", [])
213
+ if not kws:
214
+ return []
215
+ items = []
216
+ try:
217
+ models = _fetch_json("https://huggingface.co/api/models?sort=trending&limit=30")
218
+ for model in models[:30]:
219
+ mid = model.get("id", "")
220
+ if not mid or is_seen(conn, f"hf:{mid}"):
221
+ continue
222
+ text = (mid + " " + " ".join(model.get("tags", []))).lower()
223
+ if not any(kw.lower() in text for kw in kws):
224
+ continue
225
+ sc, pri = score_item(text, cfg, has_weights=True, has_code=True)
226
+ url = f"https://huggingface.co/{mid}"
227
+ mark_seen(conn, f"hf:{mid}", "huggingface", url, sc, pri, {"name": mid})
228
+ items.append({"source": "huggingface", "id": mid, "url": url,
229
+ "text": f"Model: {mid} | Tags: {', '.join(model.get('tags', [])[:5])}"
230
+ f" | Downloads: {model.get('downloads', 0)}",
231
+ "score": sc, "priority": pri})
232
+ except Exception as e:
233
+ print(f"HF error: {e}", file=sys.stderr)
234
+ return items
235
+
236
+
237
+ def collect_github(cfg, conn):
238
+ query = cfg.get("github_search", "")
239
+ if not query:
240
+ return []
241
+ items = []
242
+ try:
243
+ q = urllib.parse.quote(query)
244
+ data = _fetch_json("https://api.github.com/search/repositories"
245
+ f"?q={q}&sort=updated&order=desc&per_page=15",
246
+ headers={"Accept": "application/vnd.github.v3+json"})
247
+ kws = cfg.get("github_keywords", [])
248
+ for repo in data.get("items", []):
249
+ full = repo.get("full_name", "")
250
+ key = f"gh:{full}:{repo.get('updated_at', '')}"
251
+ if not full or is_seen(conn, key):
252
+ continue
253
+ text = (full + " " + (repo.get("description") or "")).lower()
254
+ if kws and not any(kw.lower() in text for kw in kws):
255
+ continue
256
+ stars = repo.get("stargazers_count", 0)
257
+ sc, pri = score_item(text, cfg, has_code=True, likes=min(stars, 10000))
258
+ url = repo.get("html_url", "")
259
+ mark_seen(conn, key, "github", url, sc, pri, {"name": full})
260
+ items.append({"source": "github", "id": full, "url": url,
261
+ "text": f"Repo: {full} | {repo.get('description', '')} | Stars: {stars}",
262
+ "score": sc, "priority": pri, "stars": stars})
263
+ except Exception as e:
264
+ print(f"GitHub error: {e}", file=sys.stderr)
265
+ return items
266
+
267
+
268
+ def collect_forums(cfg, conn):
269
+ items = []
270
+ title_kws = [k.lower() for k in cfg.get("forum_title_keywords", [])]
271
+ for forum_url in cfg.get("forum_urls", []):
272
+ try:
273
+ data = _fetch_json(forum_url.rstrip("/") + ".json")
274
+ base = "/".join(forum_url.split("/")[:3])
275
+ for topic in data.get("topic_list", {}).get("topics", [])[:20]:
276
+ tid = str(topic.get("id", ""))
277
+ title = topic.get("title", "")
278
+ if not tid or is_seen(conn, f"forum:{tid}"):
279
+ continue
280
+ if title_kws and not any(kw in title.lower() for kw in title_kws):
281
+ continue
282
+ views = topic.get("views", 0)
283
+ sc, pri = score_item(title, cfg, has_code=True, likes=views // 100)
284
+ url = f"{base}/t/{topic.get('slug', '')}/{tid}"
285
+ mark_seen(conn, f"forum:{tid}", "forum", url, sc, pri, {"title": title})
286
+ items.append({"source": "forum", "id": tid, "text": title, "url": url,
287
+ "score": sc, "priority": pri, "views": views,
288
+ "replies": topic.get("posts_count", 1) - 1})
289
+ except Exception as e:
290
+ print(f"Forum error for {forum_url}: {e}", file=sys.stderr)
291
+ return items
292
+
293
+
294
+ def collect_arxiv(cfg, conn):
295
+ kws = [k.lower() for k in cfg.get("arxiv_keywords", [])]
296
+ terms = cfg.get("arxiv_query_terms", [])
297
+ items = []
298
+ for cat in cfg.get("arxiv_categories", []):
299
+ try:
300
+ abs_q = "+OR+".join(f"abs:{urllib.parse.quote(t)}" for t in terms) or "abs:survey"
301
+ xml = _fetch_text("http://export.arxiv.org/api/query?search_query="
302
+ f"cat:{cat}+AND+({abs_q})"
303
+ "&start=0&max_results=15&sortBy=submittedDate&sortOrder=descending")
304
+ for entry in re.findall(r"<entry>(.*?)</entry>", xml, re.DOTALL):
305
+ m_id = re.search(r"<id>(.*?)</id>", entry)
306
+ m_title = re.search(r"<title>(.*?)</title>", entry, re.DOTALL)
307
+ if not m_id or not m_title:
308
+ continue
309
+ arxiv_id = m_id.group(1).split("/")[-1]
310
+ if is_seen(conn, f"arxiv:{arxiv_id}"):
311
+ continue
312
+ title = m_title.group(1).strip().replace("\n", " ")
313
+ m_sum = re.search(r"<summary>(.*?)</summary>", entry, re.DOTALL)
314
+ summary = m_sum.group(1).strip()[:500] if m_sum else ""
315
+ text = (title + " " + summary).lower()
316
+ if kws and not any(kw in text for kw in kws):
317
+ continue
318
+ sc, pri = score_item(title + " " + summary, cfg, has_code=True)
319
+ url = f"https://arxiv.org/abs/{arxiv_id}"
320
+ mark_seen(conn, f"arxiv:{arxiv_id}", "arxiv", url, sc, pri, {"title": title})
321
+ items.append({"source": "arxiv", "id": arxiv_id, "text": title,
322
+ "summary": summary[:300], "url": url,
323
+ "score": sc, "priority": pri})
324
+ except Exception as e:
325
+ print(f"arXiv error for {cat}: {e}", file=sys.stderr)
326
+ time.sleep(3) # arXiv rate limit: 1 req / 3s
327
+ return items
328
+
329
+
330
+ def collect_rss(cfg, conn):
331
+ items = []
332
+ for feed in cfg.get("rss", []):
333
+ name, url = feed.get("name", "rss"), feed.get("url", "")
334
+ if not url:
335
+ continue
336
+ try:
337
+ xml = _fetch_text(url)
338
+ entries = (re.findall(r"<item>(.*?)</item>", xml, re.DOTALL)
339
+ or re.findall(r"<entry>(.*?)</entry>", xml, re.DOTALL))
340
+ for entry in entries[:20]:
341
+ m_title = re.search(r"<title[^>]*>(.*?)</title>", entry, re.DOTALL)
342
+ m_link = (re.search(r"<link[^>]*href=\"(.*?)\"", entry)
343
+ or re.search(r"<link[^>]*>(.*?)</link>", entry, re.DOTALL))
344
+ if not m_title:
345
+ continue
346
+ title = re.sub(r"<!\[CDATA\[|\]\]>", "", m_title.group(1)).strip()
347
+ link = (m_link.group(1).strip() if m_link else url)
348
+ key = f"rss:{hashlib.md5((name + title).encode()).hexdigest()[:16]}"
349
+ if is_seen(conn, key):
350
+ continue
351
+ sc, pri = score_item(title, cfg)
352
+ mark_seen(conn, key, f"rss:{name}", link, sc, pri, {"title": title})
353
+ items.append({"source": f"rss:{name}", "id": key, "text": title,
354
+ "url": link, "score": sc, "priority": pri})
355
+ except Exception as e:
356
+ print(f"RSS error for {name}: {e}", file=sys.stderr)
357
+ return items
358
+
359
+
360
+ # --------------------------------------------------------------------- stages
361
+ ADAPTERS = [("x", collect_x), ("huggingface", collect_hf), ("github", collect_github),
362
+ ("forum", collect_forums), ("arxiv", collect_arxiv), ("rss", collect_rss)]
363
+
364
+
365
+ def stage_collect(cfg):
366
+ conn = get_db(cfg)
367
+ all_items = []
368
+ for label, fn in ADAPTERS:
369
+ got = fn(cfg, conn)
370
+ all_items.extend(got)
371
+ print(f" {label}: {len(got)} new items", file=sys.stderr)
372
+ all_items.sort(key=lambda x: x["score"], reverse=True)
373
+
374
+ now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %I:%M %p %Z")
375
+ queue_path = os.path.expanduser(cfg["queue_path"])
376
+ os.makedirs(os.path.dirname(queue_path), exist_ok=True)
377
+ with open(queue_path, "w", encoding="utf-8") as f:
378
+ json.dump({"feed": cfg["feed_name"], "collected_at": now,
379
+ "total_items": len(all_items), "items": all_items}, f, indent=2)
380
+
381
+ min_pri = cfg.get("alert_min_priority", "P0")
382
+ alerts = [i for i in all_items if i["priority"] <= min_pri] # 'P0' < 'P1'
383
+ if alerts:
384
+ with open(os.path.expanduser(cfg["alert_path"]), "w", encoding="utf-8") as f:
385
+ json.dump({"alerts": alerts, "generated_at": now, "delivered": False}, f, indent=2)
386
+
387
+ conn.close()
388
+ by_pri = {}
389
+ for i in all_items:
390
+ by_pri[i["priority"]] = by_pri.get(i["priority"], 0) + 1
391
+ print(json.dumps({"feed": cfg["feed_name"], "collected_at": now,
392
+ "total": len(all_items), "by_priority": by_pri,
393
+ "top_5": [{"text": i["text"][:100], "priority": i["priority"],
394
+ "url": i["url"]} for i in all_items[:5]]}, indent=2))
395
+
396
+
397
+ def stage_alert(cfg):
398
+ """Emit pending alerts once. Empty stdout = silent (no-agent cron pattern)."""
399
+ path = os.path.expanduser(cfg["alert_path"])
400
+ if not os.path.exists(path):
401
+ return
402
+ with open(path, encoding="utf-8") as f:
403
+ data = json.load(f)
404
+ if data.get("delivered") or not data.get("alerts"):
405
+ return
406
+ lines = [f"[{cfg['feed_name']}] {len(data['alerts'])} top-priority item(s):"]
407
+ for a in data["alerts"][:10]:
408
+ lines.append(f"- {a['text'][:160]}\n {a['url']}")
409
+ text = "\n".join(lines)
410
+ send_cmd = cfg.get("send_command", "")
411
+ if send_cmd:
412
+ try:
413
+ subprocess.run(send_cmd, shell=True, input=text.encode(), timeout=60, check=True)
414
+ print(f"[{cfg['feed_name']}] alert posted ({len(data['alerts'])} items)")
415
+ except Exception as e:
416
+ print(f"[{cfg['feed_name']}] alert post FAILED: {e}")
417
+ return # keep undelivered so the next tick retries
418
+ else:
419
+ print(text)
420
+ data["delivered"] = True
421
+ with open(path, "w", encoding="utf-8") as f:
422
+ json.dump(data, f, indent=2)
423
+
424
+
425
+ def main():
426
+ cfg = load_config()
427
+ stage = os.environ.get("COLONY_FEED_STAGE", "collect")
428
+ if stage == "collect":
429
+ stage_collect(cfg)
430
+ elif stage == "alert":
431
+ stage_alert(cfg)
432
+ else:
433
+ raise SystemExit(f"unknown stage: {stage}")
434
+
435
+
436
+ if __name__ == "__main__":
437
+ main()