apsimo 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (614) hide show
  1. apsimo/__init__.py +38 -0
  2. apsimo/__main__.py +6 -0
  3. apsimo/agent/__init__.py +6 -0
  4. apsimo/agent/client.py +276 -0
  5. apsimo/agent/models.py +46 -0
  6. apsimo/agents/__init__.py +20 -0
  7. apsimo/agents/models.py +264 -0
  8. apsimo/agents/store.py +861 -0
  9. apsimo/agents/websocket.py +522 -0
  10. apsimo/api/__init__.py +1 -0
  11. apsimo/api/auth_telemetry.py +287 -0
  12. apsimo/api/authority.py +1203 -0
  13. apsimo/api/contact_grants.py +347 -0
  14. apsimo/api/middleware.py +483 -0
  15. apsimo/api/routers/__init__.py +1 -0
  16. apsimo/api/routers/commitment_work.py +265 -0
  17. apsimo/api/routers/context_gate.py +123 -0
  18. apsimo/api/routers/executions.py +140 -0
  19. apsimo/api/routers/followup_plans.py +147 -0
  20. apsimo/api/routers/governed_actions.py +162 -0
  21. apsimo/api/routers/host.py +14473 -0
  22. apsimo/api/routers/initiative_work.py +115 -0
  23. apsimo/api/routers/mining.py +104 -0
  24. apsimo/api/routers/observations.py +110 -0
  25. apsimo/api/routers/social_state.py +225 -0
  26. apsimo/api/routers/task_queue.py +2715 -0
  27. apsimo/api/routers/temporal_followups.py +251 -0
  28. apsimo/api/routers/transport.py +110 -0
  29. apsimo/api/routers/transport_ingress_api.py +240 -0
  30. apsimo/api/schemas/__init__.py +1 -0
  31. apsimo/api/schemas/host.py +1949 -0
  32. apsimo/autonomy/cli.py +110 -0
  33. apsimo/autonomy/condition_worker.py +437 -0
  34. apsimo/autonomy/config.py +424 -0
  35. apsimo/autonomy/loop.py +4316 -0
  36. apsimo/autonomy/registry.py +339 -0
  37. apsimo/autonomy/scheduler.py +1822 -0
  38. apsimo/autonomy/synthesis.py +449 -0
  39. apsimo/backup.py +962 -0
  40. apsimo/beliefs/__init__.py +23 -0
  41. apsimo/beliefs/contradictions.py +109 -0
  42. apsimo/beliefs/decay.py +61 -0
  43. apsimo/beliefs/engine.py +479 -0
  44. apsimo/beliefs/models.py +67 -0
  45. apsimo/beliefs/promotion.py +41 -0
  46. apsimo/beliefs/resolve.py +58 -0
  47. apsimo/beliefs/source_claims.py +690 -0
  48. apsimo/beliefs/source_projection.py +883 -0
  49. apsimo/beliefs/source_time.py +208 -0
  50. apsimo/beliefs/store.py +133 -0
  51. apsimo/briefings/aggregators.py +824 -0
  52. apsimo/briefings/composer.py +420 -0
  53. apsimo/briefings/config.py +55 -0
  54. apsimo/briefings/delivery.py +439 -0
  55. apsimo/briefings/engagement.py +97 -0
  56. apsimo/briefings/engine.py +274 -0
  57. apsimo/briefings/enhancer.py +99 -0
  58. apsimo/briefings/models.py +183 -0
  59. apsimo/briefings/scheduler.py +382 -0
  60. apsimo/briefings/store.py +435 -0
  61. apsimo/chain/__init__.py +48 -0
  62. apsimo/chain/block.py +100 -0
  63. apsimo/chain/cli.py +704 -0
  64. apsimo/chain/genesis.py +443 -0
  65. apsimo/chain/identity.py +416 -0
  66. apsimo/chain/keys.py +1025 -0
  67. apsimo/chain/local_keys.py +187 -0
  68. apsimo/chain/manager.py +290 -0
  69. apsimo/chain/node.py +163 -0
  70. apsimo/chain/plugin_transactions.py +371 -0
  71. apsimo/chain/protocol.py +220 -0
  72. apsimo/chain/state_machine.py +676 -0
  73. apsimo/chain/storage.py +503 -0
  74. apsimo/chain/transactions.py +250 -0
  75. apsimo/chain/validation.py +397 -0
  76. apsimo/channels/__init__.py +1 -0
  77. apsimo/channels/manifest.py +31 -0
  78. apsimo/channels/migrations/001_channels_schema.sql +12 -0
  79. apsimo/channels/phone_gateways.py +42 -0
  80. apsimo/channels/presence.py +188 -0
  81. apsimo/channels/router.py +235 -0
  82. apsimo/channels/store.py +231 -0
  83. apsimo/cli.py +2688 -0
  84. apsimo/cognition/__init__.py +11 -0
  85. apsimo/cognition/charter.py +398 -0
  86. apsimo/cognition/drive_governance.py +3530 -0
  87. apsimo/cognition/evidence_pipeline.py +1627 -0
  88. apsimo/cognition/external_events.py +932 -0
  89. apsimo/cognition/goal_spine.py +3488 -0
  90. apsimo/cognition/introspection.py +214 -0
  91. apsimo/cognition/prompt.py +150 -0
  92. apsimo/cognition/runtime.py +108 -0
  93. apsimo/cognition/trigger.py +154 -0
  94. apsimo/commitments/__init__.py +18 -0
  95. apsimo/commitments/local_work.py +355 -0
  96. apsimo/commitments/store.py +1052 -0
  97. apsimo/commitments/work.py +91 -0
  98. apsimo/compat.py +53 -0
  99. apsimo/compression/__init__.py +467 -0
  100. apsimo/connectors/__init__.py +21 -0
  101. apsimo/connectors/base.py +152 -0
  102. apsimo/connectors/caldav_calendar.py +125 -0
  103. apsimo/connectors/fs_documents.py +85 -0
  104. apsimo/connectors/imap_email.py +138 -0
  105. apsimo/connectors/manager.py +218 -0
  106. apsimo/connectors/webhook_pull.py +88 -0
  107. apsimo/contacts/__init__.py +33 -0
  108. apsimo/contacts/comms.py +357 -0
  109. apsimo/contacts/config.py +79 -0
  110. apsimo/contacts/exporters/__init__.py +1 -0
  111. apsimo/contacts/exporters/vcard.py +71 -0
  112. apsimo/contacts/identity_links.py +251 -0
  113. apsimo/contacts/importer.py +280 -0
  114. apsimo/contacts/importers/__init__.py +1 -0
  115. apsimo/contacts/importers/batch.py +43 -0
  116. apsimo/contacts/importers/macos_contacts.py +101 -0
  117. apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
  118. apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
  119. apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
  120. apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
  121. apsimo/contacts/migrations/005_identity_links.sql +27 -0
  122. apsimo/contacts/models.py +308 -0
  123. apsimo/contacts/scoring.py +16 -0
  124. apsimo/contacts/store.py +1623 -0
  125. apsimo/contacts/transport_ingress.py +252 -0
  126. apsimo/contacts/world_bridge.py +314 -0
  127. apsimo/contextgate/__init__.py +69 -0
  128. apsimo/contextgate/chunker.py +169 -0
  129. apsimo/contextgate/estimate.py +54 -0
  130. apsimo/contextgate/gate.py +313 -0
  131. apsimo/contextgate/retrieve.py +115 -0
  132. apsimo/delivery/__init__.py +16 -0
  133. apsimo/delivery/bridge.py +1260 -0
  134. apsimo/delivery/channels.py +526 -0
  135. apsimo/delivery/classification.py +50 -0
  136. apsimo/delivery/rate_limiter.py +268 -0
  137. apsimo/delivery/reachout_policy.py +206 -0
  138. apsimo/directed/__init__.py +22 -0
  139. apsimo/directed/audit.py +167 -0
  140. apsimo/directed/intake.py +95 -0
  141. apsimo/directed/models.py +191 -0
  142. apsimo/directed/service.py +509 -0
  143. apsimo/directives/__init__.py +25 -0
  144. apsimo/directives/evidence.py +87 -0
  145. apsimo/directives/extractor.py +188 -0
  146. apsimo/directives/guard.py +364 -0
  147. apsimo/directives/models.py +206 -0
  148. apsimo/directives/service.py +372 -0
  149. apsimo/directives/store.py +167 -0
  150. apsimo/doctor.py +2173 -0
  151. apsimo/environment.py +43 -0
  152. apsimo/events/__init__.py +33 -0
  153. apsimo/events/broadcaster.py +98 -0
  154. apsimo/events/bus.py +217 -0
  155. apsimo/events/journal.py +863 -0
  156. apsimo/events/stream.py +131 -0
  157. apsimo/events/types.py +150 -0
  158. apsimo/execution_results.py +357 -0
  159. apsimo/feedback/__init__.py +5 -0
  160. apsimo/feedback/store.py +76 -0
  161. apsimo/feeds/__init__.py +19 -0
  162. apsimo/feeds/cli.py +84 -0
  163. apsimo/feeds/engine.py +437 -0
  164. apsimo/feeds/example-feed.yaml +77 -0
  165. apsimo/feeds/hermes_cron.py +126 -0
  166. apsimo/feeds/manager.py +235 -0
  167. apsimo/feeds/spec.py +250 -0
  168. apsimo/feeds/template.py +202 -0
  169. apsimo/gate/__init__.py +18 -0
  170. apsimo/gate/audit.py +61 -0
  171. apsimo/gate/communication_policy.py +166 -0
  172. apsimo/gate/config.py +72 -0
  173. apsimo/gate/context_provenance.py +170 -0
  174. apsimo/gate/env_risk.py +226 -0
  175. apsimo/gate/guard_audit.py +353 -0
  176. apsimo/gate/layers/__init__.py +1 -0
  177. apsimo/gate/layers/base.py +15 -0
  178. apsimo/gate/layers/l1_recipient.py +66 -0
  179. apsimo/gate/layers/l2_pii.py +134 -0
  180. apsimo/gate/layers/l3_cross_context.py +50 -0
  181. apsimo/gate/layers/l4_trust_tier.py +78 -0
  182. apsimo/gate/layers/l5_injection.py +199 -0
  183. apsimo/gate/layers/l6_review.py +86 -0
  184. apsimo/gate/layers/l7_delay.py +100 -0
  185. apsimo/gate/layers/tom2_epistemic.py +185 -0
  186. apsimo/gate/models.py +64 -0
  187. apsimo/gate/pending_dispatch.py +5 -0
  188. apsimo/gate/pipeline.py +206 -0
  189. apsimo/gate/rejection.py +259 -0
  190. apsimo/gate/response_guard.py +700 -0
  191. apsimo/gate/rulesets/injection_v1.yaml +51 -0
  192. apsimo/gate/surface_policy.py +189 -0
  193. apsimo/gate/taint.py +226 -0
  194. apsimo/genesis.json +9 -0
  195. apsimo/goals/__init__.py +100 -0
  196. apsimo/goals/config.py +38 -0
  197. apsimo/goals/decomposer.py +421 -0
  198. apsimo/goals/engine.py +617 -0
  199. apsimo/goals/inference.py +354 -0
  200. apsimo/goals/models.py +302 -0
  201. apsimo/goals/priority.py +270 -0
  202. apsimo/goals/queue_bridge.py +149 -0
  203. apsimo/goals/replan.py +450 -0
  204. apsimo/goals/schema.sql +89 -0
  205. apsimo/goals/store.py +692 -0
  206. apsimo/governed_actions.py +1708 -0
  207. apsimo/harness_integration/__init__.py +45 -0
  208. apsimo/harness_integration/context.py +41 -0
  209. apsimo/harness_integration/skills.py +231 -0
  210. apsimo/identity/__init__.py +26 -0
  211. apsimo/identity/participants.py +181 -0
  212. apsimo/identity/resolver.py +329 -0
  213. apsimo/identity_bootstrap/__init__.py +5 -0
  214. apsimo/identity_bootstrap/builder.py +208 -0
  215. apsimo/identity_bootstrap/corpus.py +443 -0
  216. apsimo/identity_bootstrap/models.py +54 -0
  217. apsimo/identity_bootstrap/runner.py +353 -0
  218. apsimo/identity_bootstrap/seeders/__init__.py +25 -0
  219. apsimo/identity_bootstrap/seeders/briefings.py +109 -0
  220. apsimo/identity_bootstrap/seeders/chain.py +57 -0
  221. apsimo/identity_bootstrap/seeders/goals.py +128 -0
  222. apsimo/identity_bootstrap/seeders/memory.py +191 -0
  223. apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
  224. apsimo/identity_bootstrap/seeders/relationship.py +152 -0
  225. apsimo/identity_bootstrap/seeders/sessions.py +67 -0
  226. apsimo/identity_bootstrap/seeders/skills.py +92 -0
  227. apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
  228. apsimo/identity_bootstrap/seeders/world_model.py +143 -0
  229. apsimo/identity_bootstrap/self_query.py +92 -0
  230. apsimo/identity_bootstrap/self_reflection.py +155 -0
  231. apsimo/identity_bootstrap/skill.py +37 -0
  232. apsimo/identity_bootstrap/verifier.py +436 -0
  233. apsimo/initiatives/__init__.py +20 -0
  234. apsimo/initiatives/action_registry.py +454 -0
  235. apsimo/initiatives/approval_authority.py +2105 -0
  236. apsimo/initiatives/approval_policy.py +123 -0
  237. apsimo/initiatives/assignment.py +263 -0
  238. apsimo/initiatives/backup_evidence.py +100 -0
  239. apsimo/initiatives/context_freshness.py +103 -0
  240. apsimo/initiatives/models.py +318 -0
  241. apsimo/initiatives/native_work.py +270 -0
  242. apsimo/initiatives/standing_approvals.py +232 -0
  243. apsimo/initiatives/store.py +1081 -0
  244. apsimo/initiatives/temporal_followup.py +410 -0
  245. apsimo/intelligence/__init__.py +1 -0
  246. apsimo/intelligence/cognition/__init__.py +24 -0
  247. apsimo/intelligence/cognition/gap_detector.py +148 -0
  248. apsimo/intelligence/cognition/metalearner.py +547 -0
  249. apsimo/intelligence/cognition/metrics_collector.py +217 -0
  250. apsimo/intelligence/cognition/performance_index.py +299 -0
  251. apsimo/intelligence/cognition/registry.py +192 -0
  252. apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
  253. apsimo/intelligence/cognition/types.py +16 -0
  254. apsimo/intelligence/components/__init__.py +66 -0
  255. apsimo/intelligence/components/anomaly_detector.py +413 -0
  256. apsimo/intelligence/components/initiative_engine.py +2643 -0
  257. apsimo/intelligence/components/preference_learner.py +521 -0
  258. apsimo/intelligence/components/research_orchestrator.py +358 -0
  259. apsimo/intelligence/components/self_directed_thinker.py +221 -0
  260. apsimo/intelligence/components/self_reflector.py +252 -0
  261. apsimo/intelligence/components/session_continuity.py +154 -0
  262. apsimo/intelligence/components/task_planner.py +320 -0
  263. apsimo/intelligence/components/tool_learner.py +217 -0
  264. apsimo/intelligence/graph/__init__.py +79 -0
  265. apsimo/intelligence/graph/client.py +2483 -0
  266. apsimo/intelligence/graph/consolidator.py +405 -0
  267. apsimo/intelligence/graph/distiller.py +312 -0
  268. apsimo/intelligence/graph/migrations.py +129 -0
  269. apsimo/intelligence/graph/queries.py +248 -0
  270. apsimo/intelligence/graph/recall.py +281 -0
  271. apsimo/intelligence/graph/reconciler.py +144 -0
  272. apsimo/intelligence/graph/schema.py +337 -0
  273. apsimo/intelligence/graph/selection.py +252 -0
  274. apsimo/intelligence/learning/__init__.py +17 -0
  275. apsimo/intelligence/learning/continuous_learner.py +245 -0
  276. apsimo/intelligence/learning/feedback_store.py +321 -0
  277. apsimo/intelligence/mind_model/__init__.py +1 -0
  278. apsimo/intelligence/mind_model/graph_baseline.py +136 -0
  279. apsimo/intelligence/mind_model/signal_collector.py +361 -0
  280. apsimo/intelligence/relationships/__init__.py +11 -0
  281. apsimo/intelligence/relationships/profiler.py +389 -0
  282. apsimo/intelligence/relationships/scorer.py +560 -0
  283. apsimo/intelligence/relationships/signal_floor.py +66 -0
  284. apsimo/intelligence/relationships/trust_tiers.py +300 -0
  285. apsimo/intelligence/synthesis/__init__.py +40 -0
  286. apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
  287. apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
  288. apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
  289. apsimo/intelligence/synthesis/insight_store.py +79 -0
  290. apsimo/intelligence/synthesis/insight_validator.py +183 -0
  291. apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
  292. apsimo/intelligence/turn_middleware/__init__.py +15 -0
  293. apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
  294. apsimo/mcp/__init__.py +41 -0
  295. apsimo/mcp/__main__.py +6 -0
  296. apsimo/mcp/config.py +287 -0
  297. apsimo/mcp/server.py +501 -0
  298. apsimo/migrations.py +187 -0
  299. apsimo/mining/__init__.py +27 -0
  300. apsimo/mining/corpus.py +239 -0
  301. apsimo/mining/escalations.py +289 -0
  302. apsimo/mining/models.py +169 -0
  303. apsimo/mining/store.py +210 -0
  304. apsimo/models/__init__.py +30 -0
  305. apsimo/models/memory.py +80 -0
  306. apsimo/models/mesh.py +72 -0
  307. apsimo/models/person.py +104 -0
  308. apsimo/models/signal.py +108 -0
  309. apsimo/observations/__init__.py +15 -0
  310. apsimo/observations/store.py +277 -0
  311. apsimo/patterns/__init__.py +6 -0
  312. apsimo/patterns/extract.py +187 -0
  313. apsimo/patterns/store.py +227 -0
  314. apsimo/persona/__init__.py +1 -0
  315. apsimo/persona/engine.py +611 -0
  316. apsimo/persona/manifest.py +140 -0
  317. apsimo/projects/__init__.py +28 -0
  318. apsimo/projects/engine.py +1681 -0
  319. apsimo/projects/event_outbox.py +188 -0
  320. apsimo/projects/models.py +216 -0
  321. apsimo/projects/planner.py +181 -0
  322. apsimo/projects/store.py +1446 -0
  323. apsimo/proposals/__init__.py +12 -0
  324. apsimo/proposals/engine.py +114 -0
  325. apsimo/proposals/models.py +207 -0
  326. apsimo/qualification/__init__.py +1 -0
  327. apsimo/qualification/cases.py +75 -0
  328. apsimo/qualification/cli.py +51 -0
  329. apsimo/qualification/memory_cases.py +209 -0
  330. apsimo/qualification/records.py +92 -0
  331. apsimo/qualification/report.py +87 -0
  332. apsimo/qualification/runner.py +311 -0
  333. apsimo/qualification/structured_cases.py +131 -0
  334. apsimo/reasoning/__init__.py +13 -0
  335. apsimo/reasoning/executor.py +506 -0
  336. apsimo/reasoning/loop.py +373 -0
  337. apsimo/reasoning/native_tools/__init__.py +16 -0
  338. apsimo/reasoning/native_tools/calculate.py +141 -0
  339. apsimo/reasoning/native_tools/file_ops.py +150 -0
  340. apsimo/reasoning/native_tools/web_search.py +49 -0
  341. apsimo/reasoning/tool_policy.py +182 -0
  342. apsimo/redact/__init__.py +176 -0
  343. apsimo/repos/__init__.py +5 -0
  344. apsimo/repos/mirrors.py +204 -0
  345. apsimo/research/__init__.py +41 -0
  346. apsimo/research/artifact.py +482 -0
  347. apsimo/research/gatherer.py +387 -0
  348. apsimo/research/pipeline.py +513 -0
  349. apsimo/research/search/__init__.py +7 -0
  350. apsimo/research/search/base.py +41 -0
  351. apsimo/research/search/brave.py +59 -0
  352. apsimo/research/search/cache.py +51 -0
  353. apsimo/research/search/duckduckgo.py +103 -0
  354. apsimo/research/search/orchestrator.py +119 -0
  355. apsimo/research/search/serpapi.py +59 -0
  356. apsimo/research/search/tavily.py +59 -0
  357. apsimo/research/synthesizer.py +309 -0
  358. apsimo/router/__init__.py +30 -0
  359. apsimo/router/complexity_scorer.py +148 -0
  360. apsimo/router/endpoints.py +153 -0
  361. apsimo/router/fallback.py +58 -0
  362. apsimo/router/functions.py +243 -0
  363. apsimo/router/native_policy.py +52 -0
  364. apsimo/router/router.py +762 -0
  365. apsimo/router/self_learning.py +174 -0
  366. apsimo/router/tiers.py +677 -0
  367. apsimo/sandbox/__init__.py +21 -0
  368. apsimo/sandbox/backend.py +195 -0
  369. apsimo/sandbox/manager.py +173 -0
  370. apsimo/scope_bounds.py +7 -0
  371. apsimo/secrets/__init__.py +6 -0
  372. apsimo/secrets/backends/__init__.py +8 -0
  373. apsimo/secrets/backends/base.py +42 -0
  374. apsimo/secrets/backends/env.py +110 -0
  375. apsimo/secrets/backends/keyring.py +72 -0
  376. apsimo/secrets/backends/onepassword.py +232 -0
  377. apsimo/secrets/cli.py +191 -0
  378. apsimo/secrets/manager.py +160 -0
  379. apsimo/secrets/migration.py +101 -0
  380. apsimo/secrets/types.py +98 -0
  381. apsimo/seed.py +41 -0
  382. apsimo/self_model/__init__.py +37 -0
  383. apsimo/self_model/appraisals.py +673 -0
  384. apsimo/self_model/benchmark.py +1314 -0
  385. apsimo/self_model/brief.py +40 -0
  386. apsimo/self_model/event_concerns.py +1128 -0
  387. apsimo/self_model/execution_forecasts.py +353 -0
  388. apsimo/self_model/expectations.py +1595 -0
  389. apsimo/self_model/experiments.py +1150 -0
  390. apsimo/self_model/journal.py +148 -0
  391. apsimo/self_model/judgments.py +705 -0
  392. apsimo/self_model/native_outcomes.py +55 -0
  393. apsimo/self_model/params.py +220 -0
  394. apsimo/self_model/perspective.py +246 -0
  395. apsimo/self_model/reconcile.py +183 -0
  396. apsimo/self_model/reply_forecasts.py +381 -0
  397. apsimo/self_model/runtime_forecasts.py +296 -0
  398. apsimo/self_model/runtime_models.py +67 -0
  399. apsimo/self_model/settlement.py +207 -0
  400. apsimo/self_model/situation.py +1731 -0
  401. apsimo/self_model/store.py +883 -0
  402. apsimo/self_model/supervised.py +137 -0
  403. apsimo/self_model/thinker.py +99 -0
  404. apsimo/self_model/trust.py +388 -0
  405. apsimo/self_model/workspace.py +2388 -0
  406. apsimo/server.py +4197 -0
  407. apsimo/services/__init__.py +1 -0
  408. apsimo/services/agent_bridge.py +474 -0
  409. apsimo/services/initiative_executor.py +914 -0
  410. apsimo/services/instance.py +297 -0
  411. apsimo/sessions/__init__.py +22 -0
  412. apsimo/sessions/config.py +13 -0
  413. apsimo/sessions/context_loader.py +88 -0
  414. apsimo/sessions/federation_session.py +75 -0
  415. apsimo/sessions/isolated_session.py +98 -0
  416. apsimo/sessions/reports.py +84 -0
  417. apsimo/sessions/store.py +148 -0
  418. apsimo/setup.py +2818 -0
  419. apsimo/setup_hermes.py +879 -0
  420. apsimo/setup_local_work.py +218 -0
  421. apsimo/setup_native_goals.py +134 -0
  422. apsimo/setup_native_reviews.py +115 -0
  423. apsimo/skills/__init__.py +10 -0
  424. apsimo/skills/base.py +108 -0
  425. apsimo/skills/budget.py +28 -0
  426. apsimo/skills/executor.py +493 -0
  427. apsimo/skills/executors/__init__.py +1 -0
  428. apsimo/skills/executors/behavioral_correction.py +75 -0
  429. apsimo/skills/executors/capability_gap.py +38 -0
  430. apsimo/skills/executors/data_quality.py +163 -0
  431. apsimo/skills/executors/knowledge_acquisition.py +41 -0
  432. apsimo/skills/executors/operational_hygiene.py +185 -0
  433. apsimo/skills/executors/subsystem_health.py +169 -0
  434. apsimo/skills/hermes_export.py +431 -0
  435. apsimo/skills/index.py +123 -0
  436. apsimo/skills/learning/__init__.py +21 -0
  437. apsimo/skills/learning/novelty_detector.py +206 -0
  438. apsimo/skills/learning/pattern_extractor.py +199 -0
  439. apsimo/skills/learning/triggers.py +159 -0
  440. apsimo/skills/loader.py +246 -0
  441. apsimo/skills/migrations/002_progressive_loading.sql +6 -0
  442. apsimo/skills/migrations/backfill_triggers.py +20 -0
  443. apsimo/skills/models.py +202 -0
  444. apsimo/skills/packager.py +128 -0
  445. apsimo/skills/protocols.py +70 -0
  446. apsimo/skills/registry.py +191 -0
  447. apsimo/skills/runtime.py +58 -0
  448. apsimo/skills/sandbox_runner.py +229 -0
  449. apsimo/skills/scheduler.py +129 -0
  450. apsimo/skills/schema.py +79 -0
  451. apsimo/skills/security/__init__.py +12 -0
  452. apsimo/skills/security/guards.py +53 -0
  453. apsimo/skills/security/scanner.py +223 -0
  454. apsimo/skills_memory/__init__.py +26 -0
  455. apsimo/skills_memory/distill.py +159 -0
  456. apsimo/skills_memory/models.py +85 -0
  457. apsimo/skills_memory/retrieve.py +62 -0
  458. apsimo/skills_memory/store.py +172 -0
  459. apsimo/surprise/__init__.py +6 -0
  460. apsimo/surprise/accumulation.py +57 -0
  461. apsimo/surprise/scorer.py +102 -0
  462. apsimo/surprise/store.py +203 -0
  463. apsimo/task_queue/__init__.py +69 -0
  464. apsimo/task_queue/action_receipts.py +148 -0
  465. apsimo/task_queue/approval_relay_canary.py +108 -0
  466. apsimo/task_queue/config.py +85 -0
  467. apsimo/task_queue/contract.py +361 -0
  468. apsimo/task_queue/events.py +130 -0
  469. apsimo/task_queue/governor.py +1031 -0
  470. apsimo/task_queue/handlers/__init__.py +16 -0
  471. apsimo/task_queue/handlers/base.py +37 -0
  472. apsimo/task_queue/handlers/inference.py +640 -0
  473. apsimo/task_queue/handlers/monitoring.py +116 -0
  474. apsimo/task_queue/handlers/registry.py +75 -0
  475. apsimo/task_queue/handlers/subtask_handler.py +173 -0
  476. apsimo/task_queue/handlers/system_maintenance.py +147 -0
  477. apsimo/task_queue/mesh_integration.py +111 -0
  478. apsimo/task_queue/models.py +317 -0
  479. apsimo/task_queue/queue_manager.py +8286 -0
  480. apsimo/task_queue/routing.py +287 -0
  481. apsimo/task_queue/scheduler.py +252 -0
  482. apsimo/task_queue/schema.sql +197 -0
  483. apsimo/task_queue/work_control.py +342 -0
  484. apsimo/task_queue/worker.py +993 -0
  485. apsimo/telemetry.py +145 -0
  486. apsimo/tom/__init__.py +6 -0
  487. apsimo/tom/affect.py +387 -0
  488. apsimo/tom/approvals.py +171 -0
  489. apsimo/tom/arcs.py +896 -0
  490. apsimo/tom/asymmetry.py +131 -0
  491. apsimo/tom/eligibility.py +248 -0
  492. apsimo/tom/engagement.py +214 -0
  493. apsimo/tom/exposure.py +214 -0
  494. apsimo/tom/extractor.py +306 -0
  495. apsimo/tom/fact_adapters.py +144 -0
  496. apsimo/tom/facts.py +326 -0
  497. apsimo/tom/integration.py +592 -0
  498. apsimo/tom/leveled.py +118 -0
  499. apsimo/tom/levels.py +247 -0
  500. apsimo/tom/recipient_audit.py +995 -0
  501. apsimo/tom/recipient_simulator.py +593 -0
  502. apsimo/tom/source_lineage.py +93 -0
  503. apsimo/tom/tom2.py +277 -0
  504. apsimo/tom/visibility.py +559 -0
  505. apsimo/tom/visibility_store.py +414 -0
  506. apsimo/tools/__init__.py +0 -0
  507. apsimo/tools/definitions.py +740 -0
  508. apsimo/tools/handlers.py +943 -0
  509. apsimo/toolsmith/__init__.py +26 -0
  510. apsimo/toolsmith/authority.py +166 -0
  511. apsimo/toolsmith/engine.py +559 -0
  512. apsimo/toolsmith/integrity.py +100 -0
  513. apsimo/toolsmith/miner.py +145 -0
  514. apsimo/toolsmith/policy.py +110 -0
  515. apsimo/toolsmith/registry.py +635 -0
  516. apsimo/turns/__init__.py +17 -0
  517. apsimo/turns/audio.py +134 -0
  518. apsimo/turns/documents.py +235 -0
  519. apsimo/turns/executions.py +486 -0
  520. apsimo/turns/hermes_history.py +245 -0
  521. apsimo/turns/hermes_kanban.py +268 -0
  522. apsimo/turns/hermes_work.py +96 -0
  523. apsimo/turns/idempotency.py +752 -0
  524. apsimo/turns/local_work.py +115 -0
  525. apsimo/turns/media.py +581 -0
  526. apsimo/turns/reported_workers.py +196 -0
  527. apsimo/turns/source_annotations.py +283 -0
  528. apsimo/turns/source_attribution.py +154 -0
  529. apsimo/turns/source_read.py +351 -0
  530. apsimo/turns/source_vectors.py +263 -0
  531. apsimo/turns/video.py +210 -0
  532. apsimo/util/autonomy_preset.py +220 -0
  533. apsimo/util/instance.py +92 -0
  534. apsimo/util/model_output.py +25 -0
  535. apsimo/util/quiet_hours.py +27 -0
  536. apsimo/util/session_safety.py +37 -0
  537. apsimo/util/temporal.py +343 -0
  538. apsimo/vector/__init__.py +75 -0
  539. apsimo/vector/backfill.py +171 -0
  540. apsimo/vector/caption.py +114 -0
  541. apsimo/vector/collections.py +51 -0
  542. apsimo/vector/config.py +102 -0
  543. apsimo/vector/embedder.py +670 -0
  544. apsimo/vector/image_preprocess.py +406 -0
  545. apsimo/vector/image_store.py +296 -0
  546. apsimo/vector/indexes.py +162 -0
  547. apsimo/vector/migrate.py +334 -0
  548. apsimo/vector/multimodal_provider.py +417 -0
  549. apsimo/vector/multimodal_types.py +87 -0
  550. apsimo/vector/openai_provider.py +119 -0
  551. apsimo/vector/query.py +49 -0
  552. apsimo/vector/reranker.py +565 -0
  553. apsimo/vector/safety_image.py +159 -0
  554. apsimo/vector/scanner.py +197 -0
  555. apsimo/vector/setup.py +289 -0
  556. apsimo/vector/store.py +533 -0
  557. apsimo/vector/tiers.py +263 -0
  558. apsimo/work_orders.py +925 -0
  559. apsimo/workers/__init__.py +21 -0
  560. apsimo/workers/agent_bridge.py +640 -0
  561. apsimo/workers/colony_worker.py +382 -0
  562. apsimo/workers/queue_worker.py +441 -0
  563. apsimo/workers/skills_sync.py +152 -0
  564. apsimo/world_model/__init__.py +71 -0
  565. apsimo/world_model/causal_maintenance.py +131 -0
  566. apsimo/world_model/causal_policy.py +43 -0
  567. apsimo/world_model/causal_query.py +125 -0
  568. apsimo/world_model/confidence.py +54 -0
  569. apsimo/world_model/config.py +64 -0
  570. apsimo/world_model/constants.py +97 -0
  571. apsimo/world_model/entities.py +145 -0
  572. apsimo/world_model/expectation_resolvers.py +177 -0
  573. apsimo/world_model/extraction/__init__.py +7 -0
  574. apsimo/world_model/extraction/base.py +62 -0
  575. apsimo/world_model/extraction/conversation_extractor.py +262 -0
  576. apsimo/world_model/extraction/detector.py +74 -0
  577. apsimo/world_model/extraction/document_extractor.py +78 -0
  578. apsimo/world_model/extraction/formats/__init__.py +24 -0
  579. apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
  580. apsimo/world_model/extraction/formats/html_fmt.py +72 -0
  581. apsimo/world_model/extraction/formats/json_fmt.py +68 -0
  582. apsimo/world_model/extraction/formats/pdf.py +43 -0
  583. apsimo/world_model/extraction/formats/text.py +27 -0
  584. apsimo/world_model/extraction/llm_extractor.py +164 -0
  585. apsimo/world_model/extraction/pipeline.py +73 -0
  586. apsimo/world_model/integrations/__init__.py +5 -0
  587. apsimo/world_model/integrations/mind_model_bridge.py +115 -0
  588. apsimo/world_model/integrations/social_intel_bridge.py +120 -0
  589. apsimo/world_model/jobs/__init__.py +4 -0
  590. apsimo/world_model/jobs/extraction_job.py +168 -0
  591. apsimo/world_model/llm_extract.py +572 -0
  592. apsimo/world_model/neo4j/__init__.py +5 -0
  593. apsimo/world_model/neo4j/backend.py +654 -0
  594. apsimo/world_model/observations.py +155 -0
  595. apsimo/world_model/populator.py +307 -0
  596. apsimo/world_model/postgres/__init__.py +1 -0
  597. apsimo/world_model/postgres/backend.py +683 -0
  598. apsimo/world_model/relationships.py +25 -0
  599. apsimo/world_model/resolution/__init__.py +13 -0
  600. apsimo/world_model/resolution/entity_resolver.py +232 -0
  601. apsimo/world_model/resolution/merge_audit.py +16 -0
  602. apsimo/world_model/resolution/merge_workflow.py +117 -0
  603. apsimo/world_model/source_reports.py +121 -0
  604. apsimo/world_model/sqlite/__init__.py +4 -0
  605. apsimo/world_model/sqlite/backend.py +855 -0
  606. apsimo/world_model/sqlite/schema.sql +132 -0
  607. apsimo/world_model/store.py +545 -0
  608. apsimo-1.3.0.dist-info/METADATA +78 -0
  609. apsimo-1.3.0.dist-info/RECORD +614 -0
  610. apsimo-1.3.0.dist-info/WHEEL +5 -0
  611. apsimo-1.3.0.dist-info/entry_points.txt +11 -0
  612. apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
  613. apsimo-1.3.0.dist-info/top_level.txt +2 -0
  614. colony_sidecar/__init__.py +4 -0
