Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Tripwire

Reads your Convex logs and tells you what needs you: wasted reads, real failures, abuse and Database I/O pace. Rules, arithmetic, one model call per window.

Upstream source
git clone https://gitlab.com/ndivij2004/convex-tripwire.git
cd convex-tripwire

This upstream package has not been adapted or qualified for Bijection. Read the original documentation before adopting it.

Description

Reads your Convex logs and tells you what needs you: wasted reads, real failures, abuse and Database I/O pace. Rules, arithmetic, one model call per window.

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: ndivij2004/convex-tripwire. License: See repository. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

A Convex component that reads your deployment's logs and tells you what matters: wasted reads, real failures, abuse, and free-tier burn. Facts by code, judgement by one cheap model call.

Explore the demo ↗

Upstream overview

From the upstream package documentation.

Tripwire is a Convex component that ingests your deployment's log stream, rolls it up into per-function window totals, and runs a combination of deterministic rules and a single model call per window to surface findings: excessive document reads, validator contract failures, error patterns, abuse signals, and Database I/O budget pace. Findings are written to a Convex table your app can subscribe to reactively. A CLI forwarder pipes logs from outside the deployment since components cannot subscribe to their own log stream.

Use cases

From the upstream package documentation.

  • You want to catch functions scanning large numbers of documents without an index, wasting Database I/O budget, without manually querying logs
  • You need to detect when a function's return value fails its own returns validator in production before users report it
  • You want a reactive dashboard that surfaces new operational findings the moment each analysis window closes, without polling
  • You need to track whether your deployment is on pace to exceed the free plan's 2 GB Database I/O monthly budget
  • You want to distinguish real application defects from expected user errors like wrong passwords, using a model call that costs fractions of a cent per analysis window

How it works

From the upstream package documentation.

A long-running forwarder process runs npx tripwire forward outside the deployment, pipes log events to a POST /tripwire/ingest HTTP endpoint mounted by the component, and folds them into per-function per-minute totals before writing. The component accumulates those totals into rolling windows configured via tripwire.configure(ctx, { windowMinutes, ioBudgetBytes, about, questions, alertUrl }). Totals older than a week are deleted; a per-function moving average persists what they contributed.

At the end of each window a scheduled judge runs. Deterministic rules handle clear cases: a function whose return value fails its own validator is marked broken without a model call. For ambiguous cases, the component sends one request to the Jev model from TypeSafe AI, batching all questions for all active functions in the window into a single round trip. The model returns probabilities and catalog-index choices, never free text. Suggested fixes come from a fixed catalog in src/core.ts.

Findings are stored with kind (waste, broken, regressed, abuse, quota, custom), probability, urgency (0-3), and a suggested fix when the model's pick exceeds a confidence threshold. The tripwire.findings(ctx) query returns open findings reactively; tripwire.resolve(ctx, id, 'fixed' | 'noise') closes them. Noise findings are muted permanently per function. tripwire.calibration(ctx) reports per probability band how many of your closed findings were real, giving you empirical calibration data for the model's scores on your own workload.

Without a JEV_API_KEY, the component still runs the rule-based and arithmetic checks (contract violations, I/O pace) at no model cost. You can also try the same logic locally before installing anything by running npx convex-tripwire check logs.jsonl against a saved log file.

Upstream package FAQ

Does Tripwire require storing every log line in the Convex database?

No. The tripwire forward process folds raw log events into one row per function per minute before they are sent to the component. The component only accumulates those totals. Raw log lines are never written to the database, keeping the component's own read footprint small. Totals older than one week are deleted, and per-function moving averages retain what they learned.

Can Tripwire run without an LLM API key?

Yes. Without a JEV_API_KEY environment variable, Tripwire still runs all rule-based checks and arithmetic: broken return validators, runtime error counts, and Database I/O pace against your configured budget. The model is only consulted to classify ambiguous cases like waste versus intentional reads and noise versus real defects. You can try the CLI path first with npx convex-tripwire check logs.jsonl to see what rules alone produce.

How does Tripwire avoid reporting on its own functions?

Tripwire explicitly ignores its own function names when processing window rollups, so the component never raises findings about its own ingest, judge, or query functions. This prevents feedback loops where monitoring overhead gets flagged as a problem.

What happens when you close a finding as noise in Tripwire?

Calling tripwire.resolve(ctx, id, 'noise') mutes that finding permanently for that function. If you close it as 'fixed' instead, a new finding can reopen if the same pattern returns in a later window. Both verdicts are stored as labels and feed the calibration report, which shows per probability band how many judged findings turned out to be real, letting you assess whether the model's scores are meaningful on your workload.

