Ask Porter: a partner-scoped Vernal co-pilot on Kitchen POS

Ask Porter: a partner-scoped Vernal co-pilot on Kitchen POS

Channel partners got a portal, a storefront, and recurring pickup windows in June. The next question affiliates ask is not philosophical — it is operational: "How do I open my window?" "What is my customer link?" "Did anyone order yet?"

Those questions are not crimes. They are the sound of a program working. They are also the kind of questions that do not scale if every answer routes through Mark's phone.

Ask Porter is our answer on Kitchen POS: a Vernal co-pilot embedded in the partner portal as an orange chat bubble — scoped to one affiliate at a time, with tools that can read their menu, windows, and orders, and explicitly cannot touch operator god-mode APIs. Phase 1b is live on dev (dev.kitchen-pos.decisionsciencecorp.com) as of June 2026. Production partner Porter is not enabled until we complete a launch slice; this post describes what we built and why, not a general availability announcement.

Why partner scope is the whole design

Tasks has Ask Q — operator-facing Vernal on Sanctum Tasks admin. Kitchen POS has Rico and operator kitchen_pos__* SMCP tools for menu mutation, secret codes, and provisioning. Partners sit between those worlds: they sell Empanada Empire pickup under their slug, but they must never receive keys that can rewrite the global menu or peek at another affiliate's orders.

Porter's job rules state the boundary plainly: one partner at a time, whoever is logged in when the bubble opens. Porter gets four partner tools — me, menu, windows, orders — and is locked out of operator routes. Commit 295f359 formalized that lock after early prototypes proved how tempting it is to give a helpful bot "just one more" admin capability.

That is not stinginess. It is how you ship a co-pilot on a live ghost kitchen without turning every affiliate chat into a lateral movement exercise.

Partner dashboard with Ask Porter bubble (orange chat control, bottom right). Seeded affiliate portal on Kitchen POS.

What the affiliate sees

