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,209 @@
1
+ """Finite cases using actual memory formation and scoped lexical recollection.
2
+
3
+ The candidate serves extraction. The configured judging role is a supporting
4
+ consumer dependency, recorded by the runner; no role is silently rebound here.
5
+ These are not native conversation or base-Hermes comparison results.
6
+ """
7
+ from contextlib import closing
8
+ from datetime import datetime
9
+ import json
10
+
11
+ from .records import CaseSpec
12
+
13
+
14
+ def _recollect(projection, query, *, contact, session, now, max_chars):
15
+ from apsimo.beliefs.source_time import interpret_time_query
16
+ from apsimo.intelligence.graph.recall import pack_memory_context
17
+ from apsimo.turns.source_annotations import expand, current_candidates
18
+ ledger = projection.ledger
19
+ hits = ledger.search_sources(query, contact_id=contact, session_id=session, limit=10)
20
+ _, rows = projection.prepare_context([], hits, contact_id=contact, session_id=session,
21
+ time_query=interpret_time_query(query, now=datetime.fromisoformat(now), timezone_name='UTC'))
22
+ rows = expand(ledger, rows, contact_id=contact, session_id=session)
23
+ rows = current_candidates(ledger, rows, contact_id=contact, session_id=session)
24
+ selected, body = pack_memory_context(rows, limit=5, max_chars=max_chars)
25
+ refs = {ref['source_id']: ref for row in selected for ref in row.get('_annotation_source_refs', [])}
26
+ return {'body': body, 'selected': selected, 'citations': list(refs.values())}
27
+
28
+
29
+ async def source_memory(inputs, context):
30
+ """One projection attempt per input, then reopen the same isolated ledger."""
31
+ from apsimo.beliefs.source_projection import SourceClaimProjection
32
+ from apsimo.turns import TurnIdempotencyLedger
33
+ turns = inputs['turns']
34
+ if not 1 <= len(turns) <= 8 or len({t['id'] for t in turns}) != len(turns):
35
+ raise ValueError('Memory qualification needs1..8 distinct declared sources')
36
+ ledger = TurnIdempotencyLedger(context.state_dir/'turn-idempotency.db')
37
+ projection = SourceClaimProjection(ledger)
38
+ recorded, process_calls = [], 0
39
+ for turn in turns:
40
+ if not isinstance(turn['text'], str) or not 1 <= len(turn['text']) <= 2000:
41
+ raise ValueError('Source input must be bounded text')
42
+ assert ledger.record_source(turn['id'], contact_id=inputs['contact_id'],
43
+ session_id=turn['session_id'], occurred_at=turn['occurred_at'],
44
+ messages=[{'role': 'user', 'content': turn['text']}])
45
+ recorded.append(turn['id'])
46
+ process_calls += 1
47
+ await projection.process_one(context.router)
48
+ job = next(row for row in projection.status(inputs['contact_id']) if row['turn_id'] == turn['id'])
49
+ if job['status'] != 'complete':
50
+ break # Retain the failed first attempt; never wait/retry its queue.
51
+ refs_before = ledger.source_references(recorded, contact_id=inputs['contact_id'],
52
+ session_id=inputs['recall_session'])
53
+ reopened = SourceClaimProjection(TurnIdempotencyLedger(ledger.db_path))
54
+ refs_after = reopened.ledger.source_references(recorded, contact_id=inputs['contact_id'],
55
+ session_id=inputs['recall_session'])
56
+ with closing(reopened.ledger._connect()) as db:
57
+ claims = [dict(json.loads(row['data_json']), **{k: row[k] for k in (
58
+ 'id', 'turn_id', 'message_hash', 'retracted_by', 'superseded_by')})
59
+ for row in db.execute('SELECT * FROM source_claims ORDER BY turn_id,id')]
60
+ sources = [{'id': row['turn_id'], 'contact_id': row['contact_id'], 'session_id': row['session_id'],
61
+ 'messages': json.loads(row['messages_json'])}
62
+ for row in db.execute('SELECT * FROM turn_sources ORDER BY turn_id')]
63
+ recall = _recollect(reopened, inputs['query'], contact=inputs['contact_id'],
64
+ session=inputs['recall_session'], now=inputs['now'], max_chars=inputs['max_chars'])
65
+ effects = {'recorded_source_ids': recorded, 'process_invocations': process_calls,
66
+ 'reopened_source_versions_equal': refs_before == refs_after,
67
+ 'source_references': refs_after, 'recall_session': inputs['recall_session'],
68
+ 'supporting_roles': ['judging'],
69
+ 'boundary': 'canonical_source_formation_and_lexical_recollection',
70
+ 'native_request_and_answer': 'not_exercised',
71
+ 'semantic_embedding_and_reranking': 'not_exercised'}
72
+ context.observe({'boundary': effects['boundary'], 'sources_recorded': len(recorded),
73
+ 'projection_invocations': process_calls, 'supporting_roles': ['judging']})
74
+ return {'output': {'sources': sources, 'claims': claims,
75
+ 'jobs': reopened.status(inputs['contact_id']), 'recall': recall}, 'effects': effects}
76
+
77
+
78
+ def _same_text(left, right):
79
+ return isinstance(left, str) and isinstance(right, str) and left.strip().casefold() == right.strip().casefold()
80
+
81
+
82
+ def _fact_matches(row, wanted):
83
+ """Only oracle-authored complete alternatives, never inferred synonyms."""
84
+ return (row.get('turn_id') == wanted['source_id']
85
+ and row.get('memory_quality', {}).get('memory_kind') == wanted['memory_kind']
86
+ and any(all(_same_text(row.get(key), form[key])
87
+ for key in ('subject', 'subject_key', 'predicate', 'value'))
88
+ for form in wanted['representations']))
89
+
90
+
91
+ def memory_outcomes(observed, oracle):
92
+ """Compare effects with independent source facts; never ask a model to grade."""
93
+ output, effects = observed['output'], observed['effects']
94
+ sources = {row['id']: row for row in output['sources']}
95
+ claims = output['claims']
96
+ active = [row for row in claims if not row['retracted_by'] and not row['superseded_by']]
97
+ jobs = output['jobs']
98
+ completed_sources = {row['turn_id'] for row in jobs
99
+ if row['status'] == 'complete' and row['attempts'] == 1}
100
+ checks = {
101
+ 'all_inputs_retained': set(sources) == set(oracle['source_ids']),
102
+ 'formation_first_attempts_complete': len(jobs) == len(oracle['source_ids']) and all(
103
+ row['status'] == 'complete' and row['attempts'] == 1 for row in jobs),
104
+ 'state_reopened_without_losing_sources': effects['reopened_source_versions_equal'],
105
+ 'recall_uses_a_later_session': all(row['session_id'] != effects['recall_session'] for row in sources.values()),
106
+ 'promoted_evidence_is_source_grounded': (all(row['turn_id'] in sources and any(
107
+ row['evidence'] in message['content'] for message in sources[row['turn_id']]['messages'])
108
+ for row in claims) if claims else None),
109
+ }
110
+ no_claim_sources = set(oracle.get('no_claim_sources', []))
111
+ if no_claim_sources:
112
+ # Unsubmitted or unfinished sources cannot demonstrate abstention.
113
+ # An observed bad promotion is still a failure even if another source
114
+ # was never reached. Do not silently remove either from the denominator.
115
+ checks['junk_and_fiction_not_promoted'] = (
116
+ False if any(row['turn_id'] in no_claim_sources for row in claims) else
117
+ True if no_claim_sources <= (set(sources) & completed_sources) else None)
118
+ for wanted in oracle['claims']:
119
+ matches = [row for row in active if _fact_matches(row, wanted)]
120
+ checks[wanted['name']] = bool(matches)
121
+ checks[wanted['name']+'_conditions_preserved'] = bool(matches) and all(
122
+ all(term in row['evidence'] for term in wanted.get('evidence_contains', [])) for row in matches)
123
+ selected = output['recall']['selected']
124
+ current_assertions = []
125
+ by_id = {row['id']: row for row in claims}
126
+ selected_fields_valid = True
127
+ for row in selected:
128
+ if row.get('content_format') == 'source_assertions_v1':
129
+ card = json.loads(row['content'])
130
+ for assertion in card.get('assertions', []):
131
+ retained = by_id.get(assertion.get('claim_id'))
132
+ if (retained is None or assertion.get('source') != 'turn:' + retained['turn_id']
133
+ or assertion.get('value') != retained['value']
134
+ or card.get('subject') != retained['subject']
135
+ or card.get('predicate') != retained['predicate']):
136
+ selected_fields_valid = False
137
+ else:
138
+ current_assertions.append(retained)
139
+ if oracle.get('correction'):
140
+ corrected = oracle['correction']
141
+ old = [row for row in claims if _fact_matches(row, corrected['old_fact'])]
142
+ new = [row for row in active if _fact_matches(row, corrected['new_fact'])]
143
+ checks['explicit_correction_retracts_prior_claim'] = bool(old and new) and any(
144
+ row.get('operation') == 'correct' and row.get('prior_claim_id') == previous['id']
145
+ and previous['retracted_by'] == row['id'] for row in new for previous in old)
146
+ checks['corrected_value_selected'] = selected_fields_valid and any(
147
+ _fact_matches(row, corrected['new_fact']) and row in active for row in current_assertions)
148
+ checks['obsolete_value_not_selected_as_current'] = (not any(
149
+ _fact_matches(row, corrected['old_fact']) for row in current_assertions)
150
+ if selected_fields_valid else None)
151
+ checks['useful_content_recollected'] = all(term in output['recall']['body'] for term in oracle['recall_contains'])
152
+ checks['recollection_has_canonical_lineage'] = bool(output['recall']['citations']) and all(
153
+ row in effects['source_references'] for row in output['recall']['citations'])
154
+ return checks
155
+
156
+
157
+ # Finite independently authored oracle data. Articles and the two equivalent
158
+ # storage predicates are listed here, not guessed from a returned model value.
159
+ _LOCATION_REPRESENTATIONS = {
160
+ place: [{'subject': subject, 'subject_key': subject_key, 'predicate': predicate, 'value': value}
161
+ for subject, subject_key in (('spare sensor', 'spare sensor'), ('The spare sensor', 'the spare sensor'))
162
+ for predicate in ('location', 'storage location') for value in values]
163
+ for place, values in {'old': ('cedar drawer', 'the cedar drawer'),
164
+ 'new': ('amber cabinet', 'the amber cabinet')}.items()}
165
+ _OLD_LOCATION = {'source_id': 'turn-a', 'memory_kind': 'personal_context',
166
+ 'representations': _LOCATION_REPRESENTATIONS['old']}
167
+ _NEW_LOCATION = {'source_id': 'turn-b', 'memory_kind': 'personal_context',
168
+ 'representations': _LOCATION_REPRESENTATIONS['new']}
169
+
170
+
171
+ CASES = [
172
+ CaseSpec(id='memory.formation-quality', version='4', role='extraction', boundary='cognition_consumer',
173
+ target_tasks=('source_claim_extraction',),
174
+ consumer='source_memory', evaluator='memory_outcomes', timeout_seconds=240,
175
+ inputs={'contact_id': 'person', 'recall_session': 'later-conversation',
176
+ 'query': 'tea preference after evening caffeine', 'now': '2026-04-10T12:00:00+00:00', 'max_chars': 6000,
177
+ 'turns': [
178
+ {'id': 'turn-a', 'session_id': 'conversation-a', 'occurred_at': '2026-04-01T12:00:00+00:00',
179
+ 'text': 'I prefer decaffeinated tea after 18:00 because caffeine keeps me awake.'},
180
+ {'id': 'turn-b', 'session_id': 'conversation-b', 'occurred_at': '2026-04-02T12:00:00+00:00',
181
+ 'text': 'Thanks, okay.'},
182
+ {'id': 'turn-c', 'session_id': 'conversation-c', 'occurred_at': '2026-04-03T12:00:00+00:00',
183
+ 'text': 'In a story I am drafting, I own a silver moonboat.'}]},
184
+ oracle={'source_ids': ['turn-a', 'turn-b', 'turn-c'], 'no_claim_sources': ['turn-b', 'turn-c'],
185
+ 'claims': [{'name': 'useful_conditional_preference_formed', 'source_id': 'turn-a',
186
+ 'memory_kind': 'preference', 'representations': [
187
+ {'subject': 'I', 'subject_key': 'speaker', 'predicate': 'tea preference', 'value': value}
188
+ for value in ('decaffeinated', 'decaffeinated tea')],
189
+ 'evidence_contains': ['after 18:00', 'caffeine keeps me awake']}],
190
+ 'recall_contains': ['decaffeinated tea', 'after 18:00']}),
191
+ CaseSpec(id='memory.corrected-recollection', version='4', role='extraction', boundary='cognition_consumer',
192
+ target_tasks=('source_claim_extraction',),
193
+ consumer='source_memory', evaluator='memory_outcomes', timeout_seconds=180,
194
+ inputs={'contact_id': 'person', 'recall_session': 'later-conversation',
195
+ 'query': 'Where is the spare sensor stored?', 'now': '2026-04-10T12:00:00+00:00', 'max_chars': 6000,
196
+ 'turns': [
197
+ {'id': 'turn-a', 'session_id': 'conversation-a', 'occurred_at': '2026-04-01T12:00:00+00:00',
198
+ 'text': 'The spare sensor is stored in the cedar drawer.'},
199
+ {'id': 'turn-b', 'session_id': 'conversation-b', 'occurred_at': '2026-04-02T12:00:00+00:00',
200
+ 'text': 'Correction: the spare sensor is stored in the amber cabinet, not the cedar drawer.'}]},
201
+ oracle={'source_ids': ['turn-a', 'turn-b'],
202
+ 'claims': [{**_NEW_LOCATION, 'name': 'corrected_location_formed',
203
+ 'evidence_contains': ['Correction:', 'not the cedar drawer']}],
204
+ 'correction': {'old_fact': _OLD_LOCATION, 'new_fact': _NEW_LOCATION},
205
+ 'recall_contains': ['amber cabinet']})
206
+ ]
207
+
208
+ CONSUMERS = {'source_memory': source_memory}
209
+ EVALUATORS = {'memory_outcomes': memory_outcomes}
@@ -0,0 +1,92 @@
1
+ """Small versioned records. Inputs and independent oracles stay separate."""
2
+ from dataclasses import asdict, dataclass
3
+ import hashlib
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import re
8
+ import tempfile
9
+
10
+ SCHEMA = 1
11
+ BOUNDARIES = {'role_completion', 'cognition_consumer', 'native_hermes', 'retrieval', 'speech', 'media_consumer'}
12
+
13
+
14
+ def encode(value):
15
+ return (json.dumps(value, sort_keys=True, indent=2, ensure_ascii=True, allow_nan=False) + '\n').encode()
16
+
17
+
18
+ def digest(value):
19
+ return hashlib.sha256(encode(value)).hexdigest()
20
+
21
+
22
+ def read(path):
23
+ path = Path(path)
24
+ if path.is_symlink() or path.stat().st_size > 8 * 1024 * 1024:
25
+ raise ValueError('Invalid qualification record')
26
+ return json.loads(path.read_bytes())
27
+
28
+
29
+ def write_once(path, value):
30
+ """Publish complete bytes without replacing an earlier outcome."""
31
+ publish(path, encode(value))
32
+
33
+
34
+ def publish(path, raw):
35
+ """Atomically publish a new private record or derived text report."""
36
+ path = Path(path)
37
+ path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
38
+ fd, temporary = tempfile.mkstemp(prefix='.record-', dir=path.parent)
39
+ try:
40
+ with os.fdopen(fd, 'wb') as stream:
41
+ stream.write(raw)
42
+ stream.flush()
43
+ os.fsync(stream.fileno())
44
+ os.link(temporary, path)
45
+ directory = os.open(path.parent, os.O_RDONLY)
46
+ try:
47
+ os.fsync(directory)
48
+ finally:
49
+ os.close(directory)
50
+ finally:
51
+ os.unlink(temporary)
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class CaseSpec:
56
+ id: str
57
+ version: str
58
+ role: str
59
+ boundary: str
60
+ consumer: str
61
+ evaluator: str
62
+ inputs: dict
63
+ oracle: dict
64
+ required_capabilities: tuple[str, ...] = ()
65
+ timeout_seconds: float = 60
66
+ max_output_bytes: int = 262144
67
+ provenance: str = 'public'
68
+ target_tasks: tuple[str, ...] = ()
69
+
70
+ def record(self):
71
+ if not re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9_.-]{0,99}', self.id):
72
+ raise ValueError('Invalid case ID')
73
+ if self.boundary not in BOUNDARIES or self.provenance not in {'public', 'private'}:
74
+ raise ValueError('Invalid case boundary/provenance')
75
+ if not self.version or not self.role or not self.consumer or not self.evaluator:
76
+ raise ValueError('Case identity is incomplete')
77
+ if isinstance(self.timeout_seconds, bool) or not .01 <= self.timeout_seconds <= 600:
78
+ raise ValueError('Case deadline must be .01..600 seconds')
79
+ if not 1 <= self.max_output_bytes <= 1024 * 1024:
80
+ raise ValueError('Case output bound must be 1..1048576 bytes')
81
+ if not isinstance(self.inputs, dict) or not isinstance(self.oracle, dict):
82
+ raise ValueError('Case input and oracle must be objects')
83
+ if (not isinstance(self.target_tasks, tuple) or len(self.target_tasks) > 16
84
+ or any(not isinstance(task, str) or not re.fullmatch(r'[a-z][a-z0-9_]{0,99}', task)
85
+ for task in self.target_tasks)
86
+ or len(set(self.target_tasks)) != len(self.target_tasks)):
87
+ raise ValueError('Target tasks must be distinct bounded task names')
88
+ value = asdict(self)
89
+ value['required_capabilities'] = list(self.required_capabilities)
90
+ value['target_tasks'] = list(self.target_tasks)
91
+ return {**value, 'sha256': digest(value), 'inputs_sha256': digest(self.inputs),
92
+ 'oracle_sha256': digest(self.oracle)}
@@ -0,0 +1,87 @@
1
+ """Descriptive results by consumer boundary; all declared cases count."""
2
+ from collections import Counter, defaultdict
3
+ from pathlib import Path
4
+ import statistics
5
+
6
+ from .records import read
7
+
8
+ OUTCOMES = {'pass', 'fail', 'unsupported', 'setup_error', 'error', 'timeout', 'interrupted', 'not_run'}
9
+
10
+
11
+ def summarize(directory):
12
+ directory = Path(directory)
13
+ run = read(directory / 'run.json')
14
+ rows, groups = [], defaultdict(list)
15
+ for case in run['cases']:
16
+ path = directory / 'attempts' / case['id'] / 'result.json'
17
+ result = read(path) if path.exists() else {'outcome': 'not_run', 'primary_outcome': 'unverified', 'elapsed_ms': None}
18
+ if path.exists() and (result.get('run_id') != run['id'] or result.get('case_sha256') != case['sha256']):
19
+ raise ValueError('Result identity does not match declared case')
20
+ if result['outcome'] not in OUTCOMES:
21
+ raise ValueError('Unknown outcome')
22
+ row = {'case_id': case['id'], 'role': case['role'], 'boundary': case['boundary'],
23
+ 'case_sha256': case['sha256'], 'inputs_sha256': case['inputs_sha256'],
24
+ 'oracle_sha256': case['oracle_sha256'],
25
+ 'evaluator_identity': run.get('evaluator_identities', {}).get(case['evaluator']), **result}
26
+ rows.append(row)
27
+ groups[(case['role'], case['boundary'])].append(row)
28
+ summaries = []
29
+ for (role, boundary), items in sorted(groups.items()):
30
+ durations = {}
31
+ for outcome in sorted({r['outcome'] for r in items}):
32
+ times = [r['elapsed_ms'] for r in items if r['outcome'] == outcome and r.get('elapsed_ms') is not None]
33
+ durations[outcome] = {'samples': len(times), 'median_ms': statistics.median(times) if times else None}
34
+ summaries.append({'role': role, 'boundary': boundary, 'declared': len(items),
35
+ 'outcomes': dict(Counter(r['outcome'] for r in items)),
36
+ 'primary_passes': sum(r.get('primary_outcome') == 'pass' for r in items),
37
+ 'duration_by_outcome': durations,
38
+ 'coverage': 'only the listed cases and consumer boundary; not full role qualification'})
39
+ return {'schema': 1, 'kind': 'run', 'run_id': run['id'], 'recipe': run['recipe'],
40
+ 'suite_sha256': run['suite_sha256'], 'evidence_mode': run['evidence_mode'],
41
+ 'implementation_sha256': run.get('implementation_sha256'),
42
+ 'declared': len(rows), 'groups': summaries, 'cases': rows}
43
+
44
+
45
+ def compare(left, right):
46
+ before, after = summarize(left), summarize(right)
47
+ old = {r['case_id']: r for r in before['cases']}
48
+ new = {r['case_id']: r for r in after['cases']}
49
+ rows = []
50
+ for identity in sorted(old.keys() | new.keys()):
51
+ a, b = old.get(identity), new.get(identity)
52
+ comparable = bool(a and b and a['case_sha256'] == b['case_sha256']
53
+ and a.get('evaluator_identity') is not None
54
+ and a['evaluator_identity'] == b.get('evaluator_identity')
55
+ and before['evidence_mode'] == after['evidence_mode'])
56
+ rows.append({'case_id': identity, 'comparable': comparable,
57
+ 'grading_changed': bool(a and b and a.get('evaluator_identity') != b.get('evaluator_identity')),
58
+ 'before': a['outcome'] if a else 'absent', 'after': b['outcome'] if b else 'absent',
59
+ 'primary_before': a.get('primary_outcome') if a else None,
60
+ 'primary_after': b.get('primary_outcome') if b else None,
61
+ 'elapsed_ms_delta': (b['elapsed_ms'] - a['elapsed_ms']) if comparable
62
+ and a['outcome'] == b['outcome'] == 'pass'
63
+ and a.get('elapsed_ms') is not None and b.get('elapsed_ms') is not None else None})
64
+ return {'schema': 1, 'kind': 'comparison', 'before': before, 'after': after, 'cases': rows,
65
+ 'basis': 'recipe comparison; no causal weight, serving identity, tail-latency or confidence claim',
66
+ 'system_implementation_changed': before['implementation_sha256'] != after['implementation_sha256'],
67
+ 'same_suite': before['suite_sha256'] == after['suite_sha256']}
68
+
69
+
70
+ def markdown(report):
71
+ if report['kind'] == 'comparison':
72
+ lines = ['# Recipe comparison', '', report['basis'], '',
73
+ '| Case | Comparable | Before | After | Primary before/after | Elapsed change ms |',
74
+ '| --- | --- | --- | --- | --- | --- |']
75
+ for row in report['cases']:
76
+ lines.append(f"| {row['case_id']} | {row['comparable']} | {row['before']} | {row['after']} | "
77
+ f"{row['primary_before']}/{row['primary_after']} | {row['elapsed_ms_delta']} |")
78
+ else:
79
+ lines = ['# Model qualification observations', '',
80
+ f"Mode: {report['evidence_mode']}. Declared cases: {report['declared']}.",
81
+ 'Coverage is limited to the listed cases and consumer boundaries. Unknown observations remain unknown.', '',
82
+ '| Role | Boundary | All cases | Outcomes | Primary passes | Durations by outcome (median ms, samples) |',
83
+ '| --- | --- | --- | --- | --- | --- |']
84
+ for group in report['groups']:
85
+ lines.append(f"| {group['role']} | {group['boundary']} | {group['declared']} | {group['outcomes']} | "
86
+ f"{group['primary_passes']} | {group['duration_by_outcome']} |")
87
+ return '\n'.join(lines) + '\n'