Skip to content
tarotenePublic

About

Embedded RPC for Rust: write #[command] on the target and get a discoverable RPC server callable from a host shell or AI agent over any byte-stream transport.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Cartoon illustration of telepathy, the namesake of this remote-call library, by Irasutoya

Telepath

Embedded RPC for Rust: write #[command] on the target and get a discoverable RPC server callable from a host shell or AI agent over any byte-stream transport.

Telepath lets you expose any MCU-side logic over the wire so you can call it interactively from a host shell or an AI agent — giving you real hardware feel while validating behaviour and API ergonomics without instrumenting tests.

It splits the problem in two:

  • Server (target): command definitions and a poll loop. No shell, no scheduler integration, no allocator.
  • Client (host): telepath shell (the unified CLI) for interactive use; telepath-client lib for building your own host or MCP server frontend.

Three things make it unusual:

  1. One attribute, zero boilerplate. #[command] generates the wire shim, schema metadata, and link-time registration. No IDL, no manual sync.
  2. Schemas travel on the wire. The host discovers commands at runtime; firmware changes never silently break the client.
  3. Transport is a two-method trait. No RTOS lock-in. UART, RTT, USB-CDC, BLE, mpsc — all valid backends.

Minimal example:

#[command]
fn ping() -> u32 { 0xDEAD_BEEF }

That one attribute registers ping in the command table, generates its wire shim, and embeds its postcard schema — no further wiring needed.

Background

Telepath's wire design follows two established protocol models rather than inventing new ones: packet framing follows the ONC RPC (RFC 5531) CALL/REPLY split, and its discovery command ID reuses the CoAP (RFC 7252) Empty/NULL convention. Schema-on-the-wire discovery exists specifically so a host — human or AI agent — never needs a hand-written or out-of-band description of what a target firmware build can do.

Architecture

sequenceDiagram
    participant H as Host (telepath-client)
    participant T as Target Server (telepath-server)

    Note over T: Link time: #[command] collects CommandMetadata
    H->>T: Connect (transport open)
    H->>T: Request [CmdID: 0x0000] — Discovery
    T-->>H: Response — command list {id, name}

    Note over H: Build dynamic client map

    H->>T: Request [CmdID: N] — postcard-encoded args
    T->>T: Deserialize args → dispatch → serialize result
    T-->>H: Response [status, postcard-encoded payload]
    H->>H: Decode payload → present to caller
Loading

Workspace structure

Telepath is a five-crate workspace (telepath-wire, telepath-macros, telepath-server, telepath-client, examples/host-pty-server) plus two workspace-excluded crates (tools/telepath, examples/nrf52840-ping). See AGENTS.md § Workspace Overview for the full table with target triples and Cargo feature flags.

Framing

Direction Method Rationale
Host → Target COBS Minimal decoder on MCU: read_until(0x00)
Target → Host rzCOBS Removes trailing-zero overhead; improves throughput for sparse sensor data

Both directions use 0x00 as the frame delimiter.

Packet model

Two packet types only (Request / Response), following the ONC RPC RFC 5531 CALL/REPLY model. Errors live in ResponseStatus, not as separate packet types. CmdID 0x0000 is reserved for the Command Discovery Protocol (CDP).

Agent-ready by design

Telepath's wire protocol is designed so that a host can enumerate commands and their full type signatures at runtime — the foundation needed to drive a Telepath server from an AI agent without hand-written tool descriptors.

Schemas on the wire

  • DiscoveryEntry.args_schema / ret_schema carry real postcard-schema bytes (NamedType serialised with postcard) over the wire.
  • client.discover() fetches all commands via CDP paging; the result lands in SchemaCache, keyed by command ID.
  • examples/host-pty-server exercises this end-to-end over a real PTY transport — no hardware required.

MCP tool auto-generation

The telepath mcp subcommand auto-generates MCP tool descriptors from live #[command] metadata — zero hand-written tool definitions required. It discovers every command and its postcard schema at runtime, maps each schema to a JSON Schema for the tool's input, and bridges MCP tool calls to RPC calls by converting JSON arguments to postcard and the response back to JSON.

See the MCP integration guide for the pipeline, the schema mapping, and running it, and the telepath CLI README for the subcommand's usage.

Install

Prerequisites

Tool Purpose
Rust stable Build host workspace
rustup target add thumbv7em-none-eabi Firmware cross-compilation
probe-rs Flash and run firmware on nRF52840-DK
just Task runner (optional but recommended)
cocogitto Conventional Commits enforcement (commit-msg hook)

MSRV: See Supported Rust Version below for the declared Minimum Supported Rust Version and policy.

The unified CLI ships three install paths:

Method Command Notes
Source build cargo install telepath Compiles from crates.io. Minutes; no extra tooling.
Pre-built binary cargo binstall telepath Fetches the matching archive from GitHub Releases. Requires cargo-binstall.
Manual download See below No Rust required.

Manual download — choose your target triple and run:

VERSION=0.2.1  # replace with desired release
TARGET=x86_64-unknown-linux-gnu  # or: aarch64-unknown-linux-gnu, aarch64-apple-darwin
curl -L "https://github.com/tarotene/telepath/releases/download/v${VERSION}/telepath-${TARGET}.tar.gz" \
  | tar -xzf -
./telepath-${TARGET}/telepath --help

Windows: download telepath-x86_64-pc-windows-msvc.zip from the Releases page and extract.

macOS Intel (x86_64-apple-darwin) users: pre-built binaries are not available yet — use cargo install telepath instead.

Pre-built binaries ship with default features (shell + mcp + rtt). The serial transport requires libudev on Linux and is available only via source build: cargo install telepath --features serial.

