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,296 @@
1
+ """Prospective native task turnaround learning, without quality claims.
2
+
3
+ Attachment is the prediction origin, so turnaround includes queue delay. Native
4
+ run elapsed time is retained separately. Hermes' ledger cannot attest which
5
+ processor actually served a request; configured overrides are not substituted.
6
+ """
7
+ from contextlib import closing
8
+ from datetime import datetime, timezone
9
+ import hashlib
10
+ import json
11
+ import logging
12
+ import math
13
+ import time
14
+
15
+ from apsimo import get_state_dir
16
+ from apsimo.turns import get_turn_idempotency_ledger
17
+ from apsimo.turns.idempotency import SourceErased
18
+ from .expectations import expectations_enabled
19
+
20
+ logger = logging.getLogger(__name__)
21
+ VERSION = 'native-task-forecast-v1'
22
+ DECISION_VERSION = 'native-task-inspection-shadow-v1'
23
+
24
+
25
+ def _digest(value):
26
+ return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
27
+
28
+
29
+ def _identity(native):
30
+ return {key: native[key] for key in ('source_home_id', 'native_board', 'native_task_id')}
31
+
32
+
33
+ def _reference(source_id):
34
+ return 'receipt:'+source_id
35
+
36
+
37
+ def _current(ledger, references, versions, owner):
38
+ ids = [r.removeprefix('receipt:') for r in references]
39
+ current = {r['source_id']:r['source_version'] for r in ledger.source_references(
40
+ ids, contact_id=owner, session_id='')}
41
+ if not all(current.get(identifier) == versions.get(_reference(identifier))
42
+ and not ledger.is_projection_erased(identifier) for identifier in ids):
43
+ return False
44
+ with closing(ledger._connect()) as db:
45
+ for identifier in ids:
46
+ if db.execute('SELECT 1 FROM source_annotations WHERE target_source_id=? LIMIT 1', (identifier,)).fetchone():
47
+ return False
48
+ return True
49
+
50
+
51
+ def _retain(ledger, *, source_id, owner, native, facts, occurred_at, dependencies=None):
52
+ # First observation survives replay. Native configuration can change later;
53
+ # never rebind an old receipt to whichever model is configured today.
54
+ with closing(ledger._connect()) as db:
55
+ exists = db.execute('SELECT 1 FROM turn_sources WHERE turn_id=?', (source_id,)).fetchone()
56
+ if not exists:
57
+ ledger.record_source(source_id, contact_id=owner,
58
+ session_id='native-runtime:'+native['native_task_id'],
59
+ occurred_at=datetime.fromtimestamp(occurred_at, timezone.utc).isoformat(),
60
+ messages=[{'role':'assistant', '_native_runtime_observation':VERSION, '_native_forecast_facts':facts,
61
+ **({'_supplied_sources': [{'source_id': r.removeprefix('receipt:'), 'source_version': v}
62
+ for r, v in dependencies.items()]} if dependencies else {}),
63
+ 'content':'Runtime-owned task forecast/lifecycle observation. This is not an owner statement, '
64
+ 'successful output evaluation, or permission for any action.\n'+json.dumps(facts, sort_keys=True)}],
65
+ derive_claims=False)
66
+ refs = ledger.source_references([source_id], contact_id=owner, session_id='')
67
+ if not refs or ledger.is_projection_erased(source_id):
68
+ raise SourceErased('runtime forecast evidence unavailable')
69
+ with closing(ledger._connect()) as db:
70
+ message = json.loads(db.execute('SELECT messages_json FROM turn_sources WHERE turn_id=?', (source_id,)).fetchone()[0])[0]
71
+ if message.get('_native_runtime_observation') != VERSION or message.get('_native_forecast_facts', {}).get('identity') != _identity(native):
72
+ raise ValueError('runtime forecast source binding conflict')
73
+ return {_reference(source_id):refs[0]['source_version']}, message['_native_forecast_facts']
74
+
75
+
76
+ def _outcome_facts(ledger, outcome):
77
+ if ledger is None or outcome is None:
78
+ return {}
79
+ with closing(ledger._connect()) as db:
80
+ row = db.execute('SELECT messages_json FROM turn_sources WHERE turn_id=?',
81
+ (outcome['receipt_ref'].removeprefix('receipt:'),)).fetchone()
82
+ facts = json.loads(row[0])[0].get('_native_forecast_facts', {}) if row else {}
83
+ return facts if facts.get('version') == VERSION and facts.get('kind') == 'duration_outcome' else {}
84
+
85
+
86
+ def _comparable(configuration, outcome_facts):
87
+ observed = outcome_facts.get('processor_observation') or {}
88
+ return bool(configuration and observed.get('configuration') == configuration
89
+ and observed.get('complete_observed_pairs') and observed.get('served_model'))
90
+
91
+
92
+ def _parts(review, native, state, owner):
93
+ from apsimo.api.routers import host
94
+ engine = host._expectations
95
+ if engine is None or not expectations_enabled():
96
+ return None
97
+ selection = (review.get('native_work') or {}).get('outcome_learning') or {}
98
+ if selection.get('version') != 'native-runtime-observation-v1':
99
+ return None
100
+ identity = _identity(native)
101
+ forecast_id = 'native-task:'+_digest(identity)
102
+ ledger = get_turn_idempotency_ledger(get_state_dir())
103
+ return engine.store, ledger, selection, identity, forecast_id
104
+
105
+
106
+ def attach(review, native, state, owner):
107
+ """Called after a verified prospective blocked task binding, before promote."""
108
+ parts = _parts(review, native, state, owner)
109
+ if parts is None:
110
+ return {'status':'disabled_or_unselected'}
111
+ store, ledger, selection, identity, fid = parts
112
+ existing = store.forecast_history(fid)['forecasts']
113
+ if existing:
114
+ return {'status':'existing', 'forecast_id':fid, 'horizon':existing[0]['horizon']}
115
+ if state['status'] != 'blocked' or state['attempt_count'] != 0:
116
+ return {'status':'not_prospective'}
117
+ bound = datetime.fromisoformat(selection['bound_at']).timestamp()
118
+ # Task/run timestamps have one-second precision. The no-runs binding above
119
+ # establishes ordering within that second; retain the precise bound too.
120
+ origin = math.floor(bound)
121
+ now = time.time()
122
+ config = state['forecast_configuration']
123
+ budget = config.get('runtime_budget_seconds')
124
+ prior = float(budget) if isinstance(budget, (int, float)) and budget > 0 else 480.
125
+ cohort = 'native-review:'+review['review']['action']
126
+ def current(prediction, outcome):
127
+ return (_current(ledger, prediction.evidence_refs, prediction.detail['source_versions'], owner)
128
+ and _current(ledger, outcome['evidence_refs'], outcome['source_versions'], owner)
129
+ and prediction.detail['model_provenance']['capabilities'] == config
130
+ and _comparable(config, _outcome_facts(ledger, outcome)))
131
+ estimate = store.estimate_duration(domain='task_duration',cohort=cohort,
132
+ subject_person_id=owner,viewer_scope='owner',prior_seconds=prior,
133
+ now=now,evidence_is_current=current)
134
+ horizon = origin + estimate['seconds']
135
+ if horizon <= now:
136
+ return {'status':'binding_too_old_for_prospective_forecast'}
137
+ source_id = 'native-forecast-binding:'+_digest(identity)
138
+ versions, retained = _retain(ledger,source_id=source_id,owner=owner,native=native,occurred_at=bound,
139
+ facts={'version':VERSION,'kind':'prospective_binding','identity':identity,
140
+ 'bound_at':selection['bound_at'],'configuration':config,
141
+ 'registered_action':review['review']['action'],
142
+ 'estimate':estimate,'measurement':'attachment_to_terminal_turnaround'})
143
+ # Recovery after source capture uses the retained original estimate, not
144
+ # a later improved estimate presented as the earlier prediction.
145
+ estimate = retained['estimate']
146
+ horizon = origin + estimate['seconds']
147
+ config = retained['configuration']
148
+ prediction = store.issue_forecast(forecast_id=fid,subject='task:'+native['native_task_id'],
149
+ domain='task_duration',expectation='The accepted native review completes within its turnaround estimate',
150
+ confidence=estimate['confidence'],horizon=horizon,origin_at=origin,issued_at=origin,
151
+ evidence_refs=list(versions),source_versions=versions,source_kind='task_receipt',
152
+ cohort=cohort,method=estimate['method'],
153
+ model_provenance={'requested_role':'native_default_worker','served_model':None,
154
+ 'model_revision':None,'capabilities':config,'fallback':None},
155
+ subject_person_id=owner,viewer_scope='owner',shareability='owner_private',
156
+ conditions={'measurement':'attachment_to_terminal_turnaround','timestamp_precision_seconds':1,
157
+ 'bound_at':selection['bound_at'],'estimate':estimate})
158
+ return {'status':'issued','forecast_id':fid,'horizon':prediction.horizon,'estimate':estimate}
159
+
160
+
161
+ def observe(review, native, state, owner):
162
+ """Readback settles duration only. Pauses/failures are explicit censoring."""
163
+ parts = _parts(review, native, state, owner)
164
+ if parts is None:
165
+ return {'status':'disabled_or_unselected'}
166
+ store, ledger, selection, identity, fid = parts
167
+ history = store.forecast_history(fid)
168
+ if not history['forecasts']:
169
+ return {'status':'no_prospective_forecast'}
170
+ prediction = history['forecasts'][0]
171
+ observed = state.get('duration_observation')
172
+ if not observed or not isinstance(observed.get('ended_at'), (int,float)):
173
+ return {'status':'pending','forecast_id':fid}
174
+ ended = observed['ended_at']
175
+ if ended < math.floor(datetime.fromisoformat(selection['bound_at']).timestamp()) or ended > time.time():
176
+ return {'status':'unqualified_native_time'}
177
+ source_id = 'native-forecast-outcome:'+_digest({**identity,'native_run_id':state.get('native_run_id'),
178
+ 'outcome':observed['outcome'],'ended_at':ended})
179
+ receipt = _reference(source_id)
180
+ if any(o['receipt_ref'] == receipt for o in history['outcomes']):
181
+ return {'status':'observed','forecast_id':fid,'receipt_ref':receipt}
182
+ valid = _current(ledger,prediction['evidence_refs'],prediction['detail']['source_versions'],owner)
183
+ paused = any(o['reason'] == 'paused' for o in history['outcomes'])
184
+ complete = state['status'] == 'done' and state.get('completed_run') and valid and not paused
185
+ reason = '' if complete else 'source_retracted' if not valid else 'paused' if paused or (state['status']=='blocked' and not state.get('gave_up')) else 'cancelled' if state['status'] in {'archived','cancelled'} else 'unavailable'
186
+ from .runtime_models import summarize
187
+ processor = summarize(ledger, owner, native, state.get('native_run_id'))
188
+ versions, retained = _retain(ledger,source_id=source_id,owner=owner,native=native,occurred_at=ended,
189
+ dependencies=processor['source_versions'],
190
+ facts={'version':VERSION,'kind':'duration_outcome','identity':identity,
191
+ 'native_run_id':state.get('native_run_id'),'observation':observed,
192
+ 'processor_observation':processor,
193
+ 'native_status':state['status'],'forecast_id':fid,
194
+ 'turnaround_seconds':ended-prediction['detail']['origin_at'],
195
+ 'measurement':'attachment_to_terminal_turnaround','quality_evaluated':False})
196
+ versions = {**versions, **retained.get('processor_observation', {}).get('source_versions', {})}
197
+ result = store.record_forecast_outcome(forecast_id=fid,receipt_ref=receipt,
198
+ evidence_refs=list(versions),source_versions=versions,source_kind='task_receipt',
199
+ observed_at=ended,subject_person_id=owner,viewer_scope='owner',shareability='owner_private',
200
+ status='observed' if complete else 'censored',value=True if complete else None,
201
+ reason=reason,previous_revision=len(history['outcomes']))
202
+ return {'status':'observed','forecast_id':fid,'outcome_status':result['status'],
203
+ 'receipt_ref':receipt,'quality_evaluated':False}
204
+
205
+
206
+ def project(review, native, state, owner, *, now=None):
207
+ """Project one existing forecast on a normal work read, without side effects.
208
+
209
+ The original horizon, not a revised or recomputed estimate, supplies the
210
+ shadow inspection decision. No suggestion, polling, retry or notification
211
+ is enabled by this projection, even when its forecast has many samples.
212
+ """
213
+ parts = _parts(review, native, state, owner)
214
+ if parts is None:
215
+ return {'status':'disabled_or_unselected'}
216
+ store, ledger, _, _, fid = parts
217
+ history = store.forecast_history(fid)
218
+ if not history['forecasts']:
219
+ return {'status':'no_prospective_forecast'}
220
+ prediction = history['forecasts'][0]
221
+ detail = prediction['detail']
222
+ if (prediction['subject_person_id'] != owner or prediction['viewer_scope'] != 'owner'
223
+ or not _current(ledger,prediction['evidence_refs'],detail['source_versions'],owner)):
224
+ return {'status':'source_unavailable'}
225
+ stamp = time.time() if now is None else float(now)
226
+ if not math.isfinite(stamp) or stamp < detail['origin_at']:
227
+ return {'status':'unqualified_observation_time'}
228
+ estimate = detail['conditions']['estimate']
229
+ prior_horizon = detail['origin_at'] + estimate['prior_seconds']
230
+ recorded_configuration = detail['model_provenance']['capabilities']
231
+ same_conditions = bool(recorded_configuration) and state.get('forecast_configuration') == recorded_configuration
232
+ status = state.get('status')
233
+ active = status in {'ready','running'}
234
+ latest = history['outcomes'][-1] if history['outcomes'] else None
235
+ outcome_facts = _outcome_facts(ledger, latest)
236
+ processor = outcome_facts.get('processor_observation') or {}
237
+ if latest:
238
+ # Later profile/task edits do not rewrite the conditions witnessed by
239
+ # the terminal receipt. Original future processor fields stay unknown.
240
+ same_conditions = bool(recorded_configuration) and recorded_configuration == processor.get('configuration')
241
+ outcome_current = latest is None or _current(ledger,latest['evidence_refs'],latest['source_versions'],owner)
242
+ if not outcome_current:
243
+ decision = prior_decision = 'source_unavailable'
244
+ elif latest and latest['status'] == 'censored':
245
+ decision = prior_decision = 'censored'
246
+ elif not active:
247
+ decision = prior_decision = 'terminal' if status in {'done','archived','cancelled'} else 'not_running'
248
+ elif not same_conditions:
249
+ decision = prior_decision = 'conditions_unknown_or_changed'
250
+ else:
251
+ decision = 'inspect_recorded_state' if stamp >= prediction['horizon'] else 'continue_waiting'
252
+ prior_decision = 'inspect_recorded_state' if stamp >= prior_horizon else 'continue_waiting'
253
+ result = {'status':'shadow','version':DECISION_VERSION,
254
+ 'decision_id':'native-inspection:'+_digest({'forecast_id':fid,'horizon':prediction['horizon']}),
255
+ 'forecast_id':fid,'original_revision':prediction['detail']['revision'],
256
+ 'original_horizon':prediction['horizon'],'prior_horizon':prior_horizon,
257
+ 'observed_at':stamp,'native_status':status,'decision':decision,
258
+ 'prior_decision':prior_decision,'changed_from_prior':decision != prior_decision,
259
+ 'sample_n':estimate['sample_n'],'uncertain':estimate['uncertain'],
260
+ 'configuration_matches':same_conditions,
261
+ 'conditions_comparable':bool(outcome_current and latest and latest['status'] == 'observed'
262
+ and _comparable(recorded_configuration, outcome_facts)),
263
+ 'served_model':processor.get('served_model'),
264
+ 'original_served_model':detail['model_provenance']['served_model'],
265
+ 'processor_observation':processor,
266
+ 'evidence_refs':prediction['evidence_refs'],'source_versions':detail['source_versions'],
267
+ 'suggestion_enabled':False,'quality_evaluated':False,
268
+ 'enable_criteria':{'cohort_must_be_closed':True,'minimum_comparable_terminal_receipts':10,
269
+ 'lower_mean_absolute_error':True,'no_more_premature_inspections':True,
270
+ 'no_greater_mean_inspection_lateness':True}}
271
+ if latest and latest['status'] == 'observed' and latest['value'] is True and outcome_current:
272
+ ended = latest['observed_at']
273
+ result['comparison'] = {
274
+ 'conditions_comparable':result['conditions_comparable'],
275
+ 'observed_response_model':processor.get('served_model'),
276
+ 'receipt_ref':latest['receipt_ref'],'outcome_source_versions':latest['source_versions'],
277
+ 'turnaround_seconds':ended-detail['origin_at'],
278
+ 'forecast_absolute_error_seconds':abs(ended-prediction['horizon']),
279
+ 'prior_absolute_error_seconds':abs(ended-prior_horizon),
280
+ 'forecast_premature_inspection':prediction['horizon'] < ended,
281
+ 'prior_premature_inspection':prior_horizon < ended,
282
+ 'forecast_inspection_lateness_seconds':max(0.,prediction['horizon']-ended),
283
+ 'prior_inspection_lateness_seconds':max(0.,prior_horizon-ended),
284
+ 'counterfactual_horizon_comparison':True,'projection_added_status_calls':0}
285
+ return result
286
+
287
+
288
+ def safe(operation, review, native, state, owner):
289
+ """A forecast observer failure is visible but cannot stall accepted work."""
290
+ try:
291
+ return operation(review,native,state,owner)
292
+ except SourceErased:
293
+ return {'status':'source_unavailable'}
294
+ except Exception as error:
295
+ logger.warning('Native task forecast observer unavailable: %s', type(error).__name__)
296
+ return {'status':'unavailable','error_type':type(error).__name__}
@@ -0,0 +1,67 @@
1
+ """Canonical, prospective native request metadata; no transcript classifier."""
2
+ from contextlib import closing
3
+ import json
4
+ import time
5
+
6
+ from .runtime_forecasts import VERSION, _current, _digest, _identity, _retain
7
+
8
+
9
+ def _prefix(native, run_id):
10
+ return 'native-model:'+_digest({**_identity(native), 'native_run_id': run_id})+':'
11
+
12
+
13
+ def retain(ledger, owner, native, state, value):
14
+ """The API caller already verified the active native run and claim lock."""
15
+ request = {key: value.get(key) for key in
16
+ ('api_request_id', 'phase', 'requested_model', 'provider', 'response_model')}
17
+ if request['phase'] != 'response':
18
+ request['response_model'] = None
19
+ source_id = _prefix(native, native['native_run_id'])+_digest([request['api_request_id'], request['phase']])
20
+ versions, retained = _retain(ledger, source_id=source_id, owner=owner, native=native,
21
+ occurred_at=time.time(), facts={'version': VERSION, 'kind': 'model_request',
22
+ 'identity': _identity(native), 'native_run_id': native['native_run_id'],
23
+ 'request': request, 'configuration': state['forecast_configuration']})
24
+ if retained['request'] != request:
25
+ raise ValueError('native_model_observation_conflict')
26
+ return {'accepted': True, 'evidence_refs': list(versions),
27
+ 'identity_basis': 'provider_reported_response_model_not_weight_attestation'}
28
+
29
+
30
+ def summarize(ledger, owner, native, run_id):
31
+ """Pair observed starts/ends; missing, erased and mixed remain explicit."""
32
+ prefix = _prefix(native, run_id)
33
+ with closing(ledger._connect()) as db:
34
+ rows = db.execute('SELECT turn_id,messages_json FROM turn_sources WHERE contact_id=? '
35
+ 'AND substr(turn_id,1,?)=? ORDER BY turn_id LIMIT 257',
36
+ (owner, len(prefix), prefix)).fetchall()
37
+ erased = db.execute('SELECT 1 FROM source_erasures WHERE substr(turn_id,1,?)=? LIMIT 1',
38
+ (len(prefix), prefix)).fetchone()
39
+ refs = ledger.source_references([r['turn_id'] for r in rows], contact_id=owner, session_id='')
40
+ versions = {'receipt:'+r['source_id']: r['source_version'] for r in refs}
41
+ valid = bool(rows) and len(refs) == len(rows) and len(rows) <= 256 and not erased and _current(ledger, list(versions), versions, owner)
42
+ requests, configurations, models, requested = {}, [], set(), set()
43
+ for row in rows:
44
+ facts = json.loads(row['messages_json'])[0].get('_native_forecast_facts', {})
45
+ if (facts.get('version') != VERSION or facts.get('kind') != 'model_request'
46
+ or facts.get('identity') != _identity(native) or facts.get('native_run_id') != run_id):
47
+ valid = False
48
+ continue
49
+ request = facts['request']
50
+ requests.setdefault(request['api_request_id'], {})[request['phase']] = request
51
+ configurations.append(facts['configuration'])
52
+ if request.get('requested_model'):
53
+ requested.add(request['requested_model'])
54
+ if request['phase'] == 'response' and request.get('response_model'):
55
+ models.add(request['response_model'])
56
+ complete = valid and all(set(v) in ({'start', 'response'}, {'start', 'error'}) for v in requests.values())
57
+ missing_model = any('response' in v and not v['response'].get('response_model') for v in requests.values())
58
+ same_configuration = bool(configurations) and all(c == configurations[0] for c in configurations)
59
+ return {'basis': 'provider_reported_response_model_not_weight_attestation',
60
+ 'coverage': 'registered native review API requests only; auxiliary and uninstrumented calls unknown',
61
+ 'request_count': len(requests), 'complete_observed_pairs': bool(complete),
62
+ 'response_models': sorted(models), 'requested_models': sorted(requested),
63
+ 'served_model': next(iter(models)) if complete and not missing_model and len(models) == 1 else None,
64
+ 'model_state': 'unknown' if not complete or missing_model or not models else 'mixed' if len(models) > 1 else 'observed',
65
+ 'configuration': configurations[0] if same_configuration else None,
66
+ 'configuration_state': 'observed' if same_configuration and complete else 'unknown_or_changed',
67
+ 'evidence_refs': list(versions), 'source_versions': versions}
@@ -0,0 +1,207 @@
1
+ """Source settlement: resolving a concern settles what the concern points at.
2
+
3
+ A concern raised from a durable source (an overdue commitment, an anomaly, a
4
+ stale goal) carries that source in its ``sources`` list as ``"<kind>:<id>"``.
5
+ Resolving only the concern leaves the source open, and whatever ingests that
6
+ source re-raises the concern on the next tick — the owner's resolve silently
7
+ undone minutes later. The registry maps a source kind to a settle callback so
8
+ every surface (owner deck, agent tool, MCP, API) closes the whole chain with
9
+ one call.
10
+
11
+ Deployment-agnostic: the server wires settlers for whatever stores it runs;
12
+ unknown source kinds are skipped, and one failing settler never blocks the
13
+ others.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import logging
20
+ import threading
21
+ from typing import Any, Callable, Dict, List, Mapping, Optional
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ Settler = Callable[..., Optional[Dict[str, Any]]]
26
+
27
+ _SETTLERS: Dict[str, Settler] = {}
28
+ _RETRY_SAFE: set[str] = set()
29
+ _REGISTRY_LOCK = threading.RLock()
30
+
31
+
32
+ class SettlementRetryUnsafe(ValueError):
33
+ """One or more sources lack an exact operation-bound retry contract."""
34
+
35
+ def __init__(self, sources: List[str]) -> None:
36
+ super().__init__("cascade recovery requires retry-safe source settlers")
37
+ self.sources = list(sources)
38
+
39
+
40
+ class _SettlementOperationUnverified(ValueError):
41
+ settlement_error_code = "operation_unverified"
42
+
43
+
44
+ def source_operation_id(operation_root: str, source: str) -> str:
45
+ """Derive the stable per-intent/per-source idempotency identity."""
46
+
47
+ root = str(operation_root or "").strip()
48
+ ref = str(source or "").strip()
49
+ if not root or not ref:
50
+ raise ValueError("source settlement operation identity is incomplete")
51
+ digest = hashlib.sha256(f"{root}\0{ref}".encode("utf-8")).hexdigest()
52
+ return f"concern-source-operation:{digest}"
53
+
54
+
55
+ def register_settler(
56
+ kind: str,
57
+ fn: Settler,
58
+ *,
59
+ retry_safe: bool = False,
60
+ ) -> None:
61
+ """Register the settle callback for a source kind (e.g. "commitment").
62
+
63
+ Retry-safe callbacks additionally receive a stable ``operation_id`` and
64
+ must bind it together with outcome, note, and resolver to the durable
65
+ transition. Their detail must attest those exact fields. This is a
66
+ stronger contract than merely returning the same terminal state twice.
67
+ """
68
+ if type(retry_safe) is not bool:
69
+ raise ValueError("settler retry_safe must be boolean")
70
+ with _REGISTRY_LOCK:
71
+ _SETTLERS[kind] = fn
72
+ if retry_safe:
73
+ _RETRY_SAFE.add(kind)
74
+ else:
75
+ _RETRY_SAFE.discard(kind)
76
+
77
+
78
+ def registered_kinds() -> List[str]:
79
+ with _REGISTRY_LOCK:
80
+ return sorted(_SETTLERS)
81
+
82
+
83
+ def _settle_sources(
84
+ sources: List[str],
85
+ *,
86
+ callbacks: Mapping[str, Settler],
87
+ retry_safe_kinds: set[str],
88
+ outcome: str,
89
+ note: str,
90
+ resolved_by: str,
91
+ operation_root: Optional[str],
92
+ ) -> List[Dict[str, Any]]:
93
+ results: List[Dict[str, Any]] = []
94
+ for src in sources:
95
+ kind, _, source_id = str(src).partition(":")
96
+ fn = callbacks.get(kind)
97
+ if fn is None or not source_id:
98
+ continue
99
+ entry: Dict[str, Any] = {"source": src, "settled": False}
100
+ operation_id = None
101
+ try:
102
+ kwargs: Dict[str, Any] = {
103
+ "outcome": outcome,
104
+ "note": note,
105
+ "resolved_by": resolved_by,
106
+ }
107
+ if operation_root is not None and kind in retry_safe_kinds:
108
+ operation_id = source_operation_id(operation_root, src)
109
+ kwargs["operation_id"] = operation_id
110
+ detail = fn(source_id, **kwargs)
111
+ if detail is not None:
112
+ if operation_id is not None:
113
+ note_digest = hashlib.sha256(note.encode("utf-8")).hexdigest()
114
+ if not isinstance(detail, Mapping) or any((
115
+ detail.get("operation_id") != operation_id,
116
+ detail.get("outcome") != outcome,
117
+ detail.get("note_digest") != note_digest,
118
+ detail.get("resolved_by") != resolved_by,
119
+ )):
120
+ raise _SettlementOperationUnverified(
121
+ "retry-safe settler did not attest the bound operation"
122
+ )
123
+ entry["settled"] = True
124
+ if isinstance(detail, Mapping):
125
+ entry.update({
126
+ key: value for key, value in detail.items()
127
+ if key not in {
128
+ "source", "settled", "error", "error_digest",
129
+ }
130
+ })
131
+ except Exception as exc:
132
+ logger.warning("settler for %s failed (%s)", src, type(exc).__name__)
133
+ error_code = getattr(exc, "settlement_error_code", "settler_error")
134
+ if error_code not in {
135
+ "operation_conflict", "operation_unverified", "settler_error",
136
+ }:
137
+ error_code = "settler_error"
138
+ entry["error"] = error_code
139
+ entry["error_digest"] = hashlib.sha256(
140
+ str(exc).encode("utf-8", errors="replace")
141
+ ).hexdigest()
142
+ results.append(entry)
143
+ return results
144
+
145
+
146
+ def settle_sources(
147
+ sources: Optional[List[str]],
148
+ *,
149
+ outcome: str = "done",
150
+ note: str = "",
151
+ resolved_by: str = "owner",
152
+ operation_root: Optional[str] = None,
153
+ ) -> List[Dict[str, Any]]:
154
+ """Settle every recognized source reference. Returns one entry per source
155
+ that had a registered settler, with ``settled`` and any settler detail."""
156
+ with _REGISTRY_LOCK:
157
+ callbacks = dict(_SETTLERS)
158
+ retry_safe_kinds = set(_RETRY_SAFE)
159
+ return _settle_sources(
160
+ list(sources or []),
161
+ callbacks=callbacks,
162
+ retry_safe_kinds=retry_safe_kinds,
163
+ outcome=outcome,
164
+ note=note,
165
+ resolved_by=resolved_by,
166
+ operation_root=operation_root,
167
+ )
168
+
169
+
170
+ def retry_safe_settle_sources(
171
+ sources: Optional[List[str]],
172
+ *,
173
+ operation_root: str,
174
+ outcome: str = "done",
175
+ note: str = "",
176
+ resolved_by: str = "owner",
177
+ ) -> List[Dict[str, Any]]:
178
+ """Explicitly retry only sources with exact operation-bound semantics.
179
+
180
+ The complete source set is validated before the first callback. Unknown,
181
+ malformed, or merely state-idempotent settlers are rejected together and
182
+ remain pending for explicit reconciliation.
183
+ """
184
+
185
+ refs = list(sources or [])
186
+ unsafe: List[str] = []
187
+ with _REGISTRY_LOCK:
188
+ callbacks = dict(_SETTLERS)
189
+ retry_safe_kinds = set(_RETRY_SAFE)
190
+ for src in refs:
191
+ kind, separator, source_id = str(src).partition(":")
192
+ if (
193
+ not separator or not source_id or kind not in callbacks
194
+ or kind not in retry_safe_kinds
195
+ ):
196
+ unsafe.append(str(src))
197
+ if unsafe:
198
+ raise SettlementRetryUnsafe(unsafe)
199
+ return _settle_sources(
200
+ refs,
201
+ callbacks=callbacks,
202
+ retry_safe_kinds=retry_safe_kinds,
203
+ outcome=outcome,
204
+ note=note,
205
+ resolved_by=resolved_by,
206
+ operation_root=operation_root,
207
+ )