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:
The product invariant is:
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 puretransitionLooppolicy indomain/care/loop.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 <-> paused, thencompleteor staleabandoned) 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
evidenceQuotefrom 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
ReasonResultProposedReplyProposedIntent- 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
AgentTooldefinitions - final
ReasonResultTypeBox 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 OpenAI Pi provider.
agent/model.ts selects GPT-6 Luna in code with one shared prompt cache key.
OPENAI_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.
Care playbooks in agent/skills.ts supply static purposes and boundaries,
rendered once into the system prompt. Mira chooses which fits the request.
There is no skill-loading tool or per-turn skill-use receipt. Playbooks carry
no caregiver state, current external facts, permissions, or safety authority.
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:
benefitsresourcesstrategiesguidance
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.registryprojects the benefits search bundle.gc-wiki.knowledgeprojects resources, strategies, and reviewed guidance.tools.assessmentsprojects the assessment instruments.evals.datasetprojects 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¶
- The signed Twilio webhook passes its posted message fields directly to
convex/turns.ts#receiveInbound, which records durable inbound state. evaluateGatehandles hard boundaries before model reasoning.- Boundary outcomes commit immediately.
- Allowed turns enqueue
convex/mira.ts#runTurn. convex/context.ts#assembleForMirasupplies the pending input burst, every active memory and outcome candidate, the SMS thread (up to 20 earlier messages: what the caregiver said, except messages whose remembered fact was deleted, and what Mira delivered), and the source-linked GiveCare trajectory. Pi reads the thread directly as prior messages; no relevance judgment runs before Pi. Native outcomes retain their sources without a copied memory row.agent/run.ts#runMiraAgentTurnruns Pi Agent Core with approved tools (search,search_nearby_care,final_reason_result). After two completed provider requests,prepareNextTurnexposes onlyfinal_reason_result, which reserves one result request and one repair request. A nearby-care lookup records typed evidence of what the call returned, not provider absence or availability. Pi writes the reply itself, offering an alternative category or ZIP.- Pi must call the single-assignment
final_reason_result. Code checks shape and exact references before Jev. Invalid proposals receive field, reference, or assertion feedback for bounded repair; code does not delete substantive sentences. Tool-free normal and token-limit stops get a final-only follow-up within the same request and time limits. Only a validated result can terminate successfully. turns.commitAgentReasonResultverifies 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:
POST /api/signupstrictly 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.- Component-backed limits apply to hashed IP and phone keys.
- A supplied sponsor code must resolve to an active organization with capacity. Referral codes remain reported attribution.
- 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.
- One Convex mutation creates the caregiver, active SMS channel, conversation, outbound message, consent/signup events, email contact, and
welcomeoutbox row; optional GiveCare subscription, organization count, and handoff redemption commit atomically with them. It schedules one transactional signup receipt after commit. - Existing phone enrollment is a no-op; it does not overwrite identity or reactivate stopped consent.
- Transport re-checks consent and safety before delivery and blocks stale welcome work after 15 minutes.
- 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. Convex opens a support view only from exact chip text or its typed label, before any model call; 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, including Twilio Advanced
Opt-Out
OptOutTypewhen the carrier already replied - 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 -> input judge (message)
-> reason (floored) -> reply judge (check) -> commit
domain/assessments/protocol.ts owns assessment decisions over the whole
unanswered burst. convex/assessmentRuns.ts supplies trusted state and scoring.
routeTurn checks consent and turn order before committing exact assessment or
support-menu protocol, or reserving an attempt. Duplicate workers cannot spend
another admission slot or make concurrent model calls for that attempt.
One Jev request reads the caregiver's message before Pi drafts anything: a
safety route, the concrete-question, check-back, and no-request signals,
whether the caregiver says Mira got the prior exchange wrong, and, when a
typed assessment offer is still open, assent to it. A named current care
problem seeking help now counts as a concrete question even beside a yes to an
earlier offer. An offer's chip sends its accept keyword directly, so tapping
starts the assessment without a model call; a typed "yes" goes to Pi, which
proposes starting the assessment, and verification checks that assent and
current state before the start commits. This reading is frozen for the turn:
it gates which schema Pi may propose (whether an assessment, check-back, or
loop intent is on the table) and it floors the draft's risk before the reply
is checked, so a later check cannot lower a safety floor the message already
set. An unavailable or invalid reading ends the turn with judge_unavailable
before any Pi request.
A second Jev request, after Pi's draft, checks only the reply: its claims,
clinical and legal authority, actor, effect, self-hedging, and intent support.
Missing required reply evidence ends the turn with judge_unavailable.
Each effect is read independently and follows its declared failure policy.
Mira chooses which care playbook fits the request herself, from playbooks
rendered once into the system prompt; there is no move ranking, prerequisite
classifier, or planner. Routine fact extraction does not run every turn.
Benefits screening resolves the fields its matched programs use, including
corrections to populated fields. search reads the current message and earlier
caregiver statements in the relevance request, then screens with the resolved
view. Later tools and reply verification share that view. An exact answer to a
delivered question applies before Pi and can persist without another search.
Earlier statements inform that reading but are not stored;
only an observation from the current message can commit. A recipient-subject
fact (an age, a diagnosis) carries the named recipient — Dad, Mom — so one
recipient's facts never screen or answer for another; without a named
recipient only unlabeled facts apply. The one search tool queries benefits,
resources, guidance, and strategies in parallel and reranks all of them
together with one relevance judgment after retrieval; a failed relevance
judgment returns the unranked results rather than none. 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
and screening; valid fields remain usable, and a missing field judgment
falls back to the already-known facts rather than ending the turn.
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. jev/ 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.
Exact protocol follows the deterministic policy gate. Other allowed text receives the bounded Jev input reading before support admission. Per-caregiver/global windows bound Pi 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. A quota refusal uses the recorded safety floor when present; otherwise repeated wait notices are suppressed. The quota cannot label an unread message ordinary.
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 offers an assessment only when code finds the gap (a missing baseline or an eligible targeted area, outside the offer cooldown) and the score would change the help now. The caregiver starts it: a command, or assent to the delivered offer. Convex rejects any other start. Convex owns the exact questions, observed answers, scores, and eligibility. A targeted GC-SDOH-30 refinement requires a valid baseline and eligible low domain, without an intervening support turn. 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.