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
+ """Import reviewed Hermes DM source quotations without replaying turn effects.
2
+
3
+ Run with --dry-run first. Input must be a stable SQLite backup, not a live WAL
4
+ database. No Hermes runtime, model client, network fetch or ordinary turn API is
5
+ invoked. Unmapped history remains in its original database.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ from collections import Counter
11
+ from contextlib import closing
12
+ from datetime import datetime, timezone
13
+ import hashlib
14
+ import json
15
+ import math
16
+ from pathlib import Path
17
+ import re
18
+ import sqlite3
19
+
20
+ from .idempotency import TurnIdempotencyLedger, SourceErased, canonical_turn_digest
21
+
22
+ AUTOMATION = {'cron', 'subagent', 'webhook', 'api_server', 'api', 'cli', 'tool', 'system'}
23
+ OPTIONAL_MESSAGE = {'active': '1', 'compacted': '0', '_compressed_summary': '0',
24
+ 'observed': '0', 'tool_calls': 'NULL', 'finish_reason': 'NULL',
25
+ 'effect_disposition': 'NULL', 'display_kind': 'NULL', 'platform_message_id': 'NULL'}
26
+
27
+
28
+ def mapping_document(path):
29
+ return validate_mapping(json.loads(Path(path).read_text()))
30
+
31
+
32
+ def validate_mapping(value):
33
+ if not isinstance(value, dict) or value.get('version') != 1 or not isinstance(value.get('namespace'), str) or not re.fullmatch(r'[a-zA-Z0-9_.-]{1,80}', value['namespace']):
34
+ raise ValueError('Expected a version 1 mapping with a stable database namespace')
35
+ bindings = value.get('bindings')
36
+ if not isinstance(bindings, list) or not bindings or len(bindings) > 500:
37
+ raise ValueError('Provide 1..500 explicitly reviewed session bindings')
38
+ seen = set()
39
+ for binding in bindings:
40
+ if not isinstance(binding, dict):
41
+ raise ValueError('Every binding must be an object')
42
+ for field in ('session_id', 'platform', 'actor_id', 'chat_id', 'contact_id'):
43
+ if not isinstance(binding.get(field), str) or not binding[field].strip():
44
+ raise ValueError('Every binding needs session, platform, actor, chat and contact IDs')
45
+ if binding['session_id'] in seen:
46
+ raise ValueError('A session cannot have multiple actor bindings')
47
+ seen.add(binding['session_id'])
48
+ if binding['platform'] in AUTOMATION or binding.get('chat_type') != 'dm':
49
+ raise ValueError('This importer accepts explicitly attributed direct messages only')
50
+ if not binding.get('review_evidence'):
51
+ raise ValueError('Every binding requires an independent review evidence reference')
52
+ return value
53
+
54
+
55
+ def open_snapshot(path):
56
+ path = Path(path).resolve(strict=True)
57
+ wal = path.with_name(path.name + '-wal')
58
+ if wal.exists() and wal.stat().st_size:
59
+ raise ValueError('Use a consistent SQLite backup, not an active WAL database')
60
+ with path.open('rb') as stream:
61
+ digest = hashlib.file_digest(stream, 'sha256').hexdigest()
62
+ conn = sqlite3.connect(path.as_uri() + '?mode=ro', uri=True)
63
+ conn.row_factory = sqlite3.Row
64
+ conn.execute('PRAGMA query_only=ON')
65
+ conn.execute('BEGIN')
66
+ return conn, digest
67
+
68
+
69
+ def validate_bindings(conn, mapping):
70
+ columns = {r['name'] for r in conn.execute('PRAGMA table_info(sessions)')}
71
+ required = {'id', 'source', 'user_id', 'chat_id', 'chat_type'}
72
+ if not required <= columns:
73
+ raise ValueError('Hermes session schema lacks required origin fields; do not infer missing identities')
74
+ messages = {r['name'] for r in conn.execute('PRAGMA table_info(messages)')}
75
+ if not {'id', 'session_id', 'role', 'content', 'timestamp'} <= messages:
76
+ raise ValueError('Unsupported Hermes message schema')
77
+ sessions = {}
78
+ for binding in mapping['bindings']:
79
+ row = conn.execute('SELECT * FROM sessions WHERE id=?', (binding['session_id'],)).fetchone()
80
+ if row is None:
81
+ raise ValueError('A reviewed session is missing from this snapshot')
82
+ row = dict(row)
83
+ for field, actual in (('platform', 'source'), ('actor_id', 'user_id'), ('chat_id', 'chat_id'), ('chat_type', 'chat_type')):
84
+ if row[actual] != binding[field]:
85
+ raise ValueError('A reviewed session no longer matches its exact transport binding')
86
+ origin = row.get('origin_json')
87
+ if origin:
88
+ try:
89
+ origin = json.loads(origin)
90
+ except (TypeError, ValueError):
91
+ raise ValueError('Invalid recorded session origin') from None
92
+ if not isinstance(origin, dict):
93
+ raise ValueError('Invalid recorded session origin')
94
+ for field, actual in (('platform', 'platform'), ('user_id', 'actor_id'), ('chat_id', 'chat_id'), ('chat_type', 'chat_type')):
95
+ if origin.get(field) and origin[field] != binding[actual]:
96
+ raise ValueError('Session origin conflicts with the reviewed binding')
97
+ sessions[row['id']] = row
98
+ return sessions, messages
99
+
100
+
101
+ def source_message(row, session, binding, namespace):
102
+ """Classify stored messages without treating their role as actor evidence."""
103
+ if row['role'] not in {'user', 'assistant'}:
104
+ return None, 'non_conversation_role'
105
+ if row['_compressed_summary']:
106
+ return None, 'generated_summary'
107
+ if not row['active'] and not row['compacted']:
108
+ return None, 'rewound_or_inactive'
109
+ if row['observed']:
110
+ return None, 'ambient_observation'
111
+ if row['effect_disposition'] or row['display_kind']:
112
+ return None, 'special_message_metadata'
113
+ if row['tool_calls'] not in (None, '', '[]') or row['finish_reason'] in {'tool_calls', 'function_call'}:
114
+ return None, 'tool_call_message'
115
+ content = row['content']
116
+ if not isinstance(content, str) or not content.strip():
117
+ return None, 'empty_or_unsupported_content'
118
+ # Hermes JSON-encodes multimodal block lists in this TEXT column. Retain
119
+ # exact text-only lists; leave attachment history in the original database
120
+ # rather than fetch URLs, flatten blocks, or schedule old vision jobs.
121
+ if content.lstrip().startswith('['):
122
+ try:
123
+ decoded = json.loads(content)
124
+ except ValueError:
125
+ decoded = None
126
+ if isinstance(decoded, list) and decoded and all(isinstance(b, dict) and 'type' in b for b in decoded):
127
+ if any(b['type'] not in {'text', 'input_text', 'output_text'} or not isinstance(b.get('text'), str) for b in decoded):
128
+ return None, 'historical_attachment_unretained'
129
+ content = decoded
130
+ timestamp = row['timestamp']
131
+ try:
132
+ occurred_at = datetime.fromtimestamp(float(timestamp), timezone.utc).isoformat() if math.isfinite(float(timestamp)) else None
133
+ except (ValueError, TypeError, OverflowError, OSError):
134
+ occurred_at = None
135
+ provenance = {'kind': 'hermes_history', 'database_namespace': namespace,
136
+ 'message_id': row['id'], 'session_id': row['session_id'],
137
+ 'platform': binding['platform'], 'actor_id': binding['actor_id'],
138
+ 'chat_id': binding['chat_id'], 'actor_basis': 'reviewed_direct_session',
139
+ 'speaker': 'contact' if row['role'] == 'user' else 'assistant',
140
+ 'timestamp_basis': 'hermes_recorded_timestamp' if occurred_at else 'unknown',
141
+ 'platform_message_id': row['platform_message_id'],
142
+ 'session_model_hint': session.get('model'), 'message_model_revision': 'unknown',
143
+ 'model_basis': 'session_hint_not_per_message_attribution',
144
+ 'review_evidence': binding['review_evidence'], 'learning': 'source_only'}
145
+ return ({'role': row['role'], 'content': content, 'provenance': provenance}, occurred_at), None
146
+
147
+
148
+ def _progress(ledger):
149
+ with closing(ledger._connect()) as conn, conn:
150
+ conn.execute('''CREATE TABLE IF NOT EXISTS source_import_progress (
151
+ run_id TEXT PRIMARY KEY, namespace TEXT NOT NULL, snapshot_sha256 TEXT NOT NULL,
152
+ mapping_sha256 TEXT NOT NULL, cursor INTEGER NOT NULL DEFAULT 0,
153
+ counts_json TEXT NOT NULL DEFAULT '{}')''')
154
+
155
+
156
+ def import_history(database, mapping, *, state_dir=None, apply=False, limit=1000):
157
+ mapping = validate_mapping(mapping)
158
+ if not 1 <= limit <= 10000:
159
+ raise ValueError('Batch limit must be between 1 and 10000 messages')
160
+ if apply and (mapping.get('reviewed') is not True or state_dir is None):
161
+ raise ValueError('Apply requires a reviewed mapping and an explicit target state directory')
162
+ if state_dir is not None and Path(database).resolve() == (Path(state_dir) / 'turn-idempotency.db').resolve():
163
+ raise ValueError('Source snapshot and destination ledger must be different files')
164
+ conn, snapshot_hash = open_snapshot(database)
165
+ with closing(conn):
166
+ sessions, columns = validate_bindings(conn, mapping)
167
+ mapping_hash = canonical_turn_digest(mapping)
168
+ run_id = canonical_turn_digest([mapping['namespace'], snapshot_hash, mapping_hash])
169
+ ledger = TurnIdempotencyLedger(Path(state_dir) / 'turn-idempotency.db') if apply else None
170
+ cursor, counts = 0, Counter()
171
+ if ledger:
172
+ _progress(ledger)
173
+ with closing(ledger._connect()) as progress, progress:
174
+ progress.execute('INSERT OR IGNORE INTO source_import_progress(run_id,namespace,snapshot_sha256,mapping_sha256) VALUES (?,?,?,?)',
175
+ (run_id, mapping['namespace'], snapshot_hash, mapping_hash))
176
+ previous = progress.execute('SELECT cursor,counts_json FROM source_import_progress WHERE run_id=?', (run_id,)).fetchone()
177
+ cursor, counts = previous['cursor'], Counter(json.loads(previous['counts_json']))
178
+ selected = 'id,session_id,role,content,timestamp,' + ','.join(
179
+ key if key in columns else default + ' AS ' + key for key, default in OPTIONAL_MESSAGE.items())
180
+ ids = list(sessions)
181
+ placeholders = ','.join('?' for _ in ids)
182
+ bindings = {b['session_id']: b for b in mapping['bindings']}
183
+ query = f'SELECT {selected} FROM messages WHERE session_id IN ({placeholders}) AND id>? ORDER BY id'
184
+ rows = conn.execute(query + (' LIMIT ?' if apply else ''), (*ids, cursor, limit) if apply else (*ids, cursor))
185
+ for row in rows:
186
+ previous_cursor = cursor
187
+ row = dict(row)
188
+ binding = bindings[row['session_id']]
189
+ prepared, reason = source_message(row, sessions[row['session_id']], binding, mapping['namespace'])
190
+ if reason:
191
+ counts[reason] += 1
192
+ elif ledger:
193
+ message, occurred_at = prepared
194
+ turn_id = f"hermes-history:{mapping['namespace']}:{row['id']}"
195
+ try:
196
+ created = ledger.record_source(turn_id, contact_id=binding['contact_id'],
197
+ session_id=row['session_id'], messages=[message], scope='person',
198
+ occurred_at=occurred_at, derive_claims=False)
199
+ counts['retained_new' if created else 'retained_existing'] += 1
200
+ except SourceErased:
201
+ counts['erased_not_restored'] += 1
202
+ else:
203
+ counts['eligible_source_quotations'] += 1
204
+ cursor = row['id']
205
+ if ledger:
206
+ # Source commit precedes cursor acknowledgement. A crash in
207
+ # between safely repeats record_source, never ordinary effects.
208
+ with closing(ledger._connect()) as progress, progress:
209
+ updated = progress.execute('UPDATE source_import_progress SET cursor=?,counts_json=? WHERE run_id=? AND cursor=?',
210
+ (cursor, json.dumps(counts), run_id, previous_cursor))
211
+ if updated.rowcount != 1:
212
+ raise ValueError('Another importer advanced this run; resume with one process')
213
+ remaining = conn.execute(f'SELECT count(*) FROM messages WHERE session_id IN ({placeholders}) AND id>?', (*ids, cursor)).fetchone()[0]
214
+ unbound = [dict(r) for r in conn.execute(f'''SELECT s.source,count(DISTINCT s.id) sessions,count(m.id) messages
215
+ FROM sessions s LEFT JOIN messages m ON m.session_id=s.id
216
+ WHERE s.id NOT IN ({placeholders}) GROUP BY s.source''', ids)]
217
+ return {'mode': 'apply' if apply else 'dry_run', 'run_id': run_id,
218
+ 'snapshot_sha256': snapshot_hash, 'mapping_sha256': mapping_hash,
219
+ 'bound_sessions': len(sessions), 'cursor': cursor, 'remaining': remaining,
220
+ 'complete': remaining == 0, 'counts': dict(counts),
221
+ 'unmapped_retained_in_hermes': unbound,
222
+ 'learning_replayed': False, 'models_called': False,
223
+ 'projection': 'canonical_source_fts_and_queued_semantic_text_only'}
224
+
225
+
226
+ def main():
227
+ parser = argparse.ArgumentParser(description=__doc__)
228
+ parser.add_argument('--database', type=Path, required=True)
229
+ parser.add_argument('--mapping', type=Path, required=True)
230
+ mode = parser.add_mutually_exclusive_group(required=True)
231
+ mode.add_argument('--dry-run', action='store_true')
232
+ mode.add_argument('--apply', action='store_true')
233
+ parser.add_argument('--state-dir', type=Path)
234
+ parser.add_argument('--limit', type=int, default=1000)
235
+ args = parser.parse_args()
236
+ try:
237
+ result = import_history(args.database, mapping_document(args.mapping), state_dir=args.state_dir,
238
+ apply=args.apply, limit=args.limit)
239
+ except (ValueError, OSError, sqlite3.Error) as exc:
240
+ parser.exit(1, f'History import stopped: {exc}\n')
241
+ print(json.dumps(result, indent=2))
242
+
243
+
244
+ if __name__ == '__main__':
245
+ main()
@@ -0,0 +1,268 @@
1
+ """Read selected native boards and accepted draft associations.
2
+
3
+ Hermes owns its database, task transitions and recovery. This reader never
4
+ initializes a board or imports the native runtime into the sidecar environment.
5
+ """
6
+ from contextlib import closing
7
+ import hashlib
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ import re
12
+ import sqlite3
13
+ import time
14
+
15
+ from .hermes_work import selected_home
16
+
17
+
18
+ _BOARD = re.compile(r'[a-z0-9][a-z0-9_-]{0,63}')
19
+ _TERMINAL = ('done', 'cancelled', 'archived')
20
+
21
+
22
+ def _board_path(home, board):
23
+ return home/'kanban.db' if board == 'default' else home/'kanban/boards'/board/'kanban.db'
24
+
25
+
26
+ def observed_boards():
27
+ """Select explicit boards or the native current namespace, never enumerate."""
28
+ home = selected_home()
29
+ if home is None:
30
+ return None, [], 'profile_not_bound'
31
+ if home.parent.name == 'profiles':
32
+ home = home.parent.parent
33
+ override = os.environ.get('HERMES_KANBAN_HOME', '').strip()
34
+ if override and Path(override).expanduser().resolve() != home:
35
+ raise ValueError('conflicting_native_home')
36
+ configured = os.environ.get('COLONY_HERMES_WORK_BOARDS')
37
+ if configured is not None:
38
+ boards = json.loads(configured)
39
+ if (not isinstance(boards, list) or not 1 <= len(boards) <= 8
40
+ or any(not isinstance(b, str) or not _BOARD.fullmatch(b) for b in boards)):
41
+ raise ValueError('invalid_selected_boards')
42
+ boards = list(dict.fromkeys(boards))
43
+ selection = 'configured_boards'
44
+ else:
45
+ # Match native current-board precedence. Stale native selections fall
46
+ # through to default; explicit observer selections remain unavailable.
47
+ candidates = [os.environ.get('HERMES_KANBAN_BOARD', '').strip().lower()]
48
+ pointer = home/'kanban/current'
49
+ if pointer.is_file() and pointer.stat().st_size <= 256:
50
+ candidates.append(pointer.read_text().strip().lower())
51
+ boards = [next((b for b in candidates if _BOARD.fullmatch(b) and
52
+ (b == 'default' or _board_path(home, b).exists()
53
+ or (_board_path(home, b).parent/'board.json').exists())), 'default')]
54
+ selection = 'current_board_only'
55
+ pinned = os.environ.get('HERMES_KANBAN_DB', '').strip()
56
+ if pinned and (len(boards) != 1 or Path(pinned).expanduser().resolve()
57
+ != _board_path(home, boards[0]).resolve()):
58
+ raise ValueError('conflicting_native_board')
59
+ return home, boards, selection
60
+
61
+
62
+ def kanban_view(*, limit=8, now=None, read_budget=.2):
63
+ """Owner-only operational projection; no board initialization or dispatch."""
64
+ view = {'source': 'hermes_native_kanban_ledgers', 'available': False,
65
+ 'items': [], 'recent': [], 'complete': False, 'boards': [],
66
+ 'coverage': 'selected native boards only; task/run records and goal budgets, '
67
+ 'not process liveness or verified external effects'}
68
+ try:
69
+ home, boards, selection = observed_boards()
70
+ except (OSError, ValueError, KeyError, TypeError):
71
+ return {**view, 'reason': 'invalid_native_board_binding'}
72
+ if home is None:
73
+ return {**view, 'reason': selection}
74
+ view.update(source_home_id=hashlib.sha256(str(home).encode()).hexdigest(), selection=selection)
75
+ limit = max(1, min(int(limit), 100))
76
+ now = time.time() if now is None else now
77
+ deadline = time.monotonic() + min(.2, max(.001, read_budget))
78
+ active, recent, total, recent_total = [], [], 0, 0
79
+ for board in boards:
80
+ coverage = {'board': board, 'available': False}
81
+ view['boards'].append(coverage)
82
+ path = _board_path(home, board)
83
+ if not path.is_file():
84
+ coverage['reason'] = 'native_board_absent'
85
+ continue
86
+ try:
87
+ remaining = deadline - time.monotonic()
88
+ if remaining <= 0:
89
+ raise TimeoutError
90
+ with closing(sqlite3.connect(path.as_uri()+'?mode=ro', uri=True,
91
+ timeout=min(.05, remaining))) as db:
92
+ db.row_factory = sqlite3.Row
93
+ db.execute('PRAGMA query_only=ON')
94
+ db.set_progress_handler(lambda: int(time.monotonic() >= deadline), 1000)
95
+ db.execute('BEGIN')
96
+ terminal = "status IN ('done','cancelled','archived')"
97
+ # Archiving an unfinished task records an event, not a
98
+ # completion. Preserve that distinction in the projection.
99
+ terminal_at = ("CASE WHEN status='archived' THEN "
100
+ "(SELECT MAX(e.created_at) FROM task_events e "
101
+ "WHERE e.task_id=tasks.id AND e.kind='archived') "
102
+ "ELSE completed_at END")
103
+ active_count = db.execute(f'SELECT count(*) FROM tasks WHERE NOT ({terminal})').fetchone()[0]
104
+ recent_count = db.execute(f'SELECT count(*) FROM tasks WHERE {terminal} AND ({terminal_at})>=?', (now-7*86400,)).fetchone()[0]
105
+ columns = ('id,title,status,assignee,goal_mode,goal_max_turns,current_run_id,'
106
+ 'created_at,started_at,completed_at,last_heartbeat_at,'
107
+ f'{terminal_at} AS terminal_record_at')
108
+ rows = db.execute(f'SELECT {columns} FROM tasks WHERE NOT ({terminal}) '
109
+ 'ORDER BY created_at DESC,id LIMIT ?', (limit,)).fetchall()
110
+ done = db.execute(f'SELECT {columns} FROM tasks WHERE {terminal} AND ({terminal_at})>=? '
111
+ 'ORDER BY terminal_record_at DESC,id LIMIT ?', (now-7*86400,limit)).fetchall()
112
+
113
+ def project(row):
114
+ run = db.execute('SELECT status FROM task_runs WHERE id=? AND task_id=?',
115
+ (row['current_run_id'], row['id'])).fetchone()
116
+ heartbeat = row['last_heartbeat_at']
117
+ return {'native_board': board, 'native_task_id': row['id'],
118
+ 'label': str(row['title'] or '')[:128], 'status': row['status'],
119
+ 'assignee': str(row['assignee'] or '')[:128],
120
+ 'goal_mode': bool(row['goal_mode']), 'goal_max_turns': row['goal_max_turns'],
121
+ 'native_run_id': row['current_run_id'], 'native_run_status': run['status'] if run else None,
122
+ 'created_at': row['created_at'], 'started_at': row['started_at'],
123
+ 'completed_at': row['completed_at'],
124
+ 'terminal_record_at': row['terminal_record_at'],
125
+ 'heartbeat_age_seconds': round(max(0., now-heartbeat), 1) if heartbeat else None,
126
+ 'liveness': 'native_terminal_record' if row['status'] in _TERMINAL else 'unknown'}
127
+ projected, projected_done = [project(r) for r in rows], [project(r) for r in done]
128
+ active.extend(projected); recent.extend(projected_done)
129
+ total += active_count; recent_total += recent_count
130
+ coverage.update(available=True, total=active_count, recent_total=recent_count)
131
+ except (OSError, sqlite3.Error, ValueError, TypeError):
132
+ coverage['reason'] = 'native_board_unavailable'
133
+ active.sort(key=lambda r: (-r['created_at'], r['native_board'], r['native_task_id']))
134
+ recent.sort(key=lambda r: (-r['terminal_record_at'], r['native_board'], r['native_task_id']))
135
+ available = any(b['available'] for b in view['boards'])
136
+ return {**view, 'available': available, 'partial': not all(b['available'] for b in view['boards']),
137
+ **({} if available else {'reason': 'selected_native_boards_unavailable'}),
138
+ 'items': active[:limit], 'recent': recent[:limit], 'total': total,
139
+ 'recent_total': recent_total, 'truncated': total>limit, 'recent_truncated': recent_total>limit,
140
+ 'limit': limit, 'recent_window_seconds': 7*86400}
141
+
142
+
143
+ def selected_board():
144
+ home = selected_home()
145
+ if home is not None and home.parent.name == 'profiles':
146
+ home = home.parent.parent
147
+ board = os.environ.get('COLONY_LOCAL_WORK_BOARD', 'colony-drafts').strip()
148
+ profile = os.environ.get('COLONY_LOCAL_WORK_PROFILE', 'colony-drafts').strip()
149
+ if home is None or not re.fullmatch(r'[a-z0-9][a-z0-9_-]{0,63}', board) or not profile:
150
+ raise ValueError('native_board_binding_unavailable')
151
+ path = home/'kanban.db' if board == 'default' else home/'kanban/boards'/board/'kanban.db'
152
+ return home, board, profile, path
153
+
154
+
155
+ def task_snapshot(identifier, contact_id, native, *, review=False, followup=False, read_timeout=.2):
156
+ """Verify native provenance and, when supplied, the currently held run."""
157
+ if review or followup:
158
+ home, boards, _ = observed_boards()
159
+ if home is None or 'default' not in boards:
160
+ raise ValueError('selected_native_review_board_required')
161
+ board, profile, path = 'default', 'default', _board_path(home, 'default')
162
+ else:
163
+ home, board, profile, path = selected_board()
164
+ if native['native_board'] != board:
165
+ raise ValueError('selected_native_board_required')
166
+ if not path.is_file():
167
+ raise OSError('native_board_unavailable')
168
+ with closing(sqlite3.connect(path.as_uri()+'?mode=ro', uri=True,
169
+ timeout=min(2., max(0., read_timeout)))) as db:
170
+ db.row_factory = sqlite3.Row
171
+ db.execute('PRAGMA query_only=ON')
172
+ db.execute('BEGIN')
173
+ task = db.execute('SELECT * FROM tasks WHERE id=?', (native['native_task_id'],)).fetchone()
174
+ if review:
175
+ profile = 'colony-reviews'
176
+ # Preserve historical observations; an unrestricted never-run task
177
+ # cannot be associated through this API after the boundary change.
178
+ if task is not None and task['assignee'] == 'default' and db.execute(
179
+ 'SELECT 1 FROM task_runs WHERE task_id=? LIMIT 1', (task['id'],)).fetchone():
180
+ profile = 'default'
181
+ creator = 'colony-followup' if followup else 'colony-initiative' if review else 'colony-local-work'
182
+ if (task is None or task['created_by'] != creator
183
+ or task['idempotency_key'] != creator+':'+identifier
184
+ or task['tenant'] != contact_id or task['assignee'] != profile):
185
+ raise ValueError('accepted_native_task_required')
186
+ result = {'source_home_id': hashlib.sha256(str(home).encode()).hexdigest(),
187
+ 'native_board': board, 'native_task_id': task['id']}
188
+ count = db.execute('SELECT count(*) FROM task_runs WHERE task_id=?', (task['id'],)).fetchone()[0]
189
+ run = None
190
+ if native.get('native_run_id') is not None:
191
+ run = db.execute('SELECT * FROM task_runs WHERE id=? AND task_id=?',
192
+ (native['native_run_id'], task['id'])).fetchone()
193
+ if (task['status'] != 'running' or task['current_run_id'] != native['native_run_id']
194
+ or run is None or run['status'] != 'running'
195
+ or not native.get('native_claim_lock')
196
+ or task['claim_lock'] != native['native_claim_lock']
197
+ or run['claim_lock'] != native['native_claim_lock']):
198
+ raise ValueError('current_native_run_required')
199
+ result.update(native_run_id=run['id'], native_claim_lock=run['claim_lock'])
200
+ state = {'status': task['status'], 'native_run_id': task['current_run_id'],
201
+ 'attempt_count': count, 'archived': task['status'] == 'archived'}
202
+ if review or followup:
203
+ state['forecast_configuration'] = {
204
+ 'runtime_budget_seconds': dict(task).get('max_runtime_seconds'),
205
+ 'requested_profile': task['assignee'],
206
+ 'task_model_override_at_observation': dict(task).get('model_override'),
207
+ 'task_provider_override_at_observation': dict(task).get('provider_override'),
208
+ 'served_model': None, 'timestamp_precision_seconds': 1,
209
+ }
210
+ latest = db.execute('SELECT * FROM task_runs WHERE task_id=? ORDER BY id DESC LIMIT 1',
211
+ (task['id'],)).fetchone()
212
+ gave_up = bool(latest and db.execute("SELECT 1 FROM task_events WHERE task_id=? "
213
+ "AND kind='gave_up' AND created_at>=? LIMIT 1", (task['id'], latest['started_at'])).fetchone())
214
+ state.update(contract_sha256=hashlib.sha256((task['body'] or '').encode()).hexdigest(),
215
+ completed_run=bool(latest and latest['outcome'] == 'completed'),
216
+ gave_up=gave_up and task['status'] == 'blocked' and latest['outcome'] in
217
+ {'gave_up', 'crashed', 'timed_out', 'spawn_failed'},
218
+ run_outcome=latest['outcome'] if latest else None,
219
+ error=str(latest['error'] or task['last_failure_error'] or '')[:500] if latest else '',
220
+ summary=str(latest['summary'] or '')[:1600] if latest else '',
221
+ native_run_id=latest['id'] if latest else None)
222
+ terminal_at = dict(task).get('completed_at')
223
+ if task['status'] == 'archived':
224
+ terminal_at = db.execute("SELECT MAX(created_at) FROM task_events WHERE task_id=? AND kind='archived'", (task['id'],)).fetchone()[0]
225
+ if latest and latest['ended_at'] is not None:
226
+ state['duration_observation'] = {
227
+ 'outcome': latest['outcome'], 'started_at': latest['started_at'],
228
+ 'ended_at': latest['ended_at'], 'attempt_count': count,
229
+ 'profile': latest['profile'], 'served_model': None,
230
+ 'execution_seconds': latest['ended_at'] - latest['started_at'],
231
+ 'timestamp_precision_seconds': 1,
232
+ }
233
+ elif terminal_at is not None and task['status'] in {'done', 'archived', 'cancelled'}:
234
+ state['duration_observation'] = {
235
+ 'outcome': task['status'], 'started_at': dict(task).get('started_at'),
236
+ 'ended_at': terminal_at, 'attempt_count': count,
237
+ 'profile': task['assignee'], 'served_model': None,
238
+ 'execution_seconds': None, 'timestamp_precision_seconds': 1,
239
+ }
240
+ if latest and latest['outcome'] in {'crashed', 'timed_out', 'spawn_failed', 'gave_up'}:
241
+ state['runtime_observation'] = {
242
+ 'outcome': latest['outcome'], 'started_at': latest['started_at'],
243
+ 'ended_at': latest['ended_at'], 'max_runtime_seconds': latest['max_runtime_seconds'],
244
+ 'attempt_count': count, 'profile': latest['profile'],
245
+ 'task_model_override_at_observation': dict(task).get('model_override'),
246
+ 'task_provider_override_at_observation': dict(task).get('provider_override'),
247
+ 'served_model': 'unknown',
248
+ 'role': 'native_default_worker',
249
+ }
250
+ return result, state
251
+
252
+
253
+ def project_accepted(identifier, contact_id, context):
254
+ """Observe only an accepted task, never expose a machine-wide board."""
255
+ review = context.get('native_review')
256
+ if review:
257
+ contact_id, context = review['contact_id'], {**review, 'execution_backend': 'kanban'}
258
+ if context.get('execution_backend') != 'kanban' or not context.get('native_task_id'):
259
+ return None
260
+ try:
261
+ native, state = task_snapshot(identifier, contact_id, {
262
+ 'native_board': context.get('native_board'), 'native_task_id': context['native_task_id']}, review=bool(review))
263
+ if native['source_home_id'] != context.get('source_home_id'):
264
+ raise ValueError('selected_native_home_changed')
265
+ return {'available': True, **native, **state,
266
+ 'liveness': 'native_running_record' if state['status'] == 'running' else 'native_task_record'}
267
+ except (OSError, sqlite3.Error, ValueError, KeyError, TypeError):
268
+ return {'available': False, 'reason': 'accepted_native_task_unavailable'}
@@ -0,0 +1,96 @@
1
+ """Read one explicitly bound native cron ledger, without importing its scheduler.
2
+
3
+ Hermes owns transitions and recovery. These rows are a read projection, not a
4
+ new lease, process inventory, or proof that an external effect completed.
5
+ """
6
+ from contextlib import closing
7
+ from datetime import datetime, timezone
8
+ import hashlib
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ import sqlite3
13
+ import time
14
+
15
+ from apsimo import get_state_dir
16
+
17
+
18
+ def selected_home():
19
+ """Use the private instance binding or explicit HERMES_HOME, never a scan."""
20
+ selected = os.environ.get('HERMES_HOME', '').strip()
21
+ path = get_state_dir() / 'instance.json'
22
+ if path.is_file():
23
+ manifest = json.loads(path.read_text())
24
+ if manifest.get('version') != 1 or manifest.get('profile') != 'local':
25
+ raise ValueError('unsupported_instance_binding')
26
+ bound = Path(manifest['hermes_home']).expanduser().resolve()
27
+ if selected and Path(selected).expanduser().resolve() != bound:
28
+ raise ValueError('conflicting_instance_binding')
29
+ return bound
30
+ return Path(selected).expanduser().resolve() if selected else None
31
+
32
+
33
+ def _age(value, now):
34
+ if not value:
35
+ return None
36
+ parsed = datetime.fromisoformat(value.replace('Z', '+00:00'))
37
+ if parsed.tzinfo is None:
38
+ return None
39
+ return round(max(0., now - parsed.timestamp()), 1)
40
+
41
+
42
+ def cron_view(*, limit=8, now=None):
43
+ view = {'source': 'hermes_native_cron_ledger', 'available': False,
44
+ 'items': [], 'recent': [], 'complete': False,
45
+ 'coverage': 'one selected Hermes profile; no process liveness or external-effect verification'}
46
+ try:
47
+ home = selected_home()
48
+ except (OSError, ValueError, KeyError, TypeError):
49
+ return {**view, 'reason': 'invalid_profile_binding'}
50
+ if home is None:
51
+ return {**view, 'reason': 'profile_not_bound'}
52
+ view['source_home_id'] = hashlib.sha256(str(home).encode()).hexdigest()
53
+ path = home / 'cron' / 'executions.db'
54
+ limit = max(1, min(int(limit), 100))
55
+ now = time.time() if now is None else now
56
+ deadline = time.monotonic() + .2
57
+ try:
58
+ if not path.is_file():
59
+ return {**view, 'reason': 'native_ledger_absent'}
60
+ with closing(sqlite3.connect(path.as_uri() + '?mode=ro', uri=True, timeout=.1)) as conn:
61
+ conn.row_factory = sqlite3.Row
62
+ conn.execute('PRAGMA query_only=ON')
63
+ conn.set_progress_handler(lambda: int(time.monotonic() >= deadline), 1000)
64
+ # One snapshot for active counts and rows. No initialization,
65
+ # recovery, native list_executions(), or schema writes on read.
66
+ conn.execute('BEGIN')
67
+ total = conn.execute("SELECT count(*) FROM executions WHERE status IN ('claimed','running')").fetchone()[0]
68
+ columns = 'id, job_id, source, status, claimed_at, started_at, finished_at'
69
+ active = conn.execute(f"SELECT {columns} FROM executions WHERE status IN ('claimed','running') ORDER BY claimed_at DESC, id DESC LIMIT ?", (limit,)).fetchall()
70
+ cutoff = datetime.fromtimestamp(now - 86400, timezone.utc).isoformat()
71
+ recent = conn.execute(f"SELECT {columns} FROM executions WHERE status IN ('completed','failed','unknown') AND julianday(finished_at) >= julianday(?) ORDER BY finished_at DESC, id DESC LIMIT ?", (cutoff, limit)).fetchall()
72
+ # Labels are optional, bounded, and never fall back to prompts/scripts.
73
+ names = {}
74
+ jobs = home / 'cron' / 'jobs.json'
75
+ if jobs.is_file() and jobs.stat().st_size <= 4 * 1024 * 1024:
76
+ try:
77
+ document = json.loads(jobs.read_text())
78
+ names = {str(job['id']): str(job['name'])[:200]
79
+ for job in document.get('jobs', [])
80
+ if isinstance(job, dict) and job.get('id') and isinstance(job.get('name'), str)}
81
+ except (OSError, ValueError, TypeError, AttributeError):
82
+ pass
83
+
84
+ def project(row):
85
+ item = dict(row)
86
+ item['execution_id'] = item.pop('id')
87
+ item['name'] = names.get(item['job_id'])
88
+ item['record_age_seconds'] = _age(item['finished_at'] or item['started_at'] or item['claimed_at'], now)
89
+ item['liveness'] = 'unknown' if item['status'] in {'claimed', 'running', 'unknown'} else 'native_terminal_record'
90
+ return item
91
+
92
+ return {**view, 'available': True, 'items': [project(row) for row in active],
93
+ 'recent': [project(row) for row in recent], 'total': total,
94
+ 'truncated': total > len(active), 'recent_window_seconds': 86400}
95
+ except (OSError, sqlite3.Error, ValueError, TypeError):
96
+ return {**view, 'reason': 'native_ledger_unavailable'}