Skip to content

EthanMBoos/MAF

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MAF — Modular Autonomy Framework

License: MIT C++ Status

A C++ mission execution runtime built around a typed, auditable authority boundary between high-level autonomy and a trusted low-level controller.


What It Is

MAF sits between your autonomy stack and your vehicle controller. Every command crossing that boundary — whether from a hand-coded behavior tree, a classical planner, or a learned policy — passes through a runtime monitor that enforces structural invariants before it reaches the actuators.

┌─────────────────────────────────────┐
│  Mission layer                      │
│  Hand-coded behaviors / AI nodes    │  ← untrusted, swappable
└────────────────┬────────────────────┘
                 │ command stream
                 ▼
┌─────────────────────────────────────┐
│  Runtime monitor                    │  ← trusted, always runs
│  bounds · rate-of-change · staleness│
└────────────────┬────────────────────┘
                 │ approved commands only
                 ▼
┌─────────────────────────────────────┐
│  Adapter → ArduPilot / controller   │  ← single writer
└─────────────────────────────────────┘

The boundary doesn't depend on what sits above it. A hand-coded navigate node and a 10B-parameter VLA satisfy the same interface. The monitor, adapter, and session log stay the same regardless.


Why

Most robot autonomy stacks mix mission logic, sensor fusion, command generation, and controller communication in ways that make it hard to reason about what can go wrong and harder to detect it when it does.

MAF's thesis is that a small trusted surface — a monitor and a single-writer adapter — can provide meaningful safety guarantees even when the autonomy above it is learned, opaque, or partially trusted, as long as the boundary contract is specified precisely and logged completely.

The empirically interesting question: for any given autonomy stack, what fraction of failures does the architectural monitor catch alone, the behavioral monitor catch alone, both, or neither? That partition shifts as the command distribution changes. Measuring it across policy types is what this prototype is built to do.


How It Works

The authority boundary

Only maf_ardupilot_adapter writes to the controller. No other code path has access to the transport. Commands from the mission layer are evaluated by the RuntimeMonitor before the adapter sees them. If the monitor rejects a command, it substitutes a fallback or triggers a halt — the adapter never sees the original.

The monitor

Three invariant classes cover most of what's catchable at the command interface:

  • Per-sample bounds — NaN/Inf, out-of-range values, stale timestamps
  • Rate-of-change limits — per-step deltas exceeding what the platform can execute
  • Temporal-window invariants — individually valid commands that are collectively dangerous (oscillation, jerk, geofence displacement) (in progress)

Every monitor decision records the full active invariant set alongside the outcome. Post-mortem attribution is decidable from logs without operator memory.

Mission execution

Missions are sequences of tasks. Each task has a goal, a behavior node that executes it, and transition rules for what happens on success or failure. The mission executor is the state machine. The behavior node is just the current executor — it ticks every 20 Hz and returns Success, Failure, or Running.

Hand-coded and learned nodes satisfy the same interface. Substituting one for the other changes nothing below the boundary.


Current State

Phase 1 — Monitor contract in SITL

  • BTNode contract: symmetric for hand-coded and learned nodes
  • WorldState: telemetry thread writes, BT nodes read on tick
  • ArdupilotAdapter: MAVLink telemetry → live pose in WorldState
  • RuntimeMonitor: per-sample bounds, rate-of-change, staleness
  • MonitorDecision: Passed / Fallback / Halt + full active invariant set
  • Single-writer adapter enforced at the module boundary
  • Session log: frame, timestamp, outcome, triggered invariant, throttle, steering
  • NavigateNode: navigates using real EKF pose; rejects stale observations
  • ArduPilot stream rate configuration on connect
  • Temporal-window invariants (oscillation, geofence, jerk)
  • Wire MAVLink submodule and test against ArduPilot Rover SITL
  • MCAP structured logging

Phase 2 — Learned node injection

Replace NavigateNode with a learned ONNX node above the boundary; run the same monitor unchanged. Measure catch fractions: hand-coded vs. learned, conservative vs. distribution-shifted.

Phase 3 — Behavioral monitor

Add a behavioral monitor above the architectural boundary. Measure the four-way failure partition: architectural-only, behavioral-only, both, neither.


Repository Layout

MAF/
├── shared/contracts/        # BTNode, GoalContext, CommandStream,
│                            # MonitorDecision, WorldState
└── maf_rover/
    ├── main.cpp             # tick thread (20 Hz)
    ├── monitor/             # RuntimeMonitor
    ├── mission/             # BehaviorTree, NavigateNode
    └── adapter/             # ArdupilotAdapter

Building

Requires CMake 3.16+, a C++20 compiler, and OpenSSL.

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

Optional flags:

Flag Default Effect
MAF_ENABLE_MAVLINK OFF Enable real MAVLink transport (requires third_party/mavlink submodule)
MAF_ENABLE_ONNXRUNTIME OFF Enable ONNX learned node support (Phase 2)

Without MAF_ENABLE_MAVLINK, the adapter runs in stub mode: a fixed pose is injected into WorldState and command writes are no-ops. The monitor and mission logic run normally.


Running Against ArduPilot SITL

(MAVLink submodule wiring in progress — see plan)

# Start ArduPilot Rover SITL
sim_vehicle.py -v Rover --console

# Run MAF
./build/maf_rover/maf_rover
# Session log written to /tmp/maf_<session_id>.log

Documentation

  • Architecture — authority boundary, data flow, monitor design
  • Plan — research goal, phase breakdown, exit criteria
  • Mission Design — task sequencing, state machine, user roles
  • High-Level Behaviors — behavior composition, multi-actuator output, coverage planning

Design Principles

The trusted surface stays small. Monitor + adapter + session log. Everything above is untrusted and swappable.

The contract is symmetric. Hand-coded and learned nodes satisfy the same interface. No runtime path differs between them below the boundary.

Every decision is attributable. The active invariant set is logged alongside every monitor outcome. The key post-mortem question — did no applicable invariant exist, or did one exist and the monitor miss it — is always answerable from the log.

The boundary outlasts the backend. maf_ardupilot_adapter is the first of a pluggable adapter family. The monitor contract doesn't care what controller is downstream.


License

MIT. See LICENSE.

About

MAF is a C++ mission execution runtime that enforces a typed authority boundary between high-level autonomy and a trusted controller — every command, hand-coded or learned, passes through the same runtime monitor before reaching the actuators.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors