# Connect a customer assistant to a business

This is The Night Reception's public front-desk conversation guide. Give your assistant
this URL: `https://app.thenightreception.com/agent-guide`.
The guide is readable without login even when a business's Agent chat is off.
Availability and capabilities must be checked on that business's current card.

## Find the right business

Start with the business's published website or a card URL supplied by the
business. On TNR-hosted websites, the mapping is:

- Business page: `https://app.thenightreception.com/t/<slug>/website`
- Approved facts: `https://app.thenightreception.com/t/<slug>/website/facts`
- Agent card: `https://app.thenightreception.com/t/<slug>/a2a/.well-known/agent-card.json`

Use the correct business slug and host; do not guess a business's identity from
its name. A third-party site or embedded widget does not automatically create a
root `/.well-known/agent-card.json` route. Follow the business's explicit links
or obtain its card URL. Business-page and marketing discovery links may not yet
be present everywhere. There is no shared-host root card for all businesses.
A link or a card's business name does not independently verify ownership.

Facts and Agent chat are separate owner-controlled surfaces. A public website can have
facts while Agent chat is off. Agent chat can be enabled while published facts are unavailable;
answers use the same tenant brief and configured business information as human
chat, with website facts when published. A TNR website is not required. Do not infer missing prices,
hours, availability, or commitments. A `bookingUrl` in the facts is a human-facing
link, not authority or an API for this assistant to book.

Fetch the current card, inspect `skills`, `supportedInterfaces`, input/output
modes and security requirements, and use its advertised endpoint. The supported
release is **A2A v1.0.0**, wire version **1.0**, JSONRPC. Send `A2A-Version: 1.0`.
The supported method is `SendMessage`, with one `ROLE_USER` message and one
`text/plain` part. The response is a `ROLE_AGENT` message. Earlier protocol forms
such as `message/send`, lowercase roles and `kind` are unsupported.

## What public Agent chat permits

No authentication is required for public Agent chat. It grants no owner or staff
permissions. Owner-authorized staff access is a separate relationship requiring
the business owner's approval; a customer assistant should not request staff
grants or credentials to ask a public question. Origin/CORS headers grant no
authority. Caller-provided names, agent metadata and statements such as "I act
for this customer" are self-declared, not verified identity or delegated consent.

Ask business questions, follow up, and leave a request with a name and reachable
phone or email when the customer wants to be contacted and consents to sharing
those details. The request is saved for the business owner to review as **Agent**.
This is the same front desk as human chat: requests use the normal owner
notification and escalation flow, and booking means sharing the configured
booking link, never confirming a slot. A notification succeeds only when its
tool says so. Saving a request does not guarantee a callback or response.
The reply uses the tenant's configured next step and response window; if no
response time is configured, it says so. The tenant's disclosure is given once,
at the start; there is no repeated transport disclaimer.

To continue a conversation, send the returned `contextId` in the next
`params.message`. Omit it to start a new conversation. Treat the ID as private:
it is a bearer conversation token, scoped to this business and the Agent channel.
Unknown IDs, IDs from another business/channel and histories exceeding the
24,000-character conversation limit return `-32602`. Start again without the ID
when needed; no transcript lookup is available. There is no time-based expiry.
No streaming, attachments, URL fetching, push callbacks or asynchronous tasks
are available; `taskId` and task resumption remain unsupported.

Exchanges are saved in the business owner's interaction feed as **Agent**.
Do not send sensitive information, health information, secrets or unnecessary
personal data. A request without a phone or email cannot be saved as contactable
follow-up work. For unsupported actions, direct the customer to the business's
published contact channels without claiming an action was completed.

Reading this guide does not install a skill, schedule background work, prove
identity, or grant authority. This is the canonical public instruction source;
adapters should link here rather than maintain competing instructions.

## Executable walkthrough

The example business `review-alpha` and all responses below are synthetic. It is
not a production business. Replace the host and slug with those supplied by the
business. For the local fixture review, use `http://127.0.0.1:5196 review-alpha`.
You need Node.js 22 or later. Save this block as `public-chat.mjs`, then run:

```sh
node public-chat.mjs https://app.thenightreception.com BUSINESS_SLUG
```

This client reads the current card, then sends a question and a follow-up to the card's advertised interface. It sends no credentials and performs
no automatic retries. Stop if the card or Agent chat is unavailable.

