letta-nightly 0.8.8.dev20250703104323__py3-none-any.whl → 0.8.8.dev20250703174903__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 (68) hide show
  1. letta/agent.py +1 -0
  2. letta/agents/base_agent.py +8 -2
  3. letta/agents/ephemeral_summary_agent.py +33 -33
  4. letta/agents/letta_agent.py +104 -53
  5. letta/agents/voice_agent.py +2 -1
  6. letta/constants.py +8 -4
  7. letta/functions/function_sets/files.py +22 -7
  8. letta/functions/function_sets/multi_agent.py +34 -0
  9. letta/functions/types.py +1 -1
  10. letta/groups/helpers.py +8 -5
  11. letta/groups/sleeptime_multi_agent_v2.py +20 -15
  12. letta/interface.py +1 -1
  13. letta/interfaces/anthropic_streaming_interface.py +15 -8
  14. letta/interfaces/openai_chat_completions_streaming_interface.py +9 -6
  15. letta/interfaces/openai_streaming_interface.py +17 -11
  16. letta/llm_api/openai_client.py +2 -1
  17. letta/orm/agent.py +1 -0
  18. letta/orm/file.py +8 -2
  19. letta/orm/files_agents.py +36 -11
  20. letta/orm/mcp_server.py +3 -0
  21. letta/orm/source.py +2 -1
  22. letta/orm/step.py +3 -0
  23. letta/prompts/system/memgpt_v2_chat.txt +5 -8
  24. letta/schemas/agent.py +58 -23
  25. letta/schemas/embedding_config.py +3 -2
  26. letta/schemas/enums.py +4 -0
  27. letta/schemas/file.py +1 -0
  28. letta/schemas/letta_stop_reason.py +18 -0
  29. letta/schemas/mcp.py +15 -10
  30. letta/schemas/memory.py +35 -5
  31. letta/schemas/providers.py +11 -0
  32. letta/schemas/step.py +1 -0
  33. letta/schemas/tool.py +2 -1
  34. letta/server/rest_api/routers/v1/agents.py +320 -184
  35. letta/server/rest_api/routers/v1/groups.py +6 -2
  36. letta/server/rest_api/routers/v1/identities.py +6 -2
  37. letta/server/rest_api/routers/v1/jobs.py +49 -1
  38. letta/server/rest_api/routers/v1/sources.py +28 -19
  39. letta/server/rest_api/routers/v1/steps.py +7 -2
  40. letta/server/rest_api/routers/v1/tools.py +40 -9
  41. letta/server/rest_api/streaming_response.py +88 -0
  42. letta/server/server.py +61 -55
  43. letta/services/agent_manager.py +28 -16
  44. letta/services/file_manager.py +58 -9
  45. letta/services/file_processor/chunker/llama_index_chunker.py +2 -0
  46. letta/services/file_processor/embedder/openai_embedder.py +54 -10
  47. letta/services/file_processor/file_processor.py +59 -0
  48. letta/services/file_processor/parser/mistral_parser.py +2 -0
  49. letta/services/files_agents_manager.py +120 -2
  50. letta/services/helpers/agent_manager_helper.py +21 -4
  51. letta/services/job_manager.py +57 -6
  52. letta/services/mcp/base_client.py +1 -0
  53. letta/services/mcp_manager.py +13 -1
  54. letta/services/step_manager.py +14 -5
  55. letta/services/summarizer/summarizer.py +6 -22
  56. letta/services/tool_executor/builtin_tool_executor.py +0 -1
  57. letta/services/tool_executor/files_tool_executor.py +2 -2
  58. letta/services/tool_executor/multi_agent_tool_executor.py +23 -0
  59. letta/services/tool_manager.py +7 -7
  60. letta/settings.py +11 -2
  61. letta/templates/summary_request_text.j2 +19 -0
  62. letta/utils.py +95 -14
  63. {letta_nightly-0.8.8.dev20250703104323.dist-info → letta_nightly-0.8.8.dev20250703174903.dist-info}/METADATA +2 -2
  64. {letta_nightly-0.8.8.dev20250703104323.dist-info → letta_nightly-0.8.8.dev20250703174903.dist-info}/RECORD +68 -67
  65. /letta/{agents/prompts → prompts/system}/summary_system_prompt.txt +0 -0
  66. {letta_nightly-0.8.8.dev20250703104323.dist-info → letta_nightly-0.8.8.dev20250703174903.dist-info}/LICENSE +0 -0
  67. {letta_nightly-0.8.8.dev20250703104323.dist-info → letta_nightly-0.8.8.dev20250703174903.dist-info}/WHEEL +0 -0
  68. {letta_nightly-0.8.8.dev20250703104323.dist-info → letta_nightly-0.8.8.dev20250703174903.dist-info}/entry_points.txt +0 -0
@@ -13,7 +13,8 @@ from sqlalchemy.exc import IntegrityError, OperationalError
13
13
  from starlette.responses import Response, StreamingResponse
14
14
 
15
15
  from letta.agents.letta_agent import LettaAgent
16
- from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, LETTA_MODEL_ENDPOINT
16
+ from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, LETTA_MODEL_ENDPOINT, REDIS_RUN_ID_PREFIX
17
+ from letta.data_sources.redis_client import get_redis_client
17
18
  from letta.groups.sleeptime_multi_agent_v2 import SleeptimeMultiAgentV2
18
19
  from letta.helpers.datetime_helpers import get_utc_timestamp_ns
19
20
  from letta.log import get_logger
@@ -49,26 +50,26 @@ router = APIRouter(prefix="/agents", tags=["agents"])
49
50
  logger = get_logger(__name__)
50
51
 
51
52
 
