agentengine-sdk-python 0.6.9__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/METADATA +6 -4
  2. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/RECORD +123 -128
  3. ksadk/api/client.py +19 -0
  4. ksadk/cli/cmd_create.py +34 -7
  5. ksadk/cli/cmd_hermes.py +6 -2
  6. ksadk/cli/cmd_invoke.py +208 -41
  7. ksadk/cli/cmd_mcp.py +6 -1
  8. ksadk/cli/cmd_openclaw.py +6 -2
  9. ksadk/cli/cmd_run.py +13 -6
  10. ksadk/cli/network_options.py +5 -3
  11. ksadk/configs/env_registry.py +1 -0
  12. ksadk/conversations/message_projection.py +269 -0
  13. ksadk/conversations/model_context.py +11 -1
  14. ksadk/conversations/runtime.py +81 -13
  15. ksadk/deployment/base.py +1 -1
  16. ksadk/deployment/providers/serverless.py +18 -8
  17. ksadk/memory/adk/resilient_session_service.py +268 -0
  18. ksadk/memory/adk/short_term_memory.py +21 -6
  19. ksadk/runners/adk_runner.py +974 -115
  20. ksadk/runners/langchain_runner.py +219 -7
  21. ksadk/runners/langgraph_runner.py +145 -12
  22. ksadk/runners/remote_runner.py +143 -18
  23. ksadk/server/app.py +148 -6
  24. ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-D-SSn2ee.js} +1 -1
  25. ksadk/server/static/assets/{CodeBlock-BbI4HPtJ.js → CodeBlock-P16VOLYi.js} +1 -1
  26. ksadk/server/static/assets/{MathMessageMarkdown-DMLQYoLp.js → MathMessageMarkdown-CdGiQWHR.js} +1 -1
  27. ksadk/server/static/assets/{MermaidBlock-DCR8WZ17.js → MermaidBlock-IOcal8cm.js} +6 -6
  28. ksadk/server/static/assets/{NativeTerminalPanel-CKUvWhUz.js → NativeTerminalPanel-DGSDWsei.js} +1 -1
  29. ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-BA13sHc9.js} +1 -1
  30. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  31. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-DzJzgVoL.js} +1 -1
  32. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-UTdVcZhn.js} +1 -1
  33. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-Dix0GD4m.js} +1 -1
  34. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  35. ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-C2JxuZ42.js} +1 -1
  36. ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-Be57QPgD.js} +1 -1
  37. ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-WjSuy4nv.js} +1 -1
  38. ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-BOovP-b5.js} +1 -1
  39. ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DOmZNmWj.js} +1 -1
  40. ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-DzwlodkU.js} +1 -1
  41. ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-DMqAikEU.js} +1 -1
  42. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  43. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  44. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  45. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-Zhy2GBnP.js} +1 -1
  46. ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-CbKrtIUz.js} +1 -1
  47. ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-BRApgQRK.js} +1 -1
  48. ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-D0xbnDiK.js} +1 -1
  49. ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-CVugVI9L.js} +1 -1
  50. ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-DozUGgee.js} +1 -1
  51. ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-ZOYsKlru.js} +1 -1
  52. ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-2hInLS_J.js} +1 -1
  53. ksadk/server/static/assets/{dist-BMKM66B4.js → dist-8EEyGgDx.js} +1 -1
  54. ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-8_SfK1IP.js} +1 -1
  55. ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-B0NP_Z0z.js} +1 -1
  56. ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-BIJVDMuB.js} +1 -1
  57. ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-BmlMPsCc.js} +1 -1
  58. ksadk/server/static/assets/{dist-D99wf6_X.js → dist-BrCcQ47T.js} +1 -1
  59. ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-BssVmjIo.js} +1 -1
  60. ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-C3HIIaN5.js} +1 -1
  61. ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-CPBtqRTW.js} +1 -1
  62. ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CkEKJFtk.js} +1 -1
  63. ksadk/server/static/assets/{dist-B67uF2uK.js → dist-D-JMdWPQ.js} +1 -1
  64. ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-D-xOD048.js} +1 -1
  65. ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-D57wnGWd.js} +1 -1
  66. ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-DL_NKc_z.js} +1 -1
  67. ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-DMezvBGJ.js} +1 -1
  68. ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-DmFaq_Iy.js} +1 -1
  69. ksadk/server/static/assets/{dist-DLMjL01k.js → dist-Dp0k7bWI.js} +1 -1
  70. ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DpKtt4Bo.js} +1 -1
  71. ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-DyOkZmze.js} +1 -1
  72. ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-DyWsb78Q.js} +1 -1
  73. ksadk/server/static/assets/{dist-S4klkK5W.js → dist-gX6GOLKq.js} +1 -1
  74. ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-Bk-9bgE5.js} +1 -1
  75. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  76. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-Dn9kZVuZ.js} +1 -1
  77. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-BGxXlJqm.js} +1 -1
  78. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  79. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-C7Q0X-wi.js} +1 -1
  80. ksadk/server/static/assets/{index-BH7-pC0B.js → index-C00Lwg3L.js} +6 -6
  81. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  82. ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-BO_CSqq5.js} +1 -1
  83. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-eYLPLd46.js} +1 -1
  84. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-De9geSb0.js} +1 -1
  85. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-Cs80kQto.js} +1 -1
  86. ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DSrCFD8n.js} +1 -1
  87. ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CN9sbKTF.js} +1 -1
  88. ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-DlgSBU8l.js} +2 -2
  89. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-9afz7Oug.js} +1 -1
  90. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  91. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  92. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-DPv_VstN.js} +1 -1
  93. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-o77wZ1XK.js} +1 -1
  94. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  95. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-BYVw81VC.js} +1 -1
  96. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-B7nZFMKT.js} +1 -1
  97. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-BGRRrFw5.js} +1 -1
  98. ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-BUfHCoPs.js} +1 -1
  99. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  100. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-FbCTGrf9.js} +1 -1
  101. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  102. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  103. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-DWgwtPsJ.js} +1 -1
  104. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  105. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-2x8G-tTM.js} +1 -1
  106. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-Drma3AYl.js} +1 -1
  107. ksadk/server/static/index.html +1 -1
  108. ksadk/sessions/__init__.py +17 -21
  109. ksadk/sessions/continuity.py +28 -4
  110. ksadk/sessions/postgres_service.py +77 -14
  111. ksadk/sessions/resilience.py +52 -0
  112. ksadk/sessions/resilient.py +360 -0
  113. ksadk/toolsets/__init__.py +2 -0
  114. ksadk/toolsets/workspace.py +49 -10
  115. ksadk/tracing/setup.py +63 -0
  116. ksadk/tui/__init__.py +22 -7
  117. ksadk/tui/loop.py +1877 -0
  118. ksadk/tui/stream_render.py +68 -0
  119. ksadk/version.py +1 -1
  120. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
  121. ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
  122. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
  123. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
  124. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
  125. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
  126. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
  127. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
  128. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
  129. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
  130. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
  131. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
  132. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
  133. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
  134. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
  135. ksadk/tui/app.py +0 -400
  136. ksadk/tui/clipboard.py +0 -100
  137. ksadk/tui/widgets/__init__.py +0 -22
  138. ksadk/tui/widgets/assistant.py +0 -89
  139. ksadk/tui/widgets/base.py +0 -54
  140. ksadk/tui/widgets/chat_input.py +0 -246
  141. ksadk/tui/widgets/history.py +0 -156
  142. ksadk/tui/widgets/system.py +0 -87
  143. ksadk/tui/widgets/thinking.py +0 -110
  144. ksadk/tui/widgets/tool_call.py +0 -161
  145. ksadk/tui/widgets/user.py +0 -37
  146. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +0 -0
  147. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/entry_points.txt +0 -0
  148. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
  149. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,268 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import Any, Optional
