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

@olurabian/tripwire

v0.2.1

Published

Catch the silent wrong action, the thing an AI agent does that returns ok but is wrong. A read-only scanner for agent runs. Zero dependencies.

Downloads

72

Readme

Your AI agent's worst failure is the one that returns ok. A refund that fires at ten times its cap. A record silently overwritten. A call to the wrong endpoint that succeeds. The run looks green, and the customer finds the bug.

Tripwire is a read-only scanner that catches the action that looks fine and isn't, before it reaches anyone. Point it at your agent, say what must always be true, and it runs the agent against your inputs and a library of adversarial ones, then flags every action that completed but broke a rule. It also watches the live record, the same expectations on every new receipt, and pushes only the flags. Zero dependencies.

Tripwire is the watch in Deadlatch, the open runtime governance stack. Purse enforces what an agent can do, blackbox proves what it did, Tripwire catches what slipped through.

npm i @olurabian/tripwire

Quick start

Say what must hold. Point Tripwire at your agent. Scan.

import { scan, defineExpectations, renderTerminal } from "@olurabian/tripwire";
import type { Recorder } from "@olurabian/tripwire";

// 1. State the invariants. A false means that action is an offender.
const expectations = defineExpectations([
  {
    id: "refund-within-cap",
    reason: "A refund went over the 1000 ceiling.",
    where: { action: "refund" },
    must: (r) => (r.input as { amount: number }).amount <= 1000,
  },
]);

// 2. Your agent. Wrap each action it takes so Tripwire can see it.
async function agent(input: unknown, rec: Recorder) {
  if (String(input).includes("refund")) {
    // this returns "ok", but the amount is wrong
    await rec.wrap("refund", { amount: 999999 }, () => "done");
  } else {
    await rec.wrap("charge", { amount: 20 }, () => "done");
  }
}

// 3. Scan. Runs your agent, plus a library of adversarial inputs.
const report = await scan({
  entrypoint: agent,
  expectations,
  adversarial: true,
  report: "tripwire-report.html",
});

console.log(renderTerminal(report));
// the refund completed "ok" and still broke the rule, so Tripwire flags it

How it works

Three parts, and one rule about which of them gets to decide.

Record. Wrap each action your agent takes with rec.wrap(action, input, fn). Tripwire captures the action name, the input, the outcome, the latency, and any error into a trace. Nothing is blocked and nothing is changed. It only watches.

Check. Your expectations are deterministic predicates. Each one names the records it applies to with where, and returns true when that record is fine. A false is an offender, full stop. This is the only thing that decides pass or fail.

Judge (optional). Pass an LLM-backed judge that reviews the trace and raises suspicions, notes about actions that look off. A suspicion is advisory only. It carries no verdict and can never flip a result, so a hallucinating judge cannot pass a real failure or fail a real pass. The deterministic check stays in charge. The default is noopJudge, which raises nothing.

Adversarial. Set adversarial: true and Tripwire runs your agent against a built-in library of hostile inputs designed to trigger the silent wrong action. Add your own with scenarios.

You get back a ScanReport with every scenario, the offending records, and a summary. Print it with renderTerminal(report), or write an HTML report with the report option.

Watch the live record

The same expectations run against receipts as they land. A monitor reads a receipt stream through a Source, keeps a sliding window, judges each new record, and pushes only the flags to a sink. Ordinary receipts never leave your side.

import { createMonitor, fromReceipts, deadlatchSink, fileCursorStore } from "@olurabian/tripwire/monitor";
import type { Source } from "@olurabian/tripwire/monitor";

// map your receipts to action records. null means skip this receipt.
const toRecords = fromReceipts("purse", (r) =>
  r.kind === "decision" ? { action: r.payload.event, input: r.payload.request, outcome: "ok" } : null,
);

// a source reads rows after the cursor, oldest first, and returns the new cursor
const source: Source = {
  async next(cursor) {
    const rows = await db.query("select seq, record from receipts where stream = $1 and seq > $2 order by seq limit 500", ["purse", cursor?.seq ?? 0]);
    const { records, skipped } = toRecords(rows.map((row) => ({ seq: row.seq, ...JSON.parse(row.record) })));
    return { records, skipped, cursor: rows.length ? { seq: rows.at(-1)!.seq } : cursor };
  },
};

const monitor = createMonitor({
  source,
  expectations,
  sink: deadlatchSink({
    url: "https://www.deadlatch.dev",
    projectKey: process.env.DEADLATCH_PROJECT_KEY!,
    monitor: { version: "1", stream: "purse", intervalMs: 60_000 },
  }),
  cursorStore: fileCursorStore("./monitor-cursor.json"),
  window: { count: 500, ms: 24 * 60 * 60 * 1000 },
  intervalMs: 60_000,
});
monitor.start();

What holds, by construction.

  • Only a new record is judged. A record the window has already seen, on a replay or otherwise, is never judged again.
  • The cursor is saved only after the sink confirms every flag the monitor holds. A failed push keeps those flags inside the monitor, holds the cursor, and the next tick pushes them again with the same ids before the cursor moves.
  • A flag id is sha256 of the stream, the seq and the expectation id. Retries, replays and two monitors on one chain converge on one row.
  • The window is bounded by count and by the receipt's own timestamp, measured against the newest receipt the window has accepted and never ahead of the wall clock, so a backfill of old history sees the windows the live run saw and one future-dated receipt cannot empty it. A receipt whose timestamp does not parse is skipped, never windowed.
  • stdoutSink and fileSink work with no hosted side. deadlatchSink queues while the hosted side is unreachable, backs off, drops a rejected batch with an event, stops the monitor on an unknown or revoked key, and sends the heartbeat the dashboard's liveness dot reads. The monitor calls that heartbeat once per tick with the cursor and the time of the last accepted flag.
  • start() keeps the process alive. Pass unref: true when something else, such as a server, already does.

Every flag carries the offending record, its input as cause, the receipt reference (stream, seq, id, hash, ts), and the window that tripped it. The scan report prints the receipt reference under a live offender too.

What it is, and what it is not

Tripwire detects. It does not block. It is read-only by design, so you can put it next to a live agent today without changing how the agent behaves. When you want to stop the action before it happens, that is enforcement, and that is Purse.

Already recording actions with blackbox? Hand a blackbox log to fromBlackbox() and scan the trace you already have.

The Deadlatch stack

Three primitives, one control loop. Adopt one, or run all three.

  • Purse, enforce. Stop the action off-policy, at the moment it happens.
  • blackbox, prove. A tamper-evident record of what happened, verifiable outside the tool.
  • Tripwire, watch. Catch the silent wrong action before a customer does.

License

MIT