> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentium.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a research harness

> A complete source-backed assistant with a real tool loop, explicit grants, bounded execution, and events.

Build an assistant that answers a delivery question from an application-owned policy catalog. This recipe uses the standalone `@agentium/harness` package and an Agent execution driver.

The application supplies evidence and a lookup tool. The harness binds them for the run, enforces the model/tool grants and budgets, records events, and cleans up. No web-search service is required.

[Download the complete project](/downloads/harness-research.zip), extract it, then run `npm install` and `npm run check`. Use `npm start` to execute the recipe with the requirements below.

## Set up the project

Use Node.js `^22.18.0` or `^24.11.0`. In an empty directory:

```bash theme={null}
npm init -y
npm pkg set type=module
npm install @agentium/core@4.0.0 @agentium/harness@4.0.0 openai zod
npm install --save-dev typescript tsx @types/node
export OPENAI_API_KEY="your-key"
```

Running this recipe contacts OpenAI. Set `OPENAI_MODEL` to a compatible model your account can use; see [model choices](/reference/example-models).

## Run the application

Save this as `harness-research.ts`:

```typescript harness-research.ts theme={null}
import { pathToFileURL } from "node:url";
import { defineTool, openai, type ModelProvider } from "@agentium/core";
import { agentDriver, HarnessRuntime, research } from "@agentium/harness";
import { z } from "zod";

export async function runResearch(model: ModelProvider) {
  const sources = {
    delivery: { text: "Standard delivery takes 3–5 business days.", uri: "policy:delivery" },
    refunds: { text: "Unopened items can be returned within 30 days.", uri: "policy:refunds" },
  };
  const lookup = defineTool({
    name: "lookup_policy",
    description: "Read the delivery or refunds policy. Cite the returned URI.",
    parameters: z.object({ topic: z.enum(["delivery", "refunds"]) }),
    execute: async ({ topic }) => JSON.stringify(sources[topic]),
  });
  const runtime = new HarnessRuntime({
    definition: research({
      id: "policy-research",
      text: {
        id: "scope",
        entries: [{ id: "catalog", text: "Available policy topics: delivery, refunds." }],
      },
      tools: [lookup],
    }),
    driver: agentDriver({
      name: "policy-researcher", model,
      instructions: "Use lookup_policy for policy facts. Cite its URI. If evidence is missing, say so.",
      maxToolRoundtrips: 3,
    }),
    grants: { toolIds: ["lookup_policy"], modelRoles: ["main"] },
    budgets: { maxModelCalls: 4, maxToolCalls: 3, maxTokens: 8000 },
  });
  // In a service, derive this identity from authenticated application state.
  const identity = { tenantId: "demo", userId: "reader" };
  const sessionId = "policy-research";
  try {
    const handle = runtime.start("How long does delivery take? Cite the policy.", { identity, sessionId });
    for await (const event of handle.events()) {
      if (event.payload.type === "tool.complete") {
        console.log("tool:", event.payload.toolName, "denied:", event.payload.denied);
      }
    }
    const result = await handle.result();
    if (result.status !== "completed") {
      throw new Error(`Research ended: ${result.status} (${result.reason?.code ?? "no reason"})`);
    }
    console.log(result.text);
    return result;
  } finally {
    const diagnostics = await runtime.resources.closeSession(identity, sessionId);
    if (diagnostics.length) throw new Error(`Session cleanup failed: ${diagnostics.join(", ")}`);
  }
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  await runResearch(openai(process.env.OPENAI_MODEL ?? "gpt-6.1-sol"));
}
```

```bash theme={null}
npx tsx harness-research.ts
```

Expect a `lookup_policy` tool event with `denied: false`, followed by an answer that standard delivery takes 3–5 business days and cites `policy:delivery`. Wording and the number of model calls can vary.

## How the pieces fit

| Piece | Why it is here |
| - | - |
| `research({ text, tools })` | Supplies source data and application tool definitions. It does not browse or choose a model. |
| `agentDriver({ model, ... })` | Runs the model/tool loop and closes its run-owned Agent when settled. |
| `grants` | Authorizes `lookup_policy` and the `main` model role. Merely supplying a tool does not grant it. |
| `budgets` | Bounds model calls, tool calls, and token accounting for this run. |
| `handle.events()` | Exposes ordered runtime events, including tool outcomes and one terminal result. |
| `closeSession()` | Releases session resources after the run has settled. It does not erase stored history. |

The source catalog is fixture data, but the Agent tool loop is real. Replace `lookup_policy.execute` with an authorized application service; preserve the schema, response bounds, and source URI. The function accepts a `ModelProvider`, so the same application can use another provider or a deterministic test provider.

## Adapt and troubleshoot

* **No evidence:** have the lookup return an explicit missing result; keep the instruction to admit missing evidence.
* **Denied tool:** compare the tool's registered name with `grants.toolIds`. Per-run grants may narrow this list, never widen it.
* **Stopped run:** inspect `result.status` and `result.reason`. Budget exhaustion is a bounded outcome, not permission to retry effects automatically.
* **Progressive text:** configure `agentDriver(config, { stream: true })` and consume `text.delta` events. This recipe observes completed tool events only.
* **Service endpoint:** derive identity from authentication and serialize concurrent runs for one session. See [session recipe](/examples/harness-sessions) and [authentication](/ship/authentication).

Next: [read selected workspace files](/examples/harness-workspace), [add policies](/harness/policies), or [compose your own ability](/harness/definitions).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.