Capture
Every call you already make to a frontier model is a labeled example you paid for. Capture tags each call by task, at the source, on your own machine, so each task becomes a clean dataset you can train a small specialist from. It's local-first: nothing is sent anywhere until you choose to train.
Local by default
Capture writes to a store you control, a JSONL file per task on your box by default (./gerbil-data/<task>.jsonl), or your own database via a sink. Nothing leaves your environment. The only step that sends anything is tune(), and only the compiled set for that one task, when you choose to train it.
That is the whole privacy story: collect freely, tag as you go, and the data compounds locally. When you want a model you send just the curated set; teams that can't send even that can run training on their own infrastructure so it never leaves. Prefer Gerbil to hold the datasets for you? The same capture() can post to a hosted bucket instead (the HTTP endpoint below), a prosumer convenience, not the default.
What happens to your data
- It stays on your machine, and only what you tag. Nothing is collected automatically. A pair is recorded only on an explicit
capture()/tap()call at a call-site you chose, and it's written to your local store, it doesn't leave unless you opt into the hosted bucket. - Redact on-device before it leaves. With
redact: true, Capture runs a local PII pass first; the pair that departs is the redacted one, so raw user data never crosses the wire. - Scoped to you, and yours to keep. Captured pairs are stored task-tagged and row-scoped to your account (row-level security), no other tenant can read them. They're your dataset: train from a bucket, export it, or delete it.
- Provenance travels with it. Each pair keeps its task tag, source, and the model that produced it, so a dataset always knows where it came from and the model you train ships with that on its receipt.
Capture, step by step
- 1 · Install the SDK & set your key. Grab an API key from the dashboard; the SDK reads
GERBIL_API_KEYfrom the environment. - 2 · Tag each call-site. Drop
gerbil.capture()(or wrap the call withgerbil.tap()) wherever you already hit a model, giving each site atasktag. The tag is the dataset it lands in. - 3 · Buckets fill, then train. Each tag accumulates into its own dataset. When one has enough, train a specialist from it (or the whole set) on the Tune surface.
npm i @tryhamster/gerbilexport GERBIL_API_KEY=your_key # from the dashboardWant to keep raw content on your side entirely? See Label your logs , collect and label locally with a model you trained, nothing sent.
Capture, tagged by task
Drop this wherever you already call a model. The tag you give it is the dataset it lands in, so your title generator and your triage classifier never end up in the same pile.
import { capture, tap, configureCapture } from "@tryhamster/gerbil/tune";
// Local-first: rows land in ./gerbil-data/<task>.jsonl by default — nothing// is sent. Point it at your own store instead if you like.configureCapture({ dir: "./gerbil-data" });// configureCapture({ sink: (row) => db.insert(row) });
// Tag any call-site. The pair lands in the "title-gen" dataset,// a set you can train from later.await capture("title-gen", { input: draft, output: title, model: "gpt-5", // provenance});
// Or wrap an existing call so it tags automatically and// passes the result through untouched:const title = await tap( { task: "title-gen", input: draft }, () => openai.chat.completions.create({ /* ... */ }),);Hosted, over HTTP (opt-in)
Prefer Gerbil to hold the datasets instead of keeping them local? Point capture at the hosted bucket with configureCapture({ apiKey }), or post to the plain authenticated endpoint from anything. It accepts a single pair or a batch, and takes prompt/completion as aliases for input/output:
curl -X POST https://gerbilsdk.com/api/tune/capture \ -H "Authorization: Bearer $GERBIL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task":"title-gen","input":"draft ...","output":"5 titles ..."}'
# Batch, with a default task for items that omit their own:# {"task":"title-gen","examples":[{"input":"...","output":"..."}, ...]}It returns what landed, per tag, and silently drops empties and duplicates:
{ "received": 50, "accepted": 47, "rejected": 3, "byTask": { "title-gen": 47 } }Best practices
- Tag by task, not by firehose. One tag per distinct job. Dumping your whole stream into one bucket only trains a model that generalizes over everything you do; tagging is what makes a specialist possible.
- Keep it local unless you have a reason not to. The default store never leaves your machine, that's the safest posture and needs no account. Only opt into the hosted bucket if you actually want Gerbil to hold the datasets for you.
- Redact on the device first. Capture runs an optional PII pass locally before anything leaves; the pair that departs is the redacted one. Turn it on for user data.
- Accumulate, then train what costs the most. No target count up front. Buckets rank by volume and spend; train the one bleeding the most money first.
From a bucket to a model
Every captured pair carries its task, source, and the model that produced it, so the dataset knows exactly where it came from. As buckets fill they rank by how much that task is costing you, and training a specialist from the top bucket is one step, the model ships with that provenance on its receipt. See Tune for training, and the Tune surface to run the whole flow in your browser.