How does the forwarder handle restarts without double-counting log events?

When tripwire forward starts, it queries the component for the furthest log position already ingested and skips any events before that point. This makes restarts safe. You can also run it with --once to catch up and exit, which suits scheduled job environments, or with --file logs.jsonl to backfill from a saved log export.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Tripwire with Bijection.

Read: https://bijection.com/components/convex-tripwire/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from ndivij2004/convex-tripwire, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

A Convex component that reads your deployment's logs and tells you what needs you: functions that read far more than their work needs, failures that are real rather than noise, traffic that looks like probing, and whether you are on pace to run out of Database I/O.

Code computes every number. One cheap model call per window makes the judgements a number cannot: is this read waste or the job, is this error a defect or a user typing the wrong password. Findings land in a table your app can subscribe to.

987 events over 5.5 h, 11 windows of 30 min
Database I/O pace: about 2.15 GB per 30 days
32 questions asked, $0.001391

[broken · today · rule · seen 1x] wall:scorecard failed 1 of 1 call
    It returned a value its own `returns` validator rejects.
    → Bring the validator and the data back in line: widen the validator, or strip the extra fields before returning.

[waste · this week · p=0.77 · seen 2x] lookup:building read 1.3 MB in 3 calls
    429.5 KB and 422.7 documents per call, returning 86.1 KB. Every read byte counts against Database I/O.
    → Define an index on the fields it filters by and query with .withIndex(), so it reads only the documents that match.

[quota · rule · seen 8x] On pace for 2.15 GB of Database I/O in 30 days, budget 2.00 GB

That is a real run against a production app. The first finding was a page that was failing in production without anyone having noticed; it turned up the first time this code ran.

Try it before installing anything

npx convex logs --prod --jsonl --success --history 5000 > logs.jsonl   # Ctrl-C once it goes quiet
npx convex-tripwire check logs.jsonl --budget-gb 2

Without a JEV_API_KEY in the environment this runs the rules and the arithmetic only (broken contracts, runtime errors, I/O pace). With one, it asks the model too. check uses the same code as the component, so what it prints is what the component would find.

How it works

npx convex logs ──► tripwire forward ──► POST <site>/tripwire/ingest
   (your logs)      one-minute totals        │
                                             ▼
                                   rollups: one row per function per window
                                             │  window closes
                                             ▼
                          ┌──────────── the judge ────────────┐
                          │ rules       broken contracts       │  no model
                          │ arithmetic  Database I/O pace      │  no model
                          │ one call    waste? defect? abuse?  │  Jev
                          │             which fix? how urgent? │
                          └────────────────┬──────────────────┘
                                           ▼
                           findings: open · resolved · muted

Totals, not events. A watcher that stored every log line would be the heaviest reader in the deployment it watches. The forwarder folds events into one row per function per minute before they arrive; the component only adds to those rows. Totals older than a week are deleted; a per-function moving average keeps what they taught.

Code decides what is worth asking. A function that read 300 bytes is never asked about waste. A function with no errors is never asked if it is broken. A function whose return value fails its own returns validator is a defect by rule, and the model is not consulted: when that case was left to judgement on real logs it came back 0.51.

One call per window. The judge model is Jev from TypeSafe AI, which answers many typed questions in parallel over one shared state: one round trip, and the state is paid for once however many questions ride on it. Every question for every function in a window goes in a single request. On the run above that was 32 questions across 11 windows for $0.0014, about 450 ms each. You bring your own key.

It picks, it does not write. The model returns probabilities and choices, never text. A suggested fix is one entry from a catalog written by a person (add an index, skip unchanged reads, bound the read, keep a summary, split large documents, call it less), chosen per function. It is shown only when the pick is confident; a finding with no suggestion is better than one with a guess.

It grades its own judge. When you close a finding you say what it was: fixed or noise. Noise is muted for good on that function. Both are labels, and calibration reports, per probability band, how many findings you judged and how many were real. Nobody can tell you in advance whether a 0.7 from the model means 70%; your own verdicts are the only ground truth there is, so Tripwire keeps score.

Install

npm install convex-tripwire
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import tripwire from "convex-tripwire/convex.config";

const app = defineApp({
  env: { JEV_API_KEY: v.optional(v.string()), TRIPWIRE_SECRET: v.optional(v.string()) },
});

