Skip to content

Latest commit

 

History

History
805 lines (618 loc) · 27.2 KB

File metadata and controls

805 lines (618 loc) · 27.2 KB

RFC: Agentic Control Plane for Automated Insulin Delivery Systems

Document Version: 1.0
Last Updated: January 2026
Status: Draft (2026 Proposal)
Authors: Nightscout Community
Created: 2026-01-01


Abstract

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.


Table of Contents

  1. Motivation
  2. Design Principles
  3. Architecture Overview
  4. Core Data Model
  5. API Design
  6. Bridge Mode: Legacy Compatibility
  7. Multi-Writer Semantics & Conflict Resolution
  8. Security & Authority Model
  9. Implementation Phases
  10. Integration Questions for Loop/AAPS/Trio
  11. Appendix: JSON Schemas

Motivation

Current State Problems

  1. Profiles as monolithic blobs — Entire profile uploaded on every change; no versioning, content-hashing, or stable identifiers
  2. Overrides buried in devicestatus — Temporary adjustments embedded in controller snapshots rather than discrete, auditable events
  3. No "effective policy" view — No materialized representation of "what parameters are actually in force right now"
  4. Implicit authority — No distinction between human intent, controller automation, and delegated agent actions
  5. Intent vs. reality gap — Difficult to distinguish suggested actions from requested commands from confirmed delivery

Why This Matters for Agents

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?

Design Principles

  1. Config vs. Runtime vs. Computed — Separate user-authored configuration from runtime activations from computed effective state
  2. Events over Snapshots — Append-only event streams with cursor-based sync, not mutable state blobs
  3. MDI as First-Class — Manual injections are DeliveryObservations from a human source; the system never assumes automation
  4. Authority Hierarchy — Human > Agent > Controller for conflict resolution
  5. Bridge Compatibility — Synthesize canonical events from legacy devicestatus uploads
  6. Neutral Control Plane — Nightscout stores intent and policy; controllers execute

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                              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)     │
└─────────────────┘         └─────────────────┘         └─────────────────┘

Core Data Model

Event Envelope

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 data

Event 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

Configuration Objects

These are user-authored, versioned, addressable objects.

ProfileDefinition

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 mapping

OverrideDefinition

OverrideDefinition:
  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: datetime

Runtime State Objects

These represent concrete activations and intents.

ProfileSelection

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 annotation

OverrideInstance

OverrideInstance:
  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 metadata

Computed State Objects

These are materialized views computed by Nightscout from events.

PolicyComposition

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 ordering

Delivery Tracking Objects

These complete the intent → action → confirmation loop.

DeliveryRequest

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 this

DeliveryObservation

DeliveryObservation:
  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: string

Reconciliation

Reconciliation:
  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: string

Capabilities Model

For "digital twin honesty"—knowing what the controller can actually do right now.

ControllerKindDefinition

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 emit

ControllerInstanceRegistration

ControllerInstanceRegistration:
  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: datetime

CapabilitySnapshot

CapabilitySnapshot:
  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

API Design

New Collections (API v3)

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

Event Stream Endpoint

GET /api/v3/events?cursor={lastCursor}&eventTypes={types}&issuers={ids}

Returns events after the given cursor, optionally filtered by type and issuer.

WebSocket / SSE Subscriptions

WS /api/v3/events/subscribe
SSE /api/v3/events/stream?cursor={cursor}

Real-time event delivery with cursor-based resumption.


Bridge Mode: Legacy Compatibility

For controllers that continue uploading devicestatus blobs, Nightscout synthesizes canonical events.

Bridge Processing Pipeline

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    │
└──────────────────────┘

Bridge Rules

  1. Profile hashing: Canonicalize (sort keys, normalize units/timezone), then SHA-256
  2. Override diffing: Compare active override state; emit activated/ended as needed
  3. Delivery extraction: Map enacted to DeliveryObservation, suggested to metadata
  4. Idempotency: Use (issuer, issuerSeq) or (devicestatus._id, field) for dedup

Multi-Writer Semantics & Conflict Resolution

Writers

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)

Authority Hierarchy

HUMAN (primary)
   │
   ├── HUMAN (caregiver, delegated)
   │
   ├── AGENT (delegated)
   │
   └── CONTROLLER (automated)

Conflict Rules

  1. 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
  2. Supersession: A new override of the same type from equal or higher authority supersedes the previous

  3. 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
  4. Profile selection: Most recent selection wins; PolicyComposition always references current selection


Security & Authority Model

Identity Model

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

Authority Scopes

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.grant

Delegation Model

DelegationGrant:
  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

Audit Requirements

  1. All events are append-only
  2. Events include issuer, authority, timestamp
  3. Optional: issuer-signed events with device keys
  4. Optional: hash chain per issuer for tamper evidence

Implementation Phases

Phase 1: Foundation (4-6 weeks)

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

Phase 2: Delivery & Capabilities (4-6 weeks)

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

Phase 3: Agents & Delegation (6-8 weeks)

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

Integration Questions for Loop/AAPS/Trio

See integration-questionnaire.md for the complete questionnaire.

Key Questions Summary

A) Profiles & Overrides

  1. Do you have stable profile identifiers beyond name?
  2. Can you represent overrides as template vs. activation?
  3. What override dimensions exist (target, sensitivity, basal, CR)?
  4. 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?


Appendix: JSON Schemas

Full JSON Schema (draft-2020-12) files are available in the schemas/ directory:


References


Changelog

Date Change
2026-01-01 Initial draft