# Search a synthetic page

Run an exact catalog release with the vendor's real SDK in a local World.
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`. For a different vendor or
release, [use the twin you selected](./use-a-catalog-twin.md) rather than copying this example's
Tavily dependencies and fixtures.

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.158",
    "@volter/twin-tavily": "1.0.1",
    "@volter/world-core": "3.0.148",
    "@volter/world-runtime": "3.0.156",
    "@volter/world-console": "3.0.149"
  }
}
```

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

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

```bash
npm install
./node_modules/.bin/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
./node_modules/.bin/volter world up
./node_modules/.bin/volter world clock set 2026-01-15T12:00:00Z
./node_modules/.bin/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
./node_modules/.bin/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
./node_modules/.bin/volter world log
./node_modules/.bin/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
./node_modules/.bin/volter world reset
./node_modules/.bin/volter world clock set 2026-01-15T12:00:00Z
./node_modules/.bin/volter world run -- node seed-page.mjs
./node_modules/.bin/volter world run -- node search.mjs
./node_modules/.bin/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. -->
