You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This bug is part of the broader "remote agent sessions" design being written. This specific bug covers: loading a remote session's chat history from the remote data store over the dev tunnel (for display), plus a Restoring history ... HTML indicator and surfacing restore exceptions. Where the agent process itself runs (locally on the remote profile machine) is covered by the parent design.
Summary
When we open an existing agent session whose associated user-computer-profile is a different (remote) computer reachable over a dev tunnel, the chat output pane comes up empty — no history is restored.
The correct behavior is:
The agent should start / run locally on that remote profile computer (i.e. on the session's own host profile machine, not on the machine that opened it).
The chat history for the session should be loaded from the remote data store over the dev tunnel (the same dev-tunnel data-access channel we already use for entities and for WebClientAgentPersistenceStore) and displayed in the local client.
Today neither happens for history: the local AgentChat used as the display facade has its persistence store replaced with NullAgentPersistenceStore, so it loads zero messages and the pane renders empty. My earlier framing on this bug ("no transport RPC fetches remote history, treat it as a fundamentally missing feature") was wrong: the history already lives in the remote store and is already reachable over the dev-tunnel data-access path — it just isn't being read for display.
In addition:
Add a "Restoring history ..." HTML indicator that stays visible until history is fully restored.
Surface any exception raised during restore into a visible error banner instead of silently showing nothing.
Root Cause
Verified in the features submodule.
1. Local history restore path (works)
Phantom.Workspaces.Llm.Core/AgentChat.csInitializeAsync reads persisted messages via ConfiguredStore.ReadMessagesAsync(...) (~L382), pushes them with LoadInitialHistory(persistedMessages) (~L415), and completes historyPopulated.TrySetResult() (~L420). HistoryPopulated is exposed at ~L511.
UI side: Phantom.Workspaces.Agent.Gui/Controls/AgentChatOutputControl.axaml.csOnBrowserReady awaits vm.HistoryPopulated (~L303), then builds ChatOutputHtmlModel(vm.History, ...) (~L322). The model prepends history chunks via ChatOutputHtmlModels.csLoadHistoryChunksAsync (~L1968-2084) into container id chat-history-container (ChatOutputHtmlRenderer.HistoryContainerId ~L33; shell markup at chat-output-shell.html ~L506).
2. Why remote sessions never restore history (primary bug)
Opening an existing agent session is routed through Phantom.Workspaces/ViewModels/OpenAgentSessionShortcutHandler.cs (~L310-337). When the session's host-profile-entity-id (ReadHostProfileEntityId ~L455) does not match the local user-computer-profile, the handler calls CreateTrustedAgentChatAsync (~L434-453), which dispatches to Phantom.Workspaces.Llm.Core/Transport/TransportTrustedExecutor.CreateAgentChatAsync (~L43).
Class doc-comment (~L6-11) explicitly states the remote side owns authoritative history and the local side must never prepend stored history to outbound messages.
So AgentChat.InitializeAsync loads zero messages for remote sessions, historyPopulated completes immediately, and the chat renders empty. The "don't prepend stored history to outbound" rule is correct and must be preserved — but nothing currently reads the remote history for display.
3. The remote history IS already reachable over the dev tunnel
The history the local display should show is already persisted on the remote instance and served over the same dev-tunnel data-access channel we use for other data:
Authoritative store on the remote instance: Phantom.Workspaces.Data.MongoDB/MongoDbAgentPersistenceStore.cs.
HTTP endpoint exposing it: Phantom.Workspaces.Web.Server/AgentPersistenceEndpointRouteBuilderExtensions.cs (routes under /agent/persistence/...).
Dev-tunnel-aware client: Phantom.Workspaces.Data.Web.Client/WebClientAgentPersistenceStore.cs (~L10-46 constructs an HttpClient against the dev-tunnel endpoint, sends the X-Tunnel-Authorization header, and calls /agent/persistence/store, /agent/persistence/read-messages, etc.).
Reconnect wrapper used by the app: Phantom.Workspaces/Services/DevTunnel/ReconnectingWebAgentPersistenceStore.cs.
The transport-based execution path (TransportTrustedExecutor) does not use this store — it forces NullAgentPersistenceStore — even though we already have a working dev-tunnel-backed IAgentPersistenceStore implementation.
4. Exceptions during restore are swallowed
Phantom.Workspaces.Agent.Gui/Controls/ChatOutputHtmlModels.csLoadHistoryChunksAsync catches only OperationCanceledException (~L2075); other exceptions fault the HistoryLoaded task silently.
AgentChatOutputControl.axaml.cs swallows exceptions from await vm.HistoryPopulated (~L305-309) and inside CompleteWhenLoadedAsync (~L351-354) with empty catch blocks — no UI feedback.
AgentChat.RunSessionInitAsync (~L435-445) does emit an ErrorContent"Failed to load session: {ex}" — but only for the session-init phase with the real store. NullAgentPersistenceStore never throws, so remote-restore failures produce no error at all today, and any future remote-fetch failure would similarly need an explicit surface.
5. No existing loading/status affordance
chat-output-shell.html has only <div id="chat-history-container"></div> (~L506). No status / spinner / restoring element.
There is a .chat-error CSS class used by ErrorContent (ChatOutputHtmlRenderer.cs ~L532) that can be reused for the new error banner.
The render model has a private historyLoading field (ChatOutputHtmlModels.cs ~L1605, set true at ~L1650, false at ~L2063) that is never surfaced as bindable state. HistoryLoaded task at ~L1613.
AgentViewModel.cs exposes HistoryPopulated (~L288) — the natural place to add IsRestoringHistory / RestoreError.
Affected Files
File
Lines
Role
features\Phantom.Workspaces.Llm.Core\AgentChat.cs
185-465, 511
InitializeAsync loads history via configured store; exposes HistoryPopulated.
Load remote history from the remote store over the dev tunnel (for display). In TransportTrustedExecutor.CreateAgentChatAsync (~L43-65), instead of setting AgentPersistenceStoreOverride = NullAgentPersistenceStore.Instance, provide an IAgentPersistenceStore that reads the session's history from the remote data store over the existing dev-tunnel data-access channel (i.e. the same WebClientAgentPersistenceStore / ReconnectingWebAgentPersistenceStore path already used elsewhere in the app; the remote server exposes it via AgentPersistenceEndpointRouteBuilderExtensions on top of MongoDbAgentPersistenceStore).
Constraint (preserve the NullAgentPersistenceStore intent): the local display facade must remain read-only for outbound messages. That is: ReadMessagesAsync / RestoreAsync should hit the remote store over the dev tunnel and return real history for display, but StoreAsync / AddSubAgentLinkAsync on the display facade must NOT write — the remote agent owns authoritative writes, so we must not prepend locally-stored history to outbound messages. A thin read-only wrapper over WebClientAgentPersistenceStore (delegating reads, no-op on writes) satisfies this.
The dev-tunnel endpoint / access token to use is the one associated with the session's host user-computer-profile (resolved the same way TransportTrustedExecutor already resolves the transport target via ExecutionTargetResolver).
"Restoring history ..." HTML indicator. In chat-output-shell.html add sibling elements near ~L506:
Add HistoryStatusId / HistoryErrorId constants in ChatOutputHtmlRenderer.cs near ~L33. Drive them from ChatOutputHtmlModels.cs: show Restoring history ... when history load begins (~L1650, historyLoading = true) and clear when the load completes (~L2063). Surface the state on AgentViewModel as IsRestoringHistory (near ~L288) so the indicator also covers the async remote fetch window — the indicator must remain until history is fully restored (both the remote-store fetch and the local chunked render), not just for the local chunk render.
Surface restore exceptions. Wrap LoadHistoryChunksAsync (~L1972+) and the remote fetch in catch (Exception ex) that writes ex.ToString() into the new chat-history-error element (styled with .chat-error from ChatOutputHtmlRenderer.cs ~L532). Replace the empty catch blocks in AgentChatOutputControl.axaml.cs (~L305-309, ~L351-354) with error-banner reporting. Add a RestoreError string on AgentViewModel.
Full-restore semantics.IsRestoringHistory reflects the union of the async phases (remote fetch + local chunked render) and only transitions to false after both complete.
Considered / Background
Adding a brand-new "transport RPC that fetches remote history" (the framing in the earlier version of this bug) is unnecessary: the remote store is already exposed over HTTP via the dev tunnel through WebClientAgentPersistenceStore and the AgentPersistenceEndpointRouteBuilderExtensions routes. The fix reuses that channel rather than inventing a new transport message.
Keeping NullAgentPersistenceStore intact and adding a separate "restore history over dev tunnel" call that populates AgentChat.History post-InitializeAsync is an alternative. Wiring the read-only web-client store as AgentPersistenceStoreOverride is preferred because it lets InitializeAsync populate History and complete HistoryPopulated through its existing code path.
Expected Tests
Existing test files to align with (read before implementing):
features\Phantom.Workspaces.Llm.Core.Tests\TransportTrustedExecutorTests.cs (if present; otherwise add a new file next to the other Phantom.Workspaces.Llm.Core.Tests transport tests)
For a remote target, AgentPersistenceStoreOverride is set to a dev-tunnel-backed read-only store (delegating reads to WebClientAgentPersistenceStore) rather than NullAgentPersistenceStore.
Opening a remote session invokes ReadMessagesAsync against the dev-tunnel IAgentPersistenceStore for the session id, and AgentChat.History is populated with the returned messages before HistoryPopulated completes.
The display-side store used for the local AgentChat no-ops on StoreAsync / AddSubAgentLinkAsync, preserving the NullAgentPersistenceStore "no outbound prepend / no local write" intent.
Note
This bug is part of the broader "remote agent sessions" design being written. This specific bug covers: loading a remote session's chat history from the remote data store over the dev tunnel (for display), plus a
Restoring history ...HTML indicator and surfacing restore exceptions. Where the agent process itself runs (locally on the remote profile machine) is covered by the parent design.Summary
When we open an existing agent session whose associated user-computer-profile is a different (remote) computer reachable over a dev tunnel, the chat output pane comes up empty — no history is restored.
The correct behavior is:
WebClientAgentPersistenceStore) and displayed in the local client.Today neither happens for history: the local
AgentChatused as the display facade has its persistence store replaced withNullAgentPersistenceStore, so it loads zero messages and the pane renders empty. My earlier framing on this bug ("no transport RPC fetches remote history, treat it as a fundamentally missing feature") was wrong: the history already lives in the remote store and is already reachable over the dev-tunnel data-access path — it just isn't being read for display.In addition:
Root Cause
Verified in the
featuressubmodule.1. Local history restore path (works)
Phantom.Workspaces.Llm.Core/AgentChat.csInitializeAsyncreads persisted messages viaConfiguredStore.ReadMessagesAsync(...)(~L382), pushes them withLoadInitialHistory(persistedMessages)(~L415), and completeshistoryPopulated.TrySetResult()(~L420).HistoryPopulatedis exposed at ~L511.Phantom.Workspaces.Agent.Gui/Controls/AgentChatOutputControl.axaml.csOnBrowserReadyawaitsvm.HistoryPopulated(~L303), then buildsChatOutputHtmlModel(vm.History, ...)(~L322). The model prepends history chunks viaChatOutputHtmlModels.csLoadHistoryChunksAsync(~L1968-2084) into container idchat-history-container(ChatOutputHtmlRenderer.HistoryContainerId~L33; shell markup atchat-output-shell.html~L506).2. Why remote sessions never restore history (primary bug)
Opening an existing agent session is routed through
Phantom.Workspaces/ViewModels/OpenAgentSessionShortcutHandler.cs(~L310-337). When the session'shost-profile-entity-id(ReadHostProfileEntityId~L455) does not match the local user-computer-profile, the handler callsCreateTrustedAgentChatAsync(~L434-453), which dispatches toPhantom.Workspaces.Llm.Core/Transport/TransportTrustedExecutor.CreateAgentChatAsync(~L43).That method sets:
Phantom.Workspaces.Llm.Core/NullAgentPersistenceStore.cs:ReadMessagesAsyncreturnsArray.Empty<ChatMessage>()(~L32-35)RestoreAsyncreturnsnull(~L26-29)So
AgentChat.InitializeAsyncloads zero messages for remote sessions,historyPopulatedcompletes immediately, and the chat renders empty. The "don't prepend stored history to outbound" rule is correct and must be preserved — but nothing currently reads the remote history for display.3. The remote history IS already reachable over the dev tunnel
The history the local display should show is already persisted on the remote instance and served over the same dev-tunnel data-access channel we use for other data:
Phantom.Workspaces.Data.MongoDB/MongoDbAgentPersistenceStore.cs.Phantom.Workspaces.Web.Server/AgentPersistenceEndpointRouteBuilderExtensions.cs(routes under/agent/persistence/...).Phantom.Workspaces.Data.Web.Client/WebClientAgentPersistenceStore.cs(~L10-46 constructs anHttpClientagainst the dev-tunnel endpoint, sends theX-Tunnel-Authorizationheader, and calls/agent/persistence/store,/agent/persistence/read-messages, etc.).Phantom.Workspaces/Services/DevTunnel/ReconnectingWebAgentPersistenceStore.cs.The transport-based execution path (
TransportTrustedExecutor) does not use this store — it forcesNullAgentPersistenceStore— even though we already have a working dev-tunnel-backedIAgentPersistenceStoreimplementation.4. Exceptions during restore are swallowed
Phantom.Workspaces.Agent.Gui/Controls/ChatOutputHtmlModels.csLoadHistoryChunksAsynccatches onlyOperationCanceledException(~L2075); other exceptions fault theHistoryLoadedtask silently.AgentChatOutputControl.axaml.csswallows exceptions fromawait vm.HistoryPopulated(~L305-309) and insideCompleteWhenLoadedAsync(~L351-354) with emptycatchblocks — no UI feedback.AgentChat.RunSessionInitAsync(~L435-445) does emit anErrorContent"Failed to load session: {ex}"— but only for the session-init phase with the real store.NullAgentPersistenceStorenever throws, so remote-restore failures produce no error at all today, and any future remote-fetch failure would similarly need an explicit surface.5. No existing loading/status affordance
chat-output-shell.htmlhas only<div id="chat-history-container"></div>(~L506). No status / spinner / restoring element..chat-errorCSS class used byErrorContent(ChatOutputHtmlRenderer.cs~L532) that can be reused for the new error banner.historyLoadingfield (ChatOutputHtmlModels.cs~L1605, set true at ~L1650, false at ~L2063) that is never surfaced as bindable state.HistoryLoadedtask at ~L1613.AgentViewModel.csexposesHistoryPopulated(~L288) — the natural place to addIsRestoringHistory/RestoreError.Affected Files
features\Phantom.Workspaces.Llm.Core\AgentChat.csInitializeAsyncloads history via configured store; exposesHistoryPopulated.features\Phantom.Workspaces.Llm.Core\NullAgentPersistenceStore.csfeatures\Phantom.Workspaces.Llm.Core\Transport\TransportTrustedExecutor.csAgentPersistenceStoreOverride = NullAgentPersistenceStore.Instance; must instead provide a read-only, dev-tunnel-backed store for display.features\Phantom.Workspaces\ViewModels\OpenAgentSessionShortcutHandler.csTransportTrustedExecutor.features\Phantom.Workspaces.Data.Web.Client\WebClientAgentPersistenceStore.csIAgentPersistenceStore(HTTP +X-Tunnel-Authorization). Candidate for the display-side restore path.features\Phantom.Workspaces\Services\DevTunnel\ReconnectingWebAgentPersistenceStore.csfeatures\Phantom.Workspaces\Services\AgentPersistenceStoreCache.csRepositorySourceIAgentPersistenceStoreinstances.features\Phantom.Workspaces.Agent.Gui\Controls\AgentChatOutputControl.axaml.csHistoryPopulated; empty catches swallow exceptions.features\Phantom.Workspaces.Agent.Gui\Controls\ChatOutputHtmlModels.csLoadHistoryChunksAsync; internalhistoryLoadingnever surfaced; only catchesOperationCanceledException.features\Phantom.Workspaces.Agent.Gui\Controls\ChatOutputHtmlRenderer.csHistoryContainerIdconstant;.chat-errorCSS class.features\Phantom.Workspaces.Agent.Gui\Controls\chat-output-shell.htmlfeatures\Phantom.Workspaces.Agent.Gui\ViewModels\AgentViewModel.csHistoryPopulated; needsIsRestoringHistory+RestoreError.Design / Fix
Load remote history from the remote store over the dev tunnel (for display). In
TransportTrustedExecutor.CreateAgentChatAsync(~L43-65), instead of settingAgentPersistenceStoreOverride = NullAgentPersistenceStore.Instance, provide anIAgentPersistenceStorethat reads the session's history from the remote data store over the existing dev-tunnel data-access channel (i.e. the sameWebClientAgentPersistenceStore/ReconnectingWebAgentPersistenceStorepath already used elsewhere in the app; the remote server exposes it viaAgentPersistenceEndpointRouteBuilderExtensionson top ofMongoDbAgentPersistenceStore).Constraint (preserve the
NullAgentPersistenceStoreintent): the local display facade must remain read-only for outbound messages. That is:ReadMessagesAsync/RestoreAsyncshould hit the remote store over the dev tunnel and return real history for display, butStoreAsync/AddSubAgentLinkAsyncon the display facade must NOT write — the remote agent owns authoritative writes, so we must not prepend locally-stored history to outbound messages. A thin read-only wrapper overWebClientAgentPersistenceStore(delegating reads, no-op on writes) satisfies this.The dev-tunnel endpoint / access token to use is the one associated with the session's host user-computer-profile (resolved the same way
TransportTrustedExecutoralready resolves the transport target viaExecutionTargetResolver)."Restoring history ..." HTML indicator. In
chat-output-shell.htmladd sibling elements near ~L506:Add
HistoryStatusId/HistoryErrorIdconstants inChatOutputHtmlRenderer.csnear ~L33. Drive them fromChatOutputHtmlModels.cs: showRestoring history ...when history load begins (~L1650,historyLoading = true) and clear when the load completes (~L2063). Surface the state onAgentViewModelasIsRestoringHistory(near ~L288) so the indicator also covers the async remote fetch window — the indicator must remain until history is fully restored (both the remote-store fetch and the local chunked render), not just for the local chunk render.Surface restore exceptions. Wrap
LoadHistoryChunksAsync(~L1972+) and the remote fetch incatch (Exception ex)that writesex.ToString()into the newchat-history-errorelement (styled with.chat-errorfromChatOutputHtmlRenderer.cs~L532). Replace the emptycatchblocks inAgentChatOutputControl.axaml.cs(~L305-309, ~L351-354) with error-banner reporting. Add aRestoreErrorstring onAgentViewModel.Full-restore semantics.
IsRestoringHistoryreflects the union of the async phases (remote fetch + local chunked render) and only transitions tofalseafter both complete.Considered / Background
WebClientAgentPersistenceStoreand theAgentPersistenceEndpointRouteBuilderExtensionsroutes. The fix reuses that channel rather than inventing a new transport message.NullAgentPersistenceStoreintact and adding a separate "restore history over dev tunnel" call that populatesAgentChat.Historypost-InitializeAsyncis an alternative. Wiring the read-only web-client store asAgentPersistenceStoreOverrideis preferred because it letsInitializeAsyncpopulateHistoryand completeHistoryPopulatedthrough its existing code path.Expected Tests
Existing test files to align with (read before implementing):
features\Phantom.Workspaces.Agent.Gui.Tests\AgentChatOutputControlTests.csfeatures\Phantom.Workspaces.Agent.Gui.Tests\ChatOutputHtmlRendererTests.csfeatures\Phantom.Workspaces.Agent.Gui.Tests\ChatOutputHtmlModelTests.csfeatures\Phantom.Workspaces.Agent.Gui.Tests\AgentViewModelTests.csfeatures\Phantom.Workspaces.Llm.Core.Tests\AgentChatPersistenceTests.csfeatures\Phantom.Workspaces.Llm.Core.Tests\TransportTrustedExecutorTests.cs(if present; otherwise add a new file next to the otherPhantom.Workspaces.Llm.Core.Teststransport tests)features\Phantom.Workspaces.Data.Web.Client.Tests\WebClientAgentPersistenceStoreTests.csNaming convention:
Subject_Scenario_ExpectedOutcome(PascalCase).ChatOutputShellHtml_ContainsHistoryStatusAndErrorContainersAgentChatOutputControlTestschat-history-statusandchat-history-errorsibling elements next tochat-history-container.ChatOutput_WhileRestoringHistory_ShowsRestoringIndicatorChatOutputHtmlModelTestsRestoring history ...indicator is shown while restore is in progress and hidden once history is fully restored.LoadHistoryChunks_WhenRestoreThrows_EmitsErrorBannerChatOutputHtmlModelTestsLoadHistoryChunksAsyncis rendered into thechat-history-errorelement (styled.chat-error) instead of being swallowed.TransportTrustedExecutor_RemoteSession_UsesDevTunnelBackedPersistenceStoreForReadTransportTrustedExecutorTestsAgentPersistenceStoreOverrideis set to a dev-tunnel-backed read-only store (delegating reads toWebClientAgentPersistenceStore) rather thanNullAgentPersistenceStore.TransportTrustedExecutor_RemoteSession_LoadsHistoryFromRemoteStoreOverDevTunnelTransportTrustedExecutorTestsReadMessagesAsyncagainst the dev-tunnelIAgentPersistenceStorefor the session id, andAgentChat.Historyis populated with the returned messages beforeHistoryPopulatedcompletes.TransportTrustedExecutor_RemoteSession_DisplayStoreDoesNotWriteOnStoreTransportTrustedExecutorTestsAgentChatno-ops onStoreAsync/AddSubAgentLinkAsync, preserving theNullAgentPersistenceStore"no outbound prepend / no local write" intent.AgentViewModel_IsRestoringHistory_TrueUntilHistoryFullyRestoredAgentViewModelTestsIsRestoringHistoryistruefrom restore start and only transitions tofalseafter both the remote fetch and the local chunked render complete.AgentChatOutputControl_WhenHistoryPopulatedThrows_ReportsRestoreErrorAgentChatOutputControlTestsawait vm.HistoryPopulatedare surfaced asRestoreError/ an error banner instead of being swallowed by empty catch blocks.Exact method/class names above should be reconciled against the existing test files before implementation.