Skip to content

GiveCare SMS Runtime Contract

Diátaxis: reference

This reviewed document is the canonical public contract for the current SMS runtime. Cross-repo consumers use its exact gc-sms.care care.runtime.project owner projection, pinned to a commit through the root projection-ref command. They do not infer current behavior from source files.

GiveCare SMS is organized around a small runtime kernel:

Inbound -> Gate -> Context -> Reason -> Commit

The product invariant is:

The model may propose; only the harness may make reality true.

State-Machine Kernel

The kernel above is one instance of a broader rule: every durable concern in this runtime is a revisioned state machine advanced by a pure transition policy, not a process that keeps running on its own.

  • One state machine per concern. Turns (received -> reasoning -> committed -> review -> failed), loops (open -> blocked -> closed -> dismissed, via the pure transitionLoop policy in domain/reasonResult.ts), outbox rows (queued -> sending -> accepted -> delivered -> undelivered -> failed -> blocked), reviews (open -> resolved), memories (active -> superseded -> deleted), consent (active -> paused -> stopped, with the safety hold an independent timestamp beside it), and assessment runs (in_progress -> complete) are each one row with an enumerable set of legal transitions.
  • Gates are transition guards. A gate does not exist to classify a message; it exists to decide whether a transition may happen at all.
  • Evidence is the price of a transition. A loop or memory intent must carry an evidenceQuote from one message in the current pending input burst. Memory provenance retains that message's source turn. Source references must come from tool evidence observed in the same reasoning span. See Commit Rules below for enforcement.
  • Events are history, not state. Append-oriented event rows explain how a row got here; nothing reads them back as the source of truth for what happens next.
  • Observed action changes. A caregiver action advances only from new observed evidence. There are no action reminders or fixed assessment invitations. Mira may direct a needed assessment in an active conversation. Convex owns bounded transport jobs and checks consent before each send.

Design test for any new feature: what row, what transitions, what guard, what evidence, what revision? A feature that cannot answer all five does not yet fit this kernel.

Layers

Domain Layer

domain/ defines the product contract in plain TypeScript:

  • inbound shape
  • boundary decisions
  • context shape
  • ReasonResult
  • ProposedReply
  • ProposedIntent
  • deterministic gates
  • validation rules

This layer should not depend on Convex or Pi. It is the shared contract the runtime must preserve.

Agent Layer

agent/ adapts Pi Agent Core to the GiveCare contract.

It owns:

  • the Mira system prompt
  • approved AgentTool definitions
  • final ReasonResult TypeBox parameters
  • redacted Pi event trace capture
  • provider-request, tool-call, and total-span bounds
  • final result validation before returning to Convex

The live Convex action registers only the Cerebras Pi provider. agent/cerebras.ts selects Qwen 3.8 27B in code and supplies its strict tool schemas. CEREBRAS_API_KEY supplies authentication. The bakeoff may register other providers inside an isolated evaluation runtime with outbox dispatch disabled.

Single-provider is a deliberate refusal, not a gap: a failover path would add a second model contract to verify (prompt parity, tool parity, safety-parity evidence) for an outage mode Twilio/Convex incidents have not yet produced. Model changes go through the bakeoff screen instead. Revisit only when a real provider outage or a bakeoff-proven second model creates the need.

It does not own memory, scheduling, outbox, consent, transport, proof, or learning promotion.

Caregiver skills are versioned, static procedures inside this layer. The model may load at most one reviewed skill from agent/careSkills.ts during a reasoning span. A skill organizes questions and actions; it contains no caregiver state, current external facts, permissions, or safety authority. Skill use is emitted as versioned reasoning evidence so scenario outcomes and operator review can distinguish the procedure from the model and prompt around it.

eval/scenarioRunner.ts and eval/scenarioKernel.ts seed isolated Convex state and compose the production gate, context, Pi turn, commit, fallback, review, and outbox path for private scenario trees. State commits are local to the evaluation instance and outbox dispatch is disabled. This is an evaluation adapter over the runtime, not a second product commit path.

Convex Layer

convex/ owns durable reality:

  • caregiver and channel state
  • conversations, turns, messages
  • memory and loop commits
  • assessment runs and score snapshots
  • events and outbox
  • durable operator reviews
  • operator/proof projections
  • revisioned partner-health snapshots
  • scheduling the Pi action
  • validating and applying proposed intents

Convex is not the entire domain harness by itself, but it hosts and enforces most of the harness.

Owner Projection Layer

knowledge/ adapts generated JSON bundles into runtime search contracts:

  • benefits
  • resources
  • strategies
  • guidance

All generated inputs are downstream artifacts. Each source owner commits its projection to Git. Each gc-sms consumer resolves a pinned owner-projection ArtifactRef for an exact owner commit through the shared GiveCare protocol's projection-ref command, verifies the artifact digest, validates the owner schema, and replaces only fixed local files atomically.

  • gc-benefits.registry projects the benefits search bundle.
  • gc-wiki.knowledge projects resources, strategies, and reviewed guidance.
  • tools.assessments projects the assessment instruments.
  • evals.dataset projects the public cases used by the retrieval quality gate.

