export type HandoffStatus = 'queued' | 'accepted' | 'running' | 'completed' | 'failed'; export interface CoordinationScope { readonly actorId: string; readonly tenantId: string; readonly correlationId: string; /** Trusted gateway/configuration identity; never supplied by a channel client. */ readonly requesterAgentId: string; } export interface InteractionCoordinationIdentity { readonly interactionAgentId: string; readonly orchestrationAgentId: string; } export interface HandoffRequest { readonly idempotencyKey: string; readonly summary: string; readonly context?: string; readonly missionId?: string; } export interface Handoff { readonly handoffId: string; readonly targetAgentId: string; readonly request: HandoffRequest; readonly scope: CoordinationScope; } export interface HandoffReceipt { readonly handoffId: string; readonly targetAgentId: string; readonly status: 'queued' | 'accepted'; readonly correlationId: string; } export interface InteractionCoordinationActivity { readonly occurredAt: string; readonly status: HandoffStatus; readonly summary: string; } export interface CoordinationObservation { readonly handoffId: string; readonly targetAgentId: string; readonly status: HandoffStatus; readonly correlationId: string; readonly activity: readonly InteractionCoordinationActivity[]; } export interface CoordinationResult { readonly handoffId: string; readonly targetAgentId: string; readonly status: 'completed' | 'failed' | 'pending'; readonly correlationId: string; readonly summary?: string; } /** * Transport-neutral boundary. The interaction plane can request work and read * its progress/result, but it cannot issue worker, review, merge, or other * general orchestration commands. */ export interface InteractionCoordinationPort { handoff(handoff: Handoff): Promise; observe(handoffId: string, scope: CoordinationScope): Promise; result(handoffId: string, scope: CoordinationScope): Promise; } export type InteractionCoordinationAuthorityErrorCode = | 'invalid_identity' | 'requester_forbidden' | 'target_drift' | 'correlation_drift'; export class InteractionCoordinationAuthorityError extends Error { constructor( readonly code: InteractionCoordinationAuthorityErrorCode, message: string, ) { super(message); this.name = InteractionCoordinationAuthorityError.name; } } /** * Enforces the interaction-to-orchestration authority boundary before a * transport is reached. Identity names remain configuration data. */ export class InteractionCoordinationClient { private readonly identity: InteractionCoordinationIdentity; constructor( identity: InteractionCoordinationIdentity, private readonly port: InteractionCoordinationPort, private readonly handoffIdFactory: () => string = (): string => crypto.randomUUID(), ) { this.identity = normalizeIdentity(identity); } async handoff(request: HandoffRequest, scope: CoordinationScope): Promise { this.assertRequester(scope); const handoff: Handoff = { handoffId: this.handoffIdFactory(), targetAgentId: this.identity.orchestrationAgentId, request: snapshotRequest(request), scope: snapshotScope(scope), }; const receipt = await this.port.handoff(handoff); if ( receipt.handoffId !== handoff.handoffId || receipt.targetAgentId !== handoff.targetAgentId ) { throw new InteractionCoordinationAuthorityError( 'target_drift', 'Interaction coordination transport returned a mismatched handoff target', ); } if (receipt.correlationId !== handoff.scope.correlationId) { throw new InteractionCoordinationAuthorityError( 'correlation_drift', 'Interaction coordination transport returned a mismatched correlation ID', ); } return receipt; } async observe(handoffId: string, scope: CoordinationScope): Promise { this.assertRequester(scope); return this.assertObservation(await this.port.observe(handoffId, snapshotScope(scope)), scope); } async result(handoffId: string, scope: CoordinationScope): Promise { this.assertRequester(scope); return this.assertResult(await this.port.result(handoffId, snapshotScope(scope)), scope); } private assertRequester(scope: CoordinationScope): void { if (scope.requesterAgentId !== this.identity.interactionAgentId) { throw new InteractionCoordinationAuthorityError( 'requester_forbidden', 'Requester is not the configured interaction agent', ); } } private assertObservation( observation: CoordinationObservation, scope: CoordinationScope, ): CoordinationObservation { if (observation.targetAgentId !== this.identity.orchestrationAgentId) { throw new InteractionCoordinationAuthorityError( 'target_drift', 'Interaction coordination transport returned an unexpected observation target', ); } if (observation.correlationId !== scope.correlationId) { throw new InteractionCoordinationAuthorityError( 'correlation_drift', 'Interaction coordination transport returned a mismatched observation correlation ID', ); } return observation; } private assertResult(result: CoordinationResult, scope: CoordinationScope): CoordinationResult { if (result.targetAgentId !== this.identity.orchestrationAgentId) { throw new InteractionCoordinationAuthorityError( 'target_drift', 'Interaction coordination transport returned an unexpected result target', ); } if (result.correlationId !== scope.correlationId) { throw new InteractionCoordinationAuthorityError( 'correlation_drift', 'Interaction coordination transport returned a mismatched result correlation ID', ); } return result; } } function normalizeIdentity( identity: InteractionCoordinationIdentity, ): InteractionCoordinationIdentity { const interactionAgentId = identity.interactionAgentId.trim(); const orchestrationAgentId = identity.orchestrationAgentId.trim(); if (interactionAgentId.length === 0 || orchestrationAgentId.length === 0) { throw new InteractionCoordinationAuthorityError( 'invalid_identity', 'Interaction and orchestration identities are required', ); } if (interactionAgentId === orchestrationAgentId) { throw new InteractionCoordinationAuthorityError( 'invalid_identity', 'Interaction and orchestration identities must differ', ); } return Object.freeze({ interactionAgentId, orchestrationAgentId }); } function snapshotRequest(request: HandoffRequest): HandoffRequest { return Object.freeze({ ...request }); } function snapshotScope(scope: CoordinationScope): CoordinationScope { return Object.freeze({ ...scope }); }