Worked example: a Register streamline for an organization

Organization example, interview to dry run

Raw Markdown the file as the skill ships it All pages ↓

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):

ToolAnnotationsDecision
customer_search, item_search, staff_search, cart_get, offer_statusreadOnlyreadOnly
cart_add_item, cart_remove_itemidempotentplain, direct (the Add button on search results)
customer_create, cart_set_rep, offer_submit— (small, reversible, inside the job)plain
payment_request_cardopens a hosted pageplain (the page is the gate)
financing_applyopens a sealed formplain; form financing.apply
payment_add_cashdestructiveconfirm, card confirm.payment
sale_submitdestructiveconfirm, preview cart_get, card confirm.sale
admin_void_alldestructivenot 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

{
  "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

{
  "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

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.