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,245 @@
1
+ """ContinuousLearner — fuse live signals into intelligence component weights.
2
+
3
+ Unlike MetaLearner (which operates on historical windows), this component
4
+ processes signals in near-real-time and updates component weights without
5
+ waiting for a training window to close.
6
+
7
+ Signal sources:
8
+ - User corrections (explicit: "that's wrong", "don't do that again")
9
+ - Implicit feedback (task re-done immediately → low quality)
10
+ - Engagement signals (briefing sections read vs skipped)
11
+ - Goal success/failure outcomes
12
+
13
+ Weight update rule: exponential moving average with per-signal-type learning
14
+ rates. Weights are clamped to [0.1, 2.0] so no component is permanently
15
+ silenced or runs unrestrained.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from dataclasses import dataclass, field
22
+ from datetime import datetime, timezone
23
+ from typing import Dict, List, Optional
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Signal types
29
+ # ---------------------------------------------------------------------------
30
+
31
+ @dataclass
32
+ class BriefingEngagement:
33
+ """Engagement data from a delivered briefing."""
34
+
35
+ briefing_id: str
36
+ section: str # e.g. "goals", "relationships", "insights"
37
+ read: bool # True = read, False = skipped
38
+ dwell_seconds: float = 0.0
39
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
40
+
41
+
42
+ @dataclass
43
+ class GoalOutcome:
44
+ """Outcome of a goal managed by Colony."""
45
+
46
+ goal_id: str
47
+ success: bool
48
+ component: str # Which component's output influenced this goal
49
+ confidence_at_time: float = 1.0
50
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Learning rates by signal type
55
+ # ---------------------------------------------------------------------------
56
+
57
+ _LEARNING_RATES: Dict[str, float] = {
58
+ # correction_type → lr (how much a single correction moves the weight)
59
+ "factual": 0.10,
60
+ "tone": 0.05,
61
+ "action": 0.08,
62
+ "preference": 0.06,
63
+ # engagement
64
+ "engagement_read": 0.02,
65
+ "engagement_skip": 0.03,
66
+ # outcomes
67
+ "outcome_success": 0.04,
68
+ "outcome_failure": 0.06,
69
+ }
70
+
71
+ _DEFAULT_WEIGHT = 1.0
72
+ _WEIGHT_MIN = 0.1
73
+ _WEIGHT_MAX = 2.0
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Main class
78
+ # ---------------------------------------------------------------------------
79
+
80
+ class ContinuousLearner:
81
+ """Fuse live signals into intelligence component weights.
82
+
83
+ Weights are maintained in-memory and optionally persisted to a graph
84
+ if a ``graph_client`` is provided.
85
+
86
+ Args:
87
+ graph_client: Optional ColonyGraph instance for weight persistence.
88
+ initial_weights: Optional seed weights (component_name → float).
89
+ """
90
+
91
+ def __init__(
92
+ self,
93
+ graph_client: Optional[object] = None,
94
+ initial_weights: Optional[Dict[str, float]] = None,
95
+ ) -> None:
96
+ self._graph = graph_client
97
+ # Component weights: maps component name to a multiplier [0.1, 2.0]
98
+ self._weights: Dict[str, float] = dict(initial_weights or {})
99
+ self._correction_count = 0
100
+ self._engagement_count = 0
101
+ self._outcome_count = 0
102
+
103
+ # ------------------------------------------------------------------
104
+ # Signal ingestion
105
+ # ------------------------------------------------------------------
106
+
107
+ async def ingest_correction(self, correction: "UserCorrection") -> None: # noqa: F821
108
+ """Update component weights based on an explicit user correction.
109
+
110
+ The component inferred from correction_type has its weight reduced
111
+ (we were wrong); complementary components may be boosted slightly.
112
+ """
113
+ lr = _LEARNING_RATES.get(correction.correction_type, 0.05)
114
+ component = self._correction_type_to_component(correction.correction_type)
115
+
116
+ # Penalise the component that produced the wrong output
117
+ self._adjust_weight(component, -lr)
118
+
119
+ # Slight boost to components that weren't responsible
120
+ if correction.correction_type == "factual":
121
+ self._adjust_weight("retrieval", -lr * 0.5)
122
+ elif correction.correction_type == "action":
123
+ self._adjust_weight("task_planner", -lr * 0.5)
124
+
125
+ self._correction_count += 1
126
+ logger.debug(
127
+ "ingest_correction: type=%s component=%s lr=%.3f new_weight=%.3f",
128
+ correction.correction_type,
129
+ component,
130
+ lr,
131
+ self._weights.get(component, _DEFAULT_WEIGHT),
132
+ )
133
+
134
+ await self._maybe_persist_weights()
135
+
136
+ async def ingest_engagement(self, engagement: BriefingEngagement) -> None:
137
+ """Update weights from briefing section engagement signal."""
138
+ signal_type = "engagement_read" if engagement.read else "engagement_skip"
139
+ lr = _LEARNING_RATES[signal_type]
140
+ component = self._section_to_component(engagement.section)
141
+
142
+ # Read → boost, skip → penalise
143
+ delta = lr if engagement.read else -lr
144
+ self._adjust_weight(component, delta)
145
+
146
+ self._engagement_count += 1
147
+ logger.debug(
148
+ "ingest_engagement: section=%s read=%s component=%s delta=%.3f",
149
+ engagement.section,
150
+ engagement.read,
151
+ component,
152
+ delta,
153
+ )
154
+
155
+ await self._maybe_persist_weights()
156
+
157
+ async def ingest_outcome(self, outcome: GoalOutcome) -> None:
158
+ """Update weights from goal success/failure."""
159
+ lr = _LEARNING_RATES["outcome_success" if outcome.success else "outcome_failure"]
160
+ # Scale by confidence: high-confidence failures are penalised more
161
+ effective_lr = lr * outcome.confidence_at_time
162
+ delta = effective_lr if outcome.success else -effective_lr
163
+ self._adjust_weight(outcome.component, delta)
164
+
165
+ self._outcome_count += 1
166
+ logger.debug(
167
+ "ingest_outcome: goal=%s success=%s component=%s delta=%.3f",
168
+ outcome.goal_id,
169
+ outcome.success,
170
+ outcome.component,
171
+ delta,
172
+ )
173
+
174
+ await self._maybe_persist_weights()
175
+
176
+ # ------------------------------------------------------------------
177
+ # Weight access
178
+ # ------------------------------------------------------------------
179
+
180
+ async def get_component_weights(self) -> Dict[str, float]:
181
+ """Return a snapshot of current component weights."""
182
+ return dict(self._weights)
183
+
184
+ def get_weight(self, component: str) -> float:
185
+ """Return current weight for *component* (1.0 if not yet set)."""
186
+ return self._weights.get(component, _DEFAULT_WEIGHT)
187
+
188
+ def set_weight(self, component: str, value: float) -> None:
189
+ """Manually set a component weight (e.g., MetaLearner override)."""
190
+ self._weights[component] = max(_WEIGHT_MIN, min(_WEIGHT_MAX, value))
191
+
192
+ @property
193
+ def stats(self) -> Dict[str, int]:
194
+ """Return ingestion counters."""
195
+ return {
196
+ "corrections": self._correction_count,
197
+ "engagements": self._engagement_count,
198
+ "outcomes": self._outcome_count,
199
+ }
200
+
201
+ # ------------------------------------------------------------------
202
+ # Internal helpers
203
+ # ------------------------------------------------------------------
204
+
205
+ def _adjust_weight(self, component: str, delta: float) -> None:
206
+ """Apply *delta* to *component* weight, clamping to [min, max]."""
207
+ current = self._weights.get(component, _DEFAULT_WEIGHT)
208
+ updated = max(_WEIGHT_MIN, min(_WEIGHT_MAX, current + delta))
209
+ self._weights[component] = updated
210
+
211
+ async def _maybe_persist_weights(self) -> None:
212
+ """Persist current weights to the graph if a client is available."""
213
+ if self._graph is None:
214
+ return
215
+ try:
216
+ weights_json = str(self._weights) # simple repr; real impl uses JSON
217
+ if hasattr(self._graph, "execute"):
218
+ await self._graph.execute(
219
+ "MERGE (w:LearnerWeights {id: 'continuous_learner'}) "
220
+ "SET w.weights = $weights, w.updated_at = datetime()",
221
+ weights=weights_json,
222
+ )
223
+ except Exception as exc:
224
+ logger.debug("Weight persistence skipped: %s", exc)
225
+
226
+ @staticmethod
227
+ def _correction_type_to_component(correction_type: str) -> str:
228
+ mapping = {
229
+ "factual": "knowledge",
230
+ "tone": "response_quality",
231
+ "action": "task_planner",
232
+ "preference": "preference_learner",
233
+ }
234
+ return mapping.get(correction_type, "general")
235
+
236
+ @staticmethod
237
+ def _section_to_component(section: str) -> str:
238
+ mapping = {
239
+ "goals": "goal_engine",
240
+ "relationships": "engagement_profile",
241
+ "insights": "research_orchestrator",
242
+ "anomalies": "anomaly_detector",
243
+ "predictions": "mind_predictor",
244
+ }
245
+ return mapping.get(section, "briefing")
@@ -0,0 +1,321 @@
1
+ """FeedbackStore — persist and retrieve user corrections for learning.
2
+
3
+ Corrections feed into ContinuousLearner and are periodically summarized
4
+ by MetaLearner into durable preference updates.
5
+
6
+ Storage: SQLite (default ~/.colony/feedback.db) so corrections survive
7
+ process restarts without requiring Neo4j to be reachable.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ import os
15
+ import sqlite3
16
+ import uuid
17
+ from contextlib import contextmanager
18
+ from dataclasses import asdict, dataclass, field
19
+ from datetime import datetime, timezone
20
+ from typing import Any, Dict, Generator, List, Optional
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _DEFAULT_DB_PATH = os.path.join(
25
+ os.path.expanduser("~"), ".colony", "feedback.db"
26
+ )
27
+
28
+ _SCHEMA = """
29
+ CREATE TABLE IF NOT EXISTS corrections (
30
+ correction_id TEXT PRIMARY KEY,
31
+ timestamp TEXT NOT NULL,
32
+ original_response TEXT NOT NULL,
33
+ correction_text TEXT NOT NULL,
34
+ correction_type TEXT NOT NULL,
35
+ context_hash TEXT NOT NULL,
36
+ applied INTEGER NOT NULL DEFAULT 0,
37
+ person_id TEXT NOT NULL DEFAULT '',
38
+ processed_at TEXT
39
+ );
40
+ """
41
+
42
+ # Migrations for existing databases that pre-date new columns.
43
+ _MIGRATIONS = [
44
+ "ALTER TABLE corrections ADD COLUMN person_id TEXT NOT NULL DEFAULT ''",
45
+ "ALTER TABLE corrections ADD COLUMN processed_at TEXT",
46
+ ]
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # Data types
51
+ # ---------------------------------------------------------------------------
52
+
53
+ @dataclass
54
+ class UserCorrection:
55
+ """A single user correction event."""
56
+
57
+ correction_id: str
58
+ timestamp: datetime
59
+ original_response: str
60
+ correction_text: str
61
+ correction_type: str # "factual" | "tone" | "action" | "preference"
62
+ context_hash: str
63
+ applied: bool = False
64
+ person_id: str = ""
65
+ processed_at: Optional[datetime] = None
66
+
67
+ @classmethod
68
+ def create(
69
+ cls,
70
+ original_response: str,
71
+ correction_text: str,
72
+ correction_type: str,
73
+ context_hash: str,
74
+ person_id: str = "",
75
+ ) -> "UserCorrection":
76
+ """Factory that generates a UUID and timestamps the correction."""
77
+ return cls(
78
+ correction_id=str(uuid.uuid4()),
79
+ timestamp=datetime.now(timezone.utc),
80
+ original_response=original_response,
81
+ correction_text=correction_text,
82
+ correction_type=correction_type,
83
+ context_hash=context_hash,
84
+ person_id=person_id,
85
+ )
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Store
90
+ # ---------------------------------------------------------------------------
91
+
92
+ class FeedbackStore:
93
+ """Persist and retrieve user corrections for learning.
94
+
95
+ Thread-safe via SQLite's WAL mode. All public methods are synchronous
96
+ so they can be called from both sync and async contexts without a
97
+ dedicated event loop.
98
+
99
+ Args:
100
+ db_path: Path to the SQLite database file. Defaults to
101
+ ``~/.colony/feedback.db``. Pass ``:memory:`` for tests.
102
+ """
103
+
104
+ def __init__(self, db_path: str = _DEFAULT_DB_PATH) -> None:
105
+ self._db_path = db_path
106
+ # For :memory: DBs we keep a single persistent connection so schema survives
107
+ self._mem_conn: sqlite3.Connection | None = None
108
+ if db_path == ":memory:":
109
+ self._mem_conn = sqlite3.connect(":memory:", check_same_thread=False)
110
+ self._mem_conn.row_factory = sqlite3.Row
111
+ self._mem_conn.execute("PRAGMA journal_mode=WAL")
112
+ self._mem_conn.executescript(_SCHEMA)
113
+ self._mem_conn.commit()
114
+ else:
115
+ self._ensure_db()
116
+
117
+ # ------------------------------------------------------------------
118
+ # Public API
119
+ # ------------------------------------------------------------------
120
+
121
+ def record_correction(self, correction: UserCorrection) -> None:
122
+ """Persist *correction* to the store (idempotent by correction_id)."""
123
+ with self._conn() as conn:
124
+ conn.execute(
125
+ "INSERT OR REPLACE INTO corrections "
126
+ "(correction_id, timestamp, original_response, correction_text, "
127
+ " correction_type, context_hash, applied, person_id, processed_at) "
128
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
129
+ (
130
+ correction.correction_id,
131
+ correction.timestamp.isoformat(),
132
+ correction.original_response,
133
+ correction.correction_text,
134
+ correction.correction_type,
135
+ correction.context_hash,
136
+ int(correction.applied),
137
+ correction.person_id,
138
+ correction.processed_at.isoformat() if correction.processed_at else None,
139
+ ),
140
+ )
141
+ logger.debug("Recorded correction %s (%s)", correction.correction_id, correction.correction_type)
142
+
143
+ def get_unapplied(self, limit: int = 50) -> List[UserCorrection]:
144
+ """Return up to *limit* corrections that have not yet been applied."""
145
+ with self._conn() as conn:
146
+ rows = conn.execute(
147
+ "SELECT * FROM corrections WHERE applied = 0 "
148
+ "ORDER BY timestamp ASC LIMIT ?",
149
+ (limit,),
150
+ ).fetchall()
151
+ return [self._row_to_correction(r) for r in rows]
152
+
153
+ def mark_processed(self, correction_ids: List[str]) -> None:
154
+ """Stamp *processed_at* on each correction so consume_unprocessed skips it."""
155
+ if not correction_ids:
156
+ return
157
+ now_iso = datetime.now(timezone.utc).isoformat()
158
+ placeholders = ",".join("?" * len(correction_ids))
159
+ with self._conn() as conn:
160
+ conn.execute(
161
+ f"UPDATE corrections SET processed_at = ? "
162
+ f"WHERE correction_id IN ({placeholders})",
163
+ [now_iso] + list(correction_ids),
164
+ )
165
+ logger.debug("Marked %d corrections as processed", len(correction_ids))
166
+
167
+ def consume_unprocessed(self) -> List[UserCorrection]:
168
+ """Fetch all unprocessed corrections, mark them processed, and return them."""
169
+ with self._conn() as conn:
170
+ cols = {row[1] for row in conn.execute("PRAGMA table_info(corrections)").fetchall()}
171
+ if "processed_at" not in cols:
172
+ conn.execute("ALTER TABLE corrections ADD COLUMN processed_at TEXT")
173
+
174
+ rows = conn.execute(
175
+ "SELECT * FROM corrections WHERE processed_at IS NULL ORDER BY timestamp ASC"
176
+ ).fetchall()
177
+
178
+ if not rows:
179
+ return []
180
+
181
+ corrections = [self._row_to_correction(r) for r in rows]
182
+ self.mark_processed([c.correction_id for c in corrections])
183
+ logger.info("consume_unprocessed: returned %d corrections", len(corrections))
184
+ return corrections
185
+
186
+ def mark_applied(self, correction_ids: List[str]) -> None:
187
+ """Mark the given corrections as applied (won't be returned by get_unapplied)."""
188
+ if not correction_ids:
189
+ return
190
+ placeholders = ",".join("?" * len(correction_ids))
191
+ with self._conn() as conn:
192
+ conn.execute(
193
+ f"UPDATE corrections SET applied = 1 WHERE correction_id IN ({placeholders})",
194
+ correction_ids,
195
+ )
196
+ logger.debug("Marked %d corrections as applied", len(correction_ids))
197
+
198
+ def get_correction_summary(self, days: int = 7) -> Dict[str, object]:
199
+ """Return summary statistics for corrections in the last *days* days."""
200
+ cutoff = (
201
+ datetime.now(timezone.utc)
202
+ .replace(hour=0, minute=0, second=0, microsecond=0)
203
+ )
204
+ # Subtract days manually (no timedelta import needed above)
205
+ import datetime as _dt
206
+ cutoff = cutoff - _dt.timedelta(days=days)
207
+ cutoff_iso = cutoff.isoformat()
208
+
209
+ with self._conn() as conn:
210
+ total = conn.execute(
211
+ "SELECT COUNT(*) FROM corrections WHERE timestamp >= ?", (cutoff_iso,)
212
+ ).fetchone()[0]
213
+ applied = conn.execute(
214
+ "SELECT COUNT(*) FROM corrections WHERE timestamp >= ? AND applied = 1",
215
+ (cutoff_iso,),
216
+ ).fetchone()[0]
217
+ by_type_rows = conn.execute(
218
+ "SELECT correction_type, COUNT(*) FROM corrections "
219
+ "WHERE timestamp >= ? GROUP BY correction_type",
220
+ (cutoff_iso,),
221
+ ).fetchall()
222
+
223
+ by_type: Dict[str, int] = {row[0]: row[1] for row in by_type_rows}
224
+ return {
225
+ "days": days,
226
+ "total": total,
227
+ "applied": applied,
228
+ "unapplied": total - applied,
229
+ "by_type": by_type,
230
+ }
231
+
232
+ def between(
233
+ self,
234
+ since_iso: str,
235
+ until_iso: str,
236
+ *,
237
+ person_id: str = "",
238
+ limit: int = 10000,
239
+ ) -> List[Dict[str, Any]]:
240
+ """Read a stable correction cohort without consuming its rows."""
241
+
242
+ query = (
243
+ "SELECT correction_id,timestamp,correction_type,context_hash,"
244
+ "applied,person_id,processed_at FROM corrections "
245
+ "WHERE timestamp>=? AND timestamp<?")
246
+ params: List[Any] = [since_iso, until_iso]
247
+ if person_id:
248
+ query += " AND person_id=?"
249
+ params.append(person_id)
250
+ query += " ORDER BY timestamp,correction_id LIMIT ?"
251
+ params.append(max(1, min(int(limit), 100000)))
252
+ with self._conn() as conn:
253
+ rows = conn.execute(query, params).fetchall()
254
+ return [dict(row) for row in rows]
255
+
256
+ def count(self) -> int:
257
+ """Return total number of stored corrections."""
258
+ with self._conn() as conn:
259
+ return conn.execute("SELECT COUNT(*) FROM corrections").fetchone()[0]
260
+
261
+ def clear(self) -> None:
262
+ """Delete all records (useful for tests / onboarding reset)."""
263
+ with self._conn() as conn:
264
+ conn.execute("DELETE FROM corrections")
265
+
266
+ # ------------------------------------------------------------------
267
+ # Private helpers
268
+ # ------------------------------------------------------------------
269
+
270
+ def _ensure_db(self) -> None:
271
+ """Create the database file and schema if they don't exist, then migrate."""
272
+ if self._db_path != ":memory:":
273
+ os.makedirs(os.path.dirname(self._db_path), exist_ok=True)
274
+ with self._conn() as conn:
275
+ conn.executescript(_SCHEMA)
276
+ # Best-effort migrations for pre-existing databases.
277
+ for stmt in _MIGRATIONS:
278
+ try:
279
+ conn.execute(stmt)
280
+ except sqlite3.OperationalError:
281
+ pass # Column already exists — that's fine.
282
+
283
+ @contextmanager
284
+ def _conn(self) -> Generator[sqlite3.Connection, None, None]:
285
+ if self._mem_conn is not None:
286
+ # In-memory: reuse the persistent connection; caller manages commit
287
+ try:
288
+ yield self._mem_conn
289
+ self._mem_conn.commit()
290
+ except Exception:
291
+ self._mem_conn.rollback()
292
+ raise
293
+ return
294
+ conn = sqlite3.connect(self._db_path, check_same_thread=False)
295
+ conn.execute("PRAGMA journal_mode=WAL")
296
+ conn.row_factory = sqlite3.Row
297
+ try:
298
+ yield conn
299
+ conn.commit()
300
+ except Exception:
301
+ conn.rollback()
302
+ raise
303
+ finally:
304
+ conn.close()
305
+
306
+ @staticmethod
307
+ def _row_to_correction(row: sqlite3.Row) -> UserCorrection:
308
+ ts_raw = row["timestamp"]
309
+ try:
310
+ ts = datetime.fromisoformat(ts_raw)
311
+ except ValueError:
312
+ ts = datetime.now(timezone.utc)
313
+ return UserCorrection(
314
+ correction_id=row["correction_id"],
315
+ timestamp=ts,
316
+ original_response=row["original_response"],
317
+ correction_text=row["correction_text"],
318
+ correction_type=row["correction_type"],
319
+ context_hash=row["context_hash"],
320
+ applied=bool(row["applied"]),
321
+ )
@@ -0,0 +1 @@
1
+ # mind_model