Models as Independent Layer – Framework-Agnostic Device Logic

One of the most important concepts in KOS architecture is that KOS models are not UI artifacts. They form an independent layer that can exist and operate without any user interface, creating a distinct architectural boundary between device logic and presentation concerns.

Models: Framework-Agnostic Device Logic

Independence from UI Frameworks

KOS models are standalone TypeScript classes that:

  • Run in the browser but are not tied to React, Vue, or any UI framework
  • Operate independently – they can monitor devices, process events, and coordinate operations without any UI
  • Provide consistent APIs that can be consumed by any presentation layer
  • Maintain device state regardless of how (or if) it’s visualized

Use Cases Beyond UI

Background Device Monitoring:

// Model runs independently, collecting sensor data
const PROP_SENSOR_ID = createPropKey<EnvironmentalMonitorModel>("sensorId");
@kosModel({ modelTypeId: "environmental-monitor", singleton: false })
export class EnvironmentalMonitorModel {
  sensorId!: string; // The sensor this model monitors
  @kosTopicHandler({
    topic: `/sensors/${PROP_SENSOR_ID}/environment`
  })
  collectEnvironmentalData(data: SensorReading): void {
    this.environmentHistory.push(data)
    // Model operates without UI - logs data, triggers alerts
    if (data.temperature > this.alertThreshold) {
      this.triggerEmergencyAlert()
    }
  }
  // No UI required for core functionality
  private triggerEmergencyAlert(): void {
    this.sendCommand('/systems/emergency/activate')
  }
}

Device Automation and Coordination:

// Model coordinates multiple devices automatically
@kosModel({ modelTypeId: "irrigation-controller", singleton: false })
export class IrrigationControllerModel {
  @kosDependency({ modelType: "soil-sensor-model" })
  soilSensor!: SoilSensorModel
  @kosDependency({ modelType: "water-valve-model" })  
  waterValve!: WaterValveModel
  @kosModelEffect
  automaticIrrigation(): void {
    // Model operates autonomously based on sensor data
    if (this.soilSensor.moistureLevel < 30 && !this.waterValve.isOpen) {
      this.waterValve.open()
    }
    if (this.soilSensor.moistureLevel > 70 && this.waterValve.isOpen) {
      this.waterValve.close()  
    }
  }
}

Developer-Friendly Design Decisions

Why TypeScript with Decorators?

The decorator-based syntax was chosen to be familiar to developers with enterprise/backend experience, while remaining accessible to any developer:

Device Protocol Knowledge:

const PROP_CHILLER_ID = createPropKey<IndustrialChillerModel>("chillerId");
@kosModel({ modelTypeId: "industrial-chiller", singleton: false })
export class IndustrialChillerModel {
  chillerId!: string; // The specific chiller this model manages
  // Developer with device knowledge understands communication protocol
  @kosTopicHandler({
    topic: `/chillers/${PROP_CHILLER_ID}/telemetry`,
    transform: (raw) => this.parseChillerTelemetry(raw)
  })
  handleTelemetry(telemetry: ChillerTelemetry): void {
    // Developer understands what this data means
    this.updateOperationalState(telemetry)
    this.checkMaintenanceSchedule(telemetry.runtime)
  }
  // Developer knows the device's API endpoints
  @kosFuture
  setChillerMode(mode: ChillerMode): Future<ModeChangeResult> {
    return this.sendCommand('/chiller/mode', { 
      mode,
      authorization: this.getServiceToken()
    })
  }
}

Enterprise-Familiar Patterns:

@kosModel({ modelTypeId: "pump-controller", singleton: false })
export class PumpControllerModel {
  // Dependency injection - familiar pattern
  @kosDependency({ modelType: "pressure-sensor-model" })
  pressureSensor!: PressureSensorModel
  // Configuration properties - declarative style
  @kosConfigProperty({ path: "/config/pump/max-pressure" })
  maxPressure!: number
  // Lifecycle methods - structured approach
  @kosModelEffect
  onPressureChange(): void {
    if (this.pressureSensor.currentPressure > this.maxPressure) {
      this.emergencyShutdown()
    }
  }
}

Device Domain Knowledge:

@kosModel({ modelTypeId: "robotic-arm-controller", singleton: false })
export class RoboticArmControllerModel {
  // Developer understands safety constraints
  validateMovement(targetPosition: Position): boolean {
    return (
      this.isWithinWorkspace(targetPosition) &&
      this.hasValidApproach(targetPosition) &&
      !this.wouldCollideWithObstacles(targetPosition) &&
      this.isWithinSpeedLimits(targetPosition)
    )
  }
  // Developer knows the device's operational modes
  @kosFuture
  moveToPosition(position: Position): Future<MovementResult> {
    if (!this.validateMovement(position)) {
      throw new Error("Invalid movement parameters")
    }
    return this.executeMovement(position)
  }
}