52
- @router.get("/", response_model=List[AgentState], operation_id="list_agents")
53
+ @router.get("/", response_model=list[AgentState], operation_id="list_agents")
53
54
  async def list_agents(
54
- name: Optional[str] = Query(None, description="Name of the agent"),
55
- tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
55
+ name: str | None = Query(None, description="Name of the agent"),
56
+ tags: list[str] | None = Query(None, description="List of tags to filter agents by"),
56
57
  match_all_tags: bool = Query(
57
58
  False,
58
59
  description="If True, only returns agents that match ALL given tags. Otherwise, return agents that have ANY of the passed-in tags.",
59
60
  ),
60
61
  server: SyncServer = Depends(get_letta_server),
61
- actor_id: Optional[str] = Header(None, alias="user_id"),
62
- before: Optional[str] = Query(None, description="Cursor for pagination"),
63
- after: Optional[str] = Query(None, description="Cursor for pagination"),
64
- limit: Optional[int] = Query(50, description="Limit for pagination"),
65
- query_text: Optional[str] = Query(None, description="Search agents by name"),
66
- project_id: Optional[str] = Query(None, description="Search agents by project ID"),
67
- template_id: Optional[str] = Query(None, description="Search agents by template ID"),
68
- base_template_id: Optional[str] = Query(None, description="Search agents by base template ID"),
69
- identity_id: Optional[str] = Query(None, description="Search agents by identity ID"),
70
- identifier_keys: Optional[List[str]] = Query(None, description="Search agents by identifier keys"),
71
- include_relationships: Optional[List[str]] = Query(
62
+ actor_id: str | None = Header(None, alias="user_id"),
63
+ before: str | None = Query(None, description="Cursor for pagination"),
64
+ after: str | None = Query(None, description="Cursor for pagination"),
65
+ limit: int | None = Query(50, description="Limit for pagination"),
66
+ query_text: str | None = Query(None, description="Search agents by name"),
67
+ project_id: str | None = Query(None, description="Search agents by project ID"),
68
+ template_id: str | None = Query(None, description="Search agents by template ID"),
69
+ base_template_id: str | None = Query(None, description="Search agents by base template ID"),
70
+ identity_id: str | None = Query(None, description="Search agents by identity ID"),
71
+ identifier_keys: list[str] | None = Query(None, description="Search agents by identifier keys"),
72
+ include_relationships: list[str] | None = Query(
72
73
  None,
73
74
  description=(
74
75
  "Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
@@ -80,7 +81,7 @@ async def list_agents(
80
81
  False,
81
82
  description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default)",
82
83
  ),
83
- sort_by: Optional[str] = Query(
84
+ sort_by: str | None = Query(
84
85
  "created_at",
85
86
  description="Field to sort by. Options: 'created_at' (default), 'last_run_completion'",
86
87
  ),
@@ -119,7 +120,7 @@ async def list_agents(
119
120
  @router.get("/count", response_model=int, operation_id="count_agents")
120
121
  async def count_agents(
121
122
  server: SyncServer = Depends(get_letta_server),
122
- actor_id: Optional[str] = Header(None, alias="user_id"),
123
+ actor_id: str | None = Header(None, alias="user_id"),
123
124
  ):
124
125
  """
125
126
  Get the count of all agents associated with a given user.
@@ -139,10 +140,10 @@ class IndentedORJSONResponse(Response):
139
140
  def export_agent_serialized(
140
141
  agent_id: str,
141
142
  server: "SyncServer" = Depends(get_letta_server),
142
- actor_id: Optional[str] = Header(None, alias="user_id"),
143
+ actor_id: str | None = Header(None, alias="user_id"),
143
144
  # do not remove, used to autogeneration of spec
144
145
  # TODO: Think of a better way to export AgentSchema
145
- spec: Optional[AgentSchema] = None,
146
+ spec: AgentSchema | None = None,
146
147
  ) -> JSONResponse:
147
148
  """
148
149
  Export the serialized JSON representation of an agent, formatted with indentation.
@@ -160,13 +161,13 @@ def export_agent_serialized(
160
161
  def import_agent_serialized(
161
162
  file: UploadFile = File(...),
162
163
  server: "SyncServer" = Depends(get_letta_server),
163
- actor_id: Optional[str] = Header(None, alias="user_id"),
164
+ actor_id: str | None = Header(None, alias="user_id"),
164
165
  append_copy_suffix: bool = Query(True, description='If set to True, appends "_copy" to the end of the agent name.'),
165
166
  override_existing_tools: bool = Query(
166
167
  True,
167
168
  description="If set to True, existing tools can get their source code overwritten by the uploaded tool definitions. Note that Letta core tools can never be updated externally.",
168
169
  ),
169
- project_id: Optional[str] = Query(None, description="The project ID to associate the uploaded agent with."),
170
+ project_id: str | None = Query(None, description="The project ID to associate the uploaded agent with."),
170
171
  strip_messages: bool = Query(
171
172
  False,
172
173
  description="If set to True, strips all messages from the agent before importing.",
@@ -198,24 +199,24 @@ def import_agent_serialized(
198
199
  raise HTTPException(status_code=400, detail="Corrupted agent file format.")
199
200
 
200
201
  except ValidationError as e:
201
- raise HTTPException(status_code=422, detail=f"Invalid agent schema: {str(e)}")
202
+ raise HTTPException(status_code=422, detail=f"Invalid agent schema: {e!s}")
202
203
 
203
204
  except IntegrityError as e:
204
- raise HTTPException(status_code=409, detail=f"Database integrity error: {str(e)}")
205
+ raise HTTPException(status_code=409, detail=f"Database integrity error: {e!s}")
205
206
 
206
207
  except OperationalError as e:
207
- raise HTTPException(status_code=503, detail=f"Database connection error. Please try again later: {str(e)}")
208
+ raise HTTPException(status_code=503, detail=f"Database connection error. Please try again later: {e!s}")
208
209
 
209
210
  except Exception as e:
210
211
  traceback.print_exc()
211
- raise HTTPException(status_code=500, detail=f"An unexpected error occurred while uploading the agent: {str(e)}")
212
+ raise HTTPException(status_code=500, detail=f"An unexpected error occurred while uploading the agent: {e!s}")
212
213
 
213
214
 
214
215
  @router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window")
215
216
  async def retrieve_agent_context_window(
216
217
  agent_id: str,
217
218
  server: "SyncServer" = Depends(get_letta_server),
218
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
219
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
219
220
  ):
220
221
  """
221
222
  Retrieve the context window of a specific agent.
@@ -234,15 +235,17 @@ class CreateAgentRequest(CreateAgent):
234
235
  """
235
236
 
236
237
  # Override the user_id field to exclude it from the request body validation
237
- actor_id: Optional[str] = Field(None, exclude=True)
238
+ actor_id: str | None = Field(None, exclude=True)
238
239
 
239
240
 
240
241
  @router.post("/", response_model=AgentState, operation_id="create_agent")
241
242
  async def create_agent(
242
243
  agent: CreateAgentRequest = Body(...),
243
244
  server: "SyncServer" = Depends(get_letta_server),
244
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
245
- x_project: Optional[str] = Header(None, alias="X-Project"), # Only handled by next js middleware
245
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
246
+ x_project: str | None = Header(
247
+ None, alias="X-Project", description="The project slug to associate with the agent (cloud only)."
248
+ ), # Only handled by next js middleware
246
249
  ):
247
250
  """
248
251
  Create a new agent with the specified configuration.
@@ -260,18 +263,18 @@ async def modify_agent(
260
263
  agent_id: str,
261
264
  update_agent: UpdateAgent = Body(...),
262
265
  server: "SyncServer" = Depends(get_letta_server),
263
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
266
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
264
267
  ):
265
268
  """Update an existing agent"""
266
269
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
267
270
  return await server.update_agent_async(agent_id=agent_id, request=update_agent, actor=actor)
268
271
 
269
272
 
270
- @router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="list_agent_tools")
273
+ @router.get("/{agent_id}/tools", response_model=list[Tool], operation_id="list_agent_tools")
271
274
  def list_agent_tools(
272
275
  agent_id: str,
273
276
  server: "SyncServer" = Depends(get_letta_server),
274
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
277
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
275
278
  ):
276
279
  """Get tools from an existing agent"""
277
280
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
@@ -283,7 +286,7 @@ async def attach_tool(
283
286
  agent_id: str,
284
287
  tool_id: str,
285
288
  server: "SyncServer" = Depends(get_letta_server),
286
- actor_id: Optional[str] = Header(None, alias="user_id"),
289
+ actor_id: str | None = Header(None, alias="user_id"),
287
290
  ):
288
291
  """
289
292
  Attach a tool to an agent.
@@ -297,7 +300,7 @@ async def detach_tool(
297
300
  agent_id: str,
298
301
  tool_id: str,
299
302
  server: "SyncServer" = Depends(get_letta_server),
300
- actor_id: Optional[str] = Header(None, alias="user_id"),
303
+ actor_id: str | None = Header(None, alias="user_id"),
301
304
  ):
302
305
  """
303
306
  Detach a tool from an agent.
@@ -311,7 +314,7 @@ async def attach_source(
311
314
  agent_id: str,
312
315
  source_id: str,
313
316
  server: "SyncServer" = Depends(get_letta_server),
314
- actor_id: Optional[str] = Header(None, alias="user_id"),
317
+ actor_id: str | None = Header(None, alias="user_id"),
315
318
  ):
316
319
  """
317
320
  Attach a source to an agent.
@@ -339,7 +342,7 @@ async def detach_source(
339
342
  agent_id: str,
340
343
  source_id: str,
341
344
  server: "SyncServer" = Depends(get_letta_server),
342
- actor_id: Optional[str] = Header(None, alias="user_id"),
345
+ actor_id: str | None = Header(None, alias="user_id"),
343
346
  ):
344
347
  """
345
348
  Detach a source from an agent.
@@ -364,10 +367,27 @@ async def detach_source(
364
367
  return agent_state
365
368
 
366
369
 
370
+ @router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="close_all_open_files")
371
+ async def close_all_open_files(
372
+ agent_id: str,
373
+ server: "SyncServer" = Depends(get_letta_server),
374
+ actor_id: Optional[str] = Header(None, alias="user_id"),
375
+ ):
376
+ """
377
+ Closes all currently open files for a given agent.
378
+
379
+ This endpoint updates the file state for the agent so that no files are marked as open.
380
+ Typically used to reset the working memory view for the agent.
381
+ """
382
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
383
+
384
+ return server.file_agent_manager.close_all_other_files(agent_id=agent_id, keep_file_names=[], actor=actor)
385
+
386
+
367
387
  @router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent")
368
388
  async def retrieve_agent(
369
389
  agent_id: str,
370
- include_relationships: Optional[List[str]] = Query(
390
+ include_relationships: list[str] | None = Query(
371
391
  None,
372
392
  description=(
373
393
  "Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
@@ -376,7 +396,7 @@ async def retrieve_agent(
376
396
  ),
377
397
  ),
378
398
  server: "SyncServer" = Depends(get_letta_server),
379
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
399
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
380
400
  ):
381
401
  """
382
402
  Get the state of the agent.
@@ -393,7 +413,7 @@ async def retrieve_agent(
393
413
  async def delete_agent(
394
414
  agent_id: str,
395
415
  server: "SyncServer" = Depends(get_letta_server),
396
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
416
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
397
417
  ):
398
418
  """
399
419
  Delete an agent.
@@ -406,11 +426,11 @@ async def delete_agent(
406
426
  raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found for user_id={actor.id}.")
407
427
 
408
428
 
409
- @router.get("/{agent_id}/sources", response_model=List[Source], operation_id="list_agent_sources")
429
+ @router.get("/{agent_id}/sources", response_model=list[Source], operation_id="list_agent_sources")
410
430
  async def list_agent_sources(
411
431
  agent_id: str,
412
432
  server: "SyncServer" = Depends(get_letta_server),
413
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
433
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
414
434
  ):
415
435
  """
416
436
  Get the sources associated with an agent.
@@ -424,7 +444,7 @@ async def list_agent_sources(
424
444
  async def retrieve_agent_memory(
425
445
  agent_id: str,
426
446
  server: "SyncServer" = Depends(get_letta_server),
427
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
447
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
428
448
  ):
429
449
  """
430
450
  Retrieve the memory state of a specific agent.
@@ -440,7 +460,7 @@ async def retrieve_block(
440
460
  agent_id: str,
441
461
  block_label: str,
442
462
  server: "SyncServer" = Depends(get_letta_server),
443
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
463
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
444
464
  ):
445
465
  """
446
466
  Retrieve a core memory block from an agent.
@@ -453,11 +473,11 @@ async def retrieve_block(
453
473
  raise HTTPException(status_code=404, detail=str(e))
454
474
 
455
475
 
456
- @router.get("/{agent_id}/core-memory/blocks", response_model=List[Block], operation_id="list_core_memory_blocks")
476
+ @router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks")
457
477
  async def list_blocks(
458
478
  agent_id: str,
459
479
  server: "SyncServer" = Depends(get_letta_server),
460
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
480
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
461
481
  ):
462
482
  """
463
483
  Retrieve the core memory blocks of a specific agent.
@@ -476,7 +496,7 @@ async def modify_block(
476
496
  block_label: str,
477
497
  block_update: BlockUpdate = Body(...),
478
498
  server: "SyncServer" = Depends(get_letta_server),
479
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
499
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
480
500
  ):
481
501
  """
482
502
  Updates a core memory block of an agent.
@@ -498,7 +518,7 @@ async def attach_block(
498
518
  agent_id: str,
499
519
  block_id: str,
500
520
  server: "SyncServer" = Depends(get_letta_server),
501
- actor_id: Optional[str] = Header(None, alias="user_id"),
521
+ actor_id: str | None = Header(None, alias="user_id"),
502
522
  ):
503
523
  """
504
524
  Attach a core memoryblock to an agent.
@@ -512,7 +532,7 @@ async def detach_block(
512
532
  agent_id: str,
513
533
  block_id: str,
514
534
  server: "SyncServer" = Depends(get_letta_server),
515
- actor_id: Optional[str] = Header(None, alias="user_id"),
535
+ actor_id: str | None = Header(None, alias="user_id"),
516
536
  ):
517
537
  """
518
538
  Detach a core memory block from an agent.
@@ -521,18 +541,18 @@ async def detach_block(
521
541
  return await server.agent_manager.detach_block_async(agent_id=agent_id, block_id=block_id, actor=actor)
522
542
 
523
543
 
524
- @router.get("/{agent_id}/archival-memory", response_model=List[Passage], operation_id="list_passages")
544
+ @router.get("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="list_passages")
525
545
  async def list_passages(
526
546
  agent_id: str,
527
547
  server: "SyncServer" = Depends(get_letta_server),
528
- after: Optional[str] = Query(None, description="Unique ID of the memory to start the query range at."),
529
- before: Optional[str] = Query(None, description="Unique ID of the memory to end the query range at."),
530
- limit: Optional[int] = Query(None, description="How many results to include in the response."),
531
- search: Optional[str] = Query(None, description="Search passages by text"),
532
- ascending: Optional[bool] = Query(
548
+ after: str | None = Query(None, description="Unique ID of the memory to start the query range at."),
549
+ before: str | None = Query(None, description="Unique ID of the memory to end the query range at."),
550
+ limit: int | None = Query(None, description="How many results to include in the response."),
551
+ search: str | None = Query(None, description="Search passages by text"),
552
+ ascending: bool | None = Query(
533
553
  True, description="Whether to sort passages oldest to newest (True, default) or newest to oldest (False)"
534
554
  ),
535
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
555
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
536
556
  ):
537
557
  """
538
558
  Retrieve the memories in an agent's archival memory store (paginated query).
@@ -550,12 +570,12 @@ async def list_passages(
550
570
  )
551
571
 
552
572
 
553
- @router.post("/{agent_id}/archival-memory", response_model=List[Passage], operation_id="create_passage")
573
+ @router.post("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="create_passage")
554
574
  async def create_passage(
555
575
  agent_id: str,
556
576
  request: CreateArchivalMemory = Body(...),
557
577
  server: "SyncServer" = Depends(get_letta_server),
558
- actor_id: Optional[str] = Header(None, alias="user_id"),
578
+ actor_id: str | None = Header(None, alias="user_id"),
559
579
  ):
560
580
  """
561
581
  Insert a memory into an agent's archival memory store.
@@ -565,13 +585,13 @@ async def create_passage(
565
585
  return await server.insert_archival_memory_async(agent_id=agent_id, memory_contents=request.text, actor=actor)
566
586
 
567
587
 
568
- @router.patch("/{agent_id}/archival-memory/{memory_id}", response_model=List[Passage], operation_id="modify_passage")
588
+ @router.patch("/{agent_id}/archival-memory/{memory_id}", response_model=list[Passage], operation_id="modify_passage")
569
589
  def modify_passage(
570
590
  agent_id: str,
571
591
  memory_id: str,
572
592
  passage: PassageUpdate = Body(...),
573
593
  server: "SyncServer" = Depends(get_letta_server),
574
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
594
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
575
595
  ):
576
596
  """
577
597
  Modify a memory in the agent's archival memory store.
@@ -588,7 +608,7 @@ async def delete_passage(
588
608
  memory_id: str,
589
609
  # memory_id: str = Query(..., description="Unique ID of the memory to be deleted."),
590
610
  server: "SyncServer" = Depends(get_letta_server),
591
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
611
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
592
612
  ):
593
613
  """
594
614
  Delete a memory from an agent's archival memory store.
@@ -600,7 +620,7 @@ async def delete_passage(
600
620
 
601
621
 
602
622
  AgentMessagesResponse = Annotated[
603
- List[LettaMessageUnion], Field(json_schema_extra={"type": "array", "items": {"$ref": "#/components/schemas/LettaMessageUnion"}})
623
+ list[LettaMessageUnion], Field(json_schema_extra={"type": "array", "items": {"$ref": "#/components/schemas/LettaMessageUnion"}})
604
624
  ]
605
625
 
606
626
 
@@ -608,14 +628,14 @@ AgentMessagesResponse = Annotated[
608
628
  async def list_messages(
609
629
  agent_id: str,
610
630
  server: "SyncServer" = Depends(get_letta_server),
611
- after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."),
612
- before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
631
+ after: str | None = Query(None, description="Message after which to retrieve the returned messages."),
632
+ before: str | None = Query(None, description="Message before which to retrieve the returned messages."),
613
633
  limit: int = Query(10, description="Maximum number of messages to retrieve."),
614
- group_id: Optional[str] = Query(None, description="Group ID to filter messages by."),
634
+ group_id: str | None = Query(None, description="Group ID to filter messages by."),
615
635
  use_assistant_message: bool = Query(True, description="Whether to use assistant messages"),
616
636
  assistant_message_tool_name: str = Query(DEFAULT_MESSAGE_TOOL, description="The name of the designated message tool."),
617
637
  assistant_message_tool_kwarg: str = Query(DEFAULT_MESSAGE_TOOL_KWARG, description="The name of the message argument."),
618
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
638
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
619
639
  ):
620
640
  """
621
641
  Retrieve message history for an agent.
@@ -643,7 +663,7 @@ def modify_message(
643
663
  message_id: str,
644
664
  request: LettaMessageUpdateUnion = Body(...),
645
665
  server: "SyncServer" = Depends(get_letta_server),
646
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
666
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
647
667
  ):
648
668
  """
649
669
  Update the details of a message associated with an agent.
@@ -653,6 +673,7 @@ def modify_message(
653
673
  return server.message_manager.update_message_by_letta_message(message_id=message_id, letta_message_update=request, actor=actor)
654
674
 
655
675
 
676
+ # noinspection PyInconsistentReturns
656
677
  @router.post(
657
678
  "/{agent_id}/messages",
658
679
  response_model=LettaResponse,
@@ -663,7 +684,7 @@ async def send_message(
663
684
  request_obj: Request, # FastAPI Request
664
685
  server: SyncServer = Depends(get_letta_server),
665
686
  request: LettaRequest = Body(...),
666
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
687
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
667
688
  ):
668
689
  """
669
690
  Process a user message and return the agent's response.
@@ -678,55 +699,95 @@ async def send_message(
678
699
  agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
679
700
  model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
680
701
 
681
- if agent_eligible and model_compatible:
682
- if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
683
- agent_loop = SleeptimeMultiAgentV2(
684
- agent_id=agent_id,
685
- message_manager=server.message_manager,
686
- agent_manager=server.agent_manager,
687
- block_manager=server.block_manager,
688
- passage_manager=server.passage_manager,
689
- group_manager=server.group_manager,
690
- job_manager=server.job_manager,
691
- actor=actor,
692
- group=agent.multi_agent_group,
702
+ # Create a new run for execution tracking
703
+ job_status = JobStatus.created
704
+ run = await server.job_manager.create_job_async(
705
+ pydantic_job=Run(
706
+ user_id=actor.id,
707
+ status=job_status,
708
+ metadata={
709
+ "job_type": "send_message",
710
+ "agent_id": agent_id,
711
+ },
712
+ request_config=LettaRequestConfig(
713
+ use_assistant_message=request.use_assistant_message,
714
+ assistant_message_tool_name=request.assistant_message_tool_name,
715
+ assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
716
+ include_return_message_types=request.include_return_message_types,
717
+ ),
718
+ ),
719
+ actor=actor,
720
+ )
721
+ job_update_metadata = None
722
+ # TODO (cliandy): clean this up
723
+ redis_client = await get_redis_client()
724
+ await redis_client.set(f"{REDIS_RUN_ID_PREFIX}:{agent_id}", run.id)
725
+
726
+ try:
727
+ if agent_eligible and model_compatible:
728
+ if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
729
+ agent_loop = SleeptimeMultiAgentV2(
730
+ agent_id=agent_id,
731
+ message_manager=server.message_manager,
732
+ agent_manager=server.agent_manager,
733
+ block_manager=server.block_manager,
734
+ passage_manager=server.passage_manager,
735
+ group_manager=server.group_manager,
736
+ job_manager=server.job_manager,
737
+ actor=actor,
738
+ group=agent.multi_agent_group,
739
+ current_run_id=run.id,
740
+ )
741
+ else:
742
+ agent_loop = LettaAgent(
743
+ agent_id=agent_id,
744
+ message_manager=server.message_manager,
745
+ agent_manager=server.agent_manager,
746
+ block_manager=server.block_manager,
747
+ job_manager=server.job_manager,
748
+ passage_manager=server.passage_manager,
749
+ actor=actor,
750
+ step_manager=server.step_manager,
751
+ telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
752
+ current_run_id=run.id,
753
+ )
754
+
755
+ result = await agent_loop.step(
756
+ request.messages,
757
+ max_steps=request.max_steps,
758
+ use_assistant_message=request.use_assistant_message,
759
+ request_start_timestamp_ns=request_start_timestamp_ns,
760
+ include_return_message_types=request.include_return_message_types,
693
761
  )
694
762
  else:
695
- agent_loop = LettaAgent(
763
+ result = await server.send_message_to_agent(
696
764
  agent_id=agent_id,
697
- message_manager=server.message_manager,
698
- agent_manager=server.agent_manager,
699
- block_manager=server.block_manager,
700
- job_manager=server.job_manager,
701
- passage_manager=server.passage_manager,
702
765
  actor=actor,
703
- step_manager=server.step_manager,
704
- telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
766
+ input_messages=request.messages,
767
+ stream_steps=False,
768
+ stream_tokens=False,
769
+ # Support for AssistantMessage
770
+ use_assistant_message=request.use_assistant_message,
771
+ assistant_message_tool_name=request.assistant_message_tool_name,
772
+ assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
773
+ include_return_message_types=request.include_return_message_types,
705
774
  )
706
-
707
- result = await agent_loop.step(
708
- request.messages,
709
- max_steps=request.max_steps,
710
- use_assistant_message=request.use_assistant_message,
711
- request_start_timestamp_ns=request_start_timestamp_ns,
712
- include_return_message_types=request.include_return_message_types,
713
- )
714
- else:
715
- result = await server.send_message_to_agent(
716
- agent_id=agent_id,
775
+ job_status = result.stop_reason.stop_reason.run_status
776
+ return result
777
+ except Exception as e:
778
+ job_update_metadata = {"error": str(e)}
779
+ job_status = JobStatus.failed
780
+ raise
781
+ finally:
782
+ await server.job_manager.safe_update_job_status_async(
783
+ job_id=run.id,
784
+ new_status=job_status,
717
785
  actor=actor,
718
- input_messages=request.messages,
719
- stream_steps=False,
720
- stream_tokens=False,
721
- # Support for AssistantMessage
722
- use_assistant_message=request.use_assistant_message,
723
- assistant_message_tool_name=request.assistant_message_tool_name,
724
- assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
725
- include_return_message_types=request.include_return_message_types,
786
+ metadata=job_update_metadata,
726
787
  )
727
- return result
728
788
 
729
789
 
790
+ # noinspection PyInconsistentReturns
730
791
  @router.post(
731
792
  "/{agent_id}/messages/stream",
732
793
  response_model=None,
@@ -745,7 +806,7 @@ async def send_message_streaming(
745
806
  request_obj: Request, # FastAPI Request
746
807
  server: SyncServer = Depends(get_letta_server),
747
808
  request: LettaStreamingRequest = Body(...),
748
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
809
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
749
810
  ) -> StreamingResponse | LettaResponse:
750
811
  """
751
812
  Process a user message and return the agent's response.
@@ -761,88 +822,160 @@ async def send_message_streaming(
761
822
  agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
762
823
  model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "together", "google_ai", "google_vertex", "bedrock"]
763
824
  model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "bedrock"]
764
- not_letta_endpoint = LETTA_MODEL_ENDPOINT != agent.llm_config.model_endpoint
825
+ not_letta_endpoint = agent.llm_config.model_endpoint != LETTA_MODEL_ENDPOINT
826
+
827
+ # Create a new job for execution tracking
828
+ job_status = JobStatus.created
829
+ run = await server.job_manager.create_job_async(
830
+ pydantic_job=Run(
831
+ user_id=actor.id,
832
+ status=job_status,
833
+ metadata={
834
+ "job_type": "send_message_streaming",
835
+ "agent_id": agent_id,
836
+ },
837
+ request_config=LettaRequestConfig(
838
+ use_assistant_message=request.use_assistant_message,
839
+ assistant_message_tool_name=request.assistant_message_tool_name,
840
+ assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
841
+ include_return_message_types=request.include_return_message_types,
842
+ ),
843
+ ),
844
+ actor=actor,
845
+ )
765
846
 
766
- if agent_eligible and model_compatible:
767
- if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
768
- agent_loop = SleeptimeMultiAgentV2(
769
- agent_id=agent_id,
770
- message_manager=server.message_manager,
771
- agent_manager=server.agent_manager,
772
- block_manager=server.block_manager,
773
- passage_manager=server.passage_manager,
774
- group_manager=server.group_manager,
775
- job_manager=server.job_manager,
776
- actor=actor,
777
- step_manager=server.step_manager,
778
- telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
779
- group=agent.multi_agent_group,
780
- )
847
+ job_update_metadata = None
848
+ # TODO (cliandy): clean this up
849
+ redis_client = await get_redis_client()
850
+ await redis_client.set(f"{REDIS_RUN_ID_PREFIX}:{agent_id}", run.id)
851
+
852
+ try:
853
+ if agent_eligible and model_compatible:
854
+ if agent.enable_sleeptime and agent.agent_type != AgentType.voice_convo_agent:
855
+ agent_loop = SleeptimeMultiAgentV2(
856
+ agent_id=agent_id,
857
+ message_manager=server.message_manager,
858
+ agent_manager=server.agent_manager,
859
+ block_manager=server.block_manager,
860
+ passage_manager=server.passage_manager,
861
+ group_manager=server.group_manager,
862
+ job_manager=server.job_manager,
863
+ actor=actor,
864
+ step_manager=server.step_manager,
865
+ telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
866
+ group=agent.multi_agent_group,
867
+ current_run_id=run.id,
868
+ )
869
+ else:
870
+ agent_loop = LettaAgent(
871
+ agent_id=agent_id,
872
+ message_manager=server.message_manager,
873
+ agent_manager=server.agent_manager,
874
+ block_manager=server.block_manager,
875
+ job_manager=server.job_manager,
876
+ passage_manager=server.passage_manager,
877
+ actor=actor,
878
+ step_manager=server.step_manager,
879
+ telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
880
+ current_run_id=run.id,
881
+ )
882
+ from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode
883
+
884
+ if request.stream_tokens and model_compatible_token_streaming and not_letta_endpoint:
885
+ result = StreamingResponseWithStatusCode(
886
+ agent_loop.step_stream(
887
+ input_messages=request.messages,
888
+ max_steps=request.max_steps,
889
+ use_assistant_message=request.use_assistant_message,
890
+ request_start_timestamp_ns=request_start_timestamp_ns,
891
+ include_return_message_types=request.include_return_message_types,
892
+ ),
893
+ media_type="text/event-stream",
894
+ )
895
+ else:
896
+ result = StreamingResponseWithStatusCode(
897
+ agent_loop.step_stream_no_tokens(
898
+ request.messages,
899
+ max_steps=request.max_steps,
900
+ use_assistant_message=request.use_assistant_message,
901
+ request_start_timestamp_ns=request_start_timestamp_ns,
902
+ include_return_message_types=request.include_return_message_types,
903
+ ),
904
+ media_type="text/event-stream",
905
+ )
781
906
  else:
782
- agent_loop = LettaAgent(
907
+ result = await server.send_message_to_agent(
783
908
  agent_id=agent_id,
784
- message_manager=server.message_manager,
785
- agent_manager=server.agent_manager,
786
- block_manager=server.block_manager,
787
- job_manager=server.job_manager,
788
- passage_manager=server.passage_manager,
789
909
  actor=actor,
790
- step_manager=server.step_manager,
791
- telemetry_manager=server.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
792
- )
793
- from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode
794
-
795
- if request.stream_tokens and model_compatible_token_streaming and not_letta_endpoint:
796
- result = StreamingResponseWithStatusCode(
797
- agent_loop.step_stream(
798
- input_messages=request.messages,
799
- max_steps=request.max_steps,
800
- use_assistant_message=request.use_assistant_message,
801
- request_start_timestamp_ns=request_start_timestamp_ns,
802
- include_return_message_types=request.include_return_message_types,
803
- ),
804
- media_type="text/event-stream",
805
- )
806
- else:
807
- result = StreamingResponseWithStatusCode(
808
- agent_loop.step_stream_no_tokens(
809
- request.messages,
810
- max_steps=request.max_steps,
811
- use_assistant_message=request.use_assistant_message,
812
- request_start_timestamp_ns=request_start_timestamp_ns,
813
- include_return_message_types=request.include_return_message_types,
814
- ),
815
- media_type="text/event-stream",
910
+ input_messages=request.messages,
911
+ stream_steps=True,
912
+ stream_tokens=request.stream_tokens,
913
+ # Support for AssistantMessage
914
+ use_assistant_message=request.use_assistant_message,
915
+ assistant_message_tool_name=request.assistant_message_tool_name,
916
+ assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
917
+ request_start_timestamp_ns=request_start_timestamp_ns,
918
+ include_return_message_types=request.include_return_message_types,
816
919
  )
817
- else:
818
- result = await server.send_message_to_agent(
819
- agent_id=agent_id,
920
+ job_status = JobStatus.running
921
+ return result
922
+ except Exception as e:
923
+ job_update_metadata = {"error": str(e)}
924
+ job_status = JobStatus.failed
925
+ raise
926
+ finally:
927
+ await server.job_manager.safe_update_job_status_async(
928
+ job_id=run.id,
929
+ new_status=job_status,
820
930
  actor=actor,
821
- input_messages=request.messages,
822
- stream_steps=True,
823
- stream_tokens=request.stream_tokens,
824
- # Support for AssistantMessage
825
- use_assistant_message=request.use_assistant_message,
826
- assistant_message_tool_name=request.assistant_message_tool_name,
827
- assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
828
- request_start_timestamp_ns=request_start_timestamp_ns,
829
- include_return_message_types=request.include_return_message_types,
931
+ metadata=job_update_metadata,
830
932
  )
831
933
 
832
- return result
833
934
 
935
+ @router.post("/{agent_id}/messages/cancel", operation_id="cancel_agent_run")
936
+ async def cancel_agent_run(
937
+ agent_id: str,
938
+ run_ids: list[str] | None = None,
939
+ server: SyncServer = Depends(get_letta_server),
940
+ actor_id: str | None = Header(None, alias="user_id"),
941
+ ) -> dict:
942
+ """
943
+ Cancel runs associated with an agent. If run_ids are passed in, cancel those in particular.
834
944
 
835
- async def process_message_background(
836
- job_id: str,
945
+ Note to cancel active runs associated with an agent, redis is required.
946
+ """
947
+
948
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
949
+ if not run_ids:
950
+ redis_client = await get_redis_client()
951
+ run_id = await redis_client.get(f"{REDIS_RUN_ID_PREFIX}:{agent_id}")
952
+ if run_id is None:
953
+ logger.warning("Cannot find run associated with agent to cancel.")
954
+ return {}
955
+ run_ids = [run_id]
956
+
957
+ results = {}
958
+ for run_id in run_ids:
959
+ success = await server.job_manager.safe_update_job_status_async(
960
+ job_id=run_id,
961
+ new_status=JobStatus.cancelled,
962
+ actor=actor,
963
+ )
964
+ results[run_id] = "cancelled" if success else "failed"
965
+ return results
966
+
967
+
968
+ async def _process_message_background(
969
+ run_id: str,
837
970
  server: SyncServer,
838
971
  actor: User,
839
972
  agent_id: str,
840
- messages: List[MessageCreate],
973
+ messages: list[MessageCreate],
841
974
  use_assistant_message: bool,
842
975
  assistant_message_tool_name: str,
843
976
  assistant_message_tool_kwarg: str,
844
977
  max_steps: int = DEFAULT_MAX_STEPS,
845
- include_return_message_types: Optional[List[MessageType]] = None,
978
+ include_return_message_types: list[MessageType] | None = None,
846
979
  ) -> None:
847
980
  """Background task to process the message and update job status."""
848
981
  request_start_timestamp_ns = get_utc_timestamp_ns()
@@ -886,7 +1019,7 @@ async def process_message_background(
886
1019
  result = await agent_loop.step(
887
1020
  messages,
888
1021
  max_steps=max_steps,
889
- run_id=job_id,
1022
+ run_id=run_id,
890
1023
  use_assistant_message=use_assistant_message,
891
1024
  request_start_timestamp_ns=request_start_timestamp_ns,
892
1025
  include_return_message_types=include_return_message_types,
@@ -898,7 +1031,7 @@ async def process_message_background(
898
1031
  input_messages=messages,
899
1032
  stream_steps=False,
900
1033
  stream_tokens=False,
901
- metadata={"job_id": job_id},
1034
+ metadata={"job_id": run_id},
902
1035
  # Support for AssistantMessage
903
1036
  use_assistant_message=use_assistant_message,
904
1037
  assistant_message_tool_name=assistant_message_tool_name,
@@ -911,7 +1044,7 @@ async def process_message_background(
911
1044
  completed_at=datetime.now(timezone.utc),
912
1045
  metadata={"result": result.model_dump(mode="json")},
913
1046
  )
914
- await server.job_manager.update_job_by_id_async(job_id=job_id, job_update=job_update, actor=actor)
1047
+ await server.job_manager.update_job_by_id_async(job_id=run_id, job_update=job_update, actor=actor)
915
1048
 
916
1049
  except Exception as e:
917
1050
  # Update job status to failed
@@ -932,11 +1065,14 @@ async def send_message_async(
932
1065
  agent_id: str,
933
1066
  server: SyncServer = Depends(get_letta_server),
934
1067
  request: LettaAsyncRequest = Body(...),
935
- actor_id: Optional[str] = Header(None, alias="user_id"),
1068
+ actor_id: str | None = Header(None, alias="user_id"),
936
1069
  ):
937
1070
  """
938
1071
  Asynchronously process a user message and return a run object.
939
1072
  The actual processing happens in the background, and the status can be checked using the run ID.
1073
+
1074
+ This is "asynchronous" in the sense that it's a background job and explicitly must be fetched by the run ID.
1075
+ This is more like `send_message_job`
940
1076
  """
941
1077
  MetricRegistry().user_message_counter.add(1, get_ctx_attributes())
942
1078
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
@@ -961,8 +1097,8 @@ async def send_message_async(
961
1097
 
962
1098
  # Create asyncio task for background processing
963
1099
  asyncio.create_task(
964
- process_message_background(
965
- job_id=run.id,
1100
+ _process_message_background(
1101
+ run_id=run.id,
966
1102
  server=server,
967
1103
  actor=actor,
968
1104
  agent_id=agent_id,
@@ -983,7 +1119,7 @@ async def reset_messages(
983
1119
  agent_id: str,
984
1120
  add_default_initial_messages: bool = Query(default=False, description="If true, adds the default initial messages after resetting."),
985
1121
  server: "SyncServer" = Depends(get_letta_server),
986
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
1122
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
987
1123
  ):
988
1124
  """Resets the messages for an agent"""
989
1125
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
@@ -992,12 +1128,12 @@ async def reset_messages(
992
1128
  )
993
1129
 
994
1130
 
995
- @router.get("/{agent_id}/groups", response_model=List[Group], operation_id="list_agent_groups")
1131
+ @router.get("/{agent_id}/groups", response_model=list[Group], operation_id="list_agent_groups")
996
1132
  async def list_agent_groups(
997
1133
  agent_id: str,
998
- manager_type: Optional[str] = Query(None, description="Manager type to filter groups by"),
1134
+ manager_type: str | None = Query(None, description="Manager type to filter groups by"),
999
1135
  server: "SyncServer" = Depends(get_letta_server),
1000
- actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
1136
+ actor_id: str | None = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
1001
1137
  ):
1002
1138
  """Lists the groups for an agent"""
1003
1139
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
@@ -1011,7 +1147,7 @@ async def summarize_agent_conversation(
1011
1147
  request_obj: Request, # FastAPI Request
1012
1148
  max_message_length: int = Query(..., description="Maximum number of messages to retain after summarization."),
1013
1149
  server: SyncServer = Depends(get_letta_server),
1014
- actor_id: Optional[str] = Header(None, alias="user_id"),
1150
+ actor_id: str | None = Header(None, alias="user_id"),
1015
1151
  ):
1016
1152
  """
1017
1153
  Summarize an agent's conversation history to a target message length.