# Worked example: a Register streamline for an organization

Meridian Jewelers is Streamliner's fictional demo account. This walks one streamline from interview to dry run.
All names, hosts and numbers are invented.

## Contents
- 1. Interview notes
- 2. What the server offers
- 3. tenant.json changes
- 4. agents/register.json
- 5. private/agents/register.json
- 6. Templates
- 7. Check, sign, dry run

## 1. Interview notes

- Department: the stores. Job: **ring up one sale** (customer → pieces → price → payment → sales rep → submit).
- Who: sales associates, store managers, regional managers (roles `associate`, `store_manager`, `regional`).
- System: the POS MCP server `https://api.meridian.example/mcp`, OAuth, one connection `pos`.
- Rules (in the POS API, not in config): offers below 90% of tag go to a manager; a sale can't be submitted with a
  balance. Card payments go to the processor's page; financing through the credit partner's sealed form.
- Done when: `sale_submit` succeeds. Overview: the ticket (`cart_get`).

## 2. What the server offers

`inspect-mcp.mjs https://api.meridian.example/mcp --draft org` (abridged):

| Tool | Annotations | Decision |
|---|---|---|
| `customer_search`, `item_search`, `staff_search`, `cart_get`, `offer_status` | readOnly | `readOnly` |
| `cart_add_item`, `cart_remove_item` | idempotent | plain, `direct` (the *Add* button on search results) |
| `customer_create`, `cart_set_rep`, `offer_submit` | — (small, reversible, inside the job) | plain |
| `payment_request_card` | opens a hosted page | plain (the page is the gate) |
| `financing_apply` | opens a sealed form | plain; form `financing.apply` |
| `payment_add_cash` | destructive | `confirm`, card `confirm.payment` |
| `sale_submit` | destructive | `confirm`, preview `cart_get`, card `confirm.sale` |
| `admin_void_all` | destructive | **not allowlisted** (not this job) |

## 3. `tenant.json` changes

Add `"register"` to `kinds`, the `pos` connection to `connections`, and `img.meridian.example` to `imageHosts`.

## 4. `agents/register.json`

```json
{
  "id": "register",
  "name": "Register",
  "description": "Ring up a sale: customer, pieces, price, payment, submit.",
  "icon": "bag.fill",
  "color": { "light": "#1F7A5C", "dark": "#3FBF8F" },
  "roles": ["associate", "store_manager", "regional"],
  "newChatLabel": "New sale",
  "chatTitle": { "rules": [
    { "tool": "cart_set_customer", "text": "{{ $.customer.id }} · {{ $.customer.name }}" },
    { "tool": "offer_submit", "text": "Offer {{ $.offer.number }} · {{ $.offer.amount | money }}" }
  ] },
  "welcome": {
    "messages": ["Hi {{ user.firstName }}. This chat is one sale.",
                 "Tell me who the customer is and what they're looking at. Nothing is submitted until you tap Submit."],
    "starters": [ { "label": "Find a customer", "text": "Find customer ", "send": false },
                  { "label": "Scan a tag", "text": "Add item ", "send": false } ]
  },
  "composer": { "placeholder": "Customer, item, price, payment…", "quickPhrases": [
    { "group": "Price", "items": [ { "label": "Customer offers…", "text": "The customer offers {{amount}} for {{item}}" } ] },
    { "group": "Finish", "items": [ { "label": "Submit the sale", "text": "Submit the sale", "send": true } ] } ] },
  "overview": { "title": "Ticket", "source": { "tool": "cart_get", "args": { "cartId": "{{ chat.cartId }}" } },
                "requires": ["chat.cartId"], "template": "register.overview", "compactTemplate": "register.overview.compact",
                "refreshOn": ["cart_*", "customer_*", "offer_*", "payment_*", "sale_*"],
                "poll": { "seconds": 15, "when": { "path": "$.offer.status", "eq": "pending" } }, "empty": "Nothing on the ticket yet." },
  "renderers": { "customer_search": "customer.list", "item_search": "item.list", "offer_submit": "offer.decision", "sale_submit": "sale.receipt" },
  "activity": { "item_search": "Searching inventory", "sale_submit": "Submitting the sale" },
  "completion": { "tools": ["sale_submit"], "banner": "Sale {{ $.saleNumber }} submitted", "next": "New sale" },
  "forms": ["card.hosted", "financing.apply"]
}
```