These consumers are declared as owner-projection-sync adapters in .givecare/module.json. They do not import another repository, fetch live owner data, accept an unverified file path, or maintain another source of truth.

Main Flow

  1. The signed Twilio webhook passes its posted message fields directly to convex/turns.ts#receiveInbound, which records durable inbound state.
  2. evaluateGate handles hard boundaries before model reasoning.
  3. Boundary outcomes commit immediately.
  4. Allowed turns enqueue convex/mira.ts#runTurn.
  5. convex/context.ts#assembleForMira supplies the last committed goal and exchange, delivery status, pending input burst, bounded memory/outcome candidates, and the source-linked GiveCare trajectory. The inbound Jev request judges candidate relevance before Pi receives up to three selected evidence items. Native outcomes retain their sources without a copied memory row.
  6. agent/piRunner.ts#runMiraAgentTurn runs Pi Agent Core with approved tools. After two completed provider requests, prepareNextTurn exposes only final_reason_result, which reserves one result request and one repair request. One optional memory recall remains caregiver-scoped inside Convex, and one optional nearby-place result remains typed for deterministic rendering.
  7. Pi must call the single-assignment final_reason_result; one invalid result may receive the reserved in-span repair, and only a validated result can terminate successfully.
  8. turns.commitAgentReasonResult verifies and commits. New-memory comparisons share the reply-validation request to detect changes that require an explicit correction. Semantic matches cannot suppress new observations; only identical same-source proposals reuse a row. Convex rereads compared evidence before acting. Semantic or contract failure recovers immediately; only a transient action or expired reasoning lease gets one retry before recovery and durable review.

Signup Flow

Web enrollment is a separate adapter, not an inbound turn or onboarding workflow:

  1. POST /api/signup strictly validates the phone against its numbering plan, normalizes it to E.164, and validates first name, email, explicit SMS consent, a separate optional GiveCare-update opt-in, and bounded attribution.
  2. Component-backed limits apply to hashed IP and phone keys.
  3. A supplied sponsor code must resolve to an active organization with capacity. Referral codes remain reported attribution.
  4. A supplied assessment handoff imports its reading only when unused, unexpired, and bound to the normalized signup email; otherwise enrollment proceeds without the reading and the drop is recorded as an event.
  5. One Convex mutation creates the caregiver, active SMS channel, conversation, outbound message, consent/signup events, email contact, and welcome outbox row; optional GiveCare subscription, organization count, and handoff redemption commit atomically with them. It schedules one transactional signup receipt after commit.
  6. Existing phone enrollment is a no-op; it does not overwrite identity or reactivate stopped consent.
  7. Transport re-checks consent and safety before delivery and blocks stale welcome work after 15 minutes.
  8. The welcome explains the care-support aim and offers an introduction, help, or a starting point. Taps and typed messages enter the same SMS flow. Jev selects support navigation within the shared care request; code renders the view from current state. Specific needs go straight to ordinary care reasoning. Mira learns care context gradually. Score views show stored readings and next choices; requesting a score alone never starts questions.

First-Party Web Adapter

The active web properties keep three narrow contracts: event capture, one named-publication opt-in, and transactional email delivery. Inputs are origin-restricted, rate-limited, allowlisted, and bounded. Transactional signup/assessment delivery is independent of marketing consent. Each publication unsubscribes independently; bounce and complaint suppress the contact globally. The server derives the native BSFC-s total and published interpretation from submitted answers; the browser cannot supply scoring or local subscales.

This surface is intentionally an adapter around current domain and Convex primitives. It does not add campaign workflows, generic admin actions, or client-authored assessment results. The /api/admin route exposes only an atomic authenticated read of the latest committed population-health snapshot consumed by gc-web; it never fans out over caregivers on request.

Boundary Paths

Boundary decisions are deterministic for:

  • exact STOP/HELP transport-managed SMS responses
  • pause/restart language
  • safety-hold blocking
  • obvious emergency/self-harm/poison/abuse signals
  • identity-sensitive account requests when identity is unverified

The model is used only after the gate returns allow and Convex reserves the reasoning attempt. Exact assessment protocol commits before any model call:

gate -> exact protocol or reservation -> context -> inbound judge
     -> optional assessment assent -> reason -> reply judge -> commit

domain/assessmentProtocol.ts owns assessment decisions over the whole unanswered burst. convex/assessmentRuns.ts supplies trusted state and scoring. routeTurn checks consent and turn order before either committing protocol or reserving an attempt. Duplicate workers cannot spend another admission slot or make concurrent model calls for that attempt.

