---
title: "Step 3: Query sample data"
description: "Add a run_sql tool over the bundled sample dataset and watch the tool loop."
---

Right now the agent can hold a conversation but it can't see a single row of data. So give it a tool. A tool is the action primitive: typed input goes in, your code runs, structured output comes back. The name the model sees is just the filename, so `agent/tools/run_sql.ts` becomes the tool `run_sql`.

## A tiny sample dataset

To make the first query work without any setup, bundle a small in-memory dataset under `agent/lib/`. Keep it tiny. This is throwaway scaffolding, not the real warehouse (that comes in Step 4).

```ts title="agent/lib/sample-db.ts"
// A toy SQLite-in-memory stand-in. Swap for your real warehouse in Step 4.
import initSqlJs from "sql.js";

const SEED = `
  CREATE TABLE orders (id INTEGER, customer_id INTEGER, amount_cents INTEGER, created_at TEXT);
  INSERT INTO orders VALUES
    (1, 10, 4200, '2026-05-01'), (2, 10, 1500, '2026-05-03'),
    (3, 11, 9900, '2026-05-04'), (4, 12,  800, '2026-05-06');
  CREATE TABLE customers (id INTEGER, name TEXT, plan TEXT);
  INSERT INTO customers VALUES
    (10, 'Acme', 'pro'), (11, 'Globex', 'enterprise'), (12, 'Initech', 'free');
`;

let dbPromise: Promise<import("sql.js").Database> | null = null;

async function db() {
  dbPromise ??= initSqlJs().then((SQL) => {
    const database = new SQL.Database();
    database.run(SEED);
    return database;
  });
  return dbPromise;
}

export async function runReadOnlySql(sql: string) {
  const database = await db();
  const [result] = database.exec(sql);
  if (!result) return { columns: [], rows: [] as unknown[][] };
  return { columns: result.columns, rows: result.values };
}
```

## The tool

```ts title="agent/tools/run_sql.ts"
import { defineTool } from "eve/tools";
import { z } from "zod";
import { runReadOnlySql } from "../lib/sample-db.js";

export default defineTool({
  description:
    "Run a read-only SQL query against the analytics tables (orders, customers) " +
    "and return the columns and rows.",
  inputSchema: z.object({
    sql: z.string().describe("A single read-only SELECT statement."),
  }),
  async execute({ sql }) {
    const { columns, rows } = await runReadOnlySql(sql);
    // Bound the output so a wide query can't flood the model's context.
    return { columns, rows: rows.slice(0, 500), truncated: rows.length > 500 };
  },
});
```

Tools run in your app runtime with full `process.env`, not in the sandbox. The `inputSchema` both validates the call and types the `input` you get inside `execute`. For output bounding, `toModelOutput`, and authorization, see [Tools](../tools).

## Watch the tool loop

Restart `eve dev` and ask:

```text
Which customer has spent the most, and how much?
```

Watch the loop play out in the TUI. The model emits a `run_sql` call, Eve runs your `execute`, and the rows come back as a tool result. The model reads them and answers with a real number. Eve drove the whole loop. All you supplied was the tool.

→ Next: [Step 4: Connect a warehouse](./connect-a-warehouse)

Depth: [Tools](../tools)