Usage

Quickstart

The fastest way to see Telepath in action requires no hardware.

git clone https://github.com/tarotene/telepath.git
cd telepath
just host-pty-smoke

Expected output:

ping -> 0xDEADBEEF

host-pty-smoke starts examples/host-pty-server (a TelepathServer over a PTY master), then drives it from telepath shell --transport serial via the slave end. The full wire path (postcard serialization + COBS framing) runs identically to real hardware. Switching to an MCU is purely a transport swap.

Using telepath as a library

Server side (target)

Annotate functions with #[command] and pass them to TelepathServer::new in a poll() loop:

use telepath_server::{command, TelepathServer};

#[command]
fn ping() -> u32 { 0xDEAD_BEEF }

let mut server = TelepathServer::<MyTransport, 512>::new(transport, telepath_server::commands());
loop { server.poll(); }

See telepath-server § Usage for the full recipe including #[resource] injection and the postcard direct-dependency requirement.

Host side

[dependencies]
telepath-client = { git = "https://github.com/tarotene/telepath", branch = "main", features = ["rtt"] }
use telepath_client::TelepathClient;

// transport: anything implementing `std::io::Read + std::io::Write`
let mut client = TelepathClient::new(transport);

// Discover commands registered on the target, then issue a typed call.
client.discover()?;
let ping_id = client.cmd_id_by_name("ping").expect("ping not registered");
let val: u32 = client.call::<(), u32>(ping_id, &())?;
println!("ping -> 0x{:08X}", val);

Real hardware: nRF52840-DK

See examples/nrf52840-ping/README.md for the full hardware walk-through (udev rules, APPROTECT unlock, RTT channel layout).

# Flash firmware (downloads and exits; probe is released)
cd examples/nrf52840-ping && cargo run --release

# Ping over RTT (RPC traffic on channel 1)
cd tools/telepath && cargo run -- shell --exec ping

Scope

In:

  • The #[command] macro and the server/client/wire crates
  • The unified telepath CLI (shell REPL + mcp server, auto-generated tool descriptors)
  • Transport backends (UART/RTT/USB-CDC/BLE/PTY)
  • On-wire schema discovery (CDP)

Out:

  • Board support and specific firmware application logic beyond reference examples
  • RTOS/scheduler integration
  • Repository governance templates (the responsibility of a separate Rust repository governance skill, such as the one used by dotfiles)

Development

Git hooks setup

The repository ships hooks under .githooks/ that enforce quality gates at commit and push time. They are not active by default — Git reads hooks from .git/hooks/ unless told otherwise.

Run once per clone to wire them up:

git config --local core.hooksPath .githooks

The three hooks (commit-msg, pre-commit, pre-push) and their expected wall times are documented in AGENTS.md § Git Hooks.

Why this split? Commits happen frequently, so pre-commit runs only the instant format check. Pushes are less frequent and signal intent to share code, so pre-push runs the slower static analysis and test suite. The full CI gate (just ci) additionally runs the PTY-based host-pty-server smoke (host-pty-smoke) and the telepath CLI tests (mcp-test), and is intentionally left to CI — see CI / Quality gates. commit-msg fires before pre-commit and validates only the message format via cocogitto — instant feedback with no build step.

Troubleshooting

Hook does not run. Check git config core.hooksPath. If it prints a path other than .githooks (e.g. ~/.config/git/hooks set globally), the repo-local setting above overrides it once you run the command.

just: command not found. Install via cargo install just or your OS package manager.

cog: command not found. Install cocogitto via cargo install --locked cocogitto.

Bypass in an emergency. Pass --no-verify to skip hooks: git commit --no-verify. The CI gate still applies on every PR.

Build

The full list of build and test commands — host workspace, firmware example, the workspace-excluded tools/telepath CLI, format/clippy gates, and the just ci aggregator — lives in AGENTS.md § Build Commands. For first-time setup, run through Quickstart above first.

CI / Quality gates

Run just ci locally before opening a PR — it covers format check, clippy, tests, the PTY smoke, and the CLI tests. Full gate details and individual commands live in AGENTS.md § Build Commands and AGENTS.md § Required CI gates.

See CHANGELOG.md for the project history.

Releases

Telepath uses unified versioning: all workspace members share the same vX.Y.Z version and are released together as a single GitHub Release tagged vX.Y.Z.

Releases are driven by Conventional Commits on main — no manual tagging required. For the full developer flow see AGENTS.md § How releases work.

Supported Rust Version

Telepath declares a Minimum Supported Rust Version (MSRV) of 1.88. This applies to all workspace members and the excluded crates (tools/telepath, examples/nrf52840-ping). The MSRV is verified in CI via the msrv job (dtolnay/rust-toolchain@1.88.0).

A bump to the MSRV is treated as a MINOR change under SemVer for pre-1.0 releases. See AGENTS.md § Toolchain for the full MSRV policy including manifest updates and commit convention.

Dependency updates

Renovate opens dependency-bump PRs every Monday morning (JST). Per the account-wide policy preset that renovate.json extends, non-major updates (except 0.x minor) auto-merge once the required checks pass; major and 0.x minor updates stay human-reviewed PRs. Embedded HAL updates (embassy-*, nrf-pac, cortex-m-rt, …) carry the needs-smoke-test label and require an on-device just firmware-ping run on nRF52840-DK before merging.

License

Licensed under either of

at your option.

The banner illustration (docs/assets/readme-banner.png) is by Irasutoya (source page) and is not covered by this repository's license; it is used under the Irasutoya terms of use.

About

Embedded RPC for Rust: write #[command] on the target and get a discoverable RPC server callable from a host shell or AI agent over any byte-stream transport.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages