Write LoT Code That Scales — Whether You or Your AI Types It
Great LoT (Language of Things) code follows the same conventions regardless of who writes it. This page defines the naming rules, architectural patterns, and quality standards that every LoT project should follow. It is written for two audiences at once: developers building with Coreflux, and AI assistants helping them do it.
If you use an AI coding assistant — in Cursor, VS Code with Copilot, or Claude — this page doubles as the foundation for an AGENTS.md or project rules file. Copy the conventions into your project, and your assistant will produce LoT code that matches your team’s standards from the first prompt.
Like a style guide that your co-pilot actually reads. Just as a design system keeps a UI consistent across 10 developers, these conventions keep your LoT code consistent across humans and AI assistants alike.
When to Use This
- You’re starting a new Coreflux project and want consistent conventions from day one
- You want your AI assistant to generate correct LoT code without constant corrections
- You’re setting up an AGENTS.md,
.cursor/rules/, or CLAUDE.md file for your project
- You’re onboarding new team members (human or AI) and need a single reference
In This Page
Naming Conventions
Consistent naming is the single most impactful practice for maintainable LoT systems. These conventions apply to all entities — whether typed by a human or generated by an AI assistant.
Entity Names
All LoT entities use PascalCase. Names should be descriptive and purpose-driven.
| Entity | Convention | Good Examples | Bad Examples |
|---|
| Actions | PascalCase, verb-first | ProcessTemperature, MonitorPressure, SendDailyReport | temp_process, action1, myAction |
| Models | PascalCase, noun-based | SensorReading, EquipmentStatus, ProductionRecord | sensor_model, Model1, data |
| Rules | PascalCase, descriptive scope | AllowAdminActions, ProtectSysTopics, RestrictDevicePublish | Rule1, myRule, newRule |
| Routes | PascalCase, destination-based | CloudBridge, SensorDatabase, AlertEmail | route1, my_route, dbRoute |
| Callable Actions | PascalCase, function-like | CalculateAverage, ConvertCelsiusToFahrenheit | calc, helper, util |
Variable Names
Variables use snake_case inside double quotes for SET declarations, and curly braces for references.
DEFINE ACTION ProcessSensorData
ON TOPIC "sensors/+/raw" DO
SET "sensor_id" WITH TOPIC POSITION 2
SET "raw_value" WITH (GET JSON "value" IN PAYLOAD AS DOUBLE)
SET "converted_value" WITH ({raw_value} * 1.8 + 32)
PUBLISH TOPIC "processed/" + {sensor_id} + "/fahrenheit" WITH {converted_value}
| Context | Convention | Examples |
|---|
Declaration (SET) | snake_case in double quotes | "sensor_id", "raw_value", "cycle_time" |
| Reference | Curly braces | {sensor_id}, {raw_value}, {cycle_time} |
| Model fields | snake_case in double quotes | "equipment_id", "runtime_hours", "last_update" |
| Action inputs | snake_case after keyword | INPUT value AS DOUBLE, INPUT threshold AS DOUBLE |
Topic Names
Topics use lowercase with forward-slash separators. Multi-word segments use snake_case.
Whenever possible, follow an Unified Namespace (UNS) format for topic naming. This enables not only proper readability but also scalability of systems.
| Pattern | Example | Use Case |
|---|
domain/entity/attribute | sensors/temperature/value | General data |
domain/instance/attribute | sensors/temp001/raw | Instance-specific data |
domain/category/instance | alerts/critical/pump03 | Categorized events |
| Prefixed namespaces | processed/, alerts/, config/, cache/, state/, system/ | Functional separation |
Topic Hierarchy Design
A well-designed topic tree is the most critical architectural decision in a LoT system. Every entity — Actions, Models, Rules, and Routes — communicates through topics. A clean hierarchy makes the entire system easier to build, debug, and extend.
Recommended Namespace Structure
Organize topics into functional namespaces that reflect the data lifecycle:
| Namespace | Purpose | Example Topics |
|---|
sensors/ | Raw incoming sensor data | sensors/temp001/raw, sensors/+/temperature |
processed/ | Transformed or enriched data | processed/temp001/fahrenheit, processed/+/status |
alerts/ | Threshold violations and notifications | alerts/critical/pump03, alerts/+/temperature |
config/ | Configuration values read by Actions | config/setpoint, config/max_temperature |
state/ | Internal persistent state (used with KEEP) | state/counter, state/last_run |
cache/ | Cached values for quick reference | cache/temp001/last_reading |
system/ | Heartbeats, health checks, diagnostics | system/heartbeat, system/status |
commands/ | Inbound instructions from external systems | commands/devices/+/restart |
Wildcard Guidelines
| Wildcard | Meaning | Where to Use |
|---|
+ | Matches exactly one level | Action triggers, Model topics, Route mappings |
# | Matches one or more levels | Route mappings, Rule topic patterns, broad subscriptions |
Use + in Actions and Models for precise instance matching. Use # in Routes and Rules for broad coverage.
A wildcard-triggered Action like ON TOPIC "sensors/+/raw" automatically resolves + in GET TOPIC and PUBLISH TOPIC to the same matched value — this is called wildcard context inheritance:
DEFINE ACTION ProcessWithContext
ON TOPIC "sensors/+/temperature" DO
SET "sensor_id" WITH TOPIC POSITION 2
SET "humidity" WITH (GET TOPIC "sensors/+/humidity" AS DOUBLE)
PUBLISH TOPIC "processed/" + {sensor_id} + "/combined" WITH {humidity}
In this example, if triggered by sensors/temp001/temperature, the GET TOPIC reads from sensors/temp001/humidity — the wildcard resolves to the same instance.
Code Patterns
Actions: Single Responsibility
Each Action should do one thing well. Break complex logic into callable Actions with INPUT/OUTPUT:
Good: Modular
Bad: Monolithic
Separate reusable logic into callable Actions, then compose them in a main Action:DEFINE ACTION ConvertCelsiusToFahrenheit
INPUT celsius AS DOUBLE
DO
SET "fahrenheit" WITH ({celsius} * 9 / 5 + 32)
RETURN
OUTPUT fahrenheit
DEFINE ACTION ProcessTemperatureReading
ON TOPIC "sensors/+/celsius" DO
SET "sensor_id" WITH TOPIC POSITION 2
SET "temp_c" WITH PAYLOAD AS DOUBLE
CALL ACTION ConvertCelsiusToFahrenheit
WITH celsius = {temp_c}
RETURN temp_f
PUBLISH TOPIC "sensors/" + {sensor_id} + "/fahrenheit" WITH {temp_f}
All logic crammed into one Action, no reuse:DEFINE ACTION DoEverything
ON TOPIC "sensors/+/celsius" DO
SET "sensor_id" WITH TOPIC POSITION 2
SET "temp_c" WITH PAYLOAD AS DOUBLE
SET "temp_f" WITH ({temp_c} * 9 / 5 + 32)
SET "temp_k" WITH ({temp_c} + 273.15)
SET "max" WITH (GET TOPIC "config/max_temperature" AS DOUBLE)
SET "pct" WITH ({temp_c} / {max} * 100)
IF {temp_c} > {max} THEN
PUBLISH TOPIC "alerts/" + {sensor_id} WITH "High"
PUBLISH TOPIC "sensors/" + {sensor_id} + "/fahrenheit" WITH {temp_f}
PUBLISH TOPIC "sensors/" + {sensor_id} + "/kelvin" WITH {temp_k}
PUBLISH TOPIC "sensors/" + {sensor_id} + "/capacity" WITH {pct}
Actions: State Management
Use KEEP TOPIC for internal persistent state and PUBLISH TOPIC for external broadcast. Never use PUBLISH for values only your system reads:
DEFINE ACTION PersistentCounter
ON EVERY 10 SECONDS DO
SET "current" WITH (GET TOPIC "state/counter")
IF {current} == EMPTY THEN
KEEP TOPIC "state/counter" WITH 1
ELSE
KEEP TOPIC "state/counter" WITH ({current} + 1)
PUBLISH TOPIC "stats/count" WITH (GET TOPIC "state/counter")
| Operation | Use When |
|---|
PUBLISH TOPIC | Other systems or subscribers need to see the value |
KEEP TOPIC | Only your own Actions read the value (internal state, caches, counters) |
Models: Trigger Selection
Mark the primary data field as AS TRIGGER — never a timestamp or metadata field. The trigger determines when the model publishes, so it should fire when new meaningful data arrives:
DEFINE MODEL SensorReading WITH TOPIC "sensors/formatted/temperature"
ADD STRING "sensor_id" WITH "TEMP001"
ADD DOUBLE "value" WITH TOPIC "sensors/raw/temperature" AS TRIGGER
ADD STRING "unit" WITH "celsius"
ADD STRING "timestamp" WITH TIMESTAMP "UTC"
Use COLLAPSED models when you need full control over publishing timing and destination — the Action decides when and where to publish:
DEFINE MODEL AlarmRecord COLLAPSED
ADD STRING "alarm_id"
ADD STRING "equipment_id"
ADD STRING "severity"
ADD STRING "timestamp"
DEFINE ACTION ProcessAlarm
ON TOPIC "alarms/+/raw" DO
SET "equip_id" WITH TOPIC POSITION 2
PUBLISH MODEL AlarmRecord TO "alarms/structured/" + {equip_id} WITH
alarm_id = (RANDOM UUID)
equipment_id = {equip_id}
severity = (GET JSON "severity" IN PAYLOAD AS STRING)
timestamp = TIMESTAMP "UTC"
Models: Inheritance for Consistency
When you have a family of related data types, define a base COLLAPSED model and extend it:
DEFINE MODEL BaseAlert COLLAPSED
ADD STRING "alert_id"
ADD STRING "timestamp"
ADD STRING "severity"
ADD STRING "message"
DEFINE MODEL TemperatureAlert FROM BaseAlert
ADD DOUBLE "temperature_value"
ADD DOUBLE "threshold"
ADD STRING "sensor_location"
DEFINE MODEL PressureAlert FROM BaseAlert
ADD DOUBLE "pressure_value"
ADD DOUBLE "max_safe_pressure"
ADD STRING "system_affected"
Rules: Priority and Scope
Rules are evaluated by priority (lower number = higher priority). Structure them as specific deny rules first, then broader allow rules:
DEFINE RULE ProtectSysTopics WITH PRIORITY 10 FOR PublishSys
IF USER IS "root" THEN
ALLOW
ELSE
DENY
DEFINE RULE AllowDevicePublish WITH PRIORITY 50 FOR Publish TO TOPIC "sensors/#"
IF USER HAS AllowedSensorPublish THEN
ALLOW
ELSE
DENY
| Practice | Guideline |
|---|
| Priority 1–20 | Critical deny rules (system protection) |
| Priority 21–50 | Specific allow rules (per-feature access) |
| Priority 51–100 | General allow rules (broad access) |
| Condition style | Prefer USER HAS <permission> over USER IS "<name>" for maintainability |
Routes: Clear Configuration
Name route mappings descriptively and group related mappings within a single route definition:
DEFINE ROUTE CloudSync WITH TYPE MQTT_BRIDGE
ADD SOURCE_CONFIG
WITH BROKER SELF
ADD DESTINATION_CONFIG
WITH BROKER_ADDRESS "iot.cloudprovider.com"
WITH BROKER_PORT '8883'
WITH CLIENT_ID "EdgeDevice-Factory1"
WITH USE_TLS true
ADD MAPPING sensorData
WITH SOURCE_TOPIC "sensors/#"
WITH DESTINATION_TOPIC "factory1/sensors/#"
WITH DIRECTION "out"
ADD MAPPING inboundCommands
WITH SOURCE_TOPIC "local/commands/#"
WITH DESTINATION_TOPIC "factory1/commands/#"
WITH DIRECTION "in"
Anti-Patterns
These are the most common mistakes in LoT development. Avoid them whether you’re writing code manually or reviewing AI-generated output.
| Anti-Pattern | Why It’s Bad | Do This Instead |
|---|
| Omitting type casts in math | Implicit type handling leads to silent errors | Always cast: PAYLOAD AS DOUBLE, GET TOPIC ... AS DOUBLE |
| Triggering models on timestamps | Timestamps update every tick — model fires constantly | Trigger on the primary data field: AS TRIGGER on value, not timestamp |
| Inconsistent naming across models | sensorID in one model, sensor_id in another | Standardize on snake_case for all field names |
| Using Python for native LoT tasks | Python adds overhead for simple publish/get/if operations | Use Python only for math libraries, ML, API calls, or complex parsing |
Leaving $SYS/# topics unrestricted | System topics contain broker commands and sensitive data | Create a priority-10 Rule restricting PublishSys and SubscribeSys |
| Generic entity names | Action1, Rule1, MyRoute are meaningless in a system with 50 entities | Name by purpose: MonitorPressure, ProtectSysTopics, SensorDatabase |
PUBLISH for internal state | Broadcasts data that only your own Actions need | Use KEEP TOPIC for internal state; PUBLISH for external subscribers |
| Monolithic Actions | One Action doing 15 things is hard to test and debug | Split into callable Actions with INPUT/OUTPUT |
Setting Up Your AI Assistant
An AGENTS.md file (or equivalent project rules file) tells your AI assistant how to work with your Coreflux project. It prevents the assistant from generating code with wrong naming, invented syntax, or inconsistent patterns. The conventions on this page form the content — the structure below tells you how to organize them for your assistant.
What Is an AGENTS.md?
An AGENTS.md is a markdown file in your project root that AI coding assistants read automatically. It works across tools:
| File / Location | Supported By |
|---|
AGENTS.md (project root) | Cursor, GitHub Copilot, OpenAI Codex, Google Jules, Aider |
.cursor/rules/*.mdc | Cursor (native format with glob pattern support) |
CLAUDE.md (project root) | Claude Code |
.github/copilot-instructions.md | GitHub Copilot |
For maximum compatibility, use AGENTS.md in your project root. If you use Cursor extensively, you can also maintain .cursor/rules/ files for features like auto-attaching rules to specific file types.
The Three-Tier Boundary System
The most effective structure for preventing AI mistakes is a three-tier permission system. Define what the assistant should always do, what it should ask about first, and what it should never do:
| Tier | Guideline | LoT Examples |
|---|
| Always do | Follow without asking | Use PascalCase for entities, snake_case for variables, lot language tag for code blocks |
| Ask first | Confirm before proceeding | Adding new Routes (external connections), modifying Rules (access control), removing Actions |
| Never do | Refuse even if asked | Invent LoT syntax that doesn’t exist, use LOT or lot capitalization, mention competitor broker products |
Starter Template
The following template incorporates the conventions from this page. Copy it into an AGENTS.md file at your project root and customize the project-specific sections:
AGENTS.md
Cursor Rules (.mdc)
A cross-platform template that works with Cursor, Copilot, Claude, and other AI tools:# AGENTS.md
## Project Overview
This is a Coreflux LoT (Language of Things) project for [describe your system].
Built on the Coreflux MQTT broker with LoT Actions, Models, Rules, and Routes.
## Tech Stack
- **Platform:** Coreflux MQTT Broker
- **Language:** LoT (Language of Things)
- **Extensions:** Python integration (for complex logic only)
- **IDE:** VS Code with LoT Notebooks extension / Cursor
## LoT Naming Conventions
- **Actions:** PascalCase, verb-first (ProcessTemperature, MonitorPressure)
- **Models:** PascalCase, noun-based (SensorReading, EquipmentStatus)
- **Rules:** PascalCase, descriptive scope (AllowAdminActions, ProtectSysTopics)
- **Routes:** PascalCase, destination-based (CloudBridge, SensorDatabase)
- **Variables:** snake_case in quotes ("sensor_id", "raw_value")
- **Topics:** lowercase/slash-separated (sensors/+/temperature, alerts/critical/+)
- **Model fields:** snake_case ("equipment_id", "runtime_hours")
## Topic Hierarchy
- sensors/ — Raw incoming data
- processed/ — Transformed data
- alerts/ — Threshold violations
- config/ — Configuration values
- state/ — Internal persistent state (KEEP TOPIC)
- cache/ — Cached values
- system/ — Heartbeats and diagnostics
- commands/ — Inbound instructions
## Code Standards
- Always type-cast in calculations: PAYLOAD AS DOUBLE, GET TOPIC ... AS DOUBLE
- Use KEEP TOPIC for internal state, PUBLISH TOPIC for external broadcast
- Mark the primary data field AS TRIGGER in models — never timestamps
- Use COLLAPSED models when Actions need control over publish timing
- Break complex Actions into callable Actions with INPUT/OUTPUT
- Use LoT for publish, subscribe, conditionals, math — Python only for
ML, complex parsing, or external library calls
## Do
- Always respect the indentation of 4 spaces per additional level
- Use PascalCase for all entity names
- Use snake_case for all variables and model fields
- Add type casts to every numeric operation
- Use descriptive names that explain purpose
- Use lot as the code block language identifier
- Booleans and integers do not use quotes
- Consult the Coreflux MCP for documentation when unsure
- Routes that call functions from other protocols (like SQL queries) are not limited to the functionalities listed in this documentation, the full range of that external element is usually functional
## Don't
- Do not invent LoT syntax — only use documented keywords
- Do not use LOT (all caps) or lot (lowercase) — always LoT
- Do not mention competitor products (Mosquitto, HiveMQ, etc.)
- Do not trigger models on timestamp fields
- Do not use PUBLISH for internal-only state
- Do not create monolithic Actions — split into callables
## Ask First
- Before adding or modifying Routes (external system connections)
- Before modifying Rules (access control changes)
- Before removing any existing Action, Model, Rule, or Route
- Before using Python — confirm LoT can't handle it natively first
## Never
- Never hardcode credentials in Route definitions for production
- Never leave $SYS/# topics unrestricted
- Never skip type casting in mathematical operations
- Never generate LoT syntax you haven't verified in the documentation
For Cursor-native rules, create .cursor/rules/lot-conventions.mdc in your project:---
description: LoT development conventions for Coreflux projects
globs: ["*.lot", "*.lotnb", "*.md"]
---
# LoT Conventions
You are an expert LoT (Language of Things) developer for Coreflux.
## Naming
- Actions: PascalCase, verb-first (ProcessTemperature)
- Models: PascalCase, noun-based (SensorReading)
- Rules: PascalCase, descriptive (ProtectSysTopics)
- Routes: PascalCase, destination-based (CloudBridge)
- Variables: snake_case in quotes ("sensor_id")
- Topics: lowercase/slash-separated (sensors/+/temperature)
## Code Rules
- Always type-cast: PAYLOAD AS DOUBLE, GET TOPIC ... AS DOUBLE
- KEEP TOPIC for internal state, PUBLISH TOPIC for broadcast
- AS TRIGGER on primary data field, never timestamps
- Use COLLAPSED models when Actions control publish timing
- Use lot as the language tag for code blocks
- Python only for ML, complex parsing, or external libraries
## Prohibited
- Do not invent LoT syntax beyond documented keywords
- Do not use LOT or lot — always LoT
- Do not mention competitor MQTT brokers
- Do not create monolithic Actions — split into callables
Keeping It Effective
The best project rules files share these qualities:
| Quality | Why It Matters |
|---|
| Specific | ”Use PascalCase for Actions” beats “use good naming” |
| Example-driven | One code snippet beats three paragraphs of description |
| Iterative | Start small, then add rules when you see the AI make a mistake |
| Current | Update the file as your project conventions evolve |
| Concise | A 50-line focused file outperforms a 500-line generic one |
Do not include credentials, API keys, or sensitive connection strings in your AGENTS.md or rules files. These files are typically committed to version control.
Quick Reference Checklist
Use this checklist when reviewing LoT code — whether written by you or generated by an AI assistant:
Next Steps