# Use a catalog twin

Take an exact catalog release into a local World and run the vendor's real SDK against it.
This example uses Tavily search and extraction. Search returns synthetic stored pages;
generated answer text is a labeled stub. It does not search the public web or evaluate answer quality.

Use Node 22.6 or newer and npm. No Tavily account, real API key or platform login is needed. Installation
needs registry access. Start in an empty directory and save the files below.

## Install the selected release

[Choose a twin](./choose-a-twin.md) explains publishers, measurements and defaults. This example
selects `@volter/twin-tavily@1.0.1` from catalog snapshot `0.2.3`.

Save `package.json`:

```json file=package.json
{
  "name": "search-app",
  "private": true,
  "type": "module",
  "dependencies": { "@tavily/core": "0.7.13" },
  "devDependencies": {
    "@volter/world": "3.0.63",
    "@volter/twin-tavily": "1.0.1"
  }
}
```

Save `.env.example` with an empty value:

```text file=.env.example
TAVILY_API_KEY=
```

```bash
npm install
npx volter world init --name search-app
```

```text
tavily
COVERED:
```

Review the detected vendor. Inspect `.volter/world.json`: its service should name the selected
package and version. Commit the config and lockfile. The catalog supplies discovery; the World
config records which implementation this app runs.

## Supply a synthetic page

Tavily searches public web content in production. This local example supplies its page content.
The twin's `/_twin/pages` fixture API is explicitly twin-specific, not a Tavily production API.
It creates stored data; a response handler must not pretend to create it.

Save `seed-page.mjs`:

```js file=seed-page.mjs
import assert from 'node:assert/strict';

const response = await fetch(`${process.env.TAVILY_TWIN_URL}/_twin/pages`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    url: 'https://schedule.example/yoga',
    title: 'Yoga schedule',
    content: 'Yoga starts Tuesday at 18:00.'
  })
});
assert.equal(response.status, 201);
console.log('synthetic page created');
```

```bash
npx volter world up
npx volter world clock set 2026-01-15T12:00:00Z
npx volter world run -- node seed-page.mjs
```

```text
synthetic page created
```

The fixture is stored locally. The `.example` URL names the synthetic record; nothing fetches
a real page at that address.

## Run the real SDK

Save `search.mjs`. It uses the SDK's normal destination and the World's throwaway credential:

```js file=search.mjs
import assert from 'node:assert/strict';
import { tavily } from '@tavily/core';

const client = tavily({ apiKey: process.env.TAVILY_API_KEY });
const found = await client.search('yoga schedule', {
  maxResults: 3, includeAnswer: true, includeRawContent: 'text'
});
assert.equal(found.results[0].url, 'https://schedule.example/yoga');
assert.match(found.results[0].rawContent, /Tuesday at 18:00/);
assert.match(found.answer, /\[twin-stub\]/);
const extracted = await client.extract([
  'https://schedule.example/yoga', 'https://missing.example/page'
]);
assert.equal(extracted.results[0].url, 'https://schedule.example/yoga');
assert.match(extracted.results[0].rawContent, /Tuesday at 18:00/);
assert.equal(extracted.failedResults[0].url, 'https://missing.example/page');
console.log('search, extraction and missing-page checks passed');
```

```bash
npx volter world run -- node search.mjs
```

```text
search, extraction and missing-page checks passed
```

These assertions check finding the fixture, reading its content and handling an absent page.
They do not establish real search ranking, generated answer quality or support for every operation.

## Inspect, repeat and stop

```bash
npx volter world log
npx volter world status
```

The log shows recorded state effects, rather than a transcript of every read. For the dashboard,
see [inspect a World](./inspect-a-world.md).

Reset removes the run's state. Recreate the page and freeze the same clock before repeating:

```bash
npx volter world reset
npx volter world clock set 2026-01-15T12:00:00Z
npx volter world run -- node seed-page.mjs
npx volter world run -- node search.mjs
npx volter world down
```

```text
search, extraction and missing-page checks passed
Stopped search-app
```

[Seed and reset](./seed-and-reset.md) puts starting data in a project-owned seed.
[Shape the World for a test](./shape-the-world-for-a-test.md) scripts stateless answers and faults.
[Update a twin](./update-a-twin.md) covers changing the selected release deliberately.

<!-- Fenced tutorial executor: packages/cli/src/journeys/tutorial.ts. Execution scope is recorded separately. -->