6
+
7
+ from google.adk.events.event import Event
8
+ from google.adk.sessions import InMemorySessionService
9
+ from google.adk.sessions.base_session_service import (
10
+ BaseSessionService,
11
+ GetSessionConfig,
12
+ ListSessionsResponse,
13
+ )
14
+ from google.adk.sessions.session import Session
15
+
16
+ from ksadk.sessions.resilience import is_session_backend_failure
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ResilientADKSessionService(BaseSessionService):
22
+ """Run ADK sessions locally while mirroring them to durable storage."""
23
+
24
+ _probe_interval_seconds: float = 30.0
25
+
26
+ def __init__(self, primary: BaseSessionService) -> None:
27
+ self.primary = primary
28
+ self.live = InMemorySessionService()
29
+ self._primary_sessions: dict[tuple[str, str, str], Session] = {}
30
+ self._primary_enabled = True
31
+ self._probe_task: asyncio.Task[None] | None = None
32
+
33
+ @property
34
+ def degraded(self) -> bool:
35
+ return not self._primary_enabled
36
+
37
+ def _degrade(self, exc: Exception) -> None:
38
+ if not self._primary_enabled:
39
+ return
40
+ self._primary_enabled = False
41
+ logger.error(
42
+ "ADK session persistence degraded; using in-memory live session: %s",
43
+ exc,
44
+ extra={
45
+ "session_backend_state": "degraded",
46
+ "session_backend": type(self.primary).__name__,
47
+ },
48
+ )
49
+ self._start_probe()
50
+
51
+ def _handle_primary_failure(self, exc: Exception) -> bool:
52
+ if not is_session_backend_failure(exc):
53
+ return False
54
+ self._degrade(exc)
55
+ return True
56
+
57
+ def _start_probe(self) -> None:
58
+ if self._probe_task is not None and not self._probe_task.done():
59
+ return
60
+ self._probe_task = asyncio.create_task(self._probe_loop())
61
+
62
+ async def _probe_loop(self) -> None:
63
+ while not self._primary_enabled:
64
+ await asyncio.sleep(self._probe_interval_seconds)
65
+ if self._primary_enabled:
66
+ break
67
+ try:
68
+ await self.primary.get_session(
69
+ app_name="__ksadk_probe__",
70
+ user_id="__ksadk_probe__",
71
+ session_id="__ksadk_probe__",
72
+ )
73
+ except Exception:
74
+ continue
75
+ self._primary_enabled = True
76
+ logger.info(
77
+ "ADK session persistence recovered; durable backend re-enabled",
78
+ extra={
79
+ "session_backend_state": "recovered",
80
+ "session_backend": type(self.primary).__name__,
81
+ },
82
+ )
83
+
84
+ @staticmethod
85
+ def _key(app_name: str, user_id: str, session_id: str) -> tuple[str, str, str]:
86
+ return app_name, user_id, session_id
87
+
88
+ async def _hydrate(self, durable: Session) -> Session:
89
+ existing = await self.live.get_session(
90
+ app_name=durable.app_name,
91
+ user_id=durable.user_id,
92
+ session_id=durable.id,
93
+ )
94
+ if existing is None:
95
+ existing = await self.live.create_session(
96
+ app_name=durable.app_name,
97
+ user_id=durable.user_id,
98
+ state=durable.state,
99
+ session_id=durable.id,
100
+ )
101
+ existing_ids = {event.id for event in existing.events}
102
+ for event in durable.events:
103
+ if event.id not in existing_ids:
104
+ await self.live.append_event(existing, event)
105
+ self._primary_sessions[self._key(durable.app_name, durable.user_id, durable.id)] = durable
106
+ hydrated = await self.live.get_session(
107
+ app_name=durable.app_name,
108
+ user_id=durable.user_id,
109
+ session_id=durable.id,
110
+ )
111
+ if hydrated is None:
112
+ raise RuntimeError(f"Failed to hydrate ADK live session {durable.id}")
113
+ return hydrated
114
+
115
+ async def create_session(
116
+ self,
117
+ *,
118
+ app_name: str,
119
+ user_id: str,
120
+ state: Optional[dict[str, Any]] = None,
121
+ session_id: Optional[str] = None,
122
+ ) -> Session:
123
+ if session_id:
124
+ existing = await self.live.get_session(
125
+ app_name=app_name,
126
+ user_id=user_id,
127
+ session_id=session_id,
128
+ )
129
+ if self._primary_enabled:
130
+ try:
131
+ durable = await self.primary.get_session(
132
+ app_name=app_name,
133
+ user_id=user_id,
134
+ session_id=session_id,
135
+ )
136
+ if durable is not None:
137
+ return await self._hydrate(durable)
138
+ except Exception as exc:
139
+ if not self._handle_primary_failure(exc):
140
+ raise
141
+ if existing is not None:
142
+ return existing
143
+
144
+ live = await self.live.create_session(
145
+ app_name=app_name,
146
+ user_id=user_id,
147
+ state=state,
148
+ session_id=session_id,
149
+ )
150
+ if self._primary_enabled:
151
+ try:
152
+ durable = await self.primary.create_session(
153
+ app_name=app_name,
154
+ user_id=user_id,
155
+ state=state,
156
+ session_id=live.id,
157
+ )
158
+ self._primary_sessions[self._key(app_name, user_id, live.id)] = durable
159
+ except Exception as exc:
160
+ if not self._handle_primary_failure(exc):
161
+ raise
162
+ return live
163
+
164
+ async def get_session(
165
+ self,
166
+ *,
167
+ app_name: str,
168
+ user_id: str,
169
+ session_id: str,
170
+ config: Optional[GetSessionConfig] = None,
171
+ ) -> Optional[Session]:
172
+ live = await self.live.get_session(
173
+ app_name=app_name,
174
+ user_id=user_id,
175
+ session_id=session_id,
176
+ config=config,
177
+ )
178
+ if not self._primary_enabled:
179
+ return live
180
+ try:
181
+ durable = await self.primary.get_session(
182
+ app_name=app_name,
183
+ user_id=user_id,
184
+ session_id=session_id,
185
+ config=config,
186
+ )
187
+ return await self._hydrate(durable) if durable is not None else live
188
+ except Exception as exc:
189
+ if not self._handle_primary_failure(exc):
190
+ raise
191
+ return live
192
+
193
+ async def list_sessions(
194
+ self,
195
+ *,
196
+ app_name: str,
197
+ user_id: Optional[str] = None,
198
+ ) -> ListSessionsResponse:
199
+ if self._primary_enabled:
200
+ try:
201
+ durable = await self.primary.list_sessions(app_name=app_name, user_id=user_id)
202
+ for session in durable.sessions:
203
+ await self._hydrate(session)
204
+ except Exception as exc:
205
+ if not self._handle_primary_failure(exc):
206
+ raise
207
+ return await self.live.list_sessions(app_name=app_name, user_id=user_id)
208
+
209
+ async def delete_session(self, *, app_name: str, user_id: str, session_id: str) -> None:
210
+ await self.live.delete_session(app_name=app_name, user_id=user_id, session_id=session_id)
211
+ self._primary_sessions.pop(self._key(app_name, user_id, session_id), None)
212
+ if self._primary_enabled:
213
+ try:
214
+ await self.primary.delete_session(
215
+ app_name=app_name,
216
+ user_id=user_id,
217
+ session_id=session_id,
218
+ )
219
+ except Exception as exc:
220
+ if not self._handle_primary_failure(exc):
221
+ raise
222
+
223
+ async def append_event(self, session: Session, event: Event) -> Event:
224
+ stored = await self.live.append_event(session, event)
225
+ if not self._primary_enabled:
226
+ return stored
227
+ key = self._key(session.app_name, session.user_id, session.id)
228
+ durable_session = self._primary_sessions.get(key)
229
+ try:
230
+ if durable_session is None:
231
+ durable_session = await self.primary.get_session(
232
+ app_name=session.app_name,
233
+ user_id=session.user_id,
234
+ session_id=session.id,
235
+ )
236
+ if durable_session is None:
237
+ durable_session = await self.primary.create_session(
238
+ app_name=session.app_name,
239
+ user_id=session.user_id,
240
+ state=session.state,
241
+ session_id=session.id,
242
+ )
243
+ self._primary_sessions[key] = durable_session
244
+ await self.primary.append_event(durable_session, event)
245
+ except Exception as exc:
246
+ if not self._handle_primary_failure(exc):
247
+ raise
248
+ return stored
249
+
250
+ async def flush(self) -> None:
251
+ await self.live.flush()
252
+ if self._primary_enabled:
253
+ try:
254
+ await self.primary.flush()
255
+ except Exception as exc:
256
+ if not self._handle_primary_failure(exc):
257
+ raise
258
+
259
+ async def close(self) -> None:
260
+ if self._probe_task is not None and not self._probe_task.done():
261
+ self._probe_task.cancel()
262
+ try:
263
+ await self._probe_task
264
+ except asyncio.CancelledError:
265
+ pass
266
+ close = getattr(self.primary, "close", None)
267
+ if close is not None:
268
+ await close()
@@ -109,9 +109,7 @@ class ShortTermMemory(BaseModel):
109
109
  def model_post_init(self, __context: Any) -> None:
