agentengine-sdk-python 0.8.1__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.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +179 -63
- ksadk/a2a/space_client.py +6 -333
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +443 -259
- ksadk/api/client.py +525 -27
- ksadk/builders/code_builder.py +284 -8
- ksadk/builders/container_builder.py +8 -1
- ksadk/builders/managed_runtime_builder.py +39 -35
- ksadk/cli/__init__.py +17 -0
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +10 -2
- ksadk/cli/cmd_eval.py +66 -9
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +1 -126
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +1 -435
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_studio.py +99 -8
- ksadk/cli/cmd_web.py +10 -2
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/codex/client.py +180 -30
- ksadk/codex/runtime.py +428 -326
- ksadk/configs/env_registry.py +66 -12
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +5 -0
- 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/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 +184 -2
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +141 -0
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +272 -2
- ksadk/conversations/runtime_resume.py +20 -1
- ksadk/conversations/runtime_stream_events.py +94 -9
- 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 +5 -0
- ksadk/deployment/providers/serverless.py +143 -69
- ksadk/evaluation/__init__.py +32 -0
- ksadk/evaluation/a2a_adapter.py +9 -1
- ksadk/evaluation/adapters.py +18 -5
- 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 +73 -4
- ksadk/evaluation/evaluators.py +183 -17
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +31 -6
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +15 -6
- 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 -265
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/runtime.py +160 -57
- 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 +7 -0
- ksadk/knowledge_base/service.py +32 -5
- 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 +384 -2
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- 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 +188 -6
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +39 -15
- ksadk/model_proxy/server.py +24 -5
- ksadk/model_proxy/transform.py +60 -2
- 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 +5 -5
- ksadk/runners/base_runner.py +16 -0
- ksadk/runners/factory.py +1 -2
- ksadk/runners/langgraph_runner.py +198 -437
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +118 -2
- ksadk/runtime/conversation_execution.py +316 -182
- ksadk/runtime/executor.py +144 -3
- ksadk/runtime/factory.py +193 -7
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +14 -1
- ksadk/runtime/preprocessing.py +25 -3
- ksadk/runtime/runner_adapter.py +288 -508
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +35 -2
- ksadk/server/composition.py +8 -0
- ksadk/server/factory.py +69 -6
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/openai_compat.py +88 -8
- ksadk/server/routes/projection.py +35 -20
- ksadk/server/routes/run.py +94 -0
- ksadk/server/routes/sessions.py +2 -0
- ksadk/server/routes/streaming.py +5 -2
- ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.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-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.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-ChNs_Mdk.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-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-CmZHzlwZ.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-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-hdBcYzM8.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-Bjy_2v4_.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-BmkRbwCg.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-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.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-DEz24vV0.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/studio/api.py +577 -25
- ksadk/studio/api_contracts.py +114 -5
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +252 -27
- ksadk/studio/authoring_coordinator.py +799 -67
- ksadk/studio/builder.py +88 -11
- ksadk/studio/capabilities.py +17 -0
- ksadk/studio/cloud.py +1916 -95
- ksadk/studio/codex_agent_service.py +125 -14
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +138 -25
- ksadk/studio/codex_manifest.py +51 -22
- ksadk/studio/codex_run.py +46 -3
- ksadk/studio/compiler.py +1 -0
- ksadk/studio/contracts.py +114 -3
- ksadk/studio/event_store.py +25 -95
- ksadk/studio/framework_run.py +231 -3
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/model_client.py +95 -9
- ksadk/studio/model_profile_service.py +1 -1
- ksadk/studio/operations.py +37 -5
- ksadk/studio/otel_trace.py +348 -42
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/repository.py +23 -2
- ksadk/studio/resource_catalog.py +43 -0
- ksadk/studio/run_service.py +563 -137
- ksadk/studio/runtime_source.py +42 -16
- ksadk/studio/service.py +1180 -72
- ksadk/studio/shared_web.py +32 -22
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/templates.py +100 -8
- ksadk/studio/validator.py +19 -0
- ksadk/studio/workspace.py +30 -32
- ksadk/tracing/setup.py +304 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk/events/parser.py +0 -191
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
- ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
- ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
- ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-R52pJJvg.js +0 -177
- ksadk/server/static/assets/index-jJr3_955.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
- ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
- ksadk/studio/evaluation.py +0 -192
- ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
- ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""本地 SQLite Memory Provider(方案 §10 / §17.4 契约测试一致)。
|
|
2
|
+
|
|
3
|
+
提供 scope 隔离、版本化更新(乐观锁)、TTL、hard/soft delete、content_hash 去重。本地默认
|
|
4
|
+
实现,云端用 HTTP/SDK Provider,二者共用同一套契约测试。
|
|
5
|
+
|
|
6
|
+
不实现语义检索(``semantic_search=False``),仅 keyword 检索 + metadata 过滤;语义检索留
|
|
7
|
+
HTTP/SDK Provider。Provider 异常返回结构化 ``MemorySearchResult(status="failed")``,不抛
|
|
8
|
+
异常文本进模型上下文(方案 §10.8)。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sqlite3
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from datetime import datetime, timedelta, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from ksadk.memory.models import (
|
|
24
|
+
CoreMemoryRequest,
|
|
25
|
+
MemoryCapabilities,
|
|
26
|
+
MemoryDeleteRequest,
|
|
27
|
+
MemoryDeleteResult,
|
|
28
|
+
MemoryRecord,
|
|
29
|
+
MemoryScope,
|
|
30
|
+
MemorySearchRequest,
|
|
31
|
+
MemorySearchResult,
|
|
32
|
+
)
|
|
33
|
+
from ksadk.memory.policy import content_hash
|
|
34
|
+
|
|
35
|
+
_ASCII_QUERY_TOKEN = re.compile(r"[a-z0-9][a-z0-9_.-]+", re.IGNORECASE)
|
|
36
|
+
_CJK_QUERY_RUN = re.compile(r"[\u3400-\u4dbf\u4e00-\u9fff]+")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _keyword_query_terms(query: str) -> tuple[list[str], list[str]]:
|
|
40
|
+
"""Tokenize local keyword queries without assuming whitespace-delimited CJK.
|
|
41
|
+
|
|
42
|
+
ASCII terms retain AND semantics. CJK runs become a bounded bigram OR
|
|
43
|
+
group, allowing a natural question to match a shorter stored fact. This is
|
|
44
|
+
a lightweight SQLite fallback, not semantic search.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
normalized = str(query or "").lower()
|
|
48
|
+
ascii_terms = list(dict.fromkeys(_ASCII_QUERY_TOKEN.findall(normalized)))[:16]
|
|
49
|
+
cjk_terms: list[str] = []
|
|
50
|
+
for run in _CJK_QUERY_RUN.findall(normalized):
|
|
51
|
+
if len(run) < 2:
|
|
52
|
+
continue
|
|
53
|
+
cjk_terms.extend(run[index : index + 2] for index in range(len(run) - 1))
|
|
54
|
+
return ascii_terms, list(dict.fromkeys(cjk_terms))[:48]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _now_iso() -> str:
|
|
58
|
+
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _row_to_record(row: sqlite3.Row) -> MemoryRecord:
|
|
62
|
+
return MemoryRecord(
|
|
63
|
+
memory_id=row["memory_id"],
|
|
64
|
+
tenant_id=row["tenant_id"],
|
|
65
|
+
workspace_id=row["workspace_id"],
|
|
66
|
+
scope=row["scope"],
|
|
67
|
+
scope_id=row["scope_id"],
|
|
68
|
+
memory_type=row["memory_type"],
|
|
69
|
+
content=row["content"],
|
|
70
|
+
summary=row["summary"],
|
|
71
|
+
status=row["status"],
|
|
72
|
+
confidence=float(row["confidence"]),
|
|
73
|
+
importance=float(row["importance"]),
|
|
74
|
+
valid_from=row["valid_from"] or "",
|
|
75
|
+
valid_to=row["valid_to"] or "",
|
|
76
|
+
expires_at=row["expires_at"] or "",
|
|
77
|
+
source_session_id=row["source_session_id"] or "",
|
|
78
|
+
source_event_ids=json.loads(row["source_event_ids"] or "[]"),
|
|
79
|
+
source_seq_range=tuple(json.loads(row["source_seq_range"] or "null") or ()), # type: ignore[arg-type]
|
|
80
|
+
content_hash=row["content_hash"],
|
|
81
|
+
version=int(row["version"]),
|
|
82
|
+
metadata=json.loads(row["metadata"] or "{}"),
|
|
83
|
+
created_at=row["created_at"] or "",
|
|
84
|
+
updated_at=row["updated_at"] or "",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_SCHEMA = """
|
|
89
|
+
CREATE TABLE IF NOT EXISTS memory_records (
|
|
90
|
+
memory_id TEXT PRIMARY KEY,
|
|
91
|
+
tenant_id TEXT NOT NULL,
|
|
92
|
+
workspace_id TEXT NOT NULL,
|
|
93
|
+
scope TEXT NOT NULL,
|
|
94
|
+
scope_id TEXT NOT NULL,
|
|
95
|
+
memory_type TEXT NOT NULL,
|
|
96
|
+
content TEXT NOT NULL,
|
|
97
|
+
summary TEXT NOT NULL,
|
|
98
|
+
status TEXT NOT NULL,
|
|
99
|
+
confidence REAL NOT NULL,
|
|
100
|
+
importance REAL NOT NULL,
|
|
101
|
+
valid_from TEXT NOT NULL DEFAULT '',
|
|
102
|
+
valid_to TEXT NOT NULL DEFAULT '',
|
|
103
|
+
expires_at TEXT NOT NULL DEFAULT '',
|
|
104
|
+
source_session_id TEXT NOT NULL DEFAULT '',
|
|
105
|
+
source_event_ids TEXT NOT NULL DEFAULT '[]',
|
|
106
|
+
source_seq_range TEXT NOT NULL DEFAULT '',
|
|
107
|
+
content_hash TEXT NOT NULL,
|
|
108
|
+
version INTEGER NOT NULL,
|
|
109
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
110
|
+
created_at TEXT NOT NULL DEFAULT '',
|
|
111
|
+
updated_at TEXT NOT NULL DEFAULT ''
|
|
112
|
+
);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_scope ON memory_records(tenant_id, workspace_id,
|
|
114
|
+
scope, scope_id, status);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_content_hash ON memory_records(content_hash);
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class SqliteMemoryProvider:
|
|
120
|
+
"""本地 SQLite 长期记忆 Provider。
|
|
121
|
+
|
|
122
|
+
线程安全:单连接 + per-thread lock。``last_error`` 供 Coordinator 区分"吞错返空"与
|
|
123
|
+
"真无记忆"(对齐 ``LongTermMemoryService.last_error`` 语义)。
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
capabilities_def = MemoryCapabilities(
|
|
127
|
+
semantic_search=False,
|
|
128
|
+
keyword_search=True,
|
|
129
|
+
metadata_filter=True,
|
|
130
|
+
versioned_update=True,
|
|
131
|
+
hard_delete=True,
|
|
132
|
+
ttl=True,
|
|
133
|
+
max_record_chars=8192,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
*,
|
|
139
|
+
db_path: str | Path = ":memory:",
|
|
140
|
+
tenant_id: str = "local",
|
|
141
|
+
workspace_id: str = "local",
|
|
142
|
+
) -> None:
|
|
143
|
+
self._db_path = str(db_path)
|
|
144
|
+
self._tenant_id = tenant_id
|
|
145
|
+
self._workspace_id = workspace_id
|
|
146
|
+
self._lock = threading.Lock()
|
|
147
|
+
self._conn = sqlite3.connect(self._db_path, check_same_thread=False)
|
|
148
|
+
self._conn.row_factory = sqlite3.Row
|
|
149
|
+
self._conn.executescript(_SCHEMA)
|
|
150
|
+
self._conn.commit()
|
|
151
|
+
self.last_error: str = ""
|
|
152
|
+
# 每次 Provider 启动执行一次有界清理;不在每次 recall/upsert 热路径扫描全表。
|
|
153
|
+
self.cleanup(
|
|
154
|
+
max_records=_positive_env_int("KSADK_MEMORY_MAX_RECORDS", 10000),
|
|
155
|
+
expire_days=_positive_env_int("KSADK_MEMORY_RETENTION_DAYS", 90),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# ---- MemoryProvider Protocol ----
|
|
159
|
+
|
|
160
|
+
def capabilities(self) -> MemoryCapabilities:
|
|
161
|
+
return self.capabilities_def
|
|
162
|
+
|
|
163
|
+
def search(self, request: MemorySearchRequest) -> MemorySearchResult:
|
|
164
|
+
start = time.monotonic()
|
|
165
|
+
try:
|
|
166
|
+
with self._lock:
|
|
167
|
+
rows = self._query(request)
|
|
168
|
+
except Exception as exc: # noqa: BLE001
|
|
169
|
+
self.last_error = str(exc)
|
|
170
|
+
return MemorySearchResult(
|
|
171
|
+
status="failed",
|
|
172
|
+
records=[],
|
|
173
|
+
error_code="provider_error",
|
|
174
|
+
provider="sqlite",
|
|
175
|
+
latency_ms=int((time.monotonic() - start) * 1000),
|
|
176
|
+
)
|
|
177
|
+
self.last_error = ""
|
|
178
|
+
# 过滤 active + 未过期 + scope 隔离已在 SQL 完成;做 content_hash 去重 + max_tokens 装箱。
|
|
179
|
+
records = [_row_to_record(r) for r in rows]
|
|
180
|
+
records = _dedupe_active_versions(records)
|
|
181
|
+
now = _now_iso()
|
|
182
|
+
records = [r for r in records if r.is_active_now(now_iso=now)]
|
|
183
|
+
records = _box_by_tokens(records, request.max_tokens)
|
|
184
|
+
return MemorySearchResult(
|
|
185
|
+
status="ok",
|
|
186
|
+
records=records[: request.top_k] if request.top_k else records,
|
|
187
|
+
error_code=None,
|
|
188
|
+
provider="sqlite",
|
|
189
|
+
latency_ms=int((time.monotonic() - start) * 1000),
|
|
190
|
+
accounting_accuracy="estimated",
|
|
191
|
+
truncated_by_budget=len(records) >= request.top_k,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def get(self, memory_id: str) -> MemoryRecord | None:
|
|
195
|
+
with self._lock:
|
|
196
|
+
row = self._conn.execute(
|
|
197
|
+
"SELECT * FROM memory_records WHERE memory_id = ?", (memory_id,)
|
|
198
|
+
).fetchone()
|
|
199
|
+
return _row_to_record(row) if row is not None else None
|
|
200
|
+
|
|
201
|
+
def upsert(self, record: MemoryRecord, *, expected_version: int | None) -> MemoryRecord:
|
|
202
|
+
with self._lock:
|
|
203
|
+
existing = self._conn.execute(
|
|
204
|
+
"SELECT version, status FROM memory_records WHERE memory_id = ?",
|
|
205
|
+
(record.memory_id,),
|
|
206
|
+
).fetchone()
|
|
207
|
+
now = _now_iso()
|
|
208
|
+
if existing is not None:
|
|
209
|
+
if expected_version is not None and int(existing["version"]) != int(
|
|
210
|
+
expected_version
|
|
211
|
+
):
|
|
212
|
+
self.last_error = (
|
|
213
|
+
f"version_conflict:expected={expected_version},actual={existing['version']}"
|
|
214
|
+
)
|
|
215
|
+
raise VersionConflict(self.last_error)
|
|
216
|
+
version = int(existing["version"]) + 1
|
|
217
|
+
created = record.created_at or now
|
|
218
|
+
else:
|
|
219
|
+
version = record.version or 1
|
|
220
|
+
created = record.created_at or now
|
|
221
|
+
self._conn.execute(
|
|
222
|
+
"""INSERT INTO memory_records (
|
|
223
|
+
memory_id, tenant_id, workspace_id, scope, scope_id, memory_type,
|
|
224
|
+
content, summary, status, confidence, importance, valid_from, valid_to,
|
|
225
|
+
expires_at, source_session_id, source_event_ids, source_seq_range,
|
|
226
|
+
content_hash, version, metadata, created_at, updated_at
|
|
227
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
228
|
+
ON CONFLICT(memory_id) DO UPDATE SET
|
|
229
|
+
content=excluded.content, summary=excluded.summary, status=excluded.status,
|
|
230
|
+
confidence=excluded.confidence, importance=excluded.importance,
|
|
231
|
+
valid_from=excluded.valid_from, valid_to=excluded.valid_to,
|
|
232
|
+
expires_at=excluded.expires_at, source_event_ids=excluded.source_event_ids,
|
|
233
|
+
source_seq_range=excluded.source_seq_range, content_hash=excluded.content_hash,
|
|
234
|
+
version=excluded.version, metadata=excluded.metadata,
|
|
235
|
+
updated_at=excluded.updated_at
|
|
236
|
+
""",
|
|
237
|
+
(
|
|
238
|
+
record.memory_id,
|
|
239
|
+
record.tenant_id or self._tenant_id,
|
|
240
|
+
record.workspace_id or self._workspace_id,
|
|
241
|
+
record.scope,
|
|
242
|
+
record.scope_id,
|
|
243
|
+
record.memory_type,
|
|
244
|
+
record.content,
|
|
245
|
+
record.summary,
|
|
246
|
+
record.status,
|
|
247
|
+
record.confidence,
|
|
248
|
+
record.importance,
|
|
249
|
+
record.valid_from,
|
|
250
|
+
record.valid_to,
|
|
251
|
+
record.expires_at,
|
|
252
|
+
record.source_session_id,
|
|
253
|
+
json.dumps(record.source_event_ids, ensure_ascii=False),
|
|
254
|
+
json.dumps(list(record.source_seq_range) if record.source_seq_range else []),
|
|
255
|
+
record.content_hash or content_hash(record.content),
|
|
256
|
+
version,
|
|
257
|
+
json.dumps(record.metadata, ensure_ascii=False),
|
|
258
|
+
created,
|
|
259
|
+
now,
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
self._conn.commit()
|
|
263
|
+
return MemoryRecord(
|
|
264
|
+
**{
|
|
265
|
+
**record.__dict__,
|
|
266
|
+
"version": version,
|
|
267
|
+
"created_at": record.created_at or created,
|
|
268
|
+
"updated_at": now,
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def delete(self, request: MemoryDeleteRequest) -> MemoryDeleteResult:
|
|
273
|
+
with self._lock:
|
|
274
|
+
row = self._conn.execute(
|
|
275
|
+
"SELECT version FROM memory_records "
|
|
276
|
+
"WHERE memory_id = ? AND scope = ? AND scope_id = ?",
|
|
277
|
+
(request.memory_id, request.scope, request.scope_id),
|
|
278
|
+
).fetchone()
|
|
279
|
+
if row is None:
|
|
280
|
+
return MemoryDeleteResult(status="ok", deleted=False, error_code="not_found")
|
|
281
|
+
if request.hard:
|
|
282
|
+
self._conn.execute(
|
|
283
|
+
"DELETE FROM memory_records WHERE memory_id = ? AND scope = ? AND scope_id = ?",
|
|
284
|
+
(request.memory_id, request.scope, request.scope_id),
|
|
285
|
+
)
|
|
286
|
+
else:
|
|
287
|
+
self._conn.execute(
|
|
288
|
+
"UPDATE memory_records SET status='deleted', updated_at=? "
|
|
289
|
+
"WHERE memory_id=? AND scope=? AND scope_id=?",
|
|
290
|
+
(_now_iso(), request.memory_id, request.scope, request.scope_id),
|
|
291
|
+
)
|
|
292
|
+
self._conn.commit()
|
|
293
|
+
return MemoryDeleteResult(status="ok", deleted=True)
|
|
294
|
+
|
|
295
|
+
def list_core(self, request: CoreMemoryRequest) -> list[MemoryRecord]:
|
|
296
|
+
# Core memory:按 importance 降序取 active profile/fact,受 max_blocks/max_tokens 约束。
|
|
297
|
+
scopes = request.scopes
|
|
298
|
+
if not scopes:
|
|
299
|
+
return []
|
|
300
|
+
where, params = _scope_where(scopes)
|
|
301
|
+
with self._lock:
|
|
302
|
+
rows = self._conn.execute(
|
|
303
|
+
f"""SELECT * FROM memory_records
|
|
304
|
+
WHERE {where} AND status='active' AND memory_type IN ('profile','fact')
|
|
305
|
+
ORDER BY importance DESC, updated_at DESC LIMIT ?""",
|
|
306
|
+
(*params, request.max_blocks * 4),
|
|
307
|
+
).fetchall()
|
|
308
|
+
now = _now_iso()
|
|
309
|
+
records = [r for r in (_row_to_record(row) for row in rows) if r.is_active_now(now_iso=now)]
|
|
310
|
+
return _box_by_tokens(records, request.max_tokens)[: request.max_blocks]
|
|
311
|
+
|
|
312
|
+
# ---- internals ----
|
|
313
|
+
|
|
314
|
+
def _query(self, request: MemorySearchRequest) -> list[sqlite3.Row]:
|
|
315
|
+
where_parts: list[str] = []
|
|
316
|
+
params: list[Any] = []
|
|
317
|
+
if request.scopes:
|
|
318
|
+
w, p = _scope_where(request.scopes)
|
|
319
|
+
where_parts.append(w)
|
|
320
|
+
params.extend(p)
|
|
321
|
+
else:
|
|
322
|
+
where_parts.append("0") # 无 scope → 不返回(隔离)
|
|
323
|
+
where_parts.append("status='active'")
|
|
324
|
+
if request.memory_types:
|
|
325
|
+
placeholders = ",".join("?" for _ in request.memory_types)
|
|
326
|
+
where_parts.append(f"memory_type IN ({placeholders})")
|
|
327
|
+
params.extend(request.memory_types)
|
|
328
|
+
slot_key = str(request.filters.get("slot_key") or "").strip()
|
|
329
|
+
if slot_key:
|
|
330
|
+
# JSON 路径固定、值参数化;仅选择相同事实槽位,不做正文模糊猜测。
|
|
331
|
+
where_parts.append("json_extract(metadata, '$.slot_key') = ?")
|
|
332
|
+
params.append(slot_key)
|
|
333
|
+
# keyword 检索:ASCII 词项保持 AND;CJK 词项作为可选增强(不阻塞 ASCII 匹配)。
|
|
334
|
+
ascii_terms, cjk_terms = _keyword_query_terms(str(request.query or ""))
|
|
335
|
+
if ascii_terms:
|
|
336
|
+
# ASCII AND 匹配
|
|
337
|
+
for token in ascii_terms:
|
|
338
|
+
where_parts.append("(LOWER(content) LIKE ? OR LOWER(summary) LIKE ?)")
|
|
339
|
+
params.extend([f"%{token}%", f"%{token}%"])
|
|
340
|
+
# CJK 词项作为可选增强:如果有 ASCII 匹配,CJK 不匹配也不阻塞
|
|
341
|
+
# 只在 ASCII 为空时用 CJK 作为主匹配
|
|
342
|
+
elif cjk_terms:
|
|
343
|
+
# 只有 CJK 词 → 用 OR 匹配
|
|
344
|
+
cjk_like_parts = []
|
|
345
|
+
for token in cjk_terms:
|
|
346
|
+
cjk_like_parts.append("(content LIKE ? OR summary LIKE ?)")
|
|
347
|
+
params.extend([f"%{token}%", f"%{token}%"])
|
|
348
|
+
where_parts.append("(" + " OR ".join(cjk_like_parts) + ")")
|
|
349
|
+
sql = (
|
|
350
|
+
"SELECT * FROM memory_records WHERE "
|
|
351
|
+
+ " AND ".join(where_parts)
|
|
352
|
+
+ " ORDER BY importance DESC, updated_at DESC LIMIT ?"
|
|
353
|
+
)
|
|
354
|
+
params.append(max(request.top_k, 32))
|
|
355
|
+
return self._conn.execute(sql, tuple(params)).fetchall()
|
|
356
|
+
|
|
357
|
+
def cleanup(
|
|
358
|
+
self,
|
|
359
|
+
*,
|
|
360
|
+
max_records: int = 10000,
|
|
361
|
+
expire_days: int = 90,
|
|
362
|
+
) -> int:
|
|
363
|
+
"""清理过期/超量 Memory 记录(方案 §10.7 / §13.3)。
|
|
364
|
+
|
|
365
|
+
删除 expired 状态记录;如果总记录超过 max_records,删除最老的低 importance 记录。
|
|
366
|
+
返回删除的记录数。
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
deleted = 0
|
|
370
|
+
with self._lock:
|
|
371
|
+
# 删除显式过期和 TTL 已到期记录。
|
|
372
|
+
now = _now_iso()
|
|
373
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=max(0, expire_days))).strftime(
|
|
374
|
+
"%Y-%m-%dT%H:%M:%SZ"
|
|
375
|
+
)
|
|
376
|
+
cur = self._conn.execute(
|
|
377
|
+
"DELETE FROM memory_records WHERE status = 'expired' "
|
|
378
|
+
"OR (expires_at != '' AND expires_at <= ?)",
|
|
379
|
+
(now,),
|
|
380
|
+
)
|
|
381
|
+
deleted += cur.rowcount
|
|
382
|
+
# 删除超 90 天的低 importance 记录
|
|
383
|
+
cur = self._conn.execute(
|
|
384
|
+
"DELETE FROM memory_records WHERE importance < 0.5 AND created_at < ?",
|
|
385
|
+
(cutoff,),
|
|
386
|
+
)
|
|
387
|
+
deleted += cur.rowcount
|
|
388
|
+
# 如果总记录超过 max_records,删除最老的
|
|
389
|
+
count = self._conn.execute("SELECT COUNT(*) FROM memory_records").fetchone()[0]
|
|
390
|
+
if count > max_records:
|
|
391
|
+
excess = count - max_records
|
|
392
|
+
self._conn.execute(
|
|
393
|
+
"DELETE FROM memory_records WHERE memory_id IN "
|
|
394
|
+
"(SELECT memory_id FROM memory_records "
|
|
395
|
+
"ORDER BY importance ASC, updated_at ASC LIMIT ?)",
|
|
396
|
+
(excess,),
|
|
397
|
+
)
|
|
398
|
+
deleted += excess
|
|
399
|
+
self._conn.commit()
|
|
400
|
+
return deleted
|
|
401
|
+
|
|
402
|
+
def close(self) -> None:
|
|
403
|
+
with self._lock:
|
|
404
|
+
self._conn.close()
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class VersionConflict(RuntimeError):
|
|
408
|
+
"""``upsert`` 的 ``expected_version`` 乐观锁冲突。"""
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _positive_env_int(name: str, default: int) -> int:
|
|
412
|
+
try:
|
|
413
|
+
return max(1, int(os.environ.get(name, str(default))))
|
|
414
|
+
except (TypeError, ValueError):
|
|
415
|
+
return default
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _scope_where(scopes: list[tuple[MemoryScope, str]]) -> tuple[str, list[Any]]:
|
|
419
|
+
parts: list[str] = []
|
|
420
|
+
params: list[Any] = []
|
|
421
|
+
for scope, scope_id in scopes:
|
|
422
|
+
parts.append("(scope=? AND scope_id=?)")
|
|
423
|
+
params.extend([scope, scope_id])
|
|
424
|
+
return "(" + " OR ".join(parts) + ")", params
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _dedupe_active_versions(records: list[MemoryRecord]) -> list[MemoryRecord]:
|
|
428
|
+
"""同一 content_hash 多版本只保留 active 最新版本(方案 §10.6)。"""
|
|
429
|
+
seen: dict[str, MemoryRecord] = {}
|
|
430
|
+
for r in records:
|
|
431
|
+
key = r.content_hash
|
|
432
|
+
prev = seen.get(key)
|
|
433
|
+
if prev is None or r.version > prev.version:
|
|
434
|
+
seen[key] = r
|
|
435
|
+
return list(seen.values())
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _box_by_tokens(records: list[MemoryRecord], max_tokens: int) -> list[MemoryRecord]:
|
|
439
|
+
"""按 ``max_tokens`` 装箱(方案 §10.6 第 4 条),用启发式 token 估算。"""
|
|
440
|
+
from ksadk.context_engine.tokenizer import get_default_token_counter
|
|
441
|
+
|
|
442
|
+
counter = get_default_token_counter()
|
|
443
|
+
total = 0
|
|
444
|
+
out: list[MemoryRecord] = []
|
|
445
|
+
for r in records:
|
|
446
|
+
n = counter.count_text(r.content)
|
|
447
|
+
if total + n > max_tokens:
|
|
448
|
+
break
|
|
449
|
+
total += n
|
|
450
|
+
out.append(r)
|
|
451
|
+
return out
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
__all__ = ["SqliteMemoryProvider", "VersionConflict", "resolve_default_memory_provider"]
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _resolve_default_db_path() -> str:
|
|
458
|
+
"""解析默认持久化 SQLite 路径(方案 §10 / §12 本地默认)。
|
|
459
|
+
|
|
460
|
+
优先级:``KSADK_MEMORY_DB_PATH`` env > 本地 session 目录下的 memory.db > ``:memory:``。
|
|
461
|
+
默认持久化到本地 session dir,避免每次进程重启丢失(替换临时 ``:memory:``)。env 设
|
|
462
|
+
``KSADK_MEMORY_DB_PATH=:memory:`` 可显式回退内存库(测试用)。
|
|
463
|
+
"""
|
|
464
|
+
import os
|
|
465
|
+
|
|
466
|
+
configured = os.environ.get("KSADK_MEMORY_DB_PATH", "").strip()
|
|
467
|
+
if configured:
|
|
468
|
+
return configured
|
|
469
|
+
try:
|
|
470
|
+
from ksadk.sessions.local_service import resolve_local_session_dir
|
|
471
|
+
|
|
472
|
+
return str(resolve_local_session_dir() / "memory.db")
|
|
473
|
+
except Exception: # noqa: BLE001
|
|
474
|
+
# 无本地 session dir(如测试)→ 回退内存库,保持可运行
|
|
475
|
+
return ":memory:"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def resolve_default_memory_provider(
|
|
479
|
+
*, tenant_id: str = "local", workspace_id: str = "local"
|
|
480
|
+
) -> "SqliteMemoryProvider":
|
|
481
|
+
"""构造默认持久化 Memory Provider(替换临时 ``:memory:``)。
|
|
482
|
+
|
|
483
|
+
本地默认用 SQLite 文件 Provider(持久化到本地 session dir / ``KSADK_MEMORY_DB_PATH``);
|
|
484
|
+
云端应通过 ``LongTermMemoryService``/HTTP/SDK Provider 接入,不在本工厂范围(方案 §12)。
|
|
485
|
+
"""
|
|
486
|
+
return SqliteMemoryProvider(
|
|
487
|
+
db_path=_resolve_default_db_path(),
|
|
488
|
+
tenant_id=tenant_id,
|
|
489
|
+
workspace_id=workspace_id,
|
|
490
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""ResolvedMemoryPolicy —— Memory 最终运行策略的统一解析入口(方案 §10)。
|
|
2
|
+
|
|
3
|
+
统一优先级(方案 §2):
|
|
4
|
+
memory.enabled=false → recall=false, write=off
|
|
5
|
+
memory.enabled=true → recall 由 memory.recall.enabled 决定
|
|
6
|
+
→ write 由 rollout 和 write.mode 共同决定
|
|
7
|
+
|
|
8
|
+
rollout=off → 不提取、不写入
|
|
9
|
+
rollout=shadow → 生成 Candidate 和审计,不提交 Provider
|
|
10
|
+
rollout=enabled + mode=explicit_only → 只保存用户明确要求记住的内容
|
|
11
|
+
rollout=enabled + mode=candidate → 按 Candidate + Policy 判断是否提交
|
|
12
|
+
|
|
13
|
+
环境变量只作为旧 AgentVersion 缺少字段时的兼容 fallback。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ResolvedMemoryPolicy:
|
|
24
|
+
"""Memory 最终运行策略的统一解析结果。
|
|
25
|
+
|
|
26
|
+
所有 Memory 相关决策(recall/flush/extraction)都应从此结构读取,
|
|
27
|
+
不应分别从 memory.enabled / rollout / write.mode 各自判断。
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
enabled: bool
|
|
31
|
+
recall_enabled: bool
|
|
32
|
+
write_rollout: str # off / shadow / enabled
|
|
33
|
+
write_mode: str # off / explicit_only / candidate
|
|
34
|
+
flush_before_compaction: bool
|
|
35
|
+
provider_ref: str
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def should_recall(self) -> bool:
|
|
39
|
+
"""是否执行 recall。"""
|
|
40
|
+
return self.enabled and self.recall_enabled
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def should_extract_candidates(self) -> bool:
|
|
44
|
+
"""是否生成 Candidate(shadow 也生成,但不提交 Provider)。"""
|
|
45
|
+
return self.enabled and self.write_rollout in ("shadow", "enabled")
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def should_flush(self) -> bool:
|
|
49
|
+
"""是否提交 Candidate 到 Provider。"""
|
|
50
|
+
return (
|
|
51
|
+
self.enabled
|
|
52
|
+
and self.write_rollout == "enabled"
|
|
53
|
+
and self.write_mode in ("explicit_only", "candidate")
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def is_explicit_only(self) -> bool:
|
|
58
|
+
"""是否只保存用户明确要求记住的内容。"""
|
|
59
|
+
return self.should_flush and self.write_mode == "explicit_only"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_memory_policy(
|
|
63
|
+
*,
|
|
64
|
+
memory_enabled: bool | None = None,
|
|
65
|
+
recall_enabled: bool | None = None,
|
|
66
|
+
write_rollout: str | None = None,
|
|
67
|
+
write_mode: str | None = None,
|
|
68
|
+
flush_before_compaction: bool | None = None,
|
|
69
|
+
provider_ref: str | None = None,
|
|
70
|
+
) -> ResolvedMemoryPolicy:
|
|
71
|
+
"""统一解析 Memory 运行策略。
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
memory_enabled: MemorySpec.enabled
|
|
75
|
+
recall_enabled: MemorySpec.recall.enabled
|
|
76
|
+
write_rollout: ContextSpec.rollout.memoryWrite(off/shadow/enabled)
|
|
77
|
+
write_mode: MemorySpec.write.mode(off/explicit_only/candidate)
|
|
78
|
+
flush_before_compaction: MemorySpec.write.flushBeforeCompaction
|
|
79
|
+
provider_ref: MemorySpec.providerRef
|
|
80
|
+
"""
|
|
81
|
+
legacy_flush_enabled = str(
|
|
82
|
+
os.environ.get("KSADK_MEMORY_FLUSH_ENABLED", "")
|
|
83
|
+
).strip().lower() in (
|
|
84
|
+
"1",
|
|
85
|
+
"true",
|
|
86
|
+
"yes",
|
|
87
|
+
"on",
|
|
88
|
+
)
|
|
89
|
+
enabled = legacy_flush_enabled if memory_enabled is None else memory_enabled
|
|
90
|
+
recall = enabled if recall_enabled is None else recall_enabled
|
|
91
|
+
flush_before = True if flush_before_compaction is None else flush_before_compaction
|
|
92
|
+
provider = str(provider_ref or "local-default")
|
|
93
|
+
|
|
94
|
+
if not enabled:
|
|
95
|
+
return ResolvedMemoryPolicy(
|
|
96
|
+
enabled=False,
|
|
97
|
+
recall_enabled=False,
|
|
98
|
+
write_rollout="off",
|
|
99
|
+
write_mode="off",
|
|
100
|
+
flush_before_compaction=False,
|
|
101
|
+
provider_ref=provider,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# rollout 优先于 write_mode
|
|
105
|
+
rollout = str(write_rollout or "").strip().lower()
|
|
106
|
+
mode = str(write_mode or "").strip().lower()
|
|
107
|
+
|
|
108
|
+
if rollout not in ("off", "shadow", "enabled"):
|
|
109
|
+
rollout = "enabled" if legacy_flush_enabled else "off"
|
|
110
|
+
|
|
111
|
+
if rollout == "off":
|
|
112
|
+
return ResolvedMemoryPolicy(
|
|
113
|
+
enabled=True,
|
|
114
|
+
recall_enabled=recall,
|
|
115
|
+
write_rollout="off",
|
|
116
|
+
write_mode="off",
|
|
117
|
+
flush_before_compaction=flush_before,
|
|
118
|
+
provider_ref=provider,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if rollout == "shadow":
|
|
122
|
+
return ResolvedMemoryPolicy(
|
|
123
|
+
enabled=True,
|
|
124
|
+
recall_enabled=recall,
|
|
125
|
+
write_rollout="shadow",
|
|
126
|
+
write_mode=mode if mode in ("explicit_only", "candidate") else "candidate",
|
|
127
|
+
flush_before_compaction=flush_before,
|
|
128
|
+
provider_ref=provider,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# rollout == "enabled"
|
|
132
|
+
if mode not in ("explicit_only", "candidate"):
|
|
133
|
+
mode = "candidate"
|
|
134
|
+
|
|
135
|
+
return ResolvedMemoryPolicy(
|
|
136
|
+
enabled=True,
|
|
137
|
+
recall_enabled=recall,
|
|
138
|
+
write_rollout="enabled",
|
|
139
|
+
write_mode=mode,
|
|
140
|
+
flush_before_compaction=flush_before,
|
|
141
|
+
provider_ref=provider,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
__all__ = ["ResolvedMemoryPolicy", "resolve_memory_policy"]
|