After partner login on the Sanctum-styled portal (/partner/*), a floating Ask Porter bubble appears when channel_partner_enabled is on. The widget is not a generic ChatGPT tab — it is wired to the same session cookie as the portal, bootstrapped via partner_context on the partner-bridge API, and polled for replies the same way we hardened Ask Q for all-day use.

The affiliate types plain language. Porter answers with portal context: open window steps, customer URL hints, recent order summaries, menu items available at their prices. If they ask for operator-only work — change global inventory, provision a new partner, edit nginx — Porter says that is outside Ask Porter and names the right lane (operator or Mark).

First message from a new partner account gets a short orientation (Layer A in job rules). Subsequent messages carry page context (Layer B): which portal surface they are on, open window id if any, partner slug and display name. The model is not guessing who they are; the bridge injects identity before the turn ships to moya.

A Tuesday night at the church group

Picture an affiliate pastor who agreed to run a Saturday pickup window for Empanada Empire. Tuesday at 9:14 p.m. someone in the group chat asks whether the link is the same as last month and whether they need to open the window again. That message is not malice — it is a person trying to serve food on time.

Before Porter, that question becomes a text to Mark, a context switch, and a manual lookup in the partner admin. After Porter (on dev today), the affiliate opens the portal on their phone, taps the orange bubble, and asks in plain language. Porter answers with their slug, their next window state, and the customer URL pattern — because the bridge resolved identity from the same session cookie that gates /partner/orders.php.

We are not claiming Porter replaces relationship management. We are claiming it removes the boring lookups that erode goodwill when the human operator is asleep.

Ask Q taught us; Porter inherits the homework

Ask Q hardening was operator-facing on Tasks: poll loops, 429 backoff, session keys, Broca inbox/outbox, SQLite queue beside the app DB. Porter reuses that shape on Kitchen POS:

Layer Ask Q (Tasks) Ask Porter (Kitchen POS)
Widget Q bubble on admin Porter bubble on /partner/*
Bridge q-bridge PHP API partner-bridge PHP API
Broca screen q-vernal path broca-porter on moya
Letta agent Q Vernal Porter_Vernal
SMCP profile Tasks tools Four kitchen_pos_partner__* tools
Identity Operator session Partner session → kppb_… key

The difference is not plumbing — it is ACL. Q may attach provisioning and IAM tools because the logged-in human is staff. Porter must never inherit that surface. Commit 295f359 locked Porter to partner tools only after we caught how easy it is to "temporarily" attach kitchen_pos__menu for demos.

Same partner dashboard on a phone-width viewport — bubble stays reachable without hunting the menu.

Session identity end to end

Partners authenticate like any Kitchen POS affiliate user. When the bubble loads, _ask_porter.php injects JSON config: bridge base URL, partner slug as chatterUsername, page context from partner_bridge_detect_partner_page_context(), and a greeting that sets expectations ("window, menu, partner orders").

When the affiliate sends a message:

  1. Widget POSTs to partner-bridge/.../messages with session auth.
  2. Row lands in partner_bridge_webchat.db inbox.
  3. Broca on moya polls inbox with PARTNER_BRIDGE_POLL_API_KEY.
  4. Broca writes current_partner_user_id.txt and calls resolve_partner_key so SMCP never sees raw secrets.
  5. Porter_Vernal may call kitchen_pos_partner__me, __menu, __windows, __orders.
  6. Assistant text returns via outbox → widget responses poll.

If step 4 fails — wrong user id, revoked partner, feature flag off — Porter should say so plainly. Silent failure is how affiliates decide the program is flaky.

What affiliates can ask (and what they cannot)

In scope for Phase 1b:

  • "What is my customer link?" → slug-based storefront URL on empanadaempire.us.
  • "When is my window?" → read windows API for their partner id.
  • "Any orders yet?" → summarized list from partner orders endpoint.
  • "What's on my menu?" → partner-scoped menu read.
  • "How do I open a window?" → procedural guidance pointing at portal forms (read-first; mutations still via UI).

Out of scope — by design:

  • Change global menu pricing for all locations.
  • Provision a new partner or reset another affiliate's password.
  • Operator Square sync controls, secret codes, or admin-only reports.
  • Tasks, CRM, or unrelated Sanctum products.

Job rules (PORTER-VERNAL-JOB-RULES.md) encode this in Letta blocks: Layer A orientation on first turn, Layer B page context thereafter, explicit handoff language when someone asks for operator work.

Dev-only is a feature flag, not a footnote

Phase 1b runs on dev.kitchen-pos.decisionsciencecorp.com with moya broca-porter pointed at the dev bridge URL. Production partner portal does not expose Porter until we complete a launch slice: prod feature flag, rotated poll bearer, Letta tool attach verified on the prod agent id, rate limits tuned for partner poll cadence, Playwright smoke on a test affiliate.

We say this loudly because affiliates talk to each other. "I saw a chat bubble on dev" must not become "DSC said Porter is live everywhere."

Failure modes we already rehearsed

  • 429 on widget poll — same lesson as Ask Q: visible retry/backoff, no infinite spinner.
  • Broca screen down — inbox grows; widget shows stall; ops restarts broca-porter on moya.
  • Tool calls with wrong partner scope — treated as sev-1; SMCP resolver is the control, not prompt politeness.
  • Model asks for operator credentials — job rules forbid it; bridge never supplies operator keys to the partner profile.

Playwright on dev (tools/playwright_ask_porter_dev_e2e.py) checks bubble visibility, sends a probe string, asserts a non-empty bot reply. Manual smoke uses affiliate alvin per runbook when we need eyes on real copy.

How we will know it is working

Empire field metrics for Porter are operational, not vanity:

  • Fewer after-hours texts to Mark about window URLs and order status.
  • Time-to-first-window-open for new affiliates (self-serve vs hand-holding).
  • Tool call success rate scoped to the logged-in partner slug.
  • Support tickets that cite "portal confusing" vs "Porter wrong answer" — we want the second bucket tiny.

We will not publish a ROI slide. We will publish honest counts in a later chapter if the dev slice graduates to prod.

Under the hood (without duplicating the builder journal)

The pull pattern mirrors Tasks q-bridge:

  1. Widget POSTs user text to Kitchen POS partner-bridge (messages).
  2. Broca on moya (broca-porter screen) polls inbox, forwards to Porter_Vernal on Letta.
  3. Porter may call **kitchen_pos_partner_*** SMCP tools; keys resolve per partner user (`kppb…` prefix), never passed by the model.
  4. Replies land in outbox; the widget polls responses.

SQLite for the bridge queue lives beside the kitchen DB (partner_bridge_webchat.db). Rate limits follow the same discipline we documented for Ask Q — partner session traffic is not operator traffic.

For mechanism detail — Broca plugin layout, poll bearer keys, Letta attach checklist — read the companion builder post on SanctumOS: Porter Vernal partner-bridge: Broca pull plugin and partner SMCP.

How this extends the Empire experiment

Empanada Empire is a field lab. Channel partners are a distribution hypothesis: relationship-driven pickup beside marketplace discovery. Software that makes affiliates self-sufficient is part of the hypothesis — not a nice-to-have.

Porter is the difference between "we gave you a login" and "we gave you a login that answers questions at 10 p.m. when the church group text thread is loud." We measure success in fewer interrupt texts to Mark, faster window opens, and tool calls that stay inside one partner's ACL.

What we explicitly did not ship (Phase 1b boundaries)

Not in Phase 1b Why
Prod partner portal Porter Dev host + moya broca-porter target dev URL only until launch slice
Window open/close via Porter Read-first on windows; affiliates still use portal forms for mutations
Attachments / bulk / IAM tools Wrong layer; not partner-scoped
Operator menu edits Rico / operator SMCP lane

Smoke coverage: Playwright E2E on dev (tools/playwright_ask_porter_dev_e2e.py) — bubble visible, probe message, bot reply. Affiliate alvin used in manual smoke per runbook.

Lessons for product teams embedding Vernal

  1. Scope the tools before you scope the persona — four honest verbs beat twenty scary ones.
  2. Reuse bridge semantics — Ask Q hardening paid forward; do not invent a third poll budget story.
  3. Session identity is not optional — partner portal cookie → bridge → Broca current_partner_user_id.txt → key resolver.
  4. Say "dev only" in public — affiliates and operators should not think Porter is prod until it is.
  5. Pair outcome and mechanism posts — DSC field story plus SanctumOS builder journal keeps audiences straight.

Ask Porter is the affiliate-facing voice of the same Kitchen POS stack that powers Square round-trip and the Sanctum operator shell. The empanadas are real; the partners are real; the bot is allowed to be helpful only within that truth.

Questions about Kitchen POS or partner programs: contact us.