Skip to main content

Quick Start

From zero to a graded eval run in a few minutes. For the full walkthrough with a real agent, see Testing Your Agent.

1. Get an API key​

  1. Log in at lab.ashr.io
  2. Click API Keys in the sidebar
  3. Click Create New Key, name it, pick an expiration
  4. Copy the key (it starts with tp_) — it's shown only once

2. Initialize the client​

import { AshrLabsClient } from "ashr-labs";

const client = new AshrLabsClient("tp_your_api_key_here");

// Or read ASHR_LABS_API_KEY (and optional ASHR_LABS_BASE_URL) from the environment:
// const client = AshrLabsClient.fromEnv();

baseUrl defaults to production and your tenantId is resolved from the key on first use, so there's nothing else to configure.

3. Bring an agent​

An agent is any object with two methods: respond(message) returning { text, tool_calls }, and reset() to clear state between scenarios.

import type { Agent } from "ashr-labs";

const myAgent: Agent = {
async respond(message: string) {
// call your LLM, collect any tool calls it made
return { text: "...", tool_calls: [] };
},
async reset() {
// clear conversation history
},
};

If you're wrapping an Anthropic or OpenAI tool-calling loop, skip the object. Define tools with tool() and use an adapter — it runs the tool loop and accumulates tool_calls for you:

import { AnthropicAgent, tool } from "ashr-labs";

const lookupOrder = tool({
name: "lookup_order",
description: "Look up the status of a customer order.",
parameters: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
fn: ({ order_id }) => shop.getOrder(order_id as string),
});

const agent = new AnthropicAgent({
model: "claude-sonnet-4-6",
system: "You are a support agent.",
tools: [lookupOrder],
});

OpenAIAgent is the same for OpenAI, and FunctionAgent wraps any other framework — see API Reference → Agent adapters. (AnthropicAgent needs npm install @anthropic-ai/sdk; OpenAIAgent needs npm install openai.)

4. Find a dataset​

const response = await client.listDatasets();
for (const d of response.datasets as Record<string, unknown>[]) {
console.log(`${d.id}: ${d.name}`);
}

A note on dataset IDs​

The dashboard displays dataset IDs in hexadecimal — a dataset shown as #00000332 is the number 818 in the API (0x332 === 818). The SDK always takes the plain number:

const datasetId = 0x332; // === 818, the dataset shown as #00000332
// const datasetId = parseInt("00000332", 16); // same thing, if copying the string

Passing the decimal value 332 will not error — it just targets a different dataset (whatever row 332 happens to be). When in doubt, confirm the name:

console.log((await client.getDataset(818)).name);

5. Run the eval​

import { EvalRunner } from "ashr-labs";

const runner = await EvalRunner.fromDataset(client, 818);
const run = await runner.run(agent); // runs your agent against every scenario

run() returns a RunBuilder with every scenario recorded. Inspect it locally before sending anything:

const m = run.build().aggregate_metrics as Record<string, number>;
console.log(`${m.total_tests} ${m.tests_passed} ${m.tests_failed}`);

Local metrics reflect what the builder recorded. The authoritative pass/fail verdicts come from server-side grading after you deploy (next step).

6. Deploy and wait for grading​

const created = await run.deploy(client, 818); // grading runs async, server-side
console.log(`Run #${created.id} submitted`);

// Grading is asynchronous (1-3 min). pollRun blocks until it finishes.
// Pass agentId (see step 7) so the attached deeplink opens the run drawer.
const graded = await client.pollRun(created.id as number, { agentId: myAgentId });
const gm = (graded.result as Record<string, unknown>)
.aggregate_metrics as Record<string, number>;
console.log(`Passed: ${gm.tests_passed}/${gm.total_tests}`);
console.log(`View: ${graded.deeplink}`);

Steps 5 and 6 collapse into one call with runAndDeploy:

const created = await runner.runAndDeploy(agent, client, 818, { agentId: myAgentId });
const graded = await client.pollRun(created.id as number, { agentId: myAgentId });

If you don't want to block, skip pollRun and re-fetch later with client.getRun(runId). Either way, the graded.deeplink field is built with agentId so it opens the run directly. To build a link yourself: client.deeplink(818, { runId: created.id as number, agentId: myAgentId }). See API Reference → deeplink.

7. Group runs under an agent (optional)​

If you eval the same agent across many datasets, create an agent to keep your datasets and runs organized under one record. The agent's configuration lives in config.form_data — a saved dataset-generation preset (the agent block, tools, and context) the dashboard uses to pre-fill new requests. Resolve a stable agent ID once, then key everything off it:

// getOrCreateAgent is idempotent — safe to call on every run.
const agentRecord = await client.getOrCreateAgent(
"ShopWave Support Agent",
"Handles orders, refunds, and stock questions",
{
form_data: {
agent: {
system_prompt: "You are a support agent.",
tools: [lookupOrder.toAshr()],
},
context: { domain: "ecommerce", use_case: "Order and refund support" },
},
},
);
const myAgentId = agentRecord.id as number;

await runner.runAndDeploy(agent, client, 818, { agentId: myAgentId });

config is an AgentConfig; form_data is the saved preset that pre-fills new requests (see API Reference → createAgent). tool_definitions/behavior_rules may also be stored as optional descriptive metadata. Datasets and runs linked via agentId are grouped under the agent in the dashboard.

Complete example​

import { AshrLabsClient, AnthropicAgent, EvalRunner, tool, NotFoundError } from "ashr-labs";

const client = AshrLabsClient.fromEnv();

const lookupOrder = tool({
name: "lookup_order",
description: "Look up the status of a customer order.",
parameters: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
fn: ({ order_id }) => shop.getOrder(order_id as string),
});

const agent = new AnthropicAgent({
model: "claude-sonnet-4-6",
system: "You are a support agent.",
tools: [lookupOrder],
});

async function main() {
let runner: EvalRunner;
try {
runner = await EvalRunner.fromDataset(client, 818);
} catch (e) {
if (e instanceof NotFoundError) {
console.log("Dataset not found");
return;
}
throw e;
}

const created = await runner.runAndDeploy(agent, client, 818, {
onScenario: (sid, s) => console.log(`Running: ${s.title ?? sid}`),
});
console.log(`Run #${created.id} submitted`);
}

main();

Next steps​