fix(mods): preserve approval order for passive turn context - #3773
Open
letta-integration[bot] wants to merge 3 commits into
Open
fix(mods): preserve approval order for passive turn context#3773letta-integration[bot] wants to merge 3 commits into
letta-integration[bot] wants to merge 3 commits into
Conversation
Compose mod-provided context at the shared turn queue boundary so approval continuations remain first without allowing passive context to wake a turn by itself. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code <noreply@letta.com>
letta-integration
Bot
requested review from
4shub,
carenthomas,
christinatong01,
cpacker,
devanshrj,
jnjpng,
kl2806 and
sarahwooders
as code owners
August 9, 2026 20:31
Overlord (agent-c2adbf5c-8419-4211-8cd8-3740db164974). Restore the existing trigger contract so approval-only continuations do not invoke turn_start mods while mixed approval and queued-user turns still compose passive context safely. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code <noreply@letta.com>
Overlord (agent-c2adbf5c-8419-4211-8cd8-3740db164974). Mark writable turn_start input as compatibility-only while directing passive additions through the approval-safe queue item path. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code <noreply@letta.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
A queued turn can contain an approval continuation followed by ordinary user or task-notification content.
turn_startmods currently receive that finalized transport array, so a mod that prepends reminder content can move the approval away from index 0. Core then rejects the otherwise valid continuation with409 PENDING_APPROVAL.The failing order was:
This PR adds the Letta Code primitive needed to produce:
Related Core behavior: letta-ai/letta#6392.
Root cause
Queue composition and mod input mutation happened at different ownership layers. The queue produced the approval-first control envelope, then
turn_startexposed the rawArray<MessageCreate | ApprovalCreate>and accepted a replacement array with only shallow validation. That let content-oriented mods displace or replace approval objects after queue ordering had already been decided.What changed
Shared turn composition
ModTurnQueueItemwithkind: "context"for passive mod context.typediscriminator is absent.Mod event surface
queueItemstoturn_startevents and results as the safe path for passive additions.ModTurnStartEvent.inputandModTurnStartResult.inputas compatibility-only. They remain temporarily for transforms of existing ordinary messages, but must not be used to inject, remove, or reorder messages.queueItemsas the approval-safe injection path. Removing writableinputstill requires a narrower typed transform API for legitimate existing-message transforms.Runtime consumers
turn_startalready runs for a user-message turn.turn_startmods.Changed-behavior matrix
turn_startruns; approval remains first and context precedes the notificationturn_startdoes not run; no passive context is injectedinputtypediscriminatorValidation
Focused regression suite:
Result: 49 passed, 0 failed.
Full repository validation:
Result: all 12 checks passed, including Biome and TypeScript.
Limits and follow-up
This PR is containment plus a safe injection path, not removal of the broad transform surface. Writable
turn_start.inputremains temporarily for compatibility with real transforms of existing ordinary messages, but it is formally deprecated and must not be used to inject, remove, or reorder messages.queueItemsis the safe path for passive additions.queueItemsdoes not replace legitimate transforms such as image stripping or normalization. A narrower typed transform API that represents those operations without exposing the whole transport array is required before writableinputcan be removed.This PR does not migrate the goal mod itself. A follow-up in
letta-ai/modsshould stop prepending rawturn_start.inputand contribute its reminder throughqueueItemsinstead.The shared composition, trigger policy, and mod-handler isolation paths have direct regressions. TUI, listener, and headless wiring is covered by the common typed path plus full repository validation; this PR does not add three separate end-to-end runtime harnesses. The deprecation contract is JSDoc-only, following the existing source convention; no mock-heavy runtime test was added for comments.
Risk and rollback
The main compatibility risk is ordering for mods that intentionally mutate approval objects through
turn_start.input. That behavior is now deliberately blocked because approvals belong to the control envelope. Ordinary existing-message transformation remains temporarily supported through the deprecated compatibility field, and approval-only continuations retain the pre-PR behavior of not invoking TUI/listenerturn_starthandlers. Reverting these commits restores the previous raw-array behavior but also restores the approval-order failure.