Expand description
§phoxal
A production-oriented framework for autonomous robots.
Phoxal gives a robot a small, strongly-typed core: a contract bus over Zenoh, train-selected concrete API contracts, and a participant authoring model where a role marker plus a direct trait implementation is a complete service, driver, tool, or simulator. The framework owns the awkward parts - argument parsing, bus connection, scheduling, query serving, shutdown, and health - so the code you write is the robot’s behavior, not its plumbing.
Three ideas hold it together:
- A typed contract bus. Every message is a plain serde body bound to one
version-qualified contract name. Handles are body-typed
(
StatePublisher<T>,Subscriber<T>,Latest<T>,Querier<Req, Resp>), so the compiler - not a late check - rejects sending the wrong type on a topic. Publishing is additionally gated by the contract’s temporal role: the robot time a publisher can express is fixed by what the contract is, so a participant cannot stamp an instant it never reached. - One train-selected API facade. Official participants import
phoxal::api, which names the complete concrete revision selected by the locked framework train. Contract identity is realized on the wire by the revision-qualified key (D1). - Participants are authored, not wired. A role attribute declares
identity and associated
Config/State/Apitypes; a directParticipantimplementation owns lifecycle behavior, andrunturns the marker into a binary. Useservicefor ordinary robot participants,driverfor a participant launched once perrobot.componentsentry,toolfor host-side utilities, andsimulatorfor simulation-only participants.
§Author a participant
A participant is a unit role marker, optional Config/State/Api types,
and one direct trait implementation:
use phoxal::api;
use phoxal::prelude::*;
struct Api {
state: Latest<api::drive::State>, // keep-last view of the drive state
target: CommandPublisher<api::drive::Target>, // commanded drive target
}
#[phoxal::service(id = "avoid-obstacles", api = Api)]
struct AvoidObstacles;
impl Participant for AvoidObstacles {
async fn setup(
&self,
ctx: &mut SetupContext<Self>,
_config: Self::Config,
) -> Result<(Self::State, Self::Api)> {
Ok(((), Api {
state: ctx.latest(api::topic::client().drive().state()).await?,
target: ctx.command_publisher(api::topic::client().drive().target()).await?,
}))
}
#[phoxal::step(hz = 50)]
async fn step(
&self,
api: &Self::Api,
_step: StepContext,
_state: &mut Self::State,
) -> Result<()> {
api.target.send(api::drive::Target {
linear_x_mps: 0.2,
angular_z_radps: 0.0,
curvature_limit_radpm: None,
})?;
Ok(())
}
}
fn main() -> phoxal::Result<()> { phoxal::run::<AvoidObstacles>() }What each piece does:
use phoxal::api;brings the versioned API module into scope;Apistruct fields name train-selected bodies (api::drive::Target) directly, with no participant-local version attribute to keep in sync.- The role attribute records identity and sets associated types. Omitted
Config,State, andApidefault to(). - Handles are ordinary fields built in
Participant::setupfrom typed topic builders and returned alongside mutable state. #[phoxal::step(hz = ...)]adds a cadence to the trait’s step override.ctx.query(owner_endpoint, Self::handler)registers typed query handlers; the endpoint fixes the handler’s request and response types at compile time.- The runner serializes step, query, reset, and shutdown access to
State. fn main() -> phoxal::Result<()> { phoxal::run::<R>() }is the default blocking entrypoint. For a custom Tokio main, callphoxal::tokio::run::<R>().await.
The four authoring kinds share the same metadata path but describe different runtime roles:
serviceis the ordinary typed participant surface.driveris launched once perrobot.componentsentry. Only a driver can callSetupContext::componentto read the bound component instance.toolis for host-side utilities that inspect the robot model throughSetupContext::robot. Its privileged low-level transport is available only through the capability-gatedSetupContext::busmethod.simulatoris a normal participant for simulation-only processes. It carries a distinct kind and marker for simulation clock ownership.
Worked examples live in phoxal/examples/.
§Where to look next
- The
phoxal-apicrate (phoxal::api, …) - the versioned API modules: version-local wire bodies, theApiVersion/ContractBodytraits, and the api-local topic builders, all generated byphoxal_api_tree!. A participant imports it directly withuse phoxal::api as api;. The runner also links it for framework-owned out-of-band infrastructure contracts such as bus logs. prelude- everything a participant author imports withuse phoxal::prelude::*;: the handle types,SetupContext,StepContext, andResult.bus- the typed contract vocabulary normal participants need: the key scheme, MessagePack codec,BusMetadataattachment, the four non-interchangeable time types, body-typed handles, and side-brandedTopicvalues.model- immutable canonical runtime robot facts decoded from the compiledrobot.json; authored YAML and URDF live inphoxal-manifest.- The official service set ships alongside this crate in the workspace
service/tree (drive,localize,map,safety, …): full platform participants authored on exactly this surface, useful as reference reading.
Modules§
- api
- The concrete framework API revision selected by this release train.
Concrete API revision
v0.1- version-local wire bodies + topics. - bus
- Typed contract and handle vocabulary for normal participant authoring.
- model
- Curated canonical robot model.
- prelude
- Everything a participant author imports with
use phoxal::prelude::*;. - tokio
- Async host runner entrypoint for custom Tokio mains
(
phoxal::tokio::run::<Participant>().await).
Structs§
- AssetId
- A normalized, forward-slash logical asset identifier.
- Asset
Resolver - Read-only resolver for the declared assets below
<bundle>/assets. - Reset
Context - Context for
Participant::reset: the runner observed a different timeline and is about to begin releasing steps for that world history. - Setup
Context - The sole IO-construction point, handed to
Participant::setup. - Step
Context - Per-step context: the robot instant this step reached, plus the capability to publish state at it.
Traits§
- Participant
- Participant lifecycle behavior.
Functions§
- run
- Run a participant to completion on a framework-owned blocking Tokio runtime.
Type Aliases§
- Result
- The framework result type (
anyhow-backed). Authoring code uses bareResult<T>via theprelude.Result<T, Error>
Attribute Macros§
- driver
- Link a participant state struct to its
Config/Apitypes as a component driver. The driver-shaped counterpart to [service]. - service
- Link a participant state struct to its
Config/Apitypes as a checked service. Declare a service marker’sConfig/State/Apitypes. Each omitted type defaults to(); identity defaults fromCARGO_PKG_NAME. - simulator
- Link a participant state struct to its
Config/Apitypes as a simulation participant. The simulator-shaped counterpart to [service]. - step
- Attach a cadence to
Participant::step. Attach a positive, finite frequency to the ordinaryParticipant::stepoverride. - tool
- Link a participant state struct to its
Configas a raw-bus tool (Apidefaults to()- tools stay raw-bus only). The tool-shaped counterpart to [service].ApiandConfigdefault to()- tools stay raw-bus only (decided 2026-07-09), and a configless tool can launch withoutPHOXAL_CONFIG. An explicitconfig = Typeremains required at launch unless that type itself acceptsnull(for example,Option<T>).
Derive Macros§
- Config
- Derive participant config identity from a
Configstruct (schema materialization is a later slice). Derive a compile-time Draft 2020-12 JSON Schema from the same supported#[serde(...)]attributes used byDeserialize:rename,rename_all,default, anddeny_unknown_fields. Unsupported Serde attributes are a compile error rather than an approximate schema.