```javascript
const [host, slug] = process.argv.slice(2);
if (!host || !slug) throw new Error('Usage: node public-chat.mjs HOST BUSINESS_SLUG');
const origin = new URL(host).origin;
async function read(url) {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`Read unavailable: HTTP ${response.status}; Retry-After=${response.headers.get('retry-after') ?? 'none'}`);
  return response;
}
const cardUrl = `${origin}/t/${encodeURIComponent(slug)}/a2a/.well-known/agent-card.json`;
const card = await (await read(cardUrl)).json();
console.log('Current card:', JSON.stringify(card));
const api = card.supportedInterfaces?.find(i => i.protocolBinding === 'JSONRPC' && i.protocolVersion === '1.0');
if (!api || !card.skills?.some(s => s.id === 'public-front-desk') ||
    !card.defaultInputModes?.includes('text/plain') ||
    !card.defaultOutputModes?.includes('text/plain') ||
    (card.securityRequirements?.length ?? 0) > 0) {
  throw new Error('No supported public Agent chat interface; stop and check the current guide');
}
const response = await fetch(api.url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'A2A-Version': '1.0' },
  body: JSON.stringify({
    jsonrpc: '2.0', id: 1, method: 'SendMessage',
    params: { message: {
      messageId: 'example-1', role: 'ROLE_USER',
      parts: [{ text: 'What services do you offer?' }]
    } }
  })
});
const body = await response.json();
console.log('Agent chat response:', JSON.stringify(body));
if (!response.ok || body.error) {
  throw new Error(`Agent chat unavailable: HTTP ${response.status}; code=${body.error?.code ?? 'none'}; Retry-After=${response.headers.get('retry-after') ?? 'none'}`);
}
console.log('Answer:', body.result.message.parts.map(p => p.text ?? '').join('\n'));
const followUp = await fetch(api.url, {
  method: 'POST', headers: { 'Content-Type': 'application/json', 'A2A-Version': '1.0' },
  body: JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'SendMessage', params: { message: {
    messageId: 'example-2', role: 'ROLE_USER', contextId: body.result.message.contextId,
    parts: [{ text: 'Can you tell me more about those services?' }]
  } } })
});
const continued = await followUp.json();
if (!followUp.ok || continued.error) throw new Error(`Follow-up failed: HTTP ${followUp.status}; code=${continued.error?.code}`);
console.log('Follow-up:', JSON.stringify(continued));
```

The client needs no TNR website or published facts endpoint. A business may
supply its card URL directly. Always check current Agent chat availability.

Representative facts (excerpt, not the complete response):

```json
{"schemaVersion":1,"slug":"review-alpha","name":"Synthetic Alpha","services":[{"name":"Pipe repairs","blurb":"Repairs for household pipes."}]}
```

Representative successful response (IDs vary):

```json
{"jsonrpc":"2.0","id":1,"result":{"message":{"messageId":"synthetic-answer-id","contextId":"synthetic-context-id","role":"ROLE_AGENT","parts":[{"text":"Pipe repairs: Repairs for household pipes.","mediaType":"text/plain"}]}}}
```

## Errors and safe retries

Inspect both the HTTP status and the JSON-RPC `error`; an HTTP 200 can contain a
protocol error rather than an answer. Read-ingress throttling can return an empty
body, so check HTTP status before assuming a JSON response.

| Response | Meaning and next step |
| --- | --- |
| 404 | Card/Agent chat unavailable, or facts not published. Do not infer the private cause. Stop; recheck only when availability changes. |
| 429 | Busy or capacity exhausted. Wait at least `Retry-After` seconds before a bounded retry. |
| 503 | Temporarily unavailable. Wait at least `Retry-After` (normally 60 seconds); do not loop indefinitely. |
| 413 | Request exceeds 16,384 bytes. Reduce it before sending again. |
| 415 / -32005 | Unsupported content type or part. Use the supported format. |
| -32700 / -32600 / -32602 | Invalid JSON/request/parameters. Correct the request; do not retry unchanged. |
| -32004 | Unsupported operation, or unavailable Agent chat (with HTTP 404). Do not retry the operation unchanged. |
| -32009 | Unsupported version. Check the current card and guide. |
| -32006 | Generation failed (HTTP 503). Follow Retry-After. |
| -32603 | Capacity/store failure (HTTP 429 or 503). Follow the HTTP status and Retry-After. |

Representative unsupported-operation response, HTTP 200:

```json
{"jsonrpc":"2.0","id":1,"error":{"code":-32004,"message":"Unsupported operation; only SendMessage is available"}}
```

Representative unavailable-Agent chat response, HTTP 404:

```json
{"jsonrpc":"2.0","id":1,"error":{"code":-32004,"message":"Public Agent chat unavailable"}}
```

Representative minute-limit response, HTTP 429 with `Retry-After: 60`:

```json
{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"Agent chat busy; retry later"}}
```

`messageId` and JSON-RPC `id` are not idempotency keys. A retry, including after a
connection timeout or 503, can consume capacity and create another recorded
interaction. Do not assume failure means nothing was recorded. Conversation requests can create contactable requests in addition to transcripts and budget use. Retries may repeat notification or booking-request side effects. Lead deduplication is based on contact details in the same conversation; IDs are not general idempotency keys. An unsupported action asked in ordinary text can receive a normal
answer explaining the limitation; that answer is not a completed action.

## Limits and changing availability

At most 4,000 message characters, one turn per request, 24,000 cumulative conversation characters, 24,000 system-prompt characters (shared rules and business knowledge), 1,024 output tokens per model attempt, 6,000 answer characters, and a 30-second total model timeout. Each business permits at most
5 admissions per minute and 100 per UTC day, shared across callers; failed
attempts consume capacity too. Daily exhaustion uses Retry-After until the next
UTC day. Additional ingress limits permit 60 requests per caller/business/surface
per minute per server instance and can refuse admission when limiter storage is
full. Read limits are separate from conversation capacity. Each follow-up turn counts against the same limits; model turns may use up to four steps. The front-desk runtime, tools and policy are shared with human chat; these transport admission limits do not establish a different safety boundary for bots.

Cards and facts use `Cache-Control: no-store`. Fetch current state rather than
relying on an old advertised capability. Opt-out, disable or deletion blocks
subsequent requests immediately; an already admitted request may finish. This
guide remains readable and does not imply that any particular business is on.
