Hi all,
Back in March I shared Cyréna here for the first time — an offline-first AI engineering agent with PlatformIO support. A lot has changed since then. v0.4.0 just shipped and the PlatformIO extension has been significantly improved. I also have a genuine question for the community about firmware project structure.
What’s new in the PlatformIO extension
The biggest change is that the extension now enforces a strict feature-based layout inside src/ and include/. One of Cyréna’s core ideas is that consistency comes from constraints, not prompts. Every supported stack has a structure the agent cannot deviate from — it uses dedicated file creation functions that place files in the correct location automatically. It physically cannot create files or folders outside the defined structure.
For PlatformIO projects, firmware is now organised into self-contained features — each hardware or software concern (display, sensors, networking, motor control) gets its own folder with a single public header as the entry point:
src/
main.c / main.cpp
{feature}/
{feature}.c / {feature}.cpp
actions/
internals/
include/
{feature}/
{feature}.h
definitions/
actions/
internals/
Folder responsibilities:
definitions/— types, structs, enums, constants. Lives ininclude/only, never insrc/.actions/— function declarations ininclude/, implementations insrc/.internals/— private headers ininclude/, private implementations insrc/. Never exposed outside the feature.{feature}.h— the single public entry point. Consumers include only this file.
Other improvements in 0.4.0:
- The agent now properly identifies as a firmware engineer, not a generic software assistant
- Embedded-safe rules enforced throughout — static allocation preferred, no dynamic memory, no recursion, no blocking delays, no desktop abstractions
platformio.ini,sdkconfig, and dependency metadata remain strictly read-only — dependency changes are flagged to the user with explicit instructions, never applied silently- The agent queries the active environment before making any architecture decisions — board, framework, MCU, and memory constraints are always grounded in reality, never guessed
The following are indexed as read-only — the agent has full awareness of these for context but cannot modify them:
| Folder / File | Purpose |
|---|---|
lib/ |
Project libraries |
managed_components/ |
ESP-IDF managed components |
components/ |
ESP-IDF custom components |
platformio.ini |
Project configuration |
sdkconfig* |
ESP-IDF configuration files |
My question to the community
PlatformIO projects are famously unstructured inside src/ and include/. Beyond the PlatformIO defaults, people do what works for them and there’s no real community standard.
The feature-based structure above is what I felt would work well for an AI agent — predictable, self-contained, with a clear public/private boundary per feature. But I designed it based on my own experience and instincts, not years of production embedded development.
What structure do you actually use in your PlatformIO projects? Does the feature-based approach resonate? What would you change? Is there a common pattern in the embedded community I’m missing?
The reason I’m asking is practical. Cyréna supports custom extensions and I want to build extensions for structures the community actually uses. If there’s a better or more widely adopted approach, I’ll build it. Your input will directly shape what gets built next.
Cyréna is free, open source, and runs fully offline via Ollama. OpenAI is also supported.
Full release notes and downloads: cyrena.dev
Source: github.com/Christo262/Cyrena_App
Would love to hear from anyone with experience structuring larger PlatformIO codebases.