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
Full-remote AgentChat ownership and the attach-agent-session proxy protocol are designed here but are not implemented.
ChatClientOverTransport, ChatClientTransportSession, and the split-client design in remote-chat-client-session.md are reusable lower-level mechanisms. They remote an IChatClient/Copilot session, not a complete running AgentChat, and must not be described as an existing full-session attach implementation.
Existing local sessions and legacy session entities must remain usable.
The new protocol must use the existing ITransport / message-channel infrastructure rather than creating a second network stack.
Session/profile association and persistence
Every newly created agent-session entity must persist exactly one owning user-computer-profile reference.
A legacy session with no owner is associated with the current user-computer-profile when it is next started successfully. That write is the only legacy migration.
Persist the session owner, an ownership generation, executor/profile bindings, the selected trust-profile reference plus expected revision needed to reconstruct effective trust intent, and the explicit per-session continue-in-background preference. The preference defaults to false; a missing value on a legacy entity is read as false.
Do not persist an MXC SDK object, SandboxPolicy, SandboxRequest, process handle, policy-file path, or other host-specific runtime state.
The agent-session entity-type-view groups sessions by the persisted owning-profile field using the existing group-by-parent mechanism. Unstarted legacy sessions remain ungrouped.
Starting, attaching, and taking over
Opening a session owned by the current profile starts or reuses the local owning runtime.
Opening a session owned by another profile offers:
Connect on owning profile ΓÇö start there if absent, otherwise attach to the existing runtime.
Resume locally ΓÇö perform an ownership takeover, terminate the old owning runtime, persist the new owner/generation, and reconstruct locally.
Before showing that choice, the GUI performs an authorized status query and displays Running, Not running, or Unavailable for the owning profile. Unauthorized/not-found are both Unavailable and reveal no session metadata.
Every session-opening and restore path must enforce the same decision.
At most one owning runtime may accept mutations for an ownership generation. Takeover must use an ownership-generation compare/exchange or equivalent lease check.
If the old owner cannot confirm termination and its ownership lease has not expired, takeover fails closed rather than starting a concurrent local AgentChat.
Takeover rebuilds AgentServices, resolves the effective TrustProfile, and recompiles any required process confinement on the new host. Runtime MXC state is never migrated.
Full-remote AgentChat
The owning host constructs the real AgentChat, its AgentServices, persistence services, tool providers, and current-session identity from that host's WorkspaceEntitySession / CurrentSessionContext.
The owning host resolves executor bindings and routes each component to its selected execution host.
The host that actually launches a process resolves the referenced effective TrustProfile, checks the expected revision, compiles MXC locally, and launches through the shared process executor.
A local proxy mirrors history, queue state, streaming state, busy state, tools, subagents, modals, and errors. It submits user input through the common queue API and forwards modal responses and interrupt/terminate requests.
The proxy does not launch remote Copilot, stdio MCP, or tool subprocesses and must never claim to enforce their containment.
Opening a remote subagent creates another authorized proxy to the remote subagent; it does not re-parent the subagent locally.
Split-client topology
The topology in remote-chat-client-session.md remains distinct: the local host owns AgentChat, while a remote model host owns the Copilot client/process.
In split-client mode, the remote model host resolves and compiles MXC for the Copilot process. Each tool's selected executor host independently resolves and compiles MXC for subprocesses it launches.
Component executor bindings from per-component-executor-binding.md remain the source of execution placement. Agent ownership does not override an explicit component binding.
GUI-only and entity-only tools remain in-process application-authorization surfaces. They are not process launches and must not be represented as MXC-contained.
Tool-schema authorization, client-instance authorization, and MXC confinement are cumulative and remain separate.
Attach protocol and UI
attach-agent-session is a dedicated bidirectional session protocol, separate from the chat-client request/response protocol.
Initial attach returns an authoritative snapshot followed by ordered deltas. Each frame carries an owning-runtime epoch and monotonically increasing sequence.
Reconnect supplies the last applied epoch/sequence. The host resumes from retained deltas when possible and otherwise sends a fresh snapshot. Reconnect does not reconstruct AgentChat or relaunch children.
The owning AgentChat is authoritative for every input queue. Local and remote GUIs submit acknowledged commands with stable queue/item ids, a command id, and an expected revision; the owner applies each command once and broadcasts the resulting ordered delta. GUI code never receives a mutable owner collection or mutates queue projections optimistically.
A session snapshot contains every default, immediate, held, and custom queue. Queue deltas share the session epoch/global sequence, so reconnect and multiple attached GUIs converge on the same owner state.
The proxy is registered in IRunningAgentChatTable; the existing RunningAgentBrainViewModel.Rows collection and RunningAgentBrainControl popup show local owned sessions and remote sessions to which this instance is attached, but do not discover unrelated remote sessions.
Tools and subagents are mirrored for display and navigation; execution remains on the owning/routed host.
Each top-level running row exposes interrupt and terminate. A remote row also exposes a Continue in background checkbox that updates the persisted session preference through the owner; subagents remain filtered out.
A session can raise multiple modal requests. Each agent editor displays its own modal stack above its input and gates only that editor. The root editor aggregates descendant modal presence for the tab notification.
Notifications are keyed by (tab id, kind) so future kinds coexist. chat-idle clears on tab
activation; modal-pending clears only when all relevant modals are dismissed. NotificationsViewModel.HasUnread remains the application-wide logical OR of every unread kind.
Lifecycle
An owning runtime has a host-owned RemoteAgentSessionLease; each attached GUI has an independent RemoteAgentAttachmentLease. The runtime serializes attachment-count, retention-preference, and termination transitions under one lifecycle gate.
continue-in-background is an explicit persisted per-session preference, defaults to false, and is copied into every runtime snapshot. When the last viewer detaches and the value is false, the owner gracefully stops the runtime. When it is true, the runtime may continue with zero viewers until explicit termination, takeover, owner-host shutdown, or runtime-lease expiry.
Explicit detach, proxy disposal, UI tab close, and graceful viewer-application shutdown release that viewer immediately. Unexpected channel/transport loss instead reserves that logical attachment for a five-second reconnect grace period; reconnect with the same attachment token cancels expiry without changing the logical viewer count. When the grace expires, the attachment is released and the last-viewer rule runs.
A new attach racing with last-viewer stop is serialized by the lifecycle gate. If attachment reservation wins, stop is cancelled and attach receives the existing epoch. If fencing wins, that epoch accepts no attach or mutation; Attach returns the indistinguishable not-found result and StartOrAttach may create a fresh epoch only after terminal persistence and registry removal complete.
Multiple viewers are independent: removing any non-final viewer never stops the runtime. Setting continue-in-background to true preserves a zero-viewer runtime; setting it to false while viewers remain changes persistence and the snapshot but does not interrupt the run; setting it to false when the viewer count is already zero starts graceful stop immediately.
Owner-host application shutdown and runtime-lease expiry stop every runtime regardless of the preference. Viewer-application shutdown is only a graceful detach. A host crash cannot emit a terminal frame, but process containment kills owned children and recovery records the interrupted epoch as stopped before any new epoch starts; the persisted preference remains unchanged and does not itself imply auto-resume.
Graceful stop first fences the epoch so it accepts no new mutations, cancels/interrupts an active turn if needed, disposes AgentChat, component transports, MXC/process-executor leases, wrappers, stdio MCP transports, and contained child sessions/trees, persists the terminal completion and stopped state, then removes the registry entry. It emits exactly one session-terminal event after persistence and before closing attachment channels where the transport remains writable.
Interrupt cancels only the active run; terminate ends the owning runtime. These are distinct protocol verbs and UI actions.
Security and failure semantics
attach-agent-session authenticates the transport peer and authorizes that peer for the requested owning profile and session before revealing whether the session exists.
Attach authorization is evaluated on every initial attach, reconnect, child-subagent attach, mutation verb, and takeover request. Existing execution-target reachability alone is not sufficient proof of session access.
MXC does not authorize attach, and transport authorization does not sandbox processes.
A required containment compile, handoff, wrapper, or launch failure fails closed. No layer retries with a null policy or direct uncontained launch.
Host-local details remain in protected logs. Wire errors contain only a stable error code, safe operation category, retryability, user-safe message, and correlation id. They exclude policy JSON, grants, local paths, environment values, command arguments, stderr containing secrets, native handles, and credentials.
operation-error terminates the affected operation without necessarily terminating the owning session. A fatal runtime error additionally emits session-terminal.
MXC is a preview dependency and must not be described as a production-grade security boundary.
Non-goals
Discovering every running session on every profile.
Migrating live AgentChat, process handles, wrapper handoffs, or compiled MXC state between hosts.
Sending compiled MXC policy across transport.
Replacing transport authentication with containment or replacing application authorization with MXC.
Sandboxing in-process GUI/entity operations.
Merging full-remote and split-client topologies into one ambiguous mode.
Replacing ConPTY or containing processes Phantom does not launch.
Considered / Background
Option A — Extend ChatClientOverTransport
Add session attach, tools, subagents, modals, lifecycle, and replay to the existing chat-client
protocol. This reuses framing, but overloads IChatClient, mixes per-run and whole-session lifetimes,
and makes reconnect and independent owning-runtime lifetime difficult.
Option B — Dedicated attach-agent-session protocol
Add a session-oriented listener/client pair over existing message channels and keep ChatClientOverTransport focused on lower-level remote chat-client execution. This adds a protocol
vocabulary and proxy abstraction, but gives ownership, authorization, replay, lifecycle, multiple
viewers, and subagent channels explicit boundaries.
Option C — Entity-store mirror plus thin control channel
Read persisted history/tools/subagents through entity observation and use a channel only for live
state and control. This reduces control-channel data, but creates two ordering domains and cannot
reliably reconstruct unpersisted streaming, queue, modal, and lifecycle state.
Chosen design
Approach
Choose Option B, a dedicated attach-agent-session protocol.
The owning host holds a real AgentChat behind a host-owned runtime lease. An authorized RemoteAgentSessionClient receives a snapshot and ordered event stream and backs the new RemoteAgentChat implementation of the new common IAgentChat surface. IRunningAgentChatTable, AgentViewModel, and session tabs consume IAgentChat, allowing the existing concrete AgentChat and the proxy to participate without subclassing the currently sealed AgentChat.
ChatClientOverTransport is unchanged in role: it is the lower-level remote IChatClient mechanism used by split-client execution. It is not the remote-session proxy.
All APIs in this chosen design follow the named-initializer convention: APIs carrying multiple independent values use property-based *Request or *Options records, with semantically required members declared required init and optional members given explicit defaults. Constructors remain only for compact value objects that enforce a scalar invariant or for small cohesive dependency sets. Protocol DTO/property records preserve the version-1 kebab-case serializer contract, required/optional wire members, strict unknown-member rejection, and fixed concrete discriminators; only their C# construction shape changes.
Rationale
One ordered session stream avoids entity/control ordering races and gives authorization, reconnect, viewer lifetime, takeover, and sanitized failures explicit homes. A common IAgentChat surface resolves the old subclassing ambiguity without making AgentChat virtual merely for remoting. Separate owning-runtime and attachment leases permit a short reconnect grace without making viewer lifetime ambiguous: by default the final released attachment stops the runtime, while an explicit persisted preference keeps it alive.
Relationship to MXC process containment
Remote ownership and containment compose as three axes:
Axis
Decision
Authority
Agent placement
Which host owns AgentChat and publishes its event stream?
For full-remote operation, the owning host constructs AgentChat and routes components. If it launches Copilot or stdio MCP locally, it resolves and compiles there. If a binding sends a component to another host, that final host resolves and compiles there. The viewer never supplies a compiled policy.
For split-client operation, local AgentChat owns routing and persistence, the remote model host confines Copilot, and each tool executor confines its own subprocesses. The same launch-host rule applies even though no full-session proxy is involved.
The only approved serialization of MxcProcessPolicy is the protected, one-use, same-machine Copilot wrapper handoff in #1476. Machine boundaries carry stable trust-profile identity/revision and component binding intent, never compiled policy or runtime handles.
Session lease owns children; the final released attachment stops it by default.
Errors
Sanitized ordered protocol events
Structured local compiler/executor diagnostics
Map detailed host error to safe wire DTO with correlation id.
Security
Peer/session attach authorization
Filesystem/network process confinement
Both checks are required and neither substitutes for the other.
Impedance mismatches and resolutions
Per-run IChatClient lifetime versus persistent AgentChat lifetime. Resolution: retain chat-client for split-client calls and add attach-agent-session for whole-session ownership, replay, and multiple viewers.
AgentChat is sealed but the UI and running table need a proxy with equivalent behavior. Resolution: introduce IAgentChat; adapt the existing AgentChat to implement it and add RemoteAgentChat. Do not subclass or add transport branches throughout AgentChat.
Connection descriptors select a host, while MXC policy is host-specific. Resolution: persist descriptors and trust references/revisions. The selected launch host resolves, canonicalizes, compiles, and applies policy locally.
Viewer/channel disposal must normally stop an unobserved remote session without making transient network loss destructive. Resolution: separate RemoteAgentSessionLease from RemoteAgentAttachmentLease, count logical viewers under the runtime lifecycle gate, and reserve an unexpectedly lost attachment for five seconds. Final explicit detach, or grace expiry with continue-in-background == false, gracefully stops the runtime; the persisted opt-in is the only ordinary zero-viewer retention path.
Takeover wants continuity, while confinement contains host-local paths, capabilities, temp directories, and handles. Resolution: transfer persisted history and stable intent only. Terminate the old runtime, advance ownership generation, rebuild services, and compile fresh policy on the new host.
Transport errors need useful UI messages, while MXC diagnostics may expose paths or policy details. Resolution: map local diagnostics to RemoteAgentOperationError; retain details under a correlation id in host logs.
Full-remote routing may include GUI/entity tools that are not child processes. Resolution: route them according to the application/component binding and enforce normal application authorization. Do not fabricate an MXC guarantee for in-process code.
Interrupt and shutdown have different child-process consequences. Resolution:interrupt cancels the active turn while retaining the runtime and reusable clients; terminate-session releases the runtime lease and disposes all process-backed resources.
Reconnect can be mistaken for session recreation. Resolution: reconnect by attachment token, runtime epoch, and replay cursor during the five-second transport-loss grace. Reattach to the existing runtime and use a fresh snapshot when replay is unavailable. After grace expiry stops a default-policy runtime, Attach cannot resurrect it and StartOrAttach creates a new epoch only after stop completes.
Existing queue classes expose owner objects and use indexes for some edits. Resolution: retain AgentInputQueue, AgentChatQueue, AgentInputQueueManager, and AgentChatQueueManager as local implementation types, but expose immutable queue/item snapshots
through IAgentInputQueues. Assign stable ids at creation/enqueue, use indexes only as placement
hints, and route every mutation through owner-authoritative revisioned commands.
Steering is an execution decision, not a separate kind of user input. Resolution: there is no steering member on IAgentChat and no steer protocol verb. A GUI
enqueues through the same queue API whether a run is idle or active. The owning AgentChat
decides from queue immediacy, mode, and current-run state whether to consume that item as
Copilot steering or as a future turn.
Request/options type audit
The complete design contains exactly 28 request/options types: 22 public and 6 internal. Public: CreateAgentInputQueueRequest, DeleteAgentInputQueueRequest, EnqueueAgentInputRequest, EditAgentInputQueueItemRequest, RemoveAgentInputQueueItemRequest, MoveAgentInputQueueItemRequest, ConfigureAgentInputQueueRequest, RemoteAgentChatAttachOptions, AgentSessionStatusRequest, TerminateAgentSessionRequest, OpenAgentSubagentRequest, RespondToAgentModalRequest, SetAgentToolEnabledRequest, SetAgentSessionRetentionRequest, AgentSessionOpenRequest, CreateAgentSessionEntityDataRequest, AcquireAgentChatRequest, AgentViewModelOptions, CreateAgentSessionTabForRestoreRequest, CreateAgentSessionTabRequest, ComposeSessionAgentViewModelOptions, and NotificationTargetRequest. Internal: AgentSessionAuthorizationRequest, OpenAgentSessionHostRequest, TerminateAgentSessionRuntimeRequest, UpdateAgentSessionRuntimeRetentionRequest, AttachRemoteAgentSessionRequest, and AgentSessionTakeoverRequest.
Every one follows the named-initializer convention above. Property-shape tests reflect RequiredMemberAttribute, instantiate documented optional defaults, verify named mapping, and, for wire DTOs, verify exact kebab-case serialization and fixed discriminators. The unchanged #1474ProcessExecutionRequest is excluded from this audit.
Test matrix audit
The complete committed matrix contains 373 scenario bullets representing 376 named test methods;
three integration bullets each name two methods. Existing unchanged tests retain their scope. Shape
changes add or update the committed named tests for Usage defaults/named initialization, AgentInformation required members/named initialization, queue request required/default/named
mapping, client request named mapping, protocol DTO required/default/fixed-discriminator mapping,
runtime/authorization/host request mapping, entity-factory request mapping, AgentViewModelOptions,
session UI request/options mapping, and notification property/target-request mapping. The client
matrix tests direct InterruptAsync(Guid commandId, CancellationToken ct = default) and contains no
interrupt request-record shape test.
Implementation plan
Each step is independently committable and leaves existing local behavior passing.
Commit 1 - #1484 - Persist and hydrate runtime intent
Files:Phantom.Workspaces.Data.Core/JsonSchemas/agent-session.json, AgentSessionEntityFactory, CreateAgentSessionEntityDataRequest, AgentSessionRuntimeIntentData, PersistedAgentSessionRuntimeIntent, AgentSessionRuntimeContext, IAgentSessionRuntimeContextFactory, AgentSessionRuntimeContextFactory, JsonEntities/entity-type-views/agent-session-entity-type-view.json, and acquisition callers. Tests: request required-member/default/named-initializer mapping, factory, default/true background persistence, restart hydration, round-trip, legacy,
grouping, malformed, no-policy, and fresh/non-GUI acquisition tests above. Boundary: follows #1481: entity JSON is interpreted at the shared acquisition layer, never in GUI
routing. It consumes the already-present AgentSessionExecutorBindings and ExecutorBindings
surfaces on features; it neither waits for nor recreates the later MXC APIs. Dependencies: none.
Usage uses null for an unreported metric; reported counts and USD cost are nonnegative. AgentInformation requires non-null, nonblank values for its first five strings and a non-null complete AgentDefinition; CurrentModelId is null or nonblank. Publishers and the codec validate invariants, construct/deserialize a complete replacement, assign it atomically on the foreground scheduler, and then raise one corresponding change event per applied session sequence. Record equality is value equality. Both records use the existing JSON options and kebab-case protocol naming.
For every authorized viewer, AgentInformation carries the full AgentDefinition; authorization completes before serialization and there is no field-level redaction after authorization. The owner serializes it with AgentDefinition.ToJson() and the client deserializes it with PhantomAgentSchema.AgentDefinitionFromJson(string). Unauthorized peers receive no definition bytes or other session metadata.
Interrupt intentionally has no request record: there is no InterruptAgentSessionRequest. The client API is exactly Task InterruptAsync(Guid commandId, CancellationToken ct = default); the internal property-based InterruptCommand carries the shared required command/correlation/epoch members and its fixed interrupt discriminator.
Files:IAgentChat, property-based Usage and AgentInformation, IAgentInputQueues, the seven queue request types and property-based snapshot/configuration/result types,
owner queue adapter, stable ids/revisions in the existing queue domain, AgentChat, RunningAgentChat, RunningAgentChatLease, RunningAgentChatWithEntityInfo, AgentViewModel, AgentViewModelOptions, and InputQueueViewModel. Tests: required-init metadata, optional defaults, named initializers, serialization, AgentChatInterfaceTests, AgentInputQueuesTests, internal Copilot/non-Copilot queue consumption
tests, options/common-surface tests, and unchanged local regression suite. Migrate
the UI away from concrete queue collections and index identity. No transport behavior yet. Dependencies: none.
Commit 3 - #1486 - Add strict protocol and proxy state
Files:Phantom.Workspaces.Llm.Core/Remote/AgentSessionProtocol.cs, property-based protocol DTOs
and codec, RemoteAgentSessionClient, its public request types, RemoteAgentChat, and RemoteAgentChatAttachOptions. Tests: required-init metadata, optional defaults, named-initializer construction, exact discriminator/serialization round trips, codec, all client public methods (including direct InterruptAsync(Guid commandId, CancellationToken ct = default) and no interrupt request type), atomic usage/information, proxy queue parity,
full authorized AgentDefinition, no metadata on denial, background command/event/snapshot,
revisions/conflicts/deduplication, state/events/commands, cancellation, and disposal. The protocol
contains enqueue/edit/remove/move/configure/create/delete queue commands and no steering command.
Use an in-memory IMessageChannel; no server runtime or MXC dependency. Dependencies:#1485.
Files: existing #1476CopilotRuntimeConnectionFactory handoff and #1477 ProcessExecutorBackedClientTransport/ProcessOwnedMcpTransport integration only; no replacement
types. Tests: direct versus wrapper selection, stdio ownership, constrained HTTP/SSE rejection,
fail-closed launch, sanitized projection, and no compiled policy on a machine frame. Dependencies:#1490 and #1473-#1477.
Files:AgentViewModel, AgentSessionWorkspaceTabViewModel, editor/modal controls, Phantom.Workspaces/ViewModels/RunningAgentBrainViewModel.cs, Phantom.Workspaces/ViewModels/RunningAgentRowViewModel.cs, Phantom.Workspaces/Controls/RunningAgentBrainControl.axaml(.cs), Notification, NotificationEntry, NotificationTargetRequest, INotificationService, NotificationService, and NotificationsViewModel. Tests: UI request/options required-member/default/named-initializer tests, UI public API tests above, multiple modal ownership, descendant aggregation, independent
notification clearing, remote interrupt versus terminate, and checkbox binding, command,
enabled/pending state, and accessibility. Dependencies:#1485, #1488, and #1489.
Commit 10 - #1493 - End-to-end security and lifecycle validation
Files: tests only except defects found by integration. Tests: transport ordering, replay/snapshot fallback, concurrent viewers, authorization denial,
cancellation, default/background lifecycle, reconnect grace, attach/stop and terminate races,
persistence/restart, disposal, takeover, fail-closed MXC, complete placement/containment
cross-product, no compiled policy on wire, and no cross-session CurrentSessionContext
contamination. Dependencies:#1484-#1492 and completed #1471-#1477 surfaces.
Sub-issues
Complete in dependency order (items without mutual dependencies may proceed in parallel):
Design source
https://github.com/JoshuaRowePhantom/Phantom.Workspaces/blob/0c6578641befad31d5a38003baf6c0f0fe64748a/docs/design/remote-agent-sessions.md
Requirements
Status and compatibility
AgentChatownership and theattach-agent-sessionproxy protocol are designed here but are not implemented.ChatClientOverTransport,ChatClientTransportSession, and the split-client design inremote-chat-client-session.mdare reusable lower-level mechanisms. They remote anIChatClient/Copilot session, not a complete runningAgentChat, and must not be described as an existing full-session attach implementation.ITransport/ message-channel infrastructure rather than creating a second network stack.Session/profile association and persistence
continue-in-backgroundpreference. The preference defaults tofalse; a missing value on a legacy entity is read asfalse.SandboxPolicy,SandboxRequest, process handle, policy-file path, or other host-specific runtime state.MxcProcessPolicy, approved by [mxc] - Compile effective trust profiles into MXC sandbox policies #1475 as a versioned local execution DTO, may cross only the protected same-machine wrapper handoff defined by [mxc] - Launch copilot.exe through the MXC process executor #1476. It must not cross a machine transport or be persisted as session authority.group-by-parentmechanism. Unstarted legacy sessions remain ungrouped.Starting, attaching, and taking over
Not running, or Unavailable for the owning profile. Unauthorized/not-found are both
Unavailable and reveal no session metadata.
AgentChat.AgentServices, resolves the effectiveTrustProfile, and recompiles any required process confinement on the new host. Runtime MXC state is never migrated.Full-remote
AgentChatAgentChat, itsAgentServices, persistence services, tool providers, and current-session identity from that host'sWorkspaceEntitySession/CurrentSessionContext.TrustProfile, checks the expected revision, compiles MXC locally, and launches through the shared process executor.Split-client topology
remote-chat-client-session.mdremains distinct: the local host ownsAgentChat, while a remote model host owns the Copilot client/process.per-component-executor-binding.mdremain the source of execution placement. Agent ownership does not override an explicit component binding.Process and non-process tools
ProcessExecutorBackedClientTransportfrom [mxc] - Execute MCP tools through MXC-constrained process executor #1477. A null policy selects the ordinary-process branch; a required policy selects MXC.Attach protocol and UI
attach-agent-sessionis a dedicated bidirectional session protocol, separate from thechat-clientrequest/response protocol.AgentChator relaunch children.AgentChatis authoritative for every input queue. Local and remote GUIs submit acknowledged commands with stable queue/item ids, a command id, and an expected revision; the owner applies each command once and broadcasts the resulting ordered delta. GUI code never receives a mutable owner collection or mutates queue projections optimistically.IRunningAgentChatTable; the existingRunningAgentBrainViewModel.Rowscollection andRunningAgentBrainControlpopup show local owned sessions and remote sessions to which this instance is attached, but do not discover unrelated remote sessions.(tab id, kind)so future kinds coexist.chat-idleclears on tabactivation;
modal-pendingclears only when all relevant modals are dismissed.NotificationsViewModel.HasUnreadremains the application-wide logical OR of every unread kind.Lifecycle
RemoteAgentSessionLease; each attached GUI has an independentRemoteAgentAttachmentLease. The runtime serializes attachment-count, retention-preference, and termination transitions under one lifecycle gate.continue-in-backgroundis an explicit persisted per-session preference, defaults tofalse, and is copied into every runtime snapshot. When the last viewer detaches and the value isfalse, the owner gracefully stops the runtime. When it istrue, the runtime may continue with zero viewers until explicit termination, takeover, owner-host shutdown, or runtime-lease expiry.Attachreturns the indistinguishable not-found result andStartOrAttachmay create a fresh epoch only after terminal persistence and registry removal complete.continue-in-backgroundtotruepreserves a zero-viewer runtime; setting it tofalsewhile viewers remain changes persistence and the snapshot but does not interrupt the run; setting it tofalsewhen the viewer count is already zero starts graceful stop immediately.AgentChat, component transports, MXC/process-executor leases, wrappers, stdio MCP transports, and contained child sessions/trees, persists the terminal completion and stopped state, then removes the registry entry. It emits exactly onesession-terminalevent after persistence and before closing attachment channels where the transport remains writable.Security and failure semantics
attach-agent-sessionauthenticates the transport peer and authorizes that peer for the requested owning profile and session before revealing whether the session exists.operation-errorterminates the affected operation without necessarily terminating the owning session. A fatal runtime error additionally emitssession-terminal.Non-goals
AgentChat, process handles, wrapper handoffs, or compiled MXC state between hosts.Considered / Background
Option A — Extend
ChatClientOverTransportAdd session attach, tools, subagents, modals, lifecycle, and replay to the existing
chat-clientprotocol. This reuses framing, but overloads
IChatClient, mixes per-run and whole-session lifetimes,and makes reconnect and independent owning-runtime lifetime difficult.
Option B — Dedicated
attach-agent-sessionprotocolAdd a session-oriented listener/client pair over existing message channels and keep
ChatClientOverTransportfocused on lower-level remote chat-client execution. This adds a protocolvocabulary and proxy abstraction, but gives ownership, authorization, replay, lifecycle, multiple
viewers, and subagent channels explicit boundaries.
Option C — Entity-store mirror plus thin control channel
Read persisted history/tools/subagents through entity observation and use a channel only for live
state and control. This reduces control-channel data, but creates two ordering domains and cannot
reliably reconstruct unpersisted streaming, queue, modal, and lifecycle state.
Chosen design
Approach
Choose Option B, a dedicated
attach-agent-sessionprotocol.The owning host holds a real
AgentChatbehind a host-owned runtime lease. An authorizedRemoteAgentSessionClientreceives a snapshot and ordered event stream and backs the newRemoteAgentChatimplementation of the new commonIAgentChatsurface.IRunningAgentChatTable,AgentViewModel, and session tabs consumeIAgentChat, allowing the existing concreteAgentChatand the proxy to participate without subclassing the currently sealedAgentChat.ChatClientOverTransportis unchanged in role: it is the lower-level remoteIChatClientmechanism used by split-client execution. It is not the remote-session proxy.All APIs in this chosen design follow the named-initializer convention: APIs carrying multiple independent values use property-based
*Requestor*Optionsrecords, with semantically required members declaredrequired initand optional members given explicit defaults. Constructors remain only for compact value objects that enforce a scalar invariant or for small cohesive dependency sets. Protocol DTO/property records preserve the version-1 kebab-case serializer contract, required/optional wire members, strict unknown-member rejection, and fixed concrete discriminators; only their C# construction shape changes.Rationale
One ordered session stream avoids entity/control ordering races and gives authorization, reconnect, viewer lifetime, takeover, and sanitized failures explicit homes. A common
IAgentChatsurface resolves the old subclassing ambiguity without makingAgentChatvirtual merely for remoting. Separate owning-runtime and attachment leases permit a short reconnect grace without making viewer lifetime ambiguous: by default the final released attachment stops the runtime, while an explicit persisted preference keeps it alive.Relationship to MXC process containment
Remote ownership and containment compose as three axes:
AgentChatand publishes its event stream?attach-agent-sessionhostExecutorBindings/ connection descriptorTrustProfile+ #1475 compiler + #1474 executorFor full-remote operation, the owning host constructs
AgentChatand routes components. If it launches Copilot or stdio MCP locally, it resolves and compiles there. If a binding sends a component to another host, that final host resolves and compiles there. The viewer never supplies a compiled policy.For split-client operation, local
AgentChatowns routing and persistence, the remote model host confines Copilot, and each tool executor confines its own subprocesses. The same launch-host rule applies even though no full-session proxy is involved.The only approved serialization of
MxcProcessPolicyis the protected, one-use, same-machine Copilot wrapper handoff in #1476. Machine boundaries carry stable trust-profile identity/revision and component binding intent, never compiled policy or runtime handles.Overlap matrix
AgentChatprotocolExecutorBindingsresolves component connection descriptorsImpedance mismatches and resolutions
Per-run
IChatClientlifetime versus persistentAgentChatlifetime.Resolution: retain
chat-clientfor split-client calls and addattach-agent-sessionfor whole-session ownership, replay, and multiple viewers.AgentChatis sealed but the UI and running table need a proxy with equivalent behavior.Resolution: introduce
IAgentChat; adapt the existingAgentChatto implement it and addRemoteAgentChat. Do not subclass or add transport branches throughoutAgentChat.Connection descriptors select a host, while MXC policy is host-specific.
Resolution: persist descriptors and trust references/revisions. The selected launch host resolves, canonicalizes, compiles, and applies policy locally.
[mxc] - Compile effective trust profiles into MXC sandbox policies #1475 defines a serializable
MxcProcessPolicy, which could be mistaken for a network DTO.Resolution: permit it only in-process and in [mxc] - Launch copilot.exe through the MXC process executor #1476's protected same-machine wrapper envelope. Remote contracts have no compiled-policy property and reject unknown attempts to add one.
Viewer/channel disposal must normally stop an unobserved remote session without making transient network loss destructive.
Resolution: separate
RemoteAgentSessionLeasefromRemoteAgentAttachmentLease, count logical viewers under the runtime lifecycle gate, and reserve an unexpectedly lost attachment for five seconds. Final explicit detach, or grace expiry withcontinue-in-background == false, gracefully stops the runtime; the persisted opt-in is the only ordinary zero-viewer retention path.Takeover wants continuity, while confinement contains host-local paths, capabilities, temp directories, and handles.
Resolution: transfer persisted history and stable intent only. Terminate the old runtime, advance ownership generation, rebuild services, and compile fresh policy on the new host.
Transport errors need useful UI messages, while MXC diagnostics may expose paths or policy details.
Resolution: map local diagnostics to
RemoteAgentOperationError; retain details under a correlation id in host logs.Full-remote routing may include GUI/entity tools that are not child processes.
Resolution: route them according to the application/component binding and enforce normal application authorization. Do not fabricate an MXC guarantee for in-process code.
Interrupt and shutdown have different child-process consequences.
Resolution:
interruptcancels the active turn while retaining the runtime and reusable clients;terminate-sessionreleases the runtime lease and disposes all process-backed resources.Reconnect can be mistaken for session recreation.
Resolution: reconnect by attachment token, runtime epoch, and replay cursor during the five-second transport-loss grace. Reattach to the existing runtime and use a fresh snapshot when replay is unavailable. After grace expiry stops a default-policy runtime,
Attachcannot resurrect it andStartOrAttachcreates a new epoch only after stop completes.Existing queue classes expose owner objects and use indexes for some edits.
Resolution: retain
AgentInputQueue,AgentChatQueue,AgentInputQueueManager, andAgentChatQueueManageras local implementation types, but expose immutable queue/item snapshotsthrough
IAgentInputQueues. Assign stable ids at creation/enqueue, use indexes only as placementhints, and route every mutation through owner-authoritative revisioned commands.
Steering is an execution decision, not a separate kind of user input.
Resolution: there is no steering member on
IAgentChatand nosteerprotocol verb. A GUIenqueues through the same queue API whether a run is idle or active. The owning
AgentChatdecides from queue immediacy, mode, and current-run state whether to consume that item as
Copilot steering or as a future turn.
Request/options type audit
The complete design contains exactly 28 request/options types: 22 public and 6 internal. Public:
CreateAgentInputQueueRequest,DeleteAgentInputQueueRequest,EnqueueAgentInputRequest,EditAgentInputQueueItemRequest,RemoveAgentInputQueueItemRequest,MoveAgentInputQueueItemRequest,ConfigureAgentInputQueueRequest,RemoteAgentChatAttachOptions,AgentSessionStatusRequest,TerminateAgentSessionRequest,OpenAgentSubagentRequest,RespondToAgentModalRequest,SetAgentToolEnabledRequest,SetAgentSessionRetentionRequest,AgentSessionOpenRequest,CreateAgentSessionEntityDataRequest,AcquireAgentChatRequest,AgentViewModelOptions,CreateAgentSessionTabForRestoreRequest,CreateAgentSessionTabRequest,ComposeSessionAgentViewModelOptions, andNotificationTargetRequest. Internal:AgentSessionAuthorizationRequest,OpenAgentSessionHostRequest,TerminateAgentSessionRuntimeRequest,UpdateAgentSessionRuntimeRetentionRequest,AttachRemoteAgentSessionRequest, andAgentSessionTakeoverRequest.Every one follows the named-initializer convention above. Property-shape tests reflect
RequiredMemberAttribute, instantiate documented optional defaults, verify named mapping, and, for wire DTOs, verify exact kebab-case serialization and fixed discriminators. The unchanged #1474ProcessExecutionRequestis excluded from this audit.Test matrix audit
The complete committed matrix contains 373 scenario bullets representing 376 named test methods;
three integration bullets each name two methods. Existing unchanged tests retain their scope. Shape
changes add or update the committed named tests for
Usagedefaults/named initialization,AgentInformationrequired members/named initialization, queue request required/default/namedmapping, client request named mapping, protocol DTO required/default/fixed-discriminator mapping,
runtime/authorization/host request mapping, entity-factory request mapping,
AgentViewModelOptions,session UI request/options mapping, and notification property/target-request mapping. The client
matrix tests direct
InterruptAsync(Guid commandId, CancellationToken ct = default)and contains nointerrupt request-record shape test.
Implementation plan
Each step is independently committable and leaves existing local behavior passing.
Commit 1 - #1484 - Persist and hydrate runtime intent
Files:
Phantom.Workspaces.Data.Core/JsonSchemas/agent-session.json,AgentSessionEntityFactory,CreateAgentSessionEntityDataRequest,AgentSessionRuntimeIntentData,PersistedAgentSessionRuntimeIntent,AgentSessionRuntimeContext,IAgentSessionRuntimeContextFactory,AgentSessionRuntimeContextFactory,JsonEntities/entity-type-views/agent-session-entity-type-view.json, and acquisition callers.Tests: request required-member/default/named-initializer mapping, factory, default/true background persistence, restart hydration, round-trip, legacy,
grouping, malformed, no-policy, and fresh/non-GUI acquisition tests above.
Boundary: follows #1481: entity JSON is interpreted at the shared acquisition layer, never in GUI
routing. It consumes the already-present
AgentSessionExecutorBindingsandExecutorBindingssurfaces on
features; it neither waits for nor recreates the later MXC APIs.Dependencies: none.
Commit 2 - #1485 - Add the common chat surface
Exact common state contracts
The state records have exactly this property-based shape; these names, fields, types, defaults, and semantics are normative:
Usageuses null for an unreported metric; reported counts and USD cost are nonnegative.AgentInformationrequires non-null, nonblank values for its first five strings and a non-null completeAgentDefinition;CurrentModelIdis null or nonblank. Publishers and the codec validate invariants, construct/deserialize a complete replacement, assign it atomically on the foreground scheduler, and then raise one corresponding change event per applied session sequence. Record equality is value equality. Both records use the existing JSON options and kebab-case protocol naming.For every authorized viewer,
AgentInformationcarries the fullAgentDefinition; authorization completes before serialization and there is no field-level redaction after authorization. The owner serializes it withAgentDefinition.ToJson()and the client deserializes it withPhantomAgentSchema.AgentDefinitionFromJson(string). Unauthorized peers receive no definition bytes or other session metadata.Interrupt intentionally has no request record: there is no
InterruptAgentSessionRequest. The client API is exactlyTask InterruptAsync(Guid commandId, CancellationToken ct = default); the internal property-basedInterruptCommandcarries the shared required command/correlation/epoch members and its fixedinterruptdiscriminator.Files:
IAgentChat, property-basedUsageandAgentInformation,IAgentInputQueues, the seven queue request types and property-based snapshot/configuration/result types,owner queue adapter, stable ids/revisions in the existing queue domain,
AgentChat,RunningAgentChat,RunningAgentChatLease,RunningAgentChatWithEntityInfo,AgentViewModel,AgentViewModelOptions, andInputQueueViewModel.Tests: required-init metadata, optional defaults, named initializers, serialization,
AgentChatInterfaceTests,AgentInputQueuesTests, internal Copilot/non-Copilot queue consumptiontests, options/common-surface tests, and unchanged local regression suite. Migrate
the UI away from concrete queue collections and index identity. No transport behavior yet.
Dependencies: none.
Commit 3 - #1486 - Add strict protocol and proxy state
Files:
Phantom.Workspaces.Llm.Core/Remote/AgentSessionProtocol.cs, property-based protocol DTOsand codec,
RemoteAgentSessionClient, its public request types,RemoteAgentChat, andRemoteAgentChatAttachOptions.Tests: required-init metadata, optional defaults, named-initializer construction, exact discriminator/serialization round trips, codec, all client public methods (including direct
InterruptAsync(Guid commandId, CancellationToken ct = default)and no interrupt request type), atomic usage/information, proxy queue parity,full authorized
AgentDefinition, no metadata on denial, background command/event/snapshot,revisions/conflicts/deduplication, state/events/commands, cancellation, and disposal. The protocol
contains enqueue/edit/remove/move/configure/create/delete queue commands and no steering command.
Use an in-memory
IMessageChannel; no server runtime or MXC dependency.Dependencies: #1485.
Commit 4 - #1487 - Add authorization, host, and viewer lifecycle
Files: property-based peer identity/authorization/host/runtime request records, peer identity provider, attach authorizer, listener, host, runtime registry, runtime and
attachment leases, replay buffer, transport composition registration.
Tests: required-member/default/named-initializer mapping for all five non-wire internal request types, listener, authorizer, host, registry, replay limits, multiple viewers, sanitized errors, and
no existence disclosure, including default final-viewer stop, persisted background retention,
five-second reconnect grace, attach/stop races, explicit-terminate precedence, complete graceful
cleanup, queue convergence, and reconnect snapshot/replay.
Dependencies: #1484 and #1486.
Commit 5 - #1488 - Integrate running table and all open paths
Files:
AcquireAgentChatRequest,IRunningAgentChatTable,RunningAgentChatTable,OpenAgentSessionShortcutHandler,CreateAgentSessionTabForRestoreRequest,CreateAgentSessionTabRequest,ComposeSessionAgentViewModelOptions, fresh launcher, auto-resume, non-GUI acquisition path.Tests: request required-member/default/named-initializer tests, every table method, local/remote single-flight, restore/fresh/auto-resume parity, and no
rehydration of an existing runtime. Includes local/remote background preference dispatch and
authoritative running-row metadata.
Dependencies: #1484, #1485, #1486, and #1487.
Commit 6 - #1489 - Add takeover and deterministic runtime shutdown
Files: persisted ownership compare/exchange, host takeover coordinator, runtime fencing,
termination acknowledgement, lease expiry.
Tests: successful takeover, stale generation/epoch, unconfirmed old lease, command fencing,
terminal ordering, ten-second renewal/thirty-second expiry, crash recovery, and complete child
cleanup.
Dependencies: #1487 and #1488.
Commit 7 - #1490 - Integrate host-local trust and component placement
Files: runtime hydration into
AgentServices,CurrentSessionContext,AgentExecutionTrustContext, existingExecutorBindingsconsumers, remote MCP/Copilot requests.Tests: stale trust revision, per-session context isolation, and all eight
agent-placement/component-placement/containment combinations.
Dependencies: #1488 and #1472, #1474, #1475, and #1477.
Commit 8 - #1491 - Integrate Copilot wrapper and stdio MCP ownership
Files: existing #1476
CopilotRuntimeConnectionFactoryhandoff and #1477ProcessExecutorBackedClientTransport/ProcessOwnedMcpTransportintegration only; no replacementtypes.
Tests: direct versus wrapper selection, stdio ownership, constrained HTTP/SSE rejection,
fail-closed launch, sanitized projection, and no compiled policy on a machine frame.
Dependencies: #1490 and #1473-#1477.
Commit 9 - #1492 - Complete remote UI, modals, and notifications
Files:
AgentViewModel,AgentSessionWorkspaceTabViewModel, editor/modal controls,Phantom.Workspaces/ViewModels/RunningAgentBrainViewModel.cs,Phantom.Workspaces/ViewModels/RunningAgentRowViewModel.cs,Phantom.Workspaces/Controls/RunningAgentBrainControl.axaml(.cs),Notification,NotificationEntry,NotificationTargetRequest,INotificationService,NotificationService, andNotificationsViewModel.Tests: UI request/options required-member/default/named-initializer tests, UI public API tests above, multiple modal ownership, descendant aggregation, independent
notification clearing, remote interrupt versus terminate, and checkbox binding, command,
enabled/pending state, and accessibility.
Dependencies: #1485, #1488, and #1489.
Commit 10 - #1493 - End-to-end security and lifecycle validation
Files: tests only except defects found by integration.
Tests: transport ordering, replay/snapshot fallback, concurrent viewers, authorization denial,
cancellation, default/background lifecycle, reconnect grace, attach/stop and terminate races,
persistence/restart, disposal, takeover, fail-closed MXC, complete placement/containment
cross-product, no compiled policy on wire, and no cross-session
CurrentSessionContextcontamination.
Dependencies: #1484-#1492 and completed #1471-#1477 surfaces.
Sub-issues
Complete in dependency order (items without mutual dependencies may proceed in parallel):