Architectural Layer Independence

Model Layer: Not Owned by Frontend or Backend

Key Principle: Models exist in a distinct architectural layer that:

  • Runs in the browser but isn’t tied to any specific team or role
  • Can be written by any developer with the appropriate device domain knowledge
  • Maintains independence from both backend services and frontend frameworks
  • Provides clean interfaces that either frontend or backend developers can implement

Example: Model Created by Any Developer with Domain Knowledge

@kosModel({ modelTypeId: "beverage-dispenser", singleton: false })
export class BeverageDispenserModel {
  // Developer with device knowledge understands ingredient management
  @kosTopicHandler({ topic: '/dispenser/ingredient-level' })
  updateIngredientLevel(level: IngredientLevel): void {
    this.ingredientLevels.set(level.ingredientId, level.amount)
    // Developer knows business rules for this device
    if (level.amount < this.minimumLevel) {
      this.triggerSupplyAlert(level.ingredientId)
    }
  }
  // Developer understands dispense operations
  @kosFuture
  startDispense(recipe: Recipe): Future<DispenseResult> {
    // Validate against device capabilities
    if (!this.canDispenseRecipe(recipe)) {
      throw new Error("Cannot dispense: insufficient ingredients")
    }
    // Execute device-specific protocol
    return this.executeDispenseSequence(recipe)
  }
  private canDispenseRecipe(recipe: Recipe): boolean {
    // Developer knows ingredient requirements
    return recipe.ingredients.every(ingredient => 
      this.ingredientLevels.get(ingredient.id) >= ingredient.amount
    )
  }
}

Presentation Layer: Model Consumer

UI Development Focus:

  • User experience and interaction design
  • Visual presentation and responsive layouts
  • Accessibility and usability patterns
  • Animation and visual feedback

UI Developer Responsibilities:

  • Connect models to UI components through hooks
  • Handle user interactions and form inputs
  • Manage pure UI state (modals, animations, navigation)
  • Provide visual feedback for model operations

Example: UI Developer Consumes Dispenser Model

function DispenserInterface() {
  const dispenser = useKosModel(BeverageDispenserModel, 'main-dispenser')
  // UI developer focuses on presentation
  const [selectedRecipe, setSelectedRecipe] = useState<Recipe | null>(null)
  const [showIngredients, setShowIngredients] = useState(false)
  const handleDispense = async () => {
    if (!selectedRecipe) return
    try {
      // UI developer just calls the model's API
      await dispenser.startDispense(selectedRecipe)
      showSuccessMessage('Dispense started successfully')
    } catch (error) {
      // UI developer handles UI error presentation
      showErrorMessage(error.message)
    }
  }
  return (
    <div className="dispenser-interface">
      <RecipeSelector 
        recipes={dispenser.availableRecipes}
        onSelect={setSelectedRecipe}
      />
      <IngredientLevels 
        levels={dispenser.ingredientLevels}
        visible={showIngredients}
      />
      <DispenseButton
        recipe={selectedRecipe}
        onDispense={handleDispense}
        disabled={!dispenser.isReady}
      />
    </div>
  )
}

Benefits of Architectural Independence

1. Domain Knowledge Flexibility

  • Any developer can create models if they understand the device domain
  • Any developer can create UI if they understand user experience
  • No artificial ownership boundaries between frontend and backend teams

2. Independent Development

  • Models can be developed and tested without any UI
  • UI can be developed using mock models
  • Development can proceed in parallel with clear interfaces

3. Reusable Device Logic

  • Models can be used in different applications
  • Multiple UI frameworks can consume the same models
  • Background services can use models without UI

4. Testability

  • Device logic can be unit tested independently
  • UI can be tested with mock models
  • Integration tests can verify model-device communication

5. Future Flexibility

  • Models can run in web workers for performance
  • Models can be shared between web and mobile apps
  • Device logic is preserved when changing UI frameworks

The Mental Framework

Model Developer Mindset:

“I’m creating a software representation of this device’s capabilities and behavior.”

Focus on:

  • How does the device communicate?
  • What can go wrong with this device?
  • What are the safety constraints?
  • How do multiple devices coordinate?

UI Developer Mindset:

“I’m creating an interface that lets users interact with device capabilities.”

Focus on:

  • How should users control these devices?
  • What feedback do users need to see?
  • How can I make this interface intuitive?
  • What UI states need to be managed?

This architectural independence allows developers to focus on their areas of expertise while creating cohesive device control applications that are both functionally robust and user-friendly, regardless of whether they come from frontend or backend backgrounds.