1414
1515from __future__ import annotations
1616
17+ import asyncio
1718from datetime import datetime
1819from pathlib import Path
1920from typing import Optional
@@ -69,25 +70,25 @@ async def run_input_file(
6970 memory_service = memory_service ,
7071 credential_service = credential_service ,
7172 )
72- with open (input_path , 'r' , encoding = ' utf-8' ) as f :
73+ with open (input_path , "r" , encoding = " utf-8" ) as f :
7374 input_file = InputFile .model_validate_json (f .read ())
74- input_file .state [' _time' ] = datetime .now ().isoformat ()
75+ input_file .state [" _time" ] = datetime .now ().isoformat ()
7576
7677 session = await session_service .create_session (
7778 app_name = app_name , user_id = user_id , state = input_file .state
7879 )
7980 for query in input_file .queries :
80- click .echo (f' [user]: { query } ' )
81- content = types .Content (role = ' user' , parts = [types .Part (text = query )])
81+ click .echo (f" [user]: { query } " )
82+ content = types .Content (role = " user" , parts = [types .Part (text = query )])
8283 async with Aclosing (
8384 runner .run_async (
8485 user_id = session .user_id , session_id = session .id , new_message = content
8586 )
8687 ) as agen :
8788 async for event in agen :
8889 if event .content and event .content .parts :
89- if text := '' .join (part .text or '' for part in event .content .parts ):
90- click .echo (f' [{ event .author } ]: { text } ' )
90+ if text := "" .join (part .text or "" for part in event .content .parts ):
91+ click .echo (f" [{ event .author } ]: { text } " )
9192 return session
9293
9394
@@ -98,6 +99,9 @@ async def run_interactively(
9899 session_service : BaseSessionService ,
99100 credential_service : BaseCredentialService ,
100101 memory_service : Optional [BaseMemoryService ] = None ,
102+ save_session_on_runtime : bool = False ,
103+ interval : int = 60 ,
104+ agent_root : Optional [Path ] = None ,
101105) -> None :
102106 app = (
103107 root_agent_or_app
@@ -111,25 +115,78 @@ async def run_interactively(
111115 memory_service = memory_service ,
112116 credential_service = credential_service ,
113117 )
114- while True :
115- query = input ('[user]: ' )
116- if not query or not query .strip ():
117- continue
118- if query == 'exit' :
119- break
120- async with Aclosing (
121- runner .run_async (
122- user_id = session .user_id ,
123- session_id = session .id ,
124- new_message = types .Content (
125- role = 'user' , parts = [types .Part (text = query )]
126- ),
127- )
128- ) as agen :
129- async for event in agen :
130- if event .content and event .content .parts :
131- if text := '' .join (part .text or '' for part in event .content .parts ):
132- click .echo (f'[{ event .author } ]: { text } ' )
118+
119+ # Background task for periodic session saving
120+ save_task : Optional [asyncio .Task ] = None
121+
122+ async def _periodic_save_session ():
123+ """Periodically save the session to disk every interval seconds."""
124+ nonlocal session , save_task
125+ try :
126+ while True :
127+ await asyncio .sleep (interval ) # Save every Interval seconds
128+
129+ if save_session_on_runtime and agent_root :
130+ try :
131+ # Get the current session state
132+ current_session = await session_service .get_session (
133+ app_name = session .app_name ,
134+ user_id = session .user_id ,
135+ session_id = session .id ,
136+ )
137+
138+ if current_session :
139+ # Save to runtime session file
140+ runtime_session_path = (
141+ agent_root / ".adk" / "runtime_session.json"
142+ )
143+ runtime_session_path .parent .mkdir (parents = True , exist_ok = True )
144+ runtime_session_path .write_text (
145+ current_session .model_dump_json (
146+ indent = 2 , exclude_none = True , by_alias = True
147+ ),
148+ encoding = "utf-8" ,
149+ )
150+ except Exception :
151+ # Silently ignore errors to avoid breaking the interactive loop
152+ pass
153+ except asyncio .CancelledError :
154+ # Task was cancelled, exit gracefully
155+ pass
156+
157+ # Start the periodic save task if enabled
158+ if save_session_on_runtime and agent_root :
159+ save_task = asyncio .create_task (_periodic_save_session ())
160+
161+ try :
162+ while True :
163+ query = input ("[user]: " )
164+ if not query or not query .strip ():
165+ continue
166+ if query == "exit" :
167+ break
168+ async with Aclosing (
169+ runner .run_async (
170+ user_id = session .user_id ,
171+ session_id = session .id ,
172+ new_message = types .Content (
173+ role = "user" , parts = [types .Part (text = query )]
174+ ),
175+ )
176+ ) as agen :
177+ async for event in agen :
178+ if event .content and event .content .parts :
179+ if text := "" .join (part .text or "" for part in event .content .parts ):
180+ click .echo (f"[{ event .author } ]: { text } " )
181+ finally :
182+ # Clean up the background task
183+ if save_task :
184+ save_task .cancel ()
185+ try :
186+ await save_task
187+ except asyncio .CancelledError :
188+ pass
189+
133190 await runner .close ()
134191
135192
@@ -140,6 +197,8 @@ async def run_cli(
140197 input_file : Optional [str ] = None ,
141198 saved_session_file : Optional [str ] = None ,
142199 save_session : bool ,
200+ save_session_on_runtime : bool ,
201+ interval : int ,
143202 session_id : Optional [str ] = None ,
144203 session_service_uri : Optional [str ] = None ,
145204 artifact_service_uri : Optional [str ] = None ,
@@ -167,7 +226,7 @@ async def run_cli(
167226 agent_parent_path = Path (agent_parent_dir ).resolve ()
168227 agent_root = agent_parent_path / agent_folder_name
169228 load_services_module (str (agent_root ))
170- user_id = ' test_user'
229+ user_id = " test_user"
171230
172231 agents_dir = str (agent_parent_path )
173232 agent_loader = AgentLoader (agents_dir = agents_dir )
@@ -179,7 +238,7 @@ async def run_cli(
179238 if isinstance (agent_or_app , App ) and agent_or_app .name != agent_folder_name :
180239 app_name_to_dir = {agent_or_app .name : agent_folder_name }
181240
182- if not is_env_enabled (' ADK_DISABLE_LOAD_DOTENV' ):
241+ if not is_env_enabled (" ADK_DISABLE_LOAD_DOTENV" ):
183242 envs .load_dotenv_for_agent (agent_folder_name , agents_dir )
184243
185244 # Create session and artifact services using factory functions.
@@ -211,8 +270,8 @@ def _print_event(event) -> None:
211270 text_parts = [part .text for part in content .parts if part .text ]
212271 if not text_parts :
213272 return
214- author = event .author or ' system'
215- click .echo (f' [{ author } ]: { "" .join (text_parts )} ' )
273+ author = event .author or " system"
274+ click .echo (f" [{ author } ]: { '' .join (text_parts )} " )
216275
217276 if input_file :
218277 session = await run_input_file (
@@ -227,7 +286,7 @@ def _print_event(event) -> None:
227286 )
228287 elif saved_session_file :
229288 # Load the saved session from file
230- with open (saved_session_file , 'r' , encoding = ' utf-8' ) as f :
289+ with open (saved_session_file , "r" , encoding = " utf-8" ) as f :
231290 loaded_session = Session .model_validate_json (f .read ())
232291
233292 # Create a new session in the service, copying state from the file
@@ -252,22 +311,54 @@ def _print_event(event) -> None:
252311 memory_service = memory_service ,
253312 )
254313 else :
255- session = await session_service .create_session (
256- app_name = session_app_name , user_id = user_id
257- )
258- click .echo (f'Running agent { agent_or_app .name } , type exit to exit.' )
314+ # Check for runtime saved session
315+ runtime_session_path = agent_root / ".adk" / "runtime_session.json"
316+ if runtime_session_path .exists ():
317+ try :
318+ with open (runtime_session_path , "r" , encoding = "utf-8" ) as f :
319+ loaded_session = Session .model_validate_json (f .read ())
320+
321+ # Create a new session in the service, copying state from the file
322+ session = await session_service .create_session (
323+ app_name = session_app_name ,
324+ user_id = user_id ,
325+ state = loaded_session .state if loaded_session else None ,
326+ )
327+
328+ # Append events from the file to the new session and display them
329+ if loaded_session :
330+ for event in loaded_session .events :
331+ await session_service .append_event (session , event )
332+ _print_event (event )
333+
334+ click .echo (f"Loaded runtime session from { runtime_session_path } " )
335+ except Exception as e :
336+ click .echo (f"Warning: Failed to load runtime session: { e } " )
337+ # Fall back to creating a new session
338+ session = await session_service .create_session (
339+ app_name = session_app_name , user_id = user_id
340+ )
341+ else :
342+ session = await session_service .create_session (
343+ app_name = session_app_name , user_id = user_id
344+ )
345+
346+ click .echo (f"Running agent { agent_or_app .name } , type exit to exit." )
259347 await run_interactively (
260348 agent_or_app ,
261349 artifact_service ,
262350 session ,
263351 session_service ,
264352 credential_service ,
265353 memory_service = memory_service ,
354+ save_session_on_runtime = True ,
355+ interval = interval ,
356+ agent_root = agent_root ,
266357 )
267358
268359 if save_session :
269- session_id = session_id or input (' Session ID to save: ' )
270- session_path = agent_root / f' { session_id } .session.json'
360+ session_id = session_id or input (" Session ID to save: " )
361+ session_path = agent_root / f" { session_id } .session.json"
271362
272363 # Fetch the session again to get all the details.
273364 session = await session_service .get_session (
@@ -277,7 +368,7 @@ def _print_event(event) -> None:
277368 )
278369 session_path .write_text (
279370 session .model_dump_json (indent = 2 , exclude_none = True , by_alias = True ),
280- encoding = ' utf-8' ,
371+ encoding = " utf-8" ,
281372 )
282373
283- print (' Session saved to' , session_path )
374+ print (" Session saved to" , session_path )
0 commit comments