Skip to main content

Ask Brian (engineering desk)

Naming

Ask SABLE is the product's own chat for its users. Ask Brian is this separate, admin-only engineering desk, named for its persona (SABLE_ASK_PERSONA_NAME).

web/ask.html, backed by /api/ask-engineering. A separate, narrower chat surface than Ask SABLE — this one answers questions about SABLE's own documentation, code, and database schema, for engineers debugging or extending the app, not about a client's projects.

The gate: admin AND an explicit allowlist

Reaching this screen at all requires both conditions: the viewer's role must be admin, and their identity must appear in SABLE_ASK_ALLOWLIST. Unset, that allowlist means nobody — there is no fallback to "any admin." whoami's canAskEngineering field decides server-side whether the nav link (data-nav-access="ask") even shows.

Three layers protect the database access, so no single mistake can write or over-read

  1. A NOLOGIN Postgres role (sable_engineering_reader) granted SELECT on the tables an engineer may see — and explicitly nothing on secrets, the env control plane, private mail and chat, or feedback attachments. New tables are unreadable by this role until a migration grants them, deliberately with no alter default privileges — a future secret-shaped table is safe by default rather than readable until someone remembers to deny it.
  2. A permissive SELECT RLS policy for that role on every RLS-protected table it may see — necessary because the role isn't the table owner, and the normal org-scoped policies need auth.uid(), which a service-role call doesn't carry.
  3. One SECURITY DEFINER function that switches to the reader role, pins a 5-second statement timeout, marks the transaction read-only, wraps the caller's SELECT in a row cap, and returns JSON. Only service_role may call it — the web route checks the caller's role and allowlist membership before it ever reaches this function.

Nothing here grants anything to anon or authenticated — the entire path is service-role only, gated at the web layer first.

What it can do besides query the database

Read-only SQL through ask_engineering_query, connector and Trigger run status, and a search over the documentation/code corpus (searchDocs, searchCode, readFile, readDoc) — the model has tools, but every one of them reads, none writes.

No persisted memory, on purpose

Unlike /api/chat-stream, nothing here lands in chat_sessions/chat_messages. The page itself keeps the conversation in memory for the session; closing the tab loses it. This is a deliberate difference from the client-facing chat surfaces, which persist multi-turn history.

Where the code lives

  • sable-agents-demo/web/ask.html
  • sable-agents-demo/web/api/ask-engineering.js, _ask_engineering.js
  • sable-agents-demo/web/api/_ask_corpus.generated.js
  • sable-agents-demo/supabase/migrations/20260909220000_ask_engineering_readonly.sql
  • sable-agents-demo/scripts/ask/bundle-corpus.mjs, scripts/ask/rollback-ask-engineering-readonly.sql
  • sable-agents-demo/docs/superpowers/specs/2026-09-09-ask-brian-engineering-desk.md