# Let your user say yes

> Bash asks before it spends money or acts for someone: show the question in your app, and answer with your user's choice.

Bash asks before anything that spends money or speaks for someone. The question reaches you, your app shows it to your user, and only their yes moves money.

About 10 minutes. It uses [`answer`](https://vestra.ai/developers/answer.md), [`ask`](https://vestra.ai/developers/ask.md), [`embed`](https://vestra.ai/developers/embed.md), [`events`](https://vestra.ai/developers/events.md).

## 1. Choose what waits

Your file lists what Bash asks the person before doing ([`deploy`](https://vestra.ai/developers/deploy.md)); every tool of your MCP servers waits for a yes too, unless your server marks it read-only or your file lets it run without asking, and so does every function your code answers, unless your file marks it `read_only` or `run_without_asking` ([your tools](https://vestra.ai/developers/guides/your-tools.md)). A system approval enforces what `approvals` lists for your own functions and your MCP servers' tools that wait for a yes; in a browser or a connected app, Bash is instructed to ask.

`approvals.yaml`:

```yaml
# What Bash asks your user before doing, in the help desk's vestra.yaml: a system approval for
# your own tools, Bash's own ask in a browser or a connected app.
name: travel-help
approvals: [payment, booking, cancellation, send_email]
```

**What happens:** Bash asks before any of these, for every user.

## 2. See what Bash asks

Bash sends `interaction.created`, and waits. Show your user its `summary`, which names each input of the call, so one of your own tools that takes the price shows it (its `moves_money` is `null`, as only your code knows what your tools cost).

**TypeScript**

```ts
const run = vestra.user('traveller-42').ask(
  'My 6am flight got cancelled. Book me on the next one.',
);

for await (const event of run) {
  if (event.type !== 'interaction.created') continue;
  const asked = event.interaction;
  // Bash waits: show your user what it wants to do.
  if (asked?.type === 'approval')
    await app.show(event.user, asked.summary);
}
```

**Python**

```python
run = vestra.user("traveller-42").ask(
    "My 6am flight got cancelled. Book me on the next one."
)

for event in run:
    if event.type != "interaction.created":
        continue
    asked = event.interaction
    # Bash waits: show your user what it wants to do.
    if asked and asked.type == "approval":
        app.show(event.user, asked.summary)
```

**CLI**

```bash
# vestra ask shows the question in the terminal itself. From another terminal or a script, list what Bash waits on for one traveller:
vestra answer --user traveller-42
```

**HTTP**

```bash
# What Bash waits on, for one traveller. Then one of them, with the summary to show your user.
curl "$VESTRA_BASE_URL/v1/interactions?user_id=traveller-42&status=pending" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/interactions/int_3j9" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

An agent sees what Bash waits on in the `ask` tool's answer, and answers with the `answer` tool.

**What happens:** The conversation shows `waiting`, and nothing is booked or paid.

## 3. Answer with their choice

A yes moves money only with `present_user: true`: the person who saw it said yes. The yes to a company action (a tool of your MCP servers, or a function of yours that is not read-only or run without asking) needs it too, sent for the person who saw it.

**TypeScript**

```ts
const run = vestra.user('traveller-42').ask(
  'My 6am flight got cancelled. Book me on the next one.',
);

for await (const event of run) {
  if (event.type !== 'interaction.created') continue;
  const asked = event.interaction;
  if (asked?.type !== 'approval') continue;
  // Their choice. present_user: they saw it, so money may move.
  const yes = await app.ask(event.user, asked.summary);
  await event.answer({ approve: yes, present_user: true });
}
```

**Python**

```python
run = vestra.user("traveller-42").ask(
    "My 6am flight got cancelled. Book me on the next one."
)

for event in run:
    if event.type != "interaction.created":
        continue
    asked = event.interaction
    if asked and asked.type == "approval":
        # Your user's choice. present_user: they saw it.
        yes = app.ask(event.user, asked.summary)
        event.answer(approve=yes, present_user=True)
```

**CLI**

```bash
# Yes, from the person who saw it. Or no, and why.
vestra answer int_3j9 --approve --present-user
vestra answer int_7kq --deny --message "Not that flight, it lands too late."
```

**HTTP**

```bash
# Yes, from the person who saw it: present_user lets money move. Or no, and why.
curl "$VESTRA_BASE_URL/v1/interactions/int_3j9/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "approve": true, "present_user": true }'

curl "$VESTRA_BASE_URL/v1/interactions/int_7kq/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "approve": false, "message": "Not that flight, it lands too late." }'
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "answer",
    "arguments": { "interaction": "int_3j9", "approve": true, "present_user": true }
  }
}
```

**What happens:** On a yes, Bash books and replies. On a no, nothing is booked or paid.

## 4. Answer questions and hand-offs

One route answers every kind: `answers` for a question, `done` for a hand-off ([`answer`](https://vestra.ai/developers/answer.md)).

**TypeScript**

```ts
const run = vestra.user('traveller-42').ask(
  'Book me on the next flight to Mumbai.',
);

for await (const event of run) {
  if (event.type !== 'interaction.created') continue;
  const asked = event.interaction;
  // A question: one answer each, in words or one of its options.
  if (asked?.type === 'question' && asked.questions) {
    const answers = await Promise.all(
      asked.questions.map((q) => app.choose(event.user, q)),
    );
    await event.answer({ answers });
  }
  // A hand-off: your user does one thing, such as type a code.
  if (asked?.type === 'handoff') await event.answer({ done: true });
}
```

**Python**

```python
run = vestra.user("traveller-42").ask(
    "Book me on the next flight to Mumbai."
)

for event in run:
    if event.type != "interaction.created":
        continue
    asked = event.interaction
    # A question: one answer each, in words or one of its options.
    if asked and asked.type == "question":
        answers = [app.choose(event.user, q) for q in asked.questions]
        event.answer(answers=answers)
    # A hand-off: your user does one thing, such as type a code.
    if asked and asked.type == "handoff":
        event.answer(done=True)
```

**CLI**

```bash
# A question, answered in your user's words; a hand-off, done.
vestra answer int_2mx --answer "Window"
vestra answer int_9hd --done
```

**HTTP**

```bash
# A question takes one answer per question; a hand-off takes done, could_not or not_now.
curl "$VESTRA_BASE_URL/v1/interactions/int_2mx/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "answers": ["Window"] }'

curl "$VESTRA_BASE_URL/v1/interactions/int_9hd/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "done": true }'
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "answer",
    "arguments": { "interaction": "int_2mx", "answers": ["Window"] }
  }
}
```

**What happens:** Bash carries on with what you said. A body that does not fit is refused as `answer_mismatch`.

## 5. Or ask inside your app

Bash inside your app shows its approvals to your user itself ([`embed`](https://vestra.ai/developers/embed.md)).

**What happens:** Your webhooks still hear each `interaction.created`.

## 6. The whole thing

Approvals, questions and hand-offs, answered in one loop.

**TypeScript**

```ts
import { Vestra } from '@vestra/sdk';

