fix(tools): scope task CRUD state by conversation - #3587
Open
GautamSharma99 wants to merge 1 commit into
Open
Conversation
GautamSharma99
requested review from
4shub,
carenthomas,
christinatong01,
cpacker,
jnjpng,
kl2806 and
sarahwooders
as code owners
July 31, 2026 08:52
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.
Summary
Scope the
TaskCreate/TaskGet/TaskList/TaskUpdatein-memory registry by both agent and conversation instead of sharing one process-global task collection.This prevents concurrent conversations in the listener, headless runtime, TUI, and OpenAI-compatible app server from listing, reading, or updating each other's task plans.
Fixes #3577.
Root cause
The task CRUD store previously owned one module-level
Map<string, TaskRecord>and one insertion-order array. Every tool invocation in the process called that singleton directly, even though letta-code already executes tools inside a runtime context containing the activeagentIdandconversationId.That meant:
TaskListreturned tasks created by unrelated conversations;TaskGetorTaskUpdatein another;"default"value also shared task state;Implementation
Scoped store ownership
src/tools/impl/tasks/store.tsnow owns nested agent/conversation stores:Every CRUD operation requires a
TaskStoreScope. Task IDs remain process-unique, which makes a task ID created in scope A resolve as not found in scope B rather than accidentally referring to a different local task.The store also exposes
clearTaskStoreScope()so lifecycle owners can remove one conversation without affecting any other scope.Fail-closed tool scope resolution
The new
src/tools/impl/tasks/scope.tsresolves scope from the existingAsyncLocalStorageruntime context used byexecuteTool().Task tools now reject execution when either
agentIdorconversationIdis absent. They do not fall back to an unscoped bucket, because that would silently recreate the original isolation bug for any call path with incomplete context.No model-facing task schema changes are required; scope remains execution metadata rather than user-controlled tool input.
Scoped TUI rendering
AppViewandStaticTranscriptpass the active agent/conversation identity toToolCallMessage. Task snapshots are projected by the newtask-crud-renderinghelper, so the TUI reads exactly the same scoped store as the tool invocation.The projection was extracted instead of growing the already oversized renderer.
ToolCallMessageRich.tsxshrinks by one line, and its source-size baseline is ratcheted from 1109 to 1108.Ephemeral conversation cleanup
Headerless OpenAI-compatible Chat Completions and Responses requests create ephemeral conversations and delete them after the turn. After a successful backend deletion, these paths now clear the matching task scope as well.
Cleanup is deliberately tied to successful conversation deletion: a failed backend deletion retains both conversation and task state for consistent retry/debug behavior.
Correctness details
conversationId === "default"is valid for multiple agents.Tests
Added regression coverage for:
TaskGetandTaskUpdaterejection;"default"conversation ID;Focused test command:
bun test src/tools/task-crud.test.ts \ src/cli/helpers/task-crud-rendering.test.ts \ src/websocket/app-server-openai.test.ts \ src/websocket/app-server-openai-responses.test.ts \ src/cli/enter-worktree-result-renderer.test.tsxResult: 51 passed, 0 failed.
Full repository validation:
Result: all 12 checks passed, including circular dependencies, layer boundaries, source-size ratchet, module ownership, Biome, and TypeScript.