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,353 @@
1
+ """Shadow remaining-duration forecasts from existing Hermes execution callbacks.
2
+
3
+ These measure first accepted API-start observation to observed turn terminal,
4
+ not queue time, independent task count, output quality, or complete model usage.
5
+ No forecast governs work. Missing callbacks remain missing evidence.
6
+ """
7
+ from contextlib import closing
8
+ from datetime import datetime, timezone
9
+ import json
10
+ import logging
11
+ import math
12
+ import os
13
+
14
+ from .expectations import DURATION_METHOD_V1, DURATION_METHOD_V2, expectations_enabled
15
+ from .runtime_forecasts import _current, _digest
16
+
17
+ LEGACY_VERSION = 'execution-duration-observation-v1'
18
+ VERSION = 'execution-duration-observation-v2'
19
+ MEASUREMENT = 'first_api_observation_to_terminal'
20
+ PRIOR_SECONDS = 480.0
21
+ MAX_REQUESTS = 128
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def accumulate(old, value, previous, now):
26
+ """Called in the registry transaction; bounded metadata, no model calls."""
27
+ data = old or {'version': VERSION, 'start_observed': previous is None
28
+ and value['sequence'] == 1 and value['phase'] == 'turn'
29
+ and value['state'] == 'observed', 'requests': {}, 'incomplete': False}
30
+ if previous and value['sequence'] != previous['sequence'] + 1:
31
+ data['incomplete'] = True
32
+ event = value.get('runtime') or {}
33
+ request_id = event.get('request_id')
34
+ if event:
35
+ if not request_id or (request_id not in data['requests'] and len(data['requests']) >= MAX_REQUESTS):
36
+ data['incomplete'] = True
37
+ else:
38
+ pair = data['requests'].setdefault(request_id, {})
39
+ phase = event['event']
40
+ if phase in pair and pair[phase] != event:
41
+ data['incomplete'] = True
42
+ pair[phase] = event
43
+ if 'first_api' not in data:
44
+ data['first_api'] = {'observed_at': now, 'sequence': value['sequence'], 'event': event,
45
+ 'eligible_start': bool(data['start_observed'] and not data['incomplete']
46
+ and event.get('event') == 'start' and event.get('api_call_count') == 1
47
+ and event.get('retry_count') == 0 and request_id
48
+ and event.get('requested_model') and event.get('provider'))}
49
+ return data
50
+
51
+
52
+ def _parts(registry, owner):
53
+ from apsimo.api.routers import host
54
+ configured_owner = os.environ.get('COLONY_OWNER_PERSON_ID', '').strip() or os.environ.get('COLONY_OWNER_CONTACT_ID', '').strip()
55
+ if owner != configured_owner or not configured_owner or not expectations_enabled() or host._expectations is None:
56
+ return None
57
+ return host._expectations.store, registry.ledger
58
+
59
+
60
+ def _read(registry, execution_id):
61
+ with closing(registry.ledger._connect()) as db:
62
+ row = db.execute('SELECT e.*,r.metadata_json FROM execution_observations e '
63
+ 'JOIN execution_runtime_observations r USING(execution_id) WHERE execution_id=?', (execution_id,)).fetchone()
64
+ return (dict(row), json.loads(row['metadata_json'])) if row else (None, {})
65
+
66
+
67
+ ROUTING_KEYS = ('requested_model', 'provider', 'api_mode', 'profile_id', 'runtime_kind')
68
+
69
+
70
+ def _config(event):
71
+ size = event.get('approx_input_tokens')
72
+ bucket = ('up_to_4k' if size <= 4096 else '4k_to_16k' if size <= 16384
73
+ else '16k_to_64k' if size <= 65536 else 'over_64k') if type(size) is int else 'unknown'
74
+ return {**{key: event.get(key) or '' for key in ROUTING_KEYS},
75
+ 'input_bucket': bucket, 'max_tokens': event.get('max_tokens'),
76
+ 'output_limit_kind': event.get('output_limit_kind', 'unknown'), 'tool_count': event.get('tool_count')}
77
+
78
+
79
+ def _known(config):
80
+ # Observed provider-default requests form their own cohort. Their actual
81
+ # server cap stays unknown; missing legacy telemetry never joins it.
82
+ output_known = ((config.get('output_limit_kind') == 'request'
83
+ and type(config.get('max_tokens')) is int and config['max_tokens'] > 0)
84
+ or (config.get('output_limit_kind') == 'provider_default'
85
+ and config.get('max_tokens') is None))
86
+ return (all(config.get(key) and config[key] != 'unknown' for key in (*ROUTING_KEYS, 'input_bucket'))
87
+ and output_known
88
+ and type(config.get('tool_count')) is int)
89
+
90
+
91
+
92
+ def _processor(data, config, version=LEGACY_VERSION):
93
+ requests = data['requests']
94
+ complete = bool(requests) and not data['incomplete']
95
+ configuration_matches = _known(config)
96
+ served = set()
97
+ errors = 0
98
+ input_buckets, tool_counts = [], []
99
+ for pair in requests.values():
100
+ start = pair.get('start')
101
+ terminal = pair.get('response') or pair.get('error')
102
+ complete &= bool(start and terminal and not (pair.get('response') and pair.get('error')))
103
+ for event in pair.values():
104
+ configuration_matches &= all(event.get(key) == config[key] for key in ROUTING_KEYS)
105
+ if start:
106
+ observed = _config(start)
107
+ if version == LEGACY_VERSION:
108
+ configuration_matches &= observed == config
109
+ else:
110
+ # Initial workload features select the cohort. Their ordinary
111
+ # evolution must not censor a longer completed execution.
112
+ configuration_matches &= (_known(observed) and all(
113
+ observed[key] == config[key]
114
+ for key in (*ROUTING_KEYS, 'max_tokens', 'output_limit_kind')))
115
+ input_buckets.append(observed['input_bucket'])
116
+ tool_counts.append(observed['tool_count'])
117
+ response = pair.get('response')
118
+ if response:
119
+ if response.get('response_model'):
120
+ served.add(response['response_model'])
121
+ else:
122
+ complete = False
123
+ errors += bool(pair.get('error'))
124
+ # A response model is a provider-reported label, not weight attestation.
125
+ # Aliases and fallback never borrow the requested label. The original
126
+ # forecast separately freezes the historical served-model stratum.
127
+ return {'served_models': sorted(served), 'served_model': next(iter(served)) if len(served) == 1 else None,
128
+ 'complete_observed_pairs': bool(complete), 'observed_request_count': len(requests),
129
+ 'error_request_count': errors, 'configuration': config,
130
+ 'conditions_comparable': bool(complete and configuration_matches and len(served) == 1),
131
+ 'configuration_matches': bool(configuration_matches),
132
+ **({'workload_evolution': {'input_buckets': list(dict.fromkeys(input_buckets)),
133
+ 'tool_counts': list(dict.fromkeys(tool_counts))}}
134
+ if version == VERSION else {}),
135
+ 'coverage': 'registered callback pairs only; auxiliary or dropped hooks may be absent'}
136
+
137
+
138
+ def _receipt(ledger, owner, execution_id, kind, facts, occurred_at):
139
+ source = 'execution-runtime:'+_digest({'execution_id': execution_id, 'kind': kind})
140
+ # Empty content intentionally produces zero lexical/vector chunks. Runtime
141
+ # facts remain exact canonical source metadata, not conversational memory.
142
+ with closing(ledger._connect()) as db:
143
+ previous = db.execute('SELECT messages_json FROM turn_sources WHERE turn_id=?', (source,)).fetchone()
144
+ if previous:
145
+ retained = json.loads(previous[0])[0].get('_execution_runtime_facts', {})
146
+ if (retained.get('version'), retained.get('execution_id'), retained.get('kind')) != (facts['version'], execution_id, facts['kind']):
147
+ raise ValueError('execution evidence binding conflict')
148
+ facts = retained
149
+ else:
150
+ ledger.record_source(source, contact_id=owner, session_id='execution-runtime:'+execution_id,
151
+ occurred_at=datetime.fromtimestamp(occurred_at, timezone.utc).isoformat(),
152
+ messages=[{'role': 'assistant', 'content': '', '_execution_runtime_facts': facts}], derive_claims=False)
153
+ refs = ledger.source_references([source], contact_id=owner, session_id='')
154
+ if not refs or ledger.is_projection_erased(source):
155
+ raise ValueError('execution evidence unavailable')
156
+ return {'receipt:'+source: refs[0]['source_version']}, facts
157
+
158
+
159
+ def _facts(ledger, outcome):
160
+ with closing(ledger._connect()) as db:
161
+ row = db.execute('SELECT messages_json FROM turn_sources WHERE turn_id=?',
162
+ (outcome['receipt_ref'].removeprefix('receipt:'),)).fetchone()
163
+ return json.loads(row[0])[0].get('_execution_runtime_facts', {}) if row else {}
164
+
165
+
166
+ def _fid(execution_id):
167
+ return 'execution-duration:'+execution_id
168
+
169
+
170
+ def observe(registry, execution_id, owner):
171
+ parts = _parts(registry, owner)
172
+ if parts is None:
173
+ return None
174
+ store, ledger = parts
175
+ row, data = _read(registry, execution_id)
176
+ if row is None or row['contact_id'] != owner:
177
+ return None
178
+ first = data.get('first_api', {})
179
+ history = store.forecast_history(_fid(execution_id))
180
+ if not history['forecasts']:
181
+ # Never issue from a terminal callback, a late first response, or a
182
+ # preexisting execution whose sequence-1 start was not observed.
183
+ if (row['state'] != 'observed' or not first.get('eligible_start')
184
+ or row['sequence'] != first['sequence'] or row['phase'] != 'model'):
185
+ return {'status': 'no_prospective_forecast', 'suggestion_enabled': False}
186
+ config = _config(first['event'])
187
+ prefix = 'hermes-execution:' if VERSION == LEGACY_VERSION else 'hermes-execution-v2:'
188
+ cohort = prefix+row['platform']+(':'+ 'child' if row['parent_execution_id'] else ':root')+':'+_digest(config)[:16]
189
+ method = DURATION_METHOD_V1 if VERSION == LEGACY_VERSION else DURATION_METHOD_V2
190
+ training_model = [None]
191
+ def current(prediction, outcome):
192
+ facts = _facts(ledger, outcome)
193
+ processor = facts.get('processor', {})
194
+ duration = facts.get('duration_seconds')
195
+ eligible = (_known(config) and prediction.detail['method'] == method
196
+ and prediction.detail['model_provenance']['capabilities'] == config
197
+ and facts.get('version') == VERSION and facts.get('observation_eligible') is True
198
+ and outcome['status'] == 'observed' and outcome['value'] is True
199
+ and processor.get('configuration') == config
200
+ and (VERSION == LEGACY_VERSION or (type(duration) in (int, float)
201
+ and math.isfinite(duration) and duration > 0))
202
+ and _current(ledger, prediction.evidence_refs, prediction.detail['source_versions'], owner)
203
+ and _current(ledger, outcome['evidence_refs'], outcome['source_versions'], owner))
204
+ if not eligible:
205
+ return False
206
+ # The estimator visits newest eligible outcomes first. Freeze one
207
+ # historical actual processor, not an assumed requested alias.
208
+ if training_model[0] is None:
209
+ training_model[0] = processor['served_model']
210
+ return processor['served_model'] == training_model[0]
211
+ now = registry.clock()
212
+ estimate = store.estimate_duration(domain='task_duration', cohort=cohort, subject_person_id=owner,
213
+ viewer_scope='owner', prior_seconds=PRIOR_SECONDS, now=now, evidence_is_current=current,
214
+ method=method)
215
+ estimate['training_served_model'] = training_model[0]
216
+ # Origin is server receipt time, not the host's earlier callback clock.
217
+ origin = first['observed_at']
218
+ if origin + estimate['seconds'] <= now:
219
+ return {'status': 'observation_too_old', 'suggestion_enabled': False}
220
+ facts = {'version': VERSION, 'kind': 'prospective', 'execution_id': execution_id,
221
+ 'measurement': MEASUREMENT, 'configuration': config, 'estimate': estimate,
222
+ 'first_api_observed_at': origin, 'issued_at': now, 'parent_execution_id': row['parent_execution_id']}
223
+ versions, retained = _receipt(ledger, owner, execution_id, 'prospective', facts, now)
224
+ estimate, config = retained['estimate'], retained['configuration']
225
+ now = registry.clock()
226
+ if origin + estimate['seconds'] <= now:
227
+ return {'status': 'observation_too_old', 'suggestion_enabled': False}
228
+ store.issue_forecast(forecast_id=_fid(execution_id), subject='execution:'+execution_id,
229
+ domain='task_duration', expectation='The observed Hermes execution reaches completion within its remaining-duration estimate',
230
+ confidence=estimate['confidence'], horizon=origin+estimate['seconds'], origin_at=origin, issued_at=now,
231
+ evidence_refs=list(versions), source_versions=versions, source_kind='task_receipt', cohort=cohort,
232
+ method=estimate['method'], model_provenance={'requested_role': None, 'served_model': None,
233
+ 'model_revision': None, 'capabilities': config, 'fallback': None},
234
+ subject_person_id=owner, viewer_scope='owner', shareability='owner_private',
235
+ conditions={'measurement': MEASUREMENT, 'estimate': estimate, 'prior_is_slo': False,
236
+ **({'observation_version': VERSION} if VERSION != LEGACY_VERSION else {})})
237
+ return {'status': 'issued', 'forecast_id': _fid(execution_id), 'suggestion_enabled': False}
238
+ if row['state'] == 'observed' or history['outcomes']:
239
+ return {'status': 'pending' if not history['outcomes'] else 'observed', 'suggestion_enabled': False}
240
+ prediction = history['forecasts'][0]
241
+ version = prediction['detail']['conditions'].get('observation_version', LEGACY_VERSION)
242
+ processor = _processor(data, prediction['detail']['model_provenance']['capabilities'], version)
243
+ ended = row['last_observed_at']
244
+ valid = _current(ledger, prediction['evidence_refs'], prediction['detail']['source_versions'], owner)
245
+ eligible = bool(row['state'] == 'completed' and valid and processor['conditions_comparable']
246
+ and prediction['created_at'] <= ended and ended >= prediction['detail']['origin_at'])
247
+ training_model = prediction['detail']['conditions']['estimate'].get('training_served_model')
248
+ comparable = bool(eligible and (training_model is None or training_model == processor['served_model']))
249
+ facts = {'version': version, 'kind': 'terminal', 'execution_id': execution_id, 'measurement': MEASUREMENT,
250
+ 'state': row['state'], 'ended_at': ended, 'processor': processor,
251
+ 'conditions_comparable': comparable, 'observation_eligible': eligible, 'quality_evaluated': False,
252
+ 'duration_seconds': ended-prediction['detail']['origin_at'],
253
+ 'parent_execution_id': row['parent_execution_id']}
254
+ versions, retained = _receipt(ledger, owner, execution_id, 'terminal', facts, ended)
255
+ comparable = bool(comparable and retained['conditions_comparable'])
256
+ eligible = bool(eligible and retained['observation_eligible'])
257
+ # A failed/cancelled/incomplete observation is retained without training
258
+ # the estimator. There is no inference of successful external effects.
259
+ reason = 'source_retracted' if not valid else 'cancelled' if row['state'] == 'interrupted' else 'unavailable'
260
+ store.record_forecast_outcome(forecast_id=_fid(execution_id), receipt_ref=next(iter(versions)),
261
+ evidence_refs=list(versions), source_versions=versions, source_kind='task_receipt', observed_at=ended,
262
+ recorded_at=registry.clock(), subject_person_id=owner, viewer_scope='owner', shareability='owner_private',
263
+ status='observed' if eligible else 'censored', value=True if eligible else None, reason='' if eligible else reason)
264
+ return {'status': 'observed', 'conditions_comparable': comparable, 'suggestion_enabled': False}
265
+
266
+
267
+ def safe_reconcile(registry, owner):
268
+ """Replay a bounded set of durable terminals on callbacks and owner reads.
269
+
270
+ The marker is only an optimization in the existing operational metadata.
271
+ A crash before it commits replays the same immutable outcome receipt.
272
+ No terminal row can issue a retrospective forecast.
273
+ """
274
+ try:
275
+ if _parts(registry, owner) is None:
276
+ return
277
+ with closing(registry.ledger._connect()) as db:
278
+ rows = db.execute(
279
+ "SELECT e.execution_id FROM execution_observations e "
280
+ "JOIN execution_runtime_observations r USING(execution_id) "
281
+ "WHERE e.contact_id=? AND e.state!='observed' "
282
+ "AND e.last_observed_at>=? "
283
+ "AND json_extract(r.metadata_json,'$.forecast_settled') IS NULL "
284
+ "ORDER BY e.last_observed_at,e.execution_id LIMIT 20",
285
+ (owner, registry.clock()-7*86400)).fetchall()
286
+ for row in rows:
287
+ safe_observe(registry, row['execution_id'], owner)
288
+ except Exception as error:
289
+ logger.warning('Execution duration reconciliation unavailable: %s', type(error).__name__)
290
+
291
+
292
+ def safe_observe(registry, execution_id, owner):
293
+ try:
294
+ result = observe(registry, execution_id, owner)
295
+ if result and result.get('status') in {'observed', 'no_prospective_forecast'}:
296
+ with closing(registry.ledger._connect()) as db, db:
297
+ db.execute(
298
+ "UPDATE execution_runtime_observations "
299
+ "SET metadata_json=json_set(metadata_json,'$.forecast_settled',1) "
300
+ "WHERE execution_id=? AND execution_id IN "
301
+ "(SELECT execution_id FROM execution_observations "
302
+ "WHERE contact_id=? AND state!='observed')", (execution_id, owner))
303
+ return result
304
+ except Exception as error:
305
+ logger.warning('Execution duration observer unavailable: %s', type(error).__name__)
306
+ return {'status': 'unavailable', 'error_type': type(error).__name__, 'suggestion_enabled': False}
307
+
308
+
309
+ def project(registry, execution_id, owner):
310
+ try:
311
+ parts = _parts(registry, owner)
312
+ if parts is None:
313
+ return {'status': 'disabled', 'suggestion_enabled': False}
314
+ store, ledger = parts
315
+ history = store.forecast_history(_fid(execution_id))
316
+ if not history['forecasts']:
317
+ return {'status': 'no_prospective_forecast', 'suggestion_enabled': False}
318
+ prediction = history['forecasts'][0]
319
+ if not _current(ledger, prediction['evidence_refs'], prediction['detail']['source_versions'], owner):
320
+ return {'status': 'source_unavailable', 'suggestion_enabled': False}
321
+ estimate = prediction['detail']['conditions']['estimate']
322
+ result = {'status': 'shadow', 'forecast_id': _fid(execution_id), 'measurement': MEASUREMENT,
323
+ 'original_horizon': prediction['horizon'], 'prior_horizon': prediction['detail']['origin_at']+PRIOR_SECONDS,
324
+ 'sample_n': estimate['sample_n'], 'uncertain': estimate['uncertain'],
325
+ 'method': prediction['detail']['method'], 'cohort': prediction['cohort'],
326
+ 'observation_version': prediction['detail']['conditions'].get('observation_version', LEGACY_VERSION),
327
+ 'suggestion_enabled': False, 'quality_evaluated': False, 'conditions_comparable': False}
328
+ if 'clamped_comparison_seconds' in estimate:
329
+ result['clamped_comparison_horizon'] = prediction['detail']['origin_at']+estimate['clamped_comparison_seconds']
330
+ if history['outcomes']:
331
+ outcome = history['outcomes'][-1]
332
+ facts = _facts(ledger, outcome)
333
+ result.update(outcome_status=outcome['status'], processor=facts.get('processor'),
334
+ conditions_comparable=bool(facts.get('conditions_comparable') and
335
+ _current(ledger, outcome['evidence_refs'], outcome['source_versions'], owner)))
336
+ if result['conditions_comparable']:
337
+ ended = outcome['observed_at']
338
+ result['comparison'] = {'duration_seconds': facts['duration_seconds'],
339
+ 'forecast_absolute_error_seconds': abs(ended-result['original_horizon']),
340
+ 'prior_absolute_error_seconds': abs(ended-result['prior_horizon']),
341
+ 'forecast_premature_inspection': result['original_horizon'] < ended,
342
+ 'prior_premature_inspection': result['prior_horizon'] < ended,
343
+ 'forecast_inspection_lateness_seconds': max(0., result['original_horizon']-ended),
344
+ 'prior_inspection_lateness_seconds': max(0., result['prior_horizon']-ended)}
345
+ if 'clamped_comparison_horizon' in result:
346
+ clamped = result['clamped_comparison_horizon']
347
+ result['comparison'].update(
348
+ clamped_absolute_error_seconds=abs(ended-clamped),
349
+ clamped_premature_inspection=clamped < ended,
350
+ clamped_inspection_lateness_seconds=max(0., clamped-ended))
351
+ return result
352
+ except Exception:
353
+ return {'status': 'unavailable', 'suggestion_enabled': False}