agentengine-sdk-python 0.8.0__py3-none-any.whl → 0.8.2__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 (494) hide show
  1. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
  2. agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
  3. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
  4. ksadk/a2a/__init__.py +2 -0
  5. ksadk/a2a/_space_client_events.py +381 -0
  6. ksadk/a2a/bootstrap.py +0 -2
  7. ksadk/a2a/card.py +6 -111
  8. ksadk/a2a/control_plane.py +239 -1
  9. ksadk/a2a/event_adapter.py +156 -64
  10. ksadk/a2a/executor.py +195 -172
  11. ksadk/a2a/routes.py +12 -5
  12. ksadk/a2a/server.py +1 -8
  13. ksadk/a2a/service_env.py +33 -0
  14. ksadk/a2a/space_client.py +43 -349
  15. ksadk/a2a_card.py +101 -0
  16. ksadk/a2ui/core.py +222 -53
  17. ksadk/agui/_agent_helpers.py +243 -0
  18. ksadk/agui/a2ui_projection.py +12 -5
  19. ksadk/agui/agent.py +474 -275
  20. ksadk/agui/config.py +11 -5
  21. ksadk/agui/routes.py +7 -15
  22. ksadk/api/client.py +530 -40
  23. ksadk/api/environment.py +27 -0
  24. ksadk/builders/code_builder.py +352 -53
  25. ksadk/builders/container_builder.py +71 -44
  26. ksadk/builders/framework_requirements.py +2 -1
  27. ksadk/builders/ks3_uploader.py +2 -2
  28. ksadk/builders/managed_runtime_builder.py +60 -28
  29. ksadk/cli/__init__.py +25 -0
  30. ksadk/cli/cmd_a2a.py +18 -31
  31. ksadk/cli/cmd_create.py +13 -26
  32. ksadk/cli/cmd_dashboard.py +15 -1
  33. ksadk/cli/cmd_deploy.py +28 -3
  34. ksadk/cli/cmd_eval.py +443 -0
  35. ksadk/cli/cmd_evalset.py +284 -0
  36. ksadk/cli/cmd_files.py +5 -1
  37. ksadk/cli/cmd_hermes.py +137 -163
  38. ksadk/cli/cmd_invoke.py +28 -38
  39. ksadk/cli/cmd_launch.py +12 -0
  40. ksadk/cli/cmd_managed_runtime.py +95 -0
  41. ksadk/cli/cmd_observe.py +85 -0
  42. ksadk/cli/cmd_openclaw.py +85 -479
  43. ksadk/cli/cmd_replay.py +28 -4
  44. ksadk/cli/cmd_run.py +29 -67
  45. ksadk/cli/cmd_studio.py +201 -0
  46. ksadk/cli/cmd_web.py +15 -6
  47. ksadk/cli/dry_run.py +2 -1
  48. ksadk/cli/env_options.py +83 -0
  49. ksadk/cli/global_options.py +3 -1
  50. ksadk/cli/hermes_env.py +151 -0
  51. ksadk/cli/invoke_payload.py +49 -0
  52. ksadk/cli/network_options.py +7 -5
  53. ksadk/cli/openclaw_env.py +458 -0
  54. ksadk/cli/runtime_bootstrap.py +51 -0
  55. ksadk/cli/ui.py +6 -0
  56. ksadk/cli/workflow_common.py +5 -5
  57. ksadk/codex/__init__.py +3 -3
  58. ksadk/codex/client.py +833 -49
  59. ksadk/codex/phase.py +1 -1
  60. ksadk/codex/runtime.py +720 -127
  61. ksadk/common/kop_client.py +232 -0
  62. ksadk/configs/__init__.py +1 -3
  63. ksadk/configs/env_registry.py +244 -45
  64. ksadk/configs/env_registry_pcm.py +181 -0
  65. ksadk/configs/env_var_spec.py +18 -0
  66. ksadk/configs/global_config.py +6 -1
  67. ksadk/configs/settings.py +5 -108
  68. ksadk/context_engine/__init__.py +98 -0
  69. ksadk/context_engine/assembler.py +176 -0
  70. ksadk/context_engine/baseline.py +419 -0
  71. ksadk/context_engine/cache_observability.py +228 -0
  72. ksadk/context_engine/capabilities.py +443 -0
  73. ksadk/context_engine/contributors.py +344 -0
  74. ksadk/context_engine/hosted_pipeline.py +417 -0
  75. ksadk/context_engine/models.py +117 -0
  76. ksadk/context_engine/planner.py +698 -0
  77. ksadk/context_engine/policies.py +319 -0
  78. ksadk/context_engine/projection.py +31 -0
  79. ksadk/context_engine/shadow_plan.py +266 -0
  80. ksadk/context_engine/tokenizer.py +164 -0
  81. ksadk/conversations/__init__.py +85 -94
  82. ksadk/conversations/context.py +126 -6
  83. ksadk/conversations/message_projection.py +292 -28
  84. ksadk/conversations/model_context.py +49 -3
  85. ksadk/conversations/runtime_compaction.py +207 -5
  86. ksadk/conversations/runtime_input.py +193 -3
  87. ksadk/conversations/runtime_invocation.py +81 -3
  88. ksadk/conversations/runtime_observability.py +157 -4
  89. ksadk/conversations/runtime_payloads.py +42 -0
  90. ksadk/conversations/runtime_preparation.py +280 -2
  91. ksadk/conversations/runtime_resume.py +71 -32
  92. ksadk/conversations/runtime_stream_events.py +94 -9
  93. ksadk/conversations/runtime_streaming.py +175 -22
  94. ksadk/conversations/semantic_summary.py +311 -1
  95. ksadk/conversations/session_lock.py +51 -0
  96. ksadk/deployment/env_forward.py +72 -0
  97. ksadk/deployment/managed_runtime.py +38 -0
  98. ksadk/deployment/manager.py +24 -8
  99. ksadk/deployment/providers/serverless.py +228 -92
  100. ksadk/evaluation/__init__.py +116 -0
  101. ksadk/evaluation/a2a_adapter.py +452 -0
  102. ksadk/evaluation/adapters.py +67 -0
  103. ksadk/evaluation/agent_eval_client.py +395 -0
  104. ksadk/evaluation/cloud_binding.py +80 -0
  105. ksadk/evaluation/cloud_converter.py +194 -0
  106. ksadk/evaluation/cloud_service.py +199 -0
  107. ksadk/evaluation/contracts.py +478 -0
  108. ksadk/evaluation/evalset.py +351 -0
  109. ksadk/evaluation/evaluators.py +627 -0
  110. ksadk/evaluation/evidence.py +258 -0
  111. ksadk/evaluation/executor.py +143 -0
  112. ksadk/evaluation/local_adapter.py +550 -0
  113. ksadk/evaluation/service_env.py +27 -0
  114. ksadk/evaluation/storage.py +68 -0
  115. ksadk/evaluation/studio_build_adapter.py +253 -0
  116. ksadk/evaluation/target.py +82 -0
  117. ksadk/events/__init__.py +13 -13
  118. ksadk/events/_v1_compat/__init__.py +1 -0
  119. ksadk/events/_v1_compat/models.py +299 -0
  120. ksadk/events/_v1_compat/parser.py +247 -0
  121. ksadk/events/_v1_compat/projection.py +753 -0
  122. ksadk/events/adapters/__init__.py +34 -0
  123. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  124. ksadk/events/adapters/_a2a_support.py +275 -0
  125. ksadk/events/adapters/_codex_interactions.py +375 -0
  126. ksadk/events/adapters/_codex_items.py +801 -0
  127. ksadk/events/adapters/_codex_validators.py +311 -0
  128. ksadk/events/adapters/_langgraph_support.py +785 -0
  129. ksadk/events/adapters/a2a.py +841 -0
  130. ksadk/events/adapters/adk.py +503 -0
  131. ksadk/events/adapters/codex.py +717 -0
  132. ksadk/events/adapters/langgraph.py +745 -0
  133. ksadk/events/canonical.py +440 -0
  134. ksadk/events/canonical_replay.py +497 -0
  135. ksadk/events/canonical_store.py +468 -0
  136. ksadk/events/cold_recovery.py +257 -0
  137. ksadk/events/content.py +89 -0
  138. ksadk/events/identity.py +86 -0
  139. ksadk/events/pipeline.py +455 -0
  140. ksadk/events/projections.py +66 -0
  141. ksadk/events/reducer.py +661 -0
  142. ksadk/events/replay.py +3 -36
  143. ksadk/events/runtime_event.py +20 -256
  144. ksadk/events/session_event.py +324 -0
  145. ksadk/events/store.py +3 -316
  146. ksadk/events/v1_compat.py +41 -0
  147. ksadk/harness/__init__.py +3 -2
  148. ksadk/harness/app.py +114 -123
  149. ksadk/harness/config.py +1 -1
  150. ksadk/harness/runtime.py +467 -0
  151. ksadk/hermes_terminal.py +5 -0
  152. ksadk/identity/resolver.py +4 -3
  153. ksadk/interaction/__init__.py +28 -0
  154. ksadk/interaction/contracts.py +136 -0
  155. ksadk/interaction/ledger.py +189 -0
  156. ksadk/interaction/provider.py +122 -0
  157. ksadk/interaction/providers/__init__.py +52 -0
  158. ksadk/interaction/providers/adk.py +40 -0
  159. ksadk/interaction/providers/codex.py +94 -0
  160. ksadk/interaction/providers/langgraph.py +58 -0
  161. ksadk/kernel/__init__.py +117 -0
  162. ksadk/kernel/authorization.py +258 -0
  163. ksadk/kernel/bootstrap.py +1074 -0
  164. ksadk/kernel/contract_fingerprints.py +61 -0
  165. ksadk/kernel/contracts.py +362 -0
  166. ksadk/kernel/control.py +219 -0
  167. ksadk/kernel/errors.py +79 -0
  168. ksadk/kernel/ingress.py +1102 -0
  169. ksadk/kernel/mapping.py +111 -0
  170. ksadk/kernel/memory_store.py +1133 -0
  171. ksadk/kernel/postgres_store.py +1756 -0
  172. ksadk/kernel/recovery.py +452 -0
  173. ksadk/kernel/runtime_identity.py +103 -0
  174. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  175. ksadk/kernel/sqlite_store.py +1410 -0
  176. ksadk/kernel/state.py +145 -0
  177. ksadk/kernel/store.py +232 -0
  178. ksadk/kernel/worker.py +953 -0
  179. ksadk/knowledge_base/client.py +22 -4
  180. ksadk/knowledge_base/service.py +32 -5
  181. ksadk/managed_a2a_card.py +160 -0
  182. ksadk/managed_runtime.py +16 -9
  183. ksadk/memory/__init__.py +61 -0
  184. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  185. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  186. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  187. ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
  188. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  189. ksadk/memory/adk/long_term_memory.py +3 -2
  190. ksadk/memory/coordinator.py +461 -0
  191. ksadk/memory/events.py +202 -0
  192. ksadk/memory/extraction.py +228 -0
  193. ksadk/memory/ltm_backend_factory.py +16 -3
  194. ksadk/memory/models.py +316 -0
  195. ksadk/memory/policy.py +215 -0
  196. ksadk/memory/provider.py +47 -0
  197. ksadk/memory/provider_adapter.py +159 -0
  198. ksadk/memory/provider_resolver.py +71 -0
  199. ksadk/memory/providers/__init__.py +5 -0
  200. ksadk/memory/providers/local_sqlite.py +490 -0
  201. ksadk/memory/resolved_policy.py +145 -0
  202. ksadk/memory/service.py +198 -8
  203. ksadk/memory/tool.py +4 -0
  204. ksadk/model_proxy/bootstrap.py +16 -9
  205. ksadk/model_proxy/config.py +22 -1
  206. ksadk/model_proxy/detect.py +202 -9
  207. ksadk/model_proxy/namespace.py +67 -15
  208. ksadk/model_proxy/server.py +168 -6
  209. ksadk/model_proxy/transform.py +105 -3
  210. ksadk/observability/__init__.py +25 -0
  211. ksadk/observability/session_log.py +420 -0
  212. ksadk/observability/trajectory.py +105 -0
  213. ksadk/prompts/__init__.py +71 -0
  214. ksadk/prompts/compiler.py +215 -0
  215. ksadk/prompts/models.py +82 -0
  216. ksadk/prompts/projection.py +89 -0
  217. ksadk/prompts/resolved.py +137 -0
  218. ksadk/prompts/sources.py +233 -0
  219. ksadk/runners/_langgraph_runner_streams.py +822 -0
  220. ksadk/runners/adk_runner.py +14 -31
  221. ksadk/runners/base_runner.py +44 -3
  222. ksadk/runners/factory.py +3 -3
  223. ksadk/runners/langgraph_runner.py +252 -433
  224. ksadk/runners/utils/__init__.py +0 -10
  225. ksadk/runners/utils/loader.py +16 -0
  226. ksadk/runtime/__init__.py +67 -41
  227. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  228. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  229. ksadk/runtime/adapter.py +199 -16
  230. ksadk/runtime/conversation_execution.py +687 -0
  231. ksadk/runtime/executor.py +392 -0
  232. ksadk/runtime/factory.py +342 -0
  233. ksadk/runtime/framework_adapters.py +6 -12
  234. ksadk/runtime/hosted_finalizer.py +268 -0
  235. ksadk/runtime/launch.py +61 -0
  236. ksadk/runtime/preprocessing.py +49 -14
  237. ksadk/runtime/runner_adapter.py +330 -391
  238. ksadk/runtime/runner_loading.py +36 -0
  239. ksadk/runtime/usage.py +33 -0
  240. ksadk/sandbox/backends/e2b.py +36 -3
  241. ksadk/server/__init__.py +39 -4
  242. ksadk/server/app.py +18 -492
  243. ksadk/server/composition.py +36 -2
  244. ksadk/server/factory.py +210 -149
  245. ksadk/server/routes/common.py +15 -46
  246. ksadk/server/routes/control.py +13 -9
  247. ksadk/server/routes/kernel_ingress.py +258 -0
  248. ksadk/server/routes/misc.py +10 -33
  249. ksadk/server/routes/models.py +14 -6
  250. ksadk/server/routes/openai_compat.py +129 -27
  251. ksadk/server/routes/projection.py +84 -16
  252. ksadk/server/routes/run.py +347 -460
  253. ksadk/server/routes/sessions.py +37 -40
  254. ksadk/server/routes/streaming.py +52 -7
  255. ksadk/server/routes/workspace.py +54 -21
  256. ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  257. ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
  258. ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  259. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  260. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  261. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  262. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  263. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  264. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  265. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  266. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  267. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  268. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  269. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  270. ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  271. ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  272. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  273. ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  274. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  275. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  276. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  277. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  278. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  279. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  280. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  281. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  282. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  283. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  284. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  285. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  286. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  287. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  288. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  289. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  290. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  291. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  292. ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
  293. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  294. ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
  295. ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
  296. ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
  297. ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
  298. ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
  299. ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
  300. ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
  301. ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
  302. ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
  303. ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
  304. ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
  305. ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
  306. ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
  307. ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
  308. ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
  309. ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
  310. ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
  311. ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
  312. ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
  313. ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
  314. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  315. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  316. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  317. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  318. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  319. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  320. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  321. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  322. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  323. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  324. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  325. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  326. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  327. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  328. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  329. ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
  330. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  331. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  332. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  333. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  334. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  335. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  336. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  337. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  338. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  339. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  340. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  341. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  342. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  343. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  344. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  345. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  346. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  347. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  348. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  349. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  350. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  351. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  352. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  353. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  354. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  355. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  356. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  357. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  358. ksadk/server/static/index.html +2 -2
  359. ksadk/sessions/__init__.py +26 -9
  360. ksadk/sessions/_local_service_sync.py +688 -0
  361. ksadk/sessions/_local_tables.py +21 -0
  362. ksadk/sessions/_postgres_schema.py +305 -0
  363. ksadk/sessions/_postgres_tables.py +15 -0
  364. ksadk/sessions/base.py +80 -1
  365. ksadk/sessions/in_memory.py +53 -2
  366. ksadk/sessions/local_service.py +63 -604
  367. ksadk/sessions/postgres_service.py +58 -148
  368. ksadk/sessions/resilient.py +35 -1
  369. ksadk/skills/runtime/backends/e2b.py +1 -1
  370. ksadk/skills/service_client.py +13 -3
  371. ksadk/studio/__init__.py +5 -0
  372. ksadk/studio/agent_avatar_assets.py +120 -0
  373. ksadk/studio/agent_lifecycle.py +51 -0
  374. ksadk/studio/api.py +1598 -0
  375. ksadk/studio/api_catalog_routes.py +324 -0
  376. ksadk/studio/api_contracts.py +286 -0
  377. ksadk/studio/api_helpers.py +132 -0
  378. ksadk/studio/api_memory_routes.py +82 -0
  379. ksadk/studio/authoring.py +749 -0
  380. ksadk/studio/authoring_coordinator.py +1017 -0
  381. ksadk/studio/builder.py +397 -0
  382. ksadk/studio/capabilities.py +368 -0
  383. ksadk/studio/cloud.py +2099 -0
  384. ksadk/studio/codex_agent_service.py +694 -0
  385. ksadk/studio/codex_authoring.py +469 -0
  386. ksadk/studio/codex_builder.py +437 -0
  387. ksadk/studio/codex_manifest.py +307 -0
  388. ksadk/studio/codex_run.py +395 -0
  389. ksadk/studio/compiler.py +190 -0
  390. ksadk/studio/contracts.py +755 -0
  391. ksadk/studio/errors.py +47 -0
  392. ksadk/studio/event_store.py +214 -0
  393. ksadk/studio/framework_run.py +342 -0
  394. ksadk/studio/hosted_kernel.py +317 -0
  395. ksadk/studio/identifiers.py +33 -0
  396. ksadk/studio/manifest_resolver.py +131 -0
  397. ksadk/studio/mcp_runtime.py +229 -0
  398. ksadk/studio/model_client.py +629 -0
  399. ksadk/studio/model_profile_service.py +214 -0
  400. ksadk/studio/operations.py +275 -0
  401. ksadk/studio/otel_trace.py +1060 -0
  402. ksadk/studio/pagination.py +182 -0
  403. ksadk/studio/pcm_memory.py +85 -0
  404. ksadk/studio/python_tool_inspection.py +183 -0
  405. ksadk/studio/repository.py +327 -0
  406. ksadk/studio/resource_catalog.py +1260 -0
  407. ksadk/studio/run_service.py +1133 -0
  408. ksadk/studio/runtime_catalog.py +74 -0
  409. ksadk/studio/runtime_source.py +365 -0
  410. ksadk/studio/service.py +2249 -0
  411. ksadk/studio/shared_web.py +1004 -0
  412. ksadk/studio/skill_discovery.py +628 -0
  413. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
  414. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  415. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  416. ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
  417. ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  418. ksadk/studio/static/index.html +15 -0
  419. ksadk/studio/templates.py +722 -0
  420. ksadk/studio/validator.py +265 -0
  421. ksadk/studio/workspace.py +135 -0
  422. ksadk/tools/gateway.py +9 -0
  423. ksadk/toolsets/__init__.py +27 -1
  424. ksadk/toolsets/a2a.py +426 -0
  425. ksadk/tracing/__init__.py +19 -21
  426. ksadk/tracing/setup.py +317 -403
  427. ksadk/tracing/span_utils.py +0 -44
  428. ksadk/version.py +1 -1
  429. ksadk_runtime_common/memory_backend/registry.py +56 -56
  430. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  431. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  432. ksadk_runtime_common/workspace_files/router.py +8 -2
  433. agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
  434. ksadk/a2a/card_builder.py +0 -64
  435. ksadk/a2a/client.py +0 -309
  436. ksadk/events/parser.py +0 -191
  437. ksadk/harness/runner.py +0 -211
  438. ksadk/runners/codex_runner.py +0 -132
  439. ksadk/runners/utils/langfuse.py +0 -230
  440. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  441. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
  442. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
  443. ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
  444. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
  445. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
  446. ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
  447. ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
  448. ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
  449. ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
  450. ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
  451. ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
  452. ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
  453. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
  454. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
  455. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
  456. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  457. ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
  458. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  459. ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
  460. ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
  461. ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
  462. ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
  463. ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
  464. ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
  465. ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
  466. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
  467. ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
  468. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
  469. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
  470. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
  471. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  472. ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
  473. ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
  474. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
  475. ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
  476. ksadk/server/static/assets/katex-DATssjp1.js +0 -265
  477. ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
  478. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
  479. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
  480. ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
  481. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
  482. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
  483. ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
  484. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
  485. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
  486. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
  487. ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
  488. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
  489. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
  490. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
  491. ksadk/tracing/exporters/langfuse_exporter.py +0 -460
  492. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  493. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  494. {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1074 @@
1
+ # -*- coding: utf-8 -*-
2
+ """生产 composition root(Phase 1 Task 4 Step 4)。
3
+
4
+ ``build_agent_kernel_runtime(config) -> AgentKernelRuntime`` 把 AgentKernel
5
+ 栈的全部运行时角色组装成一个可启动 / 可关闭的单元:
6
+
7
+ - ``AgentKernel``(Store + fenced SessionEvent store + permit verifier,
8
+ verifier 挂 durable nonce store);
9
+ - ``AgentKernelWorker``(per-session FIFO 执行);
10
+ - ``LeaseHeartbeat``(activation lease 的获取 / 续约 / takeover 检测);
11
+ - ``RecoveryCoordinator``(open run 的 attach / resume / 确定性 interrupted);
12
+ - ``AgentKernelReadiness``(真实 store 查询 + worker 运行态 + lease 健康 +
13
+ digest 比对),供 ``/agent-kernel/v1/health`` 与 Operator
14
+ ``AgentKernelReady`` 消费。
15
+
16
+ hosted 模式 fail loud:缺 PG DSN、Server JWKS、permit issuer、
17
+ RuntimeAdapter provider、contract digest 或 durable nonce store 时
18
+ ``build_agent_kernel_runtime`` 直接抛 ``RuntimeError``,绝不静默降级到
19
+ 内存栈或本地自签 authority。
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ import hashlib
25
+ import logging
26
+ import os
27
+ import time
28
+ from collections.abc import Callable
29
+ from dataclasses import dataclass, field
30
+ from datetime import datetime
31
+ from typing import Any, Literal
32
+
33
+ from ksadk.events.session_event import SessionServiceEventStore
34
+ from ksadk.kernel.authorization import AgentControlPermitVerifier, InMemoryNonceStore
35
+ from ksadk.kernel.contract_fingerprints import (
36
+ AGENT_KERNEL_V1_AGGREGATE_DIGEST,
37
+ runtime_capability_matrix_digest,
38
+ runtime_capability_matrix_wire_value,
39
+ )
40
+ from ksadk.kernel.contracts import RuntimeCapabilityMatrix
41
+ from ksadk.kernel.control import AgentKernel, default_capability_matrix
42
+ from ksadk.kernel.errors import InvalidCommandError
43
+ from ksadk.kernel.recovery import RecoveryCoordinator
44
+ from ksadk.kernel.runtime_identity import runtime_identity
45
+ from ksadk.kernel.store import AgentKernelStore, now_utc
46
+ from ksadk.kernel.worker import AgentKernelWorker
47
+ from ksadk.runtime.adapter import RuntimeAdapter
48
+
49
+ AuthorityMode = Literal["local", "hosted"]
50
+ DurabilityTier = Literal["durable", "ephemeral"]
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ @dataclass
56
+ class AgentKernelRuntimeConfig:
57
+ """生产装配配置(Operator env 投影或测试注入 fake PG provider)。"""
58
+
59
+ agent_instance_id: str
60
+ authority_mode: AuthorityMode = "local"
61
+ driver: str = "memory" # postgres | sqlite | memory
62
+ # durable: PG-backed inbox/lease/nonce + recovery; ephemeral: one-pod
63
+ # runtime that intentionally loses kernel state on restart.
64
+ durability_tier: DurabilityTier = "durable"
65
+ dsn: str = ""
66
+ # server authority(hosted 必填)
67
+ jwks: Any | None = None
68
+ permit_issuer: str | None = None
69
+ nonce_store: Any | None = None
70
+ # 运行时
71
+ adapter_provider: Callable[[], RuntimeAdapter] | None = None
72
+ capabilities: Callable[[], RuntimeCapabilityMatrix] | None = None
73
+ start_request_defaults: dict[str, Any] = field(default_factory=dict)
74
+ # 契约 digest(hosted 必填 contract_digest)
75
+ contract_digest: str = ""
76
+ capability_digest: str = ""
77
+ bundle_digest: str = ""
78
+ # Session log 的 scope 必须与普通 SessionService、canonical event log
79
+ # 完全一致;否则 worker 可启动却会在首条 command 后看不到 session。
80
+ session_namespace: str = "default"
81
+ tenant_id: str = "default"
82
+ workspace_id: str = "default"
83
+ # 测试注入的 fake PG provider:提供时不再从 dsn 建真实连接,
84
+ # 但 hosted 模式的 dsn 必填校验仍然生效。
85
+ store: AgentKernelStore | None = None
86
+ session_events: Any | None = None
87
+ session_service: Any | None = None
88
+ # Runtime App composition root supplies these so recovery can attach via
89
+ # the same RuntimeAdapter registry rather than creating an unrelated path.
90
+ runtime_executor: Any | None = None
91
+ launch_context: Any | None = None
92
+ pool: Any | None = None
93
+ owns_pool: bool = False
94
+ # 生命周期参数
95
+ queue_limit: int = 100
96
+ lease_ttl_seconds: float = 60.0
97
+ poll_interval: float = 0.25
98
+ activation_id: str | None = None
99
+ runtime_type: str = "ksadk-agent-kernel"
100
+ clock: Callable[[], datetime] = now_utc
101
+ # 容错粒度:连续多少个不同 session 恢复失败才认为全局性故障(进程级
102
+ # degraded);store 连续多少个 poll 周期不可达才整体降级。
103
+ quarantine_degrade_threshold: int = 5
104
+ store_failure_degrade_threshold: int = 10
105
+
106
+
107
+ class LeaseHeartbeat:
108
+ """activation lease 的获取 / 续约 / takeover 检测。
109
+
110
+ 同一 workload activation 在每个 session 有一个派生且稳定的
111
+ ``activation_id``。这样 Store 的 ``renew_activation(id)`` / fenced event
112
+ guard 可以无歧义定位一行 lease;不能把单个 Pod id 原样复用于多行
113
+ session activation。token 变化(> 已知值)说明发生过 takeover,调用方
114
+ 应触发 RecoveryCoordinator 对 open run 做确定性收口。
115
+ """
116
+
117
+ def __init__(
118
+ self,
119
+ store: AgentKernelStore,
120
+ *,
121
+ agent_instance_id: str,
122
+ activation_id: str,
123
+ runtime_type: str,
124
+ bundle_digest: str,
125
+ capability_digest: str,
126
+ lease_ttl_seconds: float,
127
+ ) -> None:
128
+ self._store = store
129
+ self.agent_instance_id = agent_instance_id
130
+ self.activation_id = activation_id
131
+ self._request = dict(
132
+ agent_instance_id=agent_instance_id,
133
+ runtime_type=runtime_type,
134
+ bundle_digest=bundle_digest or "unknown",
135
+ capability_digest=capability_digest or "unknown",
136
+ lease_ttl_seconds=lease_ttl_seconds,
137
+ )
138
+ self._last_tokens: dict[str, int] = {}
139
+
140
+ def activation_id_for_session(self, session_id: str) -> str:
141
+ """Return the opaque per-session lease owner id for this workload."""
142
+
143
+ digest = hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:16]
144
+ return f"{self.activation_id}:s:{digest}"
145
+
146
+ def owns_lease(self, session_id: str, lease: Any) -> bool:
147
+ return str(getattr(lease, "activation_id", "")) == self.activation_id_for_session(
148
+ session_id
149
+ )
150
+
151
+ async def ensure_lease(self, session_id: str) -> tuple[Any, bool]:
152
+ """获取(或幂等续约)session 的 lease。
153
+
154
+ 返回 ``(lease, took_over)``:lease 为 None 表示被其它 owner 持有;
155
+ ``took_over`` 表示本次拿到的 fencing token 比已知值新(发生过
156
+ takeover,需要 recovery)。
157
+ """
158
+
159
+ from ksadk.kernel.store import ActivationLeaseRequest
160
+
161
+ try:
162
+ lease = await self._store.acquire_activation(
163
+ ActivationLeaseRequest(
164
+ session_id=session_id,
165
+ activation_id=self.activation_id_for_session(session_id),
166
+ **self._request,
167
+ )
168
+ )
169
+ except InvalidCommandError:
170
+ return None, False
171
+ last = self._last_tokens.get(session_id)
172
+ took_over = (last is None and lease.fencing_token > 1) or (
173
+ last is not None and lease.fencing_token > last
174
+ )
175
+ self._last_tokens[session_id] = lease.fencing_token
176
+ return lease, took_over
177
+
178
+ def forget(self, session_id: str) -> None:
179
+ self._last_tokens.pop(session_id, None)
180
+
181
+
182
+ @dataclass
183
+ class AgentKernelReadiness:
184
+ """truthful readiness probe:每个维度都是真实查询,不是配置回显。"""
185
+
186
+ runtime: "AgentKernelRuntime"
187
+
188
+ async def check(self) -> dict[str, Any]:
189
+ config = self.runtime.config
190
+ store_ok = False
191
+ try:
192
+ # 真实 store 查询(PG driver 即真实 SQL round-trip)。
193
+ await self.runtime.kernel_store.list_messages(
194
+ config.agent_instance_id
195
+ )
196
+ store_ok = True
197
+ except Exception:
198
+ store_ok = False
199
+
200
+ lease_healthy = True
201
+ activation_id: str | None = None
202
+ for session_id in self.runtime.heartbeat_sessions():
203
+ try:
204
+ lease = await self.runtime.kernel_store.current_lease(
205
+ config.agent_instance_id, session_id
206
+ )
207
+ except Exception:
208
+ lease = None
209
+ if lease is None:
210
+ lease_healthy = False
211
+ continue
212
+ if not self.runtime.lease_heartbeat.owns_lease(session_id, lease):
213
+ # lease 存在但已被其它 activation 接管:对本 runtime 而言
214
+ # 等价于丢失,必须如实上报 not-ready。
215
+ lease_healthy = False
216
+ continue
217
+ activation_id = activation_id or lease.activation_id
218
+ expires = getattr(lease, "lease_expires_at", "")
219
+ try:
220
+ from datetime import datetime
221
+
222
+ expires_at = datetime.fromisoformat(
223
+ str(expires).replace("Z", "+00:00")
224
+ )
225
+ if expires_at <= datetime.now(expires_at.tzinfo):
226
+ lease_healthy = False
227
+ except ValueError:
228
+ lease_healthy = False
229
+
230
+ worker_running = self.runtime.worker_running
231
+ degraded = self.runtime.degraded
232
+ quarantined = self.runtime.quarantined_sessions()
233
+ capability = self.runtime.kernel.capabilities()
234
+ capability_matrix = runtime_capability_matrix_wire_value(capability)
235
+ computed_capability_digest = runtime_capability_matrix_digest(capability)
236
+ # The control plane compares all three digests before declaring an
237
+ # AgentInstance ready. Reporting ready with only a contract digest
238
+ # would conceal a missing bundle/capability projection and make the
239
+ # runtime's health endpoint more optimistic than Server readiness.
240
+ digests_match = all(
241
+ (
242
+ config.contract_digest == AGENT_KERNEL_V1_AGGREGATE_DIGEST,
243
+ config.capability_digest == computed_capability_digest,
244
+ config.bundle_digest,
245
+ )
246
+ )
247
+ ready = (
248
+ store_ok and worker_running and lease_healthy and digests_match
249
+ and not degraded
250
+ )
251
+ health = {
252
+ "ready": ready,
253
+ "store_ok": store_ok,
254
+ "worker_running": worker_running,
255
+ "degraded": degraded,
256
+ # additive:被隔离(恢复失败)的 session 数量;隔离本身不影响
257
+ # ready,其余 session 照常服务。
258
+ "quarantined_sessions": len(quarantined),
259
+ "lease_healthy": lease_healthy,
260
+ "activation_id": activation_id,
261
+ # Contract support is packaged with this KsADK image; never echo
262
+ # an unverified control-plane environment value as evidence.
263
+ "contract_digest": AGENT_KERNEL_V1_AGGREGATE_DIGEST,
264
+ # Likewise derive capabilities from the actual Adapter matrix,
265
+ # rather than trusting the requested deployment digest.
266
+ "capability_digest": computed_capability_digest,
267
+ # Runtime/Operator/Server readiness chain must carry the actual
268
+ # typed capability facts, not merely a digest supplied at deploy
269
+ # time. Server admission uses these to reject unsupported control
270
+ # operations before they enter the durable inbox.
271
+ "capabilities": capability_matrix,
272
+ "bundle_digest": config.bundle_digest,
273
+ "durability_tier": config.durability_tier,
274
+ # Identity is derived from the KsADK source Python imported, not
275
+ # ``importlib.metadata`` for the base image distribution.
276
+ "runtime_identity": runtime_identity(),
277
+ }
278
+ # 诊断字段(additive,runtime 内部端点非 wire 冻结合同):
279
+ # degraded 时必须能从 health 直接回答 "为什么降级、何时降级",
280
+ # 出问题的 session 明细同样可见,运维不必再对着布尔值猜。
281
+ if quarantined:
282
+ health["quarantined_session_ids"] = sorted(quarantined)
283
+ if degraded:
284
+ health["degradation_reason"] = self.runtime._degradation_reason
285
+ health["degraded_at"] = self.runtime._degraded_at
286
+ health["degradation_last_error"] = (
287
+ self.runtime._degraded_last_error
288
+ )
289
+ return health
290
+
291
+
292
+ @dataclass
293
+ class AgentKernelRuntime:
294
+ """生产 kernel runtime:start() 启动后台 worker/lease loop,close() 全停。"""
295
+
296
+ config: AgentKernelRuntimeConfig
297
+ kernel: AgentKernel
298
+ worker: AgentKernelWorker
299
+ recovery: RecoveryCoordinator
300
+ lease_heartbeat: LeaseHeartbeat
301
+ readiness: AgentKernelReadiness
302
+ kernel_store: AgentKernelStore = field(repr=False)
303
+ session_events: Any = field(repr=False)
304
+ _owns_pool: bool = field(default=False, repr=False)
305
+ _pool: Any = field(default=None, repr=False)
306
+
307
+ def __post_init__(self) -> None:
308
+ self._tasks: list[asyncio.Task] = []
309
+ self._worker_running = False
310
+ self._heartbeat_sessions: set[str] = set()
311
+ self._last_renewed: dict[str, float] = {}
312
+ self._degraded = False
313
+ # P0-1 粒度修正:单个 session 恢复失败不再拖垮整个 runtime。
314
+ self._quarantined: set[str] = set()
315
+ self._recovery_failed_sessions: set[str] = set()
316
+ self._store_failures = 0
317
+ # 诊断状态:degraded 必须能回答 "为什么、什么时候、哪些 session",
318
+ # 让 kubectl logs 与 health 端点一眼可见(此前只有 degraded 布尔值)。
319
+ self._degradation_reason: str | None = None
320
+ self._degraded_at: str | None = None
321
+ self._degraded_last_error: str | None = None
322
+
323
+ # ------------------------------------------------------------ degradation
324
+
325
+ def _mark_degraded(
326
+ self, reason: str, exc: BaseException | None = None
327
+ ) -> None:
328
+ """统一降级入口:醒目 ERROR 日志 + 可供 health 端点回读的诊断状态。"""
329
+
330
+ self._degraded = True
331
+ if self._degradation_reason is None:
332
+ self._degradation_reason = reason
333
+ last_error = (
334
+ f"{type(exc).__name__}: {exc}" if exc is not None else "n/a"
335
+ )
336
+ self._degraded_last_error = last_error
337
+ try:
338
+ self._degraded_at = self.config.clock().isoformat()
339
+ except Exception: # pragma: no cover - clock 异常不应影响降级本身
340
+ self._degraded_at = None
341
+ logger.error(
342
+ "agent kernel degraded: agent_instance_id=%s reason=%s "
343
+ "failed_sessions=%d quarantined=%d last_error=%s",
344
+ self.config.agent_instance_id,
345
+ self._degradation_reason,
346
+ len(self._recovery_failed_sessions),
347
+ len(self._quarantined),
348
+ last_error,
349
+ )
350
+
351
+ # ------------------------------------------------------------ properties
352
+
353
+ @property
354
+ def worker_running(self) -> bool:
355
+ return self._worker_running
356
+
357
+ @property
358
+ def degraded(self) -> bool:
359
+ """P0-1:takeover 收口彻底失败后 runtime 显式降级(停止消费 Inbox)。"""
360
+
361
+ return self._degraded
362
+
363
+ def heartbeat_sessions(self) -> set[str]:
364
+ return set(self._heartbeat_sessions)
365
+
366
+ def quarantined_sessions(self) -> set[str]:
367
+ """被隔离的 session:恢复失败且不再被本 runtime 消费。"""
368
+
369
+ return set(self._quarantined)
370
+
371
+ @property
372
+ def background_tasks(self) -> list[asyncio.Task]:
373
+ return list(self._tasks)
374
+
375
+ # ------------------------------------------------------------- lifecycle
376
+
377
+ async def start(self) -> None:
378
+ if self._tasks:
379
+ return
380
+ self._worker_running = True
381
+ self._tasks.append(asyncio.create_task(self._run_loop(), name="kernel-runtime"))
382
+ # 心跳续约必须是独立任务:run loop 可能长时间阻塞在某个 session 的
383
+ # adapter.start()(hosted pod 上 codex 握手可超过 lease TTL),内联
384
+ # 续约会把其它已持有 lease 的 session 拖过期(stream guard StaleFence)。
385
+ self._tasks.append(
386
+ asyncio.create_task(self._heartbeat_loop(), name="kernel-heartbeat")
387
+ )
388
+
389
+ async def close(self) -> None:
390
+ for task in self._tasks:
391
+ if not task.done():
392
+ task.cancel()
393
+ for task in self._tasks:
394
+ try:
395
+ await task
396
+ except (asyncio.CancelledError, Exception):
397
+ pass
398
+ self._tasks.clear()
399
+ self._worker_running = False
400
+ # best-effort 释放持有的 activation(不阻塞关闭)。
401
+ for session_id in list(self._heartbeat_sessions | self._quarantined):
402
+ try:
403
+ lease = await self.kernel_store.current_lease(
404
+ self.config.agent_instance_id, session_id
405
+ )
406
+ if lease is not None and self.lease_heartbeat.owns_lease(
407
+ session_id, lease
408
+ ):
409
+ await self.kernel_store.release_activation(
410
+ lease.activation_id, expected_fence=lease.fencing_token
411
+ )
412
+ except Exception:
413
+ pass
414
+ self.lease_heartbeat.forget(session_id)
415
+ self._heartbeat_sessions.clear()
416
+ self._last_renewed.clear()
417
+ if self._owns_pool and self._pool is not None and hasattr(self._pool, "close"):
418
+ try:
419
+ await self._pool.close()
420
+ except Exception:
421
+ pass
422
+
423
+ # ------------------------------------------------------------- run loop
424
+
425
+ async def _run_loop(self) -> None:
426
+ self._worker_running = True
427
+ try:
428
+ while True:
429
+ if self._degraded:
430
+ return
431
+ progressed = False
432
+ try:
433
+ sessions = await self._pending_sessions()
434
+ for session_id in sorted(sessions):
435
+ if session_id in self._quarantined:
436
+ # 隔离中的 session:不 claim inbox、不恢复、
437
+ # 不写任何 canonical 事件(等待人工清理)。
438
+ continue
439
+ lease, took_over = await self.lease_heartbeat.ensure_lease(
440
+ session_id
441
+ )
442
+ if lease is None:
443
+ continue
444
+ self._heartbeat_sessions.add(session_id)
445
+ self._last_renewed[session_id] = time.monotonic()
446
+ if took_over:
447
+ # takeover:对 open run 做确定性收口(attach /
448
+ # resume / interrupted),再继续消费 inbox。
449
+ # P0-1:recover 抛错不得静默吞掉——先尝试
450
+ # durable 兜底收口;连收口都失败则隔离该
451
+ # session 并上报,其余 session 继续服务;只有
452
+ # 全局性故障(store 不可达或失败扩散到阈值)
453
+ # 才进程级 degraded。
454
+ failure = await self._recover_safely(
455
+ lease, session_id
456
+ )
457
+ if failure is not None:
458
+ if not await self._quarantine_session(
459
+ session_id, failure
460
+ ):
461
+ return
462
+ continue
463
+ result = await self.worker.run_once(
464
+ self.config.agent_instance_id,
465
+ lease,
466
+ session_id=session_id,
467
+ )
468
+ if result.outcome != "idle":
469
+ progressed = True
470
+ self._store_failures = 0
471
+ except asyncio.CancelledError:
472
+ raise
473
+ except Exception:
474
+ self._store_failures += 1
475
+ if (
476
+ self._store_failures
477
+ >= self.config.store_failure_degrade_threshold
478
+ and not await self._store_reachable()
479
+ ):
480
+ # store 持续不可达是全局性故障:宁降级不静默。
481
+ self._mark_degraded("store_unreachable")
482
+ return
483
+ await asyncio.sleep(self.config.poll_interval * 4)
484
+ continue
485
+ if not progressed:
486
+ await asyncio.sleep(self.config.poll_interval)
487
+ finally:
488
+ self._worker_running = False
489
+
490
+ async def _heartbeat_loop(self) -> None:
491
+ """独立心跳任务:按 TTL/3 节奏续约所有已持有的 lease。"""
492
+
493
+ interval = max(
494
+ self.config.lease_ttl_seconds / 3.0, self.config.poll_interval
495
+ )
496
+ while True:
497
+ await asyncio.sleep(interval / 2.0)
498
+ await self._renew_leased_sessions()
499
+ if self._degraded:
500
+ return
501
+
502
+ async def _renew_leased_sessions(self) -> None:
503
+ """P0:已持有 lease 的 session 在固定间隔上持续续约。
504
+
505
+ 之前续约只发生在有 pending inbox 工作(accepted/claimed 消息)时,
506
+ run 完成 / 等待审批的 session 不再续约但留在 heartbeat 集合里,
507
+ lease TTL 过后 readiness 误判 not-ready(预发需重启 pod 才恢复)。
508
+ readiness 语义应是 "runtime 存活且能服务",不是 "正在忙":只要
509
+ activation lease 仍由本 runtime 持有,就以 TTL/3 的节奏幂等续约。
510
+ """
511
+
512
+ interval = max(
513
+ self.config.lease_ttl_seconds / 3.0, self.config.poll_interval
514
+ )
515
+ now = time.monotonic()
516
+ for session_id in sorted(self._heartbeat_sessions):
517
+ if session_id in self._quarantined:
518
+ continue
519
+ if now - self._last_renewed.get(session_id, 0.0) < interval:
520
+ continue
521
+ self._last_renewed[session_id] = now
522
+ try:
523
+ lease, took_over = await self.lease_heartbeat.ensure_lease(
524
+ session_id
525
+ )
526
+ except asyncio.CancelledError:
527
+ raise
528
+ except Exception:
529
+ # 瞬时 store 错误:保留 session,下个续约周期重试。
530
+ continue
531
+ if lease is None:
532
+ # lease 被其它 activation 持有(真正丢失):保留在集合里,
533
+ # readiness 如实上报 not-ready。
534
+ continue
535
+ failure = None
536
+ if took_over:
537
+ failure = await self._recover_safely(lease, session_id)
538
+ if failure is not None:
539
+ if not await self._quarantine_session(session_id, failure):
540
+ if not self._degraded:
541
+ self._mark_degraded(
542
+ "renew_recovery_failure", failure
543
+ )
544
+ return
545
+
546
+ async def _recover_safely(
547
+ self, lease, session_id: str | None = None
548
+ ) -> Exception | None:
549
+ """takeover 后的安全恢复:失败必须持久化收口,否则返回失败原因。
550
+
551
+ 返回 None 表示恢复路径已收口(含 durable interrupted 兜底),
552
+ 可以继续消费 Inbox;返回异常表示连兜底收口都失败,由调用方决定
553
+ 隔离该 session 还是进程级 degraded。
554
+ """
555
+
556
+ try:
557
+ await self.recovery.recover(self.config.agent_instance_id, lease)
558
+ return None
559
+ except Exception as exc:
560
+ first_failure = exc
561
+ # 恢复主路径失败:降级/quarantine 决策前必须先留下完整现场
562
+ # (此前这里静默吞掉,坏 session 全程零日志)。
563
+ logger.exception(
564
+ "agent kernel takeover recovery failed: "
565
+ "agent_instance_id=%s session_id=%s activation_id=%s "
566
+ "error=%s: %s",
567
+ self.config.agent_instance_id,
568
+ session_id or getattr(lease, "session_id", None),
569
+ getattr(lease, "activation_id", None),
570
+ type(exc).__name__,
571
+ exc,
572
+ )
573
+ try:
574
+ await self.recovery.settle_interrupted(
575
+ self.config.agent_instance_id, lease
576
+ )
577
+ # 主恢复失败但 durable interrupted 兜底收口成功:半恢复状态,
578
+ # 运维需要可见(事件流里会出现确定性的 interrupted 收口)。
579
+ logger.warning(
580
+ "agent kernel settled interrupted after recovery failure: "
581
+ "agent_instance_id=%s session_id=%s activation_id=%s "
582
+ "recovery_error=%s: %s",
583
+ self.config.agent_instance_id,
584
+ session_id or getattr(lease, "session_id", None),
585
+ getattr(lease, "activation_id", None),
586
+ type(first_failure).__name__,
587
+ first_failure,
588
+ )
589
+ return None
590
+ except Exception as exc:
591
+ logger.exception(
592
+ "agent kernel interrupted-settlement fallback failed: "
593
+ "agent_instance_id=%s session_id=%s activation_id=%s "
594
+ "error=%s: %s",
595
+ self.config.agent_instance_id,
596
+ session_id or getattr(lease, "session_id", None),
597
+ getattr(lease, "activation_id", None),
598
+ type(exc).__name__,
599
+ exc,
600
+ )
601
+ return first_failure or exc
602
+
603
+ async def _store_reachable(self) -> bool:
604
+ """store 是否仍可用:用于区分 session 级故障与全局连接故障。"""
605
+
606
+ try:
607
+ await self.kernel_store.list_messages(self.config.agent_instance_id)
608
+ except Exception:
609
+ return False
610
+ return True
611
+
612
+ async def _quarantine_session(self, session_id: str, exc: Exception) -> bool:
613
+ """隔离一个恢复失败的 session;返回 False 表示已触发进程级降级。
614
+
615
+ 被隔离的 session 不再被本 runtime claim / 恢复 / 续约,其 inbox
616
+ 消息保持 accepted(人工清理后可被新 activation 恢复);不写任何
617
+ canonical 事件,避免污染日志。只有全局性故障——store 不可达或
618
+ 恢复失败扩散到 ``quarantine_degrade_threshold`` 个不同 session——
619
+ 才升级为进程级 degraded。
620
+ """
621
+
622
+ if not await self._store_reachable():
623
+ # store 本身不可达:这不是单个 session 的问题。
624
+ self._mark_degraded(
625
+ "store_unreachable_during_recovery", exc
626
+ )
627
+ return False
628
+ self._quarantined.add(session_id)
629
+ self._recovery_failed_sessions.add(session_id)
630
+ self._heartbeat_sessions.discard(session_id)
631
+ self._last_renewed.pop(session_id, None)
632
+ self.lease_heartbeat.forget(session_id)
633
+ logger.warning(
634
+ "agent kernel session %s quarantined after takeover recovery "
635
+ "failed: agent_instance_id=%s error=%s: %s",
636
+ session_id,
637
+ self.config.agent_instance_id,
638
+ type(exc).__name__,
639
+ exc,
640
+ )
641
+ if (
642
+ len(self._recovery_failed_sessions)
643
+ >= self.config.quarantine_degrade_threshold
644
+ ):
645
+ self._mark_degraded(
646
+ "recovery_failures_spread_to_%d_sessions" % len(
647
+ self._recovery_failed_sessions
648
+ ),
649
+ exc,
650
+ )
651
+ return False
652
+ return True
653
+
654
+ async def _pending_sessions(self) -> set[str]:
655
+ messages = await self.kernel_store.list_messages(
656
+ self.config.agent_instance_id
657
+ )
658
+ inbox_sessions = {
659
+ message.session_id
660
+ for message in messages
661
+ if message.status.value in ("accepted", "claimed")
662
+ }
663
+ # Inbox is completed as soon as a stream is launched. Keep renewing
664
+ # the owning lease after the independent live execution finishes too:
665
+ # this runtime remains the session's activation owner while the Pod is
666
+ # healthy, so a later control command stays on the same fenced owner
667
+ # and readiness can truthfully detect an external takeover. ``close``
668
+ # releases the retained leases; an ungraceful stop lets their TTL
669
+ # expire for recovery by a new activation.
670
+ active_sessions = self.worker.active_session_ids()
671
+ return inbox_sessions | active_sessions
672
+
673
+
674
+ # ---------------------------------------------------------------------------
675
+ # 进程级 runtime 注册(/agent-kernel/v1/health 消费)
676
+ # ---------------------------------------------------------------------------
677
+
678
+ _runtime: AgentKernelRuntime | None = None
679
+
680
+
681
+ def set_agent_kernel_runtime(runtime: AgentKernelRuntime | None) -> None:
682
+ global _runtime
683
+ _runtime = runtime
684
+
685
+
686
+ def get_agent_kernel_runtime() -> AgentKernelRuntime | None:
687
+ return _runtime
688
+
689
+
690
+ def clear_agent_kernel_runtime() -> None:
691
+ set_agent_kernel_runtime(None)
692
+
693
+
694
+ # ---------------------------------------------------------------------------
695
+ # build
696
+ # ---------------------------------------------------------------------------
697
+
698
+
699
+ def _validate_hosted(config: AgentKernelRuntimeConfig) -> None:
700
+ if config.authority_mode != "hosted":
701
+ return
702
+ missing: list[str] = []
703
+ if not config.agent_instance_id or config.agent_instance_id == "local-agent":
704
+ missing.append("agent_instance_id")
705
+ if config.driver not in {"postgres", "memory"}:
706
+ missing.append("driver(postgres|memory)")
707
+ if config.driver == "postgres" and not config.dsn:
708
+ missing.append("dsn")
709
+ if config.driver == "postgres" and config.durability_tier != "durable":
710
+ missing.append("durability_tier=durable for postgres")
711
+ if config.driver == "memory" and config.durability_tier != "ephemeral":
712
+ missing.append("durability_tier=ephemeral for memory")
713
+ if config.jwks is None:
714
+ missing.append("jwks")
715
+ if not config.permit_issuer:
716
+ missing.append("permit_issuer")
717
+ if config.adapter_provider is None:
718
+ missing.append("adapter_provider")
719
+ if not config.contract_digest:
720
+ missing.append("contract_digest")
721
+ if not config.capability_digest:
722
+ missing.append("capability_digest")
723
+ if not config.bundle_digest:
724
+ missing.append("bundle_digest")
725
+ if config.nonce_store is None:
726
+ missing.append("nonce_store")
727
+ # 租约的 owner 必须是实际 workload identity。固定的 instance-level
728
+ # fallback 会把多 Pod 误识别为同一个 activation,破坏 fencing/takeover。
729
+ if not config.activation_id:
730
+ missing.append("activation_id")
731
+ if missing:
732
+ raise RuntimeError(
733
+ "hosted agent kernel runtime requires "
734
+ + ", ".join(missing)
735
+ + "; refusing to bootstrap (fail closed)"
736
+ )
737
+ if config.contract_digest != AGENT_KERNEL_V1_AGGREGATE_DIGEST:
738
+ raise RuntimeError(
739
+ "contract_digest_mismatch: hosted agent kernel runtime image "
740
+ "does not support the control-plane contract digest"
741
+ )
742
+
743
+
744
+ def build_agent_kernel_runtime(
745
+ config: AgentKernelRuntimeConfig,
746
+ ) -> AgentKernelRuntime:
747
+ """组装生产 kernel runtime;hosted 模式缺依赖时 fail loud。"""
748
+
749
+ _validate_hosted(config)
750
+
751
+ store = config.store
752
+ session_events = config.session_events
753
+ session_service = config.session_service
754
+ owns_pool = config.owns_pool
755
+ pool = config.pool
756
+
757
+ if store is None or session_events is None:
758
+ if config.driver == "postgres":
759
+ from ksadk.kernel.postgres_store import (
760
+ PostgresAgentKernelStore,
761
+ PostgresFencedSessionEventStore,
762
+ PostgresKernelEventLog,
763
+ )
764
+ from ksadk.sessions.postgres_service import PostgresSessionService
765
+
766
+ if not config.dsn:
767
+ raise RuntimeError(
768
+ "postgres agent kernel runtime requires a store DSN"
769
+ )
770
+ if session_service is None:
771
+ session_service = PostgresSessionService(
772
+ dsn=config.dsn,
773
+ namespace=config.session_namespace,
774
+ tenant_id=config.tenant_id,
775
+ workspace_id=config.workspace_id,
776
+ )
777
+ pool = getattr(session_service, "_pool", None)
778
+ event_log = PostgresKernelEventLog(
779
+ pool,
780
+ namespace=session_service.namespace,
781
+ tenant_id=session_service.tenant_id,
782
+ workspace_id=session_service.workspace_id,
783
+ )
784
+ kernel_store: AgentKernelStore = PostgresAgentKernelStore(
785
+ pool, event_log
786
+ )
787
+ # typed RuntimeEvent 写路径走 fenced store:每个
788
+ # ActivationWriteGuard append 在同一事务验证 activation 行。
789
+ events = PostgresFencedSessionEventStore(kernel_store) # type: ignore[arg-type]
790
+ else:
791
+ from ksadk.kernel.memory_store import InMemoryAgentKernelStore
792
+ from ksadk.sessions.in_memory import InMemorySessionService
793
+
794
+ if session_service is None:
795
+ session_service = InMemorySessionService()
796
+ base_events = SessionServiceEventStore(session_service)
797
+ kernel_store = InMemoryAgentKernelStore(base_events)
798
+ events = base_events
799
+ store = store or kernel_store
800
+ session_events = session_events or events
801
+
802
+ if config.authority_mode == "hosted":
803
+ verifier = AgentControlPermitVerifier(config.jwks, nonce_store=config.nonce_store)
804
+ else:
805
+ from ksadk.kernel.ingress import _default_issuer
806
+
807
+ verifier = _default_issuer().verifier(nonce_store=config.nonce_store)
808
+
809
+ adapter_provider = config.adapter_provider or _no_adapter_provider
810
+ capabilities = config.capabilities
811
+ if config.authority_mode == "hosted":
812
+ # Snapshot the actual adapter declaration before accepting work. A
813
+ # hosted pod must not downgrade to the default matrix if its adapter
814
+ # fails to describe itself: that could make Server's capability
815
+ # admission disagree with the execution owner.
816
+ try:
817
+ capability_snapshot = (
818
+ capabilities() if capabilities is not None else adapter_provider().capabilities()
819
+ )
820
+ except Exception as exc:
821
+ raise RuntimeError(
822
+ "hosted agent kernel runtime cannot determine adapter capabilities"
823
+ ) from exc
824
+ computed_capability_digest = runtime_capability_matrix_digest(
825
+ capability_snapshot
826
+ )
827
+ if config.capability_digest != computed_capability_digest:
828
+ raise RuntimeError(
829
+ "capability_digest_mismatch: hosted adapter capabilities do not "
830
+ "match the control-plane deployment digest"
831
+ )
832
+
833
+ def capabilities() -> RuntimeCapabilityMatrix: # type: ignore[misc]
834
+ return capability_snapshot
835
+
836
+ elif capabilities is None:
837
+ probe = adapter_provider()
838
+
839
+ def capabilities() -> RuntimeCapabilityMatrix: # type: ignore[misc]
840
+ try:
841
+ return probe.capabilities()
842
+ except Exception:
843
+ return default_capability_matrix()
844
+
845
+ kernel = AgentKernel(
846
+ store,
847
+ session_events,
848
+ verifier,
849
+ queue_limit=config.queue_limit,
850
+ capabilities=capabilities,
851
+ clock=config.clock,
852
+ )
853
+ worker = AgentKernelWorker(
854
+ store,
855
+ adapter_factory=adapter_provider,
856
+ session_events=session_events,
857
+ start_request_defaults=config.start_request_defaults,
858
+ )
859
+ recovery = RecoveryCoordinator(
860
+ store,
861
+ session_events,
862
+ capabilities,
863
+ executor=config.runtime_executor,
864
+ launch_context=config.launch_context,
865
+ adapter_factory=adapter_provider,
866
+ # takeover 重建的 live execution 交还 worker(ActiveExecution 归
867
+ # 当前 activation 持有,Interaction 回包才能打到同一 client 实例)。
868
+ execution_sink=worker.adopt_execution,
869
+ )
870
+ heartbeat = LeaseHeartbeat(
871
+ store,
872
+ agent_instance_id=config.agent_instance_id,
873
+ activation_id=config.activation_id
874
+ or f"{config.agent_instance_id}:kernel-runtime",
875
+ runtime_type=config.runtime_type,
876
+ bundle_digest=config.bundle_digest,
877
+ # Lease metadata must represent the exact matrix this owner executes,
878
+ # not an unverified environment projection.
879
+ capability_digest=runtime_capability_matrix_digest(capabilities()),
880
+ lease_ttl_seconds=config.lease_ttl_seconds,
881
+ )
882
+ runtime = AgentKernelRuntime(
883
+ config=config,
884
+ kernel=kernel,
885
+ worker=worker,
886
+ recovery=recovery,
887
+ lease_heartbeat=heartbeat,
888
+ readiness=AgentKernelReadiness(runtime=None), # type: ignore[arg-type]
889
+ kernel_store=store,
890
+ session_events=session_events,
891
+ _owns_pool=owns_pool,
892
+ _pool=pool,
893
+ )
894
+ runtime.readiness.runtime = runtime
895
+ return runtime
896
+
897
+
898
+ def _no_adapter_provider() -> RuntimeAdapter: # pragma: no cover - defensive
899
+ raise RuntimeError("agent kernel runtime has no RuntimeAdapter provider")
900
+
901
+
902
+ async def bootstrap_agent_kernel_runtime_from_env(
903
+ *,
904
+ adapter_provider: Callable[[], RuntimeAdapter] | None = None,
905
+ runtime_executor: Any | None = None,
906
+ launch_context: Any | None = None,
907
+ start_request_defaults: dict[str, Any] | None = None,
908
+ session_service: Any | None = None,
909
+ ) -> AgentKernelRuntime | None:
910
+ """Operator env 投影 -> 生产 runtime(AGENT_KERNEL_ENABLED=1 时)。
911
+
912
+ hosted 部署(AGENT_KERNEL_STORE_DRIVER=postgres + JWKS URL)装配并启动
913
+ worker/lease/recovery,同时注册 kernel ingress 与 runtime health。
914
+ """
915
+
916
+ from ksadk.kernel.ingress import (
917
+ ENV_JWKS_URL,
918
+ _remote_jwks_source,
919
+ authority_mode,
920
+ set_agent_kernel,
921
+ )
922
+
923
+ enabled = os.environ.get("AGENT_KERNEL_ENABLED", "").strip().lower() in {
924
+ "1",
925
+ "true",
926
+ "yes",
927
+ "on",
928
+ }
929
+ if not enabled:
930
+ return None
931
+ if get_agent_kernel_runtime() is not None:
932
+ return get_agent_kernel_runtime()
933
+
934
+ driver = os.environ.get("AGENT_KERNEL_STORE_DRIVER", "memory").strip().lower()
935
+ dsn = os.environ.get("AGENT_KERNEL_STORE_DSN", "").strip()
936
+ jwks_url = os.environ.get(ENV_JWKS_URL, "").strip()
937
+ session_namespace = (
938
+ os.environ.get("KSADK_SESSION_NAMESPACE", "default").strip() or "default"
939
+ )
940
+ tenant_id = (
941
+ os.environ.get("KSADK_TENANT_ID")
942
+ or os.environ.get("AGENTENGINE_TENANT_ID")
943
+ or "default"
944
+ ).strip()
945
+ workspace_id = (
946
+ os.environ.get("KSADK_WORKSPACE_ID")
947
+ or os.environ.get("AGENTENGINE_WORKSPACE_ID")
948
+ or "default"
949
+ ).strip()
950
+ mode: AuthorityMode = authority_mode() # type: ignore[assignment]
951
+ durability_tier: DurabilityTier = os.environ.get(
952
+ "AGENT_KERNEL_DURABILITY_TIER", "durable"
953
+ ).strip().lower() # type: ignore[assignment]
954
+ injected_contract_digest = os.environ.get(
955
+ "AGENT_KERNEL_CONTRACT_DIGEST", ""
956
+ ).strip()
957
+ if (
958
+ mode == "hosted"
959
+ and injected_contract_digest != AGENT_KERNEL_V1_AGGREGATE_DIGEST
960
+ ):
961
+ raise RuntimeError(
962
+ "contract_digest_mismatch: hosted agent kernel runtime image "
963
+ "does not support the control-plane contract digest"
964
+ )
965
+
966
+ pool = None
967
+ owns_pool = False
968
+ if driver == "postgres":
969
+ from ksadk.kernel.postgres_store import (
970
+ PostgresAgentKernelStore,
971
+ PostgresFencedSessionEventStore,
972
+ PostgresKernelEventLog,
973
+ PostgresNonceStore,
974
+ )
975
+ from ksadk.sessions.postgres_service import PostgresSessionService
976
+
977
+ if not dsn:
978
+ raise RuntimeError("postgres kernel store requires AGENT_KERNEL_STORE_DSN")
979
+ session_service = PostgresSessionService(
980
+ dsn=dsn,
981
+ namespace=session_namespace,
982
+ tenant_id=tenant_id,
983
+ workspace_id=workspace_id,
984
+ )
985
+ await session_service._ensure_pool()
986
+ pool = session_service._pool
987
+ event_log = PostgresKernelEventLog(
988
+ pool,
989
+ namespace=session_service.namespace,
990
+ tenant_id=session_service.tenant_id,
991
+ workspace_id=session_service.workspace_id,
992
+ )
993
+ store: AgentKernelStore = PostgresAgentKernelStore(
994
+ pool, event_log, owns_pool=True
995
+ )
996
+ await store.ensure_schema()
997
+ session_events: Any = PostgresFencedSessionEventStore(store)
998
+ nonce_store: Any = PostgresNonceStore(pool)
999
+ owns_pool = True
1000
+ else:
1001
+ from ksadk.kernel.memory_store import InMemoryAgentKernelStore
1002
+ from ksadk.sessions.in_memory import InMemorySessionService
1003
+
1004
+ # The HTTP Session routes and the Kernel worker must project through
1005
+ # one physical Session log. The Runtime App passes its owned service
1006
+ # here so a foreground kernel stream remains replayable through
1007
+ # ListSessionEvents/ListSessionMessages after refresh. Standalone
1008
+ # bootstrap callers retain the historical in-memory default.
1009
+ if session_service is None:
1010
+ session_service = InMemorySessionService()
1011
+ session_events = SessionServiceEventStore(session_service)
1012
+ store = InMemoryAgentKernelStore(session_events)
1013
+ # Explicit ephemeral hosted mode still needs replay protection while
1014
+ # this process lives. It does not promise restart durability.
1015
+ nonce_store = InMemoryNonceStore()
1016
+
1017
+ agent_instance_id = os.environ.get("AGENT_INSTANCE_ID", "").strip()
1018
+ if not agent_instance_id and mode != "hosted":
1019
+ agent_instance_id = "local-agent"
1020
+ pod_uid = os.environ.get("POD_UID", "").strip()
1021
+ config = AgentKernelRuntimeConfig(
1022
+ agent_instance_id=agent_instance_id,
1023
+ authority_mode=mode,
1024
+ driver=driver,
1025
+ durability_tier=durability_tier,
1026
+ dsn=dsn,
1027
+ jwks=_remote_jwks_source(jwks_url) if mode == "hosted" else None,
1028
+ permit_issuer=os.environ.get("AGENT_CONTROL_PERMIT_ISSUER", ""),
1029
+ nonce_store=nonce_store,
1030
+ adapter_provider=adapter_provider,
1031
+ start_request_defaults=dict(start_request_defaults or {}),
1032
+ contract_digest=(
1033
+ AGENT_KERNEL_V1_AGGREGATE_DIGEST
1034
+ if mode == "hosted"
1035
+ else injected_contract_digest
1036
+ ),
1037
+ capability_digest=os.environ.get("AGENT_KERNEL_CAPABILITY_DIGEST", ""),
1038
+ bundle_digest=os.environ.get("AGENT_BUNDLE_DIGEST", ""),
1039
+ session_namespace=session_namespace,
1040
+ tenant_id=tenant_id,
1041
+ workspace_id=workspace_id,
1042
+ store=store,
1043
+ session_events=session_events,
1044
+ session_service=session_service,
1045
+ runtime_executor=runtime_executor,
1046
+ launch_context=launch_context,
1047
+ pool=pool,
1048
+ owns_pool=owns_pool,
1049
+ # Operator 通过 downward API 注入 POD_UID。与 stable instance id
1050
+ # 组合才是 activation owner;hosted 少了它必须拒绝启动,不能退回到
1051
+ # 所有副本共享的固定字符串。
1052
+ activation_id=f"{agent_instance_id}:{pod_uid}" if pod_uid else None,
1053
+ lease_ttl_seconds=float(
1054
+ os.environ.get("AGENT_KERNEL_LEASE_TTL_SECONDS", "60") or "60"
1055
+ ),
1056
+ )
1057
+ runtime = build_agent_kernel_runtime(config)
1058
+ await runtime.start()
1059
+ set_agent_kernel(runtime.kernel)
1060
+ set_agent_kernel_runtime(runtime)
1061
+ return runtime
1062
+
1063
+
1064
+ __all__ = [
1065
+ "AgentKernelRuntime",
1066
+ "AgentKernelRuntimeConfig",
1067
+ "AgentKernelReadiness",
1068
+ "LeaseHeartbeat",
1069
+ "build_agent_kernel_runtime",
1070
+ "bootstrap_agent_kernel_runtime_from_env",
1071
+ "set_agent_kernel_runtime",
1072
+ "get_agent_kernel_runtime",
1073
+ "clear_agent_kernel_runtime",
1074
+ ]