110
110
  # 优先使用 db_url
111
111
  if self.db_url:
112
- logger.info(
113
- f"ShortTermMemory: using db_url (ignoring backend option)"
114
- )
112
+ logger.info("ShortTermMemory: using db_url (ignoring backend option)")
115
113
  self._init_database_service(self.db_url)
116
114
  return
117
115
 
@@ -133,7 +131,8 @@ class ShortTermMemory(BaseModel):
133
131
  case "database":
134
132
  if not self.db_url:
135
133
  raise ValueError(
136
- "KSADK_SESSION_DSN is required when ADK session backend resolves to database/postgres"
134
+ "KSADK_SESSION_DSN is required when ADK session backend "
135
+ "resolves to database/postgres"
137
136
  )
138
137
  else:
139
138
  self._init_database_service(self.db_url)
@@ -151,7 +150,20 @@ class ShortTermMemory(BaseModel):
151
150
  try:
152
151
  from google.adk.sessions import DatabaseSessionService
153
152
 
154
- self._session_service = DatabaseSessionService(db_url=normalized_db_url)
153
+ from ksadk.memory.adk.resilient_session_service import ResilientADKSessionService
154
+ from ksadk.sessions.resilience import session_backend_timeout_seconds
155
+
156
+ service_kwargs = {}
157
+ if normalized_db_url.startswith(("postgresql://", "postgresql+asyncpg://")):
158
+ timeout = session_backend_timeout_seconds()
159
+ service_kwargs["connect_args"] = {
160
+ "timeout": timeout,
161
+ "command_timeout": timeout,
162
+ }
163
+
164
+ self._session_service = ResilientADKSessionService(
165
+ DatabaseSessionService(db_url=normalized_db_url, **service_kwargs)
166
+ )
155
167
  logger.info(
156
168
  f"ShortTermMemory: using DatabaseSessionService "
157
169
  f"({normalized_db_url[:30]}...)"
@@ -163,12 +175,14 @@ class ShortTermMemory(BaseModel):
163
175
  "Falling back to InMemorySessionService."
164
176
  )
165
177
  self._session_service = InMemorySessionService()
178
+ self.backend = "local"
166
179
  except Exception as e:
167
180
  logger.error(
168
181
  f"Failed to create DatabaseSessionService: {e}. "
169
182
  f"Falling back to InMemorySessionService."
170
183
  )
171
184
  self._session_service = InMemorySessionService()
185
+ self.backend = "local"
172
186
 
173
187
  @property
174
188
  def session_service(self) -> BaseSessionService:
@@ -272,7 +286,8 @@ class ShortTermMemory(BaseModel):
272
286
  backend = explicit_backend
273
287
  if _session_backend_requires_database_url(backend) and not db_url:
274
288
  raise ValueError(
275
- "KSADK_SESSION_DSN is required when ADK session backend resolves to database/postgres"
289
+ "KSADK_SESSION_DSN is required when ADK session backend "
290
+ "resolves to database/postgres"
276
291
  )
277
292
  if not backend:
278
293
  if db_url: