In-Memory Thread Lifecycle#
InMemoryThreadList manages a set of thread resources where each thread is keyed by a string thread ID. The key determines resource identity: when the selected thread ID changes, the previously mounted OwnedThread resource is unmounted and a new one is mounted for the incoming ID. This is a soft unmount — the thread's per-ID AbortSignal is not aborted on an ordinary selection change .
Thread Identity and State Isolation#
The InMemoryThreadListProps.thread factory receives a threadId string and returns a ResourceElement<ClientOutput<"thread">>. The JSDoc explicitly states that "thread-owned state does not survive a selection change" because the resource is keyed by threadId . This means:
- Composer/draft state lives inside the keyed
OwnedThreadresource. Switching to a different thread ID replaces the resource and resets composer state. - Switching back to a prior thread ID creates a fresh resource instance — draft state from the first visit is gone unless the host persists it externally.
- Scope derivations (composer, threadListItem) are re-derived from the new
mainthread automatically .
Per-Thread Lifetime Signals (createThreadLifetimes)#
InMemoryThreadList maintains one AbortController per thread ID via the createThreadLifetimes factory. Two distinct lifecycle events abort a thread's signal:
| Event | What happens to the signal |
|---|---|
| Thread selection change | Signal is not aborted. The old OwnedThread resource is unmounted but its per-ID controller stays live in the map . |
| Thread deletion | lifetimes.release(threadId) aborts the controller and removes it from the map — permanent teardown . |
| List owner destruction | lifetimes.bindOwner causes abortAll() to fire, aborting every tracked controller with the owner's reason . |
The signal is injected into the thread subtree via useDestroySignalProvider, which provides the signal as React context. Its documentation describes it as signalling only permanent teardown of the owner — never a soft resource unmount that may recur .
In-Flight Sends and Tool Executions#
ToolInvocationTracker manages the client-side tool execution pipeline for external-store thread runtimes. Its abort() and reset() lifecycle are not driven by thread selection changes or per-thread destroy signals. Instead, they are called on explicit run-boundary events inside ExternalStoreThreadRuntimeCore:
abort({ discardPending: true })— called before starting a new run (append) and before a reload (startRun), cancelling any in-flightexecute()calls so stale results don't land in the new turn .abort()(withoutdiscardPending) — called during message deletion while running , and oncancelRun.reset()— called when the adapter's message repository instance changes , when the tool-invocations feature is disabled , onimportExternalState, and onunstable_notifySessionReset.
When an in-memory thread is switched (not deleted), the underlying ExternalStoreThreadRuntimeCore is replaced or disposed. Per the runtime disposal path, this ends the old runtime's generation signals — but the ToolInvocationTracker.abort() is not explicitly wired to thread disposal in the paths covered; in-flight executions from the old runtime may settle after unmount but their onAddToolResult callbacks are no-ops if the tool call ID no longer exists in the current snapshot .
Key Entry Points#
| File | Role |
|---|---|
InMemoryThreadList.ts | Thread list resource, per-ID lifetime signals, soft vs. permanent teardown |
destroy-signal-context.ts | useDestroySignalProvider — permanent teardown signal propagation |
ToolInvocationTracker.ts | Frontend tool execution lifecycle: abort(), reset(), setState() |
external-store-thread-runtime-core.ts | Owns the ToolInvocationTracker; wires run-boundary abort/reset calls |