Individual risks carry severity: 'low' | 'medium' | 'high' and affected_nodes: string[]. After
each wave, re-evaluate severity of risks whose affected_nodes intersect the just-completed wave's
subtask_ids. Evidence from completed nodes can either resolve or escalate a risk.
Parley as Scheduled Operation. Parley is not triggered by anomaly detection or a human command.
It is scheduled by the presence of TENTATIVE nodes or a non-PROCEED premortem before execution
begins. The executor inserts a parley checkpoint into the run plan between every consecutive pair of
waves. The checkpoint runs zero or minimal LLM calls when all conditions are green (no TENTATIVE in
upcoming wave, all risks resolved or low). It runs a structured re-evaluation only when conditions
are not green.
Swarm Silence vs. DAG Parley. The swarm topology (executeSwarm in topologies/swarm.ts)
converges by idle-timeout, vote, or quality-threshold — reactive convergence driven by message bus
drain. Parley is the structural complement: it operates between scheduled waves in a DAG topology,
uses the decomposer's commitment model rather than a message bus, and results in a plan mutation
(node promotion, demotion, or pruning) rather than swarm termination.
function shouldParley(
upcomingWave: Wave,
premortem: PreMortemOutput,
waveOutputs: Map<string, NodeOutput>, // outputs from all completed waves
): boolean {
// 1. Check if any upcoming node is TENTATIVE or EXPLORATORY
const hasUncertain = upcomingWave.nodes.some(
n => n.commitment_level === 'TENTATIVE' || n.commitment_level === 'EXPLORATORY'
);
// 2. Check premortem standing recommendation
const requiresByPremortem = (
premortem.recommendation === 'ACCEPT_WITH_MONITORING' ||
premortem.recommendation === 'ESCALATE_TO_HUMAN'
);
return hasUncertain || requiresByPremortem;
}
async function parley(
upcomingWave: Wave,
premortem: PreMortemOutput,
completedWaveOutputs: Map<string, NodeOutput>,
provider: LLMProvider,
model: string,
): Promise<ParleyDecision> {
// Step 1: Collect evidence from completed nodes that are dependencies
// of TENTATIVE/EXPLORATORY nodes in the upcoming wave.
const relevantOutputs = upcomingWave.nodes
.filter(n => n.commitment_level !== 'COMMITTED')
.flatMap(n => extractDependencyIds(n.input_contract))
.map(depId => completedWaveOutputs.get(depId))
.filter(Boolean);
// Step 2: Re-evaluate risk severity for risks whose affected_nodes
// intersect the just-completed wave.
const resolvedRisks: string[] = [];
const escalatedRisks: Risk[] = [];
for (const risk of premortem.risks) {
const hasEvidence = risk.affected_nodes.some(
id => completedWaveOutputs.has(id)
);
if (hasEvidence) {
const updated = await reassessRisk(risk, completedWaveOutputs, provider, model);
if (updated.severity === 'low') resolvedRisks.push(risk.description);
else if (updated.severity === 'high') escalatedRisks.push(updated);
}
}
// Step 3: For each uncertain node, promote / demote / prune.
const mutations: NodeMutation[] = [];
for (const node of upcomingWave.nodes) {
if (node.commitment_level === 'COMMITTED') continue;
const decision = await evaluateNodeCommitment(
node,
relevantOutputs,
escalatedRisks,
provider,
model,
);
// decision.action: 'promote' | 'demote' | 'prune'
// decision.new_commitment_level: 'COMMITTED' | 'EXPLORATORY' | null (null = pruned)
mutations.push(decision);
}
// Step 4: If ESCALATE_TO_HUMAN and any risk remains high, surface to operator.
if (
premortem.recommendation === 'ESCALATE_TO_HUMAN' &&
escalatedRisks.some(r => r.severity === 'high')
) {
return { action: 'escalate', mutations, resolvedRisks, escalatedRisks };
}
// Step 5: Return updated wave plan. Pruned nodes removed. Promoted nodes
// proceed as COMMITTED. Demoted nodes pushed to a later wave.
return { action: 'proceed', mutations, resolvedRisks, escalatedRisks };
}
// Execution loop with parley:
async function executeWithParley(dag: PredictedDAG, ...) {
for (let i = 0; i < dag.waves.length; i++) {
const wave = dag.waves[i];
// Execute wave N in parallel (as dag-runtime does)
const waveOutputs = await executeWaveParallel(wave, ...);
completedOutputs.mergeAll(waveOutputs);
// Parley before wave N+1
if (i + 1 < dag.waves.length) {
const nextWave = dag.waves[i + 1];
if (shouldParley(nextWave, dag.premortem, completedOutputs)) {
const decision = await parley(
nextWave, dag.premortem, completedOutputs, provider, model
);
if (decision.action === 'escalate') {
await notifyOperator(decision);
return; // halt until operator responds
}
applyMutations(dag.waves[i + 1], decision.mutations);
}
}
}
}