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,914 @@
1
+ """Built-in Initiative Executor service -- closes the autonomy circuit.
2
+
3
+ Processes pending initiatives using Colony's own reasoning pipeline
4
+ (ReasoningLoop + LLMRouter + tools). This is the generic execution
5
+ backend that replaces external webhook delivery for same-machine
6
+ deployments.
7
+
8
+ What it does every cycle (default 30s):
9
+ - Claims pending initiatives from the store
10
+ - Builds a reasoning prompt from the initiative context
11
+ - Runs the ReasoningLoop (LLM + Colony tools) to determine and
12
+ execute the appropriate action
13
+ - Reports results back (complete/fail with retry)
14
+ - Respects rate limits and concurrency caps
15
+
16
+ Auto-starts at sidecar boot when COLONY_EXECUTOR_ENABLED is "true"
17
+ and an LLM provider is configured. Stays dormant otherwise.
18
+
19
+ Environment / config:
20
+ COLONY_EXECUTOR_ENABLED "false" (default) / "true"
21
+ COLONY_EXECUTOR_CYCLE_SECS cycle interval (default 30)
22
+ COLONY_EXECUTOR_MAX_PER_CYCLE max initiatives per cycle (default 5)
23
+ COLONY_EXECUTOR_MODEL_TIER LLM tier: small/medium/large (default "small")
24
+ COLONY_EXECUTOR_TYPES comma-separated initiative types to handle
25
+ (default: all non-self types)
26
+ COLONY_EXECUTOR_AGENT_ID agent identity (default "colony-executor")
27
+ COLONY_EXECUTOR_MAX_TOOL_ITERS max tool-continuation rounds per initiative
28
+ when the reasoning loop returns needs_tool
29
+ (default 8)
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import asyncio
35
+ import json
36
+ import logging
37
+ import os
38
+ import time
39
+ from datetime import datetime, timedelta, timezone
40
+ from typing import Any, Optional
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+ # Every initiative type this internal-tool backend is capable of processing.
45
+ _EXECUTABLE_TYPES = {
46
+ "follow_up", "relationship", "introduction", "health", "scheduling",
47
+ "coding", "subsystem_health", "data_quality", "operational",
48
+ "capability_gap", "knowledge_acquisition", "behavioral_correction",
49
+ "agent_action", "commitment", "calendar", "research", "task",
50
+ "project", "system",
51
+ }
52
+
53
+
54
+ def _default_allowed_types() -> set[str]:
55
+ """Internal-execution types = everything except reach-out types.
56
+
57
+ Reach-out (person-facing) initiatives are delivered through the autonomy
58
+ loop's guarded delivery path (push_initiative -> Hermes), where the host
59
+ agent composes and sends them under the delivery rate limiter. Executing
60
+ them here with the internal-only toolset would just swallow them into
61
+ bookkeeping and starve delivery, so they are excluded by default. A
62
+ deployment can still opt them back in via COLONY_EXECUTOR_TYPES.
63
+ """
64
+ try:
65
+ from apsimo.delivery.classification import reachout_types
66
+ return set(_EXECUTABLE_TYPES) - set(reachout_types())
67
+ except Exception:
68
+ return set(_EXECUTABLE_TYPES)
69
+
70
+
71
+ _DEFAULT_TYPES = _default_allowed_types()
72
+
73
+ # System prompts compose through the shared cognition charter
74
+ # (cognition/charter.py, role "executor"); see _build_system_prompt below.
75
+
76
+
77
+ def _build_initiative_prompt(initiative: Any) -> str:
78
+ itype = getattr(initiative, "type", "") or getattr(initiative, "initiative_type", "")
79
+ desc = getattr(initiative, "description", "")
80
+ rationale = getattr(initiative, "rationale", "")
81
+ action_hint = getattr(initiative, "action_hint", "")
82
+ entity_id = getattr(initiative, "entity_id", "")
83
+ priority = getattr(initiative, "priority", 0.5)
84
+ context = getattr(initiative, "context", None) or {}
85
+
86
+ parts = [f"## Initiative: {itype}", f"**Priority**: {priority:.2f}"]
87
+ if desc:
88
+ parts.append(f"**Description**: {desc}")
89
+ if rationale:
90
+ parts.append(f"**Rationale**: {rationale}")
91
+ if action_hint:
92
+ parts.append(f"**Suggested action**: {action_hint}")
93
+ if entity_id:
94
+ parts.append(f"**Related entity**: {entity_id}")
95
+ if context:
96
+ parts.append(f"**Context**: {json.dumps(context, default=str, indent=2)}")
97
+
98
+ parts.append(
99
+ "\nProcess this initiative using the available tools. "
100
+ "When done, summarize what you accomplished in 1-2 sentences."
101
+ )
102
+ return "\n".join(parts)
103
+
104
+
105
+ _TIMEOUT_MARKERS = (
106
+ "timeout", "timed out", "deadline", "read operation timed out",
107
+ "readtimeout", "timeouterror", "etimedout",
108
+ )
109
+
110
+ _TERM_STOP = frozenset({
111
+ "the", "a", "an", "to", "of", "on", "in", "for", "with", "and", "or",
112
+ "follow", "up", "check", "review", "investigate", "diagnose", "verify",
113
+ "initiative", "task", "this", "that", "about", "into", "your", "my",
114
+ })
115
+
116
+
117
+ def _is_timeout_error(err: Optional[str]) -> bool:
118
+ if not err:
119
+ return False
120
+ low = str(err).lower()
121
+ return any(m in low for m in _TIMEOUT_MARKERS)
122
+
123
+
124
+ def _subject_terms(text: Optional[str]) -> set:
125
+ """Significant tokens of an initiative subject, for repeat-work matching."""
126
+ import re as _re
127
+ toks = _re.findall(r"[a-z0-9][a-z0-9_\-]{2,}", (text or "").lower())
128
+ return {t for t in toks if t not in _TERM_STOP}
129
+
130
+
131
+ def _assistant_tool_message(
132
+ content: str, tool_calls: list[dict[str, Any]],
133
+ ) -> dict[str, Any]:
134
+ """Build an OpenAI-shaped assistant message carrying tool calls.
135
+
136
+ Mirrors ``ReasoningLoop._build_assistant_message`` so the continuation
137
+ messages we feed back into ``run_turn`` have the exact shape the model
138
+ provider expects (tool_call ids on the assistant turn must match the
139
+ following ``role: "tool"`` result turns).
140
+ """
141
+ msg: dict[str, Any] = {"role": "assistant", "content": content or None}
142
+ if tool_calls:
143
+ msg["tool_calls"] = [
144
+ {
145
+ "id": tc.get("id", ""),
146
+ "type": "function",
147
+ "function": {
148
+ "name": tc.get("name", ""),
149
+ "arguments": (
150
+ tc["arguments"]
151
+ if isinstance(tc.get("arguments"), str)
152
+ else json.dumps(tc.get("arguments", {}))
153
+ ),
154
+ },
155
+ }
156
+ for tc in tool_calls
157
+ ]
158
+ return msg
159
+
160
+
161
+ class InitiativeExecutorService:
162
+ """Async service that executes pending initiatives via the reasoning loop."""
163
+
164
+ def __init__(
165
+ self,
166
+ initiative_store: Any,
167
+ reasoning_loop: Any,
168
+ tool_executor: Any = None,
169
+ cycle_secs: float = 30.0,
170
+ max_per_cycle: int = 5,
171
+ model_tier: str = "small",
172
+ allowed_types: Optional[set[str]] = None,
173
+ agent_id: str = "colony-executor",
174
+ max_tool_iterations: int = 8,
175
+ directive_manager: Any = None,
176
+ skill_store: Any = None,
177
+ self_model: Any = None,
178
+ ):
179
+ self._store = initiative_store
180
+ self._reasoning = reasoning_loop
181
+ self._tools = tool_executor
182
+ # Boundary enforcement: consulted before executing an initiative so the
183
+ # executor never acts on a subject the owner told it to leave alone.
184
+ self._directives = directive_manager
185
+ # Compounding learning (item 3) + self-model/trust (item 4).
186
+ self._skills = skill_store
187
+ self._self_model = self_model
188
+ self._cycle_secs = cycle_secs
189
+ self._max_per_cycle = max_per_cycle
190
+ self._model_tier = model_tier
191
+ self._allowed_types = allowed_types or _DEFAULT_TYPES
192
+ self._agent_id = agent_id
193
+ # Upper bound on continuation rounds when the reasoning loop returns
194
+ # ``needs_tool`` (its internal per-call budget was exhausted mid-action).
195
+ # Each round executes the pending tool calls and re-enters run_turn.
196
+ self._max_tool_iterations = max(1, int(max_tool_iterations))
197
+
198
+ self._running = False
199
+ self._stop_event = asyncio.Event()
200
+ # Skill attribution: which skills were retrieved into the prompt for
201
+ # each in-flight initiative, so their win/loss record reflects the
202
+ # outcome of the runs they informed (closes the item-3 feedback loop).
203
+ self._skills_used: dict[str, list[str]] = {}
204
+ self._stats = {
205
+ "cycles": 0,
206
+ "initiatives_processed": 0,
207
+ "initiatives_completed": 0,
208
+ "initiatives_failed": 0,
209
+ "total_tool_calls": 0,
210
+ "total_tokens": 0,
211
+ "boundary_check_errors": 0,
212
+ }
213
+
214
+ @property
215
+ def is_running(self) -> bool:
216
+ return self._running
217
+
218
+ @property
219
+ def stats(self) -> dict:
220
+ return dict(self._stats)
221
+
222
+ # ------------------------------------------------------------------
223
+ # Lifecycle
224
+ # ------------------------------------------------------------------
225
+
226
+ async def start(self) -> None:
227
+ if self._reasoning is None:
228
+ logger.info("Initiative executor: no reasoning loop, staying dormant")
229
+ return
230
+
231
+ self._running = True
232
+ self._stop_event.clear()
233
+ logger.info(
234
+ "Initiative executor starting (cycle=%ds, max=%d/cycle, tier=%s, types=%s)",
235
+ int(self._cycle_secs),
236
+ self._max_per_cycle,
237
+ self._model_tier,
238
+ ",".join(sorted(self._allowed_types)),
239
+ )
240
+
241
+ try:
242
+ while not self._stop_event.is_set():
243
+ await self._cycle()
244
+ try:
245
+ await asyncio.wait_for(
246
+ self._stop_event.wait(), timeout=self._cycle_secs
247
+ )
248
+ except asyncio.TimeoutError:
249
+ pass
250
+ finally:
251
+ self._running = False
252
+ logger.info("Initiative executor stopped. Stats: %s", self._stats)
253
+
254
+ async def stop(self) -> None:
255
+ logger.info("Initiative executor stop requested")
256
+ self._stop_event.set()
257
+
258
+ # ------------------------------------------------------------------
259
+ # Main cycle
260
+ # ------------------------------------------------------------------
261
+
262
+ async def _cycle(self) -> None:
263
+ self._stats["cycles"] += 1
264
+
265
+ pending = await self._claim_pending()
266
+ if not pending:
267
+ return
268
+
269
+ for initiative in pending:
270
+ if self._stop_event.is_set():
271
+ break
272
+ await self._execute_one(initiative)
273
+
274
+ # ------------------------------------------------------------------
275
+ # Claim pending initiatives
276
+ # ------------------------------------------------------------------
277
+
278
+ async def _claim_pending(self) -> list:
279
+ if self._store is None:
280
+ return []
281
+
282
+ try:
283
+ loop = asyncio.get_event_loop()
284
+ pending = await loop.run_in_executor(
285
+ None,
286
+ lambda: self._store.list(
287
+ status=["pending"],
288
+ limit=self._max_per_cycle,
289
+ ),
290
+ )
291
+ except Exception as exc:
292
+ logger.warning("Failed to list pending initiatives: %s", exc)
293
+ return []
294
+
295
+ claimed = []
296
+ for initiative in pending:
297
+ itype = getattr(initiative, "type", "") or getattr(initiative, "initiative_type", "")
298
+ if itype not in self._allowed_types:
299
+ continue
300
+
301
+ iid = getattr(initiative, "id", "")
302
+ try:
303
+ loop = asyncio.get_event_loop()
304
+ result = await loop.run_in_executor(
305
+ None,
306
+ lambda id_=iid: self._store.assign(id_, self._agent_id),
307
+ )
308
+ if result is not None:
309
+ claimed.append(result)
310
+ except Exception as exc:
311
+ logger.debug("Failed to claim initiative %s: %s", iid, exc)
312
+
313
+ if claimed:
314
+ logger.info("Claimed %d initiatives for execution", len(claimed))
315
+ return claimed
316
+
317
+ # ------------------------------------------------------------------
318
+ # Execute a single initiative
319
+ # ------------------------------------------------------------------
320
+
321
+ async def _execute_one(self, initiative: Any) -> None:
322
+ iid = getattr(initiative, "id", "")
323
+ itype = getattr(initiative, "type", "") or getattr(initiative, "initiative_type", "")
324
+ start = time.monotonic()
325
+ model_provenance = {'requested_role': self._model_tier}
326
+
327
+ logger.info("Executing initiative %s (%s)", iid, itype)
328
+
329
+ # Boundary gate: refuse to act on anything the owner set as off-limits.
330
+ if self._directives is not None:
331
+ try:
332
+ from apsimo.directives import Action
333
+ desc = getattr(initiative, "description", "") or ""
334
+ rationale = getattr(initiative, "rationale", "") or ""
335
+ entity_id = getattr(initiative, "entity_id", "") or ""
336
+ verdict = self._directives.check(Action(
337
+ kind="execute",
338
+ text=f"{desc} {rationale}",
339
+ target=entity_id,
340
+ entity_id=entity_id,
341
+ ))
342
+ if not verdict.allowed:
343
+ logger.warning(
344
+ "Initiative %s (%s) REFUSED by boundary: %s",
345
+ iid, itype, verdict.reason,
346
+ )
347
+ # Terminal: a boundary is not a transient failure, so do not
348
+ # retry (it would just be refused again until lifted).
349
+ await self._fail_initiative(
350
+ iid, f"refused: {verdict.reason}", retry=False,
351
+ )
352
+ self._stats["initiatives_failed"] += 1
353
+ self._stats["initiatives_processed"] += 1
354
+ return
355
+ except Exception:
356
+ # An owner boundary we cannot evaluate must not be assumed
357
+ # permissive: fail CLOSED by default (a delayed initiative is
358
+ # recoverable; acting on a forbidden subject is not). Retryable:
359
+ # a healthy store on a later cycle evaluates it properly.
360
+ self._stats["boundary_check_errors"] += 1
361
+ from apsimo.directives.guard import boundary_fail_closed
362
+ if boundary_fail_closed():
363
+ logger.warning(
364
+ "Initiative %s (%s) REFUSED: boundary_check_error "
365
+ "(boundary check raised; failing closed)",
366
+ iid, itype, exc_info=True,
367
+ )
368
+ await self._fail_initiative(
369
+ iid, "boundary_check_error: boundary check raised; "
370
+ "failing closed", retry=True,
371
+ )
372
+ self._stats["initiatives_failed"] += 1
373
+ self._stats["initiatives_processed"] += 1
374
+ return
375
+ logger.debug("boundary pre-check failed (allowing)", exc_info=True)
376
+
377
+ # Repeat-work suppression: if a recent completion already covers this
378
+ # subject, close it with a pointer instead of re-running the reasoning.
379
+ try:
380
+ dup = await self._find_recent_completion(initiative)
381
+ except Exception:
382
+ dup = None
383
+ if dup is not None:
384
+ dup_id = getattr(dup, "id", "?")
385
+ dup_res = (getattr(dup, "result", "") or "")[:200]
386
+ logger.info("Initiative %s (%s) already covered by %s — skipping re-run",
387
+ iid, itype, dup_id)
388
+ await self._complete_initiative(
389
+ iid, f"Already addressed by recent completion {dup_id}. {dup_res}")
390
+ self._stats["initiatives_completed"] += 1
391
+ self._stats["initiatives_processed"] += 1
392
+ return
393
+
394
+ try:
395
+ prompt = _build_initiative_prompt(initiative)
396
+ session_id = f"executor-{iid}"
397
+ is_research = itype in ("research", "knowledge_acquisition")
398
+
399
+ # Charter-composed system prompt: shared doctrine + executor role
400
+ # + injected self-model brief, boundaries (soft layer atop the
401
+ # hard gate above and the per-tool gate below), retrieved
402
+ # procedure memory, and avoid-lines from past failures.
403
+ system_prompt = self._build_system_prompt(initiative, itype)
404
+
405
+ # Conversation accumulated across continuation rounds. The reasoning
406
+ # loop runs its own internal tool loop, but returns ``needs_tool``
407
+ # when its per-call iteration budget is exhausted while an action is
408
+ # still pending. We then execute the pending tool calls ourselves and
409
+ # re-enter run_turn with the results appended, extending the budget.
410
+ working: list[dict[str, Any]] = [{"role": "user", "content": prompt}]
411
+ total_tokens = 0
412
+ iterations = 0
413
+ done = False
414
+
415
+ while iterations < self._max_tool_iterations:
416
+ iterations += 1
417
+ result = await self._run_turn_resilient(
418
+ session_id=session_id,
419
+ messages=working,
420
+ system_prompt=system_prompt,
421
+ is_research=is_research,
422
+ )
423
+ model_provenance.update(getattr(result, 'model_provenance', {}) or {})
424
+
425
+ usage = result.usage or {}
426
+ total_tokens += usage.get("total_tokens", 0)
427
+
428
+ if result.status == "completed":
429
+ response_text = ""
430
+ if result.message:
431
+ response_text = result.message.get("content", "") or ""
432
+ await self._complete_initiative(iid, response_text)
433
+ self._stats["initiatives_completed"] += 1
434
+ self._stats["total_tokens"] += total_tokens
435
+ elapsed = time.monotonic() - start
436
+ logger.info(
437
+ "Initiative %s completed in %.1fs (%d tokens, %d round(s)): %s",
438
+ iid, elapsed, total_tokens, iterations,
439
+ response_text[:120],
440
+ )
441
+ self._record_outcome(itype, "success", elapsed, initiative=initiative, model_provenance=model_provenance)
442
+ await self._maybe_distill(initiative, itype, response_text)
443
+ done = True
444
+ break
445
+
446
+ if result.status == "error":
447
+ await self._fail_initiative(
448
+ iid, result.error or "reasoning error", retry=True
449
+ )
450
+ self._stats["initiatives_failed"] += 1
451
+ self._stats["total_tokens"] += total_tokens
452
+ logger.warning("Initiative %s failed: %s", iid, result.error)
453
+ self._record_outcome(
454
+ itype,
455
+ "timeout" if _is_timeout_error(result.error) else "failure",
456
+ time.monotonic() - start, initiative=initiative, model_provenance=model_provenance)
457
+ self._note_failure(initiative, itype, result.error or "")
458
+ done = True
459
+ break
460
+
461
+ if result.status == "needs_tool":
462
+ pending = list(result.tool_calls or [])
463
+ if not pending:
464
+ # needs_tool with nothing to run: no forward progress
465
+ # possible, so stop rather than spin.
466
+ await self._fail_initiative(
467
+ iid,
468
+ "needs_tool returned no tool calls",
469
+ retry=True,
470
+ )
471
+ self._stats["initiatives_failed"] += 1
472
+ self._stats["total_tokens"] += total_tokens
473
+ logger.warning(
474
+ "Initiative %s: needs_tool with empty tool_calls", iid
475
+ )
476
+ done = True
477
+ break
478
+
479
+ if self._tools is None:
480
+ await self._fail_initiative(
481
+ iid,
482
+ "needs_tool but no tool executor is wired",
483
+ retry=True,
484
+ )
485
+ self._stats["initiatives_failed"] += 1
486
+ self._stats["total_tokens"] += total_tokens
487
+ logger.warning(
488
+ "Initiative %s needs a tool but no ToolExecutor is "
489
+ "injected; cannot proceed", iid,
490
+ )
491
+ done = True
492
+ break
493
+
494
+ # Per-tool boundary gate (defense in depth): a boundary-
495
+ # violating tool call the model produced mid-reasoning is
496
+ # dropped and reported back so it adapts, rather than run.
497
+ if self._directives is not None:
498
+ pending = self._filter_tool_calls_by_boundary(
499
+ pending, working,
500
+ )
501
+ if not pending:
502
+ # everything was blocked; give the model the refusal
503
+ # and let it continue or wrap up.
504
+ working.append({
505
+ "role": "user",
506
+ "content": "Those actions violate a standing boundary "
507
+ "from the owner and were refused. Do not "
508
+ "attempt them; summarise and stop.",
509
+ })
510
+ continue
511
+
512
+ # Execute the pending tool calls through the SAME executor the
513
+ # reasoning loop uses internally (identical gating/handlers),
514
+ # then feed the results back for another round.
515
+ tool_results = await self._tools.execute_batch(
516
+ pending, session_id=session_id,
517
+ )
518
+ self._stats["total_tool_calls"] += len(pending)
519
+
520
+ working.append(_assistant_tool_message(
521
+ result.message.get("content", "") if result.message else "",
522
+ pending,
523
+ ))
524
+ for tr in tool_results:
525
+ working.append({
526
+ "role": "tool",
527
+ "tool_call_id": tr.get("tool_call_id", ""),
528
+ "content": tr.get("content", ""),
529
+ })
530
+ logger.info(
531
+ "Initiative %s round %d: executed %d tool call(s), continuing",
532
+ iid, iterations, len(pending),
533
+ )
534
+ continue
535
+
536
+ # Any status outside the known domain must never be dropped
537
+ # silently again.
538
+ await self._fail_initiative(
539
+ iid, f"unexpected status: {result.status}", retry=True
540
+ )
541
+ self._stats["initiatives_failed"] += 1
542
+ self._stats["total_tokens"] += total_tokens
543
+ logger.warning(
544
+ "Initiative %s: unexpected reasoning status %r", iid, result.status
545
+ )
546
+ done = True
547
+ break
548
+
549
+ if not done:
550
+ # Hit the continuation cap without completing.
551
+ await self._fail_initiative(
552
+ iid,
553
+ f"tool-loop cap reached after {self._max_tool_iterations} rounds",
554
+ retry=True,
555
+ )
556
+ self._stats["initiatives_failed"] += 1
557
+ self._stats["total_tokens"] += total_tokens
558
+ self._record_outcome(itype, "failure",
559
+ time.monotonic() - start,
560
+ initiative=initiative, model_provenance=model_provenance)
561
+ logger.warning(
562
+ "Initiative %s hit tool-loop cap (%d rounds) without completing",
563
+ iid, self._max_tool_iterations,
564
+ )
565
+
566
+ except Exception as exc:
567
+ elapsed = time.monotonic() - start
568
+ logger.error("Initiative %s execution error (%.1fs): %s", iid, elapsed, exc)
569
+ await self._fail_initiative(iid, str(exc), retry=True)
570
+ self._stats["initiatives_failed"] += 1
571
+ self._record_outcome(
572
+ itype, "timeout" if _is_timeout_error(str(exc)) else "failure",
573
+ elapsed, initiative=initiative, model_provenance=model_provenance)
574
+
575
+ self._stats["initiatives_processed"] += 1
576
+
577
+ # ------------------------------------------------------------------
578
+ # Self-model + skills-memory hooks (items 3 + 4)
579
+ # ------------------------------------------------------------------
580
+
581
+ def _build_system_prompt(self, initiative: Any, itype: str) -> str:
582
+ """Compose the executor prompt through the shared charter."""
583
+ self_brief = boundaries = skills_block = None
584
+ corrections: list[str] = []
585
+ if self._directives is not None:
586
+ try:
587
+ boundaries = self._directives.context_brief() or None
588
+ except Exception:
589
+ pass
590
+ if self._self_model is not None:
591
+ try:
592
+ self_brief = self._self_model.brief() or None
593
+ except Exception:
594
+ pass
595
+ if self._skills is not None:
596
+ try:
597
+ from apsimo.skills_memory import (
598
+ format_block, relevant_skills, skills_enabled,
599
+ )
600
+ if skills_enabled():
601
+ retrieved = relevant_skills(
602
+ self._skills,
603
+ f"{itype} {getattr(initiative, 'description', '')}",
604
+ k=3, domain=itype)
605
+ skills_block = format_block(retrieved) or None
606
+ # Remember which skills informed this run so the outcome
607
+ # can be attributed back to them (wins/losses).
608
+ iid = getattr(initiative, "id", None)
609
+ if iid and retrieved:
610
+ self._skills_used[str(iid)] = [
611
+ s.id for s in retrieved if getattr(s, "id", None)]
612
+ # Failure post-mortems become avoid-lines.
613
+ note = self._skills.get_note(itype)
614
+ corrections = [ln.lstrip("- ").strip()
615
+ for ln in note.splitlines() if ln.strip()]
616
+ except Exception:
617
+ logger.debug("skills block failed", exc_info=True)
618
+ try:
619
+ from apsimo.cognition.charter import build_system_prompt
620
+ return build_system_prompt(
621
+ "executor", self_brief=self_brief, boundaries=boundaries,
622
+ skills=skills_block, corrections=corrections or None)
623
+ except Exception:
624
+ logger.debug("charter compose failed; minimal fallback",
625
+ exc_info=True)
626
+ return ("You execute one initiative for Colony using the "
627
+ "available tools; report a truthful result."
628
+ + (f"\n\nStanding boundaries (obey):\n{boundaries}"
629
+ if boundaries else ""))
630
+
631
+ def _record_outcome(self, itype: str, outcome: str,
632
+ latency: Optional[float] = None,
633
+ initiative: Any = None, model_provenance: Optional[dict] = None) -> None:
634
+ # Attribute the outcome to the skills that informed this run
635
+ # (item 3 feedback edge): success bumps wins, anything else losses.
636
+ self._record_skill_outcomes(initiative, outcome == "success")
637
+ if self._self_model is None:
638
+ return
639
+ try:
640
+ # Stated-vs-realized calibration: a thinker/planner-stated
641
+ # confidence rides in the initiative context (charter contract).
642
+ stated = None
643
+ if initiative is not None:
644
+ ctx = getattr(initiative, "context", None) or {}
645
+ if isinstance(ctx, dict):
646
+ stated = ctx.get("stated_confidence")
647
+ try:
648
+ stated = float(stated) if stated is not None else None
649
+ except (TypeError, ValueError):
650
+ stated = None
651
+ iid = str(getattr(initiative, 'id', '') or '')
652
+ attempt = int(getattr(initiative, 'attempt_count', 0) or 0)
653
+ self._self_model.record(itype, outcome, latency_secs=latency,
654
+ stated_confidence=stated, source='initiative_executor',
655
+ source_ref=iid or None, event_key=f'{iid}:{attempt}:{outcome}' if iid else None,
656
+ evidence_status='observed', outcome_contract='colony.initiative-runtime-outcome/v1',
657
+ evidence={**(model_provenance or {}), 'attempt': attempt,
658
+ 'meaning': 'runtime_completed_or_failed; semantic_success_unverified'})
659
+ except Exception:
660
+ pass
661
+
662
+ def _record_skill_outcomes(self, initiative: Any, win: bool) -> None:
663
+ """Update wins/losses on every skill retrieved into this run's prompt."""
664
+ if self._skills is None or initiative is None:
665
+ return
666
+ iid = str(getattr(initiative, "id", "") or "")
667
+ skill_ids = self._skills_used.pop(iid, None)
668
+ if not skill_ids:
669
+ return
670
+ for sid in skill_ids:
671
+ try:
672
+ self._skills.record_outcome(sid, win)
673
+ except Exception:
674
+ logger.debug("skill outcome record failed for %s", sid,
675
+ exc_info=True)
676
+
677
+ def _note_failure(self, initiative: Any, itype: str, error: str) -> None:
678
+ """Failure post-mortem: keep a short per-domain strategy note."""
679
+ if self._skills is None:
680
+ return
681
+ try:
682
+ desc = (getattr(initiative, "description", "") or "")[:80]
683
+ self._skills.record_failure_note(
684
+ itype, f"'{desc}' failed: {(error or 'unknown')[:120]}")
685
+ except Exception:
686
+ pass
687
+
688
+ async def _maybe_distill(self, initiative: Any, itype: str,
689
+ response_text: str) -> None:
690
+ """Distill a reusable procedure from a qualifying completion."""
691
+ if self._skills is None:
692
+ return
693
+ try:
694
+ from apsimo.skills_memory import (
695
+ distill_from_completion, should_distill, skills_distill_mode,
696
+ )
697
+ if skills_distill_mode() == "off":
698
+ return
699
+ attempts = int(getattr(initiative, "attempt_count", 0) or 0)
700
+ if not should_distill(max(0, attempts - 1), response_text,
701
+ self._skills):
702
+ return
703
+ router = getattr(self._reasoning, "_model", None)
704
+ await distill_from_completion(
705
+ router, self._skills, domain=itype,
706
+ task_text=getattr(initiative, "description", "") or "",
707
+ result_text=response_text,
708
+ source_ref=getattr(initiative, "id", "") or "")
709
+ except Exception:
710
+ logger.debug("skill distillation failed", exc_info=True)
711
+
712
+ def _filter_tool_calls_by_boundary(
713
+ self, pending: list, working: list,
714
+ ) -> list:
715
+ """Drop tool calls that violate a standing boundary. Returns survivors."""
716
+ if self._directives is None:
717
+ return pending
718
+ try:
719
+ from apsimo.directives import Action
720
+ from apsimo.directives.guard import boundary_fail_closed
721
+ except Exception:
722
+ # Cannot even construct a check: never assume permissive.
723
+ logger.warning(
724
+ "Tool boundary gate unavailable (directives import failed); "
725
+ "dropping %d pending tool call(s) (failing closed)",
726
+ len(pending), exc_info=True,
727
+ )
728
+ return []
729
+ survivors = []
730
+ for tc in pending:
731
+ name = tc.get("name", "")
732
+ args = tc.get("arguments", {}) if isinstance(tc.get("arguments"), dict) else {}
733
+ try:
734
+ verdict = self._directives.check(Action(
735
+ kind="execute_tool", tool_name=name, args=args,
736
+ text=name, high_risk=True,
737
+ ))
738
+ except Exception:
739
+ # An owner boundary we cannot evaluate must not be assumed
740
+ # permissive: fail CLOSED by default and drop the call.
741
+ self._stats["boundary_check_errors"] += 1
742
+ if boundary_fail_closed():
743
+ logger.warning(
744
+ "Tool call %s REFUSED: boundary_check_error "
745
+ "(boundary check raised; failing closed)",
746
+ name, exc_info=True,
747
+ )
748
+ continue
749
+ logger.debug("tool boundary check failed (allowing)",
750
+ exc_info=True)
751
+ survivors.append(tc)
752
+ continue
753
+ if not verdict.allowed:
754
+ logger.warning(
755
+ "Tool call %s REFUSED by boundary: %s", name, verdict.reason,
756
+ )
757
+ continue
758
+ survivors.append(tc)
759
+ return survivors
760
+
761
+ # ------------------------------------------------------------------
762
+ # Store callbacks
763
+ # ------------------------------------------------------------------
764
+
765
+ async def _run_turn_resilient(self, *, session_id, messages, system_prompt, is_research):
766
+ """run_turn with adaptive retry+backoff on M3 timeouts (resilience -- 5).
767
+
768
+ A transient timeout is absorbed by retrying in-call (no store attempt is
769
+ burned). Research-type work gets more retries. Only a persistent timeout
770
+ falls through to the normal error handling.
771
+ """
772
+ max_retries = 3 if is_research else 2
773
+ backoff = 2.0
774
+ result = None
775
+ for attempt in range(max_retries + 1):
776
+ result = await self._reasoning.run_turn(
777
+ session_id=session_id, messages=messages,
778
+ system_prompt=system_prompt, model_override=self._model_tier,
779
+ )
780
+ if result.status != "error" or not _is_timeout_error(result.error):
781
+ return result
782
+ if attempt < max_retries:
783
+ logger.info("run_turn timeout (attempt %d/%d), backing off %.0fs",
784
+ attempt + 1, max_retries + 1, backoff)
785
+ try:
786
+ await asyncio.sleep(backoff)
787
+ except Exception:
788
+ pass
789
+ backoff *= 2
790
+ return result
791
+
792
+ async def _find_recent_completion(self, initiative: Any) -> Any:
793
+ """A recently-completed initiative that already covers this subject."""
794
+ if self._store is None:
795
+ return None
796
+ itype = getattr(initiative, "type", "") or getattr(initiative, "initiative_type", "")
797
+ iid = getattr(initiative, "id", "")
798
+ entity_id = getattr(initiative, "entity_id", "") or ""
799
+ my_terms = _subject_terms(getattr(initiative, "description", ""))
800
+ if not itype:
801
+ return None
802
+ window_h = float(os.environ.get("COLONY_EXECUTOR_REPEAT_WINDOW_HOURS", "48"))
803
+ since = datetime.now(timezone.utc) - timedelta(hours=window_h)
804
+ try:
805
+ loop = asyncio.get_event_loop()
806
+ recent = await loop.run_in_executor(
807
+ None,
808
+ lambda: self._store.list(
809
+ status=["completed"], type=itype, created_after=since, limit=50),
810
+ )
811
+ except Exception:
812
+ return None
813
+ for r in recent or []:
814
+ if getattr(r, "id", "") == iid:
815
+ continue
816
+ if entity_id and getattr(r, "entity_id", "") == entity_id:
817
+ return r
818
+ rt = _subject_terms(getattr(r, "description", ""))
819
+ if my_terms and rt:
820
+ overlap = len(my_terms & rt) / max(1, min(len(my_terms), len(rt)))
821
+ if overlap >= 0.7:
822
+ return r
823
+ return None
824
+
825
+ async def _complete_initiative(self, initiative_id: str, result: str) -> None:
826
+ try:
827
+ loop = asyncio.get_event_loop()
828
+ await loop.run_in_executor(
829
+ None,
830
+ lambda: self._store.complete(
831
+ initiative_id,
832
+ agent_id=self._agent_id,
833
+ result=result,
834
+ ),
835
+ )
836
+ except Exception as exc:
837
+ logger.warning("Failed to mark initiative %s complete: %s", initiative_id, exc)
838
+
839
+ async def _fail_initiative(
840
+ self, initiative_id: str, reason: str, retry: bool = False,
841
+ ) -> None:
842
+ try:
843
+ loop = asyncio.get_event_loop()
844
+ await loop.run_in_executor(
845
+ None,
846
+ lambda: self._store.fail(
847
+ initiative_id,
848
+ agent_id=self._agent_id,
849
+ reason=reason,
850
+ retry=retry,
851
+ ),
852
+ )
853
+ except Exception as exc:
854
+ logger.warning("Failed to mark initiative %s failed: %s", initiative_id, exc)
855
+
856
+
857
+ # ---------------------------------------------------------------------------
858
+ # Factory + wiring
859
+ # ---------------------------------------------------------------------------
860
+
861
+ def create_from_env(
862
+ initiative_store: Any = None,
863
+ reasoning_loop: Any = None,
864
+ tool_executor: Any = None,
865
+ directive_manager: Any = None,
866
+ skill_store: Any = None,
867
+ self_model: Any = None,
868
+ ) -> Optional[InitiativeExecutorService]:
869
+ """Create an InitiativeExecutorService from environment variables.
870
+
871
+ Returns None if COLONY_EXECUTOR_ENABLED is not "true" or required
872
+ dependencies are missing.
873
+ """
874
+ from apsimo.util.autonomy_preset import resolve_bool
875
+ if not resolve_bool("COLONY_EXECUTOR_ENABLED", False):
876
+ logger.info(
877
+ "Initiative executor disabled (COLONY_EXECUTOR_ENABLED != true). "
878
+ "Set COLONY_EXECUTOR_ENABLED=true or COLONY_AUTONOMY_PRESET="
879
+ "calibration|autonomous to enable autonomous initiative processing."
880
+ )
881
+ return None
882
+
883
+ if reasoning_loop is None:
884
+ logger.warning(
885
+ "Initiative executor: no ReasoningLoop available (LLM not configured). "
886
+ "Cannot execute initiatives without an LLM."
887
+ )
888
+ return None
889
+
890
+ if initiative_store is None:
891
+ logger.warning("Initiative executor: no InitiativeStore available.")
892
+ return None
893
+
894
+ types_env = os.environ.get("COLONY_EXECUTOR_TYPES", "")
895
+ allowed_types = (
896
+ {t.strip() for t in types_env.split(",") if t.strip()}
897
+ if types_env
898
+ else None
899
+ )
900
+
901
+ return InitiativeExecutorService(
902
+ initiative_store=initiative_store,
903
+ reasoning_loop=reasoning_loop,
904
+ tool_executor=tool_executor,
905
+ directive_manager=directive_manager,
906
+ skill_store=skill_store,
907
+ self_model=self_model,
908
+ cycle_secs=float(os.environ.get("COLONY_EXECUTOR_CYCLE_SECS", "30")),
909
+ max_per_cycle=int(os.environ.get("COLONY_EXECUTOR_MAX_PER_CYCLE", "5")),
910
+ model_tier=os.environ.get("COLONY_EXECUTOR_MODEL_TIER", "small"),
911
+ allowed_types=allowed_types,
912
+ agent_id=os.environ.get("COLONY_EXECUTOR_AGENT_ID", "colony-executor"),
913
+ max_tool_iterations=int(os.environ.get("COLONY_EXECUTOR_MAX_TOOL_ITERS", "8")),
914
+ )