Skip to content

Commit 8355610

Browse files
taroteneclaude
andauthored
refactor: remove in-process loopback; symmetric build-time transport selection (#66)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 56cdd54 commit 8355610

21 files changed

Lines changed: 623 additions & 873 deletions

File tree

.github/workflows/ci.yml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ jobs:
2525
with:
2626
components: rustfmt, clippy
2727

28+
- name: Install system libudev (required by serialport on Linux)
29+
run: |
30+
sudo apt-get update
31+
sudo apt-get install -y libudev-dev pkg-config
32+
2833
- name: Cache Cargo artifacts
2934
uses: Swatinem/rust-cache@v2
3035

@@ -37,10 +42,25 @@ jobs:
3742
- name: Run tests
3843
run: cargo test --workspace
3944

40-
- name: Smoke test loopback-demo
45+
- name: Smoke test host-pty-server
4146
run: |
42-
timeout 30 cargo run -p loopback-demo | tee /tmp/loopback-demo.out
43-
grep -qF "ping -> 0xDEADBEEF" /tmp/loopback-demo.out
47+
cargo build -p host-pty-server
48+
cargo build --manifest-path tools/telepath-shell/Cargo.toml --no-default-features --features serial
49+
cargo run -p host-pty-server > /tmp/server.out &
50+
SERVER_PID=$!
51+
trap 'kill "$SERVER_PID" 2>/dev/null || true; wait "$SERVER_PID" 2>/dev/null || true' EXIT
52+
for i in $(seq 1 15); do
53+
SLAVE=$(grep 'HOST_PTY_SERVER_PATH=' /tmp/server.out 2>/dev/null | sed 's/HOST_PTY_SERVER_PATH=//' | head -1 || true)
54+
if [ -n "$SLAVE" ]; then break; fi
55+
sleep 1
56+
done
57+
if [ -z "$SLAVE" ]; then
58+
echo "host-pty-server did not print PTY path within 15s"
59+
cat /tmp/server.out
60+
exit 1
61+
fi
62+
tools/telepath-shell/target/debug/telepath-shell --port "$SLAVE" --exec ping | tee /tmp/shell.out
63+
grep -qF "ping -> 0xDEADBEEF" /tmp/shell.out
4464
4565
- name: Test MCP server (workspace-excluded)
4666
working-directory: tools/telepath-mcp-server

AGENTS.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@
1010
| `telepath-wire` | Shared wire protocol types | server + client |
1111
| `telepath-macros` | `#[command]` proc-macro | server (build time only) |
1212
| `telepath-server` | Target-side RPC server library | `thumbv7em-none-eabi` |
13-
| `telepath-client` | Host-side RPC client library | native (`std`) |
14-
| `examples/loopback-demo` | In-process server+client loopback | native (`std`) |
13+
| `telepath-client` | Host-side RPC client library; `rtt` and `serial` Cargo features select the transport | native (`std`) |
14+
| `examples/host-pty-server` | Host-side server deployment over a PTY pair (hardware-free regression) | native (`std`) |
1515
| `examples/nrf52840-ping` | Reference server deployment on nRF52840-DK | `thumbv7em-none-eabi` |
16-
| `tools/telepath-shell` | Interactive shell for Telepath servers | native (`std`) |
17-
| `tools/telepath-mcp-server` | MCP server — discovers commands, exposes as MCP tools | native (`std`) |
16+
| `tools/telepath-shell` | Interactive shell for Telepath servers; `default = ["rtt"]`, `serial` opt-in | native (`std`) |
17+
| `tools/telepath-mcp-server` | MCP server — discovers commands, exposes as MCP tools; `default = ["rtt"]`, `serial` opt-in | native (`std`) |
1818

1919
## Build Commands
2020

2121
```
22-
# Host workspace (all 5 members including loopback-demo)
22+
# Host workspace (all 5 members including host-pty-server)
2323
cargo build --workspace
2424
25-
# Run the in-process loopback end-to-end (no hardware required)
26-
cargo run -p loopback-demo
25+
# Run host-pty-server (prints slave PTY path; connect telepath-shell --features serial to that path)
26+
cargo run -p host-pty-server
27+
28+
# Full hardware-free smoke via just (spawns server + serial shell, asserts ping)
29+
just host-pty-smoke
2730
2831
# Host tests
2932
cargo test --workspace
@@ -35,15 +38,19 @@ cd examples/nrf52840-ping && cargo build --release
3538
cd examples/nrf52840-ping && cargo run --release
3639
3740
# Shell tool (excluded from workspace — requires cd)
41+
# Default build: RTT transport (--features rtt, the default)
3842
cd tools/telepath-shell && cargo build
3943
cd tools/telepath-shell && cargo run -- ping
4044
cd tools/telepath-shell && cargo run
45+
# Serial build: CDC-ACM / PTY transport
46+
cd tools/telepath-shell && cargo build --no-default-features --features serial
47+
cd tools/telepath-shell && cargo run --no-default-features --features serial -- --port /dev/ttyACM0
4148
4249
# MCP server (excluded from workspace — requires cd)
4350
cd tools/telepath-mcp-server && cargo build
4451
cd tools/telepath-mcp-server && cargo test
45-
cd tools/telepath-mcp-server && cargo run -- --transport loopback
46-
cd tools/telepath-mcp-server && cargo run -- --transport rtt --chip nRF52840_xxAA
52+
cd tools/telepath-mcp-server && cargo run
53+
cd tools/telepath-mcp-server && cargo run --no-default-features --features serial -- --port /dev/ttyACM0
4754
4855
# Format check
4956
cargo fmt --all -- --check
@@ -68,11 +75,12 @@ just ci
6875
- Cross-compilation REQUIRES `rustup target add thumbv7em-none-eabi`.
6976
- `cargo run --release` invokes `probe-rs download` (flash + exit). The probe is released immediately so `telepath-shell` can attach.
7077

71-
### `examples/loopback-demo`
78+
### `examples/host-pty-server`
7279
- IS a workspace member (`std` target, no cross-compile). Build with `cargo build --workspace`.
73-
- MUST exercise the full wire path including COBS framing — it is the primary hardware-free regression for `telepath-server` and `telepath-client`.
80+
- MUST exercise the full wire path including COBS framing via a real PTY transport — it is the primary hardware-free regression for `telepath-server` and the serial path of `telepath-client`.
7481
- MUST use only public APIs of the dependent crates; it MUST NOT poke internal state to aid the round-trip.
75-
- CI runs `timeout 30 cargo run -p loopback-demo` and grep-asserts the `ping -> 0xDEADBEEF` output on every push.
82+
- On startup, prints `HOST_PTY_SERVER_PATH=<path>` to stdout then flushes; the test harness reads this to obtain the slave device path.
83+
- CI spawns `host-pty-server` in background, reads the slave path, runs `telepath-shell --features serial --port <path> --exec ping`, and grep-asserts `ping -> 0xDEADBEEF`.
7684

7785
### `tools/telepath-shell`
7886
- MUST be built separately; it is excluded from the workspace.
@@ -166,6 +174,6 @@ git config --local core.hooksPath .githooks
166174

167175
- `pre-commit``just fmt-check` (sub-second; runs on every commit)
168176
- `pre-push``just clippy` + `just test` (~30 s; runs before every push)
169-
- `just ci` (fmt-check + clippy + test + emulator) SHOULD be run before opening a PR.
177+
- `just ci` (fmt-check + clippy + test + host-pty-smoke + mcp-test) SHOULD be run before opening a PR.
170178
- `just firmware-ping` SHOULD additionally be run when the PR touches wire / macros /
171179
server / client / shell / nrf52840-ping (see "Commit and PR Rules" above).

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ members = [
55
"telepath-macros",
66
"telepath-server",
77
"telepath-client",
8-
"examples/loopback-demo",
8+
"examples/host-pty-server",
99
]
1010
exclude = ["examples/nrf52840-ping", "tools/telepath-shell", "tools/telepath-mcp-server"]
1111

Justfile

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,29 @@ mcp-test:
5353

5454
# Run telepath-mcp-server against a flashed nRF52840-DK (firmware must already be flashed)
5555
mcp-run-rtt:
56-
cd tools/telepath-mcp-server && cargo run -- --transport rtt
56+
cd tools/telepath-mcp-server && cargo run
5757

5858
# Build everything: workspace + firmware + CLI + MCP server
5959
check-all: build firmware-build cli-build mcp-build
6060

61-
# Run the in-process emulator end-to-end (no hardware required)
62-
emulator:
63-
cargo run -p loopback-demo
64-
65-
# Full CI gate: fmt-check + clippy + test + emulator smoke + mcp-test
66-
ci: fmt-check clippy test emulator mcp-test
61+
# Smoke test host-pty-server end-to-end via serial telepath-shell (no hardware required)
62+
host-pty-smoke:
63+
#!/usr/bin/env bash
64+
set -euo pipefail
65+
cargo build -p host-pty-server
66+
cargo build --manifest-path tools/telepath-shell/Cargo.toml --no-default-features --features serial
67+
cargo run -p host-pty-server > /tmp/host-pty-server.out &
68+
SERVER_PID=$!
69+
trap 'kill "$SERVER_PID" 2>/dev/null || true; wait "$SERVER_PID" 2>/dev/null || true' EXIT
70+
for i in $(seq 1 15); do
71+
SLAVE=$(grep 'HOST_PTY_SERVER_PATH=' /tmp/host-pty-server.out 2>/dev/null | sed 's/HOST_PTY_SERVER_PATH=//' | head -1 || true)
72+
if [ -n "$SLAVE" ]; then break; fi
73+
sleep 1
74+
done
75+
if [ -z "$SLAVE" ]; then
76+
echo "ERROR: host-pty-server did not print PTY path"; exit 1
77+
fi
78+
tools/telepath-shell/target/debug/telepath-shell --port "$SLAVE" --exec ping | tee /dev/stderr | grep -qF "ping -> 0xDEADBEEF"
79+
80+
# Full CI gate: fmt-check + clippy + test + host-pty smoke + mcp-test
81+
ci: fmt-check clippy test host-pty-smoke mcp-test

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ sequenceDiagram
5252
| `telepath-macros` | `#[command]` proc-macro |
5353
| `telepath-server` | Target-side RPC server — `no_std` |
5454
| `telepath-client` | Host-side RPC client — `std` |
55-
| `examples/loopback-demo` | In-process server+client emulator — no hardware required |
55+
| `examples/host-pty-server` | Host-side server deployment over a PTY pair — hardware-free regression |
5656
| `examples/nrf52840-ping` | Reference server deployment on nRF52840-DK (workspace-excluded) |
5757
| `tools/telepath-shell` | Interactive shell for Telepath servers — REPL and one-shot commands (workspace-excluded) |
5858
| `tools/telepath-mcp-server` | MCP server — exposes discovered commands as MCP tools (workspace-excluded) |
@@ -84,7 +84,7 @@ from an AI agent without hand-written tool descriptors.
8484
(`NamedType` serialised with postcard) over the wire.
8585
- `client.discover()` fetches all commands via CDP paging; the result lands in
8686
`SchemaCache`, keyed by command ID.
87-
- `examples/loopback-demo` exercises this end-to-end — no hardware required.
87+
- `examples/host-pty-server` exercises this end-to-end over a real PTY transport — no hardware required.
8888

8989
### MCP tool auto-generation
9090

@@ -121,7 +121,7 @@ The fastest way to see Telepath in action requires no hardware.
121121
```
122122
git clone https://github.com/tarotene/telepath.git
123123
cd telepath
124-
cargo run -p loopback-demo
124+
just host-pty-smoke
125125
```
126126

127127
Expected output:
@@ -130,10 +130,10 @@ Expected output:
130130
ping -> 0xDEADBEEF
131131
```
132132

133-
The emulator runs a `TelepathServer` and `TelepathClient` on two OS threads
134-
connected by `std::sync::mpsc` byte channels. The full wire path
135-
(postcard serialization + COBS framing) executes identically to real hardware.
136-
Switching to an MCU is purely a transport swap.
133+
`host-pty-smoke` starts `examples/host-pty-server` (a `TelepathServer` over a PTY
134+
master), then drives it from `telepath-shell --features serial` via the slave end.
135+
The full wire path (postcard serialization + COBS framing) runs identically to real
136+
hardware. Switching to an MCU is purely a transport swap.
137137

138138
## Prerequisites
139139

Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
[package]
2-
name = "loopback-demo"
2+
name = "host-pty-server"
33
version.workspace = true
44
edition.workspace = true
55
license.workspace = true
6-
description = "Hardware-free loopback demo: full wire round-trip via mpsc channels"
6+
description = "Host-side Telepath server deployment over a PTY pair (parallel to nrf52840-ping)"
77

88
[[bin]]
9-
name = "loopback-demo"
9+
name = "host-pty-server"
1010
path = "src/main.rs"
1111

1212
[dependencies]
1313
telepath-wire = { workspace = true }
1414
telepath-server = { workspace = true }
15-
telepath-client = { workspace = true }
1615
postcard = { version = "1", features = ["alloc"] }
1716
heapless = { workspace = true }
17+
anyhow = "1"
18+
nix = { version = "0.29", features = ["term", "fs"] }

examples/host-pty-server/README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# host-pty-server
2+
3+
Hardware-free regression server: full wire round-trip over a real PTY pair.
4+
5+
Opens a PTY with `openpty(3)`, prints the slave device path, then runs a
6+
`TelepathServer` on the master side in a poll loop. A client (e.g.
7+
`telepath-shell --no-default-features --features serial`) connects to the slave end and speaks the
8+
full Telepath wire protocol — COBS framing + postcard serialization — over
9+
the PTY byte stream.
10+
11+
## Run
12+
13+
```
14+
cargo run -p host-pty-server
15+
```
16+
17+
The process prints one line to stdout and then keeps running:
18+
19+
```
20+
HOST_PTY_SERVER_PATH=/dev/pts/3
21+
```
22+
23+
Connect a client to the printed path:
24+
25+
```
26+
cd tools/telepath-shell
27+
cargo run --no-default-features --features serial -- --port /dev/pts/3 --exec ping
28+
```
29+
30+
Or use `just host-pty-smoke` to run the full two-process smoke automatically.
31+
32+
## Expected output (smoke)
33+
34+
```
35+
ping -> 0xDEADBEEF
36+
```
37+
38+
## What this demonstrates
39+
40+
| Layer | Real hardware (nRF52840-DK) | This server |
41+
|---|---|---|
42+
| Transport | probe-rs RTT channel 1 | PTY master (`O_NONBLOCK` `File`) |
43+
| Framing | COBS, delimiter `0x00` | Identical — raw COBS bytes traverse the PTY |
44+
| Serialization | postcard | Identical |
45+
| Server | `TelepathServer` over `RttTransport` | `TelepathServer` over `PtyTransport` |
46+
| Client | `telepath-shell --features rtt` | `telepath-shell --features serial` |
47+
48+
The same `telepath-server` code path executes as on real hardware. Switching
49+
to a real MCU is purely a transport swap — framing and serialization are
50+
unchanged.
51+
52+
## Code structure
53+
54+
```
55+
src/
56+
└── main.rs — #[command] fns, PtyTransport impl, TelepathServer poll loop
57+
```
58+
59+
## Registered commands
60+
61+
The same four CPU-only demo commands as `examples/nrf52840-ping`:
62+
63+
| Command | Args | Return |
64+
|---------|------|--------|
65+
| `ping` || `u32` (`0xDEAD_BEEF`) |
66+
| `add` | `i32, i32` | `i32` |
67+
| `crc32` | `heapless::Vec<u8, 128>` | `u32` |
68+
| `echo` | `heapless::Vec<u8, 128>` | `heapless::Vec<u8, 128>` |

0 commit comments

Comments
 (0)