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.
Files changed (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {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"]