Canonical source: docs/claude/atlas-build-state.md. This page is generated by docs/scripts/sync-handbook.mjs. Edit the source file in the repo; changes appear here on the next build.
Atlas — Build State & Remaining Steps
Snapshot for context-compaction. Last updated 2026-06-23. Companion to
docs/claude/copilot.md. Memory:project_atlas_copilot_tier_a.md,project_observation_engine.md.
☠️ TWO DEPLOY LANDMINES (both bit us 2026-07-01, cost real time)
- Decorator displacement (AGAIN): inserting a helper
defimmediately BELOW a decorator block and ABOVE the intended function moves the decorator onto the helper. Refactoringexecute_copilot_action(extractingrun_copilot_draftabove it) put@https_fn.on_call+@error_handleronto the helper →execute_copilot_actionlost its decorator → firebase DELETED it from prod (web confirm cards broke). ALWAYS check for a decorator above a function before inserting code above it; after any such editgrep -nE "@https_fn|def <fn>"to confirm alignment. execute_copilot_actionis NOT in main.py's FUNCTION_TARGET dispatch — it only loads via the bottom wildcardfrom src.functions_ai_agent import *. Sofirebase deploy --only functions:execute_copilot_actionALONE fails discovery → DELETES it. ALWAYS deploy execute_copilot_action TOGETHER with clinical_agent_sse (which forces functions_ai_agent to load). Same applies to any other functions_ai_agent function not in the FUNCTION_TARGET set.
⚙️ ATLAS-BRAIN DEPLOY GROUP (tool/prompt changes)
Atlas's brain (TOOLS + orchestrate_query + SYSTEM_PROMPT) is shared code bundled into MULTIPLE functions. A tool/prompt change is only live in functions you REDEPLOY. When you change tools/prompt/orchestrator, redeploy ALL of these together or Slack/onboarding lag behind web:
firebase deploy --only functions:clinical_agent_sse,functions:clinical_agent_stream,functions:atlas_slack_worker,functions:heartbeat_worker --force
(Per-clinic = fully automatic; ONE multi-tenant backend serves every clinic, no clinic action ever. This deploy-set is OUR hygiene only. New Slack SCOPES are the only thing that makes clinics re-consent.)
Deployment model (READ FIRST)
- Backend (Cloud Functions) = LIVE in production. Every
firebase deploy --only functions:…this work-stream shipped to prod. Real clinics run on it. - Frontend (
basisflow-web) = localhost only. All the Atlas UI lives on the dev server + the SSE endpoint. Nothing is on platform hosting yet — that's the pending "publish". - The SSE endpoint
clinical_agent_sseis anon_requestGen2 fn; the copilot + chat draft-assist call it directly viafetch(Bearer ID token). It must haveallUsersinvoker (it self-auths inside) — redeploys can drop this; re-grant withgcloud functions add-invoker-policy-binding clinical_agent_sse --gen2 --region=us-central1 --member=allUsers.
What's built & DEPLOYED (backend)
Copilot core (functions_ai_agent.py, functions_orchestrator.py)
clinical_agent_sse(SSE streaming, in-request CPU) +orchestrate_query(parallel snapshot+intent classify, model auto-routing, prompt caching, quota gate).- Grounding gate:
_grounding_judge(Haiku) verifies patient-specific claims vs that turn's DATA tool outputs; emits SSEgroundingevent → badge + "sources". Excludes action/stage tools (prepare_/save_/create_/set_/update_/send_/execute_) so it doesn't flag operational details. Grounding persisted on the saved message. - Context-packet cache:
_get_cached_patient_snapshot(150s TTL, process-local) in orchestrator. - Draft provenance: every
copilot_draftsdoc stampedsession_id+request_idvia_draft_meta()(contextvars set inrun_claude_agent; parallel tools usecopy_context().run). Frontend pins confirm cards to the originating chat+turn.
Agency tools (all confirm-card gated; writers in _COPILOT_ACTION_WRITERS/_COPILOT_ACTION_PERMS, via execute_copilot_action)
- book / reschedule / find-slots, assign_protocol (+ catalog reconciliation), update_protocol, save_clinical_note, save_report_to_documents, set_benchmark (+ list_benchmarks), create_heartbeat_task, create/update/enroll sequences, prepare_client_message (editable send-to-client card), prepare_create_task (staff tasks).
assign_protocol(inclinic_service) now expands the fullsupplements[]array + find-or-creates supplements/exercises into the clinic catalog (slug-id dedup) via_reconcile_protocol_catalog.
Proactive observation engine (functions_observations.py, wired into heartbeat_dispatcher)
- Store:
clinicsv2/{clinic}/observations. Deterministic scorer → tier;briefingflag (tier≥2) for the dashboard query;source= template|rule|atlas. - Detectors:
lab_out_of_range,stale_panel,sleep_trend(templates, toggleable),attention_rule(readsattentionConfig.rulesfrom Settings→Need Attention), Atlas autonomous (run_autonomous_review, Sonnet cross-signal, enqueued to heartbeat_queue →heartbeat_worker, capped 5/scan). - Bounded round-robin scan (cursor, 30-min interval). Auto-resolve (
_resolve_stale_observations) closes cleared template/rule findings. - Settings → Need Attention has a "Default monitoring" toggle section (
attentionConfig.defaults.{key}.enabled).
Chat / sectioned-Atlas (functions_chat_option_a.py)
- Draft-assist:
clinical_agent_ssemode:'chat_draft'(_chat_draft_reply). - Auto-categorize: hooked into the warm
chat_unread_countertrigger (addedsecrets=['ANTHROPIC_API_KEY'])._classify_chat_message(Haiku) → top-levelatlasCategory/atlasUrgency/lastMessageAt/lastMessageFromClient/lastMessageSnippetonchats/{clientId}. Loop-guarded onlastMessageAt+_CAT_VERSION(currently 2). Taxonomy: clinical_question · symptoms · data · fitness · nutrition · refill · scheduling · admin · urgent · general(default). Backfill:tools/backfill_chat_categories.py <clinic>(touches chats → fires trigger; per-clinic only, NOT global).
KB / memory: per-patient clinic_users/{uid}/settings/memory facts (auto-extracted from staff turns now, via Anthropic — Vertex was failing); staff-editable via AtlasMemoryPanel (frontend-direct, rule added). Manual KB items in Settings.
Test tools: tools/trigger_atlas_insight.py <clinic> [uid], tools/backfill_chat_categories.py <clinic>.
2026-06-28 cross-client analytics — slice 1 DEPLOYED. Q: can Atlas answer clinic-wide queries (who cancels most, who hasn't booked in 3mo, cancelled this quarter, adherence<80%, revenue membership vs one-off in April)? Was NO (all tools single-client). VERIFIED data shapes live: cancellations in scheduled (status early_canceled/contains 'cancel', uids[]+start); REVENUE already solved by existing functions_analytics.py::_compute_revenue_analytics (returns byMembership/byProduct/byService/totalsByDate from clinic transactions — payload in nested data); ADHERENCE data EXISTS in users/{uid}/events (eventType, completionStatus, protocolId) — was wrongly thought unplumbed. DECISIONS: hybrid (nightly rollup + live fallback); per-client metrics written ONTO clinic_users/{uid}.analytics (NOT a giant rollup doc — scale) so query_clients uses Firestore range filters; reuse warm heartbeat_dispatcher (24h-gated maybe_run_daily_rollup), no new scheduled fn. BUILT (slice 1): NEW functions_atlas_analytics.py (compute_booking_rollup → clinic_users.analytics {lastBookingAt,bookingCount,cancelCount}; query_clients_rollup) + tools query_clients (min_cancellations/no_booking_days/status) and get_revenue_report (wraps _compute_revenue_analytics, membership vs one-off %) — 4 wirings each (schema/dispatch/perms[query_clients=VIEW_CLINIC_MEMBERS+COPILOT_OPS, revenue=VIEW_REVENUE+COPILOT_OPS]/orchestrator OPS). Hooked rollup into heartbeat_dispatcher. Tested live on axuk-khwf-prkr: 17 clients rolled up, all queries + revenue split (33% membership/66% one-off) work. Slice 1.5 — GENERAL FILTER GRAMMAR (DEPLOYED 2026-06-30). Refactored query_clients from fixed knobs → a general grammar so we add COLUMNS not queries. query_clients_grammar(filters[], sort, aggregate, limit): filters = [{field,op,value}] AND-ed; ops gt/gte/lt/lte/eq/ne/contains; sort {field,dir}; aggregate {op:count|avg|sum, field?, group_by?}. Published FIELD CATALOG in the tool description (Atlas composes from NL): NUMERIC cancelCount,bookingCount,orderCount,revenueToDate(cents),daysSinceLastBooking,daysSinceLastPurchase,daysSinceLastLab(999999=never),tenureDays; STRING status,membershipType; BOOL isMember. Rollup expanded → compute_client_rollup adds orders(LTV: revenueToDate/orderCount/lastPurchaseAt) + lab recency(lastLabAt, needs collectedAt desc index per clinic_users/{uid}/labs — may log index warnings until built) onto clinic_users.analytics. Read engine: bounded scan _MAX_SCAN=5000 + status-eq server-side anchor + in-memory eval; sets truncated flag. Tested live axuk-khwf-prkr: combined filters, count, avg-group-by-membership, top-spenders sort all work. NOTE: group_by membershipType shows raw membership IDs (stores id not name) — cosmetic, map later. No-show NOT a distinct status in data (only none/early_canceled/confirmed) — omitted, not faked. Slice 2 — adherencePct column DONE + DEPLOYED 2026-06-30. Computed in compute_client_rollup per client from users/{uid}/events over last 30d: adherencePct = completed/(completed+missed) of PAST habit occurrences. completionStatus semantics: 'unknown'(past)=missed; {completed,autoCompleted,createCompleted}=done; future occurrences ignored. None when no protocol data → numeric ops SKIP (clients w/o data NOT false-flagged as low-adherence — verified). Added to NUMERIC_FIELDS + record + tool catalog. Tested live: George 14% (real), 'adherence<80' finds him, avg works, 16 no-data clients correctly excluded. Needs start index on users/{uid}/events (likely exists). Original 5 cross-client example queries all now answerable. Catalog DONE for now: cancelCount,bookingCount,orderCount,revenueToDate,daysSinceLastBooking/Purchase/Lab,tenureDays,adherencePct,status,membershipType,isMember. Future columns slot in identically. COSMETIC TODO: map membershipType id→name in group_by output.
PLACEHOLDER (was "Slice 2 TODO"): (compute expected-vs-completed from protocol habits recurrence vs users/{uid}/events.completionStatus → clinic_users.analytics.adherencePct; add no_adherence filter to query_clients). Note _read_intake precedent. Don't forget single-field indexes may be needed for analytics.cancelCount / analytics.lastBookingAt range queries at scale.
2026-06-28 get_intake_forms + snapshot include (DEPLOYED, backend live): New Atlas tool reads completed intake/onboarding form ANSWERS from clinicsv2/{clinic}/clinic_users/{uid}/onboardingForms/{formKey}. TWO storage shapes exist and BOTH are handled: answersJson (JSON STRING, current _save_submission_and_pdf pipeline) AND answers (native DICT, older write path — e.g. real user GoOU3d2QcyRZMD83IhbQeIclpwh2 in xdoz-pjao-zxvm). Reader prefers answersJson, falls back to answers-dict. RETROACTIVE — verified live on real data. get_documents only lists the PDF name, NOT its contents; this returns structured Q&A (humanized labels, submitted date, empties skipped, base64/data-URI signatures + >300/500-char blobs collapsed to [provided], optional form_key filter). 4 wirings: TOOLS schema + execute_tool + TOOL_PERMISSION_MAP (VIEW_DOCUMENTS) + orchestrator CLINICAL+RESEARCH. re imported locally in _humanize_field_key. ALSO added _read_intake section to build_patient_snapshot (compact: ≤8 forms, ≤12 answers each, blob-guarded) → observations + chat-drafts now factor intake context. All in functions_orchestrator.py + functions_ai_agent.py.
2026-06-23 dashboard polish: AtlasBriefingCard hover bug fixed (unnamed Tailwind group→group/obs named group; ancestor group was leaking group-hover to every row's Discuss). Card now caps at top 5 + "View all (N) →" → new app/(main)/attention/page.tsx (full observations list, status new/seen/snoozed, orderBy score, filters: search / source / type; Discuss+snooze+dismiss). No new index (orderBy score = auto single-field).
What's built (FRONTEND, localhost only — ships on publish)
components/copilot/CopilotDrawer.tsx (SSE client, grounding badge+sources, draft cards pinned by requestId/session, ClientMessageCard with ✓Sent, generic confirm cards with ✓done, token usage meter, AtlasMemoryPanel toggle), AtlasBriefingCard.tsx (dashboard proactive briefing → seeded fresh thread + deep-link), DashboardMessagesCard.tsx (top-5 inbox preview), ChatDrawer.tsx + app/(main)/messages/page.tsx (✦ Draft-with-Atlas + category chips + urgent-first sort), AtlasMemoryPanel.tsx, sendClientChatMessage.ts, Settings→Need Attention default-monitoring toggles.
REMAINING STEPS (in intended order)
-
Scheduled tasks / crons — ✅ DONE + DEPLOYED 2026-06-23. New Atlas tool
schedule_task(confirm-card gated;_exec_schedule_task+_parse_whenin functions_ai_agent.py; registered in_COPILOT_ACTION_WRITERS/_COPILOT_ACTION_PERMS, TOOLS schema, execute_tool dispatch,UNIVERSAL_TOOLS,TOOL_PERMISSION_MAP→COPILOT_OPERATIONS). Creates aheartbeat_taskwithkind(reminder|message|report) +payload+oneShot+schedule.runAt(one-time) orschedule.cron(recurring).- Dispatcher (functions_heartbeat.py): passes
kind+payloadinto the queue item; one-shot/runAt-only tasks setenabled=falseafter firing (no nextRunAt recompute); recurring recompute cron. - Worker:
_handle_scheduled_kind— reminder →_write_inbox_result(heartbeat_results + push); message → pre-authored text sent tochats/{clientId}(ArrayUnion, role coach) + result, else degrade to reminder (never auto-send unreviewed Atlas content); report/empty kind → existing Atlas-run branch. - Results surface in existing
components/copilot/HeartbeatInbox.tsx(reads heartbeat_results). LIVE-TESTED: enqueued reminder → worker wrote result, queue done. - NOTE:
create_heartbeat_taskkept as-is for recurring monitoring;schedule_taskis the richer one-time/dated tool. Deferred:scheduled_actionkind (execute an arbitrary writer); draft-on-date for messages (currently pre-authored auto-send or degrade-to-reminder); push-to-CLIENT on scheduled message (only writes to chat doc now).
- Dispatcher (functions_heartbeat.py): passes
-
Clinic-config knowledge + capability self-awareness — ✅ DONE + DEPLOYED 2026-06-23.
- Capabilities: added a "WHAT YOU CAN DO" section to
SYSTEM_PROMPT(functions_ai_agent.py, after PLATFORM HELP) — understand / analyze / act-with-approval / schedule / watch / configure, + honesty-about-limits. So "what can you do?" answers accurately. - Config how-tos: rewrote the
PLATFORM_HELPdict entries in_exec_get_platform_helpwith the REAL flows (mapped from basisflow-web UI): services, memberships, products, billing/Stripe, staff, protocols, forms, settings(+availability/booking params/Need Attention). Fixed stale nav (Services live under Services & Plans, Stripe connects in Settings → Integrations, real staff roles, etc.). - Deployed via clinical_agent_sse; public invoker re-verified (204).
- Capabilities: added a "WHAT YOU CAN DO" section to
-
PUBLISH to production — ✅ DONE 2026-06-23. Built clean + deployed
hosting:platform→ https://basis-hybrid-platform.web.app (platform.basishealth.io). The whole Atlas frontend (copilot SSE, briefing card, inbox card, /attention, chat draft-assist + category chips, capability/help) is now LIVE alongside the already-live backend.- Fixed 7 pre-existing build-blocking type errors with type-only casts (no runtime change): dashboard ClinicFulfillmentWidget (
clinicId ?? undefined,const u: any), LabsTab(selectedAnalyte as any), PaymentTabclinicId ?? undefined, PlanTab(a as any).anytime, protocolsicon: t.icon as any. (NoignoreBuildErrorsin next.config — kept type-checking on.) - Build:
rm -rf .next out && npm run build(output:'export' →out/) →cp -R out/* basis-functions/public-platform/→firebase deploy --only hosting:platform.
- Fixed 7 pre-existing build-blocking type errors with type-only casts (no runtime change): dashboard ClinicFulfillmentWidget (
-
Self-improvement + ops loops (PLANNED 2026-06-30, decisions locked, NOT built):
- Knowledge-gap loop (task #30) — SLICE 1 BUILT + DEPLOYED 2026-06-30. NEW
functions_atlas_gaps.py:capture_deflection(inline regex_DEFLECTION_RE, NO LLM — wired into clinical_agent_sse after answer save, surface-tagged) → writes clinicsv2/{clinic}/atlas_gaps.maybe_enqueue_weekly_digest(7-day gate, in heartbeat_dispatcher) enqueues type='gap_digest'. heartbeat_worker branchgap_digest→classify_and_digest(has ANTHROPIC_API_KEY): Haiku batch-classify (capability_gap|knowledge_gap|feature_request|appropriate|other) + topic, cluster, write clinicsv2/{clinic}/atlas_gap_digests. Tested live: regex correct (caught 3 deflections, skipped real LDL answer), capture+digest work; classification verified-by-pattern (same httpx+Haiku as the working chat categorizer). SURFACE CLARIFIED 2026-06-30: the "Atlas tells clients to ask team" report is from BASISFLOW-WEB (clinician told George), NOT consumer. basisflow-web has TWO Atlas answer paths — clinical_agent_sse (main copilot, 5 frontend refs) AND clinical_agent_stream (onboarding chat AtlasOnboardingChat.tsx, 2 refs) — capture_deflection now wired into BOTH (surface='copilot'/'onboarding'). Digest now DELIVERS to Slack: classify_and_digest →_post_digest_to_slackDMs OWNER_SLACK_ID via the Support bot's post_slack_message (get_slack_bot_token reads system/slack_config, no secret-scope issue). Did NOT fire a live test (would DM George's real Slack) — delivers on weekly schedule or on manual trigger. REMAINING TODO: (a) frontend digest panel in basisflow-web (optional, Slack covers visibility); (b) closing-the-loop KB self-fill + auto-route bugs to build pipeline (gated on git reconciliation — PARKED, addressing this week). - Ops-troubleshooting (task #31) — detect+correlate+notify ONLY (read-only). Sentry/CloudLogging/PostHog error clusters ↔ client-reported issue (chat auto-categorize) → Slack with cause+suggested fix. SAFETY: no autonomous prod code deploys (HIPAA). Future gated tiers: propose-fix-as-PR (build agent), reversible config-only auto-remediate w/ approval. Mostly CONNECTS existing infra (functions_sentry_webhook.py, functions_slack_bot.py triage, build agent, Sourcebot, nightly orchestrator) — verify before building.
- NOTE: three initiatives now in flight — (a) analytics grammar+column catalog, (b) Slack full push, (c) these two loops. Confirm sequencing with George before starting a new track.
- Knowledge-gap loop (task #30) — SLICE 1 BUILT + DEPLOYED 2026-06-30. NEW
SLACK P1 — install + identity + Settings UI (BACKEND DEPLOYED 2026-06-30; frontend localhost).
- NEW
functions_atlas_slack.py(helpers, lazily imported — no standalone deploy):store_atlas_slack_install(config doc clinicsv2/{clinic}/integrations/slack [staff-readable] + ENCRYPTED token in integrations/slack_secret [server-only, encrypt_message/STRIPE_ENCRYPTION_KEY] + reverse index slackTeams/{teamId}→clinic),get_clinic_slack_token,clinic_id_for_team,slack_config,clinician_uid_by_email(P2 identity). - ENHANCED existing deployed
slack_oauth_callback(functions_slack_bot.py) — when state=clinic_id, calls store_atlas_slack_install (SECURE) instead of the old plaintext-token-in-clinic-doc write; added STRIPE_ENCRYPTION_KEY to its secrets. Verified public (HTTP 200). Reuses existing SLACK_CLIENT_ID/SECRET (prototype creds). - Firestore rules added + DEPLOYED: integrations/slack (staff read, admin create/update for phi toggle+channel), integrations/slack_secret (deny all client), top-level slackTeams (deny all client).
- Frontend (localhost, ships next publish):
components/settings/AtlasSlackCard.tsxin Settings→Integrations — "Add to Slack" (OAuth authorize URL w/ state=clinicId, scopes, redirect=slack_oauth_callback), connection status, default channel, PHI attestation toggle (writes phiEnabled/phiAttestedBy/At). Needs NEXT_PUBLIC_SLACK_CLIENT_ID env set (the app's public client id) for the Connect button. - GEORGE'S PREREQ (P0 manual): register the dedicated multi-tenant "Atlas" Slack app (or enable OAuth distribution + add slack_oauth_callback as an allowed redirect URL on the existing app), set NEXT_PUBLIC_SLACK_CLIENT_ID. Until then the install flow can't complete end-to-end.
SLACK P2 — inbound ops Q&A (DEPLOYED 2026-07-01, endpoint-verified). In functions_slack_bot.py (reuses verify_slack_signature): NEW
atlas_slack_events(on_request, HMAC, 3s ack, dedup on retry header; handles app_mention + DM message.im; resolves team→clinic via slackTeams; enqueues clinicsv2/{clinic}/slack_queue) + NEWatlas_slack_worker(on_document_created slack_queue):_slack_user_email(users.info w/ clinic token) →clinician_uid_by_email→ perms(copilot_access) → PHI GATE: if NOT phiEnabled, filter allowed_tools to_ATLAS_NO_PHI_TOOLS(ops/config/revenue only, no patient tools) + system note to link out → orchestrate_query(user_id='') →_post_as_clinic(chat.postMessage w/ decrypted CLINIC token, in-thread). Registered in main.py (both spots). atlas_slack_events granted allUsers invoker; url_verification challenge test passes. SEPARATE from Support bot's slack_events (different app, different workspaces). Full end-to-end needs the Atlas app configured (George's prereq: Event Subscriptions URL → atlas_slack_events, DM/mention scopes, install to a clinic workspace). NEXT: P4 approvals as Slack buttons (P3 ops tools partly done via analytics engine).
ATLAS PROACTIVE EMPLOYEE #32 (DEPLOYED 2026-07-01). Decision: aggressive presence but grounded + back-off (no filler — the Viktor cautionary tale). In functions_atlas_slack.py: (1) onboarding_welcome_text/post_onboarding_welcome — ONE-TIME welcome DM to the installer on OAuth (slack_oauth_callback, gated on welcomeSent flag). (2) push_attention_pings(clinic_id, enc_key) — earned proactive nudge: posts clinic's tier>=3 (push-tier) observations to Slack (defaultChannelId or installer DM), once each (slackPinged flag), PHI-GATED (attested → name+title+recommendation+client link; else generic link-out, no PHI). Called post-scan from heartbeat_dispatcher (added secrets=['STRIPE_ENCRYPTION_KEY']). max 3 pings/clinic/tick. Verified: welcome renders, ping safely no-ops (0) on non-connected clinics. v1 pings are informative + deep-link (interactive [Draft it] buttons = P4). Live test needs Slack app registered + a tier-3 observation. NEXT logical: P4 (approvals as buttons → makes pings + answers actionable), then publish (ships AtlasSlackCard), then P3 ops tools.
SLACK P4 (approvals) + P3 (partial) DEPLOYED 2026-07-01. P4: extracted shared run_copilot_draft core (functions_ai_agent.py) used by BOTH execute_copilot_action (web) AND atlas_slack_interactivity (new on_request, HMAC, allUsers-invoker) — Slack worker posts staged drafts as Block Kit Approve/Discard cards (matched by draft_request_id → _post_draft_cards), click → run_copilot_draft → replace_original message. VERIFIED the two risky assumptions: verify_slack_signature uses RAW body (works for JSON events AND form-encoded interactivity); draft_request_id → run_claude_agent contextvar → stamped on drafts (orchestrator line 999 forwards it). P3: get_orders READ tool DONE+deployed (fulfillment status). P3 REMAINING = invite_client (add_clinic_user) + invite_staff writers (auth-creating writes; do carefully w/ email validation — see task #24). All Slack machinery (install/Q&A/proactive/approvals/orders) now deployed; live e2e still gated on George registering the Atlas app.
- After publish (deferred): Slack AI employee (Viktor-style) — PLAN WRITTEN:
docs/claude/atlas-slack-plan.md(decisions locked 2026-06-23: PHI hybrid = no-PHI default + per-clinic attestation toggle; surface = operational command center to run the clinic from Slack, channel or DM; shared multi-workspace app). Build is ops-first, 6 phases, includes a tool-gap buildout (orders/fulfillment, invite client, invite staff, clinic reports). Reuses orchestrate_query + execute_copilot_action + observation/heartbeat producers. + growth detectors on the obs engine; client-facing "talk to your record"; continuous-learning loop; observation auto-resolve for atlas insights;lastMessageAt-indexed inbox query (replace per-client getDoc scan in DashboardMessagesCard); deep-link inbox rows to/messages?chat=; dashboardClinicFulfillmentWidgettype fix.
Key gotchas (hard-won)
- Decorator displacement: inserting a
defimmediately above a decorated fn silently moves the decorator → fn de-registers → 404 + silent no-op deploys. Verify withgcloud functions describe NAME --gen2 --region=us-central1or curl; don't trust "Deploy complete". - New Gen2 fns start PRIVATE → re-grant
allUsersinvoker for self-authing public endpoints. - Cloud Run throttles CPU after response → don't do LLM work in fire-and-forget daemon threads; run inline within the request (e.g., staff fact-extraction, grounding gate).
- Big-codebase cold start can fail new on_call healthchecks → prefer extending warm functions/dispatchers/triggers over new standalone callables (e.g., categorize hooked into
chat_unread_counter; autonomous review viaheartbeat_worker). /messagespage andChatDrawerare separate composers — features must be added to both.- Confirm cards:
via:'copilot'→execute_copilot_action; else →clinic_service. Drafts stamped session_id/request_id; cards pin to the message.