Commit Graph

7037 Commits

Author SHA1 Message Date
Kian Jones
c1a02fa180 feat: add metadata-only provider trace storage option (#9155)
* feat: add metadata-only provider trace storage option

Add support for writing provider traces to a lightweight metadata-only
table (~1.5GB) instead of the full table (~725GB) since request/response
JSON is now stored in GCS.

- Add `LETTA_TELEMETRY_PROVIDER_TRACE_PG_METADATA_ONLY` setting
- Create `provider_trace_metadata` table via alembic migration
- Conditionally write to new table when flag is enabled
- Include backfill script for migrating existing data

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API spec and SDK

* fix: use composite PK (created_at, id) for provider_trace_metadata

Aligns with GCS partitioning structure (raw/date=YYYY-MM-DD/{id}.json.gz)
and enables efficient date-range queries via the B-tree index.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* ammendments

* fix: add bulk data copy to migration

Copy existing provider_traces metadata in-migration instead of separate
backfill script. Creates indexes after bulk insert for better performance.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: remove data copy from migration, create empty table only

Old data stays in provider_traces, new writes go to provider_trace_metadata
when flag is enabled. Full traces are in GCS anyway.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: address PR comments

- Remove GCS mention from ProviderTraceMetadata docstring
- Move metadata object creation outside session context

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: reads always use full provider_traces table

The metadata_only flag should only control writes. Reads always go to
the full table to avoid returning ProviderTraceMetadata where
ProviderTrace is expected.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* feat: enable metadata-only provider trace writes in prod

Add LETTA_TELEMETRY_PROVIDER_TRACE_PG_METADATA_ONLY=true to all
Helm values (memgpt-server and lettuce-py, prod and dev).

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
amysguan
69cad47e6a fix: respect enable_reasoner setting from .af imports instead of falling back to model defaults (#9163)
Co-authored-by: Amy Guan <amy@letta.com>
2026-01-29 12:44:04 -08:00
Ari Webb
a798cc90c4 fix: openrouter provider (#9166)
* fix: openrouter provider

* just stage publish api

* web openapi
2026-01-29 12:44:04 -08:00
cthomas
59ffaec8f4 fix: revert test comments (#9161) 2026-01-29 12:44:04 -08:00
Ari Webb
9ce1249738 feat: openrouter byok (#9148)
* feat: openrouter byok

* new client is unnecessary

* revert json diffs
2026-01-29 12:44:04 -08:00
cthomas
d992aa0df4 fix: non-streaming conversation messages endpoint (#9159)
* fix: non-streaming conversation messages endpoint

**Problems:**
1. `AssertionError: run_id is required when enforce_run_id_set is True`
   - Non-streaming path didn't create a run before calling `step()`

2. `ResponseValidationError: Unable to extract tag using discriminator 'message_type'`
   - `response_model=LettaStreamingResponse` but non-streaming returns `LettaResponse`

**Fixes:**
1. Add run creation before calling `step()` (mirrors agents endpoint)
2. Set run_id in Redis for cancellation support
3. Pass `run_id` to `step()`
4. Change `response_model` from `LettaStreamingResponse` to `LettaResponse`
   (streaming returns `StreamingResponse` which bypasses response_model validation)

**Test:**
Added `test_conversation_non_streaming_raw_http` to verify the fix.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* api sync

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
dd7e28fae6 fix: return 400 instead of 500 for image fetch errors (#9157)
**Problem:**
When a user sends a message with an image URL that times out or fails to
fetch, the server returns a 500 Internal Server Error with a generic message.
This is confusing because the user doesn't know what went wrong.

**Root Cause:**
`LettaImageFetchError` was not registered in the exception handlers, so it
bubbled up as an unhandled exception.

**Fix:**
Register `LettaImageFetchError` with the 400 Bad Request handler. Now users
get a clear error message like:
```
Failed to fetch image from https://...: Timeout after 2 attempts
```

This tells users exactly what went wrong so they can retry with a different
image or verify the URL is accessible.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
372c8dcc85 fix: add conversation_id support to LettaAgentV3 constructor (#9156)
**Error:**
```
TypeError: LettaAgentV2.__init__() got an unexpected keyword argument 'conversation_id'
```

**Trace:** https://letta.grafana.net/goto/afbk4da3fuxhcf?orgId=stacks-1189126

**Problem:**
The `POST /v1/conversations/{conversation_id}/compact` endpoint was failing
because `LettaAgentV3` inherits from `LettaAgentV2` without overriding
`__init__`, so passing `conversation_id` to the constructor failed.

**Fix:**
1. Add `__init__` to `LettaAgentV3` that accepts optional `conversation_id`
2. Remove redundant `conversation_id` param from `_checkpoint_messages` -
   use `self.conversation_id` consistently instead
3. Clean up internal callers that were passing `conversation_id=self.conversation_id`

Backward compatible - existing code creating `LettaAgentV3(agent_state, actor)`
still works since `conversation_id` defaults to `None`.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Shubham Naik
bb2145c24c connections (#9113)
* chore: release code

* chore: release code

* chore: release code

* chore: release code

* chore: release code

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: remote

* chore: support  multi project chat
2026-01-29 12:44:04 -08:00
Kian Jones
45c0a4cd0d feat: add ID format validation to batch request schema (#9154)
* feat: add ID format validation to batch request schema

Add ID format validation to LettaBatchRequest using existing validator
types from letta.validators.

Changes:
- LettaBatchRequest.agent_id: str → AgentId

This ensures malformed agent IDs in batch requests are rejected with 422
validation errors instead of causing 500 database errors.

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API spec and SDK

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Kian Jones
eaaca141f7 feat: add ID format validation to identity schemas (#9153)
* feat: add ID format validation to identity schemas

Add ID format validation to IdentityCreate, IdentityUpsert, and IdentityUpdate
schemas using existing validator types from letta.validators.

Changes:
- agent_ids: Optional[List[str]] → Optional[List[AgentId]]
- block_ids: Optional[List[str]] → Optional[List[BlockId]]

This ensures malformed IDs are rejected with 422 validation errors instead
of causing 500 database errors.

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API spec and SDK

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Kian Jones
dd8be95142 feat: add ID format validation to group schemas (#9152)
* feat: add ID format validation to group schemas

Add ID format validation to GroupCreate, GroupUpdate, and manager config
schemas using existing validator types from letta.validators.

Changes:
- GroupCreate/GroupUpdate: agent_ids → List[AgentId], shared_block_ids → List[BlockId]
- SupervisorManager, DynamicManager, SleeptimeManager, VoiceSleeptimeManager:
  manager_agent_id → AgentId
- Update variants: manager_agent_id → Optional[AgentId]

This ensures malformed IDs are rejected with 422 validation errors instead
of causing 500 database errors.

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API spec and SDK

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Kian Jones
42b1e741dc fix: prevent duplicate block attachment in sleeptime agents (#9150)
Check if a block with the same label already exists before attaching
to sleeptime agents. This prevents UniqueConstraintViolationError on
the (agent_id, block_label) constraint when the same block is attached
multiple times due to race conditions.

🤖 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Ari Webb
04e6d668ec fix: make it so sync updates model_endpoint info (#9138) 2026-01-29 12:44:04 -08:00
cthomas
6f8f227e64 fix: improve approval retry idempotency check for server-side tool calls (#9136)
**Problem:**
When retrying an approval response, the idempotency check only looked at
the last message. If the approved tool triggered server-side tool calls
(e.g., `memory`), those tool returns would be the last message, causing
the idempotency check to fail with:
"Cannot process approval response: No tool call is currently awaiting approval."

**Root Cause:**
The check at line 249 only validated `current_in_context_messages[-1]`,
but server-side tool calls can add additional tool return messages after
the original approved tool's return.

**Fix:**
Search the last 10 messages (instead of just the last one) for a tool
return matching the approval's tool_call_ids. This handles the case where
server-side tool calls happen after the approved tool executes, while
keeping the search bounded and efficient.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Charles Packer
0c016d3ee3 fix(core): correct cursor direction for descending pagination in list_agent_blocks_async (#9122)
The cursor-based pagination was not accounting for sort order. When using
descending order (the default), "after cursor X" should return items with
id < X (items that come after X in the descending result set), but the code
was using id > X which caused infinite loops in clients iterating through pages.

This fix adjusts the cursor comparison based on the sort order:
- ascending: after=id > X, before=id < X
- descending: after=id < X, before=id > X

Note: Other pagination methods (list_agent_sources_async, list_agent_tools_async,
list_agent_groups_async) may have the same issue and should be audited.

🐾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Kian Jones
34eed72150 feat: add user id validation (#9128)
* add user id validation

* relax conversation id check to allow default while I'm here

* fix annotation validation

* -api changes
2026-01-29 12:44:04 -08:00
cthomas
5530d541f1 fix: skip default actor init on app start (#9125)
* fix: skip default actor init on app start

* opposite check
2026-01-29 12:44:04 -08:00
cthomas
674bfe95ec fix: no default actor setting check (#9124)
* fix: no default actor setting check

* prevent default actor creation in fallback
2026-01-29 12:44:04 -08:00
Kian Jones
0099a95a43 fix(sec): first pass of ensuring actor id is required everywhere (#9126)
first pass of ensuring actor id is required
2026-01-29 12:44:04 -08:00
Sarah Wooders
b34ad43691 feat: add minimax byok to ui (#9101)
* fix: patch minimax

* feat: add frontend changes for minimax

* add logo, fix backend

* better check for is minimax

* more references fixed for minimax

* start revering unnecessary changes

* revert backend changes, just ui

* fix minimax fully

* fix test

* add key to deploy action

---------

Co-authored-by: Ari Webb <ari@letta.com>
Co-authored-by: Ari Webb <arijwebb@gmail.com>
2026-01-29 12:44:04 -08:00
github-actions[bot]
62a00cc672 fix: remove deprecation from agent passages endpoints (#9117)
* fix: remove deprecation from agent passages endpoints

The client.agent.passages endpoints (list, create, search, delete) were
incorrectly marked as deprecated. This would break significant amounts
of user code and negatively impact developer experience.

Fixes #9116

Co-authored-by: Ari Webb <AriWebb@users.noreply.github.com>

* stage publish api

---------

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Ari Webb <AriWebb@users.noreply.github.com>
Co-authored-by: Ari Webb <ari@letta.com>
2026-01-29 12:44:04 -08:00
Shelley Pham
5dc70e48eb Shelley/let 7218 editor should be compatible with typescript [LET-7218] (#9087)
* fix python icon not showing up

* make typescript compatible for updating tools in typescript

* Update flags.ts

* display tools properly in navigation

* add default json schema to newly created tools

* add typescript to code editor

* make editor typescript compatible

* Update ToolsEditor.tsx

* typescript ocmpatible editor

* sandbox stuff

* update breadcrumb icon

* pass in source type to tool simulator

* undo

* Update tool-editor.cy.ts
2026-01-29 12:44:04 -08:00
Charles Packer
e0d9238bb6 fix(core): add check_api_key method to MiniMaxProvider (#9112)
The MiniMaxProvider class was missing a check_api_key() implementation,
causing /v1/providers/check to return a 500 error when validating
MiniMax API keys. The base Provider class raises NotImplementedError.

This adds check_api_key() using the Anthropic client (since MiniMax uses
an Anthropic-compatible API), following the same pattern as AnthropicProvider.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Shubham Naik
8f0ac630ab chore: nw [LET-6982] (#9081)
* chore: nw

* chore: more

* feat: redesign details view

* feat: redesign details view

* chore: poll every hour
2026-01-29 12:44:04 -08:00
Sarah Wooders
fb69a96cd6 fix: patch minimax (#9099) 2026-01-29 12:44:04 -08:00
Sarah Wooders
adab8cd9b5 feat: add MiniMax provider support (#9095)
* feat: add MiniMax provider support

Add MiniMax as a new LLM provider using their Anthropic-compatible API.

Key implementation details:
- Uses standard messages API (not beta) - MiniMax supports thinking blocks natively
- Base URL: https://api.minimax.io/anthropic
- Models: MiniMax-M2.1, MiniMax-M2.1-lightning, MiniMax-M2 (all 200K context, 128K output)
- Temperature clamped to valid range (0.0, 1.0]
- All M2.x models treated as reasoning models (support interleaved thinking)

Files added:
- letta/schemas/providers/minimax.py - MiniMax provider schema
- letta/llm_api/minimax_client.py - Client extending AnthropicClient
- tests/test_minimax_client.py - Unit tests (13 tests)
- tests/model_settings/minimax-m2.1.json - Integration test config

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API spec with MiniMax provider

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: use MiniMax-M2.1-lightning for CI tests

Switch to the faster/cheaper lightning model variant for integration tests.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: add MINIMAX_API_KEY to deploy-core command

Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>

* chore: regenerate web openapi spec with MiniMax provider

Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>

🐾 Generated with [Letta Code](https://letta.com)

---------

Co-authored-by: Letta <noreply@letta.com>
Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>
2026-01-29 12:44:04 -08:00
Sarah Wooders
221b4e6279 refactor: add extract_usage_statistics returning LettaUsageStatistics (#9065)
👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
2bccd36382 Revert "fix: ensure stop_reason is always set and reduce noisy logs (… (#9086)
Revert "fix: ensure stop_reason is always set and reduce noisy logs (#9046)"

This reverts commit 4241a360579440d2697124ba69061d0e46ecc5e9.

**Problem:**
After the original change, caren-code-agent reported streams hanging
indefinitely. The trace shows ttft (time to first token) succeeds, but
the stream never closes.

**Root Cause (suspected):**
The change modified `is_complete=is_done` to `is_complete=saw_done`,
meaning error events no longer mark the stream as complete immediately.
This may cause timing issues where clients wait for more data before
the finalizer runs.

**Fix:**
Revert to the defensive "belt-and-suspenders" approach that always
appends [DONE]. The noisy logs are preferable to hanging streams.

The original comment noted: "Even if a previous chunk set `complete`,
an extra [DONE] is harmless and ensures SDKs that rely on explicit
[DONE] will exit."

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
18274b5a42 fix: handle null step_id on approval request messages (#9074)
**Problem:**
Runs failed with error:
```
Argument step_id does not match type <class 'str'>; is None of type <class 'NoneType'>
```

This happened when processing approval responses where the original
approval request message had `step_id=None`.

**Root Cause:**
Line 672 in `_step()` directly used `approval_request.step_id`:
```python
step_id = approval_request.step_id  # Can be None!
step_metrics = await self.step_manager.get_step_metrics_async(step_id=step_id, ...)
```

`Message.step_id` is `Optional[str]` (default None), but `get_step_metrics_async`
has `step_id: str` with `@enforce_types` validation.

Old approval messages or edge cases could have `step_id=None`, causing
the enforce_types decorator to reject the call.

**Fix:**
Check if `step_id is None` and generate a new step_id + initialize step
checkpoint if needed, instead of assuming step_id always exists.

**Note:**
Similar issue exists in letta_agent_v2.py and temporal agents, but v2
is deprecated.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
3e49cf5d44 fix: load default provider config when summarizer uses different prov… (#9051)
fix: load default provider config when summarizer uses different provider

**Problem:**
Summarization failed when agent used one provider (e.g., Google AI) but
summarizer config specified a different provider (e.g., Anthropic):

```python
# Agent LLM config
model_endpoint_type='google_ai', handle='gemini-something/gemini-2.5-pro',
context_window=100000

# Summarizer config
model='anthropic/claude-haiku-4-5-20251001'

# Bug: Resulting summarizer_llm_config mixed Google + Anthropic settings
model='claude-haiku-4-5-20251001', model_endpoint_type='google_ai',  #  Wrong endpoint!
context_window=100000  #  Google's context window, not Anthropic's default!
```

This sent Claude requests to Google AI endpoints with incorrect parameters.

**Root Cause:**
`_build_summarizer_llm_config()` always copied the agent's LLM config as base,
then patched model/provider fields. But this kept all provider-specific settings
(endpoint, context_window, etc.) from the wrong provider.

**Fix:**
1. Parse provider_name from summarizer handle
2. Check if it matches agent's model_endpoint_type (or provider_name for custom)
3. **If YES** → Use agent config as base, override model/handle (same provider)
4. **If NO** → Load default config via `provider_manager.get_llm_config_from_handle()` (new provider)

**Example Flow:**
```python
# Agent: google_ai/gemini-2.5-pro
# Summarizer: anthropic/claude-haiku

provider_name = "anthropic"  # Parsed from handle
provider_matches = ("anthropic" == "google_ai")  # False 

# Different provider → load default Anthropic config
base = await provider_manager.get_llm_config_from_handle(
    handle="anthropic/claude-haiku",
    actor=self.actor
)
# Returns: model_endpoint_type='anthropic', endpoint='https://api.anthropic.com', etc. 
```

**Result:**
- Summarizer with different provider gets correct default config
- No more mixing Google endpoints with Anthropic models
- Same-provider summarizers still inherit agent settings efficiently

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Shubham Naik
55a89398e1 chore: rebuild api requests (#9069) 2026-01-29 12:44:04 -08:00
github-actions[bot]
194c743223 refactor: rename stream to streaming in ConversationMessageRequest (#9063) 2026-01-29 12:44:04 -08:00
github-actions[bot]
1d1bb29a43 feat: add override_model support for agent file import (#9058) 2026-01-29 12:44:04 -08:00
Charles Packer
82c01368fc feat: add conversation_id to message search results (#9056)
* feat: add conversation_id to message search results

Add conversation_id field to all *MessageListResult classes
(SystemMessageListResult, UserMessageListResult, ReasoningMessageListResult,
AssistantMessageListResult) so that conversation IDs are returned from
the /messages/search endpoint alongside agent IDs.

Fixes #9055

Co-authored-by: Charles Packer <cpacker@users.noreply.github.com>

* chore: regenerate SDK and OpenAPI spec

Regenerate autogenerated files after adding conversation_id to
message search result schemas.

Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>

---------

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Charles Packer <cpacker@users.noreply.github.com>
Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>
2026-01-29 12:44:04 -08:00
Sarah Wooders
6c415b27f8 feat: add non-streaming option for conversation messages (#9044)
* feat: add non-streaming option for conversation messages

- Add ConversationMessageRequest with stream=True default (backwards compatible)
- stream=true (default): SSE streaming via StreamingService
- stream=false: JSON response via AgentLoop.load().step()

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API schema for ConversationMessageRequest

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
208992170c fix: gracefully skip assistant messages with empty content in LLM for… (#9050)
fix: gracefully skip assistant messages with empty content in LLM format conversion

**Problem:**
Context window calculation crashed with AssertionError when converting messages
to Google/Anthropic/OpenAI format:
```
AssertionError at line 2047: assert self.tool_calls is not None or
text_content is not None or len(self.content) > 1
```

This happened when loading agents with old/malformed messages that had
`content=None` or `content=[]` in the database.

**Root Cause:**
The Message ORM model allows `content: Optional[List[...]] = None` (line 252),
but format conversion methods assumed content would always have extractable text
or tool calls.

Scenarios that triggered crashes:
1. Assistant message with `content=None` (old migrations/edge cases)
2. Assistant message with `content=[]` (message creation bugs)
3. Assistant message with single non-text content that doesn't match extraction logic

**Fix:**
Replaced assertions with defensive checks in 3 conversion methods:

1. `to_google_dict()` (line 2054) - Return None to skip unconvertible messages
2. `to_openai_responses_api_dicts()` (line 1476) - Return early to skip
3. `to_anthropic_dict()` (line 1794) - Return None to skip

Pattern: Check for empty content, return None/early to skip gracefully.

**Result:**
- Context window calculation no longer crashes on malformed/old messages
- Messages with no convertible content are silently skipped
- Consistent with existing Anthropic reasoning-only message handling (line 1308)

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
4c2253dc76 fix: use repr() fallback for empty exception messages in error logging (#9047)
**Problem:**
Error logs showed empty detail fields when exceptions had no message:
```
Error during step processing:
Run run-xxx stopped with unknown error: , error_data: {...'detail': ''}
```

This made debugging production issues difficult as the actual error type
was hidden.

**Root Cause:**
Python exceptions created with no arguments (e.g., `Exception()` or caught
and re-raised in certain ways) have `str(e) == ""`:

```python
e = Exception()
str(e)   # Returns ""
repr(e)  # Returns "Exception()"
```

When exceptions with empty string representations were caught, all logging
and error messages showed blank details.

**Fix:**
Use `str(e) or repr(e)` fallback pattern in 3 places:
1. `letta_agent_v3.py` stream() exception handler (line 406)
2. `letta_agent_v3.py` step() exception handler (line 928)
3. `streaming_service.py` generic exception handler (line 469)

**Result:**
- Error logs now show `Exception()` or similar instead of empty string
- Helps identify exception types even when message is missing
- Better production debugging without changing exception handling logic

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
2a2e777807 fix: ensure stop_reason is always set and reduce noisy logs (#9046)
fix: consume [DONE] token after error events to prevent forced finalizer append

**Problem:**
Stream finalizer was frequently logging warning and appending forced [DONE]:
```
[Stream Finalizer] Appending forced [DONE] for run=run-xxx (saw_error=True,
saw_done=False, final_stop_reason=llm_api_error)
```

This happened on every error, even though streaming_service.py already yields
[DONE] after all error events.

**Root Cause:**
Line 266: `is_done = saw_done or saw_error` caused loop to break immediately
after seeing error event, BEFORE consuming the [DONE] chunk that follows:

```python
is_done = saw_done or saw_error
await writer.write_chunk(...)
if is_done:  # Breaks on error!
    break
```

Sequence:
1. streaming_service.py yields: `event: error\ndata: {...}\n\n`
2. Redis reader sees error → sets `saw_error=True`
3. Sets `is_done=True` and breaks
4. Never reads next chunk: `data: [DONE]\n\n`
5. Finalizer runs → `saw_done=False` → appends forced [DONE]

**Fix:**
1. Only break when `saw_done=True` (not `saw_error`) → allows consuming [DONE]
2. Only run finalizer when `saw_done=False` → reduces log noise

**Result:**
- [DONE] now consumed naturally from streaming_service.py error handlers
- Finalizer warning only appears when truly needed (fallback cases)
- Cleaner production logs

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
ca40eff7bc fix: ensure stop_reason is always set when marking runs as failed (#9045)
**Problem:**
Production error showed runs being marked as failed with stop_reason=None,
which violates LettaStopReason's Pydantic schema (requires valid enum value).
This caused cascading validation errors that got stored in metadata.

Example error:
```
Run is already in a terminal state failed with stop reason None, but is being
updated with data {'status': 'failed', 'stop_reason': None, 'metadata':
{'error': "1 validation error for LettaStopReason\nstop_reason Input should
be 'end_turn', 'error', ... [type=enum, input_value=None]"}}
```

**Root Causes:**
1. routers/v1/agents.py had 3 exception handlers creating RunUpdate(status=failed)
   without stop_reason
2. Success path assumed result.stop_reason always exists (AttributeError if None)
3. run_manager.py tried to create LettaStopReason(stop_reason=None) when
   refreshing result messages

**Fixes:**
1. Added stop_reason=StopReasonType.error to 3 exception handlers
2. Added defensive None checks before accessing result.stop_reason.stop_reason
3. Added fallback to StopReasonType.error when pydantic_run.stop_reason is None

**Trigger:**
OpenAI BadRequestError for invalid tool schema → exception handlers marked
run as failed without stop_reason → validation error when constructing response

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Ari Webb
5533c723df fix: bedrock third time (#9043) 2026-01-29 12:44:04 -08:00
Ari Webb
e5afbd0972 fix: base url wrong (#9040) 2026-01-29 12:44:04 -08:00
cthomas
57ab117437 feat: dedupe approval response retries on server (#9038) 2026-01-29 12:44:04 -08:00
Sarah Wooders
25e9539a6e feat: add batch passage create and optional search query (#8866) 2026-01-29 12:44:04 -08:00
github-actions[bot]
179a1df524 feat: add conversation compact endpoint to SDK and add integration tests (#9025) 2026-01-29 12:44:04 -08:00
Shubham Naik
8ced2e0c82 Shub/let 7138 support custom feeds that recieve data via an endpoint [LET-7138] (#9027)
* feat: support custom endpoint

* feat: support custom endpoint

* chore: add webhook

* chore: add webhook

* chore: fix types

* chore: fix types

* chore: docs
2026-01-29 12:44:04 -08:00
cthomas
c162de5127 fix: use shared event + .athrow() to properly set stream_was_cancelle… (#9019)
fix: use shared event + .athrow() to properly set stream_was_cancelled flag

**Problem:**
When a run is cancelled via /cancel endpoint, `stream_was_cancelled` remained
False because `RunCancelledException` was raised in the consumer code (wrapper),
which closes the generator from outside. This causes Python to skip the
generator's except blocks and jump directly to finally with the wrong flag value.

**Solution:**
1. Shared `asyncio.Event` registry for cross-layer cancellation signaling
2. `cancellation_aware_stream_wrapper` sets the event when cancellation detected
3. Wrapper uses `.athrow()` to inject exception INTO generator (not consumer-side raise)
4. All streaming interfaces check event in `finally` block to set flag correctly
5. `streaming_service.py` handles `RunCancelledException` gracefully, yields [DONE]

**Changes:**
- streaming_response.py: Event registry + .athrow() injection + graceful handling
- openai_streaming_interface.py: 3 classes check event in finally
- gemini_streaming_interface.py: Check event in finally
- anthropic_*.py: Catch RunCancelledException
- simple_llm_stream_adapter.py: Create & pass event to interfaces
- streaming_service.py: Handle RunCancelledException, yield [DONE], skip double-update
- routers/v1/{conversations,runs}.py: Pass event to wrapper
- integration_test_human_in_the_loop.py: New test for approval + cancellation

**Tests:**
- test_tool_call with cancellation (OpenAI models) 
- test_approve_with_cancellation (approval flow + concurrent cancel) 

**Known cosmetic warnings (pre-existing):**
- "Run already in terminal state" - agent loop tries to update after /cancel
- "Stream ended without terminal event" - background streaming timing race

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Ari Webb
5ca0f55079 fix: fix bedrock again (#9021) 2026-01-29 12:44:04 -08:00
Kian Jones
e3fb00f970 feat(crouton): add orgId, userId, Compaction_Settings and LLM_Config (#9022)
* LC one shot?

* api changes

* fix summarizer nameerror
2026-01-29 12:44:04 -08:00
Kian Jones
194fa7d1c6 fix: anthropic message packing bugs (#9017)
* fix: anthroppic message packing bugs - traling whitespace and final assistant message missing thinking

* revert bug Caren will fix upstream?
2026-01-29 12:44:04 -08:00