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
| Surface | Where | Who sees it |
|---|---|---|
| gEye Friday bubble (draggable eyeball + chat) | Global, all routes (FridayBubble.tsx) | owner/admin only (FridayBubble.tsx:864,929) |
Full page /friday | FridayPage.tsx → FridayPanel | owner/admin (App.tsx:105) |
| Per-project panel (“§13 FRIDAY”) | ProjectDetail.tsx:1710 | owner/admin |
Friday’s Notebook /friday-notes | notes CRUD + screenshots | owner/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 Rob | dead (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)
| Voice | Roles | Model | Tools | Notes |
|---|---|---|---|---|
| internal_owner/admin | up_owner, up_admin | Opus 4.8 | all 34 | schema ref, cross-brain digest, Tufte |
| staff_scheduler | scheduler | Sonnet 4.6 | 13 | create_rfi is the one write |
| staff_assistant | assistant | Sonnet 4.6 | 13 | read-only |
| staff_goa | goa, goa_readonly | Sonnet 4.6 | 11 | read-only |
| staff_triage | triage | Sonnet 4.6 | 11 | financial redaction flag |
| client | (fallback) | Sonnet 4.6 | 4 | no financials, no jargon |
| field | (unreachable) | Sonnet/Opus mismatch | 6 | mobile-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:
- Financial redaction does not apply on the main
/chatpath.redact_financialsand per-voicemax_tokensare honored only in/chat-context(portal_friday.py:3489,3518);/chathardcodes 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. - The SQL table allowlist is dead code.
_ALLOWED_TABLESand_inject_limitinfriday_sql_service.py:39-49,129are never referenced. The RPC is SECURITY DEFINER over all ofpublic—run_sqlcan readportal_usersetc., bypassing RLS. Blast radius is owner/admin only today, but the guard everyone assumes exists doesn’t. /chat-contexthas no rate limit and no conversation_log write — unlike/chat.- The
/chat-contextredaction 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. GET /portal/friday/statusis unauthenticated (static payload — low impact).render_sandboxexecutes model-authored HTML/JS in the operator’s browser — owner/admin only, but worth confirming the iframe carries a restrictivesandboxattribute + CSP.
4. Broken, dead, or stranded capability
| Item | Detail |
|---|---|
| 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 mapping | Falls 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 regression | v1 portal called /chat-context from Messages/ProjectThread; v2’s MessageQueue/ChatContext dropped it entirely. |
| Slash commands mostly fake | 8 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 save | Absent from _NOTE_PREFIX_MAP (:162-173). |
| Unused endpoints | voice-info (zero references), notes/export (no UI button), status (no health indicator). |
| Stale code | MODEL_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 prompts —
SLASH_COMMANDSin 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
/fridayand/projects/:idrender 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)
- Apply
redact_financials+ per-voicemax_tokenson/chat(not just/chat-context); replace the substring redaction with column-level stripping (the machinery already exists infriday_sql_service._FINANCIAL_COLUMNS). - Wire up the SQL table allowlist for real (or enforce it in the RPC); decide deliberately which tables owner-tier SQL may touch.
- Add rate limiting + conversation_log to
/chat-context. - Map
ap→ a proper voice; add auth to/statusif 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
| Order | Work | Effort (rough) | Unlocks |
|---|---|---|---|
| 1 | Phase 0 safety gates | small–medium | everything below |
| 2 | Phase 1 staff un-gate + suggested prompts | medium (5 frontend files + QA per voice) | 9 active users |
| 3 | Phase 2 Kilroy field hookup + filter fix | small–medium | Darius |
| 4 | Phase 3 proactive digests | medium | daily habit formation |
| 5 | Hygiene items | small, incremental | trust + polish |
| 6 | Phase 4 client tier | business decision first | external users |