Starting a project: archec new
A model is easier to change than to start. archec new gives you one that
already works — it passes archec check, its test runs in the simulator, and
it can generate firmware — so the first thing you do is change names, not
stare at an empty file.
archec new my-rover
In a terminal it asks four questions (five with kinds: the kind of project comes first):
- What do you build? A template:
blank,rover-esp32,can-sensor-nodeordrone-fc(below), orowner/name, a template of the Arche Store. - Which board? The template's, none, or one of the
board store (
archec board list). - The main parts, and what talks to what. One
a -> bper line:lidar -> planner,planner -> motors. A name alone is a part with no link yet. Parts that are not there are made (a component, a part, a port at each end and a message between them); a part the template has gets a new port. - Generated code?
none,c,embassy(Rust) orgo. With a board and no deployment yet, a deployment is written: one firmware on the board, one thread running the parts.
It writes main.arche, a README.md with the commands to run next, and a
.gitignore. Then:
cd my-rover
archec check main.arche # no errors
archec test main.arche # the template's test, in the simulator
code . # the VS Code extension draws it
Every step is checked as archec check does. A step that would add an error —
a board whose ports the template's links do not find, say — is left out, and
the output says so in a note.
Without questions#
The flags answer the questions; --yes takes the defaults for the others
(which is also what happens when the input is not a terminal):
archec new my-node --template can-sensor-node --yes
archec new my-robot --board raspberry-pi-4 --parts "lidar -> planner, planner -> motors" --target go --yes
archec new my-drone --template drone-fc --board nucleo-f446re --name "Racer" --dry-run
--name names the project (my-drone gives project MyDrone "My drone";
the default is the folder's name). --dry-run prints the files and writes
nothing.
The templates#
| Template | What it is | Board |
|---|---|---|
blank |
One component that blinks, a requirement, its test. The smallest project that does something. | none (--board adds one) |
rover-esp32 |
A wheeled rover: a remote, a distance sensor, a drive controller whose state machine stops before an obstacle, two motors. | ESP32-DevKitC |
can-sensor-node |
A temperature reported on CAN every 100 ms, a fault state for a bad reading, a gateway on the bus that resets it. | NUCLEO-G474RE (STM32G4, FDCAN) |
drone-fc |
A quadcopter flight controller: RC receiver, IMU, arming and failsafe, a 1 kHz rate loop, four ESCs. | NUCLEO-H743ZI2 |
They are in lib/templates/ beside archec, each a library of kind template;
archec store seed publishes them, so a store has them as arche/<id>. A
template of your own is a library folder with kind template and a
main.arche, published like any package.
Kinds#
--kind says what the project is. Then archec new does not stop at a model
that checks: it ends with a repository that already runs.
archec new my-node --kind firmware
First run (firmware):
✓ archec check
✓ archec test — 3/3 pass
✓ archec gen --target c
✓ archec gen --target c-tests
✓ make (rover_fw)
✓ the test plans on the C (make test) — 1/1 pass
✓ rover_fw runs 0.5 s on this computer
✓ first run: simulated 500 ms, built (make), 4/4 tests pass, rover_fw runs 0.5 s on this computer
In a terminal, without --kind, the first question is What kind of
project?: one of the kinds below, or a template or store package as before.
With --describe, the kind is the one the words of the sentence name
(firmware, esp32, stm32…), unless --kind says it.
What every kind gets:
- Its template, its code language, its deployment. The kind starts from its
own template (
--templatepicks another), says the language of the behaviour (--handlersanother), and writes the deployment its code needs when the project has none. Like every step, one that would add an error is left out with a note. src/andgenerated/. The code you fill in is generated intosrc/and committed: write between itsUSER CODE BEGINandUSER CODE ENDmarkers, and eacharchec genputs it back (regeneration). The rest is generated intogenerated/, which.gitignoreleaves out, with what the build writes.- A test for each part. A part no test plan names gets a first case in
tests.arche, whicharchec testruns in the simulator: a message a link brings it moves its state machine, or reaches it; what it sends reaches the part at the other end; or it starts in its first state. Change them to say what the part must do. - CI.
.github/workflows/archec.ymland.gitlab-ci.ymlat the root: the model's job ofarchec gen --target ci(archec check,archec ci), then acodejob that generates the code, builds it and runs its tests, with the tools it needs set up. - VS Code.
.vscode/extensions.jsonrecommends arche-lang and the extensions for the code;.vscode/tasks.jsonhas build (generate, then build), test (the simulator, then the code's tests) and archec: live. When the folder opens, the extension offers to startarchec live. - The first run. Once written:
archec check,archec test(the test plans in the simulator),archec genof each target, the kind's build, its tests — each step said, then one line. A tool this computer does not have, or a daemon that is not running (podman, Docker), skips its step with a note. A step that fails is shown with the end of its output, andarchec newexits 1: on a project it just made, that is a bug in archec.--no-runskips it;archec new --first-run [dir]runs it again later (the kind is in.archec/kind.json).
| Kind | What you get | Code | Built and tested with |
|---|---|---|---|
firmware |
Code for a microcontroller board: the model's parts placed on the board, its firmware generated. Template rover-esp32; --board another, --target c (default), embassy or go. |
src/c/ (or src/embassy/, src/go/), one folder per binary; generated/c-tests/ |
C: make (the POSIX runtime, on this computer), the binary run for half a second, the test plans as C unit tests (make test). Go: go build, go vet. Rust: cargo check when the board's target is installed (rustup target list --installed); an ESP32's Xtensa target needs Espressif's toolchain, so it is skipped. |
service |
A backend service in Go on a Linux or macOS host, no board. Template web-service: api -> orders -> store, HTTP routes, a store machine. |
src/go/<project>/ (with http.go, the routes); generated/openapi/ |
go build, go vet, go test (each route answers); the program serves HTTP. |
containers |
Several programs, each in its own container, talking over the network. Template container-pipeline: ingest -> processor -> dashboard. |
src/go/<program>/ per container; generated/containerd/, generated/k8s/ |
go build for the container's Linux, go vet, go test; the compose file and the Kubernetes manifests read offline; podman build of each image when podman (or Docker) is running. |
ros2 |
A robot as ROS 2 nodes, with a simulated world closing the loop. Template ros2-rover: lidar, planner, motors. |
src/ros2*/ (packages, nodes, launch); generated/c-tests/, generated/gazebo/ |
make test (the C tests), a static check of the packages; with ROS 2: colcon build, then the nodes launched against the world. CI runs it in ros:jazzy-ros-base. |
desktop |
A program on a PC: parts on threads, an operator screen. Template desktop-app: a source, a limit monitor, a console. |
src/c/, src/ui-qml/<Ui>/; generated/cmake/, generated/c-tests/, generated/ui-web/ |
CMake, the C tests, the program run for a second; the Qt screen built and started off screen when Qt 6 is there. |
library |
Components others include: a store package and a C library. Template component-library: a threshold, a latch, an example system. |
src/c/; CMakeLists.txt (yours); generated/c-tests/ |
CMake, ctest (a test per case), and archec store publish . --dry-run: the package as published compiles. |
A web service (--kind service)#
archec new my-shop --kind service
archec new my-shop --kind service --parts "api -> orders, orders -> store, store -> billing"
A backend service in Go, one process on a Linux or macOS host; no board. It
starts from the template web-service: three modules as parts, api -> orders -> store. api answers HTTP (POST /orders, GET /orders/count, DELETE /orders), orders
checks each order, store keeps the ones accepted. Four requirements, and a
test plan the simulator passes. The behaviour is in the action language
(handlers arche): Arche's simulator runs it, and archec translates it to Go.
What archec new adds and makes:
- the deployment: a
Hostprocessor (compute { os linux }) in aMachineplatform, anddeployment OnHostwith one binary,language go, one thread that runs the parts — their messages and HTTP calls one at a time; src/go/<project>/(archec gen --target go -o src, committed): the Go program.parts.go(each part: its variables, its functions, its state machine),main.go(the parts wired as the model links them, a goroutine per thread),http.go(a route perhandler GET "/path" -> fnof the model) andhttp_test.go(a test of each route), all regenerated. Your code goes in the USER CODE regions (<Part>.init, and the body of a function the model gives none) and inuser.go, written once;archec genkeeps both;generated/openapi/<Component>.json(--target openapi): the same API as OpenAPI 3.1, for clients and docs;- the first run:
go build(the binary intogenerated/bin/),go vet,go test. Without Go on the PATH these are skipped with a note.
Run it:
cd src/go/my_shop
go run . # listens on :8080; ADDR=:9000 for another
curl -X POST -d '{"item":7,"qty":3}' localhost:8080/orders
curl localhost:8080/orders/count
A route takes its function's parameters from the path (/items/{id}), else
from the query (GET, DELETE, HEAD) or a JSON body (POST, PUT, PATCH), and
answers the function's return as JSON (a struct with the model's field names),
or 204 when it returns nothing; a parameter that does not parse is a 400. The
call runs on its part's thread, as a message would, so a part never runs two
things at once. The port is the one of the part's attr url (the template:
http://localhost:8080), unless ADDR says another.
To add a route, add a handler to a port of a part, and its function:
in http: ethernet "HTTP" {
handler GET "/orders/{item}" -> stock_of
}
fn stock_of "Stock of an item" (item: uint32) -> uint32 {
```arche
return stock
```
}
then archec gen main.arche --target go -o src and --target openapi -o generated.
Limits: the simulator does not run HTTP; the test plan sends the messages the
routes lead to (send(api.orders, PLACE(…))). The route test checks that each
route answers with its status, not what it answers: write your own
*_test.go beside it for that. A function the model declares without a body
is a USER CODE region in Go, but Arche's simulator stops on a call to it: give
it a body in the model when a test calls it.
Processes in containers (--kind containers)#
Several processes, each in its own container, talking over the network. The
template, container-pipeline, is a pipeline of three: ingest takes a reading
every second, processor summarises it, dashboard shows the summary and goes
to Alarm when a reading is over LIMIT.
archec new my-pipes --kind containers
What you get:
- A model that simulates. Three components, their messages, three
requirements and a test plan. The deployment puts each part in a container
on a Linux server (
runtime containerd), as a Go program;archec testruns the template's cases and one for the deployment: a reading every second. - The code, yours to fill in, in
src/go/<part>(--target go). Each part is a Go program: its variables, its ```arche bodies translated, its state machine, aUSER CODEregion in each constructor and in each function the model gives no body, anduser.go, written once. A link between two parts is JSON over HTTP (net.go): each program listens on its own port, from 9001 in the order of the deployment, and reaches the others on 127.0.0.1.ARCHE_LISTEN(:8080) andARCHE_PEER_<PART>(http://processor:8080) move them.net_test.gotests both ends. - The images and the manifests, in
generated/(regenerated, not committed).generated/containerd: a Containerfile per container, which takes the program frombin/<part>, andcontainers/server/compose.yaml.generated/k8s/k8s: a Kubernetes Deployment per container, forkubectl apply -k. The containers use the host's network, so the same ports work on one machine, under compose, and on a node of a cluster.
The first run builds each program for the server's Linux into
generated/containerd/bin, runs go vet and go test on each, checks the
compose file (docker-compose config) and the manifests (kubectl kustomize)
without a daemon or a cluster, and builds the images with podman when it
reaches its machine (else it says podman machine start, and goes on).
Run it on your machine, one terminal per part (cd src/go/ingest && go run .),
or in containers:
podman compose -f generated/containerd/containers/server/compose.yaml up
The server's isa is the machine's that made the project (arm64 on an ARM
Mac), so the images run there. For an x86 server, write isa x86_64: the
programs and the images are then built for linux/amd64. Under podman on an
ARM Mac those do not run (Go's runtime crashes under emulation); build them in
CI or on the server.
A part you add (--parts "processor -> archive") gets its own container and
program. A part with no in port runs its first function with no parameters
every second; the others run on the messages they receive. A part with no
behaviour yet is in no program: a link to it is left out until it does
something.
ROS 2 robot (--kind ros2)#
archec new my-robot --kind ros2
A robot as ROS 2 nodes, from the template ros2-rover: a lidar driver, a
planner that drives toward a wall, slower as it gets closer, and stops 0.3 m
before it, and a motor driver that turns the speed into wheel speeds. The
world is a plant (kind plant): it moves the rover by its wheel speeds and gives
the lidar the distance left. The simulator runs it with the rest, so the test
plan runs with nothing installed; it is not a node — on the robot, in Gazebo or
in a test script, something else is the world.
What you get:
| Deployment | <Project>Ros2, packaging ros2: one binary <project>_nodes on a Linux computer, a process per part with behaviour that is not hardware (the plant is left out) |
src/ (yours, committed) |
archec gen --target ros2: the message package <project>_msgs (src/ros2), the package of nodes src/ros2_nodes/<project>_nodes (an rclcpp executable per process around the part's C module), <project>_ros2_bringup with the launch file |
generated/ (ignored) |
c-tests (the test plans as C unit tests of the same C modules), gazebo (the world for gz sim: SDF, bridge, adapter), colcon (colcon's build, install and log) |
| Tests | the test plan in the simulator (archec test); make test in generated/c-tests; tests/ros2_check.py --static (each package.xml parses, the Python compiles, each launch file has generate_launch_description, the C modules compile); tests/ros2_check.py with ROS 2: the nodes launched, the script plays the world, the rover must stop between 0.25 and 0.35 m |
| Build | colcon build --base-paths src into generated/colcon |
| CI | the build and test steps in the ros:jazzy-ros-base image (ciContainer) |
Your code in the nodes goes in their USER CODE regions, which archec gen
keeps: in each src/<exe>.cpp the includes, the end of each node's constructor
(<part>.node, a subscription to a real driver's topic, say), the node's
members (<part>.members) and main before the threads start; in
CMakeLists.txt one for more packages and sources; in each C module the
includes and the end of its init. The behaviour itself is in the model.
No ROS 2 on the machine (macOS, say): colcon build and the live run are
skipped with a note; the simulator, the C unit tests and the package checks
still run. To build anyway:
podman run --rm -v "$PWD:/ws" -w /ws ros:jazzy-ros-base bash -c \
'. /opt/ros/jazzy/setup.sh && colcon --log-base generated/colcon/log build --base-paths src --build-base generated/colcon/build --install-base generated/colcon/install && . generated/colcon/install/setup.sh && python3 tests/ros2_check.py'
The template row for the table of templates:
| ros2-rover | A rover as ROS 2 nodes: lidar driver, planner, motor driver; a simulated world closes the loop, and the rover stops before the wall. | none (a Linux computer) |
Desktop program (--kind desktop)#
A program on a PC — Linux, macOS or Windows: a controller tool, a test bench, a
simulator's front end. The parts are C modules on a few threads of one program;
the part with a ui { } is its screen.
archec new my-bench --kind desktop
It starts from the desktop-app template: a signal source that sends a sample
every 10 ms, a monitor that goes High above a limit and logs each alarm, an
operator console with a gauge and Start/Stop buttons, three requirements and the
test plan that verifies them. Its deployment puts one program, app, on the PC:
the source on a 10 ms thread, the monitor and the console on another. (A project
that comes without a deployment gets one program with a thread per part.)
What you get:
| Where | What | Committed |
|---|---|---|
src/c/ |
the C of the parts (--target c): your code goes between the USER CODE BEGIN and END markers (Monitor.init, Monitor.includes, app.start in app/wiring.c, …) and archec gen keeps it |
yes |
src/ui-qml/Console/ |
the screen as a Qt Quick application (--target ui-qml): main.cpp and CMakeLists.txt are yours, the QML is the model's |
yes |
generated/cmake/ |
the CMake project of the program (--target cmake) |
no |
generated/c-tests/ |
the test plan as C unit tests (--target c-tests) |
no |
generated/ui-web/ |
the screen as a web page, the model simulated in it: open index.html |
no |
The first run builds and runs it:
archec test main.arche # the test plan, simulated
cmake -S generated/cmake -B build -DARCHE_GEN=src # the C is in src/c
cmake --build build
make -C generated/c-tests test # 3 passed, 0 failed
./build/app/app 1000 # runs for a second, then exits
cmake -S src/ui-qml/Console -B build/ui-Console && cmake --build build/ui-Console
QT_QPA_PLATFORM=offscreen ARCHE_UI_QUIT_AFTER=1000 ./build/ui-Console/console_ui
-DARCHE_GEN=src is given with no type, so CMake makes it absolute from the
folder you run it in. ./build/app/app with no number runs until Ctrl-C. The
Qt steps need Qt 6 (qmake6 on PATH, and its CMake config); without it they are
skipped with a note.
Limits:
make testruns the C that--target c-testswrites from the model, not your code insrc/c/: what you write in a region is built and run by the program, not unit-tested.- The screen and the program are not connected yet: the Qt screen reads its
values over a WebSocket (
ARCHE_UI_WS, see ui), which the C program does not serve; the web page simulates the model itself. - On Windows with a multi-configuration generator (Visual Studio) the program
is at
build/app/Debug/app.exe, not where the first run looks.
Component library (--kind library)#
archec new my-sensors --kind library
A library of components that other projects include, in two forms: an Arche
Store package (the model) and a C library built with CMake (the code). It
starts from the component-library template:
| File | What it is |
|---|---|
components.arche |
the library: its messages, requirements and components (a threshold with hysteresis, a latch) |
main.arche |
the project, and a small system that uses the components (Example), with the test plan |
library.arche |
the store's card: kind library, its name (my-sensors), version, owner, license, export control |
CMakeLists.txt |
the CMake library my_sensors, its ports my_sensors_ports, a ctest test per case; yours, written once |
src/c/ |
the C of each component (archec gen --target c -o src): your code between the USER CODE markers is kept |
generated/c-tests/ |
the test plan as C (--target c-tests), which ctest runs on src/c |
The first run checks the model, runs the test plan in the simulator, writes
the C, builds it with CMake, runs ctest, then packs the folder as the store
would and compiles it as the store's validator does (archec store publish . --dry-run: nothing is sent).
No deployment: a library does not run anywhere by itself. --target c writes
the components without one; what each component sends goes through a function
in src/c/<component>_ports.c, which the project using the library writes
(the file is a start: it encodes the message and leaves the sending to you).
The C tests carry the messages between the example's parts themselves.
Using the C in another CMake project:
add_subdirectory(path/to/my-sensors)
target_link_libraries(app PRIVATE my_sensors) # and my_sensors_ports, or your own send functions
Publishing: set owner, license and export-control in library.arche
(the template writes owner your-name, Apache-2.0 and none: they are your
statements as publisher), then archec store check . and archec store publish ..
A project takes it with archec add main.arche your-name/my-sensors and
includes my-sensors/components.arche. What archec builds (generated/,
arche-out/) is not part of the package.
Limits: the C goes with the package as you wrote it, but a project that
includes the components and runs archec gen itself writes their C again,
without the code in your regions; use the library's CMake target for the code.
Remixing a store package#
Any package of the store — a whole architecture, a template, a board — can be the start of a project:
archec new my-car --from arche/autonomous-car
archec new my-node --from acme/can-node@^1.2 --store https://store.example.com
It is a copy, not a dependency: the files are yours to change, and nothing
updates them behind your back (archec add is for a library you use). The
package is checked as archec add checks it — its hash, its publisher's
signature, the files' fingerprint — and what it uses from the store comes along.
The project records where it came from:
project MyCar "My car" {
version "0.1.0"
derived-from "arche/autonomous-car@0.3.0"
}
When you publish it, its library carries that line on, and the store's package page shows the family: what it was derived from, and what was derived from it.
In VS Code, a package of the Store view has Use as Starting Point (it asks
for a folder and opens the new main.arche), and Arche: New Project… in the
command palette asks the four questions above.
From a description#
archec new my-robot --describe "a robot with 2 motors, a lidar and a Raspberry Pi"
Claude writes the first model. It gets a short reference of the language, the
boards of the board store and a template as an example; archec checks what comes
back and sends the errors to Claude, which corrects them — up to four rounds
(--rounds). The result is a first draft: read it before you trust it.
When the description has a display — a screen, a dashboard, an HMI, a touch
panel, an OLED — Claude also gets the ui { } reference and is asked
for the screens: the component that owns the display gets a ui bound to what
the model sends it, which archec checks like the rest.
This sends your description, and archec check's messages about what Claude
wrote, to the Anthropic API (the model claude-opus-5-5), and only when you run
it. --dry-run prints the whole prompt and sends nothing. It needs
ANTHROPIC_API_KEY.
Next#
- The tutorial — what each line of the template means, from a water pump to its firmware.
- Boards —
archec board addandarchec device add, to put a board or a sensor in later. archec explain <code>— what a message ofarchec checkmeans.