npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agent-ablation

v0.1.0

Published

Leave-one-out ablation testing for multi-agent decision systems — find out which agents' findings actually change the outcome.

Readme

agent-ablation

Leave-one-out ablation testing for multi-agent decision systems. You have a set of per-agent findings (scores, confidences, whatever your pipeline produces) and a function that turns those findings into a verdict. agent-ablation answers one question: which of my agents' findings actually changed that verdict, and which were along for the ride?

It removes each finding one at a time, re-runs your decision function on what's left, and reports which removals flipped the outcome. Zero runtime dependencies, zero opinions about how your agents work — you supply the findings and the decision function, it does the leave-one-out loop and the bookkeeping.

Install

npm install agent-ablation

Quick example

import { runAblation, type Finding } from "agent-ablation";

type Verdict = "approve" | "decline" | "escalate";

function decide(findings: Finding[]): Verdict {
  const risk = 1 - findings.reduce((p, f) => p * (1 - f.score / 100), 1);
  if (risk >= 0.7) return "decline";
  if (risk <= 0.3) return "approve";
  return "escalate";
}

const findings: Finding[] = [
  { agentId: "transaction_pattern", score: 25 },
  { agentId: "identity_signal", score: 90 },
  { agentId: "network_analysis", score: 20 },
];

const result = runAblation(findings, decide);

console.log(result.baseline);        // "decline"
console.log(result.loadBearingRatio); // fraction of agents whose removal changed the verdict
for (const p of result.perAgent) {
  console.log(p.removedAgentId, "->", p.verdictWithout, p.changed ? "(load-bearing)" : "");
}

For a batch of cases, batchAblation runs the same ablation over each one and aggregates the results — including, per agent, the fraction of cases in which removing that agent changed the outcome:

import { batchAblation } from "agent-ablation";

const { results, summary } = batchAblation(allCases, decide);

console.log(summary.averageLoadBearingRatio);
console.log(summary.perAgentInfluence); // { transaction_pattern: 0.17, identity_signal: 0.83, ... }

Worked example: reproducing SentryMesh's 33% multi-signal-share finding

SentryMesh is a four-specialist multi-agent fraud investigation system. Its own eval harness runs an ablation over its 23-case bank and reports the result plainly in its README: of 9 cases the system auto-resolved without escalating to a human, only 3 survive removal of their single loudest specialist — 6 collapse to escalate. SentryMesh calls this "the most important number in the report," because it means two-thirds of those auto-decisions rested on one specialist's finding, with the other three specialists' LLM calls spent for nothing.

Those six cases, straight from SentryMesh's ablation table:

| Case | Decision | Remove | Becomes | |---|---|---|---| | SM-001 | auto_decline | identity_signal (100) | escalate | | SM-002 | auto_decline | identity_signal (75) | escalate | | SM-005 | auto_decline | identity_signal (80) | escalate | | SM-006 | auto_decline | identity_signal (70) | escalate | | SM-012 | auto_decline | network_analysis (90) | escalate | | SM-016 | auto_approve | transaction_pattern (26) | escalate |

tests/ablation.test.ts in this repo reproduces all six as a worked example: it builds each case's four-specialist Finding[] (with the named specialist's score matching SentryMesh's table exactly), runs it through a decision function modeled on SentryMesh's own description of its aggregation — findings combine via noisy-OR, gated by panel confidence — and asserts that runAblation correctly identifies the named specialist's removal as the one that flips each case to escalate. It's the credibility anchor for this package: if agent-ablation couldn't reproduce a real, previously-published ablation result on real case data, it wouldn't be trustworthy on your data either.

Limitations

This tool detects verdict change, not verdict quality. A flipped verdict after removing an agent tells you that agent was load-bearing for that decision — it says nothing about whether the original verdict or the post-removal one was correct. Conversely, a low loadBearingRatio is not automatically a flaw: redundancy across agents can be exactly what you want (independent corroboration is the point of running more than one specialist), and this tool has no way to distinguish "healthy redundancy" from "wasted compute" for you.

Leave-one-out misses agents that only matter in pairs. This is a real gap, not a hedge. If removing agent A alone doesn't flip the verdict, and removing agent B alone doesn't either, but removing both together would, leave-one-out ablation will report both as not load-bearing. Catching that requires ablating combinations, which this package deliberately does not do — the combinatorics blow up fast, and a leave-one-out pass over a modest agent panel is already the useful 80% case. If you suspect joint effects, ablate the suspected pair manually by filtering findings yourself before calling decide.

decide() must be pure and deterministic. runAblation calls decide once per finding removed, expecting each call to depend only on the findings it's given. If your decision logic calls an LLM internally, this tool does not apply to that call — it only makes sense as a probe over a deterministic aggregation step that runs after the LLM reasoning is done. This mirrors SentryMesh's own architecture: its supervisor's model call interprets why specialists conflict and produces a combined risk and confidence, but turning those numbers into an auto-decline/auto-approve/escalate action is decide(), a pure function with no model call in it — "an LLM is a good place for the interpretation and a bad place for a threshold that compliance will one day have to explain in writing," in SentryMesh's own words. agent-ablation ablates that downstream pure function, not the LLM call that fed it.

API reference

interface Finding {
  agentId: string;
  score: number;
  confidence?: number;
  metadata?: Record<string, unknown>;
}

type DecisionFn<TVerdict> = (findings: Finding[]) => TVerdict;

interface PerAgentAblation<TVerdict> {
  removedAgentId: string;
  verdictWithout: TVerdict;
  changed: boolean;
}

interface AblationResult<TVerdict> {
  baseline: TVerdict;
  perAgent: PerAgentAblation<TVerdict>[];
  loadBearingCount: number;
  totalAgents: number;
  loadBearingRatio: number;
}

function runAblation<TVerdict>(
  findings: Finding[],
  decide: DecisionFn<TVerdict>,
  equals?: (a: TVerdict, b: TVerdict) => boolean
): AblationResult<TVerdict>;

interface BatchAblationSummary {
  cases: number;
  averageLoadBearingRatio: number;
  perAgentInfluence: Record<string, number>;
}

function batchAblation<TVerdict>(
  cases: Finding[][],
  decide: DecisionFn<TVerdict>,
  equals?: (a: TVerdict, b: TVerdict) => boolean
): { results: AblationResult<TVerdict>[]; summary: BatchAblationSummary };

equals defaults to ===. If TVerdict is an object (or anything else compared by reference rather than value), you must supply your own equals — otherwise every ablation will read as "changed" purely because two structurally identical verdict objects are never === to each other, regardless of whether the decision actually differed.

License

MIT


Ayush Verma — [email protected]