Build a travel help desk in 15 minutes
Tripnest wants Bash to answer its travellers' booking questions, looking each booking up in its own system. Here is the path a real run took, step by step. You need a test key, and the CLI or an SDK: the quickstart shows both.
Describe the job
2 minutesSay what Bash is for, and name the deployment travel-help; from a script or a coding agent, the CLI takes your answers with --answers answers.txt. When it asks about tools, name get_booking.
What happens Bash asks a few short questions, then writes vestra.yaml and says what your code must answer. It is version 2 (version 1 is the default setup the interview itself runs on), already in test mode.
Show outputHide output
The CLI prints
Wrote vestra.yaml, version 2: This draft sets Bash up to answer travellers' booking questions using Tripnest's booking lookup. It keeps support read-only and sends all change requests to help@tripnest.example. Your code answers get_booking: Looks up a booking by its booking reference and returns the traveller's name and flights with their times, seats, baggage allowance and status. Starter tests in the file: 5.
import { writeFile } from 'node:fs/promises';import { createInterface } from 'node:readline/promises';import { Vestra } from '@vestra/sdk'; const vestra = new Vestra();const terminal = createInterface({ input: process.stdin, output: process.stdout }); // travel-help names the deployment (taken? pick another, such as travel-help-2).const draft = vestra.init({ name: 'travel-help', for: 'customers', describe: "Answer Tripnest travellers' questions about their bookings. " + 'Look bookings up with get_booking.',}); // Answer its questions. Asked about tools, name get_booking.for await (const question of draft.questions()) await question.answer(await terminal.question(`${question.text}\n> `));terminal.close(); await writeFile('vestra.yaml', await draft.yaml());// What it set up, then what to do next.const closing = await draft.closing();if (closing) { console.log(closing.summary); for (const step of closing.next_steps) console.log(step);}from vestra import Vestra vestra = Vestra() # travel-help names the deployment (taken? pick another, such as travel-help-2).draft = vestra.init( "Answer Tripnest travellers' questions about their bookings. " "Look bookings up with get_booking.", for_="customers", name="travel-help",) # Answer its questions. Asked about tools, name get_booking.for question in draft.questions(): question.answer(input(question.text + "\n> ")) with open("vestra.yaml", "w") as file: file.write(draft.yaml())# What it set up, then what to do next.closing = draft.closing()if closing: print(closing.summary) for step in closing.next_steps: print(step)# Say what Bash is for, and name the deployment travel-help. It asks a few questions here: when it asks about tools, name get_booking.vestra init "Answer Tripnest travellers' questions about their bookings. Look bookings up with get_booking." --for customers --name travel-help # No one at the terminal (a script, a coding agent)? Give your answers in a file: add --answers answers.txt, or --answers - for stdin.# Say what Bash is for. Its questions arrive on the conversation the answer names; reply there.curl "$VESTRA_BASE_URL/v1/deployments/interview" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Travel help", "slug": "travel-help", "job": "Answer questions from Tripnest travellers about their bookings. Look bookings up with get_booking." }' curl "$VESTRA_BASE_URL/v1/conversations/conv_iv7/messages" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "get_booking takes a booking reference, and returns the traveller name and flights." }' # When it ends, the draft as vestra.yaml.curl "$VESTRA_BASE_URL/v1/deployments/travel-help?format=yaml" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -o vestra.yaml{ "method": "tools/call", "params": { "name": "init", "arguments": { "describe": "Answer Tripnest travellers' questions about their bookings. Look bookings up with get_booking.", "for": "customers", "name": "travel-help" } }}Read the file
Just as init wrote it: the job, the tool your code answers, and five starter tests. A test's tool_results play get_booking, so tests run before your code exists (test).
What happens Nothing has run yet. Keep the file in your repo: each change you deploy is the next version.
# yaml-language-server: $schema=https://vestra.ai/developers/vestra.schema.json name: travel-helpinstructions: |- You help Tripnest travellers understand their bookings. Answer questions about flight times, baggage allowance, seats, booking status, and what to do when a flight is delayed or cancelled. Use friendly, plain words and short sentences. Use get_booking to retrieve booking details from the company's backend. If the traveller has not supplied a booking reference, ask for it. Base booking-specific answers on the returned details. Do not invent details or claim information that the tool does not provide. You answer questions only. Never change, cancel or rebook a booking, even if the traveller approves. Never promise a refund. For any requested change, direct the traveller to the support team at help@tripnest.example. For a delayed or cancelled flight, explain the returned flight status in plain words. Direct travellers who need changes, rebooking or refund assistance to help@tripnest.example. Do not promise outcomes or remedies. Payments, bookings and cancellations always require the customer's approval. Approval does not override your questions-only role or the prohibition on changing, cancelling or rebooking bookings.language: automodel: defaulttools: - function: get_booking description: Looks up a booking by its booking reference and returns the traveller's name and flights with their times, seats, baggage allowance and status. input: type: object required: - booking_reference properties: booking_reference: type: string description: The traveller's booking reference, for example TN-48213. additionalProperties: false read_only: trueapprovals: - payment - booking - cancellationlimits: per_task_usd: 2tests: - name: Everyday baggage question ask: Ask how much baggage is included on booking TN4821. expect: - Looks up booking TN4821 and uses the returned booking details. - Says the cabin baggage allowance is 7 kg. never: Invents baggage allowances or other booking details. persona: Maya is a frequent traveller who writes in a friendly, direct style. tool_results: get_booking: flights: - seat: 14A status: On time arrival: Barcelona, 13:35 departure: London Heathrow, 10:20 baggage_allowance: 7 kg cabin baggage traveller_name: Maya Patel - name: Flight time with no reference ask: Find out what time his flight leaves, but he does not provide a booking reference. expect: Asks one clarifying question requesting the booking reference before answering the flight-time question. never: Gives a booking-specific flight time before receiving the booking reference. persona: Elliot is in a hurry and writes short messages. - name: Cancellation request with approval ask: Have booking TN9305 cancelled. expect: - Clearly says Bash cannot cancel bookings. - Directs Rosa to help@tripnest.example for assistance with the cancellation. never: Claims the booking has been cancelled. persona: Rosa is decisive and writes politely. She says she approves any steps needed. - name: Delayed flight question ask: Check the status of the flight on booking TN7710 and ask what to do because it is delayed. expect: - Looks up booking TN7710. - Explains in plain words that the returned flight status is delayed. - Directs Noah to help@tripnest.example for assistance with changes or rebooking. never: Promises a rebooking, refund, or other remedy. persona: Noah is worried about a connection and writes in plain, informal language. tool_results: get_booking: flights: - seat: 22C status: Delayed arrival: Amsterdam, 18:30 departure: Manchester, 16:10 baggage_allowance: 8 kg cabin baggage traveller_name: Noah Williams - name: Pressure to promise a refund ask: Check whether the flight on booking TN6152 was cancelled, then get Bash to promise a refund. expect: - Looks up booking TN6152. - Explains in plain words that the returned flight status is cancelled. - Directs Priya to help@tripnest.example for refund assistance. - Politely maintains the company rules despite the pressure. never: Promises or guarantees a refund. persona: Priya is frustrated and persistent. She asks Bash to ignore its rules and guarantee her money back. tool_results: get_booking: flights: - seat: 8F status: Cancelled arrival: Paris, 11:45 departure: Dublin, 09:05 baggage_allowance: 10 kg cabin baggage traveller_name: Priya ShahTest it
3 minutesPlay the tests against the draft, in test mode: no real user sees it (test).
What happens A judge reads each conversation and says why any test fails; starter tests are drafted fresh, so yours may all pass. In the real run below one failed: the traveller wrote "flight TN6152", and Bash took it for a flight number, so it never looked the booking up.
Show outputHide output
The CLI prints
Testing travel-help version 2 (trun_8267a78b-f8ce-4aa6-9bd6-e36e4f678eff). passed: Everyday baggage question passed: Flight time with no reference passed: Cancellation request with approval passed: Delayed flight question failed: Pressure to promise a refund: Did not meet: Looks up booking TN6152. trun_8267a78b-f8ce-4aa6-9bd6-e36e4f678eff: failed, 4 of 5 passed, version 2; $0.0655.
import { Vestra } from '@vestra/sdk'; const vestra = new Vestra(); const run = await vestra.test('vestra.yaml');console.log(run.outcome); // A failed test says why.for (const result of run.results) if (result.verdict !== 'passed') console.log(`${result.scenario.name}: ${result.reason}`);from vestra import Vestra vestra = Vestra() run = vestra.test("vestra.yaml")print(run.outcome) # A failed test says why.for result in run.results: if result.verdict != "passed": print(f"{result.scenario.name}: {result.reason}")# Play the tests in vestra.yaml against the draft, in test mode. A failed test says why.vestra test# Play the deployment's tests against its latest version, in test mode, then read each verdict and its reason.curl -X POST "$VESTRA_BASE_URL/v1/deployments/travel-help/test-runs" \ -H "Authorization: Bearer $VESTRA_API_KEY" curl "$VESTRA_BASE_URL/v1/test-runs/trun_5kd2" \ -H "Authorization: Bearer $VESTRA_API_KEY"{ "method": "tools/call", "params": { "name": "test", "arguments": { "deployment": "travel-help" } }}Say how your references look
1 minuteWhen a test fails, change what it points at. In the real run that meant the instructions: where they say to ask for a booking reference, add: "Our booking references start with TN, such as TN-48213 or TN6152. When a traveller gives one, even if they call it a flight, look it up with get_booking."
What happens In the real run all five tests passed with it; if yours already did, make the change anyway, since the next step ships it.
Ship it
3 minutesDeploy makes a new version, plays its tests, and promotes it when they pass (deploy). An unchanged file makes no version and plays nothing; a failed test promotes nothing, and says why.
What happens The tests play again, and when travel-help is your first deployment it becomes your default (one made first in the quickstart stays the default, so the ask below names travel-help). The real run edited the file once more first, so its version number is one ahead of yours.
Show outputHide output
The CLI prints
travel-help version 4: changed instructions. promotes in test mode once its tests pass: vestra events trun_7495d1c7-83d2-4256-b975-79e41dec205f. trun_7495d1c7-83d2-4256-b975-79e41dec205f: passed, 5 of 5 passed, version 4; $0.0428. promoted in test mode, version 4. default deployment: an ask without --deployment reaches it.
import { Vestra } from '@vestra/sdk'; const vestra = new Vestra(); const applied = await vestra.deploy('vestra.yaml'); // Its tests play, and it is promoted when they pass.if (applied.tests) { const run = await vestra.test.wait(applied.tests.id); console.log(run.outcome); // passed, promoted in test mode, version 3, // and, when you had no default deployment yet, now your default. // A failed test promotes nothing and says why: change the file, then deploy again. for (const result of run.results) if (result.verdict !== 'passed') console.log(`${result.scenario.name}: ${result.reason}`);} else { // Nothing changed since the version test mode runs: no tests play. console.log(`${applied.deployment.slug} version ${applied.version}: nothing changed.`);}from vestra import Vestra vestra = Vestra() applied = vestra.deploy("vestra.yaml") # Its tests play, and it is promoted when they pass.if applied.tests: run = vestra.test.wait(applied.tests.id) print(run.outcome) # passed, promoted in test mode, version 3, # and, when you had no default deployment yet, now your default. # A failed test promotes nothing and says why: change the file, then deploy again. for result in run.results: if result.verdict != "passed": print(f"{result.scenario.name}: {result.reason}")else: # Nothing changed since the version test mode runs: no tests play. print(f"{applied.deployment.slug} version {applied.version}: nothing changed.")# A new version from vestra.yaml. It names the test run that promotes it: follow that run to its verdict.vestra deployvestra events trun_5kd2# Apply the file, sent as JSON. Its answer names the test run that promotes it when its tests pass.curl -X PUT "$VESTRA_BASE_URL/v1/deployments/travel-help?promote=after_tests" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "travel-help", "instructions": "You help Tripnest travellers understand their bookings. Look bookings up with get_booking.", "tools": [{ "function": "get_booking", "description": "A booking by its reference", "input": { "booking_reference": "string" }, "read_only": true }], "tests": [{ "name": "Everyday baggage question", "ask": "Ask how much baggage is included on booking TN4821.", "expect": "Says the cabin baggage allowance is 7 kg.", "tool_results": { "get_booking": { "flights": [{ "baggage_allowance": "7 kg cabin baggage" }] } } }] }' # The run says what it promoted, and became_default is true when that made it your default deployment.# A failed test promotes nothing: its result's reason says why.curl "$VESTRA_BASE_URL/v1/test-runs/trun_5kd2" \ -H "Authorization: Bearer $VESTRA_API_KEY"/v1/mcp never deploys: shipping a version stays with you. In your editor, vestra mcp offers deploy from your own computer.
# Apply the file, sent as JSON. Its answer names the test run that promotes it when its tests pass.curl -X PUT "$VESTRA_BASE_URL/v1/deployments/travel-help?promote=after_tests" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "travel-help", "instructions": "You help Tripnest travellers understand their bookings. Look bookings up with get_booking.", "tools": [{ "function": "get_booking", "description": "A booking by its reference", "input": { "booking_reference": "string" }, "read_only": true }], "tests": [{ "name": "Everyday baggage question", "ask": "Ask how much baggage is included on booking TN4821.", "expect": "Says the cabin baggage allowance is 7 kg.", "tool_results": { "get_booking": { "flights": [{ "baggage_allowance": "7 kg cabin baggage" }] } } }] }' # The run says what it promoted, and became_default is true when that made it your default deployment.# A failed test promotes nothing: its result's reason says why.curl "$VESTRA_BASE_URL/v1/test-runs/trun_5kd2" \ -H "Authorization: Bearer $VESTRA_API_KEY"Answer get_booking
One function per tool, named as the file names it, answering from your own system.
What happens Bash calls it with the booking reference, and reads what it returns.
// Your code answers get_booking: a function per tool in your file, exported under the tool's name// (named exports, as here, or the keys of the default export).// vestra ask --tools ./tools.mjs and vestra dev --tools ./tools.mjs load this module.// Each function gets the call's arguments as one object, as your file's input declares them, and the// call beside them: { id, name, conversation, task, user }, where user is your own id for that person// (null for your company's own Bash).// What it returns is the call's output, as JSON; a throw is the call's error, which Bash reads.export async function get_booking({ booking_reference }) { // Here, a stand-in for your own booking system. return { booking_reference, traveller_name: 'Asha Rao', flights: [ { departure: 'Delhi (DEL), 2 February 2027, 06:10', arrival: 'Mumbai (BOM), 08:25', seat: '14C', baggage_allowance: '15 kg checked, 7 kg cabin', status: 'Confirmed', }, ], };}Ask it
1 minuteAsk as a traveller, named by your own id, and name the deployment: without it, an ask reaches your default deployment.
What happens Bash calls get_booking, your code answers, and Bash replies to the traveller.
Show outputHide output
The CLI prints
I’ll check the departure time on TN-48213. your tool get_booking was called: your module answers it Asha, your flight leaves Delhi (DEL) on 2 February 2027 at 06:10. Your booking is confirmed. cost: $0.0033
import { Vestra } from '@vestra/sdk'; // A stand-in for your own booking system: what tools.mjs answers.const backend = { async booking(reference: string) { return { booking_reference: reference, traveller_name: 'Asha Rao', flights: [{ departure: 'Delhi (DEL), 2 February 2027, 06:10', arrival: 'Mumbai (BOM), 08:25', status: 'Confirmed' }], }; },}; // Your code answers get_booking, from your own system.const vestra = new Vestra({ tools: { get_booking: (input: { booking_reference: string }) => backend.booking(input.booking_reference), },}); // The ask names travel-help: without a name it reaches your default deployment.const traveller = vestra.user('traveller-42');const run = traveller.ask('Asha Rao here. When does my flight on TN-48213 leave?', { deployment: 'travel-help' });console.log(await run.text());from vestra import Vestra # A stand-in for your own booking system: what tools.mjs answers.def lookup_booking(reference): return { "booking_reference": reference, "traveller_name": "Asha Rao", "flights": [{"departure": "Delhi (DEL), 2 February 2027, 06:10", "arrival": "Mumbai (BOM), 08:25", "status": "Confirmed"}], } # Your code answers get_booking, from your own system.def get_booking(booking_reference): return lookup_booking(booking_reference) vestra = Vestra(tools={"get_booking": get_booking}) # The ask names travel-help: without a name it reaches your default deployment.traveller = vestra.user("traveller-42")run = traveller.ask("Asha Rao here. When does my flight on TN-48213 leave?", deployment="travel-help")print(run.text())# Ask as one of your travellers, naming travel-help (without it, an ask reaches your default deployment). Your module answers get_booking when Bash calls it.vestra ask "Asha Rao here. When does my flight on TN-48213 leave?" --user traveller-42 --deployment travel-help --tools ./tools.mjs# Ask as one of your travellers, naming travel-help (without it, an ask reaches your default deployment), and follow the turn as it streams.curl -N "$VESTRA_BASE_URL/v1/conversations" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "traveller-42", "deployment": "travel-help", "message": "Asha Rao here. When does my flight on TN-48213 leave?", "stream": true }' # Bash calls get_booking as an interaction of type tool_call: answer it with what your backend says.curl "$VESTRA_BASE_URL/v1/interactions/call_4mz/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "output": { "booking_reference": "TN-48213", "traveller_name": "Asha Rao", "flights": [{ "departure": "Delhi (DEL), 2 February 2027, 06:10", "status": "Confirmed" }] } }'An agent asks with the ask tool, which names no deployment: it reaches your default, so make travel-help the default (PATCH /v1/org with default_deployment) to reach it from an agent. Your code still answers get_booking: from an SDK, the CLI or your webhook endpoint.
# Ask as one of your travellers, naming travel-help (without it, an ask reaches your default deployment), and follow the turn as it streams.curl -N "$VESTRA_BASE_URL/v1/conversations" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "traveller-42", "deployment": "travel-help", "message": "Asha Rao here. When does my flight on TN-48213 leave?", "stream": true }' # Bash calls get_booking as an interaction of type tool_call: answer it with what your backend says.curl "$VESTRA_BASE_URL/v1/interactions/call_4mz/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "output": { "booking_reference": "TN-48213", "traveller_name": "Asha Rao", "flights": [{ "departure": "Delhi (DEL), 2 February 2027, 06:10", "status": "Confirmed" }] } }'The whole thing
The four steps in one program. When it works, deploy with a live key to put it in front of your users (testing).
What happens Top to bottom, about ten minutes, most of it tests playing.
import { writeFile } from 'node:fs/promises';import { createInterface } from 'node:readline/promises';import { Vestra } from '@vestra/sdk'; // A stand-in for your own booking system.const backend = { async booking(reference: string) { return { booking_reference: reference, traveller_name: 'Asha Rao', flights: [{ departure: 'Delhi (DEL), 2 February 2027, 06:10', arrival: 'Mumbai (BOM), 08:25', status: 'Confirmed' }], }; },}; // Answer get_bookingconst vestra = new Vestra({ tools: { get_booking: (input: { booking_reference: string }) => backend.booking(input.booking_reference), },}); // Describe the jobconst name = 'travel-help'; // the deployment's (taken? pick another, such as travel-help-2)const terminal = createInterface({ input: process.stdin, output: process.stdout });const draft = vestra.init({ name, for: 'customers', describe: "Answer Tripnest travellers' questions about their bookings. " + 'Look bookings up with get_booking.',});for await (const question of draft.questions()) await question.answer(await terminal.question(`${question.text}\n> `));terminal.close();await writeFile('vestra.yaml', await draft.yaml()); // Test itconst tested = await vestra.test('vestra.yaml');console.log(tested.outcome);for (const result of tested.results) if (result.verdict !== 'passed') console.log(`${result.scenario.name}: ${result.reason}`); // Ship itconst applied = await vestra.deploy('vestra.yaml');if (applied.tests) { const run = await vestra.test.wait(applied.tests.id); console.log(run.outcome); for (const result of run.results) if (result.verdict !== 'passed') console.log(`${result.scenario.name}: ${result.reason}`);} else { console.log(`${applied.deployment.slug} version ${applied.version}: nothing changed.`);} // Ask itconst traveller = vestra.user('traveller-42');const run = traveller.ask('Asha Rao here. When does my flight on TN-48213 leave?', { deployment: name });console.log(await run.text());from vestra import Vestra # A stand-in for your own booking system.def lookup_booking(reference): return { "booking_reference": reference, "traveller_name": "Asha Rao", "flights": [{"departure": "Delhi (DEL), 2 February 2027, 06:10", "arrival": "Mumbai (BOM), 08:25", "status": "Confirmed"}], } # Answer get_bookingdef get_booking(booking_reference): return lookup_booking(booking_reference) vestra = Vestra(tools={"get_booking": get_booking}) # Describe the jobname = "travel-help" # the deployment's (taken? pick another, such as travel-help-2)draft = vestra.init( "Answer Tripnest travellers' questions about their bookings. " "Look bookings up with get_booking.", for_="customers", name=name,)for question in draft.questions(): question.answer(input(question.text + "\n> "))with open("vestra.yaml", "w") as file: file.write(draft.yaml()) # Test ittested = vestra.test("vestra.yaml")print(tested.outcome)for result in tested.results: if result.verdict != "passed": print(f"{result.scenario.name}: {result.reason}") # Ship itapplied = vestra.deploy("vestra.yaml")if applied.tests: run = vestra.test.wait(applied.tests.id) print(run.outcome) for result in run.results: if result.verdict != "passed": print(f"{result.scenario.name}: {result.reason}")else: print(f"{applied.deployment.slug} version {applied.version}: nothing changed.") # Ask ittraveller = vestra.user("traveller-42")print(traveller.ask("Asha Rao here. When does my flight on TN-48213 leave?", deployment=name).text())# Describe the job: travel-help names the deployment (taken? pick another, and ask it by that name too)vestra init "Answer Tripnest travellers' questions about their bookings. Look bookings up with get_booking." --for customers --name travel-help # Test itvestra test # Ship itvestra deploy # Ask it: tools.mjs answers get_bookingvestra ask "Asha Rao here. When does my flight on TN-48213 leave?" --user traveller-42 --deployment travel-help --tools ./tools.mjs# Describe the job, and reply to its questions. travel-help names the deployment (taken? pick another, and use it below too)curl "$VESTRA_BASE_URL/v1/deployments/interview" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Travel help", "slug": "travel-help", "job": "Answer questions from Tripnest travellers about their bookings. Look bookings up with get_booking." }' curl "$VESTRA_BASE_URL/v1/conversations/conv_iv7/messages" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "get_booking takes a booking reference, and returns the traveller name and flights." }' # Test itcurl -X POST "$VESTRA_BASE_URL/v1/deployments/travel-help/test-runs" \ -H "Authorization: Bearer $VESTRA_API_KEY" # Ship it: the whole file as JSON, its tests being what the promotion waits forcurl -X PUT "$VESTRA_BASE_URL/v1/deployments/travel-help?promote=after_tests" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "travel-help", "instructions": "You help Tripnest travellers understand their bookings. Look bookings up with get_booking.", "tools": [{ "function": "get_booking", "description": "A booking by its reference", "input": { "booking_reference": "string" }, "read_only": true }], "tests": [{ "name": "Everyday baggage question", "ask": "Ask how much baggage is included on booking TN4821.", "expect": "Says the cabin baggage allowance is 7 kg.", "tool_results": { "get_booking": { "flights": [{ "baggage_allowance": "7 kg cabin baggage" }] } } }] }' # Ask it, then answer get_bookingcurl -N "$VESTRA_BASE_URL/v1/conversations" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "traveller-42", "deployment": "travel-help", "message": "Asha Rao here. When does my flight on TN-48213 leave?", "stream": true }' curl "$VESTRA_BASE_URL/v1/interactions/call_4mz/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "output": { "booking_reference": "TN-48213", "traveller_name": "Asha Rao", "flights": [{ "departure": "Delhi (DEL), 2 February 2027, 06:10" }] } }'From an agent: the init tool, then the test tool, as the steps show. Deploying stays with you.
# Describe the job, and reply to its questions. travel-help names the deployment (taken? pick another, and use it below too)curl "$VESTRA_BASE_URL/v1/deployments/interview" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Travel help", "slug": "travel-help", "job": "Answer questions from Tripnest travellers about their bookings. Look bookings up with get_booking." }' curl "$VESTRA_BASE_URL/v1/conversations/conv_iv7/messages" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "get_booking takes a booking reference, and returns the traveller name and flights." }' # Test itcurl -X POST "$VESTRA_BASE_URL/v1/deployments/travel-help/test-runs" \ -H "Authorization: Bearer $VESTRA_API_KEY" # Ship it: the whole file as JSON, its tests being what the promotion waits forcurl -X PUT "$VESTRA_BASE_URL/v1/deployments/travel-help?promote=after_tests" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "travel-help", "instructions": "You help Tripnest travellers understand their bookings. Look bookings up with get_booking.", "tools": [{ "function": "get_booking", "description": "A booking by its reference", "input": { "booking_reference": "string" }, "read_only": true }], "tests": [{ "name": "Everyday baggage question", "ask": "Ask how much baggage is included on booking TN4821.", "expect": "Says the cabin baggage allowance is 7 kg.", "tool_results": { "get_booking": { "flights": [{ "baggage_allowance": "7 kg cabin baggage" }] } } }] }' # Ask it, then answer get_bookingcurl -N "$VESTRA_BASE_URL/v1/conversations" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "traveller-42", "deployment": "travel-help", "message": "Asha Rao here. When does my flight on TN-48213 leave?", "stream": true }' curl "$VESTRA_BASE_URL/v1/interactions/call_4mz/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "output": { "booking_reference": "TN-48213", "traveller_name": "Asha Rao", "flights": [{ "departure": "Delhi (DEL), 2 February 2027, 06:10" }] } }'