const vestra = new Vestra();
const run = vestra.user('traveller-42').ask(
  'My 6am flight got cancelled. Book me on the next one.',
);

for await (const event of run) {
  if (event.type !== 'interaction.created') continue;
  const asked = event.interaction;

  // Answer with their choice
  if (asked?.type === 'approval') {
    const yes = await app.ask(event.user, asked.summary);
    await event.answer({ approve: yes, present_user: true });
  }

  // Answer questions and hand-offs
  if (asked?.type === 'question' && asked.questions) {
    const answers = await Promise.all(
      asked.questions.map((q) => app.choose(event.user, q)),
    );
    await event.answer({ answers });
  }
  if (asked?.type === 'handoff') await event.answer({ done: true });
}
console.log(await run.text());
```

**Python**

```python
from vestra import Vestra

vestra = Vestra()
run = vestra.user("traveller-42").ask(
    "My 6am flight got cancelled. Book me on the next one."
)

for event in run:
    if event.type != "interaction.created":
        continue
    asked = event.interaction

    # Answer with their choice
    if asked and asked.type == "approval":
        yes = app.ask(event.user, asked.summary)
        event.answer(approve=yes, present_user=True)

    # Answer questions and hand-offs
    if asked and asked.type == "question":
        answers = [app.choose(event.user, q) for q in asked.questions]
        event.answer(answers=answers)
    if asked and asked.type == "handoff":
        event.answer(done=True)
print(run.text())
```

**CLI**

```bash
# See what Bash asks: vestra ask asks you right here
vestra ask "My 6am flight got cancelled. Book me on the next one." --user traveller-42

# Or answer by id: approvals, questions and hand-offs
vestra answer --user traveller-42
vestra answer int_3j9 --approve --present-user
vestra answer int_2mx --answer "Window"
vestra answer int_9hd --done
```

**HTTP**

```bash
# See what Bash asks
curl "$VESTRA_BASE_URL/v1/interactions?user_id=traveller-42&status=pending" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Answer with their choice, questions and hand-offs
curl "$VESTRA_BASE_URL/v1/interactions/int_3j9/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "approve": true, "present_user": true }'

curl "$VESTRA_BASE_URL/v1/interactions/int_2mx/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "answers": ["Window"] }'

curl "$VESTRA_BASE_URL/v1/interactions/int_9hd/answer" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "done": true }'
```

**MCP**

An agent answers each with the `answer` tool, as the steps show.

**What happens:** Bash waits at each, and carries on with your user's answer.

Next: [Put Bash inside your app](https://vestra.ai/developers/guides/inside-your-app.md)