The inbound request asks for a safety route and the concrete-question, check-back, and no-request signals. It asks for assent only for a relevant delivered typed assessment offer. resolveAssessmentAssent rechecks that offer and current state before committing a start. Shared domain policy blocks both assent and model-selected assessment starts when a caregiver question is pending or the inbound judgment is missing or uncertain.

The inbound request also reads current caregiver facts and selects a care move from code-defined candidates. It uses native memory, commitments, assessments, and reported outcomes. GiveCare owns that state. Jev interprets it. Mira uses the selected move's goal to prepare the draft, lookup, or needed question. Existing action and assessment policy filters the candidates. There is no separate prerequisite classifier or planner.

Benefits read current typed facts from memory and this turn's observations. An assent continuation reuses the first inbound and care result within the same action attempt. Only candidate relevance needs another Jev request after retrieval. Benefit and knowledge lookups share that relevance filter. Code owns field bounds and eligibility arithmetic. Convex validates exact-source field observations before committing them to memory. A bad or uncertain field answer withholds only that field's old known value from the current reply, recall, and screening. Valid fields remain usable. A missing allowed-move or relevance judgment ends the span; safety and reply judgments also remain strict.

Every generated reply sentence is judged in one batch. Convex requires a complete receipt bound to the exact reply, redacted caregiver input, and cited evidence. agent/judgments.ts owns model requests and response validation; domain/judgments.ts owns the product rules over normalized evidence. Missing reply judgments use recovery and review. riskFloor derives urgent or emergency handling from an observed inbound route and preserves it through generation, commit validation, and recovery. A lower-priority proposed route cannot remove an emergency hold. The deterministic safety gate remains independent of Jev.

Earlier calibration results in docs/model-selection.md describe the earlier question contract. They do not validate the current prompts or operating cutoffs. Current receipts and independent labels are required before release.

Safety is enforced at increasing-cost boundaries: deterministic Gate decisions, typed and tool-bounded Reason output, reply/intent verification, and effect-preflighted Commit. Offline gc-bench verification consumes transcripts afterward and never substitutes for the live gate.

The code-coupled hazard and proof contract is in safety-contract.md.

Commit Rules

Commit re-checks channel state before creating an outbound outbox row. This protects against races where a caregiver opts out while Pi is reasoning.

Transport owns delivery state. The committing mutation schedules due outbox work immediately; the one-minute drain is recovery, not the normal delivery path, and fans out each due row as its own send action. Convex mutations atomically move an outbox row from queued to sending before Twilio is called; the action records provider acceptance, and later callbacks reconcile delivered/undelivered/failed status. If a signed callback races ahead of SID attachment, Convex stores it by provider SID and replays it transactionally when acceptance commits. The model never participates in delivery state.

Consent is send-class based. Welcome messages, ordinary replies, deterministic system replies, safety replies, and accepted follow-ups have explicit delivery rules, so enrollment or a same-turn reply does not become blanket follow-up permission. The pure delivery policy returns send, defer, or block; transport is the sole final evaluator and reuses sendAfter for caregiver-local follow-up windows.

Action reminders and fixed assessment invitations are retired; historical rows of those classes cannot send. Two scheduled messages exist, each one consent-checked outbox row: the bounded signup welcome follow-up, and one check-back the caregiver asked for in their own words, sent at the time they named inside their daytime window.

Reply and intent effects are validated together. Effects commit before the reply row. Convex adds truthful receipts for memory correction, deletion, and action closure. An optional low-confidence new memory can be skipped without an operator review. Privacy and correction failures still require review.

Reasoning is admitted only after deterministic policy-gate paths. Per-caregiver/global windows bound model admission, while the Pi adapter independently caps provider tokens, each request's time, four provider requests, tool calls, and the total reasoning-span deadline. The final-result tool permits one in-span repair and never captures an invalid result. The single retry for a transient action or expired lease does not consume a second admission slot; semantic failures recover immediately. Repeated admission notices are suppressed so cost protection does not become SMS spam.

The caregiver loop is deliberately small: one active loop stores the larger objective, one current action, the expected observation, and a stop condition. Observed results update that same row through a pure transition policy; append-only events retain history. Parallel active loops, nested plans, and model-owned workflow state are not allowed.

Mira directs an assessment when care context calls for measurement and the caregiver has capacity. Convex owns the exact questions, observed answers, scores, and eligibility. A targeted GC-SDOH-30 refinement requires a low domain and one ordinary support turn after the structural reading. Current needs interrupt without losing the question. No assessment completion schedules another invitation.

Current Production Gaps

The runtime is not yet a full SMS production service. Missing pieces include:

  • deployed Twilio smoke tests against the exact component and account configuration

Operator notification uses one timestamp-and-creation cursor per source. notifyOperatorWork runs every 15 minutes for open reviews, new safety holds, and blocked loops. sendOperatorAlert advances those cursors only after at least one configured channel accepts the alert (webhook 2xx or Resend enqueue). A failed channel set therefore re-alerts instead of dropping work; channel acceptance is not delivery confirmation.