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.
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
- agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
- ksadk/a2a/__init__.py +2 -0
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/bootstrap.py +0 -2
- ksadk/a2a/card.py +6 -111
- ksadk/a2a/control_plane.py +239 -1
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +195 -172
- ksadk/a2a/routes.py +12 -5
- ksadk/a2a/server.py +1 -8
- ksadk/a2a/service_env.py +33 -0
- ksadk/a2a/space_client.py +43 -349
- ksadk/a2a_card.py +101 -0
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +474 -275
- ksadk/agui/config.py +11 -5
- ksadk/agui/routes.py +7 -15
- ksadk/api/client.py +530 -40
- ksadk/api/environment.py +27 -0
- ksadk/builders/code_builder.py +352 -53
- ksadk/builders/container_builder.py +71 -44
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +2 -2
- ksadk/builders/managed_runtime_builder.py +60 -28
- ksadk/cli/__init__.py +25 -0
- ksadk/cli/cmd_a2a.py +18 -31
- ksadk/cli/cmd_create.py +13 -26
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +28 -3
- ksadk/cli/cmd_eval.py +443 -0
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +137 -163
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_launch.py +12 -0
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +85 -479
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_run.py +29 -67
- ksadk/cli/cmd_studio.py +201 -0
- ksadk/cli/cmd_web.py +15 -6
- ksadk/cli/dry_run.py +2 -1
- ksadk/cli/env_options.py +83 -0
- ksadk/cli/global_options.py +3 -1
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/network_options.py +7 -5
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/cli/runtime_bootstrap.py +51 -0
- ksadk/cli/ui.py +6 -0
- ksadk/cli/workflow_common.py +5 -5
- ksadk/codex/__init__.py +3 -3
- ksadk/codex/client.py +833 -49
- ksadk/codex/phase.py +1 -1
- ksadk/codex/runtime.py +720 -127
- ksadk/common/kop_client.py +232 -0
- ksadk/configs/__init__.py +1 -3
- ksadk/configs/env_registry.py +244 -45
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +6 -1
- ksadk/configs/settings.py +5 -108
- ksadk/context_engine/__init__.py +98 -0
- ksadk/context_engine/assembler.py +176 -0
- ksadk/context_engine/baseline.py +419 -0
- ksadk/context_engine/cache_observability.py +228 -0
- ksadk/context_engine/capabilities.py +443 -0
- ksadk/context_engine/contributors.py +344 -0
- ksadk/context_engine/hosted_pipeline.py +417 -0
- ksadk/context_engine/models.py +117 -0
- ksadk/context_engine/planner.py +698 -0
- ksadk/context_engine/policies.py +319 -0
- ksadk/context_engine/projection.py +31 -0
- ksadk/context_engine/shadow_plan.py +266 -0
- ksadk/context_engine/tokenizer.py +164 -0
- ksadk/conversations/__init__.py +85 -94
- ksadk/conversations/context.py +126 -6
- ksadk/conversations/message_projection.py +292 -28
- ksadk/conversations/model_context.py +49 -3
- ksadk/conversations/runtime_compaction.py +207 -5
- ksadk/conversations/runtime_input.py +193 -3
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +157 -4
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +280 -2
- ksadk/conversations/runtime_resume.py +71 -32
- ksadk/conversations/runtime_stream_events.py +94 -9
- ksadk/conversations/runtime_streaming.py +175 -22
- ksadk/conversations/semantic_summary.py +311 -1
- ksadk/conversations/session_lock.py +51 -0
- ksadk/deployment/env_forward.py +72 -0
- ksadk/deployment/managed_runtime.py +38 -0
- ksadk/deployment/manager.py +24 -8
- ksadk/deployment/providers/serverless.py +228 -92
- ksadk/evaluation/__init__.py +116 -0
- ksadk/evaluation/a2a_adapter.py +452 -0
- ksadk/evaluation/adapters.py +67 -0
- ksadk/evaluation/agent_eval_client.py +395 -0
- ksadk/evaluation/cloud_binding.py +80 -0
- ksadk/evaluation/cloud_converter.py +194 -0
- ksadk/evaluation/cloud_service.py +199 -0
- ksadk/evaluation/contracts.py +478 -0
- ksadk/evaluation/evalset.py +351 -0
- ksadk/evaluation/evaluators.py +627 -0
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +143 -0
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/storage.py +68 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +82 -0
- ksadk/events/__init__.py +13 -13
- ksadk/events/_v1_compat/__init__.py +1 -0
- ksadk/events/_v1_compat/models.py +299 -0
- ksadk/events/_v1_compat/parser.py +247 -0
- ksadk/events/_v1_compat/projection.py +753 -0
- ksadk/events/adapters/__init__.py +34 -0
- ksadk/events/adapters/_a2a_snapshot.py +622 -0
- ksadk/events/adapters/_a2a_support.py +275 -0
- ksadk/events/adapters/_codex_interactions.py +375 -0
- ksadk/events/adapters/_codex_items.py +801 -0
- ksadk/events/adapters/_codex_validators.py +311 -0
- ksadk/events/adapters/_langgraph_support.py +785 -0
- ksadk/events/adapters/a2a.py +841 -0
- ksadk/events/adapters/adk.py +503 -0
- ksadk/events/adapters/codex.py +717 -0
- ksadk/events/adapters/langgraph.py +745 -0
- ksadk/events/canonical.py +440 -0
- ksadk/events/canonical_replay.py +497 -0
- ksadk/events/canonical_store.py +468 -0
- ksadk/events/cold_recovery.py +257 -0
- ksadk/events/content.py +89 -0
- ksadk/events/identity.py +86 -0
- ksadk/events/pipeline.py +455 -0
- ksadk/events/projections.py +66 -0
- ksadk/events/reducer.py +661 -0
- ksadk/events/replay.py +3 -36
- ksadk/events/runtime_event.py +20 -256
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/__init__.py +3 -2
- ksadk/harness/app.py +114 -123
- ksadk/harness/config.py +1 -1
- ksadk/harness/runtime.py +467 -0
- ksadk/hermes_terminal.py +5 -0
- ksadk/identity/resolver.py +4 -3
- ksadk/interaction/__init__.py +28 -0
- ksadk/interaction/contracts.py +136 -0
- ksadk/interaction/ledger.py +189 -0
- ksadk/interaction/provider.py +122 -0
- ksadk/interaction/providers/__init__.py +52 -0
- ksadk/interaction/providers/adk.py +40 -0
- ksadk/interaction/providers/codex.py +94 -0
- ksadk/interaction/providers/langgraph.py +58 -0
- ksadk/kernel/__init__.py +117 -0
- ksadk/kernel/authorization.py +258 -0
- ksadk/kernel/bootstrap.py +1074 -0
- ksadk/kernel/contract_fingerprints.py +61 -0
- ksadk/kernel/contracts.py +362 -0
- ksadk/kernel/control.py +219 -0
- ksadk/kernel/errors.py +79 -0
- ksadk/kernel/ingress.py +1102 -0
- ksadk/kernel/mapping.py +111 -0
- ksadk/kernel/memory_store.py +1133 -0
- ksadk/kernel/postgres_store.py +1756 -0
- ksadk/kernel/recovery.py +452 -0
- ksadk/kernel/runtime_identity.py +103 -0
- ksadk/kernel/sql/001_agent_kernel.sql +120 -0
- ksadk/kernel/sqlite_store.py +1410 -0
- ksadk/kernel/state.py +145 -0
- ksadk/kernel/store.py +232 -0
- ksadk/kernel/worker.py +953 -0
- ksadk/knowledge_base/client.py +22 -4
- ksadk/knowledge_base/service.py +32 -5
- ksadk/managed_a2a_card.py +160 -0
- ksadk/managed_runtime.py +16 -9
- ksadk/memory/__init__.py +61 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
- ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
- ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- ksadk/memory/adk/long_term_memory.py +3 -2
- ksadk/memory/coordinator.py +461 -0
- ksadk/memory/events.py +202 -0
- ksadk/memory/extraction.py +228 -0
- ksadk/memory/ltm_backend_factory.py +16 -3
- ksadk/memory/models.py +316 -0
- ksadk/memory/policy.py +215 -0
- ksadk/memory/provider.py +47 -0
- ksadk/memory/provider_adapter.py +159 -0
- ksadk/memory/provider_resolver.py +71 -0
- ksadk/memory/providers/__init__.py +5 -0
- ksadk/memory/providers/local_sqlite.py +490 -0
- ksadk/memory/resolved_policy.py +145 -0
- ksadk/memory/service.py +198 -8
- ksadk/memory/tool.py +4 -0
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/config.py +22 -1
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +67 -15
- ksadk/model_proxy/server.py +168 -6
- ksadk/model_proxy/transform.py +105 -3
- ksadk/observability/__init__.py +25 -0
- ksadk/observability/session_log.py +420 -0
- ksadk/observability/trajectory.py +105 -0
- ksadk/prompts/__init__.py +71 -0
- ksadk/prompts/compiler.py +215 -0
- ksadk/prompts/models.py +82 -0
- ksadk/prompts/projection.py +89 -0
- ksadk/prompts/resolved.py +137 -0
- ksadk/prompts/sources.py +233 -0
- ksadk/runners/_langgraph_runner_streams.py +822 -0
- ksadk/runners/adk_runner.py +14 -31
- ksadk/runners/base_runner.py +44 -3
- ksadk/runners/factory.py +3 -3
- ksadk/runners/langgraph_runner.py +252 -433
- ksadk/runners/utils/__init__.py +0 -10
- ksadk/runners/utils/loader.py +16 -0
- ksadk/runtime/__init__.py +67 -41
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +199 -16
- ksadk/runtime/conversation_execution.py +687 -0
- ksadk/runtime/executor.py +392 -0
- ksadk/runtime/factory.py +342 -0
- ksadk/runtime/framework_adapters.py +6 -12
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +61 -0
- ksadk/runtime/preprocessing.py +49 -14
- ksadk/runtime/runner_adapter.py +330 -391
- ksadk/runtime/runner_loading.py +36 -0
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +36 -3
- ksadk/server/__init__.py +39 -4
- ksadk/server/app.py +18 -492
- ksadk/server/composition.py +36 -2
- ksadk/server/factory.py +210 -149
- ksadk/server/routes/common.py +15 -46
- ksadk/server/routes/control.py +13 -9
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/misc.py +10 -33
- ksadk/server/routes/models.py +14 -6
- ksadk/server/routes/openai_compat.py +129 -27
- ksadk/server/routes/projection.py +84 -16
- ksadk/server/routes/run.py +347 -460
- ksadk/server/routes/sessions.py +37 -40
- ksadk/server/routes/streaming.py +52 -7
- ksadk/server/routes/workspace.py +54 -21
- ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
- ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
- ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
- ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
- ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
- ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
- ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
- ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
- ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
- ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
- ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
- ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
- ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
- ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
- ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
- ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
- ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
- ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
- ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
- ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
- ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
- ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
- ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
- ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
- ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
- ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
- ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
- ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
- ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
- ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
- ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
- ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
- ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
- ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
- ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
- ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
- ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
- ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
- ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
- ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
- ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
- ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
- ksadk/server/static/index.html +2 -2
- ksadk/sessions/__init__.py +26 -9
- ksadk/sessions/_local_service_sync.py +688 -0
- ksadk/sessions/_local_tables.py +21 -0
- ksadk/sessions/_postgres_schema.py +305 -0
- ksadk/sessions/_postgres_tables.py +15 -0
- ksadk/sessions/base.py +80 -1
- ksadk/sessions/in_memory.py +53 -2
- ksadk/sessions/local_service.py +63 -604
- ksadk/sessions/postgres_service.py +58 -148
- ksadk/sessions/resilient.py +35 -1
- ksadk/skills/runtime/backends/e2b.py +1 -1
- ksadk/skills/service_client.py +13 -3
- ksadk/studio/__init__.py +5 -0
- ksadk/studio/agent_avatar_assets.py +120 -0
- ksadk/studio/agent_lifecycle.py +51 -0
- ksadk/studio/api.py +1598 -0
- ksadk/studio/api_catalog_routes.py +324 -0
- ksadk/studio/api_contracts.py +286 -0
- ksadk/studio/api_helpers.py +132 -0
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +749 -0
- ksadk/studio/authoring_coordinator.py +1017 -0
- ksadk/studio/builder.py +397 -0
- ksadk/studio/capabilities.py +368 -0
- ksadk/studio/cloud.py +2099 -0
- ksadk/studio/codex_agent_service.py +694 -0
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +437 -0
- ksadk/studio/codex_manifest.py +307 -0
- ksadk/studio/codex_run.py +395 -0
- ksadk/studio/compiler.py +190 -0
- ksadk/studio/contracts.py +755 -0
- ksadk/studio/errors.py +47 -0
- ksadk/studio/event_store.py +214 -0
- ksadk/studio/framework_run.py +342 -0
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/identifiers.py +33 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/mcp_runtime.py +229 -0
- ksadk/studio/model_client.py +629 -0
- ksadk/studio/model_profile_service.py +214 -0
- ksadk/studio/operations.py +275 -0
- ksadk/studio/otel_trace.py +1060 -0
- ksadk/studio/pagination.py +182 -0
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/python_tool_inspection.py +183 -0
- ksadk/studio/repository.py +327 -0
- ksadk/studio/resource_catalog.py +1260 -0
- ksadk/studio/run_service.py +1133 -0
- ksadk/studio/runtime_catalog.py +74 -0
- ksadk/studio/runtime_source.py +365 -0
- ksadk/studio/service.py +2249 -0
- ksadk/studio/shared_web.py +1004 -0
- ksadk/studio/skill_discovery.py +628 -0
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
- ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- ksadk/studio/static/index.html +15 -0
- ksadk/studio/templates.py +722 -0
- ksadk/studio/validator.py +265 -0
- ksadk/studio/workspace.py +135 -0
- ksadk/tools/gateway.py +9 -0
- ksadk/toolsets/__init__.py +27 -1
- ksadk/toolsets/a2a.py +426 -0
- ksadk/tracing/__init__.py +19 -21
- ksadk/tracing/setup.py +317 -403
- ksadk/tracing/span_utils.py +0 -44
- ksadk/version.py +1 -1
- ksadk_runtime_common/memory_backend/registry.py +56 -56
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk_runtime_common/workspace_files/router.py +8 -2
- agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
- ksadk/a2a/card_builder.py +0 -64
- ksadk/a2a/client.py +0 -309
- ksadk/events/parser.py +0 -191
- ksadk/harness/runner.py +0 -211
- ksadk/runners/codex_runner.py +0 -132
- ksadk/runners/utils/langfuse.py +0 -230
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
- ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
- ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
- ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
- ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
- ksadk/server/static/assets/katex-DATssjp1.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
- ksadk/tracing/exporters/langfuse_exporter.py +0 -460
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
]
|