Friday AI — Capability Audit

Date: 2026-08-06 · Auditor: Claude Code (session audit, read-only — no changes made) Sources: /opt/unlikely-api (routes/portal_friday.py + 9 friday_* services), /opt/portal-v2 frontend, friday_sql_audit.db, conversation_log.db, portal_users.db, vault design notes (TB-08a, friday-ai.md)


1. Headline

Friday is a far more capable platform than its usage suggests — and the gap has one dominant cause: the portal UI hard-gates every Friday surface to up_owner/up_admin, while the backend has fully built, purpose-scoped voices for 6 other roles that no human can reach.

Usage evidence (all time):

  • 91 logged conversations total: 82 Dustin, 4 Jacob (last 2026-04-01), rest test accounts
  • SQL tool: 100 queries ever, owner-tier only; dead April–July (the 3.5-month outage fixed 2026-08-05), 7 queries since
  • 9 active non-owner portal users (Zoe Kelley & Julia Marketis — schedulers; Stephaney Bilyard + 3 others — triage; Madison Carter — assistant; Angie Lathrum — corporate GOA readonly), several daily, with zero Friday exposure
  • No scheduled/background invocation anywhere — Friday is 100% reactive, and only 2 people can react to it

2. What Friday can do today (capability inventory)

Surfaces

SurfaceWhereWho sees it
gEye Friday bubble (draggable eyeball + chat)Global, all routes (FridayBubble.tsx)owner/admin only (FridayBubble.tsx:864,929)
Full page /fridayFridayPage.tsxFridayPanelowner/admin (App.tsx:105)
Per-project panel (“§13 FRIDAY”)ProjectDetail.tsx:1710owner/admin
Friday’s Notebook /friday-notesnotes CRUD + screenshotsowner/admin (App.tsx:124)
Friday-in-chat-thread (/portal/friday/chat-context)v1 portal only — dropped in v2 (regression)nobody in practice
WhatsApp field ask (/whatsapp/ask-friday)Darius-only, via Robdead (Rob shut down 7/27 + a filter bug — §4)

Tools — 34 total (portal_friday.py:1334-1369)

  • 25 read tools: project search/detail, schedule, RFIs, notes, schedule changes, semantic similar-projects, analytics (pipeline + revenue), 4-year archive search (by address/invoice/date), pricing intelligence, precedent search, live pipeline, structured query, schema introspection, raw SQL (hardened Postgres RPC, 5s timeout, SELECT-only, audited), calendar, Shepherd alerts, recon status, certcheck, and 3 renderers (Tufte SVG — 14 chart types, Recharts interactive, HTML sandbox).
  • 6+ write tools with preview → confirm-token (60s TTL, user-bound, rate-limited, comm_log audited): update_status (write-guard protected), create_rfi, update_field (5-field allowlist), send_cert (direct or queue-for-review), modify_schedule, review_schedule_change, delete_calendar_event, send_whatsapp (immediate, Dustin/Darius allowlist only).
  • 11 deterministic chat-command intents (chat_command_executor.py) that run before the LLM in chat threads: schedule visit, approve cert, send invoice, assign/close conversation, flag RFI, hold project, approve review, needs revision, resolve RFI.

Voice tiers (friday_voice_service.py)

VoiceRolesModelToolsNotes
internal_owner/adminup_owner, up_adminOpus 4.8all 34schema ref, cross-brain digest, Tufte
staff_schedulerschedulerSonnet 4.613create_rfi is the one write
staff_assistantassistantSonnet 4.613read-only
staff_goagoa, goa_readonlySonnet 4.611read-only
staff_triagetriageSonnet 4.611financial redaction flag
client(fallback)Sonnet 4.64no financials, no jargon
field(unreachable)Sonnet/Opus mismatch6mobile-concise

Cross-brain: every /chat exchange logs to conversation_log; recent Kilroy/Kulu exchanges are injected into Friday’s prompt.


3. Security findings — fix BEFORE widening access

These matter precisely because the recommendation is to open Friday to more roles. In gate order:

  1. Financial redaction does not apply on the main /chat path. redact_financials and per-voice max_tokens are honored only in /chat-context (portal_friday.py:3489,3518); /chat hardcodes 2048 tokens and applies no tool-result redaction (:3001). The triage voice’s redaction — an explicit standing requirement (feedback_friday_client_tone) — is currently decorative on the path the UI would actually use. Blocking prerequisite for any access widening.
  2. The SQL table allowlist is dead code. _ALLOWED_TABLES and _inject_limit in friday_sql_service.py:39-49,129 are never referenced. The RPC is SECURITY DEFINER over all of publicrun_sql can read portal_users etc., bypassing RLS. Blast radius is owner/admin only today, but the guard everyone assumes exists doesn’t.
  3. /chat-context has no rate limit and no conversation_log write — unlike /chat.
  4. The /chat-context redaction is a blunt substring match on ["$","revenue","invoice_total","amount"] that nukes entire tool results (false-positives on $$FRIDAY_CHART$$) while missing financial data without those tokens.
  5. GET /portal/friday/status is unauthenticated (static payload — low impact).
  6. render_sandbox executes model-authored HTML/JS in the operator’s browser — owner/admin only, but worth confirming the iframe carries a restrictive sandbox attribute + CSP.

4. Broken, dead, or stranded capability

