ARCH: замкнуть metadata-scoped clarification recovery loops

This commit is contained in:
2026-04-23 13:42:14 +03:00
parent c96c9bab86
commit cddd6667fb
20 changed files with 993 additions and 43 deletions
@@ -329,6 +329,27 @@ export function readAssistantMcpDiscoveryLoopUnsupportedFamily(
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
}
export function readAssistantMcpDiscoveryLoopMetadataScopeHint(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
): string | null {
return (
toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.metadata_scope_hint) ??
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_scope_hint) ??
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.metadata_scope_hint)
);
}
export function readAssistantMcpDiscoveryLoopSubjectResolutionOptional(
debug: Record<string, unknown> | null
): boolean {
return (
readAssistantMcpDiscoveryLoopState(debug)?.subject_resolution_optional === true ||
readAssistantMcpDiscoveryTurnMeaning(debug)?.subject_resolution_optional === true ||
readAssistantMcpDiscoveryDataNeedGraph(debug)?.subject_resolution_optional === true
);
}
export function readAssistantMcpDiscoveryMetadataRouteFamily(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
@@ -557,6 +578,13 @@ export function readAddressDebugCounterparty(
if (String(debug?.anchor_type ?? "") === "counterparty") {
return toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
}
const discoveryPilotScope = readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString);
const suppressDiscoveryEntityCarryover =
discoveryPilotScope === "metadata_inspection_v1" ||
readAssistantMcpDiscoveryLoopSubjectResolutionOptional(debug);
if (suppressDiscoveryEntityCarryover) {
return null;
}
const discoveryEntities = collectAssistantMcpDiscoveryEntityCandidates(debug, toNonEmptyString);
for (const entity of discoveryEntities) {
const text = toNonEmptyString(entity);
@@ -14,6 +14,8 @@ export interface AssistantMcpDiscoveryDataNeedGraphContract {
schema_version: typeof ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION;
policy_owner: "assistantMcpDiscoveryDataNeedGraph";
subject_candidates: string[];
metadata_scope_hint?: string | null;
subject_resolution_optional?: boolean;
business_fact_family: string | null;
action_family: string | null;
aggregation_need: string | null;
@@ -205,6 +207,16 @@ function allowsOpenScopeWithoutSubject(input: {
);
}
function allowsMetadataScopedOpenLaneWithoutSubject(input: {
family: string | null;
subjectResolutionOptional: boolean;
}): boolean {
return Boolean(
input.subjectResolutionOptional &&
(input.family === "movement_evidence" || input.family === "document_evidence")
);
}
function rankingNeedFromRawUtterance(value: string): string | null {
const text = lower(value);
if (!text) {
@@ -249,6 +261,7 @@ function decompositionCandidatesFor(input: {
comparisonNeed: string | null;
rankingNeed: string | null;
openScopeWithoutSubject: boolean;
metadataScopedOpenLaneWithoutSubject: boolean;
}): string[] {
const result: string[] = [];
if (input.family === "schema_surface") {
@@ -295,13 +308,17 @@ function decompositionCandidatesFor(input: {
return result;
}
if (input.family === "movement_evidence") {
pushUnique(result, "resolve_entity_reference");
if (!input.metadataScopedOpenLaneWithoutSubject) {
pushUnique(result, "resolve_entity_reference");
}
pushUnique(result, "fetch_scoped_movements");
pushUnique(result, "probe_coverage");
return result;
}
if (input.family === "document_evidence") {
pushUnique(result, "resolve_entity_reference");
if (!input.metadataScopedOpenLaneWithoutSubject) {
pushUnique(result, "resolve_entity_reference");
}
pushUnique(result, "fetch_scoped_documents");
pushUnique(result, "probe_coverage");
return result;
@@ -345,6 +362,8 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
.map((item) => toNonEmptyString(item))
.filter((item): item is string => Boolean(item));
@@ -369,6 +388,12 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
rankingNeed,
oneSidedOpenScopeTotalHint
});
const metadataScopedOpenLaneWithoutSubject =
subjectCandidates.length === 0 &&
allowsMetadataScopedOpenLaneWithoutSubject({
family: businessFactFamily,
subjectResolutionOptional
});
const clarificationGaps: string[] = [];
if (unsupported === "metadata_lane_choice_clarification" || action === "resolve_next_lane") {
pushUnique(clarificationGaps, "lane_family_choice");
@@ -380,7 +405,18 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
!explicitOrganizationScope
) {
pushUnique(clarificationGaps, "organization");
} else if (subjectCandidates.length === 0 && businessFactFamily !== "schema_surface" && !openScopeWithoutSubject) {
} else if (
subjectCandidates.length === 0 &&
metadataScopedOpenLaneWithoutSubject &&
!explicitOrganizationScope
) {
pushUnique(clarificationGaps, "organization");
} else if (
subjectCandidates.length === 0 &&
businessFactFamily !== "schema_surface" &&
!openScopeWithoutSubject &&
!metadataScopedOpenLaneWithoutSubject
) {
pushUnique(clarificationGaps, "subject");
}
const timeScopeNeed = timeScopeNeedFor({
@@ -393,12 +429,13 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
}
const decompositionCandidates = decompositionCandidatesFor({
family: businessFactFamily,
action,
aggregationNeed,
comparisonNeed,
rankingNeed,
openScopeWithoutSubject
});
action,
aggregationNeed,
comparisonNeed,
rankingNeed,
openScopeWithoutSubject,
metadataScopedOpenLaneWithoutSubject
});
const reasonCodes: string[] = [];
pushReason(reasonCodes, "data_need_graph_built");
if (businessFactFamily) {
@@ -418,6 +455,9 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
if (openScopeWithoutSubject && !rankingNeed && !comparisonNeed) {
pushReason(reasonCodes, "data_need_graph_open_scope_total_without_subject");
}
if (metadataScopedOpenLaneWithoutSubject) {
pushReason(reasonCodes, "data_need_graph_metadata_scoped_open_lane_without_subject");
}
if (allTimeScopeHint) {
pushReason(reasonCodes, "data_need_graph_all_time_scope_hint");
}
@@ -432,6 +472,8 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
schema_version: ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: subjectCandidates,
metadata_scope_hint: metadataScopeHint,
subject_resolution_optional: subjectResolutionOptional || undefined,
business_fact_family: businessFactFamily,
action_family: toNonEmptyString(turnMeaning?.asked_action_family),
aggregation_need: aggregationNeed,
@@ -127,6 +127,16 @@ function hasSubjectCandidates(graph: AssistantMcpDiscoveryDataNeedGraphContract
return (graph?.subject_candidates.length ?? 0) > 0;
}
function hasMetadataScopedOpenLane(
graph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined,
meaning: AssistantMcpDiscoveryTurnMeaningRef | null | undefined
): boolean {
return Boolean(
graph?.subject_resolution_optional === true ||
meaning?.subject_resolution_optional === true
);
}
function hasReasonCode(
graph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined,
reasonCode: string
@@ -150,6 +160,12 @@ function addScopeAxes(axes: string[], meaning: AssistantMcpDiscoveryTurnMeaningR
}
}
function addMetadataScopeAxis(axes: string[], meaning: AssistantMcpDiscoveryTurnMeaningRef | null | undefined): void {
if (toNonEmptyString(meaning?.metadata_scope_hint)) {
pushUnique(axes, "metadata_scope");
}
}
function addTimeScopeAxes(
axes: string[],
dataNeedGraph: AssistantMcpDiscoveryDataNeedGraphContract | null | undefined
@@ -417,7 +433,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
const graphAction = lower(dataNeedGraph?.action_family);
const graphAggregation = lower(dataNeedGraph?.aggregation_need);
const graphClarificationGaps = (dataNeedGraph?.clarification_gaps ?? []).map((item) => lower(item));
const organizationScope = toNonEmptyString(meaning?.explicit_organization_scope);
const metadataScopedOpenLane = hasMetadataScopedOpenLane(dataNeedGraph, meaning);
const openScopeTotalWithoutSubject =
graphFactFamily === "value_flow" &&
!hasSubjectCandidates(dataNeedGraph) &&
@@ -426,6 +442,7 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
const axes: string[] = [];
const requestedAggregationAxis = aggregationAxis(meaning);
addScopeAxes(axes, meaning);
addMetadataScopeAxis(axes, meaning);
addTimeScopeAxes(axes, dataNeedGraph);
if (graphClarificationGaps.includes("lane_family_choice")) {
@@ -654,6 +671,27 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
}
if (graphFactFamily === "movement_evidence") {
if (metadataScopedOpenLane) {
pushUnique(axes, "organization");
pushUnique(axes, "coverage_target");
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
dataNeedGraph,
fallbackPrimitives: ["query_movements", "probe_coverage"],
requiredAxes: axes,
metadataSurface: input.metadataSurface,
actionFamily: action
});
return {
semanticDataNeed: "movement evidence",
chainId: "movement_evidence",
chainSummary:
"Keep the metadata-scoped movement lane, ask only for the remaining business scope, then fetch scoped movement rows and probe coverage without pretending there is a grounded counterparty.",
primitives: primitiveSelection.primitives,
axes,
reason: "planner_selected_metadata_scoped_movement_from_data_need_graph",
extraReasons: primitiveSelection.reasonCodes
};
}
pushUnique(axes, "coverage_target");
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
dataNeedGraph,
@@ -674,6 +712,27 @@ function recipeFor(input: AssistantMcpDiscoveryPlannerInput): PlannerRecipe {
}
if (graphFactFamily === "document_evidence") {
if (metadataScopedOpenLane) {
pushUnique(axes, "organization");
pushUnique(axes, "coverage_target");
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
dataNeedGraph,
fallbackPrimitives: ["query_documents", "probe_coverage"],
requiredAxes: axes,
metadataSurface: input.metadataSurface,
actionFamily: action
});
return {
semanticDataNeed: "document evidence",
chainId: "document_evidence",
chainSummary:
"Keep the metadata-scoped document lane, ask only for the remaining business scope, then fetch scoped document rows and probe coverage without pretending there is a grounded counterparty.",
primitives: primitiveSelection.primitives,
axes,
reason: "planner_selected_metadata_scoped_document_from_data_need_graph",
extraReasons: primitiveSelection.reasonCodes
};
}
pushUnique(axes, "coverage_target");
const primitiveSelection = selectPrimitivesFromGraphAndCatalog({
dataNeedGraph,
@@ -27,8 +27,10 @@ export interface AssistantMcpDiscoveryTurnMeaningRef {
seeded_ranking_need?: string | null;
explicit_entity_candidates?: string[];
metadata_ambiguity_entity_sets?: string[];
metadata_scope_hint?: string | null;
explicit_organization_scope?: string | null;
explicit_date_scope?: string | null;
subject_resolution_optional?: boolean | null;
meaning_confidence?: number | null;
unsupported_but_understood_family?: string | null;
stale_replay_forbidden?: boolean | null;
@@ -53,6 +53,8 @@ export interface AssistantMcpDiscoveryLoopStateContract {
pending_axes: string[];
provided_axes: string[];
explicit_entity_candidates: string[];
metadata_scope_hint: string | null;
subject_resolution_optional: boolean;
explicit_organization_scope: string | null;
explicit_date_scope: string | null;
}
@@ -184,6 +186,13 @@ function buildLoopState(
pending_axes: plannerClarificationGaps.length > 0 ? plannerClarificationGaps : flattenAxes(pilot, "missing_axis_options"),
provided_axes: flattenAxes(pilot, "provided_axes"),
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
metadata_scope_hint:
planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint ??
planner.data_need_graph?.metadata_scope_hint ??
null,
subject_resolution_optional:
planner.discovery_plan.turn_meaning_ref?.subject_resolution_optional === true ||
planner.data_need_graph?.subject_resolution_optional === true,
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
explicit_date_scope: planner.discovery_plan.turn_meaning_ref?.explicit_date_scope ?? null
};
@@ -427,6 +427,8 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
rankingNeed: string | null;
organization: string | null;
dateScope: string | null;
metadataScopeHint: string | null;
subjectResolutionOptional: boolean;
metadataRouteFamily: AssistantMcpDiscoveryMetadataRouteFamily | null;
metadataRouteFamilySelectionBasis: AssistantMcpDiscoveryMetadataSurfaceRef["route_family_selection_basis"];
metadataSelectedEntitySet: string | null;
@@ -445,6 +447,9 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
const loopSubjectResolutionOptional =
followupContext?.previous_discovery_loop_subject_resolution_optional === true;
const previousIntent =
toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
const loopMapped =
@@ -474,14 +479,19 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
followupContext?.previous_discovery_entity_ambiguity_candidates
);
const ambiguityBlocksImplicitGrounding =
pilotScope === "entity_resolution_search_v1" && entityResolutionStatus === "ambiguous";
effectivePilotScope === "entity_resolution_search_v1" && entityResolutionStatus === "ambiguous";
const metadataPilotCarriesScopeOnly =
effectivePilotScope === "metadata_inspection_v1" || loopSubjectResolutionOptional;
const metadataScopeHint =
loopMetadataScopeHint ??
(loopSubjectResolutionOptional ? discoveryEntities[0] ?? null : null);
const counterparty =
toNonEmptyString(previousFilters?.counterparty) ??
toNonEmptyString(rootFilters?.counterparty) ??
(toNonEmptyString(followupContext?.previous_anchor_type) === "counterparty"
? toNonEmptyString(followupContext?.previous_anchor_value)
: null) ??
(ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null);
(ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly ? null : discoveryEntities[0] ?? null);
const organization =
toNonEmptyString(previousFilters?.organization) ??
toNonEmptyString(rootFilters?.organization) ??
@@ -501,12 +511,15 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
loopPendingAxes,
loopProvidedAxes,
counterparty,
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
discoveryEntity:
ambiguityBlocksImplicitGrounding || loopSubjectResolutionOptional ? null : discoveryEntities[0] ?? null,
entityResolutionStatus,
entityResolutionAmbiguityCandidates,
rankingNeed: toNonEmptyString(followupContext?.previous_discovery_ranking_need),
organization,
dateScope,
metadataScopeHint,
subjectResolutionOptional: loopSubjectResolutionOptional,
metadataRouteFamily: normalizeMetadataRouteFamily(followupContext?.previous_discovery_metadata_route_family),
metadataRouteFamilySelectionBasis: normalizeMetadataRouteFamilySelectionBasis(
followupContext?.previous_discovery_metadata_route_family_selection_basis
@@ -1084,11 +1097,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
!rawValueFlowSignal &&
hasMetadataObjectHint(rawText)
);
const metadataLaneCarryoverAvailable = Boolean(
followupSeed.counterparty ||
followupSeed.discoveryEntity ||
followupSeed.metadataScopeHint ||
followupSeed.metadataSelectedEntitySet ||
followupSeed.metadataSelectedSurfaceObjects.length > 0
);
const metadataGroundedDocumentFollowupApplicable = Boolean(
followupSeed.pilotScope === "metadata_inspection_v1" &&
followupSeed.metadataRouteFamily === "document_evidence" &&
!followupSeed.metadataAmbiguityDetected &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
metadataDocumentHintSignal
@@ -1098,7 +1118,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.metadataAmbiguityDetected &&
(followupSeed.metadataAmbiguityEntitySets.length === 0 ||
metadataEntitySetsSuggestDocumentLane(followupSeed.metadataAmbiguityEntitySets)) &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
metadataDocumentHintSignal
@@ -1107,7 +1127,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.pilotScope === "metadata_inspection_v1" &&
followupSeed.metadataRouteFamily === "movement_evidence" &&
!followupSeed.metadataAmbiguityDetected &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
metadataMovementHintSignal
);
@@ -1116,7 +1136,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.metadataAmbiguityDetected &&
(followupSeed.metadataAmbiguityEntitySets.length === 0 ||
metadataEntitySetsSuggestMovementLane(followupSeed.metadataAmbiguityEntitySets)) &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
metadataMovementHintSignal
);
@@ -1206,7 +1226,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
(followupSeed.metadataRouteFamily === "document_evidence" ||
followupSeed.metadataRouteFamily === "movement_evidence") &&
!followupSeed.metadataAmbiguityDetected &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1229,7 +1249,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.pilotScope === "metadata_inspection_v1" &&
followupSeed.metadataAmbiguityDetected &&
metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1241,7 +1261,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.pilotScope === "metadata_inspection_v1" &&
followupSeed.metadataAmbiguityDetected &&
metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1254,7 +1274,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.metadataAmbiguityDetected &&
!metadataAmbiguityCollapsesToDocumentLane(followupSeed.metadataAmbiguityEntitySets) &&
!metadataAmbiguityCollapsesToMovementLane(followupSeed.metadataAmbiguityEntitySets) &&
followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1265,6 +1285,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
const metadataGroundedDocumentLaneApplicable =
metadataGroundedDocumentFollowupApplicable ||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
(followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
followupSeed.domain === "documents" &&
followupSeed.action === "list_documents") ||
entityResolutionGroundedDocumentFollowupApplicable ||
entityResolutionClarifiedDocumentFollowupApplicable ||
valueFlowGroundedDocumentFollowupApplicable ||
@@ -1274,6 +1302,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
const metadataGroundedMovementLaneApplicable =
metadataGroundedMovementFollowupApplicable ||
metadataAmbiguityResolvedMovementFollowupApplicable ||
(followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
followupSeed.domain === "movements" &&
followupSeed.action === "list_movements") ||
entityResolutionGroundedMovementFollowupApplicable ||
entityResolutionClarifiedMovementFollowupApplicable ||
valueFlowGroundedMovementFollowupApplicable ||
@@ -1346,7 +1382,20 @@ export function buildAssistantMcpDiscoveryTurnInput(
!metadataGroundedDocumentLaneApplicable &&
!metadataGroundedMovementLaneApplicable
});
const groundedFollowupEntity = followupSeed.counterparty ?? followupSeed.discoveryEntity;
const metadataLaneScopeHint =
rawMetadataScopeHint ??
followupSeed.metadataScopeHint ??
followupSeed.discoveryEntity ??
followupSeed.metadataSelectedEntitySet ??
null;
const metadataScopedLaneWithoutSubject = Boolean(
(metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
!followupSeed.counterparty &&
metadataLaneCarryoverAvailable
);
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
const entityCandidates = entityResolutionSignal ? [] : [];
if (entityResolutionSignal) {
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
@@ -1366,11 +1415,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
pushScopedEntityCandidate(entityCandidates, normalizedPredecomposeCounterparty, groundedFollowupEntity);
if (!groundedFollowupEntity) {
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
if (!metadataScopedLaneWithoutSubject) {
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
}
}
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
}
if ((rawMetadataSignal || metadataFollowupSeedApplicable) && !groundedFollowupEntity) {
if (
(rawMetadataSignal || metadataFollowupSeedApplicable) &&
!groundedFollowupEntity &&
!metadataScopedLaneWithoutSubject
) {
pushUnique(entityCandidates, followupSeed.discoveryEntity);
pushUnique(entityCandidates, rawMetadataScopeHint);
}
@@ -1487,8 +1542,10 @@ export function buildAssistantMcpDiscoveryTurnInput(
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
? followupSeed.metadataAmbiguityEntitySets
: undefined,
metadata_scope_hint: metadataLaneScopeHint,
explicit_organization_scope: explicitOrganizationScope,
explicit_date_scope: explicitDateScope,
subject_resolution_optional: metadataScopedLaneWithoutSubject || undefined,
unsupported_but_understood_family:
unsupported ??
(lifecycleSignal
@@ -1546,12 +1603,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
if ((turnMeaning.metadata_ambiguity_entity_sets?.length ?? 0) > 0) {
cleanTurnMeaning.metadata_ambiguity_entity_sets = turnMeaning.metadata_ambiguity_entity_sets;
}
if (toNonEmptyString(turnMeaning.metadata_scope_hint)) {
cleanTurnMeaning.metadata_scope_hint = turnMeaning.metadata_scope_hint;
}
if (toNonEmptyString(turnMeaning.explicit_organization_scope)) {
cleanTurnMeaning.explicit_organization_scope = turnMeaning.explicit_organization_scope;
}
if (toNonEmptyString(turnMeaning.explicit_date_scope)) {
cleanTurnMeaning.explicit_date_scope = turnMeaning.explicit_date_scope;
}
if (turnMeaning.subject_resolution_optional) {
cleanTurnMeaning.subject_resolution_optional = true;
}
if (toNonEmptyString(turnMeaning.unsupported_but_understood_family)) {
cleanTurnMeaning.unsupported_but_understood_family = turnMeaning.unsupported_but_understood_family;
}
@@ -1668,6 +1731,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
if (metadataAmbiguityResolvedMovementFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_metadata_ambiguity_resolved_to_movement_lane");
}
if (metadataScopedLaneWithoutSubject) {
pushReason(reasonCodes, "mcp_discovery_metadata_scoped_lane_without_subject");
}
if (entityResolutionGroundedDocumentFollowupApplicable) {
pushReason(reasonCodes, "mcp_discovery_entity_resolution_grounded_document_followup");
}
@@ -28,6 +28,8 @@ import {
readAssistantMcpDiscoveryLoopAskedDomainFamily,
readAssistantMcpDiscoveryLoopAskedActionFamily,
readAssistantMcpDiscoveryLoopUnsupportedFamily,
readAssistantMcpDiscoveryLoopMetadataScopeHint,
readAssistantMcpDiscoveryLoopSubjectResolutionOptional,
readAddressDebugTemporalScope,
readAssistantMcpDiscoveryPilotScope,
resolveOrganizationClarificationContinuation,
@@ -727,6 +729,12 @@ export function createAssistantTransitionPolicy(deps) {
carryoverSourceDebug,
deps.toNonEmptyString
);
const sourceDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint(
carryoverSourceDebug,
deps.toNonEmptyString
);
const sourceDiscoveryLoopSubjectResolutionOptional =
readAssistantMcpDiscoveryLoopSubjectResolutionOptional(carryoverSourceDebug);
const sourceDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed(
carryoverSourceDebug,
deps.toNonEmptyString
@@ -1083,6 +1091,9 @@ export function createAssistantTransitionPolicy(deps) {
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
previous_discovery_loop_subject_resolution_optional:
sourceDiscoveryLoopSubjectResolutionOptional || undefined,
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
previous_discovery_entity_ambiguity_candidates:
sourceDiscoveryEntityAmbiguityCandidates.length > 0