## 5. `private/agents/register.json`

```json
{
  "id": "register",
  "instructions": "You are the register at a Meridian Jewelers store. You help one sales associate complete one sale in this chat.\n\nWork in this order unless the associate leads elsewhere: customer, items, price, payment, sales rep, submit.\n- Confirm the customer by name and the last four digits of their phone before adding anything.\n- When the customer offers a price below the tag, call offer_submit and report the decision exactly as returned. Never state cost or margin.\n- Never ask for, repeat or accept card numbers, Social Security numbers, dates of birth or license numbers in chat. Use payment_request_card or financing_apply.\n- sale_submit and payment_add_cash wait for the associate's tap.\n- Keep replies to one or two short sentences; the ticket shows the details.\n- Today is {{ now | date:'long' }}. The associate is {{ user.name }} at {{ user.store }}.",
  "model": { "provider": "anthropic", "id": "claude-haiku-5-5", "effort": "low", "maxTokens": 2048, "credentialRef": "anthropic-main" },
  "fallbackModel": { "provider": "anthropic", "id": "claude-sonnet-5-5", "effort": "low", "maxTokens": 2048, "credentialRef": "anthropic-main", "when": ["overloaded", "refusal"] },
  "mcp": [ { "id": "pos", "url": "https://api.meridian.example/mcp", "auth": "oauth", "connection": "pos", "resource": "https://api.meridian.example/mcp",
    "tools": [
      { "name": "customer_search", "readOnly": true },
      { "name": "customer_create" },
      { "name": "item_search", "readOnly": true },
      { "name": "cart_get", "readOnly": true },
      { "name": "cart_add_item", "direct": true },
      { "name": "cart_remove_item", "direct": true },
      { "name": "cart_set_rep" },
      { "name": "staff_search", "readOnly": true },
      { "name": "offer_submit" },
      { "name": "offer_status", "readOnly": true },
      { "name": "payment_add_cash", "confirm": { "template": "confirm.payment", "button": "Record cash" } },
      { "name": "payment_request_card" },
      { "name": "financing_apply" },
      { "name": "sale_submit", "confirm": { "template": "confirm.sale", "preview": "cart_get", "button": "Submit sale" } }
    ] } ],
  "limits": { "maxToolCallsPerTurn": 10, "maxTurnsPerChat": 120, "tokensPerUserPerDay": 600000 },
  "transcript": { "keepToolResults": 6 }
}
```

The key is named (`anthropic-main`); the owner stores the actual key in the account, never in this file.

## 6. Templates

`templates/item.list.json`, `templates/confirm.sale.json` and the overview follow the patterns in templates.md.
Bind each against a real result from the server: `item_search` returns
`{ "results": [{ "sku", "title", "photo", "tagPrice", "onHand", "metal" }] }`; `cart_get` returns
`{ cartId, customer, items[], offer, subtotal, tax, total, payments[], paid, balance, rep, status, saleNumber }`.

## 7. Check, sign, dry run

```bash
node scripts/check.mjs ./meridian
node scripts/sign-config.mjs ./meridian --publisher-key ~/keys/meridian.publisher.jwk
node scripts/publish.mjs ./meridian --host https://<back end> --key ~/keys/meridian.server-key.jwk --baseline ./meridian-live
```

```
DRY RUN — nothing was written: meridian on https://<back end>, version 13 → 14 (key claude-setup-oct)
  + agents/register.json   "Register" for roles associate, store_manager, regional
  + private/agents/register.json   model claude-haiku-5-5; tools: customer_search (read), …, sale_submit (confirm)
  + templates/confirm.sale.json
  ~ tenant.json
      ~ /kinds/2
  4 added, 1 changed, 0 deleted; the manifest goes last
```

Show that to the owner, say what each line means in plain words, and publish (`--yes`) only on their explicit yes.
