Serial Tether

A daemon and CLI that lets AI agents and humans share a single serial device. Race-free RPC, raw-mode shell, UDS or TCP, cross-platform.

View on GitHub Install How it works

Why

Embedded development means a lot of staring at a serial console — kicking a bootloader, reading kernel logs, exercising firmware against a corner case. Increasingly an AI coding agent wants to do that staring too: react to a stack trace, set a U-Boot env var, drive a board through a regression suite. The naïve loop — agent describes a command in chat → human copies it into tio → human pastes the output back — is slow, brittle, and pointless.

Serial Tether's job is to hand that loop directly to the agent without elbowing the human out. The daemon takes ownership of /dev/ttyUSB0 once. From there, three audiences share the same port at the same time:

AI agents

Transactional, structured, bounded RPC. Race-free run, ANSI- and echo-stripped output, standard exit codes, configurable length truncation that protects LLM context budgets.

Humans

A tio-style raw-mode shell, plus tail to watch every byte the agent sends or receives. No "agent mode" that locks the operator out — the human gets a god's-eye view.

Scripts & CI

Shell-friendly exit codes (124 timeout, 7 unauthorized, 4 device gone, …), JSON output with a decoded text field. Same wire whether the daemon is local, behind SSH, or on a VM across the room.

Usage model: human and AI assistant viewing the same serial stream in real time
One serial port. Real-time, bi-directional access for both humans and AI.

Demo

Two humans in their own tether shells, attached to the same daemon on a single serial port. Every byte the device emits is broadcast to every session — screen -x for a U-Boot prompt.

More flavors — same idea, different right-pane

CLI + shell

Human in a tether shell on the left; on the right, a scripter running one-shot tether run / ports / config from another terminal. The scripter's commands echo live into the human's pane.

Agent + shell

Same setup, but the right pane is an LLM/agent calling tether --json run for transactional RPCs, plus a live tether config --baud toward the end.

Multiple boards on one daemon (v0.8) — different concept

One tetherd process owns N serial ports. Clients address each by an operator-chosen id (tether -d board0, tether -d board1), with per-device baud / parity / etc. inline in -D. Each device has its own ring buffer, writer lock, and event broadcast — traffic stays isolated.

Install

All four paths give you the same two binaries: tetherd (daemon) and tether (client).

Homebrew

macOS or Linuxbrew — no Rust toolchain needed.

brew install hulryung/tether/serial-tether

cargo install

Anywhere Rust runs.

cargo install serial-tether

curl installer

Pre-built tar.xz, no dependencies.

curl -fsSL https://github.com/hulryung/serial-tether/releases/latest/download/serial-tether-installer.sh | sh

Build from source

Clone and cargo build --release.

git clone https://github.com/hulryung/serial-tether
cd serial-tether
cargo build --workspace --release

Quick start

Open one terminal for the daemon, another for the client.

# Terminal 1 — daemon owns the serial port
tetherd -D /dev/tty.usbserial-XXXX -b 115200 --tcp
# banner prints reachable IPs and an auto-generated auth token

# Terminal 2 — drop into a tio-style interactive shell
tether
# Ctrl-A then Q to quit

# Or, for an AI agent / shell script:
tether --json run "version" --newline crlf -u "ASAD SOC => " --literal --timeout-ms 3000

# Or, from a remote host (lima VM, CI runner, your laptop):
TETHER_AUTH_TOKEN=<token-from-banner> \
  tether -s tcp://daemon-host:5557 status

If the daemon isn't running, tether tells you exactly how to start one. Friendly errors all the way down.

Highlights

Race-free transactions

run acquires a writer-lock at the daemon, captures the ring-buffer head before the write, and matches starting from there. No client-side composition of send + expect.

Two cursors per session

consumer_cursor for matched RPC responses and notify_cursor for live data push are independent — a tail can watch while a run matches, no race.

Auto-reconnect

Daemon retries the device with backoff if the USB hiccups; clients can pass --auto-reconnect to retry their failed RPC once after the daemon recovers.

Cross-machine

Native TCP transport with token auth. Same wire format as UDS. tetherd can listen on both at once.

Plain JSON-RPC over NDJSON

socat- and nc-debuggable. No codegen, no schema files. Per-request --log-protocol dump on the daemon side.

Agent-friendly defaults

--strip-ansi, --strip-echo, --max-output-bytes 8192, exit codes you can branch on in shell. JSON has a decoded output field — no base64.

Documentation

OVERVIEW →

Concepts, architecture, mental model, and design rationale. Read this first.

Agent Usage →

One-page command cookbook for AI agents — exit codes, JSON shape, race-free patterns, common pitfalls.

Wire Protocol →

JSON-RPC 2.0 over NDJSON specification, v1 draft. Methods, notifications, error codes.

Examples →

Five working Bash automation scripts: U-Boot bdinfo, set-and-verify env, parse help, wait for boot, detect shell.