Skip to content

Backend

Internal

This page describes implementation details for the GiveCare development team.

GiveCare's backend runs on Convex — a reactive database with built-in serverless functions1. The backend manages users, messages, assessments, eligibility, loops, and safety flags.

Core tables

Table Purpose Key fields
users Caregiver profiles phone, name, state, consent status, bootstrap stage, current loop
messages SMS conversation history userId, direction (inbound/outbound), content, timestamp, loop, riskLevel
assessments Instrument responses and scores userId, instrumentId, responses, zoneScores, compositeScore, confidence
eligibility Benefits matching results userId, programId, eligible (boolean), matchedZones, surfacedAt, status
loops Conversational loop state userId, loopName, stage, enteredAt, exitedAt, metadata
safety_flags Crisis detection records userId, tier (distress/imminent/continuity), triggeredAt, resolvedAt, reason

Schema relationships

erDiagram
    users ||--o{ messages : "sends/receives"
    users ||--o{ assessments : "completes"
    users ||--o{ eligibility : "matched to"
    users ||--o{ loops : "participates in"
    users ||--o{ safety_flags : "may trigger"
    assessments }o--|| users : "scored for"
    eligibility }o--|| users : "checked for"

Data agent services

The backend exposes services consumed by the SMS pipeline and scheduler:

Message handling

  • ingestMessage — Receives inbound SMS, runs risk assessment, stores message, returns risk level
  • sendMessage — Validates outbound message, stores in history, dispatches via SMS provider
  • getConversationHistory — Returns recent messages for a user (used in prompt assembly)

Assessment services

  • submitAssessment — Stores instrument responses, computes zone scores, updates composite GiveCare Score
  • getLatestScores — Returns most recent zone scores and composite for a user
  • detectSpike — Compares current score to previous, returns spike type if threshold exceeded

Benefits services

  • checkEligibility — Evaluates eligibility rules against user profile for a set of programs
  • surfaceBenefit — Records that a program was presented to the user
  • updateBenefitStatus — Tracks application progress (surfaced, applied, approved, denied)

Loop management

  • getCurrentLoop — Returns the user's active loop and stage
  • transitionLoop — Moves user to a new loop, recording exit from previous
  • advanceStage — Moves user to the next stage within their current loop

Safety services

  • createSafetyFlag — Records a new safety flag with tier and trigger context
  • resolveSafetyFlag — Marks a flag as resolved with reason
  • getActiveSafetyFlags — Returns unresolved flags for a user

Scheduler

The scheduler drives proactive outreach. It runs on a periodic tick (configurable interval, default 15 minutes) and determines which users need attention.

Tick flow

flowchart TD
    A["Scheduler tick"] --> B["List eligible users"]
    B --> C["For each user: arbiter routing"]
    C --> D{"Action needed?"}
    D -->|Yes| E["Select loop + objective"]
    D -->|No| F["Skip"]
    E --> G["Execute turn"]
    G --> H["Send outbound SMS"]

Eligibility filters

The scheduler evaluates each user against:

Filter Condition
Consent Must have active consent
Quiet hours Respect user's timezone, no messages during sleeping hours
Rate limit Maximum messages per day per user
Active safety flag If imminent tier active, route to continuity loop
Pending assessment If scheduled assessment due, route to assessment loop
Spike detection If score change exceeds threshold, route to proactive loop
Benefit follow-up If surfaced benefit has no status update, route to benefits loop

Arbiter routing

The arbiter is the decision layer that determines which loop a user enters on each tick. Priority ordering:

  1. Safety — Active safety flags always take precedence
  2. Assessment — Scheduled instruments that are due
  3. Benefits — Flagged zones with unsurfaced programs
  4. Proactive — General check-ins and follow-ups

Convex-specific patterns

Reactive queries

Convex queries are reactive — the admin dashboard and diagnostic tools subscribe to live data. When a safety flag is created, the dashboard updates in real time without polling.

Mutations and actions

  • Mutations are deterministic database writes (message storage, flag creation, loop transitions)
  • Actions are non-deterministic operations (calling the language model, sending SMS via provider, running risk assessment)

The SMS turn is an action that calls mutations internally — it reads state, calls the model, and writes the result back to the database.

Codegen

Convex types are generated from the schema definition. Run pnpm --filter @givecare/data-agent codegen to regenerate types after schema changes. The _generated/ directory is gitignored.

Code reference

Backend source in apps/data-agent/convex/. Key files:

Path Purpose
schema.ts Table definitions
scheduler/tick.ts Scheduler tick implementation
loops/scheduledDispatcher.ts Arbiter routing logic
sms/ingest.ts Inbound message handling
sms/send.ts Outbound message dispatch
assessments/ Instrument scoring and storage
safety/ Crisis detection and flag management
benefits/ Eligibility checking and surfacing

See the SMS Journey for the full message flow and Crisis Routing for safety tier details.


  1. GiveCare internal. "Backend Specification." Source →