refactor: split paired executor lifecycle (#187)

This commit is contained in:
Eyejoker
2026-05-29 19:45:08 +09:00
committed by GitHub
parent b2cfe385cf
commit 41f0460848
2 changed files with 402 additions and 298 deletions

View File

@@ -118,10 +118,6 @@
"maxFunctionLines": 417, "maxFunctionLines": 417,
"owner": "message executor lifecycle hotspot" "owner": "message executor lifecycle hotspot"
}, },
"src/message-agent-executor-paired.ts": {
"maxFunctionLines": 440,
"owner": "message executor paired hotspot"
},
"src/message-agent-executor-target.ts": { "src/message-agent-executor-target.ts": {
"maxFunctionLines": 274, "maxFunctionLines": 274,
"maxComplexity": 48, "maxComplexity": 48,

View File

@@ -98,7 +98,7 @@ export interface PairedExecutionLifecycle {
asyncFinalize(): Promise<void>; asyncFinalize(): Promise<void>;
} }
export function createPairedExecutionLifecycle(args: { interface CreatePairedExecutionLifecycleArgs {
pairedExecutionContext?: PreparedPairedExecutionContext; pairedExecutionContext?: PreparedPairedExecutionContext;
pairedTurnIdentity?: PairedTurnIdentity; pairedTurnIdentity?: PairedTurnIdentity;
completedRole: PairedRoomRole; completedRole: PairedRoomRole;
@@ -109,38 +109,138 @@ export function createPairedExecutionLifecycle(args: {
getCloseReason?: () => string | null; getCloseReason?: () => string | null;
onOutput?: (output: AgentOutput) => Promise<void>; onOutput?: (output: AgentOutput) => Promise<void>;
log: ExecutorLog; log: ExecutorLog;
}): PairedExecutionLifecycle { }
const {
pairedExecutionContext, type PairedExecutionStatus = 'succeeded' | 'failed';
pairedTurnIdentity,
completedRole, interface FinalizeState {
chatJid, directTerminalOutput: string | null;
effectiveStatus: PairedExecutionStatus;
sawOutputForFollowUp: boolean;
interruptedByHumanMessage: boolean;
}
class PairedExecutionLifecycleController implements PairedExecutionLifecycle {
private pairedExecutionStatus: PairedExecutionStatus = 'failed';
private pairedExecutionSummary: string | null = null;
private pairedFinalOutput: string | null = null;
private pairedSummaryLocked = false;
private pairedExecutionCompleted = false;
private pairedExecutionDelegated = false;
private pairedSawOutput = false;
private pairedTurnOutputPersisted = false;
private pairedTurnStateFinalized = false;
private leaseHeartbeatTimer: ReturnType<typeof setInterval> | null = null;
constructor(private readonly args: CreatePairedExecutionLifecycleArgs) {
if (!this.args.pairedExecutionContext) {
return;
}
this.leaseHeartbeatTimer = setInterval(
() => this.heartbeatLeaseIfNeeded(),
PAIRED_TASK_EXECUTION_LEASE_HEARTBEAT_MS,
);
this.leaseHeartbeatTimer.unref?.();
}
updateSummary({
outputText,
errorText,
}: {
outputText?: string | null;
errorText?: string | null;
}): void {
if (this.pairedSummaryLocked) {
return;
}
if (outputText && outputText.length > 0) {
this.pairedExecutionSummary = outputText.slice(0, 500);
return;
}
if (errorText && errorText.length > 0) {
this.pairedExecutionSummary = errorText.slice(0, 500);
}
}
recordFinalOutputBeforeDelivery(outputText: string): boolean {
if (this.wasInterruptedByHumanMessage()) return false;
if (!this.currentRunOwnsActiveAttempt('streamed-final-output')) {
return false;
}
this.lockVisibleVerdict(outputText);
this.completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded();
this.persistPairedTurnOutputIfNeeded();
return true;
}
completeImmediately({ status }: { status: PairedExecutionStatus }): void {
const { completedRole, pairedExecutionContext, runId } = this.args;
if (!pairedExecutionContext || this.pairedExecutionCompleted) {
return;
}
this.pairedExecutionStatus = status;
if (status === 'succeeded') {
this.persistPairedTurnOutputIfNeeded();
}
this.clearLeaseHeartbeat();
completePairedExecutionContext({
taskId: pairedExecutionContext.task.id,
role: completedRole,
status,
runId, runId,
enqueueMessageCheck, summary: this.pairedExecutionSummary,
getDirectTerminalDeliveryText, });
getCloseReason, this.pairedExecutionCompleted = true;
onOutput, }
log,
} = args;
let pairedExecutionStatus: 'succeeded' | 'failed' = 'failed'; markDelegated(): void {
let pairedExecutionSummary: string | null = null; this.pairedExecutionDelegated = true;
let pairedFinalOutput: string | null = null; }
let pairedSummaryLocked = false;
let pairedExecutionCompleted = false;
let pairedExecutionDelegated = false;
let pairedSawOutput = false;
let pairedTurnOutputPersisted = false;
let pairedTurnStateFinalized = false;
let leaseHeartbeatTimer: ReturnType<typeof setInterval> | null = null;
const requiresVisibleVerdict =
pairedExecutionContext?.requiresVisibleVerdict === true;
const missingVisibleVerdictSummary =
'Execution completed without a visible terminal verdict.';
const wasInterruptedByHumanMessage = (): boolean =>
isHumanMessageCloseReason(getCloseReason?.() ?? null);
const currentRunOwnsActiveAttempt = (reason: string): boolean => { markStatus(status: PairedExecutionStatus): void {
this.pairedExecutionStatus = status;
}
markSawOutput(sawOutput: boolean): void {
this.pairedSawOutput = sawOutput;
}
getSummary(): string | null {
return this.pairedExecutionSummary;
}
async asyncFinalize(): Promise<void> {
this.clearLeaseHeartbeat();
if (!this.currentRunOwnsActiveAttempt('async-finalize')) {
return;
}
if (this.releaseDelegatedExecutionIfNeeded()) {
return;
}
const state = this.resolveFinalizeState();
this.completeStoredExecutionIfNeeded(state);
this.finalizePairedTurnState(
state.effectiveStatus,
state.effectiveStatus === 'failed' ? this.pairedExecutionSummary : null,
);
await this.notifyCompletionAndQueueFollowUp(state);
}
private wasInterruptedByHumanMessage(): boolean {
return isHumanMessageCloseReason(this.args.getCloseReason?.() ?? null);
}
private currentRunOwnsActiveAttempt(reason: string): boolean {
const { log, pairedExecutionContext, pairedTurnIdentity, runId } =
this.args;
if (!pairedTurnIdentity) { if (!pairedTurnIdentity) {
return true; return true;
} }
@@ -176,13 +276,14 @@ export function createPairedExecutionLifecycle(args: {
'Skipping paired final side effects because this run no longer owns the active attempt', 'Skipping paired final side effects because this run no longer owns the active attempt',
); );
return false; return false;
}; }
const finalizePairedTurnState = ( private finalizePairedTurnState(
status: 'succeeded' | 'failed', status: 'succeeded' | 'failed',
errorText?: string | null, errorText?: string | null,
) => { ): void {
if (!pairedTurnIdentity || pairedTurnStateFinalized) { const { pairedTurnIdentity } = this.args;
if (!pairedTurnIdentity || this.pairedTurnStateFinalized) {
return; return;
} }
if (status === 'succeeded') { if (status === 'succeeded') {
@@ -190,20 +291,22 @@ export function createPairedExecutionLifecycle(args: {
} else { } else {
failPairedTurn({ failPairedTurn({
turnIdentity: pairedTurnIdentity, turnIdentity: pairedTurnIdentity,
error: errorText ?? pairedExecutionSummary, error: errorText ?? this.pairedExecutionSummary,
}); });
} }
pairedTurnStateFinalized = true; this.pairedTurnStateFinalized = true;
}; }
const clearLeaseHeartbeat = () => { private clearLeaseHeartbeat(): void {
if (!leaseHeartbeatTimer) { if (!this.leaseHeartbeatTimer) {
return; return;
} }
clearInterval(leaseHeartbeatTimer); clearInterval(this.leaseHeartbeatTimer);
leaseHeartbeatTimer = null; this.leaseHeartbeatTimer = null;
}; }
const heartbeatLeaseIfNeeded = () => {
private heartbeatLeaseIfNeeded(): void {
const { pairedExecutionContext, runId, log } = this.args;
if (!pairedExecutionContext) { if (!pairedExecutionContext) {
return; return;
} }
@@ -231,22 +334,15 @@ export function createPairedExecutionLifecycle(args: {
'Failed to refresh paired execution lease heartbeat', 'Failed to refresh paired execution lease heartbeat',
); );
} }
};
if (pairedExecutionContext) {
leaseHeartbeatTimer = setInterval(
heartbeatLeaseIfNeeded,
PAIRED_TASK_EXECUTION_LEASE_HEARTBEAT_MS,
);
leaseHeartbeatTimer.unref?.();
} }
const persistPairedTurnOutputIfNeeded = () => { private persistPairedTurnOutputIfNeeded(): void {
const { completedRole, pairedExecutionContext } = this.args;
if ( if (
!pairedExecutionContext || !pairedExecutionContext ||
pairedTurnOutputPersisted || this.pairedTurnOutputPersisted ||
!pairedFinalOutput || !this.pairedFinalOutput ||
pairedFinalOutput.length === 0 this.pairedFinalOutput.length === 0
) { ) {
return; return;
} }
@@ -256,57 +352,65 @@ export function createPairedExecutionLifecycle(args: {
pairedExecutionContext.task.id, pairedExecutionContext.task.id,
turnNumber, turnNumber,
completedRole, completedRole,
pairedFinalOutput, this.pairedFinalOutput,
); );
pairedTurnOutputPersisted = true; this.pairedTurnOutputPersisted = true;
}; }
const completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded = () => { private completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded(): void {
const { completedRole, pairedExecutionContext, runId } = this.args;
if ( if (
completedRole !== 'owner' || completedRole !== 'owner' ||
!pairedExecutionContext || !pairedExecutionContext ||
pairedExecutionCompleted || this.pairedExecutionCompleted ||
!pairedFinalOutput || !this.pairedFinalOutput ||
pairedFinalOutput.length === 0 this.pairedFinalOutput.length === 0
) { ) {
return; return;
} }
pairedExecutionStatus = 'succeeded'; this.pairedExecutionStatus = 'succeeded';
pairedSawOutput = true; this.pairedSawOutput = true;
persistPairedTurnOutputIfNeeded(); this.persistPairedTurnOutputIfNeeded();
clearLeaseHeartbeat(); this.clearLeaseHeartbeat();
completePairedExecutionContext({ completePairedExecutionContext({
taskId: pairedExecutionContext.task.id, taskId: pairedExecutionContext.task.id,
role: completedRole, role: completedRole,
status: 'succeeded', status: 'succeeded',
runId, runId,
summary: pairedExecutionSummary, summary: this.pairedExecutionSummary,
}); });
pairedExecutionCompleted = true; this.pairedExecutionCompleted = true;
}; }
const lockVisibleVerdict = (outputText: string) => { private lockVisibleVerdict(outputText: string): void {
if (outputText.length === 0) { if (outputText.length === 0) {
return; return;
} }
if (!pairedFinalOutput || pairedFinalOutput.length === 0) { if (!this.pairedFinalOutput || this.pairedFinalOutput.length === 0) {
pairedFinalOutput = outputText; this.pairedFinalOutput = outputText;
} }
if (!pairedSummaryLocked) { if (!this.pairedSummaryLocked) {
pairedExecutionSummary = outputText.slice(0, 500); this.pairedExecutionSummary = outputText.slice(0, 500);
pairedSummaryLocked = true; this.pairedSummaryLocked = true;
}
this.pairedSawOutput = true;
} }
pairedSawOutput = true;
};
const adoptDirectTerminalDeliveryIfNeeded = () => { private adoptDirectTerminalDeliveryIfNeeded(): string | null {
const {
completedRole,
getDirectTerminalDeliveryText,
log,
pairedExecutionContext,
runId,
} = this.args;
const outputText = getDirectTerminalDeliveryText?.(); const outputText = getDirectTerminalDeliveryText?.();
if (!outputText || outputText.length === 0) { if (!outputText || outputText.length === 0) {
return null; return null;
} }
if (!pairedFinalOutput || pairedFinalOutput.length === 0) { if (!this.pairedFinalOutput || this.pairedFinalOutput.length === 0) {
lockVisibleVerdict(outputText); this.lockVisibleVerdict(outputText);
log.info( log.info(
{ {
pairedTaskId: pairedExecutionContext?.task.id ?? null, pairedTaskId: pairedExecutionContext?.task.id ?? null,
@@ -315,85 +419,18 @@ export function createPairedExecutionLifecycle(args: {
}, },
'Adopted direct terminal delivery as paired final output', 'Adopted direct terminal delivery as paired final output',
); );
} else if (!pairedSummaryLocked) { } else if (!this.pairedSummaryLocked) {
pairedExecutionSummary = pairedFinalOutput.slice(0, 500); this.pairedExecutionSummary = this.pairedFinalOutput.slice(0, 500);
pairedSummaryLocked = true; this.pairedSummaryLocked = true;
} }
return outputText; return outputText;
};
return {
updateSummary({ outputText, errorText }) {
if (pairedSummaryLocked) {
return;
} }
if (outputText && outputText.length > 0) { private releaseDelegatedExecutionIfNeeded(): boolean {
pairedExecutionSummary = outputText.slice(0, 500); const { log, pairedExecutionContext, runId } = this.args;
return; if (!pairedExecutionContext || !this.pairedExecutionDelegated) {
}
if (errorText && errorText.length > 0) {
pairedExecutionSummary = errorText.slice(0, 500);
}
},
recordFinalOutputBeforeDelivery(outputText) {
if (wasInterruptedByHumanMessage()) return false;
if (!currentRunOwnsActiveAttempt('streamed-final-output')) {
return false; return false;
} }
lockVisibleVerdict(outputText);
completeSuccessfulOwnerTurnBeforeDeliveryIfNeeded();
persistPairedTurnOutputIfNeeded();
return true;
},
completeImmediately({ status }) {
if (!pairedExecutionContext || pairedExecutionCompleted) {
return;
}
pairedExecutionStatus = status;
if (status === 'succeeded') {
persistPairedTurnOutputIfNeeded();
}
clearLeaseHeartbeat();
completePairedExecutionContext({
taskId: pairedExecutionContext.task.id,
role: completedRole,
status,
runId,
summary: pairedExecutionSummary,
});
pairedExecutionCompleted = true;
},
markDelegated() {
pairedExecutionDelegated = true;
},
markStatus(status) {
pairedExecutionStatus = status;
},
markSawOutput(sawOutput) {
pairedSawOutput = sawOutput;
},
getSummary() {
return pairedExecutionSummary;
},
async asyncFinalize() {
clearLeaseHeartbeat();
if (!currentRunOwnsActiveAttempt('async-finalize')) {
return;
}
if (pairedExecutionContext && pairedExecutionDelegated) {
try { try {
releasePairedTaskExecutionLease({ releasePairedTaskExecutionLease({
taskId: pairedExecutionContext.task.id, taskId: pairedExecutionContext.task.id,
@@ -409,17 +446,36 @@ export function createPairedExecutionLifecycle(args: {
'Failed to release paired execution lease for delegated fallback handoff', 'Failed to release paired execution lease for delegated fallback handoff',
); );
} }
pairedExecutionCompleted = true; this.pairedExecutionCompleted = true;
return; return true;
} }
const directTerminalOutput = adoptDirectTerminalDeliveryIfNeeded(); private resolveFinalizeState(): FinalizeState {
const directTerminalOutput = this.adoptDirectTerminalDeliveryIfNeeded();
const missingVisibleVerdict = this.resolveMissingVisibleVerdict();
const effectiveStatus = this.resolveEffectiveStatus(missingVisibleVerdict);
const sawOutputForFollowUp = missingVisibleVerdict
? false
: this.pairedSawOutput;
return {
directTerminalOutput,
effectiveStatus,
sawOutputForFollowUp,
interruptedByHumanMessage: this.wasInterruptedByHumanMessage(),
};
}
private resolveMissingVisibleVerdict(): boolean {
const { completedRole, log, pairedExecutionContext, runId } = this.args;
const missingVisibleVerdict = const missingVisibleVerdict =
requiresVisibleVerdict && pairedExecutionContext?.requiresVisibleVerdict === true &&
(!pairedFinalOutput || pairedFinalOutput.length === 0); (!this.pairedFinalOutput || this.pairedFinalOutput.length === 0);
if (missingVisibleVerdict) { if (!missingVisibleVerdict) {
pairedExecutionSummary = missingVisibleVerdictSummary; return false;
}
this.pairedExecutionSummary =
'Execution completed without a visible terminal verdict.';
log.warn( log.warn(
{ {
pairedTaskId: pairedExecutionContext?.task.id ?? null, pairedTaskId: pairedExecutionContext?.task.id ?? null,
@@ -428,58 +484,71 @@ export function createPairedExecutionLifecycle(args: {
}, },
'Treating paired execution as failed because it ended without a visible terminal verdict', 'Treating paired execution as failed because it ended without a visible terminal verdict',
); );
return true;
} }
const effectiveStatus =
completedRole === 'owner' &&
pairedExecutionStatus === 'succeeded' &&
!pairedSawOutput
? 'failed'
: missingVisibleVerdict && pairedExecutionStatus === 'succeeded'
? 'failed'
: pairedExecutionStatus;
const sawOutputForFollowUp = missingVisibleVerdict
? false
: pairedSawOutput;
const interruptedByHumanMessage = wasInterruptedByHumanMessage();
if (pairedExecutionContext && !pairedExecutionCompleted) { private resolveEffectiveStatus(
if (interruptedByHumanMessage) { missingVisibleVerdict: boolean,
): PairedExecutionStatus {
if (
this.args.completedRole === 'owner' &&
this.pairedExecutionStatus === 'succeeded' &&
!this.pairedSawOutput
) {
return 'failed';
}
if (missingVisibleVerdict && this.pairedExecutionStatus === 'succeeded') {
return 'failed';
}
return this.pairedExecutionStatus;
}
private completeStoredExecutionIfNeeded(state: FinalizeState): void {
const { completedRole, log, pairedExecutionContext, runId } = this.args;
if (!pairedExecutionContext || this.pairedExecutionCompleted) {
return;
}
if (state.interruptedByHumanMessage) {
releaseInterruptedPairedExecution( releaseInterruptedPairedExecution(
pairedExecutionContext.task.id, pairedExecutionContext.task.id,
runId, runId,
log, log,
); );
} else { this.pairedExecutionCompleted = true;
if (effectiveStatus === 'succeeded') { return;
}
this.persistSuccessfulOutputBeforeCompletion(state.effectiveStatus);
completeStoredExecution(
pairedExecutionContext.task.id,
completedRole,
state.effectiveStatus,
runId,
this.pairedExecutionSummary,
);
this.pairedExecutionCompleted = true;
}
private persistSuccessfulOutputBeforeCompletion(
effectiveStatus: PairedExecutionStatus,
): void {
if (effectiveStatus !== 'succeeded') {
return;
}
try { try {
persistPairedTurnOutputIfNeeded(); this.persistPairedTurnOutputIfNeeded();
} catch (err) { } catch (err) {
log.warn( this.args.log.warn(
{ pairedTaskId: pairedExecutionContext.task.id, err }, { pairedTaskId: this.args.pairedExecutionContext?.task.id, err },
'Failed to store paired turn output', 'Failed to store paired turn output',
); );
} }
} }
completeStoredExecution(
pairedExecutionContext.task.id,
completedRole,
effectiveStatus,
runId,
pairedExecutionSummary,
);
}
pairedExecutionCompleted = true;
}
finalizePairedTurnState( private async notifyCompletionAndQueueFollowUp(
effectiveStatus, state: FinalizeState,
effectiveStatus === 'failed' ? pairedExecutionSummary : null, ): Promise<void> {
); const { chatJid, onOutput, pairedExecutionContext } = this.args;
if (!pairedExecutionContext || state.interruptedByHumanMessage) {
if (!pairedExecutionContext) {
return;
}
if (interruptedByHumanMessage) {
return; return;
} }
const finishedTask = getPairedTaskById(pairedExecutionContext.task.id); const finishedTask = getPairedTaskById(pairedExecutionContext.task.id);
@@ -488,45 +557,79 @@ export function createPairedExecutionLifecycle(args: {
chatJid, chatJid,
onOutput, onOutput,
}); });
this.queueFollowUpIfNeeded(state, finishedTask);
}
const queueAction = private queueFollowUpIfNeeded(
directTerminalOutput && state: FinalizeState,
(completedRole === 'reviewer' || completedRole === 'arbiter') finishedTask: PairedTaskRecord | null | undefined,
? 'none' ): void {
: resolvePairedFollowUpQueueAction({ const queueAction = this.resolveQueueAction(state, finishedTask);
completedRole,
executionStatus: effectiveStatus,
sawOutput: sawOutputForFollowUp,
taskStatus: finishedTask?.status ?? null,
outputSummary: pairedExecutionSummary,
});
if (queueAction !== 'pending' || !finishedTask) { if (queueAction !== 'pending' || !finishedTask) {
return; return;
} }
const followUpResult = this.enqueueFollowUp(state, finishedTask);
this.logFollowUpResult(followUpResult, state, finishedTask);
}
const followUpResult = enqueuePairedFollowUpAfterEvent({ private resolveQueueAction(
state: FinalizeState,
finishedTask: PairedTaskRecord | null | undefined,
): ReturnType<typeof resolvePairedFollowUpQueueAction> | 'none' {
const { completedRole } = this.args;
if (
state.directTerminalOutput &&
(completedRole === 'reviewer' || completedRole === 'arbiter')
) {
return 'none';
}
return resolvePairedFollowUpQueueAction({
completedRole,
executionStatus: state.effectiveStatus,
sawOutput: state.sawOutputForFollowUp,
taskStatus: finishedTask?.status ?? null,
outputSummary: this.pairedExecutionSummary,
});
}
private enqueueFollowUp(
state: FinalizeState,
finishedTask: PairedTaskRecord,
): ReturnType<typeof enqueuePairedFollowUpAfterEvent> {
const { chatJid, completedRole, enqueueMessageCheck, runId } = this.args;
return enqueuePairedFollowUpAfterEvent({
chatJid, chatJid,
runId, runId,
task: finishedTask, task: finishedTask,
source: 'executor-recovery', source: 'executor-recovery',
completedRole, completedRole,
executionStatus: effectiveStatus, executionStatus: state.effectiveStatus,
sawOutput: sawOutputForFollowUp, sawOutput: state.sawOutputForFollowUp,
fallbackLastTurnOutputRole: sawOutputForFollowUp ? completedRole : null, fallbackLastTurnOutputRole: state.sawOutputForFollowUp
? completedRole
: null,
fallbackLastTurnOutputVerdict: fallbackLastTurnOutputVerdict:
sawOutputForFollowUp && pairedExecutionSummary state.sawOutputForFollowUp && this.pairedExecutionSummary
? parseVisibleVerdict(pairedExecutionSummary) ? parseVisibleVerdict(this.pairedExecutionSummary)
: null, : null,
enqueueMessageCheck, enqueueMessageCheck,
}); });
}
private logFollowUpResult(
followUpResult: ReturnType<typeof enqueuePairedFollowUpAfterEvent>,
state: FinalizeState,
finishedTask: PairedTaskRecord,
): void {
const { completedRole, log, pairedExecutionContext } = this.args;
if (followUpResult.kind !== 'paired-follow-up') { if (followUpResult.kind !== 'paired-follow-up') {
return; return;
} }
log.info( log.info(
{ {
taskId: pairedExecutionContext.task.id, taskId: pairedExecutionContext?.task.id,
role: completedRole, role: completedRole,
pairedExecutionStatus: effectiveStatus, pairedExecutionStatus: state.effectiveStatus,
taskStatus: finishedTask.status, taskStatus: finishedTask.status,
intentKind: followUpResult.intentKind, intentKind: followUpResult.intentKind,
scheduled: followUpResult.scheduled, scheduled: followUpResult.scheduled,
@@ -535,6 +638,11 @@ export function createPairedExecutionLifecycle(args: {
? 'Queued paired follow-up after failed reviewer/arbiter execution left a pending task state' ? 'Queued paired follow-up after failed reviewer/arbiter execution left a pending task state'
: 'Skipped duplicate paired follow-up after failed reviewer/arbiter execution while task state was unchanged', : 'Skipped duplicate paired follow-up after failed reviewer/arbiter execution while task state was unchanged',
); );
}, }
}; }
export function createPairedExecutionLifecycle(
args: CreatePairedExecutionLifecycleArgs,
): PairedExecutionLifecycle {
return new PairedExecutionLifecycleController(args);
} }