ItemDetail
Field tier is doubly dead/whatsapp/ask-friday was Rob-dependent (Rob shut down 7/27) AND _get_account_filter returns [] for the field role, which strips every row → “No projects found” for every query (portal_friday.py:1391,1433). Kilroy (Telegram) never got a Friday hookup — grep of kilroy_notify.py finds nothing.
ap role has no voice mappingFalls back silently to the 4-tool client voice (friday_voice_service.py:310).
client/field not in VALID_ROLES(portal_auth.py:31-40) — both tiers unreachable via portal auth at all.
Chat-thread Friday regressionv1 portal called /chat-context from Messages/ProjectThread; v2’s MessageQueue/ChatContext dropped it entirely.
Slash commands mostly fake8 commands advertised (/schedule, /search, /rfis, /history, /pricing, /precedent, /notes, /analytics) but only /commands, /help, /certcheck, /revenue are implemented — the rest fall through to the LLM as literal text (:2667-2770).
todo:/flag: note prefixes advertised but don’t saveAbsent from _NOTE_PREFIX_MAP (:162-173).
Unused endpointsvoice-info (zero references), notes/export (no UI button), status (no health indicator).
Stale codeMODEL_ADMIN/MODEL_CLIENT/ADMIN_ROLES in the route unused; friday_engine voice configs + run_with_voice unused; whatsapp.py builds its own inline config with Opus instead of the intended Sonnet and a fallback to a decommissioned model ID; duplicate elif in /revenue (:2757).

5. UX gaps (owner-tier experience)

  • No streaming — replies land whole after multi-tool loops that can take many seconds.
  • No server-side conversation history — bubble replays last 20 messages from localStorage; the per-project panel sends no history at all (stateless per turn despite displaying a thread).
  • No suggested promptsSLASH_COMMANDS in FridayPanel is used only for text styling; nothing tells a new user what Friday can do.
  • Split implementations — FridayBubble and FridayPanel are two separate chat stacks. Bubble renders 13 chart types; Panel renders 3 and silently downgrades the rest to a bar chart, so charts on /friday and /projects/:id render wrong.
  • Screenshot-to-note coupling is fragile — requires regex-parsing the note ID out of Friday’s reply text.
  • Widening access requires edits in 5 frontend files — role gating is hardcoded literals (App.tsx:105,124, FridayBubble.tsx:864, ProjectDetail.tsx:388, Sidebar.tsx:259 + nav arrays).

6. Recommendations — leveraging Friday for all users

Phase 0 — safety gates (blocking, before any rollout)

  1. Apply redact_financials + per-voice max_tokens on /chat (not just /chat-context); replace the substring redaction with column-level stripping (the machinery already exists in friday_sql_service._FINANCIAL_COLUMNS).
  2. Wire up the SQL table allowlist for real (or enforce it in the RPC); decide deliberately which tables owner-tier SQL may touch.
  3. Add rate limiting + conversation_log to /chat-context.
  4. Map ap → a proper voice; add auth to /status if desired.

Phase 1 — internal staff rollout (highest leverage, backend already done)

Un-gate the UI for scheduler, assistant, goa, goa_readonly, triage. The voices, tool restrictions, and branch scoping already exist server-side; this is a frontend change plus QA per voice. Concrete daily wins:

  • Zoe / Julia (schedulers): “what’s on the schedule this week for my branch”, “status of 123 Main St”, “any pending schedule changes”, create RFIs conversationally instead of pinging Dustin.
  • Stephaney + triage team: permit/PPDR lookups, certcheck, precedent search — with financial redaction (once Phase 0 lands). She’s in the portal near-daily; this directly offloads the “ask Dustin/Jacob” traffic.
  • Madison (assistant), Angie (corporate GOA): read-only pipeline/schedule Q&A across their scope.
  • Add role-tailored suggested prompts in the empty state so each role discovers what its voice can do — discoverability is why Jacob stopped after 4 messages in April.
  • Cost is modest: staff tiers run Sonnet at 1024 max tokens.

Phase 2 — field tier via Kilroy

Rewire the field ask path from the dead Rob/WhatsApp route to Kilroy’s Telegram (Darius is already allowlisted there), and fix the _get_account_filter bug that returns zero rows for the field role. “What’s my next stop”, “what’s the scope at this address”, “did the pier log go through” from the truck is exactly what the field voice was built for.

Phase 3 — proactive Friday (currently zero background usage)

Friday has analytics, Shepherd alerts, recon status, and certcheck tools but is never invoked by the scheduler. Candidates:

  • Role-scoped morning digests (schedulers get their branch’s day; triage gets pending desk reviews) — delivered in-portal via notifications, reusing the existing tools.
  • Shepherd findings narrated by Friday (“3 projects stalled >10 days, here’s why”) instead of raw report rows.
  • These compound with Phase 1: proactive messages are what pull staff into the habit of asking follow-ups.

Phase 4 — client tier (deliberate, opt-in decision)

The client voice exists (4 tools, no financials) but client isn’t a valid portal role and no client-facing surface exists. This is a business decision, not a code gap — if pursued, the blocking requirements in feedback_friday_client_tone.md apply (dedicated prompt, no financial tools, professional tone), plus Phase 0 items 1–2.

Ongoing hygiene

  • Restore Friday-in-chat (/chat-context) in the v2 MessageQueue — a shipped v1 capability that silently regressed.
  • Implement or remove the 8 advertised-but-fake slash commands and the todo:/flag: prefixes.
  • Unify FridayBubble/FridayPanel chart rendering (or share the renderer component).
  • Consider streaming + server-side conversation history when usage grows.
  • Move rate-limit/pending-confirm state out of process memory (Redis is already in the stack) so restarts don’t reset limits.

7. Suggested sequencing

OrderWorkEffort (rough)Unlocks
1Phase 0 safety gatessmall–mediumeverything below
2Phase 1 staff un-gate + suggested promptsmedium (5 frontend files + QA per voice)9 active users
3Phase 2 Kilroy field hookup + filter fixsmall–mediumDarius
4Phase 3 proactive digestsmediumdaily habit formation
5Hygiene itemssmall, incrementaltrust + polish
6Phase 4 client tierbusiness decision firstexternal users