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,15 @@
1
+ """Turn middleware — host-agnostic cognitive steps that run around
2
+ each conversation turn.
3
+
4
+ These modules lift the Colony-distinctive cognition out of
5
+ ``run_agent.py`` so it is reachable from any host (OpenClaw plugin,
6
+ future adapters, standalone). See ``docs/HOST_API.md`` for the
7
+ endpoints that expose them.
8
+ """
9
+
10
+ from apsimo.intelligence.turn_middleware.memory_sync import (
11
+ TurnSyncOutcome,
12
+ sync_turn_memory,
13
+ )
14
+
15
+ __all__ = ["TurnSyncOutcome", "sync_turn_memory"]
@@ -0,0 +1,119 @@
1
+ """Post-turn memory sync — lifted from ``run_agent.py:_memory_sync``.
2
+
3
+ Host-agnostic. Accepts a plain dataclass describing what just happened
4
+ in the turn and routes the distilled signal into whichever continuity
5
+ store the host router has been wired with. Returns a
6
+ ``TurnSyncOutcome`` describing what was touched; the caller is
7
+ responsible for emitting any host events.
8
+
9
+ Two sink shapes are supported via duck-typing so the endpoint works
10
+ whether the server wires the in-process
11
+ ``colony.intelligence.components.session_continuity.SessionContinuity``
12
+ (expected when colony runs in-process — no HTTP loopback) or the
13
+ HTTP-client ``agent.memory_bridge.AgentMemoryBridge`` (useful for
14
+ standalone runs where the agent and API are in different processes).
15
+
16
+ Resolution order:
17
+
18
+ 1. Bridge shape — ``is_available`` property + ``async
19
+ update_continuity(session_id, topics, entities, pending_tasks,
20
+ tools_used)``.
21
+ 2. Continuity shape — ``async update_context(session_id, topics,
22
+ entities, pending_tasks)`` (note: ``tools_used`` is dropped because
23
+ ``SessionContinuity`` does not persist it today; if it starts doing
24
+ so, extend the branch below.)
25
+
26
+ Background review (``_spawn_background_review`` in ``run_agent.py``)
27
+ is intentionally NOT moved here. It forks a full ``AIAgent`` and
28
+ recurses into ``run_conversation``, so it can only cleanly lift out of
29
+ ``run_agent.py`` when the reasoning loop itself is extracted in
30
+ Stage B. OpenClaw owns reasoning today, so this is not blocking.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import logging
36
+ from dataclasses import dataclass, field
37
+ from typing import Any, List, Optional
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ @dataclass
43
+ class TurnSyncOutcome:
44
+ """Result of one ``sync_turn_memory`` call."""
45
+
46
+ continuity_updated: bool = False
47
+ skipped_reason: Optional[str] = None
48
+ errors: List[str] = field(default_factory=list)
49
+
50
+
51
+ async def sync_turn_memory(
52
+ sink: Any,
53
+ session_id: str,
54
+ *,
55
+ topics: Optional[List[str]] = None,
56
+ entities: Optional[List[str]] = None,
57
+ pending_tasks: Optional[List[str]] = None,
58
+ tools_used: Optional[List[str]] = None,
59
+ ) -> TurnSyncOutcome:
60
+ """Update session continuity after a turn completes.
61
+
62
+ Never raises — unexpected errors are captured on the returned
63
+ outcome so the host stays up even if Colony's continuity store is
64
+ misbehaving.
65
+ """
66
+
67
+ outcome = TurnSyncOutcome()
68
+
69
+ if sink is None:
70
+ outcome.skipped_reason = "no_sink"
71
+ return outcome
72
+
73
+ if not session_id:
74
+ outcome.skipped_reason = "no_session_id"
75
+ return outcome
76
+
77
+ safe_topics = list(topics or [])
78
+ safe_entities = list(entities or [])
79
+ safe_pending = list(pending_tasks or [])
80
+ safe_tools = list(tools_used or [])
81
+
82
+ # Branch 1 — AgentMemoryBridge shape. ``is_available`` is a property
83
+ # that gates on circuit-breaker state and backend mode.
84
+ if hasattr(sink, "update_continuity"):
85
+ if not getattr(sink, "is_available", True):
86
+ outcome.skipped_reason = "sink_unavailable"
87
+ return outcome
88
+ try:
89
+ await sink.update_continuity(
90
+ session_id=session_id,
91
+ topics=safe_topics,
92
+ entities=safe_entities,
93
+ pending_tasks=safe_pending,
94
+ tools_used=safe_tools,
95
+ )
96
+ outcome.continuity_updated = True
97
+ except Exception as exc:
98
+ logger.debug("sync_turn_memory: update_continuity raised: %s", exc)
99
+ outcome.errors.append(f"update_continuity: {exc}")
100
+ return outcome
101
+
102
+ # Branch 2 — in-process SessionContinuity shape. Drops
103
+ # ``tools_used`` because ``update_context`` doesn't accept it today.
104
+ if hasattr(sink, "update_context"):
105
+ try:
106
+ await sink.update_context(
107
+ session_id=session_id,
108
+ topics=safe_topics or None,
109
+ entities=safe_entities or None,
110
+ pending_tasks=safe_pending or None,
111
+ )
112
+ outcome.continuity_updated = True
113
+ except Exception as exc:
114
+ logger.debug("sync_turn_memory: update_context raised: %s", exc)
115
+ outcome.errors.append(f"update_context: {exc}")
116
+ return outcome
117
+
118
+ outcome.skipped_reason = "sink_shape_unsupported"
119
+ return outcome
apsimo/mcp/__init__.py ADDED
@@ -0,0 +1,41 @@
1
+ """Apsimo MCP Server — exposes cognitive substrate as MCP tools."""
2
+
3
+ import importlib.util
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def _fastmcp_available() -> bool:
10
+ """True only if the symbol this package actually imports is present.
11
+
12
+ Checking for the top-level ``mcp`` package is not sufficient: mcp 2.x
13
+ ships the distribution but no longer provides ``mcp.server.fastmcp``.
14
+ Probing the parent alone made an incompatible SDK look available and
15
+ turned an optional dependency into an import-time crash.
16
+ """
17
+ if importlib.util.find_spec("mcp") is None:
18
+ return False
19
+ try:
20
+ if importlib.util.find_spec("mcp.server.fastmcp") is None:
21
+ raise ModuleNotFoundError("mcp.server.fastmcp")
22
+ except (ImportError, AttributeError, ValueError):
23
+ logger.warning(
24
+ "mcp is installed but does not provide mcp.server.fastmcp "
25
+ "(mcp>=2 removed it). The Apsimo MCP server is UNAVAILABLE. "
26
+ "Install mcp[cli]>=1.0,<2 to enable it."
27
+ )
28
+ return False
29
+ return True
30
+
31
+
32
+ _MCP_SDK_AVAILABLE = _fastmcp_available()
33
+
34
+ if _MCP_SDK_AVAILABLE:
35
+ from apsimo.mcp.server import create_server, run_stdio, run_http
36
+ else:
37
+ # Optional dependency absent or incompatible. Callers must check for None
38
+ # rather than assume a server can be constructed.
39
+ create_server = run_stdio = run_http = None # type: ignore[assignment,misc]
40
+
41
+ __all__ = ["create_server", "run_stdio", "run_http", "_MCP_SDK_AVAILABLE"]
apsimo/mcp/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Run Apsimo MCP server via `python -m apsimo.mcp.server`."""
2
+
3
+ from apsimo.mcp.server import run_stdio
4
+
5
+ if __name__ == "__main__":
6
+ run_stdio()
apsimo/mcp/config.py ADDED
@@ -0,0 +1,287 @@
1
+ """Apsimo MCP harness configuration.
2
+
3
+ Handles detecting installed harnesses and configuring them to use Apsimo's MCP server.
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import shlex
11
+ import tomllib
12
+ import re
13
+ from pathlib import Path
14
+ from typing import Any, Optional
15
+
16
+ try:
17
+ import yaml
18
+ except ImportError:
19
+ yaml = None # type: ignore[assignment]
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Harness definitions
24
+ # ---------------------------------------------------------------------------
25
+
26
+ HARNESS_DEFS = {
27
+ "claude-code": {
28
+ "display": "Claude Code",
29
+ "detect_cmds": ["claude"],
30
+ "config_path": "~/.claude.json",
31
+ "config_format": "json",
32
+ "mcp_key": "mcpServers", # Key for MCP servers in config
33
+ "source_tag": "claude-code",
34
+ },
35
+ "codex": {
36
+ "display": "Codex",
37
+ "detect_cmds": ["codex"],
38
+ "config_path": "~/.codex/config.toml",
39
+ "config_format": "toml",
40
+ "source_tag": "codex",
41
+ },
42
+ "crush": {
43
+ "display": "Crush",
44
+ "detect_cmds": ["crush"],
45
+ "config_path": "~/.crush.json",
46
+ "config_format": "json",
47
+ "mcp_key": "mcp",
48
+ "mcp_type": "stdio",
49
+ "source_tag": "crush",
50
+ },
51
+ "opencode": {
52
+ "display": "OpenCode",
53
+ "detect_cmds": ["opencode"],
54
+ "config_path": "~/.config/opencode/opencode.json",
55
+ "config_format": "json",
56
+ "mcp_key": "mcp", # OpenCode uses "mcp" not "mcpServers"
57
+ "mcp_type": "stdio", # OpenCode requires type field
58
+ "source_tag": "opencode",
59
+ },
60
+ "hermes": {
61
+ "display": "Hermes",
62
+ "detect_cmds": ["hermes"],
63
+ "config_path": "~/.hermes/config.yaml",
64
+ "config_format": "yaml",
65
+ "mcp_key": "mcp_servers",
66
+ "source_tag": "hermes",
67
+ },
68
+ }
69
+
70
+
71
+ def detect_harnesses() -> dict[str, bool]:
72
+ """Return {harness_id: is_installed} for all known harnesses."""
73
+ result = {}
74
+ for hid, hdef in HARNESS_DEFS.items():
75
+ installed = any(shutil.which(cmd) for cmd in hdef["detect_cmds"])
76
+ result[hid] = installed
77
+ return result
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Config writers
82
+ # ---------------------------------------------------------------------------
83
+
84
+ def _mcp_config(contact_id: str, source: str, include_type: bool = False,
85
+ sidecar_url: Optional[str] = None) -> dict[str, Any]:
86
+ """New Apsimo config. Authentication is inherited, never a literal placeholder."""
87
+ from apsimo.environment import normalize_environment
88
+ env = normalize_environment()
89
+ url = sidecar_url or env.get("COLONY_SIDECAR_URL") or (
90
+ "http://127.0.0.1:" + env.get("COLONY_SIDECAR_PORT", "7777"))
91
+ custom = env.get("COLONY_MCP_COMMAND")
92
+ result = {
93
+ "command": custom or "apsimo",
94
+ "args": shlex.split(env.get("COLONY_MCP_ARGS", "")) if custom else ["mcp"],
95
+ "env": {"APSIMO_URL": url, "APSIMO_MCP_CONTACT_ID": contact_id,
96
+ "APSIMO_MCP_SOURCE": source},
97
+ }
98
+ if include_type:
99
+ result["type"] = "stdio"
100
+ return result
101
+
102
+
103
+ def _read_json(path: Path) -> dict:
104
+ # Do not replace unreadable configuration with an empty document.
105
+ return json.loads(path.read_text()) if path.exists() else {}
106
+
107
+
108
+ def _write_json(path: Path, data: dict) -> None:
109
+ path.parent.mkdir(parents=True, exist_ok=True)
110
+ path.write_text(json.dumps(data, indent=2) + "\n")
111
+
112
+
113
+ def _selected(servers: dict) -> dict:
114
+ old, new = servers.get("colony"), servers.get("apsimo")
115
+ if old is not None and new is not None and old != new:
116
+ raise ValueError("Both colony and apsimo MCP entries exist with different settings")
117
+ selected = new if new is not None else old
118
+ if selected is not None and not isinstance(selected, dict):
119
+ raise ValueError("The Apsimo MCP entry must be a configuration table")
120
+ return selected or {}
121
+
122
+
123
+ def _merged(existing: dict, desired: dict, sidecar_url: Optional[str]) -> dict:
124
+ """Retain custom launch settings and literal credentials during the rename."""
125
+ from apsimo.environment import normalize_environment
126
+ current_env = existing.get("env", {})
127
+ normalized = normalize_environment(current_env)
128
+ env = {("APSIMO_" + k[7:] if k.startswith("COLONY_") else k): v
129
+ for k, v in normalized.items()}
130
+ # Old generated placeholders are not portable across harnesses. Inherit the
131
+ # real variable from the launching environment instead of passing a literal.
132
+ if env.get("APSIMO_API_KEY") in {"${COLONY_API_KEY}", "${APSIMO_API_KEY}"}:
133
+ env.pop("APSIMO_API_KEY")
134
+ selected_env = dict(desired["env"])
135
+ if (sidecar_url is None and not any(os.environ.get(k) for k in (
136
+ "APSIMO_SIDECAR_URL", "COLONY_SIDECAR_URL",
137
+ "APSIMO_SIDECAR_PORT", "COLONY_SIDECAR_PORT"))
138
+ and "APSIMO_URL" in env):
139
+ selected_env["APSIMO_URL"] = env["APSIMO_URL"]
140
+ env.update(selected_env)
141
+ result = {**existing, **desired, "env": env}
142
+ if (existing.get("command") not in {None, "colony", "apsimo"}
143
+ and not (os.environ.get("APSIMO_MCP_COMMAND") or os.environ.get("COLONY_MCP_COMMAND"))):
144
+ result["command"] = existing["command"]
145
+ result["args"] = existing.get("args", [])
146
+ return result
147
+
148
+
149
+ def _update_servers(servers: dict, desired: dict, sidecar_url: Optional[str]) -> bool:
150
+ new = _merged(_selected(servers), desired, sidecar_url)
151
+ if servers.get("apsimo") == new and "colony" not in servers:
152
+ return False
153
+ servers.pop("colony", None)
154
+ servers["apsimo"] = new
155
+ return True
156
+
157
+
158
+ def _add_to_json_config(hdef: dict, contact_id: str, source: str,
159
+ dry_run: bool = False, sidecar_url: Optional[str] = None) -> Optional[str]:
160
+ path = Path(hdef["config_path"]).expanduser()
161
+ data = _read_json(path)
162
+ servers = data.setdefault(hdef.get("mcp_key", "mcpServers"), {})
163
+ desired = _mcp_config(contact_id, source, hdef.get("mcp_type") == "stdio", sidecar_url)
164
+ if not _update_servers(servers, desired, sidecar_url):
165
+ return None
166
+ if not dry_run:
167
+ _write_json(path, data)
168
+ return " Configure one Apsimo MCP entry; preserve other servers and credentials"
169
+
170
+
171
+ def _without_toml_entries(content: str) -> str:
172
+ """Remove only our two server tables, preserving following tables verbatim."""
173
+ output, skipping = [], False
174
+ for line in content.splitlines(keepends=True):
175
+ stripped = line.strip()
176
+ if stripped.startswith("["):
177
+ match = re.match(r"^\[\s*mcp_servers\s*\.\s*(?:colony|apsimo)(?:\s*\.[^]]*)?\s*\]", stripped)
178
+ skipping = bool(match)
179
+ if not skipping:
180
+ output.append(line)
181
+ return "".join(output)
182
+
183
+
184
+ def _toml_block(config: dict) -> str:
185
+ # JSON string escaping is TOML basic-string escaping for our scalar values.
186
+ def key(name):
187
+ return name if re.fullmatch(r"[A-Za-z0-9_-]+", name) else json.dumps(name)
188
+
189
+ def value(item):
190
+ if isinstance(item, bool):
191
+ return "true" if item else "false"
192
+ if isinstance(item, dict):
193
+ return "{ " + ", ".join(key(k) + " = " + value(v) for k, v in item.items()) + " }"
194
+ if isinstance(item, list):
195
+ return "[" + ", ".join(value(v) for v in item) + "]"
196
+ return json.dumps(item, ensure_ascii=False)
197
+ return "\n[mcp_servers.apsimo]\n" + "".join(
198
+ key(k) + " = " + value(v) + "\n" for k, v in config.items())
199
+
200
+
201
+ def _add_to_toml_config(config_path: str, contact_id: str, source: str,
202
+ dry_run: bool = False, sidecar_url: Optional[str] = None) -> Optional[str]:
203
+ path = Path(config_path).expanduser()
204
+ content = path.read_text() if path.exists() else ""
205
+ servers = tomllib.loads(content).get("mcp_servers", {})
206
+ desired = _mcp_config(contact_id, source, sidecar_url=sidecar_url)
207
+ if not _update_servers(servers, desired, sidecar_url):
208
+ return None
209
+ updated = _without_toml_entries(content).rstrip("\n") + _toml_block(servers["apsimo"])
210
+ parsed = tomllib.loads(updated)
211
+ if parsed.get("mcp_servers") != servers:
212
+ raise ValueError("Unsupported MCP TOML table layout; configuration was not modified")
213
+ if not dry_run:
214
+ path.parent.mkdir(parents=True, exist_ok=True)
215
+ path.write_text(updated)
216
+ return " Configure one Apsimo MCP entry; preserve other TOML tables and credentials"
217
+
218
+
219
+ def _add_to_yaml_config(config_path: str, contact_id: str, source: str,
220
+ dry_run: bool = False, sidecar_url: Optional[str] = None) -> Optional[str]:
221
+ if yaml is None:
222
+ return " PyYAML not installed; run: pip install pyyaml"
223
+ path = Path(config_path).expanduser()
224
+ data = (yaml.safe_load(path.read_text()) or {}) if path.exists() else {}
225
+ desired = _mcp_config(contact_id, source, sidecar_url=sidecar_url)
226
+ if not _update_servers(data.setdefault("mcp_servers", {}), desired, sidecar_url):
227
+ return None
228
+ if not dry_run:
229
+ path.parent.mkdir(parents=True, exist_ok=True)
230
+ path.write_text(yaml.safe_dump(data, sort_keys=False))
231
+ return " Configure one Apsimo MCP entry; preserve other settings and credentials"
232
+
233
+
234
+ def add_to_harness(harness_id: str, contact_id: str, dry_run: bool = False,
235
+ sidecar_url: Optional[str] = None) -> Optional[str]:
236
+ """Configure a single Apsimo MCP server without mutating process settings."""
237
+ hdef = HARNESS_DEFS.get(harness_id)
238
+ if not hdef:
239
+ return f" Unknown harness: {harness_id}"
240
+ args = (contact_id, hdef["source_tag"], dry_run, sidecar_url)
241
+ if hdef["config_format"] == "json":
242
+ return _add_to_json_config(hdef, *args)
243
+ if hdef["config_format"] == "toml":
244
+ return _add_to_toml_config(hdef["config_path"], *args)
245
+ if hdef["config_format"] == "yaml":
246
+ return _add_to_yaml_config(hdef["config_path"], *args)
247
+ return None
248
+
249
+
250
+ def remove_from_harness(harness_id: str, dry_run: bool = False) -> Optional[str]:
251
+ """Remove either recognized MCP name, leaving unrelated settings intact."""
252
+ hdef = HARNESS_DEFS.get(harness_id)
253
+ if not hdef:
254
+ return f" Unknown harness: {harness_id}"
255
+ path = Path(hdef["config_path"]).expanduser()
256
+ if not path.exists():
257
+ return None
258
+ fmt = hdef["config_format"]
259
+ if fmt == "toml":
260
+ content = path.read_text()
261
+ data = tomllib.loads(content)
262
+ key = "mcp_servers"
263
+ elif fmt == "yaml":
264
+ if yaml is None:
265
+ return " PyYAML not installed"
266
+ data = yaml.safe_load(path.read_text()) or {}
267
+ key = "mcp_servers"
268
+ else:
269
+ data = _read_json(path)
270
+ key = hdef.get("mcp_key", "mcpServers")
271
+ servers = data.get(key, {})
272
+ if not any(name in servers for name in ("colony", "apsimo")):
273
+ return None
274
+ for name in ("colony", "apsimo"):
275
+ servers.pop(name, None)
276
+ if fmt == "toml":
277
+ updated = _without_toml_entries(content)
278
+ if tomllib.loads(updated).get(key, {}) != servers:
279
+ raise ValueError("Unsupported MCP TOML table layout; configuration was not modified")
280
+ if not dry_run:
281
+ if fmt == "toml":
282
+ path.write_text(updated)
283
+ elif fmt == "yaml":
284
+ path.write_text(yaml.safe_dump(data, sort_keys=False))
285
+ else:
286
+ _write_json(path, data)
287
+ return f" Removed Apsimo MCP integration from {hdef['display']} config"