This guide is for future AI agents and engineers modifying MatrixOS. If this guide and the code disagree, trust the code first.
- Public docs: https://matrix.203.io/
- Use a local
Matrix-Wikiclone when changing public APIs or user-facing behavior.
MatrixOS is split into three layers:
Devices/Hardware layer. Board-specific drivers, storage, keypad scanning, LEDs, USB, and chip-family details.OS/Runtime layer. PublicMatrixOS::APIs, app lifecycle, UI, HID/MIDI, logging, storage wrappers, shared framework code.Applications/App layer. Apps derive fromApplicationand useMatrixOS::APIs.
Change the right layer:
- hardware behavior or board capability:
Devices/ - shared runtime or public OS behavior:
OS/ - product behavior inside one app:
Applications/
Read these first for structural work:
OS/MatrixOS.hDevices/Device.hApplications/Application.hOS/System/System.cppOS/System/Parameters.h
- Only one app is active at a time.
- Apps run in a dedicated FreeRTOS task.
- App lifecycle is owned by the supervisor.
- App memory is managed manually.
- Do not manually kill app tasks unless you are intentionally changing lifecycle internals.
- Apps are registered through CMake and generated files.
- Do not hand-edit generated registration outputs.
- Update the source CMake and family
ApplicationList.txtinstead.
- Treat
OS/MatrixOS.has the app-facing contract. - Prefer
MatrixOS::...wrappers overDevice::...from application code. - Direct
Device::...calls are usually only appropriate in device code, OS internals, or carefully justified hot paths.
Two main persistence mechanisms:
- NVS for small settings and compact state
- FileSystem for larger structured data or user content
Rules:
- keep one clear source of truth
- define precedence if multiple backends can hold the same data
- avoid stale copies fighting each other
Root CMake requires:
-DDEVICE=<device>-DMODE=<DEVELOPMENT|RELEASE|RELEASECANDIDATE|BETA|NIGHTLY|UNDEFINED>
Optional:
-DFAMILY=<family>
- Load
ESP-IDF v5.3.4into enviorment - If the user provides an ESP-IDF path, use it
- Otherwise search common local install locations for a
v5.3.4install - Do not patch or edit files under the ESP-IDF install directory
- Configure from the repo root
- Use the normal CMake/Ninja workflow
- Use
-DFAMILYonly when a non-default family override is actually required
Devices/Device.h is the hardware contract expected by the OS.
If adding a hardware capability:
- extend the device layer if the concept is truly hardware-facing
- expose an OS wrapper in
OS/only if higher layers should consume it
Do not leak board-specific details into application code without a strong reason.
MatrixOS has a UI/component system under OS/UI.
If changing UI behavior:
- inspect existing
OS/UIpatterns first - preserve current interaction patterns unless intentionally redesigning
- avoid global side effects without checking the active app and current UI stack
This codebase mixes STL containers, FreeRTOS, and manual allocation.
Be careful about:
pvPortMalloc()/vPortFree()- task stacks and queue sizes
- dynamic allocation in hot paths
- using
reserve()when indexed writes actually requireresize()
Rules of thumb:
- if you index a
vector, it must already be sized - free RTOS heap allocations with the matching RTOS free
- bounds-check protocol-derived indexes and offsets
- do not assume heap fragmentation behaves well on embedded targets
- Use the repo logging macros instead of ad hoc prints
- log state transitions, failing sizes, indexes, and error codes
- keep logs concise enough for serial output
Recommended workflow:
- identify the correct layer
- read the public API and concrete implementation
- search for an existing pattern before inventing a new one
- make the smallest coherent change
- preserve persistence and compatibility rules intentionally
- keep agent-only workflow files under
.agent/
Agent-only notes, journals, prompt files, scratch docs, and local helper scripts belong in .agent/, not the repo root or official Tools/.
- use
nullptr, notNULL - use
Function(), notFunction(void), unless an external interface requires it - use PascalCase for types and methods
- use lowerCamel for variables, parameters, fields, and booleans
- use
Type* nameandType& name - avoid
_in C++ names unless required by macros, external interfaces, or compatibility surfaces - user-visible strings should say
map; internal protocol names may still sayUAD - non-trivial modules should define and use a stable local
TAG
- types, enums, namespaces, and methods: PascalCase
- locals, parameters, fields: lowerCamel
- booleans:
isX,hasX,canX, or a clear adjective - macros and shared compile-time constants:
ALL_CAPS
- use 2 spaces in C++ files
- do not introduce tabs
- function braces on the same line
- control-flow braces on the next line
- namespace braces on the next line
- use
Type* nameandType& name - prefer
const vector<string>& args - include order: local project headers, related subsystem headers, then standard library headers
- use
#pragma oncefor headers
- keep comments short and factual
- explain why, not the obvious what
- use
TODO:only for real follow-up work - keep log tags stable and short
- prefer one informative log over many low-signal logs
- check bounds before indexing with protocol-derived values
- check allocation and I/O return values
- prefer explicit fallback behavior over undefined behavior
- use
resize()before indexed writes
- do not hand-edit generated files
- edit the source CMake, headers, or generator inputs instead
Start with the public OS API, the device contract, the application base class, and then the subsystem you actually need to change.