Document Version: 1.0
Last Updated: January 2026
Status: Draft (2026 Proposal)
Authors: Nightscout Community
Created: 2026-01-01
This RFC proposes a clean separation between control plane (policy, configuration, intent) and data plane (observations, telemetry, delivery) for Nightscout and compatible automated insulin delivery (AID) systems like Loop, Trio, and AAPS.
The goal is to enable agentic collaboration—where AI agents, caregivers, and automation systems can safely participate in therapy management alongside the primary controller—while maintaining MDI (manual insulin delivery) as an always-valid fallback.
- Motivation
- Design Principles
- Architecture Overview
- Core Data Model
- API Design
- Bridge Mode: Legacy Compatibility
- Multi-Writer Semantics & Conflict Resolution
- Security & Authority Model
- Implementation Phases
- Integration Questions for Loop/AAPS/Trio
- Appendix: JSON Schemas
- Profiles as monolithic blobs — Entire profile uploaded on every change; no versioning, content-hashing, or stable identifiers
- Overrides buried in devicestatus — Temporary adjustments embedded in controller snapshots rather than discrete, auditable events
- No "effective policy" view — No materialized representation of "what parameters are actually in force right now"
- Implicit authority — No distinction between human intent, controller automation, and delegated agent actions
- Intent vs. reality gap — Difficult to distinguish suggested actions from requested commands from confirmed delivery
For AI agents to safely assist with therapy management (reconciling hormone cycles, activity levels, geolocation, stress indicators), they need:
- Clear authority boundaries — What can an agent suggest vs. activate?
- Audit trails — Who changed what, when, and why?
- Composable overrides — Layer multiple adjustments without conflicts
- Real-time policy state — What's actually in force right now?
- Delivery verification — Did the suggested action actually happen?
- Config vs. Runtime vs. Computed — Separate user-authored configuration from runtime activations from computed effective state
- Events over Snapshots — Append-only event streams with cursor-based sync, not mutable state blobs
- MDI as First-Class — Manual injections are DeliveryObservations from a human source; the system never assumes automation
- Authority Hierarchy — Human > Agent > Controller for conflict resolution
- Bridge Compatibility — Synthesize canonical events from legacy devicestatus uploads
- Neutral Control Plane — Nightscout stores intent and policy; controllers execute
┌─────────────────────────────────────────────────────────────────────────────┐
│ NIGHTSCOUT │
│ (Neutral Control Plane) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌───────────────────┐ │
│ │ CONFIG OBJECTS │ │ RUNTIME EVENTS │ │ COMPUTED STATE │ │
│ │ │ │ │ │ │ │
│ │ • ProfileDefinition │ │ • ProfileSelection │ │ • PolicyCompos- │ │
│ │ • OverrideDefinition │ │ • OverrideInstance │ │ ition │ │
│ │ • ControllerKind │ │ • DeliveryRequest │ │ • CapabilitySnap- │ │
│ │ Definition │ │ • DeliveryObserv- │ │ shot │ │
│ │ │ │ ation │ │ │ │
│ └──────────────────────┘ │ • Reconciliation │ └───────────────────┘ │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ EVENT STREAM │ │
│ │ cursor-ordered, append-only, per-issuer sequencing │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ DATA PLANE │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌───────────────────┐ │
│ │ ENTRIES │ │ TREATMENTS │ │ DEVICESTATUS │ │
│ │ (CGM readings) │ │ (carbs, insulin) │ │ (legacy blob) │ │
│ └──────────────────────┘ └──────────────────────┘ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LOOP │ │ TRIO │ │ AAPS │
│ (Controller) │ │ (Controller) │ │ (Controller) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ AI AGENT │ │ CAREGIVER │ │ HUMAN │
│ (delegated) │ │ (remote) │ │ (primary) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
All state changes are wrapped in an Event Envelope for consistent ordering, replay, and audit.
EventEnvelope:
eventId: string # Stable UUID
eventType: string # e.g., "profile.definition.created", "override.instance.activated"
cursor: integer # Server-assigned monotonic global ordering
issuer: string # Controller/user/agent identifier
issuerSeq: integer # Monotonic sequence per issuer
idempotencyKey: string # For retry deduplication
timestamp: datetime # ISO 8601
refs: # Referenced object IDs/hashes
- refType: string
refId: string
payload: object # The actual event dataEvent Types:
| Category | Event Types |
|---|---|
| Profile | profile.definition.created, profile.definition.updated, profile.selection.changed |
| Override | override.definition.created, override.instance.activated, override.instance.ended, override.instance.superseded |
| Policy | policy.composition.computed |
| Delivery | delivery.requested, delivery.observed, delivery.reconciled |
| Capability | controller.registered, capability.snapshot.updated |
These are user-authored, versioned, addressable objects.
ProfileDefinition:
profileId: string # Stable identifier
contentHash: string # SHA-256 of canonicalized content
title: string # Human-readable name
timezone: string # IANA timezone
units: "mg/dL" | "mmol/L"
schedules:
basal: # Time-based basal rates
- time: "HH:MM"
rate: number # U/hr
isf: # Insulin sensitivity factor
- time: "HH:MM"
value: number
cr: # Carb ratio
- time: "HH:MM"
value: number
target: # Target glucose ranges
- time: "HH:MM"
low: number
high: number
insulinModel:
type: "rapid" | "fiasp" | "lyumjev" | "custom"
dia: number # Duration of insulin action (hours)
peakTime: number # Minutes to peak
createdBy:
issuerType: "human" | "controller" | "agent"
issuerId: string
createdAt: datetime
legacyProfileName: string # For backward compatibility mappingOverrideDefinition:
definitionId: string
overrideType: "exercise" | "sleep" | "preMeal" | "illness" | "highActivity" | "hormones" | "custom"
title: string
defaultDuration: integer # seconds, null = indefinite
effects:
targetRange:
low: number
high: number
targetDelta: number # mg/dL adjustment to existing target
basalMultiplier: number # 1.0 = no change, 0.5 = 50%
maxBasalCeiling: number # U/hr cap
sensitivityMultiplier: number
carbRatioMultiplier: number
automationAggressiveness: number # 0.0 - 1.0 if controller supports
createdBy:
issuerType: "human" | "controller" | "agent"
issuerId: string
createdAt: datetimeThese represent concrete activations and intents.
ProfileSelection:
selectionId: string
selectedProfileId: string
selectedProfileHash: string # For verification
effectiveAt: datetime
selectedBy:
issuerType: "human" | "controller" | "agent"
issuerId: string
authority: "primary" | "delegated" | "automated"
reason: string # Optional annotationOverrideInstance:
instanceId: string
definitionId: string # Optional - may be ad-hoc
start: datetime
duration: integer # seconds, null = indefinite
end: datetime # Computed or explicit
effectiveEffects: # Resolved effects (may differ from definition)
targetRange:
low: number
high: number
basalMultiplier: number
sensitivityMultiplier: number
carbRatioMultiplier: number
requestedBy:
issuerType: "human" | "controller" | "agent"
issuerId: string
authority: "primary" | "delegated" | "automated"
status: "active" | "ended" | "canceled" | "superseded"
supersededBy: string # instanceId of superseding override
reason: string # Why this override was activated
annotations: object # Extensible metadataThese are materialized views computed by Nightscout from events.
PolicyComposition:
compositionId: string
references:
profileId: string
profileHash: string
activeOverrideInstanceIds: [string]
capabilitySnapshotId: string
effectiveParameters:
targetRange:
low: number
high: number
effectiveISF: number
effectiveCR: number
effectiveBasal: number # Current scheduled rate after multipliers
maxBasalAllowed: number
maxBolusAllowed: number
automationEnabled: boolean
computedBy:
controllerKind: string
controllerVersion: string
computedAt: datetime
validFrom: datetime
validTo: datetime # null = current
cursor: integer # For orderingThese complete the intent → action → confirmation loop.
DeliveryRequest:
requestId: string
requestType: "tempBasal" | "bolus" | "suspend" | "resume"
parameters:
rate: number # U/hr for temp basal
units: number # Units for bolus
duration: integer # seconds for temp basal
requestedBy:
issuerType: "human" | "controller" | "agent"
issuerId: string
basedOn:
policyCompositionId: string
algorithmSuggestion: object # Optional: the suggestion that led to this
requestedAt: datetime
expiresAt: datetime # Request is stale after thisDeliveryObservation:
observationId: string
observationType: "basal" | "bolus" | "suspend" | "injection" | "pen"
source:
sourceType: "pump" | "manual" | "pen" | "inhaler"
sourceId: string # Device identifier
sourceKind: string # "omnipod" | "medtronic" | "tandem" | "pen"
observed:
rate: number
units: number
duration: integer
startTime: datetime
endTime: datetime
confidence: "confirmed" | "inferred" | "reported"
reportedBy:
issuerType: "human" | "controller" | "agent"
issuerId: string
observedAt: datetime
pumpResponse:
acked: boolean
errorCode: string
errorMessage: stringReconciliation:
reconciliationId: string
requestId: string # The DeliveryRequest
observationId: string # The DeliveryObservation
outcome: "matched" | "partial" | "blocked" | "unknown" | "expired"
discrepancy:
requestedUnits: number
deliveredUnits: number
delta: number
reason: string # "capped_by_limit" | "comm_failure" | "user_canceled" | "pump_error"
reconciledAt: datetime
reconciledBy:
issuerType: "controller" | "agent"
issuerId: stringFor "digital twin honesty"—knowing what the controller can actually do right now.
ControllerKindDefinition:
kindId: string # "loop" | "trio" | "aaps" | "openaps"
version: string
supportedFeatures:
tempBasal: boolean
microBolus: boolean
suspend: boolean
overrides: boolean
autoSens: boolean
dynamicISF: boolean
dynamicCR: boolean
smbWithCOB: boolean
uam: boolean
supportedPumps: [string]
supportedCGMs: [string]
eventCapabilities:
canEmitNativeEvents: boolean
minimalEventSet: [string] # Event types it can emitControllerInstanceRegistration:
instanceId: string
kindId: string
version: string
device:
deviceId: string
platform: "ios" | "android" | "linux"
model: string
pumpBinding:
pumpKind: string
pumpSerial: string
connectedSince: datetime
cgmBinding:
cgmKind: string
cgmId: string
registeredAt: datetime
lastSeenAt: datetimeCapabilitySnapshot:
snapshotId: string
controllerInstanceId: string
connectivity:
pumpConnected: boolean
pumpLastContact: datetime
cgmConnected: boolean
cgmLastReading: datetime
automationState:
closedLoopEnabled: boolean
suspended: boolean
suspendReason: string
effectiveLimits:
maxBasal: number
maxBolus: number
maxIOB: number
health:
reservoirUnits: number
batteryPercent: number
cgmCalibrationStatus: string
timeSyncHealth: "good" | "drift" | "unknown"
snapshotAt: datetime| Collection | Operations | Description |
|---|---|---|
/profileDefinitions |
CRUD + history | User-authored profile configs |
/profileSelections |
CRUD + history | Profile activation events |
/overrideDefinitions |
CRUD + history | Reusable override templates |
/overrideInstances |
CRUD + history | Concrete override activations |
/policyCompositions |
Read + history | Computed effective policy (read-only) |
/deliveryRequests |
CRUD + history | Delivery intent records |
/deliveryObservations |
CRUD + history | Confirmed delivery records |
/reconciliations |
Read + history | Request/observation matching |
/controllerRegistrations |
CRUD + history | Controller instance registry |
/capabilitySnapshots |
CRUD + history | Controller capability state |
/events |
Read + subscribe | Unified event stream |
GET /api/v3/events?cursor={lastCursor}&eventTypes={types}&issuers={ids}
Returns events after the given cursor, optionally filtered by type and issuer.
WS /api/v3/events/subscribe
SSE /api/v3/events/stream?cursor={cursor}
Real-time event delivery with cursor-based resumption.
For controllers that continue uploading devicestatus blobs, Nightscout synthesizes canonical events.
devicestatus upload
│
▼
┌──────────────────────┐
│ Parse devicestatus │
│ • Extract profile │
│ • Extract overrides │
│ • Extract pump │
│ • Extract suggested │
│ • Extract enacted │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Diff against last │
│ known state │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Emit events: │
│ • ProfileDefinition │ (if profile content hash changed)
│ • ProfileSelection │ (if active profile changed)
│ • OverrideInstance │ (if override state changed)
│ • PolicyComposition │ (always, as snapshot)
│ • DeliveryObserv- │ (if enacted present)
│ ation │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Store devicestatus │
│ with bridge refs │
└──────────────────────┘
- Profile hashing: Canonicalize (sort keys, normalize units/timezone), then SHA-256
- Override diffing: Compare active override state; emit
activated/endedas needed - Delivery extraction: Map
enactedtoDeliveryObservation,suggestedto metadata - Idempotency: Use
(issuer, issuerSeq)or(devicestatus._id, field)for dedup
Nightscout accepts inputs from:
- Controller app (primary automation)
- Caregiver app (remote monitoring/intervention)
- AI agents (delegated assistance)
- Manual UI (user-initiated)
- Bridge (synthesized from legacy uploads)
HUMAN (primary)
│
├── HUMAN (caregiver, delegated)
│
├── AGENT (delegated)
│
└── CONTROLLER (automated)
-
Override composition: Multiple active overrides are composed into PolicyComposition; conflicts resolved by:
- Most restrictive target range
- Lowest basal multiplier (safety bias)
- Human > Agent > Controller authority
-
Supersession: A new override of the same type from equal or higher authority supersedes the previous
-
Flip-flop prevention:
- Rate limiting per issuer (max N changes per time window)
- Cooldown period after override end before same type can be activated
- Agent-initiated overrides require human confirmation if >N in period
-
Profile selection: Most recent selection wins; PolicyComposition always references current selection
| Issuer Type | Identity Mechanism |
|---|---|
| Human | OAuth identity (Nightscout account) |
| Controller | Device-bound API key + device attestation |
| Agent | OAuth + scoped delegation token |
| Caregiver | OAuth + explicit delegation grant |
Scopes:
read:
- entries.read
- treatments.read
- policy.read
- delivery.read
suggest:
- override.suggest # Can propose, human must approve
- delivery.suggest
activate:
- override.activate # Can directly activate
- profile.select
deliver:
- delivery.request # Can issue delivery requests
admin:
- controller.register
- delegation.grantDelegationGrant:
grantId: string
grantedBy: string # Human issuer ID
grantedTo: string # Agent/caregiver issuer ID
scopes: [string]
constraints:
maxOverrideDuration: integer
allowedOverrideTypes: [string]
requireConfirmation: boolean
expiresAt: datetime
grantedAt: datetime
revokedAt: datetime- All events are append-only
- Events include
issuer,authority,timestamp - Optional: issuer-signed events with device keys
- Optional: hash chain per issuer for tamper evidence
Goal: Core event model and minimal collections
- EventEnvelope schema and storage
- ProfileDefinition collection with content hashing
- OverrideInstance collection
- PolicyComposition collection (computed)
- Basic bridge from devicestatus
- Cursor-based event polling endpoint
- SSE subscription (before WebSocket)
Deliverables:
- New API v3 collections operational
- Legacy devicestatus → event synthesis working
- Clients can poll for events
Goal: Complete intent-to-delivery loop
- DeliveryRequest / DeliveryObservation / Reconciliation
- ControllerKindDefinition / ControllerInstanceRegistration
- CapabilitySnapshot
- Enhanced bridge for delivery extraction
- WebSocket subscriptions
Deliverables:
- Full delivery tracking operational
- Controller capability awareness
- Real-time subscriptions
Goal: Safe multi-writer with agents
- Authority scopes and delegation grants
- Agent identity and authentication
- Conflict resolution rules
- Rate limiting and flip-flop prevention
- Confirmation workflows for agent suggestions
- Audit dashboard
Deliverables:
- Agents can suggest overrides
- Caregivers can delegate to agents
- Full audit trail
See integration-questionnaire.md for the complete questionnaire.
A) Profiles & Overrides
- Do you have stable profile identifiers beyond name?
- Can you represent overrides as template vs. activation?
- What override dimensions exist (target, sensitivity, basal, CR)?
- How do you resolve multiple concurrent overrides?
B) Composition 5. Do you compute an explicit "effective therapy settings" internally? 6. Can you emit: profile hash + override IDs + effective parameters?
C) Delivery Fidelity 7. Can you distinguish suggested vs. requested vs. confirmed? 8. Do you surface pump ACK/NAK/error codes? 9. How do you represent "capped by limits" vs. "comm failure"?
D) Timing & Ordering 10. Can you provide monotonic per-controller sequencing? 11. How do you handle offline batching?
E) Minimal Commitment 12. What's the smallest native event set you'd emit first?
Full JSON Schema (draft-2020-12) files are available in the schemas/ directory:
- event-envelope.schema.json
- profile-definition.schema.json
- profile-selection.schema.json
- override-definition.schema.json
- override-instance.schema.json
- policy-composition.schema.json
- delivery-request.schema.json
- delivery-observation.schema.json
- reconciliation.schema.json
- controller-kind-definition.schema.json
- controller-instance-registration.schema.json
- capability-snapshot.schema.json
| Date | Change |
|---|---|
| 2026-01-01 | Initial draft |