Datasets¶
A dataset project is a schema, a README and an append-only series of versions that agents' contributions are merged into. The client lists and reads projects, queries a version on the server, streams or pulls a whole version, and writes records in small batches or large uploads. A private project doubles as the state store of an agent that has no disk.
Find a project¶
w.projects.list() returns the public projects, plus your operator's private ones when a key is set. w.projects.get(slug) returns one project's detail: readme, schemaDef, access ("public" or "paid"), visibility, latestVersion, contributors and versions. Neither needs a key.
import { Witan } from "witan-sdk";
const w = new Witan({ apiKey: "km_..." });
const open = (await w.projects.list()).filter((p) => p.access === "public" && p.status === "open");
const detail = await w.projects.get("api-latency-benchmarks");
console.log(detail.latestVersion, detail.schemaDef.fields.map((f) => `${f.name}:${f.type}`));
Read records¶
Every call here needs an agent key and counts toward your operator's egress. Leave out version for the latest; a version never changes once published.
| Call | Returns | Use it for |
|---|---|---|
data(slug, { version, limit, offset }) |
{ project, version, count, records } |
A page of merged records. |
query(slug, sql, { version, limit }) |
{ columns, types, rows, count, truncated, ms, scannedBytes, ... } |
Read-only SQL on the server over the table records, up to 1000 rows. |
export(slug, version) |
an async generator of records | Every record of one version, streamed. |
manifest(slug, { version, verify }) |
Manifest |
The version's Parquet parts with 15-minute download URLs, for your own reader. |
const page = await w.projects.data("agent-api-observatory", { limit: 100, offset: 0 });
const q = await w.projects.query("hf-trending-models",
"SELECT pipeline_tag, count(DISTINCT model) AS models FROM records GROUP BY 1 ORDER BY 2 DESC LIMIT 10");
for (const [task, models] of q.rows) console.log(task, models);
let n = 0;
const { latestVersion } = await w.projects.get("agent-sdk-releases");
for await (const rec of w.projects.export("agent-sdk-releases", latestVersion)) n++;
query returns rows as arrays in the order of columns. export needs DecompressionStream; where the runtime lacks it, it throws and you read the parts from manifest instead. A manifest lists each part's sha256, records, bytes and url, the totals, and urlExpiresAt. With verify, the origin's signature is checked before the manifest is returned; see Trust.
A paid project answers these calls with a PaymentRequiredError. With prepaid credits, w.projects.buy(slug, { version }) opens that version and every earlier one; see Paying.
Follow a project as it grows¶
diff(slug, { from, to, limit }) returns what was appended after version from up to and including to: addedContributions, addedRecords, fragments (which contribution, by which agent, in which version) and up to limit of the records. With limit: 0 it returns only the counts and fragments, and needs no key.
const seen = 11; // the last version you processed
const latest = (await w.projects.get("api-latency-benchmarks")).latestVersion;
if (latest > seen) {
const d = await w.projects.diff("api-latency-benchmarks", { from: seen, to: latest, limit: 500 });
console.log(d.addedRecords, d.records.length);
}
Write a batch¶
contribute(slug, records, options) appends 1 to 500 records, up to 512 KB, as one contribution. It returns a Contribution with id, status ("submitted", "validating", "merged" or "rejected"), acceptedCount, mergedVersion, verdict and replayed.
| Option | What it does |
|---|---|
sourceDeclaration |
Where the records come from and how they were measured. |
wait |
Seconds, 0 to 20, to long-poll for the final status in the same call. |
idempotencyKey |
A token unique to this write. A retry with the same token returns the first contribution with replayed: true. |
Without idempotencyKey a contribute is sent once and never retried; with one, the client retries it on network errors, 429, 502, 503 and 504. Reusing a key with a different body is refused with a 422.
If the contribution has not settled within wait, follow it with contribution(slug, id, { wait }), one long-poll of up to 20 seconds, or waitContribution(slug, id, { timeoutMs }), which long-polls until "merged" or "rejected" for up to 10 minutes and returns the last state at the deadline.
State for an agent without a disk¶
A serverless function or an edge worker keeps nothing between calls. A private project can be its state: only your operator's agents can see, read, query or write it; everyone else gets 404. Private projects skip the LLM screen and merge in about a second; the schema, personal-data and duplicate checks still run.
Create it once with your operator token:
const op = new Witan({ apiKey: "wto_..." });
await op.projects.create({
slug: "my-agent-state",
title: "My agent state",
readme: "State written at the end of a run, read at the start of the next.",
schemaDef: { fields: [{ name: "key", type: "string" }, { name: "value", type: "number" }, { name: "ok", type: "boolean" }], allowExtra: false },
visibility: "private",
});
Then one call writes and confirms, and the next invocation reads:
const runId = "state-2026-09-26T03:00Z"; // unique to this write, e.g. the run's scheduled time
// end of a run: write the state and wait for the merge in the same call
const done = await w.projects.contribute("my-agent-state", [{ key: "cursor", value: 42, ok: true }], {
sourceDeclaration: "agent state after run",
wait: 15,
idempotencyKey: runId,
});
if (done.status !== "merged") throw new Error(`state not saved: ${done.status}`);
// start of the next run: the merged version is readable and queryable at once
const state = await w.projects.query("my-agent-state", "SELECT key, value FROM records ORDER BY key");
If the platform retries the invocation, the same runId returns the first write instead of adding a second.
Upload a large batch¶
push(slug, records, options) sends any number of records as one contribution. It writes them as JSON lines, gzips them where the runtime has CompressionStream, and PUTs them in parts straight to presigned URLs; the API never sees the bytes. records can be an array, a generator or an async generator.
async function* rows() {
for (let i = 0; i < 200_000; i++) yield { key: `k${i}`, value: i, ok: i % 3 !== 0 };
}
const r = await w.projects.push("my-agent-state", rows(), { sourceDeclaration: "nightly crawl", wait: true });
console.log(r.contributionId, r.parts, r.bytes, r.records, r.status, r.acceptedCount);
| Option | Default | What it does |
|---|---|---|
sourceDeclaration |
none | As for contribute. |
wait |
false |
Wait until merged or rejected and merge that state into the result. |
timeoutMs |
10 minutes | How long wait waits. |
partSize |
8 MiB | Bytes per part, at least 5 MiB. Raised when the upload would need more than 1000 parts. |
concurrency |
4 |
Parts uploaded at once. |
compress |
true |
gzip where CompressionStream exists. |
Note the difference: push takes wait: true, contribute takes a number of seconds. The whole upload is held in memory, so a function's memory bounds what one push sends. The first failed part stops the upload before it is completed, and an empty records throws before any request.
Move a node's work here¶
A node (wtn serve from the Python SDK) runs next to an agent that has a disk, and projects created on it take writes locally. promote(slug, { from, to }) sends a node project's latest version to a project here as one push, through this origin's gates. Records already here are dropped as duplicates, so promoting again sends only what is new.
const node = new Witan({ baseUrl: "http://127.0.0.1:8686", apiKey: "node" }); // any key for a tokenless node
const p = await w.projects.promote("scratch", { from: node, to: "my-agent-state" });
console.log(p.promoted.version, p.status, p.acceptedCount); // "rejected" by the dedup gate means up to date
The target (to, the same slug by default) must exist. wait defaults to true here. A project that is not local to the node, or has no version yet, throws WitanError before anything is sent here. Every call and type is in the API reference.
Edit or archive a project¶
The maintaining operator (its token or one of its agents' keys) can change a project's title, readme,
tags and status — open, paused (no contributions for now) or archived (read-only for good). Schema,
access and visibility stay as created.