Summary
For a sub-agent (child) chat, get_current_session returns only the child's own agent-session entity plus the host user-computer-profile / user / computer / agent-definition. It does not expose an explicit parent agent-session field and has no explicit indicator that the session is a sub-agent. An agent invoking get_current_session inside a sub-agent cannot tell (a) that it is a sub-agent nor (b) which parent chat(s) dispatched it, except by inspecting a raw untyped array (parent-agent-session-ids) embedded in the whole-blob-serialized agent_session.data.
The tool must present the child agent's information plus a resolved parent-linkage field that:
- Handles the fact that
parent-agent-session-ids is an array — a session can have more than one parent — so the result field must be plural (parent_agent_sessions), not a singular parent_agent_session.
- Handles arbitrary nesting depth — a sub-agent may itself dispatch further sub-agents, forming a multi-parent DAG. Each session reports only its immediate parents; deeper ancestry is walked by the caller re-invoking
get_current_session (or reading the parent entity) for each parent. Depth is thereby handled implicitly and correctly for grandchildren-and-beyond.
- Exposes an explicit
is_subagent boolean (true iff parent-agent-session-ids is non-empty) so a nested session can detect it is a sub-agent at any depth without walking the DAG.
Root Cause
The get_current_session result shape is built in GetCurrentSessionTool.InvokeCoreAsync (Phantom.Workspaces.Llm.Core/CurrentSessionContextProvider.cs lines 104-112) as an anonymous object with fixed members agent_session, user_computer_profile, user, computer, agent_definition — there is no parent-linkage field and no sub-agent indicator.
The upstream CurrentSessionContext record (Phantom.Workspaces.Llm.Core/CurrentSessionContext.cs lines 11-27) carries AgentSessionId, UserComputerProfile, User, Computer, AgentDefinitionReference — no parent reference (singular or plural). Its shared factory CurrentSessionContextFactory.CreateForHostAsync (Phantom.Workspaces.Llm.Core/CurrentSessionContextFactory.cs lines 23-51) also has no parent parameter, so hosts that start a sub-agent session have no way to plumb the parent(s) through.
Meanwhile, parent linkage exists on the agent-session entity's data as an array field:
parent-agent-session-ids: "Entity references to the parent agent sessions of this session. Set on sub-agent sessions to reference the dispatcher's own agent-session entity, so sub-agents are excluded from the top-level sessions view. When absent or empty, the session is a top-level session." — Phantom.Workspaces.Data.Core/JsonSchemas/agent-session.json lines 37-46.
The schema declares it as "type": "array" of entity-reference items — the plurality (-ids) is intentional; a session may reference more than one parent.
The sub-agent dispatcher writes it when creating the child session entity:
["parent-agent-session-ids"] = new[] { (dispatcherEntityId ?? new EntityId(Guid.Empty)).ToString() },
(Phantom.Workspaces.Llm.Core/SubAgentDispatcherChatClient.cs line 598).
Findings on multi-parent in practice. Today the dispatcher always writes exactly one element (the dispatching session's entity id) at line 598, and there is no code path that adds or merges additional parents into an existing session's parent-agent-session-ids. Multi-parent is therefore schema-supported but not currently produced by production code paths. The tool result must still model plural parents so that (a) it does not need to change shape if/when a multi-parent producer is added, (b) consumers write correct code today, and (c) it faithfully mirrors the schema. sessions-view.json (lines ~98-104) also treats the field as an array, but currently only reads index 0 as an existence check — that is an unrelated simplification and is not evidence that only one parent is ever valid.
Findings on deep nesting. Nothing in the current code special-cases grandchildren. Because the dispatcher writes the parent(s) relative to the current dispatching session — whatever depth that is — a sub-agent that itself dispatches a sub-agent will naturally produce a grandchild whose parent-agent-session-ids references the middle sub-agent's entity. There is no accumulation of transitive ancestors on a single entity; ancestry is stored one level at a time and must be walked one level at a time.
Because GetCurrentSessionTool serializes the entire entity.Data as agent_session.data (CurrentSessionContextProvider.cs lines 244-259), the raw parent ids appear inside the child's own data blob — but they are never resolved to parent entities, never hoisted to a top-level field, and there is no is_subagent flag.
Affected Files
| File |
Lines |
Role |
Phantom.Workspaces.Llm.Core/CurrentSessionContextProvider.cs |
104-112, 115-151, 244-259 |
Builds the tool result; needs a plural parent_agent_sessions array + is_subagent boolean resolved from the agent-session entity. |
Phantom.Workspaces.Llm.Core/CurrentSessionContext.cs |
11-27 |
Add an optional collection (IReadOnlyList<...>) of host-supplied parent agent-session ids so hosts can plumb them directly when starting a sub-agent. |
Phantom.Workspaces.Llm.Core/CurrentSessionContextFactory.cs |
23-51 |
Accept and forward the parent agent-session id collection when building a context for a sub-agent host. |
Phantom.Workspaces.Data.Core/JsonSchemas/agent-session.json |
37-46 |
Existing source-of-truth for parent linkage (parent-agent-session-ids, ARRAY) — read by the tool. |
Phantom.Workspaces.Llm.Core/SubAgentDispatcherChatClient.cs |
590-599 |
Existing site that writes parent-agent-session-ids; also the place that should populate the new CurrentSessionContext parents collection when starting the sub-agent's runtime. Nested sub-agents inherit the same code path, so grandchildren-and-beyond work without special casing. |
Design / Fix
Result shape
get_current_session returns an object of fixed shape with plural parent-linkage members that are always present:
parent_agent_sessions is always an array (never null, never omitted). Empty array ([]) means "top-level session". A single-parent sub-agent has a one-element array; a (currently theoretical) multi-parent sub-agent has multiple elements. Each element has the same shape as agent_session, produced by the existing ToSerializableEntity.
is_subagent is true iff parent_agent_sessions.length > 0. It is provided as a convenience so agents at arbitrary nesting depth can branch on "am I a sub-agent?" without inspecting the array.
- Unresolvable parent references (entity id present in
parent-agent-session-ids but no matching entity in the store) are skipped from parent_agent_sessions. The raw ids remain visible under agent_session.data.parent-agent-session-ids for debugging, and is_subagent still reflects the raw array's non-emptiness (so a stale reference does not silently make a sub-agent look like a root).
Immediate-parents-only vs full-ancestry
Decision: report only immediate parents.
Justification:
- Structure is a DAG, not a tree. Because
parent-agent-session-ids is plural, transitive ancestry is a DAG whose in-lined serialization has no obvious canonical order and can (in principle) contain the same ancestor via multiple paths. Inlining it in one call encodes redundant/ambiguous data.
- Depth is unbounded. A sub-agent may itself dispatch sub-agents; inlining all ancestors would make the tool result size dependent on dispatch depth, and would require the tool to perform an arbitrary number of
IDataAccessLayer reads.
- Grandchildren are handled implicitly. Because each entity stores its own immediate parents relative to the dispatching session (see
SubAgentDispatcherChatClient.cs:598), a grandchild session naturally reports its own immediate parent (the middle sub-agent). Callers that need the full chain can walk it by calling get_current_session — or reading the parent agent-session entity directly — one level at a time. is_subagent at each level answers "is this level nested?" without a walk.
- Simple, stable contract. Consumers get a fixed shape (
parent_agent_sessions: EntitySnapshot[]) that does not change with depth and does not require them to interpret a nested tree/DAG structure.
Callers that specifically want the full ancestor chain can walk upward themselves: for each id in agent_session.data.parent-agent-session-ids, look up that entity via the data-access layer and inspect its parent-agent-session-ids, terminating when a session has an empty/absent array.
Two complementary sources for the parents (both plural)
Both feed a single ResolveParentAgentSessionsAsync helper in GetCurrentSessionTool so the returned shape is uniform.
-
Entity-derived (primary, works for resumed sessions too).
After ResolveAgentSessionAsync returns the child's EntitySnapshot, read the entire parent-agent-session-ids array from entity.Data (mirroring how TryReadEntityNameReference already reads references at lines 182-203, but iterating the array). For each element, resolve the parent agent-session entity via IDataAccessLayer — either by entity-id (when the stored value is a uuid, as written at SubAgentDispatcherChatClient.cs:598) using a GetRequest, or by agent-session-id == … query the same way ResolveAgentSessionAsync (lines 122-146) already resolves the current session. Serialize each resolved entity with ToSerializableEntity. Preserve the schema order of parent-agent-session-ids.
-
Host-supplied (secondary, authoritative when non-empty).
Add an optional ParentAgentSessionEntityIds (or ParentAgentSessionIds) collection property to CurrentSessionContext and a matching parameter on CurrentSessionContextFactory.CreateForHostAsync. Populate it from the sub-agent creation path in SubAgentDispatcherChatClient (around line 598, where dispatcherEntityId is already known, and where — for future multi-parent support — additional parent ids could be threaded in) when the host starts the sub-agent's runtime. When the host-supplied collection is non-empty, GetCurrentSessionTool uses it in place of the entity-derived list.
Sketch (parents resolution):
private async Task<IReadOnlyList<EntitySnapshot>> ResolveParentAgentSessionsAsync(
EntitySnapshot? childAgentSession,
CancellationToken cancellationToken)
{
IReadOnlyList<string> parentIds =
this.currentSessionContext.ParentAgentSessionEntityIds is { Count: > 0 } hostSupplied
? hostSupplied
: ReadParentAgentSessionIds(childAgentSession); // reads the array from entity.Data
if (parentIds.Count == 0)
{
return Array.Empty<EntitySnapshot>();
}
var resolved = new List<EntitySnapshot>(parentIds.Count);
foreach (var id in parentIds)
{
var parent = await ResolveAgentSessionByEntityIdAsync(id, cancellationToken);
if (parent is not null)
{
resolved.Add(parent);
}
}
return resolved;
}
is_subagent is derived from the raw parent-id array (so it stays truthful even when one or more references cannot be resolved):
var rawParentIds = ReadParentAgentSessionIds(agentSession);
var parentAgentSessions = await ResolveParentAgentSessionsAsync(agentSession, cancellationToken);
var isSubagent = rawParentIds.Count > 0
|| (this.currentSessionContext.ParentAgentSessionEntityIds?.Count ?? 0) > 0;
return JsonSerializer.SerializeToElement(new
{
agent_session = ToSerializableEntity(agentSession),
is_subagent = isSubagent,
parent_agent_sessions = parentAgentSessions.Select(ToSerializableEntity).ToArray(),
user_computer_profile = ToSerializableEntity(userComputerProfile),
user = ToSerializableEntity(user),
computer = ToSerializableEntity(computer),
agent_definition = ToSerializableEntity(agentDefinition),
});
CurrentSessionContext gains:
/// <summary>
/// Optional host-supplied parent agent-session entity ids. When non-empty, overrides the
/// child entity's <c>parent-agent-session-ids</c> as the source for the tool's
/// <c>parent_agent_sessions</c> array. Plural because the schema permits a session to
/// reference more than one parent, and to keep the shape stable as multi-parent
/// dispatch scenarios are added.
/// </summary>
public IReadOnlyList<string>? ParentAgentSessionEntityIds { get; init; }
Update the tool Description (CurrentSessionContextProvider.cs line 82-83) to document:
parent_agent_sessions is an array of the session's immediate parent agent-sessions; empty for a top-level session.
is_subagent is true when the session was dispatched by one or more parent sessions.
- Consumers that need the full ancestor chain walk upward one level at a time.
Considered / Background
- Singular
parent_agent_session field. Rejected because parent-agent-session-ids is schema-declared plural; a singular field would misrepresent the data model and would need a breaking rename the moment multi-parent dispatch is produced. Even though today's dispatcher writes exactly one element, modelling it as an array today is essentially free and future-proof.
- Inlining the full ancestor chain / DAG. Rejected — see "Immediate-parents-only vs full-ancestry" above. Depth is unbounded and the structure is a DAG; each session reporting its immediate parents keeps the contract stable and delegates traversal to the caller.
- Emitting
parent_agent_sessions as null for root sessions. Rejected — an empty array is a clearer "no parents" signal than null, keeps the JSON type of parent_agent_sessions stable (always an array), and matches the schema where absent/empty means top-level. is_subagent remains the authoritative "am I nested?" flag.
Expected Tests
Add to Phantom.Workspaces.Llm.Core.Tests/CurrentSessionContextProviderTests.cs, matching the existing [Fact] public async Task <Subject>_<Scenario>_<ExpectedOutcome> PascalCase style and the InMemoryDataAccessLayer seeding helpers already in the file (SeedAgentSessionAsync, SeedEntityAsync, InvokeAsync).
| Test |
Scenario |
Expected |
GetCurrentSession_RootSession_ReportsEmptyParentsAndIsSubagentFalse |
Seed a top-level agent-session with no parent-agent-session-ids. |
parent_agent_sessions is a JSON array with length 0; is_subagent is JSON false. |
GetCurrentSession_SingleParentSubagent_ResolvesParentAgentSession |
Seed a parent agent-session entity + a child agent-session whose data.parent-agent-session-ids references the parent's entity-id. |
parent_agent_sessions has length 1; element 0's data.agent-session-id matches the parent; is_subagent is JSON true; agent_session.data.agent-session-id still matches the child. |
GetCurrentSession_MultiParentSubagent_ReportsAllParentsInOrder |
Seed two parent agent-session entities and a child whose data.parent-agent-session-ids array references both, in a specific order. |
parent_agent_sessions has length 2 and preserves the order of parent-agent-session-ids; is_subagent is true. |
GetCurrentSession_ParentAgentSessionShapeMatchesAgentSession |
Same seed as the single-parent case. |
Each parent_agent_sessions element exposes the same members as agent_session (entityId, concurrencyTag, modifiedTime, changeId, data), produced by ToSerializableEntity. |
GetCurrentSession_GrandchildSession_ReportsImmediateParentAndIsSubagentTrue |
Seed a root A, a sub-agent B whose parent is A, and a grandchild C whose parent-agent-session-ids references B (not A). Invoke as C. |
parent_agent_sessions has length 1 and resolves to B (not A); is_subagent is true; the full A→B→C chain is not inlined. |
GetCurrentSession_ContextParentIdsOverrideEntityData |
Seed a child whose parent-agent-session-ids points at parent A, but populate CurrentSessionContext.ParentAgentSessionEntityIds with parent B (both seeded). |
parent_agent_sessions resolves to B (host-supplied wins over entity-derived); is_subagent is true. |
GetCurrentSession_ContextParentIdsMultiple_ResolvedInOrder |
Populate CurrentSessionContext.ParentAgentSessionEntityIds with [B, A] (both seeded); child entity has no parent-agent-session-ids. |
parent_agent_sessions has length 2, order [B, A]; is_subagent is true. |
GetCurrentSession_ParentReferenceUnresolvable_SkippedFromParentsButIsSubagentTrue |
Seed a child whose parent-agent-session-ids points at a non-existent entity-id. |
parent_agent_sessions is an empty array (unresolved refs skipped); is_subagent is JSON true (raw array was non-empty). |
GetCurrentSession_UnknownSessionId_ParentsEmptyAndIsSubagentFalse |
Extension of the existing GetCurrentSession_UnknownSessionId_AgentSessionNull test — the current session id resolves to nothing. |
agent_session is JSON null; parent_agent_sessions is []; is_subagent is JSON false. |
GetCurrentSession_IncludeProfileFalse_StillIncludesParentAgentSessions |
include_profile=false on a sub-agent session. |
parent_agent_sessions and is_subagent are still present and populated (parent identity is orthogonal to the profile flag). |
If the new host-supplied source is added to CurrentSessionContext, also add a factory-level test in CurrentSessionContextFactoryTests.cs verifying that CreateForHostAsync propagates the ParentAgentSessionEntityIds argument onto the returned context (including that a null-vs-empty distinction is preserved: null means "fall back to the entity", [] means "explicitly no parents").
Summary
For a sub-agent (child) chat,
get_current_sessionreturns only the child's own agent-session entity plus the host user-computer-profile / user / computer / agent-definition. It does not expose an explicit parent agent-session field and has no explicit indicator that the session is a sub-agent. An agent invokingget_current_sessioninside a sub-agent cannot tell (a) that it is a sub-agent nor (b) which parent chat(s) dispatched it, except by inspecting a raw untyped array (parent-agent-session-ids) embedded in the whole-blob-serializedagent_session.data.The tool must present the child agent's information plus a resolved parent-linkage field that:
parent-agent-session-idsis an array — a session can have more than one parent — so the result field must be plural (parent_agent_sessions), not a singularparent_agent_session.get_current_session(or reading the parent entity) for each parent. Depth is thereby handled implicitly and correctly for grandchildren-and-beyond.is_subagentboolean (trueiffparent-agent-session-idsis non-empty) so a nested session can detect it is a sub-agent at any depth without walking the DAG.Root Cause
The
get_current_sessionresult shape is built inGetCurrentSessionTool.InvokeCoreAsync(Phantom.Workspaces.Llm.Core/CurrentSessionContextProvider.cslines 104-112) as an anonymous object with fixed membersagent_session,user_computer_profile,user,computer,agent_definition— there is no parent-linkage field and no sub-agent indicator.The upstream
CurrentSessionContextrecord (Phantom.Workspaces.Llm.Core/CurrentSessionContext.cslines 11-27) carriesAgentSessionId,UserComputerProfile,User,Computer,AgentDefinitionReference— no parent reference (singular or plural). Its shared factoryCurrentSessionContextFactory.CreateForHostAsync(Phantom.Workspaces.Llm.Core/CurrentSessionContextFactory.cslines 23-51) also has no parent parameter, so hosts that start a sub-agent session have no way to plumb the parent(s) through.Meanwhile, parent linkage exists on the agent-session entity's data as an array field:
The schema declares it as
"type": "array"ofentity-referenceitems — the plurality (-ids) is intentional; a session may reference more than one parent.The sub-agent dispatcher writes it when creating the child session entity:
(
Phantom.Workspaces.Llm.Core/SubAgentDispatcherChatClient.csline 598).Findings on multi-parent in practice. Today the dispatcher always writes exactly one element (the dispatching session's entity id) at line 598, and there is no code path that adds or merges additional parents into an existing session's
parent-agent-session-ids. Multi-parent is therefore schema-supported but not currently produced by production code paths. The tool result must still model plural parents so that (a) it does not need to change shape if/when a multi-parent producer is added, (b) consumers write correct code today, and (c) it faithfully mirrors the schema.sessions-view.json(lines ~98-104) also treats the field as an array, but currently only reads index0as an existence check — that is an unrelated simplification and is not evidence that only one parent is ever valid.Findings on deep nesting. Nothing in the current code special-cases grandchildren. Because the dispatcher writes the parent(s) relative to the current dispatching session — whatever depth that is — a sub-agent that itself dispatches a sub-agent will naturally produce a grandchild whose
parent-agent-session-idsreferences the middle sub-agent's entity. There is no accumulation of transitive ancestors on a single entity; ancestry is stored one level at a time and must be walked one level at a time.Because
GetCurrentSessionToolserializes the entireentity.Dataasagent_session.data(CurrentSessionContextProvider.cs lines 244-259), the raw parent ids appear inside the child's owndatablob — but they are never resolved to parent entities, never hoisted to a top-level field, and there is nois_subagentflag.Affected Files
Phantom.Workspaces.Llm.Core/CurrentSessionContextProvider.csparent_agent_sessionsarray +is_subagentboolean resolved from the agent-session entity.Phantom.Workspaces.Llm.Core/CurrentSessionContext.csIReadOnlyList<...>) of host-supplied parent agent-session ids so hosts can plumb them directly when starting a sub-agent.Phantom.Workspaces.Llm.Core/CurrentSessionContextFactory.csPhantom.Workspaces.Data.Core/JsonSchemas/agent-session.jsonparent-agent-session-ids, ARRAY) — read by the tool.Phantom.Workspaces.Llm.Core/SubAgentDispatcherChatClient.csparent-agent-session-ids; also the place that should populate the newCurrentSessionContextparents collection when starting the sub-agent's runtime. Nested sub-agents inherit the same code path, so grandchildren-and-beyond work without special casing.Design / Fix
Result shape
get_current_sessionreturns an object of fixed shape with plural parent-linkage members that are always present:{ "agent_session": { … child agent-session entity … }, "is_subagent": true, // false iff parent_agent_sessions is empty "parent_agent_sessions": [ { … parent agent-session entity … }, … ], // empty [] for a root session "user_computer_profile": …, "user": …, "computer": …, "agent_definition": … }parent_agent_sessionsis always an array (never null, never omitted). Empty array ([]) means "top-level session". A single-parent sub-agent has a one-element array; a (currently theoretical) multi-parent sub-agent has multiple elements. Each element has the same shape asagent_session, produced by the existingToSerializableEntity.is_subagentistrueiffparent_agent_sessions.length > 0. It is provided as a convenience so agents at arbitrary nesting depth can branch on "am I a sub-agent?" without inspecting the array.parent-agent-session-idsbut no matching entity in the store) are skipped fromparent_agent_sessions. The raw ids remain visible underagent_session.data.parent-agent-session-idsfor debugging, andis_subagentstill reflects the raw array's non-emptiness (so a stale reference does not silently make a sub-agent look like a root).Immediate-parents-only vs full-ancestry
Decision: report only immediate parents.
Justification:
parent-agent-session-idsis plural, transitive ancestry is a DAG whose in-lined serialization has no obvious canonical order and can (in principle) contain the same ancestor via multiple paths. Inlining it in one call encodes redundant/ambiguous data.IDataAccessLayerreads.SubAgentDispatcherChatClient.cs:598), a grandchild session naturally reports its own immediate parent (the middle sub-agent). Callers that need the full chain can walk it by callingget_current_session— or reading the parent agent-session entity directly — one level at a time.is_subagentat each level answers "is this level nested?" without a walk.parent_agent_sessions: EntitySnapshot[]) that does not change with depth and does not require them to interpret a nested tree/DAG structure.Callers that specifically want the full ancestor chain can walk upward themselves: for each id in
agent_session.data.parent-agent-session-ids, look up that entity via the data-access layer and inspect itsparent-agent-session-ids, terminating when a session has an empty/absent array.Two complementary sources for the parents (both plural)
Both feed a single
ResolveParentAgentSessionsAsynchelper inGetCurrentSessionToolso the returned shape is uniform.Entity-derived (primary, works for resumed sessions too).
After
ResolveAgentSessionAsyncreturns the child'sEntitySnapshot, read the entireparent-agent-session-idsarray fromentity.Data(mirroring howTryReadEntityNameReferencealready reads references at lines 182-203, but iterating the array). For each element, resolve the parent agent-session entity viaIDataAccessLayer— either by entity-id (when the stored value is a uuid, as written atSubAgentDispatcherChatClient.cs:598) using aGetRequest, or byagent-session-id == …query the same wayResolveAgentSessionAsync(lines 122-146) already resolves the current session. Serialize each resolved entity withToSerializableEntity. Preserve the schema order ofparent-agent-session-ids.Host-supplied (secondary, authoritative when non-empty).
Add an optional
ParentAgentSessionEntityIds(orParentAgentSessionIds) collection property toCurrentSessionContextand a matching parameter onCurrentSessionContextFactory.CreateForHostAsync. Populate it from the sub-agent creation path inSubAgentDispatcherChatClient(around line 598, wheredispatcherEntityIdis already known, and where — for future multi-parent support — additional parent ids could be threaded in) when the host starts the sub-agent's runtime. When the host-supplied collection is non-empty,GetCurrentSessionTooluses it in place of the entity-derived list.Sketch (parents resolution):
is_subagentis derived from the raw parent-id array (so it stays truthful even when one or more references cannot be resolved):CurrentSessionContextgains:Update the tool
Description(CurrentSessionContextProvider.cs line 82-83) to document:parent_agent_sessionsis an array of the session's immediate parent agent-sessions; empty for a top-level session.is_subagentistruewhen the session was dispatched by one or more parent sessions.Considered / Background
parent_agent_sessionfield. Rejected becauseparent-agent-session-idsis schema-declared plural; a singular field would misrepresent the data model and would need a breaking rename the moment multi-parent dispatch is produced. Even though today's dispatcher writes exactly one element, modelling it as an array today is essentially free and future-proof.parent_agent_sessionsasnullfor root sessions. Rejected — an empty array is a clearer "no parents" signal thannull, keeps the JSON type ofparent_agent_sessionsstable (always an array), and matches the schema whereabsent/empty means top-level.is_subagentremains the authoritative "am I nested?" flag.Expected Tests
Add to
Phantom.Workspaces.Llm.Core.Tests/CurrentSessionContextProviderTests.cs, matching the existing[Fact] public async Task <Subject>_<Scenario>_<ExpectedOutcome>PascalCase style and theInMemoryDataAccessLayerseeding helpers already in the file (SeedAgentSessionAsync,SeedEntityAsync,InvokeAsync).GetCurrentSession_RootSession_ReportsEmptyParentsAndIsSubagentFalseparent-agent-session-ids.parent_agent_sessionsis a JSON array with length 0;is_subagentis JSONfalse.GetCurrentSession_SingleParentSubagent_ResolvesParentAgentSessiondata.parent-agent-session-idsreferences the parent's entity-id.parent_agent_sessionshas length 1; element 0'sdata.agent-session-idmatches the parent;is_subagentis JSONtrue;agent_session.data.agent-session-idstill matches the child.GetCurrentSession_MultiParentSubagent_ReportsAllParentsInOrderdata.parent-agent-session-idsarray references both, in a specific order.parent_agent_sessionshas length 2 and preserves the order ofparent-agent-session-ids;is_subagentistrue.GetCurrentSession_ParentAgentSessionShapeMatchesAgentSessionparent_agent_sessionselement exposes the same members asagent_session(entityId,concurrencyTag,modifiedTime,changeId,data), produced byToSerializableEntity.GetCurrentSession_GrandchildSession_ReportsImmediateParentAndIsSubagentTrueparent-agent-session-idsreferences B (not A). Invoke as C.parent_agent_sessionshas length 1 and resolves to B (not A);is_subagentistrue; the full A→B→C chain is not inlined.GetCurrentSession_ContextParentIdsOverrideEntityDataparent-agent-session-idspoints at parent A, but populateCurrentSessionContext.ParentAgentSessionEntityIdswith parent B (both seeded).parent_agent_sessionsresolves to B (host-supplied wins over entity-derived);is_subagentistrue.GetCurrentSession_ContextParentIdsMultiple_ResolvedInOrderCurrentSessionContext.ParentAgentSessionEntityIdswith[B, A](both seeded); child entity has noparent-agent-session-ids.parent_agent_sessionshas length 2, order[B, A];is_subagentistrue.GetCurrentSession_ParentReferenceUnresolvable_SkippedFromParentsButIsSubagentTrueparent-agent-session-idspoints at a non-existent entity-id.parent_agent_sessionsis an empty array (unresolved refs skipped);is_subagentis JSONtrue(raw array was non-empty).GetCurrentSession_UnknownSessionId_ParentsEmptyAndIsSubagentFalseGetCurrentSession_UnknownSessionId_AgentSessionNulltest — the current session id resolves to nothing.agent_sessionis JSONnull;parent_agent_sessionsis[];is_subagentis JSONfalse.GetCurrentSession_IncludeProfileFalse_StillIncludesParentAgentSessionsinclude_profile=falseon a sub-agent session.parent_agent_sessionsandis_subagentare still present and populated (parent identity is orthogonal to the profile flag).If the new host-supplied source is added to
CurrentSessionContext, also add a factory-level test inCurrentSessionContextFactoryTests.csverifying thatCreateForHostAsyncpropagates theParentAgentSessionEntityIdsargument onto the returned context (including that a null-vs-empty distinction is preserved:nullmeans "fall back to the entity",[]means "explicitly no parents").