1010import asyncio
1111
1212from . import storage
13+ from . import memory
1314from .council import run_full_council , generate_conversation_title , stage1_collect_responses , stage2_collect_rankings , stage3_synthesize_final , calculate_aggregate_rankings
1415
1516app = FastAPI (title = "LLM Council API" )
@@ -79,6 +80,52 @@ async def get_conversation(conversation_id: str):
7980 return conversation
8081
8182
83+ @app .get ("/api/conversations/{conversation_id}/memory" )
84+ async def get_conversation_memory (conversation_id : str ):
85+ """Return the memory (short entries and summary) for a conversation."""
86+ try :
87+ mem = memory .get_memory (conversation_id )
88+ return mem
89+ except Exception :
90+ raise HTTPException (status_code = 404 , detail = "Conversation not found" )
91+
92+
93+ @app .post ("/api/conversations/{conversation_id}/memory/clear" )
94+ async def clear_conversation_memory (conversation_id : str ):
95+ """Clear a conversation's memory."""
96+ try :
97+ memory .clear_memory (conversation_id )
98+ return {"status" : "ok" }
99+ except Exception :
100+ raise HTTPException (status_code = 404 , detail = "Conversation not found" )
101+
102+
103+ @app .get ("/api/memory/mode" )
104+ async def get_memory_mode ():
105+ """Return the current runtime memory mode and local settings."""
106+ from .config import MEMORY_LOCAL_MAX_SENTENCES
107+ try :
108+ mode = memory .get_runtime_mode ()
109+ return {"mode" : mode , "local_max_sentences" : MEMORY_LOCAL_MAX_SENTENCES }
110+ except Exception :
111+ raise HTTPException (status_code = 500 , detail = "Unable to retrieve memory mode" )
112+
113+
114+ @app .post ("/api/memory/mode" )
115+ async def set_memory_mode (payload : Dict [str , str ]):
116+ """Set the runtime memory mode. Body: {"mode": "local"|"model"} """
117+ mode = payload .get ("mode" )
118+ if mode not in ("local" , "model" ):
119+ raise HTTPException (status_code = 400 , detail = "mode must be 'local' or 'model'" )
120+ try :
121+ memory .set_runtime_mode (mode )
122+ return {"status" : "ok" , "mode" : mode }
123+ except ValueError as e :
124+ raise HTTPException (status_code = 400 , detail = str (e ))
125+ except Exception :
126+ raise HTTPException (status_code = 500 , detail = "Unable to set memory mode" )
127+
128+
82129@app .post ("/api/conversations/{conversation_id}/message" )
83130async def send_message (conversation_id : str , request : SendMessageRequest ):
84131 """
@@ -117,8 +164,15 @@ async def send_message(conversation_id: str, request: SendMessageRequest):
117164 # Append the new user message (most recent)
118165 messages .append ({"role" : "user" , "content" : request .content })
119166
120- # Run the 3-stage council process with conversation context
121- stage1_results , stage2_results , stage3_result , metadata = await run_full_council (messages )
167+ # Get existing memory summary and pass it into the council
168+ try :
169+ mem = memory .get_memory (conversation_id )
170+ memory_summary = mem .get ("summary" , "" )
171+ except Exception :
172+ memory_summary = ""
173+
174+ # Run the 3-stage council process with conversation context and memory
175+ stage1_results , stage2_results , stage3_result , metadata = await run_full_council (messages , memory_summary )
122176
123177 # Add assistant message with all stages
124178 storage .add_assistant_message (
@@ -128,6 +182,17 @@ async def send_message(conversation_id: str, request: SendMessageRequest):
128182 stage3_result
129183 )
130184
185+ # Update the conversation-level memory in the background (do not block the response)
186+ try :
187+ asyncio .create_task (memory .add_exchange_and_update_summary (
188+ conversation_id ,
189+ request .content ,
190+ stage3_result .get ("response" , "" ) if isinstance (stage3_result , dict ) else ""
191+ ))
192+ except Exception :
193+ # Non-fatal if memory update fails
194+ pass
195+
131196 # Return the complete response with metadata
132197 return {
133198 "stage1" : stage1_results ,
@@ -173,20 +238,27 @@ async def event_generator():
173238 messages .append ({"role" : "assistant" , "content" : assistant_content })
174239 messages .append ({"role" : "user" , "content" : request .content })
175240
241+ # Get existing memory summary and pass it into the council
242+ try :
243+ mem = memory .get_memory (conversation_id )
244+ memory_summary = mem .get ("summary" , "" )
245+ except Exception :
246+ memory_summary = ""
247+
176248 # Stage 1: Collect responses
177249 yield f"data: { json .dumps ({'type' : 'stage1_start' })} \n \n "
178250 stage1_results = await stage1_collect_responses (messages )
179251 yield f"data: { json .dumps ({'type' : 'stage1_complete' , 'data' : stage1_results })} \n \n "
180252
181253 # Stage 2: Collect rankings
182254 yield f"data: { json .dumps ({'type' : 'stage2_start' })} \n \n "
183- stage2_results , label_to_model = await stage2_collect_rankings (messages , stage1_results )
255+ stage2_results , label_to_model = await stage2_collect_rankings (messages , stage1_results , memory_summary )
184256 aggregate_rankings = calculate_aggregate_rankings (stage2_results , label_to_model )
185257 yield f"data: { json .dumps ({'type' : 'stage2_complete' , 'data' : stage2_results , 'metadata' : {'label_to_model' : label_to_model , 'aggregate_rankings' : aggregate_rankings }})} \n \n "
186258
187259 # Stage 3: Synthesize final answer
188260 yield f"data: { json .dumps ({'type' : 'stage3_start' })} \n \n "
189- stage3_result = await stage3_synthesize_final (messages , stage1_results , stage2_results )
261+ stage3_result = await stage3_synthesize_final (messages , stage1_results , stage2_results , memory_summary )
190262 yield f"data: { json .dumps ({'type' : 'stage3_complete' , 'data' : stage3_result })} \n \n "
191263
192264 # Wait for title generation if it was started
@@ -203,6 +275,16 @@ async def event_generator():
203275 stage3_result
204276 )
205277
278+ # Update memory in the background
279+ try :
280+ asyncio .create_task (memory .add_exchange_and_update_summary (
281+ conversation_id ,
282+ request .content ,
283+ stage3_result .get ("response" , "" ) if isinstance (stage3_result , dict ) else ""
284+ ))
285+ except Exception :
286+ pass
287+
206288 # Send completion event
207289 yield f"data: { json .dumps ({'type' : 'complete' })} \n \n "
208290
0 commit comments