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,265 @@
1
+ """Explicit work coordination, with existing participant/writer authority."""
2
+ from typing import Literal
3
+ from contextlib import closing
4
+ import hashlib
5
+ import os
6
+ from pathlib import Path
7
+ import sqlite3
8
+
9
+ from fastapi import APIRouter, HTTPException, Request
10
+ from pydantic import BaseModel, ConfigDict, Field, StrictBool
11
+ from pydantic import field_validator
12
+
13
+ from apsimo.api.authority import request_authority
14
+ from apsimo.api.routers.executions import authorized_viewer
15
+ from apsimo.commitments.work import CommitmentWork
16
+ from apsimo.commitments.local_work import LocalWork, LocalWorkConflict
17
+
18
+ router = APIRouter(prefix='/v1/host/commitments', tags=['commitments'])
19
+
20
+
21
+ class WorkOperation(BaseModel):
22
+ model_config = ConfigDict(extra='forbid')
23
+ operation: Literal['claim', 'status', 'renew', 'release']
24
+ contact_id: str = Field(min_length=1, max_length=256)
25
+ session_id: str = Field(min_length=1, max_length=256)
26
+ task_id: str = Field(min_length=1, max_length=256)
27
+ turn_id: str = Field(min_length=1, max_length=256)
28
+ claim_id: str = Field(default='', pattern=r'^(?:[a-f0-9]{32})?$')
29
+
30
+
31
+ @router.post('/{commitment_id}/work')
32
+ def operate(commitment_id: str, body: WorkOperation, request: Request):
33
+ person, _ = authorized_viewer(request, body.contact_id, scope='turns:write')
34
+ from apsimo.api.routers import host
35
+ if host._commitment_store is None:
36
+ raise HTTPException(503, detail='commitment store unavailable')
37
+ try:
38
+ return CommitmentWork(host._commitment_store).operate(commitment_id,
39
+ **body.model_dump(exclude={'contact_id'}), contact_id=person,
40
+ principal_id=request_authority(request).principal_id)
41
+ except KeyError:
42
+ raise HTTPException(404, detail='unknown commitment') from None
43
+
44
+
45
+ class LocalDraftHandoff(BaseModel):
46
+ model_config = ConfigDict(extra='forbid')
47
+ session_id: str = Field(min_length=1, max_length=256)
48
+ task_id: str = Field(min_length=1, max_length=256)
49
+ turn_id: str = Field(min_length=1, max_length=256)
50
+ claim_id: str = Field(pattern=r'^[a-f0-9]{32}$')
51
+
52
+
53
+ class LocalDraftAcceptance(BaseModel):
54
+ model_config = ConfigDict(extra='forbid')
55
+ contact_id: str = Field(min_length=1, max_length=256)
56
+ session_id: str = Field(min_length=1, max_length=256)
57
+ turn_id: str = Field(min_length=1, max_length=256)
58
+ question: str = Field(min_length=1, max_length=2000)
59
+ sources: list[str] = Field(min_length=1, max_length=8)
60
+ origin: 'NativeDraftOrigin | None' = None
61
+ new_draft: StrictBool = False
62
+ handoff: LocalDraftHandoff | None = None
63
+
64
+ @field_validator('sources')
65
+ @classmethod
66
+ def explicit_paths(cls, value):
67
+ if (len(set(value)) != len(value)
68
+ or any(not Path(path).is_absolute() or len(path) > 4096 or '\x00' in path for path in value)):
69
+ raise ValueError('Distinct absolute local source paths are required')
70
+ return value
71
+
72
+
73
+ class NativeDraftOrigin(BaseModel):
74
+ model_config = ConfigDict(extra='forbid')
75
+ platform: str = Field(min_length=1, max_length=64, pattern=r'^[a-z0-9_.-]+$')
76
+ chat_id: str = Field(min_length=1, max_length=512)
77
+ thread_id: str | None = Field(default=None, max_length=512)
78
+ user_id: str | None = Field(default=None, max_length=512)
79
+ user_id_alt: str | None = Field(default=None, max_length=512)
80
+ chat_type: str | None = Field(default=None, max_length=64)
81
+ notifier_profile: str | None = Field(default=None, max_length=256)
82
+
83
+
84
+ LocalDraftAcceptance.model_rebuild()
85
+
86
+
87
+ class NativeWorkRun(BaseModel):
88
+ model_config = ConfigDict(extra='forbid')
89
+ contact_id: str = Field(min_length=1, max_length=256)
90
+ native_job_id: str = Field(min_length=1, max_length=128)
91
+ native_execution_id: str = Field(pattern=r'^[a-f0-9]{32}$')
92
+
93
+
94
+ class LocalDraftResult(BaseModel):
95
+ model_config = ConfigDict(extra='forbid')
96
+ status: Literal['draft_created', 'unavailable']
97
+ summary: str = Field(default='', max_length=1600)
98
+ report_path: str = Field(default='', max_length=4096)
99
+ report_sha256: str = Field(default='', pattern=r'^(?:[a-f0-9]{64})?$')
100
+ sources: dict[str, str] = Field(default_factory=dict, max_length=8)
101
+ model: str = Field(default='', max_length=256)
102
+ error_type: str = Field(default='', max_length=128, pattern=r'^[A-Za-z0-9_]*$')
103
+
104
+
105
+ class NativeWorkFinish(NativeWorkRun):
106
+ result: LocalDraftResult
107
+
108
+
109
+ class NativeTaskBinding(BaseModel):
110
+ model_config = ConfigDict(extra='forbid')
111
+ contact_id: str = Field(min_length=1, max_length=256)
112
+ native_board: str = Field(pattern=r'^[a-z0-9][a-z0-9_-]{0,63}$')
113
+ native_task_id: str = Field(min_length=1, max_length=128)
114
+
115
+
116
+ class NativeKanbanRun(NativeTaskBinding):
117
+ native_run_id: int = Field(ge=1)
118
+ native_claim_lock: str = Field(min_length=1, max_length=256)
119
+
120
+
121
+ class NativeKanbanFinish(NativeKanbanRun):
122
+ result: LocalDraftResult
123
+
124
+
125
+ def accepted_native(body, initiative_id, person):
126
+ from apsimo.turns.hermes_kanban import task_snapshot
127
+ try:
128
+ # An accepted transition can wait briefly for a native writer, including
129
+ # Hermes' DELETE-journal fallback. Ordinary work projections stay fast.
130
+ return task_snapshot(initiative_id, person,
131
+ body.model_dump(exclude={'contact_id', 'result'}), read_timeout=2.)
132
+ except (OSError, sqlite3.Error):
133
+ raise HTTPException(503, detail='native_board_unavailable') from None
134
+ except (ValueError, KeyError, TypeError) as error:
135
+ raise HTTPException(409, detail=str(error)) from None
136
+
137
+
138
+ def local_store(request, contact_id, *, enabled=False):
139
+ person, owner = authorized_viewer(request, contact_id, scope='turns:write')
140
+ if not owner:
141
+ raise HTTPException(403, detail='owner_local_work_required')
142
+ if enabled and os.environ.get('COLONY_LOCAL_WORK_ENABLED', '').lower() != 'true':
143
+ raise HTTPException(503, detail='local_work_not_enabled')
144
+ from apsimo.api.routers import host
145
+ if host._initiative_store is None or host._commitment_store is None:
146
+ raise HTTPException(503, detail='local_work_store_unavailable')
147
+ return LocalWork(host._initiative_store, host._commitment_store), person
148
+
149
+
150
+ def native_run(body, *, finishing=False):
151
+ from apsimo.turns.hermes_work import selected_home
152
+ home = selected_home()
153
+ if not home or body.native_job_id != os.environ.get('COLONY_LOCAL_WORK_JOB_ID'):
154
+ raise HTTPException(409, detail='selected_local_work_job_required')
155
+ home_id = hashlib.sha256(str(home).encode()).hexdigest()
156
+ def terminal(context):
157
+ if context.get('source_home_id') != home_id or context.get('native_job_id') != body.native_job_id:
158
+ return None
159
+ with closing(sqlite3.connect((home/'cron/executions.db').as_uri()+'?mode=ro', uri=True, timeout=.2)) as db:
160
+ row = db.execute('SELECT status FROM executions WHERE id=? AND job_id=?',
161
+ (context.get('native_execution_id'), body.native_job_id)).fetchone()
162
+ return row[0] if row else None
163
+ native = {'source_home_id': home_id, 'native_job_id': body.native_job_id,
164
+ 'native_execution_id': body.native_execution_id}
165
+ try:
166
+ status = terminal(native)
167
+ except (OSError, sqlite3.Error):
168
+ raise HTTPException(503, detail='native_execution_ledger_unavailable') from None
169
+ if status not in ({'running', 'completed', 'failed'} if finishing else {'running'}):
170
+ raise HTTPException(409, detail='running_native_execution_required')
171
+ return native, terminal
172
+
173
+
174
+ @router.post('/{commitment_id}/local-draft')
175
+ def accept_local_draft(commitment_id: str, body: LocalDraftAcceptance, request: Request):
176
+ return _accept_local_draft(commitment_id, body, request)
177
+
178
+
179
+ @router.post('/local-draft')
180
+ def accept_standalone_local_draft(body: LocalDraftAcceptance, request: Request):
181
+ return _accept_local_draft(None, body, request)
182
+
183
+
184
+ def _accept_local_draft(commitment_id, body, request):
185
+ store, person = local_store(request, body.contact_id, enabled=True)
186
+ backend = os.environ.get('COLONY_LOCAL_WORK_EXECUTOR', 'cron')
187
+ if backend not in {'cron', 'kanban'}:
188
+ raise HTTPException(503, detail='local_work_executor_unavailable')
189
+ try:
190
+ return store.accept(commitment_id, **body.model_dump(exclude={'contact_id'}),
191
+ contact_id=person, principal_id=request_authority(request).principal_id,
192
+ execution_backend=backend)
193
+ except KeyError:
194
+ raise HTTPException(404, detail='unknown_commitment') from None
195
+ except LocalWorkConflict as error:
196
+ raise HTTPException(409, detail={'reason': str(error), 'initiative_id': error.initiative_id}) from None
197
+ except ValueError as error:
198
+ raise HTTPException(409, detail=str(error)) from None
199
+
200
+
201
+ @router.post('/local-work/next')
202
+ def next_local_draft(body: NativeWorkRun, request: Request):
203
+ store, person = local_store(request, body.contact_id, enabled=True)
204
+ native, terminal = native_run(body)
205
+ return {'assignment': store.select(person, native, terminal)}
206
+
207
+
208
+ @router.get('/local-work/pending')
209
+ def pending_native_drafts(contact_id: str, request: Request):
210
+ store, person = local_store(request, contact_id, enabled=True)
211
+ if os.environ.get('COLONY_LOCAL_WORK_EXECUTOR', 'cron') != 'kanban':
212
+ raise HTTPException(409, detail='native_execution_backend_required')
213
+ return store.native_pending(person)
214
+
215
+
216
+ @router.post('/local-work/{initiative_id}/native-task')
217
+ def attach_native_draft(initiative_id: str, body: NativeTaskBinding, request: Request):
218
+ store, person = local_store(request, body.contact_id, enabled=True)
219
+ native, _ = accepted_native(body, initiative_id, person)
220
+ try:
221
+ return store.attach_native_task(initiative_id, person, native)
222
+ except KeyError:
223
+ raise HTTPException(404, detail='unknown_local_work') from None
224
+ except ValueError as error:
225
+ raise HTTPException(409, detail=str(error)) from None
226
+
227
+
228
+ @router.post('/local-work/{initiative_id}/native-run')
229
+ def bind_native_draft(initiative_id: str, body: NativeKanbanRun, request: Request):
230
+ store, person = local_store(request, body.contact_id, enabled=True)
231
+ native, state = accepted_native(body, initiative_id, person)
232
+ try:
233
+ return store.bind_native_run(initiative_id, person, native, attempt_count=state['attempt_count'])
234
+ except KeyError:
235
+ raise HTTPException(404, detail='unknown_local_work') from None
236
+ except ValueError as error:
237
+ raise HTTPException(409, detail=str(error)) from None
238
+
239
+
240
+ @router.get('/local-work/{initiative_id}')
241
+ def local_draft_status(initiative_id: str, contact_id: str, request: Request):
242
+ store, person = local_store(request, contact_id)
243
+ try:
244
+ return store.status(initiative_id, person)
245
+ except KeyError:
246
+ raise HTTPException(404, detail='unknown_local_work') from None
247
+
248
+
249
+ @router.post('/local-work/{initiative_id}/finish')
250
+ def finish_local_draft(initiative_id: str, body: NativeWorkFinish | NativeKanbanFinish, request: Request):
251
+ store, person = local_store(request, body.contact_id)
252
+ native, _ = (accepted_native(body, initiative_id, person) if isinstance(body, NativeKanbanFinish)
253
+ else native_run(body, finishing=True))
254
+ result = body.result.model_dump()
255
+ if result['status'] == 'draft_created':
256
+ if not result['report_sha256'] or not Path(result['report_path']).is_absolute() or not result['sources']:
257
+ raise HTTPException(422, detail='local_report_receipt_required')
258
+ elif not result['error_type']:
259
+ raise HTTPException(422, detail='failure_reason_required')
260
+ try:
261
+ return store.finish(initiative_id, person, native, result)
262
+ except KeyError:
263
+ raise HTTPException(404, detail='unknown_local_work') from None
264
+ except ValueError as error:
265
+ raise HTTPException(409, detail=str(error)) from None
@@ -0,0 +1,123 @@
1
+ """Context-gate API: budget-aware context preparation for any agent.
2
+
3
+ POST /v1/context/prepare — decide whether content fits a model's useful
4
+ context window whole, and if not, chunk + retrieve (query-focused) or
5
+ coverage-sample (holistic) down to budget. See
6
+ :mod:`apsimo.contextgate` for the underlying machinery.
7
+
8
+ Auth rides the global ApiKeyMiddleware like every other router.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from typing import List, Optional
15
+
16
+ from fastapi import APIRouter, HTTPException
17
+ from pydantic import BaseModel, Field
18
+
19
+ from apsimo.contextgate import GateConfig, prepare_context
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ router = APIRouter(prefix="/v1/context", tags=["context"])
24
+
25
+
26
+ class DocumentIn(BaseModel):
27
+ name: str = ""
28
+ content: str
29
+
30
+
31
+ class PrepareRequest(BaseModel):
32
+ content: Optional[str] = Field(
33
+ default=None, description="Raw text to prepare (or use `documents`)."
34
+ )
35
+ documents: Optional[List[DocumentIn]] = Field(
36
+ default=None, description="Named documents; concatenated with headers."
37
+ )
38
+ query: str = Field(
39
+ default="", description="The task/question the context will serve."
40
+ )
41
+ budget_tokens: int = Field(
42
+ default=0,
43
+ ge=0,
44
+ description=(
45
+ "Token budget. 0 = derive from `model_tier`'s usefulContextTokens, "
46
+ "else the COLONY_CONTEXT_GATE_BUDGET default."
47
+ ),
48
+ )
49
+ model_tier: str = Field(
50
+ default="",
51
+ description="small|medium|large — derive budget from this tier's config.",
52
+ )
53
+ task_kind: Optional[str] = Field(
54
+ default=None, description="retrieval | holistic (overrides the heuristic)."
55
+ )
56
+
57
+
58
+ class PrepareResponse(BaseModel):
59
+ text: str
60
+ decision: str
61
+ task_kind: str
62
+ est_tokens_in: int
63
+ est_tokens_out: int
64
+ budget_tokens: int
65
+ chunks_total: int
66
+ chunks_used: int
67
+ coverage: float
68
+
69
+
70
+ def _tier_budget(tier_name: str) -> int:
71
+ """Budget from the live LLMRouter's tier config (0 when unknown)."""
72
+ try:
73
+ from apsimo.api.routers.host import get_llm_router
74
+ from apsimo.router.tiers import ModelTier
75
+
76
+ llm_router = get_llm_router()
77
+ if llm_router is None:
78
+ return 0
79
+ cfg = llm_router.tier_config(ModelTier(tier_name))
80
+ return cfg.useful_context_tokens if cfg else 0
81
+ except (ValueError, ImportError):
82
+ return 0
83
+
84
+
85
+ @router.post("/prepare", response_model=PrepareResponse)
86
+ async def prepare(body: PrepareRequest) -> PrepareResponse:
87
+ """Prepare content to fit a model's useful context window."""
88
+ if body.content is None and not body.documents:
89
+ raise HTTPException(status_code=422, detail="Provide `content` or `documents`.")
90
+
91
+ if body.documents:
92
+ parts = []
93
+ for i, doc in enumerate(body.documents):
94
+ name = doc.name or f"document {i + 1}"
95
+ parts.append(f"# {name}\n\n{doc.content}")
96
+ content = "\n\n".join(parts)
97
+ if body.content:
98
+ content = body.content + "\n\n" + content
99
+ else:
100
+ content = body.content or ""
101
+
102
+ budget = body.budget_tokens
103
+ if budget <= 0 and body.model_tier:
104
+ budget = _tier_budget(body.model_tier.strip().lower())
105
+
106
+ prepared = await prepare_context(
107
+ content=content,
108
+ query=body.query,
109
+ budget_tokens=budget,
110
+ task_kind=body.task_kind,
111
+ config=GateConfig.from_env(),
112
+ )
113
+ return PrepareResponse(
114
+ text=prepared.text,
115
+ decision=prepared.decision.value,
116
+ task_kind=prepared.task_kind,
117
+ est_tokens_in=prepared.est_tokens_in,
118
+ est_tokens_out=prepared.est_tokens_out,
119
+ budget_tokens=prepared.budget_tokens,
120
+ chunks_total=prepared.chunks_total,
121
+ chunks_used=prepared.chunks_used,
122
+ coverage=prepared.coverage,
123
+ )
@@ -0,0 +1,140 @@
1
+ """Execution observations reuse turn-writer and context-reader authority."""
2
+ import os
3
+ from typing import Literal
4
+
5
+ from fastapi import APIRouter, HTTPException, Query, Request
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+ from apsimo.api.authority import request_authority, resolve_request_person
9
+ from apsimo.turns.executions import registry
10
+
11
+ router = APIRouter(prefix="/v1/host/executions", tags=["executions"])
12
+
13
+
14
+ class ExecutionRuntimeObservation(BaseModel):
15
+ model_config = ConfigDict(extra="forbid")
16
+ event: Literal['start', 'response', 'error']
17
+ request_id: str = Field(default='', max_length=256, pattern=r'^[^\x00-\x1f]*$')
18
+ requested_model: str = Field(default='', max_length=256, pattern=r'^[^\x00-\x1f]*$')
19
+ provider: str = Field(default='', max_length=256, pattern=r'^[^\x00-\x1f]*$')
20
+ response_model: str = Field(default='', max_length=256, pattern=r'^[^\x00-\x1f]*$')
21
+ api_mode: str = Field(default='', max_length=256, pattern=r'^[^\x00-\x1f]*$')
22
+ profile_id: str = Field(default='', pattern=r'^(?:[a-f0-9]{64})?$')
23
+ runtime_kind: Literal['turn', 'cron', 'kanban_worker', 'delegated_child', 'unknown'] = 'unknown'
24
+ approx_input_tokens: int | None = Field(default=None, ge=0, le=2147483647)
25
+ max_tokens: int | None = Field(default=None, ge=0, le=2147483647)
26
+ output_limit_kind: Literal['request', 'provider_default', 'unknown'] = 'unknown'
27
+ tool_count: int | None = Field(default=None, ge=0, le=2147483647)
28
+ api_call_count: int | None = Field(default=None, ge=0, le=2147483647)
29
+ retry_count: int | None = Field(default=None, ge=0, le=2147483647)
30
+ started_at: float | None = Field(default=None, gt=0, allow_inf_nan=False)
31
+ ended_at: float | None = Field(default=None, gt=0, allow_inf_nan=False)
32
+
33
+
34
+ class ExecutionInputReference(BaseModel):
35
+ model_config = ConfigDict(extra="forbid")
36
+ source_id: str = Field(min_length=1, max_length=256)
37
+ input_message_hash: str = Field(pattern=r'^[a-f0-9]{64}$')
38
+
39
+
40
+ class ExecutionObservation(BaseModel):
41
+ model_config = ConfigDict(extra="forbid")
42
+ execution_id: str = Field(pattern=r"^[a-f0-9]{64}$")
43
+ contact_id: str = Field(min_length=1, max_length=256)
44
+ session_id: str = Field(min_length=1, max_length=256, pattern=r"^[^\x00-\x1f]+$")
45
+ turn_id: str = Field(min_length=1, max_length=256)
46
+ parent_execution_id: str = Field(default="", pattern=r"^(?:[a-f0-9]{64})?$")
47
+ platform: str = Field(min_length=1, max_length=64, pattern=r"^[a-z0-9_.-]+$")
48
+ state: Literal["observed", "completed", "failed", "interrupted", "ended"] = "observed"
49
+ phase: Literal["turn", "model", "tool", "between_calls", "ended"] = "turn"
50
+ tool_name: str = Field(default="", max_length=128, pattern=r"^[a-zA-Z0-9_.:-]*$")
51
+ sequence: int = Field(ge=1, le=2147483647)
52
+ runtime: ExecutionRuntimeObservation | None = None
53
+ # Exact already-admitted human inputs, never native task wrappers or titles.
54
+ input_refs: list[ExecutionInputReference] | None = Field(default=None, min_length=1, max_length=64)
55
+
56
+
57
+ def authorized_viewer(request: Request, contact_id: str, *, scope: str) -> tuple[str, bool]:
58
+ authority = request_authority(request)
59
+ # Legacy body-selected identity is deliberately not sufficient for this new
60
+ # shared surface. Use the existing exact person grants, never an owner flag.
61
+ if not authority.authenticated or authority.anonymous or authority.legacy or not authority.has_scope(scope):
62
+ raise HTTPException(403, detail={"code": "scoped_execution_authority_required"})
63
+ person = resolve_request_person(request, claimed_person_id=contact_id)
64
+ owner = os.environ.get("COLONY_OWNER_PERSON_ID", "").strip() or os.environ.get("COLONY_OWNER_CONTACT_ID", "").strip()
65
+ return person, bool(owner and person == owner)
66
+
67
+
68
+ @router.post("/observe")
69
+ def observe(body: ExecutionObservation, request: Request):
70
+ person, _ = authorized_viewer(request, body.contact_id, scope="turns:write")
71
+ try:
72
+ return registry().observe(body.model_dump(), principal_id=request_authority(request).principal_id, contact_id=person)
73
+ except ValueError as exc:
74
+ raise HTTPException(409, detail={"code": str(exc)}) from exc
75
+
76
+
77
+ @router.get("")
78
+ async def active(request: Request, contact_id: str, session_id: str = "", limit: int = Query(20, ge=1, le=100),
79
+ projection: Literal['full', 'request'] = 'full', input_context: bool = False):
80
+ person, owner = authorized_viewer(request, contact_id, scope="context:read")
81
+ if projection == 'request' and not owner:
82
+ raise HTTPException(403, detail='owner_work_context_required')
83
+ if projection == 'request':
84
+ limit = min(limit, 8)
85
+ view = registry().view(contact_id=person, owner=owner, session_id=session_id, limit=limit,
86
+ include_ancestors=projection == 'request',
87
+ include_inputs=projection == 'full' or input_context)
88
+ view = await with_queue_work(view, owner=owner, limit=limit)
89
+ if projection == 'request':
90
+ from apsimo.turns.executions import request_work_context
91
+ return request_work_context(view, limit=limit, session_id=session_id)
92
+ return view
93
+
94
+
95
+ async def with_queue_work(view, *, owner, limit=8):
96
+ if not owner:
97
+ return view
98
+ import asyncio
99
+ from apsimo.turns.hermes_work import cron_view
100
+ from apsimo.turns.local_work import local_work_view
101
+ from apsimo.turns.hermes_kanban import kanban_view
102
+ from apsimo.turns.reported_workers import reported_worker_view
103
+ from apsimo.api.routers import host
104
+ from apsimo.turns.executions import work_source_coverage
105
+ import time
106
+
107
+ queue = getattr(host._task_queue, 'queue', host._task_queue)
108
+
109
+ async def read(reader, *, asynchronous=False, **reader_kwargs):
110
+ # These are independent read-only snapshots. One unavailable ledger
111
+ # must not hide work observed by all the other readers. Cancellation of
112
+ # a thread await does not stop its read; native readers bound SQLite work.
113
+ try:
114
+ operation = reader(limit=limit, **reader_kwargs) if asynchronous else asyncio.to_thread(reader, limit=limit, **reader_kwargs)
115
+ value = await asyncio.wait_for(operation, timeout=.2)
116
+ if value is None:
117
+ return None
118
+ return {**value, 'observed_at': time.time()}
119
+ except Exception:
120
+ return {'items': [], 'recent': [], 'available': False, 'unavailable': True,
121
+ 'reason': 'work_source_unavailable', 'observed_at': time.time()}
122
+
123
+ async def queue_view():
124
+ if queue is None or not callable(getattr(queue, 'current_work', None)):
125
+ return {'items': [], 'available': False, 'unavailable': True,
126
+ 'reason': 'queue_not_attached', 'source': 'canonical_task_queue'}
127
+ return await read(queue.current_work, asynchronous=True)
128
+
129
+ # All sources share the existing request budget, rather than each taking
130
+ # another sequential budget after the native ledgers finish.
131
+ view['native_cron'], view['local_work'], view['native_kanban'], reported, view['worker_work'] = await asyncio.gather(
132
+ # Leave executor/return slack for the multi-board reader to retain
133
+ # faster boards when its own partial-read budget is exhausted.
134
+ read(cron_view), read(local_work_view), read(kanban_view, read_budget=.15),
135
+ read(reported_worker_view), queue_view())
136
+ if reported is not None:
137
+ view['reported_worker'] = reported
138
+ view['work_sources'] = work_source_coverage(view)
139
+ view['coverage'] = 'registered Hermes turns and selected work ledgers; see work_sources for read coverage'
140
+ return view
@@ -0,0 +1,147 @@
1
+ """Exact prepared task configuration; permission remains at the private outbox."""
2
+ import hashlib
3
+ import json
4
+ import time
5
+ from contextlib import closing
6
+
7
+ from fastapi import APIRouter, HTTPException, Request
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+ from typing import Literal
10
+
11
+ from apsimo.api.authority import request_authority
12
+ from apsimo.api.routers import temporal_followups as temporal
13
+ from apsimo.commitments.work import CommitmentWork
14
+ from apsimo.initiatives.temporal_followup import TemporalFollowups
15
+
16
+ router = APIRouter(prefix='/v1/host/temporal-followups', tags=['commitments'])
17
+
18
+
19
+ class FollowupPlan(BaseModel):
20
+ model_config = ConfigDict(extra='forbid', allow_inf_nan=False)
21
+ wait_id: str = Field(min_length=1, max_length=128)
22
+ commitment_id: str = Field(min_length=1, max_length=128)
23
+ work_id: str = Field(min_length=1, max_length=128)
24
+ source_id: str = Field(min_length=1, max_length=256)
25
+ source_version: str = Field(pattern='^[a-f0-9]{64}$')
26
+ recipient_id: str = Field(min_length=1, max_length=128)
27
+ channel: Literal['whatsapp', 'sms', 'rcs']
28
+ purpose: str = Field(min_length=1, max_length=512)
29
+ expires_at: float = Field(gt=0)
30
+ max_followups: Literal[1]
31
+ message: str = Field(min_length=1, max_length=8000)
32
+ message_sha256: str = Field(pattern='^[a-f0-9]{64}$')
33
+
34
+
35
+ def digest(plan):
36
+ return hashlib.sha256(json.dumps(plan, sort_keys=True, separators=(',', ':'),
37
+ ensure_ascii=False).encode()).hexdigest()
38
+
39
+
40
+ class BindPlan(BaseModel):
41
+ model_config = ConfigDict(extra='forbid')
42
+ contact_id: str
43
+ session_id: str
44
+ turn_id: str
45
+ claim_id: str
46
+ plan: FollowupPlan
47
+
48
+
49
+ class CheckPlan(BaseModel):
50
+ model_config = ConfigDict(extra='forbid')
51
+ plan: FollowupPlan
52
+ outbound_ref: str = Field(min_length=1, max_length=256)
53
+ target: dict
54
+
55
+
56
+ def _consistent(row, plan):
57
+ if any(row[key] != plan[key] for key in ('wait_id', 'commitment_id', 'work_id')):
58
+ raise ValueError('prepared_followup_task_mismatch')
59
+ if (row['contact_id'] != plan['recipient_id'] or row['expires_at'] != plan['expires_at']
60
+ or row['source_versions'].get(plan['source_id']) != plan['source_version']
61
+ or hashlib.sha256(plan['message'].encode()).hexdigest() != plan['message_sha256']):
62
+ raise ValueError('prepared_followup_evidence_mismatch')
63
+
64
+
65
+ @router.post('/{wait_id}/bind-plan')
66
+ def bind(wait_id: str, body: BindPlan, request: Request):
67
+ store, owner = temporal.ledger(request, body.contact_id, write=True)
68
+ def register():
69
+ row = temporal.owned(store, wait_id, owner)
70
+ plan = body.plan.model_dump()
71
+ _consistent(row, plan)
72
+ if not plan['source_id'].startswith('task-instruction:'):
73
+ raise ValueError('direct_owner_task_instruction_required')
74
+ if row['source_session_id'] != body.session_id:
75
+ raise ValueError('owner_task_session_mismatch')
76
+ existing = row['authority_scope']
77
+ if existing:
78
+ if existing.get('plan') != plan:
79
+ raise ValueError('task authority configuration is immutable')
80
+ return {'bound': True, 'plan_digest': digest(plan), 'effect_authorized': False}
81
+ current = CommitmentWork(store.store).operate(plan['commitment_id'], operation='renew',
82
+ principal_id=request_authority(request).principal_id, contact_id=owner,
83
+ session_id=body.session_id, task_id=plan['work_id'], turn_id=body.turn_id,
84
+ claim_id=body.claim_id)
85
+ if not current['accepted']:
86
+ raise ValueError('current_parent_work_claim_required')
87
+ store.bind_authority_scope(wait_id, scope={'plan': plan, 'plan_digest': digest(plan),
88
+ 'owner_contact_id': owner, 'owner_evidence_ref': plan['source_id'],
89
+ 'registered_at': time.time()}, evidence_ref=plan['source_id'])
90
+ return {'bound': True, 'plan_digest': digest(plan), 'effect_authorized': False}
91
+ return temporal.guarded(register)
92
+
93
+
94
+ def _check(wait_id, body, request, *, require_coverage=False):
95
+ authority = request_authority(request)
96
+ if (not authority.authenticated or authority.anonymous or authority.legacy
97
+ or not authority.has_scope('transport:write')):
98
+ raise HTTPException(403, detail='trusted_transport_producer_required')
99
+ from apsimo.api.routers import host
100
+ if host._commitment_store is None:
101
+ raise HTTPException(503, detail='commitment_store_unavailable')
102
+ store = TemporalFollowups(host._commitment_store)
103
+ row = store.get(wait_id)
104
+ with closing(store.store._connect()) as db:
105
+ parent = db.execute('SELECT person_id FROM commitments WHERE id=?', (row['commitment_id'],)).fetchone()
106
+ if not parent:
107
+ raise ValueError('unknown_owner_task')
108
+ row = temporal.refresh_source_bindings(store, row, parent['person_id'])
109
+ if host._comms_log is not None:
110
+ from apsimo.api.routers.transport import reconcile_receipts
111
+ row = reconcile_receipts(store, host._comms_log, row)
112
+ plan = body.plan.model_dump()
113
+ _consistent(row, plan)
114
+ scope = row['authority_scope']
115
+ if (scope.get('plan') != plan or scope.get('plan_digest') != digest(plan)
116
+ or row['outbound_ref'] != body.outbound_ref
117
+ or body.target.get('channel') != plan['channel']):
118
+ raise ValueError('prepared_followup_binding_mismatch')
119
+ # Actual provider recipient/account/thread resolution and permission belong
120
+ # to the private policy/outbox. This proves the canonical task configuration.
121
+ check = store.preflight(wait_id)
122
+ current_sources = not temporal.source_bindings(row['source_refs'], row['source_versions'],
123
+ person=parent['person_id'], session_id=row['source_session_id'])
124
+ coverage = None
125
+ if require_coverage:
126
+ from .transport_ingress_api import followup_coverage
127
+ coverage = followup_coverage(principal=authority.principal_id, contact_id=row['contact_id'],
128
+ since=row['created_at'], channel=plan['channel'])
129
+ if not coverage['observed']:
130
+ check = {**check, 'review_allowed': False, 'reason': 'intake_coverage_unknown'}
131
+ return {'verified': current_sources and row['state'] not in {'cancelled', 'expired'},
132
+ 'review_allowed': check['review_allowed'],
133
+ 'reason': 'due' if check['review_allowed'] else check['reason'],
134
+ 'plan_digest': digest(plan), 'owner_contact_id': scope['owner_contact_id'],
135
+ 'owner_evidence_ref': scope['owner_evidence_ref'],
136
+ 'registered_at': scope['registered_at'], 'resolved_contact_id': row['contact_id'],
137
+ **({'transport_coverage': coverage} if coverage is not None else {})}
138
+
139
+
140
+ @router.post('/{wait_id}/verify-plan')
141
+ async def verify(wait_id: str, body: CheckPlan, request: Request):
142
+ return temporal.guarded(lambda: _check(wait_id, body, request))
143
+
144
+
145
+ @router.post('/{wait_id}/check-plan')
146
+ async def check(wait_id: str, body: CheckPlan, request: Request):
147
+ return temporal.guarded(lambda: _check(wait_id, body, request, require_coverage=True))