Porter Vernal partner-bridge: Broca and SMCP

Kitchen POS Ask Porter is a partner-scoped Vernal webchat on the affiliate portal. This post is the builder journal: how we wired partner-bridge on the PHP host

Kitchen POS Ask Porter is a partner-scoped Vernal webchat on the affiliate portal. This post is the builder journal: how we wired partner-bridge on the PHP host to Broca on moya to Porter_Vernal on Letta, with a four-tool SMCP profile that cannot see operator APIs.

The outcome story β€” why affiliates need a co-pilot, what Phase 1b includes, prod boundaries β€” lives on the DSC blog: Ask Porter: a partner-scoped Vernal co-pilot on Kitchen POS. Read that for field context; read this for transport and tool governance.


Design constraint: copy Ask Q, not invent Broca semantics

Tasks q-bridge already solved poll-based webchat against a PHP app with rate limits, session keys, and Broca inbox/outbox. Porter's bridge (public/partner-bridge/api/v1/index.php) deliberately mirrors those actions:

Action Role
partner_context / partner_session Widget bootstrap + canonical session id
messages User β†’ Porter inbox
inbox / outbox Broca poll dequeue / enqueue
responses Widget poll for assistant text
resolve_partner_key Broca fetches kppb_… key for current partner user
history Recent turns for UI
config Health metadata for ops

Poll routes authenticate with PARTNER_BRIDGE_POLL_API_KEY (env or partner_bridge_poll_api_key config). Widget routes authenticate with the partner portal session β€” same cookie as /partner/.

Queue storage: {DB_PARENT}/partner_bridge_webchat.db (SQLite beside kitchen DB). Idempotency and processed flags follow the same mental model as q-bridge: Broca owns sequencing; PHP owns persistence.

Phase 1b ops: broca-porter on moya polls dev Kitchen POS only. Do not point prod bridge URLs at moya until launch slice is approved.


Broca plugin: porter_vernal_webchat

Repo: broca/plugins/porter_vernal_webchat/ (synced to moya via kitchen-pos/tools/moya_setup_porter_broca.sh).

The plugin is a thin poll loop:

  1. PorterWebChatAPIClient hits partner-bridge inbox with bearer token.
  2. New rows become Letta messages for Porter_Vernal (agent-b871ebbf-6185-4899-8881-8efadc9224ea on moya).
  3. Assistant output posts to outbox; widget clients pick it up via responses.

Settings load from env (PorterWebChatSettings.from_env()): base URL, poll interval, platform name. Screen name on moya: broca-porter.

This is the same architectural slot as other webchat plugins β€” platform adapter in Broca, intelligence in Letta, contract in PHP. The novelty is partner ACL, not the loop shape.


Partner SMCP: four tools, server-side key resolution

Plugin tree: kitchen-pos/smcp_plugin/kitchen_pos_partner/ + porter_vernal_partner/resolve_key.py.

Tool API
kitchen_pos_partner__me GET /api/partner/me.php
kitchen_pos_partner__menu GET /api/partner/menu.php
kitchen_pos_partner__windows GET/POST /api/partner/windows.php (Phase 1b read-first emphasis)
kitchen_pos_partner__orders GET /api/partner/orders.php

Explicit exclusions: operator kitchen_pos__, Tasks q_vernal_tasks__*, provision routes, attachments, bulk, other partners' slugs.

Key resolution path:

  1. Broca writes current_partner_user_id.txt under /opt/broca-porter/run/ when dequeuing a message.
  2. resolve_key.php maps user id β†’ kppb_… partner bridge key.
  3. SMCP stdio server (run-smcp-stdio-for-letta.sh on moya) never exposes the key to the model β€” tools call Kitchen POS with server-injected credentials.

Letta attach checklist (docs/PORTER-VERNAL-TOOL-PROFILE.md):

  • MCP server: kitchen-pos-partner-smcp
  • Attach only the four tools above
  • Verify with GET /v1/agents/{porter_id}/tools
  • Smoke: dev partner sends Ask Porter message β†’ tool call succeeds for their slug only

Persona + job_rules blocks follow the Q pattern (PORTER-VERNAL-JOB-RULES.md, moya_setup_porter_agent_profile.py). Porter introduces himself as Ask Porter in the UI; Letta name remains Porter_Vernal.


Widget front-end

public/partner-bridge/widget/ β€” porter-chat-boot.js, porter-chat-widget.js, included from public/partner/includes/_ask_porter.php on logged-in partner pages.

Bootstrap flow:

  1. Load config JSON from PHP (bridge base, feature flags).
  2. partner_context β†’ session + display metadata.
  3. Poll responses with backoff on 429 (same lesson as Ask Q hardening β€” visible retry, no silent stall).

CSP requires external boot script (porter-chat-boot.js) so script-src 'self' stays satisfied.

E2E: tools/playwright_ask_porter_dev_e2e.py on dev.kitchen-pos.decisionsciencecorp.com β€” bubble visible, probe string, non-empty bot reply.


Commits and repos (June 2026)

Area Ref
Partner bridge foundation 06ee055, f35bda3
Ask Porter widget + SMCP wrapper 9252f53
Porter tool lock (no operator tools) 295f359
Letta profile alignment 70c8571, 0335934
Dev chat E2E 0335934
Broca plugin broca repo 8d6b524 (poll plugin)
SMCP partner profile smcp 6ad1f6c, 25632da

Cross-link DSC channel partners chapter β€” Porter without partners is a demo; partners without self-service answers is a support ticket factory.


Launch checklist (when Mark clears prod)

  1. channel_partner_enabled + Porter feature flag on prod host (explicit slice β€” not cron accident).
  2. moya broca-porter env β†’ prod bridge URL + rotated poll bearer.
  3. Letta tool attach verified on Porter_Vernal prod agent id.
  4. Rate limits tuned for partner session poll cadence (reuse q-bridge runbook math).
  5. Playwright smoke against prod partner test account β€” read-only unless Mark authorizes mutations.

Until then: dev only, documented honestly in both blogs.


Why this belongs on SanctumOS

SanctumOS is where we publish how Sanctum building blocks compose β€” Broca, Letta, SMCP, PHP bridge apps on multihost. Porter is a reference implementation of narrow tool profiles (see also SMCP tool governance): expose a wide API on the server, attach a thin slice to the agent, resolve credentials out of band.

If you are wiring your own product portal to Vernal, steal the pattern before you steal the code: bridge queue in the app, Broca poll plugin, SMCP profile per persona, no operator tools on affiliate agents.

Questions or PRs: SanctumOS contributing Β· Kitchen POS docs/partner-bridge.md.