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
apsimo/tom/arcs.py ADDED
@@ -0,0 +1,896 @@
1
+ """P8 append-only conversational arcs.
2
+
3
+ An arc is not a mutable summary row. It is a deterministic projection of an
4
+ append-only event ledger linking source turns, people, commitments,
5
+ expectations, and projects. Deadlines can make an arc overdue, but only an
6
+ explicit terminal event with evidence can close it.
7
+
8
+ This core has no model calls and no startup or delivery integration. Future
9
+ producers must supply stable event/idempotency identities and server-derived
10
+ scope; future readers must project through an attested ``ViewerContextV1``.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections import Counter
16
+ from dataclasses import dataclass, replace
17
+ from datetime import datetime, timezone
18
+ import hashlib
19
+ import json
20
+ import os
21
+ from pathlib import Path
22
+ import re
23
+ import sqlite3
24
+ import threading
25
+ from typing import Any, Iterable, Optional, Sequence
26
+
27
+ from apsimo.tom.visibility import (
28
+ ViewerContextV1,
29
+ validate_visibility_scope,
30
+ visibility_scope_decision,
31
+ )
32
+
33
+
34
+ SCHEMA_VERSION = 1
35
+ ARC_TYPES = frozenset({
36
+ "promise",
37
+ "open_question",
38
+ "stress_topic",
39
+ "decision",
40
+ "shared_plan",
41
+ "follow_up",
42
+ "unresolved_social_moment",
43
+ })
44
+ EVENT_KINDS = frozenset({
45
+ "open", "link", "hold", "resume", "close", "cancel", "reopen",
46
+ })
47
+ ACTIVE_STATES = frozenset({"open", "held"})
48
+ TERMINAL_STATES = frozenset({"closed", "cancelled"})
49
+
50
+ MAX_PEOPLE = 16
51
+ MAX_REFS_PER_KIND = 64
52
+ MAX_TOPIC_CHARS = 500
53
+ MAX_CLOSURE_REASON_CHARS = 500
54
+ MAX_EVENTS_PER_ARC = 1_024
55
+ MAX_PROJECTED_ARCS = 64
56
+ MAX_PROJECTED_TOPIC_CHARS = 12_000
57
+ MAX_ARCS_SCANNED = 2_000
58
+
59
+ _REF_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:@/+\-]{0,191}$")
60
+
61
+
62
+ class ArcConflictError(ValueError):
63
+ """An event replay changed content or reused another idempotency key."""
64
+
65
+
66
+ def _canonical(value: Any) -> str:
67
+ return json.dumps(
68
+ value,
69
+ ensure_ascii=True,
70
+ separators=(",", ":"),
71
+ sort_keys=True,
72
+ default=str,
73
+ )
74
+
75
+
76
+ def _digest(value: Any) -> str:
77
+ return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
78
+
79
+
80
+ def _ref(value: Any, *, field: str, allow_empty: bool = False) -> str:
81
+ normalized = str(value or "").strip()
82
+ if allow_empty and not normalized:
83
+ return ""
84
+ if not _REF_RE.fullmatch(normalized):
85
+ raise ValueError(f"{field} is not a bounded opaque reference")
86
+ return normalized
87
+
88
+
89
+ def _refs(values: Iterable[Any], *, field: str,
90
+ maximum: int = MAX_REFS_PER_KIND) -> tuple[str, ...]:
91
+ normalized = tuple(sorted(dict.fromkeys(
92
+ _ref(value, field=field) for value in values
93
+ )))
94
+ if len(normalized) > maximum:
95
+ raise ValueError(f"{field} exceeds bounded reference limit")
96
+ return normalized
97
+
98
+
99
+ def _as_utc(value: datetime | str, *, field: str,
100
+ allow_empty: bool = False) -> Optional[datetime]:
101
+ if allow_empty and not value:
102
+ return None
103
+ if isinstance(value, datetime):
104
+ parsed = value
105
+ else:
106
+ try:
107
+ parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
108
+ except (TypeError, ValueError) as exc:
109
+ raise ValueError(f"{field} must be an ISO-8601 timestamp") from exc
110
+ if parsed.tzinfo is None:
111
+ raise ValueError(f"{field} must include a timezone")
112
+ return parsed.astimezone(timezone.utc)
113
+
114
+
115
+ def _iso(value: datetime | str, *, field: str,
116
+ allow_empty: bool = False) -> str:
117
+ parsed = _as_utc(value, field=field, allow_empty=allow_empty)
118
+ return parsed.isoformat() if parsed is not None else ""
119
+
120
+
121
+ def _topic(value: Any) -> str:
122
+ normalized = " ".join(str(value or "").split())
123
+ if not normalized or len(normalized) > MAX_TOPIC_CHARS:
124
+ raise ValueError("arc topic is required and bounded")
125
+ return normalized
126
+
127
+
128
+ @dataclass(frozen=True, slots=True)
129
+ class ArcEventV1:
130
+ event_id: str
131
+ idempotency_key: str
132
+ arc_id: str
133
+ event_kind: str
134
+ source_ref: str
135
+ occurred_at: str
136
+ subject_person_id: str
137
+ arc_type: str = ""
138
+ topic: str = ""
139
+ people: tuple[str, ...] = ()
140
+ turn_refs: tuple[str, ...] = ()
141
+ commitment_refs: tuple[str, ...] = ()
142
+ expectation_refs: tuple[str, ...] = ()
143
+ project_refs: tuple[str, ...] = ()
144
+ evidence_refs: tuple[str, ...] = ()
145
+ viewer_scope: str = ""
146
+ shareability: str = ""
147
+ due_at: str = ""
148
+ next_check_at: str = ""
149
+ closure_reason: str = ""
150
+ sequence: int = 0
151
+ schema_version: int = SCHEMA_VERSION
152
+
153
+ def __post_init__(self) -> None:
154
+ if self.schema_version != SCHEMA_VERSION:
155
+ raise ValueError("unsupported arc event version")
156
+ event_id = _ref(self.event_id, field="event_id")
157
+ idempotency = _ref(self.idempotency_key, field="idempotency_key")
158
+ arc_id = _ref(self.arc_id, field="arc_id")
159
+ source_ref = _ref(self.source_ref, field="source_ref")
160
+ kind = str(self.event_kind or "").strip().lower()
161
+ if kind not in EVENT_KINDS:
162
+ raise ValueError("unknown arc event kind")
163
+ occurred_at = _iso(self.occurred_at, field="occurred_at")
164
+ people = _refs(self.people, field="person", maximum=MAX_PEOPLE)
165
+ turn_refs = _refs(self.turn_refs, field="turn_ref")
166
+ commitment_refs = _refs(
167
+ self.commitment_refs, field="commitment_ref")
168
+ expectation_refs = _refs(
169
+ self.expectation_refs, field="expectation_ref")
170
+ project_refs = _refs(self.project_refs, field="project_ref")
171
+ evidence_refs = _refs(self.evidence_refs, field="evidence_ref")
172
+ due_at = _iso(self.due_at, field="due_at", allow_empty=True)
173
+ next_check_at = _iso(
174
+ self.next_check_at, field="next_check_at", allow_empty=True)
175
+ sequence = int(self.sequence)
176
+ if sequence < 0:
177
+ raise ValueError("arc event sequence cannot be negative")
178
+
179
+ arc_type = str(self.arc_type or "").strip().lower()
180
+ topic = ""
181
+ viewer_scope, shareability, subject = validate_visibility_scope(
182
+ viewer_scope=str(self.viewer_scope or "").strip(),
183
+ shareability=str(self.shareability or "").strip().lower(),
184
+ subject_person_id=self.subject_person_id,
185
+ )
186
+ closure_reason = " ".join(str(self.closure_reason or "").split())
187
+
188
+ if kind == "open":
189
+ if arc_type not in ARC_TYPES:
190
+ raise ValueError("unknown conversational arc type")
191
+ topic = _topic(self.topic)
192
+ if not people:
193
+ raise ValueError("open arc requires at least one person")
194
+ if not turn_refs:
195
+ raise ValueError("open arc requires a source turn")
196
+ if not evidence_refs:
197
+ raise ValueError("open arc requires source evidence")
198
+ if subject not in people:
199
+ raise ValueError("arc subject must be one of its people")
200
+ if closure_reason:
201
+ raise ValueError("open arc cannot carry a closure reason")
202
+ else:
203
+ if arc_type or self.topic:
204
+ raise ValueError(
205
+ "non-open arc events cannot replace arc identity fields")
206
+ if kind == "link" and not any((
207
+ people, turn_refs, commitment_refs, expectation_refs,
208
+ project_refs, evidence_refs, due_at, next_check_at,
209
+ )):
210
+ raise ValueError("link event must add bounded evidence or links")
211
+ if kind in {"hold", "resume", "reopen"} and not evidence_refs:
212
+ raise ValueError(f"{kind} event requires evidence")
213
+ if kind in {"close", "cancel"}:
214
+ if not evidence_refs:
215
+ raise ValueError("arc closure evidence is required")
216
+ if not closure_reason\
217
+ or len(closure_reason) > MAX_CLOSURE_REASON_CHARS:
218
+ raise ValueError("arc closure reason is required and bounded")
219
+ elif closure_reason:
220
+ raise ValueError("only terminal events carry closure reason")
221
+
222
+ object.__setattr__(self, "event_id", event_id)
223
+ object.__setattr__(self, "idempotency_key", idempotency)
224
+ object.__setattr__(self, "arc_id", arc_id)
225
+ object.__setattr__(self, "event_kind", kind)
226
+ object.__setattr__(self, "source_ref", source_ref)
227
+ object.__setattr__(self, "occurred_at", occurred_at)
228
+ object.__setattr__(self, "subject_person_id", subject)
229
+ object.__setattr__(self, "arc_type", arc_type)
230
+ object.__setattr__(self, "topic", topic)
231
+ object.__setattr__(self, "people", people)
232
+ object.__setattr__(self, "turn_refs", turn_refs)
233
+ object.__setattr__(self, "commitment_refs", commitment_refs)
234
+ object.__setattr__(self, "expectation_refs", expectation_refs)
235
+ object.__setattr__(self, "project_refs", project_refs)
236
+ object.__setattr__(self, "evidence_refs", evidence_refs)
237
+ object.__setattr__(self, "viewer_scope", viewer_scope)
238
+ object.__setattr__(self, "shareability", shareability)
239
+ object.__setattr__(self, "due_at", due_at)
240
+ object.__setattr__(self, "next_check_at", next_check_at)
241
+ object.__setattr__(self, "closure_reason", closure_reason)
242
+ object.__setattr__(self, "sequence", sequence)
243
+
244
+ @classmethod
245
+ def open(
246
+ cls,
247
+ *,
248
+ event_id: str,
249
+ idempotency_key: str,
250
+ arc_id: str,
251
+ arc_type: str,
252
+ topic: str,
253
+ people: Sequence[str],
254
+ source_turn_ref: str,
255
+ source_ref: str,
256
+ viewer_scope: str,
257
+ shareability: str,
258
+ subject_person_id: str,
259
+ evidence_refs: Sequence[str],
260
+ occurred_at: str,
261
+ commitment_refs: Sequence[str] = (),
262
+ expectation_refs: Sequence[str] = (),
263
+ project_refs: Sequence[str] = (),
264
+ due_at: str = "",
265
+ next_check_at: str = "",
266
+ ) -> "ArcEventV1":
267
+ return cls(
268
+ event_id=event_id,
269
+ idempotency_key=idempotency_key,
270
+ arc_id=arc_id,
271
+ event_kind="open",
272
+ source_ref=source_ref,
273
+ occurred_at=occurred_at,
274
+ subject_person_id=subject_person_id,
275
+ arc_type=arc_type,
276
+ topic=topic,
277
+ people=tuple(people),
278
+ turn_refs=(source_turn_ref,),
279
+ commitment_refs=tuple(commitment_refs),
280
+ expectation_refs=tuple(expectation_refs),
281
+ project_refs=tuple(project_refs),
282
+ evidence_refs=tuple(evidence_refs),
283
+ viewer_scope=viewer_scope,
284
+ shareability=shareability,
285
+ due_at=due_at,
286
+ next_check_at=next_check_at,
287
+ )
288
+
289
+ @classmethod
290
+ def link(
291
+ cls,
292
+ *,
293
+ event_id: str,
294
+ idempotency_key: str,
295
+ arc_id: str,
296
+ source_ref: str,
297
+ subject_person_id: str,
298
+ viewer_scope: str,
299
+ shareability: str,
300
+ occurred_at: str,
301
+ people: Sequence[str] = (),
302
+ turn_refs: Sequence[str] = (),
303
+ commitment_refs: Sequence[str] = (),
304
+ expectation_refs: Sequence[str] = (),
305
+ project_refs: Sequence[str] = (),
306
+ evidence_refs: Sequence[str] = (),
307
+ due_at: str = "",
308
+ next_check_at: str = "",
309
+ ) -> "ArcEventV1":
310
+ return cls(
311
+ event_id=event_id,
312
+ idempotency_key=idempotency_key,
313
+ arc_id=arc_id,
314
+ event_kind="link",
315
+ source_ref=source_ref,
316
+ occurred_at=occurred_at,
317
+ subject_person_id=subject_person_id,
318
+ people=tuple(people),
319
+ turn_refs=tuple(turn_refs),
320
+ commitment_refs=tuple(commitment_refs),
321
+ expectation_refs=tuple(expectation_refs),
322
+ project_refs=tuple(project_refs),
323
+ evidence_refs=tuple(evidence_refs),
324
+ due_at=due_at,
325
+ next_check_at=next_check_at,
326
+ viewer_scope=viewer_scope,
327
+ shareability=shareability,
328
+ )
329
+
330
+ @classmethod
331
+ def _transition(
332
+ cls,
333
+ kind: str,
334
+ *,
335
+ event_id: str,
336
+ idempotency_key: str,
337
+ arc_id: str,
338
+ source_ref: str,
339
+ subject_person_id: str,
340
+ viewer_scope: str,
341
+ shareability: str,
342
+ evidence_refs: Sequence[str],
343
+ occurred_at: str,
344
+ closure_reason: str = "",
345
+ ) -> "ArcEventV1":
346
+ return cls(
347
+ event_id=event_id,
348
+ idempotency_key=idempotency_key,
349
+ arc_id=arc_id,
350
+ event_kind=kind,
351
+ source_ref=source_ref,
352
+ occurred_at=occurred_at,
353
+ subject_person_id=subject_person_id,
354
+ evidence_refs=tuple(evidence_refs),
355
+ viewer_scope=viewer_scope,
356
+ shareability=shareability,
357
+ closure_reason=closure_reason,
358
+ )
359
+
360
+ @classmethod
361
+ def close(cls, **kwargs: Any) -> "ArcEventV1":
362
+ return cls._transition("close", **kwargs)
363
+
364
+ @classmethod
365
+ def cancel(cls, **kwargs: Any) -> "ArcEventV1":
366
+ return cls._transition("cancel", **kwargs)
367
+
368
+ @classmethod
369
+ def hold(cls, **kwargs: Any) -> "ArcEventV1":
370
+ return cls._transition("hold", **kwargs)
371
+
372
+ @classmethod
373
+ def resume(cls, **kwargs: Any) -> "ArcEventV1":
374
+ return cls._transition("resume", **kwargs)
375
+
376
+ @classmethod
377
+ def reopen(cls, **kwargs: Any) -> "ArcEventV1":
378
+ return cls._transition("reopen", **kwargs)
379
+
380
+ def payload(self) -> dict[str, Any]:
381
+ return {
382
+ "schema_version": self.schema_version,
383
+ "event_id": self.event_id,
384
+ "idempotency_key": self.idempotency_key,
385
+ "arc_id": self.arc_id,
386
+ "event_kind": self.event_kind,
387
+ "source_ref": self.source_ref,
388
+ "occurred_at": self.occurred_at,
389
+ "subject_person_id": self.subject_person_id,
390
+ "arc_type": self.arc_type,
391
+ "topic": self.topic,
392
+ "people": list(self.people),
393
+ "turn_refs": list(self.turn_refs),
394
+ "commitment_refs": list(self.commitment_refs),
395
+ "expectation_refs": list(self.expectation_refs),
396
+ "project_refs": list(self.project_refs),
397
+ "evidence_refs": list(self.evidence_refs),
398
+ "viewer_scope": self.viewer_scope,
399
+ "shareability": self.shareability,
400
+ "due_at": self.due_at,
401
+ "next_check_at": self.next_check_at,
402
+ "closure_reason": self.closure_reason,
403
+ }
404
+
405
+ @property
406
+ def event_digest(self) -> str:
407
+ return _digest(self.payload())
408
+
409
+
410
+ @dataclass(frozen=True, slots=True)
411
+ class Arc:
412
+ arc_id: str
413
+ arc_type: str
414
+ topic: str
415
+ subject_person_id: str
416
+ people: tuple[str, ...]
417
+ viewer_scope: str
418
+ shareability: str
419
+ state: str
420
+ turn_refs: tuple[str, ...]
421
+ commitment_refs: tuple[str, ...]
422
+ expectation_refs: tuple[str, ...]
423
+ project_refs: tuple[str, ...]
424
+ evidence_refs: tuple[str, ...]
425
+ closure_evidence_refs: tuple[str, ...]
426
+ due_at: str
427
+ next_check_at: str
428
+ opened_at: str
429
+ updated_at: str
430
+ closed_at: str
431
+ closure_reason: str
432
+ event_count: int
433
+ revision: int
434
+ history_digest: str
435
+ overdue: bool
436
+ needs_check: bool
437
+ schema_version: int = SCHEMA_VERSION
438
+
439
+ @property
440
+ def active(self) -> bool:
441
+ return self.state in ACTIVE_STATES
442
+
443
+ def visible_to(self, viewer: ViewerContextV1) -> bool:
444
+ allowed, _ = visibility_scope_decision(
445
+ viewer_scope=self.viewer_scope,
446
+ shareability=self.shareability,
447
+ subject_person_id=self.subject_person_id,
448
+ viewer=viewer,
449
+ )
450
+ return allowed
451
+
452
+ def public(self) -> dict[str, Any]:
453
+ return {
454
+ "schema_version": self.schema_version,
455
+ "arc_id": self.arc_id,
456
+ "arc_type": self.arc_type,
457
+ "topic": self.topic,
458
+ "subject_person_id": self.subject_person_id,
459
+ "people": list(self.people),
460
+ "viewer_scope": self.viewer_scope,
461
+ "shareability": self.shareability,
462
+ "state": self.state,
463
+ "turn_refs": list(self.turn_refs),
464
+ "commitment_refs": list(self.commitment_refs),
465
+ "expectation_refs": list(self.expectation_refs),
466
+ "project_refs": list(self.project_refs),
467
+ "evidence_refs": list(self.evidence_refs),
468
+ "closure_evidence_refs": list(self.closure_evidence_refs),
469
+ "due_at": self.due_at,
470
+ "next_check_at": self.next_check_at,
471
+ "opened_at": self.opened_at,
472
+ "updated_at": self.updated_at,
473
+ "closed_at": self.closed_at,
474
+ "closure_reason": self.closure_reason,
475
+ "event_count": self.event_count,
476
+ "revision": self.revision,
477
+ "history_digest": self.history_digest,
478
+ "overdue": self.overdue,
479
+ "needs_check": self.needs_check,
480
+ }
481
+
482
+
483
+ class ArcReducer:
484
+ """Strict deterministic event reducer; invalid history never projects."""
485
+
486
+ @staticmethod
487
+ def reduce(
488
+ events: Sequence[ArcEventV1],
489
+ *,
490
+ now: datetime | str | None = None,
491
+ ) -> Arc:
492
+ if not events:
493
+ raise ValueError("arc reducer requires an open event")
494
+ if len(events) > MAX_EVENTS_PER_ARC:
495
+ raise ValueError("arc event history exceeds bounded limit")
496
+ ordered = list(events)
497
+ if any(event.sequence for event in ordered):
498
+ if not all(event.sequence for event in ordered):
499
+ raise ValueError("arc history mixes sequenced and unsequenced events")
500
+ ordered.sort(key=lambda event: event.sequence)
501
+ if ordered[0].event_kind != "open":
502
+ raise ValueError("arc history must begin with an open event")
503
+ if sum(1 for event in ordered if event.event_kind == "open") != 1:
504
+ raise ValueError("arc history must contain exactly one open event")
505
+ arc_ids = {event.arc_id for event in ordered}
506
+ if len(arc_ids) != 1:
507
+ raise ValueError("arc history cannot mix arc identities")
508
+
509
+ opened = ordered[0]
510
+ state = "open"
511
+ people = set(opened.people)
512
+ turn_refs = set(opened.turn_refs)
513
+ commitment_refs = set(opened.commitment_refs)
514
+ expectation_refs = set(opened.expectation_refs)
515
+ project_refs = set(opened.project_refs)
516
+ evidence_refs = set(opened.evidence_refs)
517
+ closure_evidence: set[str] = set()
518
+ due_at = opened.due_at
519
+ next_check_at = opened.next_check_at
520
+ closed_at = ""
521
+ closure_reason = ""
522
+ updated_at = opened.occurred_at
523
+
524
+ for event in ordered[1:]:
525
+ if (
526
+ event.viewer_scope != opened.viewer_scope
527
+ or event.shareability != opened.shareability
528
+ ):
529
+ raise ValueError(
530
+ "arc event visibility must exactly match its open event")
531
+ if state in TERMINAL_STATES and event.event_kind != "reopen":
532
+ raise ValueError("terminal arc cannot accept non-reopen events")
533
+ kind = event.event_kind
534
+ if kind == "link":
535
+ people.update(event.people)
536
+ if len(people) > MAX_PEOPLE:
537
+ raise ValueError("arc people exceed bounded limit")
538
+ turn_refs.update(event.turn_refs)
539
+ commitment_refs.update(event.commitment_refs)
540
+ expectation_refs.update(event.expectation_refs)
541
+ project_refs.update(event.project_refs)
542
+ evidence_refs.update(event.evidence_refs)
543
+ due_at = event.due_at or due_at
544
+ next_check_at = event.next_check_at or next_check_at
545
+ elif kind == "hold":
546
+ if state != "open":
547
+ raise ValueError("only an open arc can be held")
548
+ state = "held"
549
+ evidence_refs.update(event.evidence_refs)
550
+ elif kind == "resume":
551
+ if state != "held":
552
+ raise ValueError("only a held arc can resume")
553
+ state = "open"
554
+ evidence_refs.update(event.evidence_refs)
555
+ elif kind in {"close", "cancel"}:
556
+ if state not in ACTIVE_STATES:
557
+ raise ValueError("only an active arc can close")
558
+ state = "closed" if kind == "close" else "cancelled"
559
+ closure_evidence.update(event.evidence_refs)
560
+ evidence_refs.update(event.evidence_refs)
561
+ closure_reason = event.closure_reason
562
+ closed_at = event.occurred_at
563
+ elif kind == "reopen":
564
+ if state not in TERMINAL_STATES:
565
+ raise ValueError("only a terminal arc can reopen")
566
+ state = "open"
567
+ evidence_refs.update(event.evidence_refs)
568
+ closed_at = ""
569
+ closure_reason = ""
570
+ else:
571
+ raise ValueError(f"unhandled arc event kind {kind}")
572
+ if event.subject_person_id not in people:
573
+ raise ValueError(
574
+ "arc event subject must be one of the linked people")
575
+ for field, references in (
576
+ ("turn_refs", turn_refs),
577
+ ("commitment_refs", commitment_refs),
578
+ ("expectation_refs", expectation_refs),
579
+ ("project_refs", project_refs),
580
+ ("evidence_refs", evidence_refs),
581
+ ("closure_evidence_refs", closure_evidence),
582
+ ):
583
+ if len(references) > MAX_REFS_PER_KIND:
584
+ raise ValueError(f"arc {field} exceeds bounded limit")
585
+ updated_at = event.occurred_at
586
+
587
+ observed = _as_utc(
588
+ now or datetime.now(timezone.utc), field="now")
589
+ assert observed is not None
590
+ due = _as_utc(due_at, field="due_at", allow_empty=True)
591
+ check = _as_utc(
592
+ next_check_at, field="next_check_at", allow_empty=True)
593
+ active = state in ACTIVE_STATES
594
+ return Arc(
595
+ arc_id=opened.arc_id,
596
+ arc_type=opened.arc_type,
597
+ topic=opened.topic,
598
+ subject_person_id=opened.subject_person_id,
599
+ people=tuple(sorted(people)),
600
+ viewer_scope=opened.viewer_scope,
601
+ shareability=opened.shareability,
602
+ state=state,
603
+ turn_refs=tuple(sorted(turn_refs)),
604
+ commitment_refs=tuple(sorted(commitment_refs)),
605
+ expectation_refs=tuple(sorted(expectation_refs)),
606
+ project_refs=tuple(sorted(project_refs)),
607
+ evidence_refs=tuple(sorted(evidence_refs)),
608
+ closure_evidence_refs=tuple(sorted(closure_evidence)),
609
+ due_at=due_at,
610
+ next_check_at=next_check_at,
611
+ opened_at=opened.occurred_at,
612
+ updated_at=updated_at,
613
+ closed_at=closed_at,
614
+ closure_reason=closure_reason,
615
+ event_count=len(ordered),
616
+ revision=max((event.sequence for event in ordered), default=0)
617
+ or len(ordered),
618
+ history_digest=_digest([
619
+ event.event_digest for event in ordered]),
620
+ overdue=bool(active and due is not None and observed >= due),
621
+ needs_check=bool(active and check is not None and observed >= check),
622
+ )
623
+
624
+
625
+ @dataclass(frozen=True, slots=True)
626
+ class ArcAppendResultV1:
627
+ event: ArcEventV1
628
+ appended: bool
629
+ replayed: bool
630
+
631
+
632
+ @dataclass(frozen=True, slots=True)
633
+ class ArcProjectionBatchV1:
634
+ arcs: tuple[Arc, ...]
635
+ denied_count: int
636
+ corrupt_count: int
637
+ truncated: bool
638
+ viewer_digest: str
639
+ audit_digest: str
640
+
641
+ def public(self) -> dict[str, Any]:
642
+ return {
643
+ "arcs": [arc.public() for arc in self.arcs],
644
+ "denied_count": self.denied_count,
645
+ "corrupt_count": self.corrupt_count,
646
+ "truncated": self.truncated,
647
+ "viewer_digest": self.viewer_digest,
648
+ "audit_digest": self.audit_digest,
649
+ }
650
+
651
+
652
+ class ArcStore:
653
+ """SQLite append-only event ledger with transactional idempotency."""
654
+
655
+ def __init__(self, db_path: str = ":memory:") -> None:
656
+ self.path = db_path
657
+ if db_path != ":memory:":
658
+ path = Path(db_path)
659
+ path.parent.mkdir(parents=True, exist_ok=True)
660
+ if not path.exists():
661
+ path.touch(mode=0o600)
662
+ else:
663
+ os.chmod(path, 0o600)
664
+ self._lock = threading.RLock()
665
+ self._conn = sqlite3.connect(
666
+ db_path, check_same_thread=False, timeout=30.0)
667
+ self._conn.row_factory = sqlite3.Row
668
+ self._conn.execute("PRAGMA busy_timeout=30000")
669
+ self._conn.execute("PRAGMA journal_mode=WAL")
670
+ self._conn.executescript(
671
+ """
672
+ CREATE TABLE IF NOT EXISTS arc_events (
673
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
674
+ event_id TEXT NOT NULL UNIQUE,
675
+ idempotency_key TEXT NOT NULL UNIQUE,
676
+ arc_id TEXT NOT NULL,
677
+ event_kind TEXT NOT NULL,
678
+ source_ref TEXT NOT NULL,
679
+ occurred_at TEXT NOT NULL,
680
+ payload_json TEXT NOT NULL,
681
+ event_digest TEXT NOT NULL,
682
+ stored_at TEXT NOT NULL
683
+ );
684
+ CREATE INDEX IF NOT EXISTS idx_arc_events_arc
685
+ ON arc_events(arc_id,seq);
686
+ CREATE TRIGGER IF NOT EXISTS arc_events_no_update
687
+ BEFORE UPDATE ON arc_events BEGIN
688
+ SELECT RAISE(ABORT, 'arc events are append-only');
689
+ END;
690
+ CREATE TRIGGER IF NOT EXISTS arc_events_no_delete
691
+ BEFORE DELETE ON arc_events BEGIN
692
+ SELECT RAISE(ABORT, 'arc events are append-only');
693
+ END;
694
+ """
695
+ )
696
+ self._conn.commit()
697
+ if db_path != ":memory:":
698
+ os.chmod(db_path, 0o600)
699
+
700
+ @staticmethod
701
+ def _decode(row: sqlite3.Row) -> ArcEventV1:
702
+ try:
703
+ payload = json.loads(row["payload_json"])
704
+ except (TypeError, ValueError) as exc:
705
+ raise ValueError("arc event payload is unreadable") from exc
706
+ event = ArcEventV1(
707
+ **payload,
708
+ sequence=int(row["seq"]),
709
+ )
710
+ if event.event_digest != row["event_digest"]:
711
+ raise ValueError("arc event digest mismatch")
712
+ return event
713
+
714
+ def _events_locked(self, arc_id: str) -> list[ArcEventV1]:
715
+ rows = self._conn.execute(
716
+ "SELECT * FROM arc_events WHERE arc_id=? ORDER BY seq",
717
+ (arc_id,),
718
+ ).fetchall()
719
+ return [self._decode(row) for row in rows]
720
+
721
+ def append(self, event: ArcEventV1) -> ArcAppendResultV1:
722
+ if not isinstance(event, ArcEventV1):
723
+ raise ValueError("ArcStore accepts ArcEventV1 only")
724
+ with self._lock:
725
+ self._conn.execute("BEGIN IMMEDIATE")
726
+ try:
727
+ rows = self._conn.execute(
728
+ "SELECT * FROM arc_events WHERE event_id=? "
729
+ "OR idempotency_key=? ORDER BY seq",
730
+ (event.event_id, event.idempotency_key),
731
+ ).fetchall()
732
+ if rows:
733
+ for row in rows:
734
+ saved = self._decode(row)
735
+ if (
736
+ saved.event_id == event.event_id
737
+ and saved.idempotency_key == event.idempotency_key
738
+ and saved.event_digest == event.event_digest
739
+ ):
740
+ self._conn.commit()
741
+ return ArcAppendResultV1(
742
+ event=saved, appended=False, replayed=True)
743
+ raise ArcConflictError(
744
+ "arc event idempotency identity changed immutable content")
745
+
746
+ # Reject an event that would make the ledger unreducible before
747
+ # it is persisted. BEGIN IMMEDIATE reserves the next sequence,
748
+ # so validation sees the same fully-sequenced history that a
749
+ # subsequent reader will reduce.
750
+ existing = self._events_locked(event.arc_id)
751
+ row = self._conn.execute(
752
+ "SELECT COALESCE(MAX(seq),0) + 1 AS next_seq "
753
+ "FROM arc_events"
754
+ ).fetchone()
755
+ next_sequence = int(row["next_seq"] if row else 1)
756
+ ArcReducer.reduce(
757
+ [*existing, replace(event, sequence=next_sequence)],
758
+ now=event.occurred_at,
759
+ )
760
+ cursor = self._conn.execute(
761
+ "INSERT INTO arc_events "
762
+ "(event_id,idempotency_key,arc_id,event_kind,source_ref,"
763
+ "occurred_at,payload_json,event_digest,stored_at) "
764
+ "VALUES (?,?,?,?,?,?,?,?,?)",
765
+ (
766
+ event.event_id,
767
+ event.idempotency_key,
768
+ event.arc_id,
769
+ event.event_kind,
770
+ event.source_ref,
771
+ event.occurred_at,
772
+ _canonical(event.payload()),
773
+ event.event_digest,
774
+ datetime.now(timezone.utc).isoformat(),
775
+ ),
776
+ )
777
+ sequence = int(cursor.lastrowid)
778
+ if sequence != next_sequence:
779
+ raise RuntimeError("arc ledger sequence allocation changed")
780
+ self._conn.commit()
781
+ return ArcAppendResultV1(
782
+ event=replace(event, sequence=sequence),
783
+ appended=True,
784
+ replayed=False,
785
+ )
786
+ except Exception:
787
+ self._conn.rollback()
788
+ raise
789
+
790
+ def events(self, arc_id: str) -> tuple[ArcEventV1, ...]:
791
+ normalized = _ref(arc_id, field="arc_id")
792
+ with self._lock:
793
+ return tuple(self._events_locked(normalized))
794
+
795
+ def event_count(self) -> int:
796
+ with self._lock:
797
+ row = self._conn.execute(
798
+ "SELECT COUNT(*) AS n FROM arc_events").fetchone()
799
+ return int(row["n"] if row else 0)
800
+
801
+ def get_arc(
802
+ self,
803
+ arc_id: str,
804
+ *,
805
+ now: datetime | str | None = None,
806
+ ) -> Optional[Arc]:
807
+ events = self.events(arc_id)
808
+ return ArcReducer.reduce(events, now=now) if events else None
809
+
810
+ def project_active(
811
+ self,
812
+ viewer: ViewerContextV1,
813
+ *,
814
+ now: datetime | str,
815
+ max_arcs: int = 24,
816
+ max_topic_chars: int = 8_000,
817
+ ) -> ArcProjectionBatchV1:
818
+ if not 1 <= int(max_arcs) <= MAX_PROJECTED_ARCS:
819
+ raise ValueError("max_arcs is outside bounded projection range")
820
+ if not 1 <= int(max_topic_chars) <= MAX_PROJECTED_TOPIC_CHARS:
821
+ raise ValueError("max_topic_chars is outside bounded projection range")
822
+ with self._lock:
823
+ rows = self._conn.execute(
824
+ "SELECT arc_id,MAX(seq) AS last_seq FROM arc_events "
825
+ "GROUP BY arc_id ORDER BY last_seq DESC LIMIT ?",
826
+ (MAX_ARCS_SCANNED + 1,),
827
+ ).fetchall()
828
+ arc_ids = [str(row["arc_id"]) for row in rows]
829
+ scan_truncated = len(arc_ids) > MAX_ARCS_SCANNED
830
+ arc_ids = arc_ids[:MAX_ARCS_SCANNED]
831
+ eligible: list[Arc] = []
832
+ denied_count = 0
833
+ corrupt_count = 0
834
+ for arc_id in arc_ids:
835
+ try:
836
+ arc = self.get_arc(arc_id, now=now)
837
+ except Exception:
838
+ corrupt_count += 1
839
+ continue
840
+ if arc is None or not arc.active:
841
+ continue
842
+ if not arc.visible_to(viewer):
843
+ denied_count += 1
844
+ continue
845
+ eligible.append(arc)
846
+ eligible.sort(key=lambda arc: (arc.updated_at, arc.arc_id), reverse=True)
847
+
848
+ projected: list[Arc] = []
849
+ chars = 0
850
+ truncated = scan_truncated
851
+ for arc in eligible:
852
+ if len(projected) >= int(max_arcs)\
853
+ or chars + len(arc.topic) > int(max_topic_chars):
854
+ truncated = True
855
+ continue
856
+ projected.append(arc)
857
+ chars += len(arc.topic)
858
+ payload = {
859
+ "schema_version": SCHEMA_VERSION,
860
+ "viewer_digest": viewer.audit_digest,
861
+ "arc_digests": [arc.history_digest for arc in projected],
862
+ "denied_count": denied_count,
863
+ "corrupt_count": corrupt_count,
864
+ "truncated": truncated,
865
+ "now": _iso(now, field="now"),
866
+ "max_arcs": int(max_arcs),
867
+ "max_topic_chars": int(max_topic_chars),
868
+ }
869
+ return ArcProjectionBatchV1(
870
+ arcs=tuple(projected),
871
+ denied_count=denied_count,
872
+ corrupt_count=corrupt_count,
873
+ truncated=truncated,
874
+ viewer_digest=viewer.audit_digest,
875
+ audit_digest=_digest(payload),
876
+ )
877
+
878
+ def close(self) -> None:
879
+ with self._lock:
880
+ try:
881
+ self._conn.close()
882
+ except Exception:
883
+ pass
884
+
885
+
886
+ __all__ = [
887
+ "ACTIVE_STATES",
888
+ "ARC_TYPES",
889
+ "Arc",
890
+ "ArcAppendResultV1",
891
+ "ArcConflictError",
892
+ "ArcEventV1",
893
+ "ArcProjectionBatchV1",
894
+ "ArcReducer",
895
+ "ArcStore",
896
+ ]