app.use(tripwire, {
  httpPrefix: "/tripwire/", // serves <site>/tripwire/ingest
  env: { JEV_API_KEY: app.env.JEV_API_KEY, TRIPWIRE_SECRET: app.env.TRIPWIRE_SECRET },
});

export default app;
npx convex env set TRIPWIRE_SECRET "$(openssl rand -hex 24)"
npx convex env set JEV_API_KEY <your TypeSafe key>     # optional: without it, rules and arithmetic only

Then send it logs. The component cannot read its own deployment's logs from the inside, so a small process does it, from anywhere that can run npx convex logs for your project:

TRIPWIRE_SECRET=... npx tripwire forward --prod --url https://<your-deployment>.convex.site/tripwire

It is safe to restart: it asks the component how far it got and skips what was already counted. For a scheduled job instead of a long-running process, add --once; it catches up and exits. To backfill a new install from a saved file, --file logs.jsonl.

Use

// convex/tripwire.ts
import { v } from "convex/values";
import { Tripwire } from "convex-tripwire";
import { components } from "./_generated/api";
import { mutation, query } from "./_generated/server";

export const tripwire = new Tripwire(components.tripwire);

export const setup = mutation({
  args: {},
  handler: async (ctx) => {
    await tripwire.configure(ctx, {
      windowMinutes: 30,
      ioBudgetBytes: 2 * 1024 ** 3, // the free plan's Database I/O
      about: "A marketplace for used bikes. Crons sync listings from partner feeds hourly.",
      questions: [{ key: "checkout", text: "Checkout is failing for customers." }],
      alertUrl: "https://hooks.slack.com/services/...", // Slack, Discord, or any webhook
    });
  },
});

// Reactive: a dashboard subscribed to this updates the moment a window is judged.
export const findings = query({
  args: {},
  handler: async (ctx) => await tripwire.findings(ctx),
});

// Put your own auth in front of this.
export const close = mutation({
  args: { id: v.string(), verdict: v.union(v.literal("fixed"), v.literal("noise")) },
  handler: async (ctx, { id, verdict }) => await tripwire.resolve(ctx, id, verdict),
});

export const calibration = query({
  args: {},
  handler: async (ctx) => await tripwire.calibration(ctx),
});
configure(ctx, {...})Window size, I/O budget, a sentence about the app, your own questions, an alert webhook.
findings(ctx, { status?, limit? })open by default; also resolved and muted. Each has kind, fn, title, detail, probability, urgency (0 to 3), suggestion, seen, firstSeen, lastSeen.
resolve(ctx, id, "fixed" | "noise")fixed: a new finding opens if it comes back. noise: muted for good.
calibration(ctx)Per probability band: how many findings you judged, how many were real.
judgements(ctx)Every judged window: questions asked, cost, time, status.
report(ctx, { fn, error?, readBytes?, ms? })Count something by hand: a failure you caught, or an app you cannot run a forwarder for.

Finding kinds: waste, broken, regressed (worse than the function's own usual, once there are three windows of usual), abuse, quota, and custom for your own questions. The same kind on the same function is one finding while it is open; repeats raise seen. A finding not seen for a day closes itself.

Your own questions are plain sentences that can be true or false of a window's totals. They ride in the same call as everything else, so each one costs only its own few dozen tokens.

What it costs you

On your deployment: a few small writes per forwarded minute, and per window one read per active function plus the judge's writes. Tripwire ignores its own functions, so it never reports on itself.

For the model: the state is a compact table of at most 60 functions, and only functions whose facts justify a question get one. The 5.5 hours above cost $0.0014; a month of half-hour windows at that rate is well under a dollar.

Known limits

  • It needs a forwarder. A component cannot subscribe to its deployment's log stream, so something outside has to run tripwire forward. Convex log streams (Pro) as a push source are not supported yet.
  • The judge is only as good as your verdicts say it is. The probabilities come from a model whose calibration nobody has measured on your workload. Until you have closed enough findings for calibration to mean something, treat 0.6 to 0.7 as "worth a look", not "70% likely".
  • Suggestions are coarse. Totals say how much a function read, not which line did it. add_index is the most common pick because it is the most common cause; read the evidence on the finding before acting.
  • Text only. It reads log lines and numbers. It does not read your source.
  • Function names and clipped log lines leave your deployment in the request to the model: up to five log lines and three error lines per function per window, 200 to 300 characters each. If your logs carry personal data, do not log it, or run without JEV_API_KEY.

Keywords