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,287 @@
1
+ """Server-owned routing contracts for the shared ``agent_action`` job type."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+
9
+ AGENT_SYNC_ROUTE = "agent_sync:v1"
10
+ ACTION_PLANE_ROUTE = "action_plane:v1"
11
+ WORK_ORDER_ROUTE = "work_order:v1"
12
+ HERMES_RUN_ROUTE = "hermes_run:v1"
13
+ THOUGHT_ROUTE = "thought_engine:v1"
14
+
15
+ AGENT_ACTION_ROUTE_CAPABILITIES = frozenset({
16
+ AGENT_SYNC_ROUTE,
17
+ ACTION_PLANE_ROUTE,
18
+ WORK_ORDER_ROUTE,
19
+ HERMES_RUN_ROUTE,
20
+ })
21
+
22
+ _ROUTE_OWNER_ENV = {
23
+ AGENT_SYNC_ROUTE: "COLONY_AGENT_SYNC_WORKER_NODE_ID",
24
+ HERMES_RUN_ROUTE: "COLONY_HERMES_RUN_WORKER_NODE_ID",
25
+ ACTION_PLANE_ROUTE: "COLONY_ACTION_PLANE_WORKER_NODE_ID",
26
+ WORK_ORDER_ROUTE: "COLONY_ACTION_PLANE_WORKER_NODE_ID",
27
+ }
28
+
29
+
30
+ def generic_agent_job_claims_enabled() -> bool:
31
+ """Resolve the global generic agent-action containment switch."""
32
+
33
+ value = os.environ.get(
34
+ "COLONY_AGENT_JOB_CLAIMS_ENABLED", "true"
35
+ ).strip().lower()
36
+ if value in {"1", "true", "yes", "on"}:
37
+ return True
38
+ if value in {"0", "false", "no", "off"}:
39
+ return False
40
+ # An invalid containment value never silently enables generic claims.
41
+ return False
42
+
43
+
44
+ def expected_agent_action_routes(job: Any) -> tuple[str, ...]:
45
+ """Derive exact executor lanes from server-known schema/action policy."""
46
+
47
+ payload = getattr(job, "payload", {}) or {}
48
+ if payload.get("schema") == "WorkOrderV1":
49
+ from apsimo.work_orders import WorkOrderV1
50
+
51
+ order = WorkOrderV1.from_payload(payload)
52
+ if str(getattr(job, "job_id", "")) != order.work_order_id:
53
+ raise ValueError(
54
+ "WorkOrder queue job ID does not match authority digest"
55
+ )
56
+ # WorkOrders are receipt-aware Action Plane work. Requiring both
57
+ # capabilities prevents a generic protocol-aware worker from racing
58
+ # the deployment's authorized effect executor.
59
+ return (WORK_ORDER_ROUTE, ACTION_PLANE_ROUTE)
60
+
61
+ action_hint = str(payload.get("action_hint") or "").strip()
62
+ if action_hint:
63
+ from apsimo.initiatives.action_registry import (
64
+ RiskTier,
65
+ get_action,
66
+ )
67
+
68
+ spec = get_action(action_hint)
69
+ if spec is not None:
70
+ canonical_risk = spec.risk.value
71
+ declared_risk = str(payload.get("risk") or "").strip().lower()
72
+ if declared_risk and declared_risk != canonical_risk:
73
+ raise ValueError(
74
+ "agent_action risk does not match server registry"
75
+ )
76
+ if spec.risk is RiskTier.READ_ONLY:
77
+ from apsimo.task_queue.governor import (
78
+ job_declares_effect,
79
+ )
80
+ if job_declares_effect(job):
81
+ raise ValueError(
82
+ "read-only agent action declares effect capabilities"
83
+ )
84
+ return (AGENT_SYNC_ROUTE,)
85
+ return (ACTION_PLANE_ROUTE,)
86
+ raise ValueError("agent_action action_hint is not server-registered")
87
+
88
+ from apsimo.task_queue.governor import job_declares_effect
89
+
90
+ if job_declares_effect(job):
91
+ raise ValueError(
92
+ "effectful agent_action has no server-registered executor route"
93
+ )
94
+ if payload.get("schema") == "HermesRunV1":
95
+ return (HERMES_RUN_ROUTE,)
96
+ raise ValueError(
97
+ "agent_action requires WorkOrderV1, HermesRunV1, or a registered action"
98
+ )
99
+
100
+
101
+ def bind_agent_action_routes(
102
+ job: Any,
103
+ *,
104
+ allow_legacy_migration: bool = False,
105
+ ) -> tuple[str, ...]:
106
+ """Add derived route requirements and reject caller-selected mismatch."""
107
+
108
+ expected = expected_agent_action_routes(job)
109
+ payload = dict(getattr(job, "payload", None) or {})
110
+ action_hint = str(payload.get("action_hint") or "").strip()
111
+ if action_hint and payload.get("schema") != "WorkOrderV1":
112
+ from apsimo.initiatives.action_registry import get_action
113
+
114
+ spec = get_action(action_hint)
115
+ if spec is not None:
116
+ payload["risk"] = spec.risk.value
117
+ job.payload = payload
118
+ capabilities = list(getattr(job, "capabilities", None) or ())
119
+ names = {
120
+ str(getattr(capability, "name", capability))
121
+ for capability in capabilities
122
+ }
123
+ supplied_routes = names & AGENT_ACTION_ROUTE_CAPABILITIES
124
+ if (
125
+ supplied_routes
126
+ and supplied_routes != set(expected)
127
+ and not (
128
+ allow_legacy_migration
129
+ and payload.get("schema") == "WorkOrderV1"
130
+ )
131
+ ):
132
+ raise ValueError(
133
+ "agent_action route capabilities do not match server policy"
134
+ )
135
+
136
+ from apsimo.task_queue.models import JobCapabilityRequirement
137
+
138
+ # Caller/legacy metadata can never weaken a route by marking it preferred,
139
+ # adding a nonsensical numeric minimum, or duplicating it. Replace every
140
+ # route entry with one canonical required requirement.
141
+ if payload.get("schema") == "WorkOrderV1":
142
+ from apsimo.work_orders import WorkOrderV1
143
+
144
+ order = WorkOrderV1.from_payload(payload)
145
+ # Compatibility fields consumed by the host are reconstructed from the
146
+ # verified authority object. Unknown executable-looking extras and
147
+ # caller-edited description/context never survive queue admission.
148
+ payload = order.payload()
149
+ job.payload = payload
150
+ capability_names = (
151
+ WORK_ORDER_ROUTE,
152
+ ACTION_PLANE_ROUTE,
153
+ *order.capability_allowlist,
154
+ )
155
+ # The embedded authority allowlist is the complete executor contract;
156
+ # arbitrary/missing queue metadata must not broaden or weaken it.
157
+ capabilities = []
158
+ else:
159
+ capability_names = expected
160
+ capabilities = [
161
+ capability for capability in capabilities
162
+ if str(getattr(capability, "name", capability))
163
+ not in AGENT_ACTION_ROUTE_CAPABILITIES
164
+ ]
165
+ capabilities.extend(
166
+ JobCapabilityRequirement(
167
+ name=name,
168
+ minimum=None,
169
+ preferred=False,
170
+ )
171
+ for name in dict.fromkeys(capability_names)
172
+ )
173
+ job.capabilities = capabilities
174
+ tags = dict(getattr(job, "tags", None) or {})
175
+ tags["agent_action_route"] = "+".join(expected)
176
+ if payload.get("schema") == "WorkOrderV1":
177
+ tags.update({
178
+ "schema": order.schema,
179
+ "project_id": order.project_id,
180
+ "step_id": order.step_id,
181
+ "risk_class": order.risk_class,
182
+ "idempotency_key": order.idempotency_key,
183
+ "work_order_digest": order.work_order_digest,
184
+ "work_order_version": str(order.version),
185
+ "executor_protocol": WORK_ORDER_ROUTE,
186
+ "action_result_contract": "ExecutionResultV1",
187
+ })
188
+ elif expected == (ACTION_PLANE_ROUTE,):
189
+ from apsimo.initiatives.approval_authority import (
190
+ build_action_binding,
191
+ )
192
+
193
+ binding = build_action_binding(
194
+ job_id=str(getattr(job, "job_id", "")),
195
+ job_type=str(
196
+ getattr(getattr(job, "job_type", None), "value", "")
197
+ or getattr(job, "job_type", "")
198
+ ),
199
+ payload=payload,
200
+ )
201
+ tags.update({
202
+ "action_digest": binding.action_digest,
203
+ "action_result_contract": "ActionReceiptAttestationV1",
204
+ })
205
+ owners = {
206
+ os.environ.get(_ROUTE_OWNER_ENV[route], "").strip()
207
+ for route in expected
208
+ if os.environ.get(_ROUTE_OWNER_ENV[route], "").strip()
209
+ }
210
+ if len(owners) > 1:
211
+ raise ValueError("agent_action route owners conflict")
212
+ if owners:
213
+ tags["agent_action_route_node"] = next(iter(owners))
214
+ else:
215
+ tags.pop("agent_action_route_node", None)
216
+ job.tags = tags
217
+ return expected
218
+
219
+
220
+ def worker_route_capabilities(*, sync: bool, text: bool) -> set[str]:
221
+ """Build a generic non-effectful worker's explicit route set."""
222
+
223
+ routes: set[str] = set()
224
+ if sync:
225
+ routes.add(AGENT_SYNC_ROUTE)
226
+ if text:
227
+ routes.add(HERMES_RUN_ROUTE)
228
+ return routes
229
+
230
+
231
+ def bind_thought_route(job: Any) -> str:
232
+ """Canonicalize the private ThoughtJobV1 worker route."""
233
+
234
+ payload = getattr(job, "payload", {}) or {}
235
+ from apsimo.cognition.goal_spine import ThoughtJobV1
236
+
237
+ try:
238
+ thought = ThoughtJobV1.from_payload(payload)
239
+ except (KeyError, TypeError, ValueError) as exc:
240
+ raise ValueError(f"invalid ThoughtJobV1 payload: {exc}") from exc
241
+ if str(getattr(job, "job_id", "")) != thought.thought_job_id:
242
+ raise ValueError("thought queue job ID does not match authority digest")
243
+ from apsimo.task_queue.models import JobCapabilityRequirement
244
+
245
+ job.capabilities = [
246
+ JobCapabilityRequirement(name="cognition_scoped"),
247
+ JobCapabilityRequirement(name=THOUGHT_ROUTE),
248
+ ]
249
+ tags = dict(getattr(job, "tags", None) or {})
250
+ tags["thought_route"] = THOUGHT_ROUTE
251
+ tags.update({
252
+ "schema": thought.schema,
253
+ "concern_id": thought.concern_id,
254
+ "viewer_scope": thought.viewer_scope,
255
+ "shareability": thought.shareability,
256
+ "thought_job_digest": thought.thought_job_digest,
257
+ "risk_class": "read_only",
258
+ })
259
+ owner = os.environ.get("COLONY_THOUGHT_WORKER_NODE_ID", "").strip()
260
+ if owner:
261
+ tags["thought_route_node"] = owner
262
+ else:
263
+ tags.pop("thought_route_node", None)
264
+ job.tags = tags
265
+ return THOUGHT_ROUTE
266
+
267
+
268
+ def configured_route_owners() -> dict[str, str | None]:
269
+ """Return deployment-pinned owners by logical executor lane."""
270
+
271
+ return {
272
+ "agent_sync": os.environ.get(
273
+ "COLONY_AGENT_SYNC_WORKER_NODE_ID", ""
274
+ ).strip() or None,
275
+ "action_plane": os.environ.get(
276
+ "COLONY_ACTION_PLANE_WORKER_NODE_ID", ""
277
+ ).strip() or None,
278
+ "work_order": os.environ.get(
279
+ "COLONY_ACTION_PLANE_WORKER_NODE_ID", ""
280
+ ).strip() or None,
281
+ "hermes_run": os.environ.get(
282
+ "COLONY_HERMES_RUN_WORKER_NODE_ID", ""
283
+ ).strip() or None,
284
+ "thought": os.environ.get(
285
+ "COLONY_THOUGHT_WORKER_NODE_ID", ""
286
+ ).strip() or None,
287
+ }
@@ -0,0 +1,252 @@
1
+ """Distributed Task Queue — Queen Scheduler.
2
+
3
+ Runs on the Sovereign node. Drives five scheduling phases per tick:
4
+ 1. Expire past deadlines
5
+ 2. Abandon silent jobs (heartbeat timeout)
6
+ 3. Requeue retryable jobs
7
+ 4. Reconcile explicit governance holds
8
+ 5. Unblock ready jobs (deps met)
9
+ 6. Assign queued jobs to available workers
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import logging
16
+ from datetime import datetime, timezone
17
+ from typing import Any, Callable, Optional
18
+
19
+ from apsimo.task_queue.models import WorkerCapabilities
20
+ from apsimo.task_queue.queue_manager import QueueManager
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class Scheduler:
26
+ """Priority-aware, capability-matching job scheduler.
27
+
28
+ Runs on the Sovereign node. The Regent runs a passive replica
29
+ and takes over if the Sovereign is unavailable.
30
+
31
+ Usage::
32
+
33
+ scheduler = Scheduler(queue=queue_manager)
34
+ task = asyncio.create_task(scheduler.run())
35
+ ...
36
+ await scheduler.stop()
37
+ await task
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ queue: QueueManager,
43
+ tick_interval_secs: float = 2.0,
44
+ heartbeat_timeout_secs: Optional[float] = None,
45
+ claim_timeout_secs: float = 30.0,
46
+ no_worker_warning_secs: float = 300.0,
47
+ event_bus: Optional[Any] = None,
48
+ readiness_callback: Optional[Callable[[bool, str], None]] = None,
49
+ approval_timeout_hours: Optional[float] = None,
50
+ ) -> None:
51
+ self._queue = queue
52
+ self._tick = tick_interval_secs
53
+ if heartbeat_timeout_secs is not None:
54
+ # Backward-compatible constructor overrides update the queue-owned
55
+ # value rather than creating a second liveness interpretation.
56
+ self._queue.configure_worker_heartbeat_ttl(
57
+ heartbeat_timeout_secs
58
+ )
59
+ self._claim_timeout = claim_timeout_secs
60
+ self._no_worker_warning = no_worker_warning_secs
61
+ self._event_bus = event_bus
62
+ self._readiness_callback = readiness_callback
63
+ if approval_timeout_hours is None:
64
+ try:
65
+ import os
66
+ approval_timeout_hours = float(os.environ.get(
67
+ "COLONY_APPROVAL_TIMEOUT_HOURS", "72"
68
+ ))
69
+ except (TypeError, ValueError):
70
+ approval_timeout_hours = 72.0
71
+ if approval_timeout_hours <= 0:
72
+ raise ValueError("approval_timeout_hours must be positive")
73
+ self._approval_timeout_hours = approval_timeout_hours
74
+ self._running = False
75
+ self._last_tick_at: Optional[str] = None
76
+ self._last_error: Optional[str] = None
77
+ self._tick_count = 0
78
+
79
+ @property
80
+ def worker_heartbeat_ttl_secs(self) -> float:
81
+ return self._queue.worker_heartbeat_ttl_secs
82
+
83
+ @property
84
+ def health(self) -> dict:
85
+ return {
86
+ "running": self._running,
87
+ "healthy": self._last_error is None,
88
+ "last_tick_at": self._last_tick_at,
89
+ "last_error": self._last_error,
90
+ "tick_count": self._tick_count,
91
+ }
92
+
93
+ async def run(self) -> None:
94
+ """Main scheduling loop. Blocks until stop() is called."""
95
+ self._running = True
96
+ logger.info("Scheduler started (tick=%.1fs)", self._tick)
97
+ while self._running:
98
+ try:
99
+ await self.tick_once()
100
+ except Exception as exc:
101
+ self._last_error = str(exc)[:500]
102
+ if self._readiness_callback is not None:
103
+ self._readiness_callback(False, self._last_error)
104
+ logger.exception("Scheduler tick failed")
105
+ await asyncio.sleep(self._tick)
106
+ logger.info("Scheduler stopped")
107
+
108
+ async def stop(self) -> None:
109
+ """Signal the scheduling loop to exit after the current tick."""
110
+ self._running = False
111
+ if self._readiness_callback is not None:
112
+ self._readiness_callback(False, "scheduler_stopped")
113
+
114
+ async def tick_once(self) -> None:
115
+ """Public single-tick for testing or manual scheduling."""
116
+ await self._tick_once()
117
+ self._last_tick_at = datetime.now(timezone.utc).isoformat()
118
+ self._last_error = None
119
+ self._tick_count += 1
120
+ if self._readiness_callback is not None:
121
+ self._readiness_callback(True, "scheduler_tick_ok")
122
+
123
+ async def _tick_once(self) -> None:
124
+ now = datetime.now(timezone.utc)
125
+
126
+ # Expire bounded WorkControl acknowledgement leases first. Otherwise
127
+ # a dead/uncooperative worker's pending stop could suppress the very
128
+ # timeout/failure transition needed to close its attempt.
129
+ expired_controls = await self._queue.reconcile_expired_work_controls(
130
+ now,
131
+ )
132
+ if expired_controls:
133
+ logger.info(
134
+ "Scheduler: reconciled %d expired WorkControl commands",
135
+ expired_controls,
136
+ )
137
+
138
+ # Phase 1: Expire deadlines
139
+ expired = await self._queue.expire_past_deadlines(now)
140
+ if expired:
141
+ logger.info("Scheduler: expired %d deadline-past jobs", expired)
142
+
143
+ execution_timeouts = await self._queue.expire_execution_timeouts(now)
144
+ if execution_timeouts:
145
+ logger.info(
146
+ "Scheduler: expired %d execution timeouts",
147
+ execution_timeouts,
148
+ )
149
+
150
+ approval_repairs = await self._queue.reconcile_blocked_approval_authority()
151
+ if approval_repairs:
152
+ logger.info(
153
+ "Scheduler: reconciled %d owner-approval holds",
154
+ approval_repairs,
155
+ )
156
+
157
+ approval_timeouts = await self._queue.expire_blocked_approvals(
158
+ now, timeout_hours=self._approval_timeout_hours,
159
+ )
160
+ if approval_timeouts:
161
+ logger.info(
162
+ "Scheduler: expired %d owner-approval holds",
163
+ approval_timeouts,
164
+ )
165
+
166
+ # Phase 2: Detect abandoned jobs (heartbeat timeout)
167
+ abandoned = await self._queue.abandon_silent_jobs(
168
+ now,
169
+ timeout_secs=self.worker_heartbeat_ttl_secs,
170
+ claim_timeout_secs=self._claim_timeout,
171
+ )
172
+ if abandoned:
173
+ logger.info("Scheduler: abandoned %d silent jobs", abandoned)
174
+
175
+ # Phase 3: Redistribute abandoned/retryable jobs
176
+ requeued = await self._queue.requeue_retryable_jobs(now)
177
+ if requeued:
178
+ logger.info("Scheduler: requeued %d retryable jobs", requeued)
179
+
180
+ # Phase 4: Re-evaluate only explicit boundary/governor holds. This
181
+ # safely handles directive lifts and mode rollback without conflating
182
+ # them with dependency or approval state.
183
+ governance_released = await self._queue.reconcile_governance_holds()
184
+ if governance_released:
185
+ logger.info(
186
+ "Scheduler: released %d governance-held jobs",
187
+ governance_released,
188
+ )
189
+
190
+ # Durable competence/governor events survive cancellation and process
191
+ # restart. Drain them independently from worker execution enablement.
192
+ # Governor delivery may involve an LLM or outbound notice. Schedule
193
+ # its bounded reconciler without ever stalling lease/deadline phases.
194
+ self._queue._schedule_worker_outcome_drain()
195
+
196
+ # Phase 5: Unblock jobs whose dependencies are now met
197
+ unblocked = await self._queue.unblock_ready_jobs()
198
+ if unblocked:
199
+ logger.info("Scheduler: unblocked %d dependent jobs", unblocked)
200
+
201
+ # Reconcile source-owned runtime holds after dependencies so a
202
+ # rollback/re-enable cycle converges before worker notification. The
203
+ # actual claim/start boundaries independently re-evaluate the fence.
204
+ source_reconciler = getattr(
205
+ self._queue, "reconcile_runtime_claim_holds", None,
206
+ )
207
+ source_released = (
208
+ await source_reconciler() if callable(source_reconciler) else 0
209
+ )
210
+ if source_released:
211
+ logger.info(
212
+ "Scheduler: released %d source-runtime-held jobs",
213
+ source_released,
214
+ )
215
+
216
+ # Phase 6: Assign QUEUED jobs to available workers
217
+ await self._assign_queued_jobs(now)
218
+
219
+ async def _assign_queued_jobs(self, now: datetime) -> None:
220
+ queued = await self._queue.get_queued_jobs_sorted(now)
221
+ if not queued:
222
+ return
223
+
224
+ workers = await self._queue.get_available_workers()
225
+
226
+ for job in queued:
227
+ best_worker: Optional[WorkerCapabilities] = None
228
+ best_score = 0.0
229
+
230
+ for worker in workers:
231
+ # Headroom bonus: prefer workers with more open slots
232
+ running_count = round(worker.load * worker.max_concurrent)
233
+ headroom = worker.max_concurrent - running_count
234
+ score = worker.affinity_score(job)
235
+ if headroom >= 2:
236
+ score += 0.05 * (headroom - 1)
237
+ if score > best_score:
238
+ best_score = score
239
+ best_worker = worker
240
+
241
+ if best_worker is None:
242
+ if job.deadline is not None:
243
+ remaining = (job.deadline - now).total_seconds()
244
+ if 0 < remaining < self._no_worker_warning:
245
+ logger.warning(
246
+ "No capable worker for job %s (type=%s, required=%s, "
247
+ "deadline in %.0fs)",
248
+ job.job_id, job.job_type,
249
+ job.required_capabilities(), remaining,
250
+ )
251
+ else:
252
+ await self._queue.notify_worker(best_worker.node_id, job.job_id)