# phoxal
The Phoxal robot framework as one crate: a manifest-driven stack for
production-oriented autonomous robots — a typed Zenoh bus, a derive-based runtime
authoring model, and a single, dated API-version contract set.
> **Greenfield rewrite in progress** (`tmp/framework-rewrite`). The bus + runtime
> engine and the macros are new; the official runtime/simulator/tool crates are
> being re-ported onto this surface and are temporarily out of the workspace.
```toml
[dependencies]
phoxal = "0.11"
```
## The authoring model
A runtime is **one struct of typed handles + one annotated inherent impl**. The
struct declares the contracts it uses (as handle fields) and the one API version
it runs against; the impl declares the lifecycle.
```rust,ignore
use phoxal::api::y2026_1 as api;
use phoxal::prelude::*;
#[derive(phoxal::Runtime)]
#[phoxal(id = "avoid-obstacles", api = y2026_1)]
struct AvoidObstacles {
state: Latest<api::drive::State>,
target: Publisher<api::drive::Target>,
}
#[phoxal::runtime]
impl AvoidObstacles {
#[setup]
async fn setup(ctx: &mut SetupContext<Self>) -> Result<Self> {
Ok(Self {
state: ctx.subscribe(api::topic::new().drive().state()).latest().await?,
target: ctx.publisher(api::topic::new().drive().target()).await?,
})
}
#[step(hz = 50)]
async fn step(&mut self, step: StepContext) -> Result<()> {
let now = step.time();
// read inputs, publish version-local bodies
self.target.publish_at(now, api::drive::Target {
linear_x_mps: 0.2, angular_z_radps: 0.0,
}).await?;
Ok(())
}
}
fn main() -> phoxal::Result<()> { phoxal::run::<AvoidObstacles>() }
```
Key rules the example shows:
- Import exactly one dated API module (`use phoxal::api::y2026_1 as api;`) and
declare it on the derive (`#[phoxal(api = y2026_1)]`, **mandatory**).
- Handle fields use version-local bodies: `Publisher<api::drive::State>`,
`Latest<api::drive::Target>`. The derive emits a `ContractBody<Api = R::Api>`
assertion, so a body from another API version is a **compile error**.
- Topics are api-local: `api::topic::new().drive().state()`.
- The wire body is the plain payload; `api_version`, family, and codec ride bus
metadata. Normal runtimes never open Zenoh — the runner opens the
bundle-selected bus profile before `#[setup]`.
- `cargo run --example runtime_control_loop emit-apis` prints the runtime's
static metadata as one JSON document (the `emit-apis` subcommand) and exits.
## Modules
| [`phoxal::api`] | Dated API-version modules (`y2026_1`, …) generated by `phoxal_api_tree!`: the marker `enum Api` (`ApiVersion`), version-local wire bodies + their `ContractBody` impls, and the api-local `topic` builders. |
| [`phoxal::bus`] | The Zenoh-native `bus_abi` boundary: the key scheme `<namespace>/robots/<robot-id>/<topic>`, the MessagePack codec, the `BusMetadata` attachment, and the body-typed handles `Publisher`/`Subscriber`/`Latest`. |
| [`phoxal::runtime`] | The static metadata traits the macros target, `SetupContext`/`StepContext`, the clock (`RealClock`/`TestClock`) + scheduler, `ParticipantLaunch`, `emit-apis`, and the runner (`run` / `tokio::run`). |
| [`phoxal::model`] | Authored manifest schemas (`robot.yaml`, `structure.urdf`, …). |
## Entrypoints
- Default (blocking): `fn main() -> phoxal::Result<()> { phoxal::run::<R>() }`.
- Advanced (async): `phoxal::tokio::run::<R>().await` for custom Tokio mains.
## Status
Pre-1.0, building in public. One API version per robot graph (`#[phoxal(api =
y2026_1)]`); the wire body is the plain payload and the version identity rides bus
metadata, never the key. Compatibility is checked at build/check time
(`emit-apis` + `phoxal-cli check`), never by introspecting a running binary.
## License
AGPL-3.0-only. A commercial license is available — see the repository.