apsimo/router/tiers.py ADDED
@@ -0,0 +1,677 @@
1
+ """Model tier definitions for the LLM cost router.
2
+
3
+ Tiers are configured by the host (OpenClaw, Hermes, etc.) at startup
4
+ via ``POST /v1/host/configure``. Colony does not manage its own LLM
5
+ credentials — it inherits them from whichever host mounts it.
6
+
7
+ Provider presets define sensible model assignments per tier for known
8
+ providers (Anthropic, OpenAI, ZAI, Ollama, LM Studio, vLLM).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import logging
15
+ import os
16
+ from dataclasses import dataclass, replace
17
+ from enum import Enum
18
+ from typing import Any
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class ModelTier(Enum):
24
+ HEURISTIC = "heuristic" # No LLM call — rule-based response
25
+ SMALL = "small" # Fast/cheap: haiku-class, gpt-4o-mini
26
+ MEDIUM = "medium" # Balanced: sonnet-class, gpt-4o
27
+ LARGE = "large" # Best quality: opus-class, o3
28
+ VISION = "vision" # Explicit image role; never selected by cost scoring
29
+
30
+
31
+ @dataclass
32
+ class TierConfig:
33
+ tier: ModelTier
34
+ model_id: str # LiteLLM model string
35
+ max_tokens: int
36
+ cost_per_1k_input: float # USD
37
+ cost_per_1k_output: float
38
+ latency_p50_ms: int # approximate
39
+ # Per-tier endpoint overrides (empty = inherit the provider-wide
40
+ # endpoint configured via OPENAI_API_BASE / provider defaults).
41
+ # These let different tiers live on different servers — e.g. a fast
42
+ # small model on one endpoint and a large reasoning model on another.
43
+ base_url: str = ""
44
+ api_key: str = ""
45
+ # Extra request-body fields forwarded verbatim on every call to this
46
+ # tier (e.g. vLLM's per-request ``priority`` for --scheduling-policy
47
+ # priority, or provider-specific sampling knobs).
48
+ extra_body: dict[str, Any] | None = None
49
+ # The model's *useful* context window in tokens — the point up to
50
+ # which exact retrieval stays reliable, which is often well below the
51
+ # advertised maximum. 0 = unknown/unlimited. Consumed by the context
52
+ # gate to decide when to chunk/retrieve instead of passing whole.
53
+ useful_context_tokens: int = 0
54
+ # Explicit host capability declaration, not inferred from a model name.
55
+ supports_vision: bool = False
56
+
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # Provider presets — used by build_tiers_from_host()
60
+ # ---------------------------------------------------------------------------
61
+
62
+ _PROVIDER_PRESETS: dict[str, dict[ModelTier, TierConfig]] = {
63
+ "anthropic": {
64
+ ModelTier.SMALL: TierConfig(
65
+ tier=ModelTier.SMALL,
66
+ model_id="anthropic/claude-haiku-4-5-20251001",
67
+ max_tokens=4096,
68
+ cost_per_1k_input=0.0008,
69
+ cost_per_1k_output=0.004,
70
+ latency_p50_ms=800,
71
+ ),
72
+ ModelTier.MEDIUM: TierConfig(
73
+ tier=ModelTier.MEDIUM,
74
+ model_id="anthropic/claude-sonnet-4-6",
75
+ max_tokens=8192,
76
+ cost_per_1k_input=0.003,
77
+ cost_per_1k_output=0.015,
78
+ latency_p50_ms=2000,
79
+ ),
80
+ ModelTier.LARGE: TierConfig(
81
+ tier=ModelTier.LARGE,
82
+ model_id="anthropic/claude-opus-4-6",
83
+ max_tokens=32768,
84
+ cost_per_1k_input=0.015,
85
+ cost_per_1k_output=0.075,
86
+ latency_p50_ms=5000,
87
+ ),
88
+ },
89
+ "openai": {
90
+ ModelTier.SMALL: TierConfig(
91
+ tier=ModelTier.SMALL,
92
+ model_id="openai/gpt-4o-mini",
93
+ max_tokens=4096,
94
+ cost_per_1k_input=0.00015,
95
+ cost_per_1k_output=0.0006,
96
+ latency_p50_ms=600,
97
+ ),
98
+ ModelTier.MEDIUM: TierConfig(
99
+ tier=ModelTier.MEDIUM,
100
+ model_id="openai/gpt-4o",
101
+ max_tokens=8192,
102
+ cost_per_1k_input=0.0025,
103
+ cost_per_1k_output=0.01,
104
+ latency_p50_ms=2000,
105
+ ),
106
+ ModelTier.LARGE: TierConfig(
107
+ tier=ModelTier.LARGE,
108
+ model_id="openai/o3",
109
+ max_tokens=32768,
110
+ cost_per_1k_input=0.015,
111
+ cost_per_1k_output=0.06,
112
+ latency_p50_ms=5000,
113
+ ),
114
+ },
115
+ "zai": {
116
+ ModelTier.SMALL: TierConfig(
117
+ tier=ModelTier.SMALL,
118
+ model_id="openai/glm-4.7-flash",
119
+ max_tokens=8192,
120
+ cost_per_1k_input=0.06,
121
+ cost_per_1k_output=0.4,
122
+ latency_p50_ms=800,
123
+ ),
124
+ ModelTier.MEDIUM: TierConfig(
125
+ tier=ModelTier.MEDIUM,
126
+ model_id="openai/glm-5.1",
127
+ max_tokens=131072,
128
+ cost_per_1k_input=1.5,
129
+ cost_per_1k_output=5,
130
+ latency_p50_ms=2000,
131
+ ),
132
+ ModelTier.LARGE: TierConfig(
133
+ tier=ModelTier.LARGE,
134
+ model_id="openai/glm-5.1",
135
+ max_tokens=131072,
136
+ cost_per_1k_input=1.5,
137
+ cost_per_1k_output=5,
138
+ latency_p50_ms=2000,
139
+ ),
140
+ },
141
+ "local": {
142
+ ModelTier.SMALL: TierConfig(
143
+ tier=ModelTier.SMALL,
144
+ model_id="openai/local-model",
145
+ max_tokens=4096,
146
+ cost_per_1k_input=0,
147
+ cost_per_1k_output=0,
148
+ latency_p50_ms=1000,
149
+ ),
150
+ ModelTier.MEDIUM: TierConfig(
151
+ tier=ModelTier.MEDIUM,
152
+ model_id="openai/local-model",
153
+ max_tokens=8192,
154
+ cost_per_1k_input=0,
155
+ cost_per_1k_output=0,
156
+ latency_p50_ms=2000,
157
+ ),
158
+ ModelTier.LARGE: TierConfig(
159
+ tier=ModelTier.LARGE,
160
+ model_id="openai/local-model",
161
+ max_tokens=32768,
162
+ cost_per_1k_input=0,
163
+ cost_per_1k_output=0,
164
+ latency_p50_ms=3000,
165
+ ),
166
+ },
167
+ "ollama": {
168
+ ModelTier.SMALL: TierConfig(
169
+ tier=ModelTier.SMALL,
170
+ model_id="ollama/llama3.2",
171
+ max_tokens=4096,
172
+ cost_per_1k_input=0,
173
+ cost_per_1k_output=0,
174
+ latency_p50_ms=800,
175
+ ),
176
+ ModelTier.MEDIUM: TierConfig(
177
+ tier=ModelTier.MEDIUM,
178
+ model_id="ollama/mistral",
179
+ max_tokens=8192,
180
+ cost_per_1k_input=0,
181
+ cost_per_1k_output=0,
182
+ latency_p50_ms=1500,
183
+ ),
184
+ ModelTier.LARGE: TierConfig(
185
+ tier=ModelTier.LARGE,
186
+ model_id="ollama/deepseek-r1",
187
+ max_tokens=32768,
188
+ cost_per_1k_input=0,
189
+ cost_per_1k_output=0,
190
+ latency_p50_ms=5000,
191
+ ),
192
+ },
193
+ "lmstudio": {
194
+ ModelTier.SMALL: TierConfig(
195
+ tier=ModelTier.SMALL,
196
+ model_id="openai/lmstudio-small",
197
+ max_tokens=4096,
198
+ cost_per_1k_input=0,
199
+ cost_per_1k_output=0,
200
+ latency_p50_ms=800,
201
+ ),
202
+ ModelTier.MEDIUM: TierConfig(
203
+ tier=ModelTier.MEDIUM,
204
+ model_id="openai/lmstudio-medium",
205
+ max_tokens=8192,
206
+ cost_per_1k_input=0,
207
+ cost_per_1k_output=0,
208
+ latency_p50_ms=1500,
209
+ ),
210
+ ModelTier.LARGE: TierConfig(
211
+ tier=ModelTier.LARGE,
212
+ model_id="openai/lmstudio-large",
213
+ max_tokens=32768,
214
+ cost_per_1k_input=0,
215
+ cost_per_1k_output=0,
216
+ latency_p50_ms=3000,
217
+ ),
218
+ },
219
+ "vllm": {
220
+ ModelTier.SMALL: TierConfig(
221
+ tier=ModelTier.SMALL,
222
+ model_id="openai/vllm-small",
223
+ max_tokens=4096,
224
+ cost_per_1k_input=0,
225
+ cost_per_1k_output=0,
226
+ latency_p50_ms=600,
227
+ ),
228
+ ModelTier.MEDIUM: TierConfig(
229
+ tier=ModelTier.MEDIUM,
230
+ model_id="openai/vllm-medium",
231
+ max_tokens=8192,
232
+ cost_per_1k_input=0,
233
+ cost_per_1k_output=0,
234
+ latency_p50_ms=1200,
235
+ ),
236
+ ModelTier.LARGE: TierConfig(
237
+ tier=ModelTier.LARGE,
238
+ model_id="openai/vllm-large",
239
+ max_tokens=32768,
240
+ cost_per_1k_input=0,
241
+ cost_per_1k_output=0,
242
+ latency_p50_ms=2500,
243
+ ),
244
+ },
245
+ }
246
+
247
+
248
+ # Default tier configurations (Anthropic) — used only when no host
249
+ # has configured the router yet.
250
+ DEFAULT_TIERS: dict[ModelTier, TierConfig] = _PROVIDER_PRESETS["anthropic"]
251
+
252
+
253
+ # ---------------------------------------------------------------------------
254
+ # Local model discovery
255
+ # ---------------------------------------------------------------------------
256
+
257
+ def discover_ollama_models(base_url: str = "") -> list[dict[str, Any]]:
258
+ """Query an Ollama server for installed models.
259
+
260
+ Returns a list of dicts with ``name``, ``size``, and ``digest``.
261
+ The *name* field can be used directly as a LiteLLM model ID
262
+ (``ollama/<name>``).
263
+ """
264
+ import urllib.request
265
+
266
+ url = base_url.rstrip("/") + "/api/tags" if base_url else "http://127.0.0.1:11434/api/tags"
267
+ try:
268
+ with urllib.request.urlopen(url, timeout=5) as resp:
269
+ data = json.loads(resp.read().decode())
270
+ models = []
271
+ for m in data.get("models", []):
272
+ models.append({
273
+ "name": m.get("name", ""),
274
+ "size": m.get("size", 0),
275
+ "digest": m.get("digest", "")[:12],
276
+ })
277
+ return models
278
+ except Exception as exc:
279
+ logger.debug("Ollama model discovery failed (%s): %s", url, exc)
280
+ return []
281
+
282
+
283
+ def discover_openai_compatible_models(base_url: str, api_key: str = "") -> list[dict[str, Any]]:
284
+ """Query an OpenAI-compatible server (vLLM, LM Studio, etc.) for models.
285
+
286
+ Returns a list of dicts with ``id`` and ``owned_by``.
287
+ The *id* can be used as a LiteLLM model ID (``openai/<id>`` when
288
+ routed through the OpenAI compat layer).
289
+ """
290
+ import urllib.request
291
+
292
+ from .endpoints import models_url
293
+ url = models_url(base_url)
294
+ req = urllib.request.Request(url, method="GET")
295
+ if api_key:
296
+ req.add_header("Authorization", f"Bearer {api_key}")
297
+ try:
298
+ with urllib.request.urlopen(req, timeout=5) as resp:
299
+ data = json.loads(resp.read().decode())
300
+ models = []
301
+ for m in data.get("data", []):
302
+ models.append({
303
+ "id": m.get("id", ""),
304
+ "owned_by": m.get("owned_by", ""),
305
+ })
306
+ return models
307
+ except Exception as exc:
308
+ logger.debug("OpenAI-compat model discovery failed (%s): %s", url, exc)
309
+ return []
310
+
311
+
312
+ def discover_local_models(provider: str, base_url: str = "", api_key: str = "") -> list[dict[str, Any]]:
313
+ """Discover models for a local provider.
314
+
315
+ Attempts Ollama discovery first (for ``provider == "ollama"`` or when
316
+ the base URL looks like an Ollama endpoint), then falls back to the
317
+ OpenAI-compatible ``/v1/models`` endpoint.
318
+ """
319
+ if provider == "ollama":
320
+ return discover_ollama_models(base_url)
321
+
322
+ if provider in ("local", "custom", "openai", "lmstudio", "vllm") and base_url:
323
+ # Try OpenAI-compatible endpoint first
324
+ models = discover_openai_compatible_models(base_url, api_key)
325
+ if models:
326
+ return models
327
+ # Some local servers (e.g. Ollama with OpenAI compat) may also
328
+ # expose the Ollama API on the same port
329
+ ollama_models = discover_ollama_models(base_url)
330
+ if ollama_models:
331
+ return [{"id": m["name"], "source": "ollama"} for m in ollama_models]
332
+
333
+ return []
334
+
335
+
336
+ # ---------------------------------------------------------------------------
337
+ # LiteLLM prefix helpers
338
+ # ---------------------------------------------------------------------------
339
+
340
+ _LITELLM_PREFIXES: frozenset[str] = frozenset(
341
+ {
342
+ "openai",
343
+ "anthropic",
344
+ "ollama",
345
+ "azure",
346
+ "azure_ai",
347
+ "azure_text",
348
+ "azure_chat",
349
+ "bedrock",
350
+ "vertex_ai",
351
+ "gemini",
352
+ "groq",
353
+ "together_ai",
354
+ "huggingface",
355
+ "fireworks_ai",
356
+ "mistral",
357
+ "deepseek",
358
+ "xai",
359
+ "text-completion-openai",
360
+ "cohere",
361
+ "perplexity",
362
+ "ai21",
363
+ "replicate",
364
+ "baseten",
365
+ "vllm",
366
+ "sagemaker",
367
+ "cloudflare",
368
+ "watsonx",
369
+ "databricks",
370
+ "zhipu",
371
+ "clarifai",
372
+ "jina_ai",
373
+ "novita",
374
+ "siliconflow",
375
+ "openrouter",
376
+ "maritalk",
377
+ }
378
+ )
379
+
380
+
381
+ def _has_litellm_prefix(model_id: str) -> bool:
382
+ """Return True if *model_id* starts with a known LiteLLM provider prefix."""
383
+ if "/" not in model_id:
384
+ return False
385
+ prefix = model_id.split("/", 1)[0].lower()
386
+ return prefix in _LITELLM_PREFIXES
387
+
388
+
389
+ # ---------------------------------------------------------------------------
390
+ # Tier building from host config
391
+ # ---------------------------------------------------------------------------
392
+
393
+ def build_tiers_from_host(config: dict, *, configure_environment=True, discover=True) -> dict[ModelTier, TierConfig]:
394
+ """Build tier configurations from host-provided LLM config.
395
+
396
+ The host (OpenClaw, Hermes, etc.) sends its LLM provider details
397
+ via ``POST /v1/host/configure``. This function maps that config
398
+ to TierConfig objects that the LLMRouter can use.
399
+
400
+ Supports arbitrary local and remote models. Hosts may send either
401
+ fully-qualified LiteLLM model IDs (``ollama/llama3.2``,
402
+ ``openai/gpt-4o``) or bare model names for known providers.
403
+
404
+ When no explicit model overrides are provided for a local provider,
405
+ the function attempts to auto-discover installed models and maps
406
+ them to tiers heuristically (smallest → SMALL, largest → LARGE).
407
+
408
+ Expected config shape::
409
+
410
+ {
411
+ "provider": "ollama",
412
+ "apiKey": "",
413
+ "baseUrl": "http://localhost:11434",
414
+ "models": {
415
+ "small": "llama3.2",
416
+ "medium": "mistral",
417
+ "large": "deepseek-r1"
418
+ }
419
+ }
420
+
421
+ Each ``models`` value may also be an object instead of a bare model
422
+ string, enabling *per-tier endpoints* — different tiers served by
423
+ different servers — plus per-tier request extras::
424
+
425
+ {
426
+ "provider": "vllm",
427
+ "baseUrl": "http://fast-host:8000/v1", # default for all tiers
428
+ "models": {
429
+ "small": "fast-model",
430
+ "large": {
431
+ "model": "big-model",
432
+ "baseUrl": "http://big-host:8000/v1",
433
+ "apiKey": "…", # optional
434
+ "extraBody": {"priority": 10}, # optional, sent verbatim
435
+ "usefulContextTokens": 65536, # optional, context gate hint
436
+ "maxTokens": 32768 # optional, completion cap
437
+ }
438
+ }
439
+ }
440
+
441
+ Snake_case keys (``base_url``, ``api_key``, ``extra_body``,
442
+ ``useful_context_tokens``, ``max_tokens``) are accepted as aliases.
443
+
444
+ For OpenAI-compatible providers (zai, local, custom, lmstudio, vllm),
445
+ the function sets ``OPENAI_API_KEY`` and ``OPENAI_API_BASE`` so
446
+ LiteLLM routes ``openai/*`` model IDs correctly. For Ollama, it
447
+ sets ``OLLAMA_API_BASE``.
448
+ """
449
+ provider = config.get("provider", "anthropic").lower()
450
+ api_key = config.get("apiKey", "")
451
+ base_url = config.get("baseUrl", "")
452
+ models_override = config.get("models", {})
453
+
454
+ def _spec_field(spec: dict, *names: str, default: Any = None) -> Any:
455
+ """First present key among camelCase/snake_case aliases."""
456
+ for name in names:
457
+ if name in spec:
458
+ return spec[name]
459
+ return default
460
+
461
+ def _parse_model_spec(value: Any) -> tuple[str, dict[str, Any]]:
462
+ """Normalize a ``models`` entry (string or object) to (model_id, overrides).
463
+
464
+ Overrides may contain: base_url, api_key, extra_body,
465
+ useful_context_tokens, max_tokens.
466
+ """
467
+ if isinstance(value, str):
468
+ return value, {}
469
+ if isinstance(value, dict):
470
+ model_id = str(_spec_field(value, "model", "model_id", default="") or "")
471
+ overrides: dict[str, Any] = {}
472
+ v = _spec_field(value, "baseUrl", "base_url")
473
+ if v:
474
+ overrides["base_url"] = str(v)
475
+ v = _spec_field(value, "apiKey", "api_key")
476
+ if v:
477
+ overrides["api_key"] = str(v)
478
+ v = _spec_field(value, "supportsVision", "supports_vision")
479
+ if isinstance(v, bool):
480
+ overrides["supports_vision"] = v
481
+ v = _spec_field(value, "extraBody", "extra_body")
482
+ if isinstance(v, dict) and v:
483
+ overrides["extra_body"] = dict(v)
484
+ v = _spec_field(value, "usefulContextTokens", "useful_context_tokens")
485
+ if v:
486
+ try:
487
+ overrides["useful_context_tokens"] = int(v)
488
+ except (TypeError, ValueError):
489
+ logger.warning("Invalid usefulContextTokens %r — ignoring", v)
490
+ v = _spec_field(value, "maxTokens", "max_tokens")
491
+ if v:
492
+ try:
493
+ overrides["max_tokens"] = int(v)
494
+ except (TypeError, ValueError):
495
+ logger.warning("Invalid maxTokens %r — ignoring", v)
496
+ return model_id, overrides
497
+ logger.warning("Unsupported model spec %r — expected string or object", value)
498
+ return "", {}
499
+
500
+ # Determine whether the provider is OpenAI-compatible or Ollama-native
501
+ openai_compat_providers = {"zai", "local", "custom", "lmstudio", "vllm", "openai"}
502
+ ollama_providers = {"ollama"}
503
+
504
+ # ------------------------------------------------------------------
505
+ # Build tier skeleton
506
+ # ------------------------------------------------------------------
507
+ tiers = _PROVIDER_PRESETS.get(provider)
508
+ if tiers is None:
509
+ if models_override:
510
+ # Unknown provider with explicit models — build generic zero-cost tiers
511
+ # rather than silently falling back to anthropic.
512
+ logger.info(
513
+ "Unknown provider %r — building tiers from host model overrides.",
514
+ provider,
515
+ )
516
+ tiers = {}
517
+ for tier in (ModelTier.SMALL, ModelTier.MEDIUM, ModelTier.LARGE):
518
+ raw_spec = models_override.get(tier.value)
519
+ model_id = _parse_model_spec(raw_spec)[0] if raw_spec is not None else None
520
+ if not model_id:
521
+ # Fallback for missing tier — use a generic placeholder
522
+ # that will be overridden below if the host provides it.
523
+ model_id = "openai/gpt-4o-mini"
524
+ tiers[tier] = TierConfig(
525
+ tier=tier,
526
+ model_id=model_id,
527
+ max_tokens=8192,
528
+ cost_per_1k_input=0,
529
+ cost_per_1k_output=0,
530
+ latency_p50_ms=1500,
531
+ )
532
+ else:
533
+ logger.warning(
534
+ "Unknown LLM provider %r and no model overrides — falling back to anthropic preset. "
535
+ "Known providers: %s",
536
+ provider,
537
+ ", ".join(sorted(_PROVIDER_PRESETS)),
538
+ )
539
+ tiers = dict(_PROVIDER_PRESETS["anthropic"])
540
+
541
+ # Deep-copy so we don't mutate the preset
542
+ tiers = {tier: replace(cfg) for tier, cfg in tiers.items()}
543
+
544
+ # ------------------------------------------------------------------
545
+ # Apply model overrides from host
546
+ # ------------------------------------------------------------------
547
+ if models_override:
548
+ for tier_name, raw_spec in models_override.items():
549
+ try:
550
+ tier = ModelTier(tier_name)
551
+ except ValueError:
552
+ logger.warning("Unknown tier %r in model override — skipping", tier_name)
553
+ continue
554
+ if tier == ModelTier.HEURISTIC:
555
+ # HEURISTIC is a rule-based non-LLM tier — ignore model overrides for it
556
+ continue
557
+
558
+ model_id, tier_overrides = _parse_model_spec(raw_spec)
559
+ if not model_id:
560
+ logger.warning("No model in spec for tier %r — skipping", tier_name)
561
+ continue
562
+
563
+ if tier not in tiers:
564
+ # Host sent a model for a tier we don't have yet (generic build case)
565
+ tiers[tier] = TierConfig(
566
+ tier=tier,
567
+ model_id=model_id,
568
+ max_tokens=8192,
569
+ cost_per_1k_input=0,
570
+ cost_per_1k_output=0,
571
+ latency_p50_ms=1500,
572
+ )
573
+
574
+ # If the model ID already contains a known LiteLLM provider prefix
575
+ # (e.g. "ollama/llama3.2", "huggingface/mistral-7b"), preserve it exactly.
576
+ # Otherwise apply provider-specific prefixing so bare names work.
577
+ if not _has_litellm_prefix(model_id):
578
+ if provider in ollama_providers:
579
+ model_id = f"ollama/{model_id}"
580
+ elif provider in openai_compat_providers:
581
+ model_id = f"openai/{model_id}"
582
+ # For anthropic and other providers, leave bare names as-is
583
+ # (LiteLLM accepts "claude-sonnet-4-6" without a prefix when
584
+ # ANTHROPIC_API_KEY is set).
585
+
586
+ tiers[tier] = replace(tiers[tier], model_id=model_id, **tier_overrides)
587
+ logger.info(
588
+ "Host override: %s tier -> %s%s",
589
+ tier.value,
590
+ model_id,
591
+ f" @ {tier_overrides['base_url']}" if tier_overrides.get("base_url") else "",
592
+ )
593
+
594
+ # ------------------------------------------------------------------
595
+ # Auto-discovery when no overrides are provided for local providers
596
+ # ------------------------------------------------------------------
597
+ elif discover and provider in ("ollama", "local", "custom", "lmstudio", "vllm"):
598
+ discovered = discover_local_models(provider, base_url, api_key)
599
+ if discovered:
600
+ logger.info(
601
+ "Discovered %d local model(s) for provider=%s; applying to tiers",
602
+ len(discovered),
603
+ provider,
604
+ )
605
+ # Map discovered models to tiers heuristically:
606
+ # smallest -> SMALL, largest -> LARGE, middle -> MEDIUM
607
+ sorted_models = sorted(discovered, key=lambda m: m.get("size", 0))
608
+ if len(sorted_models) >= 1 and ModelTier.SMALL in tiers:
609
+ small_id = sorted_models[0].get("name") or sorted_models[0].get("id", "")
610
+ if small_id:
611
+ tiers[ModelTier.SMALL] = replace(
612
+ tiers[ModelTier.SMALL],
613
+ model_id=f"{provider}/{small_id}" if provider == "ollama" else f"openai/{small_id}",
614
+ )
615
+ if len(sorted_models) >= 2 and ModelTier.LARGE in tiers:
616
+ large_id = sorted_models[-1].get("name") or sorted_models[-1].get("id", "")
617
+ if large_id:
618
+ tiers[ModelTier.LARGE] = replace(
619
+ tiers[ModelTier.LARGE],
620
+ model_id=f"{provider}/{large_id}" if provider == "ollama" else f"openai/{large_id}",
621
+ )
622
+ if len(sorted_models) >= 3 and ModelTier.MEDIUM in tiers:
623
+ mid_idx = len(sorted_models) // 2
624
+ mid_id = sorted_models[mid_idx].get("name") or sorted_models[mid_idx].get("id", "")
625
+ if mid_id:
626
+ tiers[ModelTier.MEDIUM] = replace(
627
+ tiers[ModelTier.MEDIUM],
628
+ model_id=f"{provider}/{mid_id}" if provider == "ollama" else f"openai/{mid_id}",
629
+ )
630
+ elif len(sorted_models) == 2 and ModelTier.MEDIUM in tiers:
631
+ # Only two models — use the larger one for medium too
632
+ mid_id = sorted_models[-1].get("name") or sorted_models[-1].get("id", "")
633
+ if mid_id:
634
+ tiers[ModelTier.MEDIUM] = replace(
635
+ tiers[ModelTier.MEDIUM],
636
+ model_id=f"{provider}/{mid_id}" if provider == "ollama" else f"openai/{mid_id}",
637
+ )
638
+ else:
639
+ logger.warning(
640
+ "No model overrides provided and discovery failed for provider=%s. "
641
+ "The router will use placeholder model IDs which likely do not exist "
642
+ "on your local server. Pass explicit models in the host config, e.g.:\n"
643
+ ' {"models": {"small": "llama3.2", "medium": "mistral", "large": "deepseek-r1"}}',
644
+ provider,
645
+ )
646
+
647
+ # ------------------------------------------------------------------
648
+ # Set provider-specific environment variables for LiteLLM
649
+ # ------------------------------------------------------------------
650
+ if not configure_environment:
651
+ return tiers
652
+ if provider in openai_compat_providers:
653
+ if api_key:
654
+ os.environ["OPENAI_API_KEY"] = api_key
655
+ if base_url:
656
+ os.environ["OPENAI_API_BASE"] = base_url
657
+ elif provider == "zai":
658
+ os.environ["OPENAI_API_BASE"] = "https://api.z.ai/api/paas/v4"
659
+ logger.info(
660
+ "Configured OpenAI-compat provider: base=%s key_present=%s",
661
+ os.environ.get("OPENAI_API_BASE", "(none)"),
662
+ bool(api_key),
663
+ )
664
+ elif provider in ollama_providers:
665
+ if base_url:
666
+ os.environ["OLLAMA_API_BASE"] = base_url
667
+ else:
668
+ os.environ.setdefault("OLLAMA_API_BASE", "http://127.0.0.1:11434")
669
+ logger.info(
670
+ "Configured Ollama provider: base=%s",
671
+ os.environ.get("OLLAMA_API_BASE", "(default localhost:11434)"),
672
+ )
673
+ elif provider == "anthropic":
674
+ if api_key:
675
+ os.environ["ANTHROPIC_API_KEY"] = api_key
676
+
677
+ return tiers
@@ -0,0 +1,21 @@
1
+ """Exploration sandbox: gated, isolated code execution (cognition item 6).
2
+
3
+ Safe curiosity -- run a script inside a locked-down backend (no egress, no
4
+ credentials, capped resources, read-only rootfs + one writable workdir) behind
5
+ a mode flag, a DirectiveGuard boundary check, and approval tiering. Server-side
6
+ enforcement: the caller can never widen containment. Default off.
7
+ """
8
+
9
+ from apsimo.sandbox.backend import (
10
+ DisabledSandbox, DockerSandbox, SandboxBackend, SandboxLimits,
11
+ SandboxResult, select_backend,
12
+ )
13
+ from apsimo.sandbox.manager import (
14
+ SandboxManager, resolve_limits, sandbox_mode,
15
+ )
16
+
17
+ __all__ = [
18
+ "SandboxBackend", "SandboxLimits", "SandboxResult",
19
+ "DockerSandbox", "DisabledSandbox", "select_backend",
20
+ "SandboxManager", "sandbox_mode", "resolve_limits",
21
+ ]