Wrapper

Wrap any async function with a consensus gate using multiple reviewers and configurable strategies.

Overview

@consensus-tools/wrapper wraps any async function with a consensus gate. Multiple reviewers score the function's output, and a configurable strategy (unanimous, majority, or threshold) decides whether to allow, block, retry, or escalate.

Installation

pnpm add @consensus-tools/wrapper

Quick start

import { consensus } from "@consensus-tools/wrapper";

const safeDeploy = consensus({
  name: "deploy-to-prod",
  fn: deploy,
  reviewers: [securityReview, complianceReview],
  strategy: { strategy: "threshold", threshold: 0.7 },
  maxRetries: 2,
});

const result = await safeDeploy(env, version);
// result.action => "allow" | "block" | "retry" | "escalate"
// result.output => the return value of deploy()
// result.scores => [{ score: 0.9, rationale: "..." }, ...]
// result.aggregateScore => 0.85

API reference

consensus()

Wraps a function with a consensus gate. Returns an async function with the same signature that produces a DecisionResult.

function consensus<T, A extends any[]>(
  opts: ConsensusOptions<T, A>
): (...args: A) => Promise<DecisionResult<T>>;

Options:

FieldTypeDescription
namestringIdentifier for this consensus gate
fn(...args: A) => Promise<T>The function to wrap
reviewersReviewerFn<T>[]Array of reviewer functions
strategyStrategyConfigStrategy and threshold
maxRetriesnumberMax retry attempts before escalation
hooksLifecycleHooks<T>Optional lifecycle hooks

aggregateScores()

Use the aggregation logic directly without the full wrapper.

function aggregateScores<T>(
  scores: ReviewResult[],
  output: T,
  attempt: number,
  config: StrategyConfig,
  maxRetries: number
): DecisionResult<T>;

ReviewerFn<T>

A reviewer receives the function output and context, returning a score between 0 and 1.

type ReviewerFn<T> = (output: T, ctx: ReviewContext) => Promise<ReviewResult>;

ReviewResult fields:

FieldTypeDescription
scorenumber0-1 score
rationalestringExplanation
blockbooleanIf true, immediately blocks regardless of strategy

Strategies

StrategyPasses when...
"threshold"Average score >= threshold (default 0.5)
"majority"More than half of reviewers score >= threshold
"unanimous"Every reviewer scores >= threshold
consensus({
  name: "risky-op",
  fn: riskyOp,
  reviewers: [r1, r2, r3],
  strategy: { strategy: "unanimous", threshold: 0.8 },
});

Hard blocks

Setting block: true on any reviewer's result immediately blocks the operation regardless of strategy or scores.

Examples

Writing a reviewer

import type { ReviewerFn } from "@consensus-tools/wrapper";

const securityReview: ReviewerFn<DeployResult> = async (output, ctx) => {
  if (output.touchesProdDb) return { score: 0.1, rationale: "Modifies prod DB", block: true };
  return { score: 0.9, rationale: "Safe change" };
};

Lifecycle hooks

consensus({
  name: "managed-op",
  fn: myFn,
  reviewers: [r1],
  hooks: {
    beforeSubmit: (args) => console.log("About to call fn with", args),
    afterResolve: (result) => audit.log("Allowed", result),
    onBlock: (result) => alert("Blocked!", result.scores),
    onEscalate: (result) => pagerduty.trigger(result),
  },
});

Using aggregateScores directly

import { aggregateScores } from "@consensus-tools/wrapper";

const decision = aggregateScores(
  scores,
  output,
  1,                                         // attempt number
  { strategy: "majority", threshold: 0.6 },
  2,                                         // maxRetries
);
// decision.action => "allow" | "block" | "retry" | "escalate"
  • core -- Provides the underlying consensus infrastructure
  • policies -- Resolution strategies for job-based consensus (complementary to wrapper's function-level strategies)