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,27 @@
1
+ """Mining: escalation detection + training-corpus export.
2
+
3
+ The model-agnostic mining half of a continuous self-improvement loop:
4
+ escalation events (build-agent consultations, heavy-model / cloud-failover
5
+ turns) become skills-memory distillation inputs and golden eval cases, and
6
+ the verbatim turn capture becomes fine-tune-ready JSONL for an external
7
+ training pipeline. Everything stays under COLONY_STATE_DIR.
8
+ """
9
+ from apsimo.mining.corpus import export_corpus
10
+ from apsimo.mining.escalations import EscalationMiner
11
+ from apsimo.mining.models import (
12
+ EscalationRecord,
13
+ MinedTurn,
14
+ corpus_export_enabled,
15
+ mining_mode,
16
+ )
17
+ from apsimo.mining.store import MiningStore
18
+
19
+ __all__ = [
20
+ "EscalationMiner",
21
+ "EscalationRecord",
22
+ "MinedTurn",
23
+ "MiningStore",
24
+ "corpus_export_enabled",
25
+ "export_corpus",
26
+ "mining_mode",
27
+ ]
@@ -0,0 +1,239 @@
1
+ """Training-corpus exporter: turn store -> fine-tune-ready JSONL.
2
+
3
+ Row shape (the DeepSpec / standard fine-tune contract, one JSON object per
4
+ line): {"id": ..., "conversations": [{"role": "user"|"assistant", "content":
5
+ ...}, ...], "meta": {...}}. Conversations always start with a user message
6
+ and alternate user/assistant, so they parse under GeneralParser-style
7
+ tooling without warnings.
8
+
9
+ PII stance: everything stays local. Source rows never leave the sidecar's
10
+ SQLite; the export file is always written under COLONY_STATE_DIR/exports/
11
+ (path traversal is not accepted). Optional redact=true masks credential /
12
+ token shapes via the sidecar's redact machinery. Consumers (fine-tune
13
+ pipelines) read the file from the state dir; nothing is uploaded by this
14
+ code.
15
+
16
+ Quality gate: system/skill-origin turns are excluded using the same
17
+ sanitize/origin machinery the reachout policy uses (is_system_origin +
18
+ sanitize_text), machine markers (e.g. reply-context [[rc ...]] suffixes)
19
+ are stripped, cron/self sessions are excluded by default, and exact
20
+ duplicate exchanges are deduplicated.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import hashlib
25
+ import json
26
+ import logging
27
+ import re
28
+ import time
29
+ from datetime import datetime, timezone
30
+ from pathlib import Path
31
+ from typing import Any, Dict, List, Optional
32
+
33
+ from apsimo.mining.store import MiningStore
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ _RC_MARKER = re.compile(r"\s*\[\[rc [^\]]*\]\]")
38
+
39
+
40
+ def _parse_when(value: Optional[str]) -> Optional[float]:
41
+ """ISO timestamp or relative '<n>d'/'<n>h' -> epoch seconds."""
42
+ if not value:
43
+ return None
44
+ v = value.strip()
45
+ m = re.fullmatch(r"(\d+)([dh])", v)
46
+ if m:
47
+ n, unit = int(m.group(1)), m.group(2)
48
+ return time.time() - n * (86400 if unit == "d" else 3600)
49
+ try:
50
+ return datetime.fromisoformat(v.replace("Z", "+00:00")).timestamp()
51
+ except ValueError:
52
+ raise ValueError(f"unparseable time filter: {value!r}")
53
+
54
+
55
+ def _clean(text: str, redact: bool) -> str:
56
+ out = _RC_MARKER.sub("", text or "").strip()
57
+ try:
58
+ from apsimo.delivery.reachout_policy import sanitize_text
59
+
60
+ out = sanitize_text(out)
61
+ except Exception:
62
+ pass
63
+ if redact:
64
+ try:
65
+ from apsimo.redact import redact_sensitive_text
66
+
67
+ out = redact_sensitive_text(out)
68
+ except Exception:
69
+ pass
70
+ return out.strip()
71
+
72
+
73
+ def _system_origin(text: str) -> bool:
74
+ try:
75
+ from apsimo.delivery.reachout_policy import is_system_origin
76
+
77
+ return is_system_origin(text or "")
78
+ except Exception:
79
+ return False
80
+
81
+
82
+ def export_corpus(
83
+ store: MiningStore,
84
+ *,
85
+ state_dir: Path,
86
+ contact_id: Optional[str] = None,
87
+ channels: Optional[List[str]] = None,
88
+ since: Optional[str] = None,
89
+ until: Optional[str] = None,
90
+ group: str = "turn",
91
+ min_chars: int = 2,
92
+ include_cron: bool = False,
93
+ include_escalations: bool = False,
94
+ dedup: bool = True,
95
+ redact: bool = False,
96
+ limit: int = 100000,
97
+ filename: Optional[str] = None,
98
+ ) -> Dict[str, Any]:
99
+ """Export filtered conversations as JSONL; returns stats + path."""
100
+ import os
101
+
102
+ if group not in ("turn", "session"):
103
+ raise ValueError("group must be 'turn' or 'session'")
104
+ if contact_id is None:
105
+ contact_id = os.environ.get("COLONY_OWNER_CONTACT_ID", "owner")
106
+
107
+ turns = store.list_turns(
108
+ contact_id=contact_id,
109
+ channels=channels,
110
+ since_ts=_parse_when(since),
111
+ until_ts=_parse_when(until),
112
+ limit=limit,
113
+ )
114
+
115
+ skipped = {"quality": 0, "cron": 0, "dedup": 0}
116
+ seen: set = set()
117
+ exchanges: List[Dict[str, Any]] = []
118
+ for t in turns:
119
+ if not include_cron and t.session_id.startswith("cron_"):
120
+ skipped["cron"] += 1
121
+ continue
122
+ user = _clean(t.user_text, redact)
123
+ asst = _clean(t.assistant_text, redact)
124
+ if (
125
+ len(user) < min_chars
126
+ or len(asst) < min_chars
127
+ or _system_origin(t.user_text)
128
+ or _system_origin(t.assistant_text)
129
+ ):
130
+ skipped["quality"] += 1
131
+ continue
132
+ if dedup:
133
+ h = hashlib.sha256(f"{user}\x00{asst}".encode()).hexdigest()
134
+ if h in seen:
135
+ skipped["dedup"] += 1
136
+ continue
137
+ seen.add(h)
138
+ exchanges.append(
139
+ {
140
+ "session_id": t.session_id,
141
+ "user": user,
142
+ "assistant": asst,
143
+ "meta": {
144
+ "channel": t.channel_id,
145
+ "contact": t.contact_id,
146
+ "session": t.session_id,
147
+ "ts": t.ts,
148
+ "model": t.model,
149
+ },
150
+ }
151
+ )
152
+
153
+ rows: List[Dict[str, Any]] = []
154
+ if group == "turn":
155
+ for i, ex in enumerate(exchanges):
156
+ rows.append(
157
+ {
158
+ "id": f"turn-{i}",
159
+ "conversations": [
160
+ {"role": "user", "content": ex["user"]},
161
+ {"role": "assistant", "content": ex["assistant"]},
162
+ ],
163
+ "meta": ex["meta"],
164
+ }
165
+ )
166
+ else:
167
+ by_session: Dict[str, List[Dict[str, Any]]] = {}
168
+ for ex in exchanges:
169
+ by_session.setdefault(ex["session_id"], []).append(ex)
170
+ for i, (sid, exs) in enumerate(by_session.items()):
171
+ conv: List[Dict[str, str]] = []
172
+ for ex in exs:
173
+ conv.append({"role": "user", "content": ex["user"]})
174
+ conv.append({"role": "assistant", "content": ex["assistant"]})
175
+ rows.append(
176
+ {
177
+ "id": f"session-{i}",
178
+ "conversations": conv,
179
+ "meta": {**exs[0]["meta"], "exchanges": len(exs)},
180
+ }
181
+ )
182
+
183
+ escalations_included = 0
184
+ if include_escalations:
185
+ for e in store.list_escalations(limit=1000):
186
+ user = _clean(e.task_context, redact)
187
+ asst = _clean(e.escalated_answer, redact)
188
+ if len(user) < min_chars or len(asst) < min_chars:
189
+ continue
190
+ rows.append(
191
+ {
192
+ "id": f"escalation-{e.id[:8]}",
193
+ "conversations": [
194
+ {"role": "user", "content": user},
195
+ {"role": "assistant", "content": asst},
196
+ ],
197
+ "meta": {
198
+ "kind": e.kind,
199
+ "channel": e.channel_id,
200
+ "matched": e.matched,
201
+ "outcome": e.outcome,
202
+ "model": e.model,
203
+ "ts": e.ts,
204
+ },
205
+ }
206
+ )
207
+ escalations_included += 1
208
+
209
+ exports_dir = Path(state_dir) / "exports"
210
+ exports_dir.mkdir(parents=True, exist_ok=True)
211
+ name = filename or f"corpus-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}.jsonl"
212
+ name = Path(name).name # no traversal: basename only, always under exports/
213
+ out_path = exports_dir / name
214
+ with open(out_path, "w", encoding="utf-8") as f:
215
+ for row in rows:
216
+ f.write(json.dumps(row, ensure_ascii=False) + "\n")
217
+
218
+ stats = {
219
+ "path": str(out_path),
220
+ "rows": len(rows),
221
+ "exchanges": len(exchanges),
222
+ "sessions": len({ex["session_id"] for ex in exchanges}),
223
+ "turns_scanned": len(turns),
224
+ "skipped": skipped,
225
+ "escalations_included": escalations_included,
226
+ "group": group,
227
+ "contact_id": contact_id,
228
+ "redacted": redact,
229
+ }
230
+ try:
231
+ from apsimo.events.journal import append_event
232
+
233
+ append_event("mining.corpus_export", stats)
234
+ except Exception:
235
+ logger.debug("corpus export journal failed", exc_info=True)
236
+ logger.info(
237
+ "corpus export: %d rows (%d sessions) -> %s", len(rows), stats["sessions"], out_path
238
+ )
239
+ return stats
@@ -0,0 +1,289 @@
1
+ """EscalationMiner: detect + bank escalation events from the live turn stream.
2
+
3
+ Two detectors run on every synced turn:
4
+
5
+ 1. CONSULTATION: the agent shelled out to a build/coding agent during the
6
+ turn (a terminal-class tool ran AND the turn text matches
7
+ COLONY_ESCALATION_CONSULT_REGEX). The situation was hard enough that the
8
+ agent escalated to a stronger coding agent: exactly the material worth
9
+ distilling into a skill and worth banking as a golden case.
10
+ 2. PROVIDER ESCALATION: the turn's model (optional per-turn metadata from the
11
+ host) matches COLONY_ESCALATION_HEAVY_RE: a heavy-model or cloud-failover
12
+ turn.
13
+
14
+ Each record banks: task/prompt context, the prior local attempt in the same
15
+ session (when present), the escalated answer, channel, model, ts. Outcome is
16
+ tracked lightweight: the NEXT turn in the same session marks the record
17
+ followed_up with the user's reaction excerpt.
18
+
19
+ Modes (COLONY_ESCALATION_MINING): shadow banks + journals only; live also
20
+ feeds records into skills-memory distillation (domain "escalation"), which
21
+ itself honors COLONY_SKILLS_DISTILL. Everything is best-effort: a miner
22
+ failure must never affect the turn pipeline.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import asyncio
27
+ import logging
28
+ import re
29
+ from typing import Any, Callable, List, Optional
30
+
31
+ from apsimo.mining.models import (
32
+ EscalationRecord,
33
+ MinedTurn,
34
+ consult_regex,
35
+ heavy_model_regex,
36
+ mining_mode,
37
+ turn_cap,
38
+ )
39
+ from apsimo.mining.store import MiningStore
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ _TERMINAL_TOOLS = {"terminal", "shell", "bash", "execute_code", "shell_exec"}
44
+
45
+
46
+ def _session_tool_activity(session_id: str, max_lines: int = 400):
47
+ """Recent host tool-activity for a session: (summaries_text, tool_names).
48
+
49
+ Consultations often show only in the tool COMMANDS (e.g. `claude -p ...`
50
+ run via the terminal tool), not in the reply text. When the host exposes
51
+ its tool-activity stream (COLONY_TOOL_ACTIVITY_FILE, a JSONL of
52
+ {ts, session, tool, summary}), include those summaries in the detection
53
+ text and use the tool names as a terminal-use fallback signal.
54
+ Best-effort: any failure returns ("", empty set).
55
+ """
56
+ import json as _json
57
+ import os as _os
58
+
59
+ path = _os.environ.get("COLONY_TOOL_ACTIVITY_FILE", "")
60
+ if not path or not session_id:
61
+ return "", set()
62
+ try:
63
+ with open(_os.path.expanduser(path), "rb") as f:
64
+ try:
65
+ f.seek(-200_000, 2)
66
+ except OSError:
67
+ f.seek(0)
68
+ lines = f.read().decode("utf-8", "replace").splitlines()[-max_lines:]
69
+ out = []
70
+ tools = set()
71
+ for ln in lines:
72
+ try:
73
+ r = _json.loads(ln)
74
+ except Exception:
75
+ continue
76
+ if r.get("session") == session_id:
77
+ if r.get("tool"):
78
+ tools.add(str(r["tool"]))
79
+ if r.get("summary"):
80
+ out.append(str(r["summary"]))
81
+ return "\n".join(out[-50:]), tools
82
+ except Exception:
83
+ return "", set()
84
+
85
+
86
+ class EscalationMiner:
87
+ def __init__(
88
+ self,
89
+ store: MiningStore,
90
+ *,
91
+ skill_store: Any = None,
92
+ router_getter: Optional[Callable[[], Any]] = None,
93
+ ) -> None:
94
+ self._store = store
95
+ self._skill_store = skill_store
96
+ self._router_getter = router_getter
97
+
98
+ # -- turn intake -----------------------------------------------------------
99
+
100
+ def observe_turn(
101
+ self,
102
+ *,
103
+ session_id: str,
104
+ contact_id: str,
105
+ channel_id: str,
106
+ user_text: str,
107
+ assistant_text: str,
108
+ summary: str = "",
109
+ tools_used: Optional[List[str]] = None,
110
+ model: str = "",
111
+ ) -> Optional[EscalationRecord]:
112
+ """Bank the verbatim turn, close out a pending escalation's outcome,
113
+ and detect a new escalation. Returns the new record, if any."""
114
+ mode = mining_mode()
115
+ if mode == "off":
116
+ return None
117
+ try:
118
+ cap = turn_cap()
119
+ tools = list(tools_used or [])
120
+ prior = self._store.last_turn_in_session(session_id)
121
+
122
+ # Outcome follow-up: the next turn's user message is the reaction
123
+ # to a previously banked escalation in this session.
124
+ try:
125
+ open_esc = self._store.latest_open_escalation(session_id)
126
+ if open_esc is not None and user_text:
127
+ open_esc.outcome = "followed_up"
128
+ open_esc.outcome_note = user_text[:300]
129
+ self._store.update_escalation(open_esc)
130
+ except Exception:
131
+ logger.debug("escalation outcome follow-up failed", exc_info=True)
132
+
133
+ turn = MinedTurn(
134
+ session_id=session_id,
135
+ contact_id=contact_id,
136
+ channel_id=channel_id,
137
+ user_text=(user_text or "")[:cap],
138
+ assistant_text=(assistant_text or "")[:cap],
139
+ summary=(summary or "")[:1000],
140
+ tools_used=tools[:20],
141
+ model=model or "",
142
+ )
143
+ self._store.add_turn(turn)
144
+
145
+ record = self._detect(turn, prior)
146
+ if record is None:
147
+ return None
148
+ self._store.add_escalation(record)
149
+ self._journal(record, mode)
150
+ if mode == "live":
151
+ self._feed_distiller(record)
152
+ return record
153
+ except Exception:
154
+ logger.debug("escalation miner observe_turn failed", exc_info=True)
155
+ return None
156
+
157
+ # -- detection --------------------------------------------------------------
158
+
159
+ def _detect(
160
+ self, turn: MinedTurn, prior: Optional[MinedTurn]
161
+ ) -> Optional[EscalationRecord]:
162
+ text = f"{turn.user_text}\n{turn.assistant_text}\n{turn.summary}"
163
+ tool_text, activity_tools = _session_tool_activity(turn.session_id)
164
+ if tool_text:
165
+ text = f"{text}\n{tool_text}"
166
+
167
+ # (a) build-agent consultation. Terminal-use comes from the synced
168
+ # tools_used when the host sends it, with the host tool-activity
169
+ # stream as the fallback source.
170
+ all_tools = set(turn.tools_used) | activity_tools
171
+ ran_terminal = any(t in _TERMINAL_TOOLS for t in all_tools)
172
+ pattern = consult_regex()
173
+ if ran_terminal and pattern:
174
+ try:
175
+ m = re.search(pattern, text, re.IGNORECASE)
176
+ except re.error:
177
+ m = None
178
+ logger.warning("invalid COLONY_ESCALATION_CONSULT_REGEX; detector idle")
179
+ if m:
180
+ return EscalationRecord(
181
+ kind="consultation",
182
+ session_id=turn.session_id,
183
+ contact_id=turn.contact_id,
184
+ channel_id=turn.channel_id,
185
+ task_context=turn.user_text[:2000],
186
+ local_attempt=(prior.assistant_text[:2000] if prior else ""),
187
+ escalated_answer=turn.assistant_text[:4000],
188
+ model=turn.model,
189
+ matched=m.group(0)[:120],
190
+ )
191
+
192
+ # (b) heavy-model / cloud-failover turn
193
+ heavy = heavy_model_regex()
194
+ if heavy and turn.model:
195
+ try:
196
+ hm = re.search(heavy, turn.model, re.IGNORECASE)
197
+ except re.error:
198
+ hm = None
199
+ logger.warning("invalid COLONY_ESCALATION_HEAVY_RE; detector idle")
200
+ if hm:
201
+ return EscalationRecord(
202
+ kind="provider_escalation",
203
+ session_id=turn.session_id,
204
+ contact_id=turn.contact_id,
205
+ channel_id=turn.channel_id,
206
+ task_context=turn.user_text[:2000],
207
+ local_attempt=(prior.assistant_text[:2000] if prior else ""),
208
+ escalated_answer=turn.assistant_text[:4000],
209
+ model=turn.model,
210
+ matched=hm.group(0)[:120],
211
+ )
212
+ return None
213
+
214
+ # -- consumers ---------------------------------------------------------------
215
+
216
+ def _journal(self, record: EscalationRecord, mode: str) -> None:
217
+ try:
218
+ from apsimo.events.journal import append_event
219
+
220
+ append_event(
221
+ "mining.escalation",
222
+ {
223
+ "escalation_id": record.id,
224
+ "kind": record.kind,
225
+ "channel_id": record.channel_id,
226
+ "session_id": record.session_id,
227
+ "contact_id": record.contact_id,
228
+ "model": record.model,
229
+ "matched": record.matched,
230
+ "mode": mode,
231
+ "summary": record.task_context[:200],
232
+ },
233
+ )
234
+ except Exception:
235
+ logger.debug("mining.escalation journal failed", exc_info=True)
236
+
237
+ def _feed_distiller(self, record: EscalationRecord) -> None:
238
+ """Live mode: escalations are high-value distillation inputs."""
239
+ try:
240
+ from apsimo.skills_memory import (
241
+ distill_from_completion,
242
+ skills_distill_mode,
243
+ )
244
+
245
+ if skills_distill_mode() == "off" or self._skill_store is None:
246
+ return
247
+ router = self._router_getter() if self._router_getter else None
248
+ if router is None:
249
+ return
250
+ result_text = record.escalated_answer
251
+ if record.local_attempt:
252
+ result_text = (
253
+ f"Local attempt (before escalating):\n{record.local_attempt}\n\n"
254
+ f"Escalated resolution:\n{record.escalated_answer}"
255
+ )
256
+
257
+ async def _run() -> None:
258
+ try:
259
+ skill = await distill_from_completion(
260
+ router,
261
+ self._skill_store,
262
+ domain="escalation",
263
+ task_text=record.task_context,
264
+ result_text=result_text,
265
+ source_ref=record.id,
266
+ )
267
+ if skill is not None:
268
+ record.distilled = 1
269
+ self._store.update_escalation(record)
270
+ except Exception:
271
+ logger.debug("escalation distillation failed", exc_info=True)
272
+
273
+ try:
274
+ loop = asyncio.get_running_loop()
275
+ loop.create_task(_run())
276
+ except RuntimeError:
277
+ asyncio.run(_run())
278
+ except Exception:
279
+ logger.debug("escalation distiller feed failed", exc_info=True)
280
+
281
+ # -- reads -------------------------------------------------------------------
282
+
283
+ def recent(self, *, kind: Optional[str] = None, limit: int = 50) -> List[dict]:
284
+ return [e.to_row() for e in self._store.list_escalations(kind=kind, limit=limit)]
285
+
286
+ def stats(self) -> dict:
287
+ s = self._store.escalation_stats()
288
+ s["mode"] = mining_mode()
289
+ return s