Skip to content

Commit b8ec87b

Browse files
committed
Add plan record/replay; sweep top-level + CLI READMEs
Phase 3 of the trio. ubtctl ask can now capture its tool-call trace to a JSON file; ubtctl plan show/run replay it later without going back to the LLM. Mutating steps are gated behind --yes — read-only plans run unattended, side-effecting plans require explicit confirmation. - cli/ubtctl/tools: Spec gains a Mutating flag. NewMutating[T] wraps the constructor; send_payload is the only tool currently flagged. - cli/ubtctl/ai/plan.go: Plan + Step JSON shape (format_version: 1), recording wrapper that decorates Spec.Handler to append a Step on every call, SavePlan / LoadPlan / Replay with pre-flight mutating- step gate that fails fast instead of running half a plan. - cli/ubtctl/ai/planner.go: ai.Plan renamed to ai.RunConfig (avoids collision with the new on-disk Plan); SavePath wires recording in via wrapWithRecorder when set. - cli/ubtctl/commands/ask.go: --save FILE flag. - cli/ubtctl/commands/plan.go: 'ubtctl plan show' (pretty-print), 'ubtctl plan run' (replay) with --socket / --dry-run / --yes. - Smoke-tested all four paths: show formats; --dry-run prints without contacting ubtd; gate refuses mutating plans without --yes; --yes runs the captured plan against the stub driver and matches the recorded result. format_version mismatch is rejected cleanly. - README sweep: top-level README rewritten to reflect everything that has actually shipped (Go daemon, typed CLI, AI planner, MCP server, plan replay, linuxrfcomm driver) instead of "future Go SDK" placeholders. cli/ubtctl/README.md gains plan + MCP sections.
1 parent 3abeafa commit b8ec87b

8 files changed

Lines changed: 508 additions & 136 deletions

File tree

README.md

Lines changed: 113 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,145 @@
11
# Universal Bluetooth SDK
22

3-
Cross-language, multi-service toolkit for building Bluetooth solutions (RFCOMM
4-
today, BLE-ready tomorrow). The Python SDK is production-ready; other folders
5-
are scaffolding for future Go/Rust SDKs, microservices, and CLI tooling.
3+
Cross-language, cross-platform toolkit for building Bluetooth solutions. The
4+
control plane is a single long-lived daemon (`ubtd`) that owns every radio
5+
session; everything else — the typed CLI, the AI planner, the MCP server, the
6+
language SDKs — talks to it through one versioned wire format.
7+
8+
```
9+
ubtctl ──┐ ┌── BlueZ (Linux)
10+
ubtctl ask│ ──UDS── ubtd ─── TransportDriver ────────┤
11+
ubtctl mcp│ │ ├── CoreBluetooth (TODO)
12+
MCP client┘ └── audit / policy / sessions └── WinRT (TODO)
13+
14+
└── Python / Go / Rust SDKs (same protocol)
15+
```
16+
17+
## What's working today
18+
19+
- **`ubtd`** — Go daemon, Unix-socket control plane, pluggable transport
20+
drivers, structured slog, signal-driven shutdown.
21+
- `--driver stub` — in-memory reference driver (any host).
22+
- `--driver linuxrfcomm` — BlueZ-backed RFCOMM via the kernel's
23+
`AF_BLUETOOTH` socket; `Discover` enumerates known peers via
24+
`bluetoothctl devices`.
25+
- **`ubtctl`** — Go CLI. One binary, several front-ends:
26+
- Typed verbs: `ping`, `version`, `status`, `capabilities`,
27+
`discover`, `send`.
28+
- **AI planner**: `ubtctl ask "<goal>"` runs Claude Opus 4.7 with
29+
adaptive thinking against a tool registry that is 1:1 with the
30+
daemon's RPC surface.
31+
- **MCP server**: `ubtctl mcp` exposes the same tool registry over
32+
JSON-RPC 2.0 on stdio, so any MCP-aware editor or agent (Claude
33+
Desktop, Cursor, Zed, …) can drive ubtd directly.
34+
- **Plan record/replay**: `ubtctl ask --save plan.json …` captures
35+
every tool call; `ubtctl plan show / run` replay it later without
36+
going back to the LLM. Mutating steps are gated behind `--yes`.
37+
- **Python SDK (`sdk/python`)** — production-ready PyBluez SDK kept around
38+
as the reference implementation and as a path the daemon can shell out to
39+
on hosts where a native driver isn't ready yet.
40+
- **Common contract (`common/protocol/`)**`v1.proto` IDL (future
41+
gRPC) plus `framing.md` describing the v1 wire (length-prefixed JSON
42+
over UDS).
643

744
## Repository layout
845

946
```
1047
.
48+
├── cli/
49+
│ ├── ubtd/ # Go daemon (UDS server, dispatcher)
50+
│ └── ubtctl/ # Go CLI (typed verbs, ai planner, mcp server)
51+
│ ├── ai/ # Claude tool runner + plan record/replay
52+
│ ├── client/ # daemon client (length-prefixed JSON codec)
53+
│ ├── commands/ # subcommand registry (ping/status/.../ask/mcp/plan)
54+
│ ├── mcp/ # JSON-RPC 2.0 MCP server (stdio)
55+
│ └── tools/ # neutral Spec/Registry shared by ai + mcp
1156
├── sdk/
12-
│ ├── python/ # fully implemented PyBluez SDK
13-
│ ├── go/ # placeholder for Go SDK
14-
│ └── rust/ # placeholder for Rust SDK
57+
│ ├── go/pkg/
58+
│ │ ├── protocol/ # wire envelope + codec
59+
│ │ ├── sockaddr/ # default socket location
60+
│ │ └── transport/ # Driver port + Registry
61+
│ │ ├── stub/ # in-memory reference driver
62+
│ │ └── linuxrfcomm/ # BlueZ-backed RFCOMM (Linux only)
63+
│ ├── python/ # production-ready PyBluez SDK
64+
│ └── rust/ # planned
1565
├── microservices/
16-
│ ├── grpc-server/ # future gRPC control-plane
17-
│ └── rest-server/ # future REST façade
18-
├── cli/
19-
── ubtctl/ # future CLI tool
20-
├── examples/ # scenario-specific samples
21-
├── common/ # shared protocols & schemas
22-
└── docs/ # architecture & guides
66+
│ ├── grpc-server/ # planned (REST/gRPC façade for remote callers)
67+
│ └── rest-server/
68+
├── common/
69+
── protocol/ # v1.proto + framing.md
70+
│ └── message-schema/
71+
├── examples/ # scenario samples (chat, sensor stream, file xfer)
72+
└── docs/
2373
```
2474

25-
## Python SDK (sdk/python)
26-
27-
### Prerequisites
28-
29-
PyBluez + BlueZ on Linux (tested on Raspberry Pi OS / Ubuntu). Install via the
30-
helper script:
75+
## Quick start (Go)
3176

3277
```bash
33-
cd sdk/python
34-
sudo ./scripts/install_dependencies.sh
35-
```
36-
37-
Or install packages manually (Debian/Ubuntu):
38-
39-
```
40-
sudo apt-get install python3 python3-dev python3-pip ipython3
41-
sudo apt-get install bluetooth libbluetooth-dev bluez bluez-tools blueman
42-
sudo python3 -m pip install pybluez
43-
```
44-
45-
### Usage
78+
go build -o bin/ubtd ./cli/ubtd
79+
go build -o bin/ubtctl ./cli/ubtctl
4680

47-
Run commands from `sdk/python` (or set `PYTHONPATH` accordingly).
81+
# 1. Start the daemon. Use `stub` for a hardware-free dev loop;
82+
# on Linux, switch to linuxrfcomm to talk to real radios.
83+
./bin/ubtd --socket /tmp/ubtd.sock --driver stub &
4884

49-
1. **Start the server (e.g., on Raspberry Pi)**
50-
```bash
51-
cd sdk/python
52-
sudo python3 run_server.py
53-
```
54-
- Customize behaviour via `bluetooth_service/config.py` (`ServerSettings`).
55-
- Use `LOG_CFG=/path/to/logger.json` to override the default logger config.
85+
# 2. Drive it from the typed CLI.
86+
UBTD_SOCKET=/tmp/ubtd.sock ./bin/ubtctl status
87+
UBTD_SOCKET=/tmp/ubtd.sock ./bin/ubtctl discover --scan-timeout 3
5688

57-
2. **Send data from the client**
58-
```bash
59-
cd sdk/python
60-
sudo python3 run_client.py
61-
```
62-
- Update `bluetooth_service/client_config.py` for UUID, discovery retries,
63-
payload source, etc.
64-
- Default payload is `text.json`; inject your own `DataSource` for custom
65-
payloads.
89+
# 3. Or drive it from natural language (requires ANTHROPIC_API_KEY).
90+
ANTHROPIC_API_KEY=... ./bin/ubtctl ask \
91+
--save /tmp/last.plan.json \
92+
"show me the daemon status and list any nearby devices"
6693

67-
3. **Run unit tests (no Bluetooth hardware required)**
68-
```bash
69-
cd sdk/python
70-
python3 -m pip install pytest
71-
pytest tests/
72-
```
73-
Tests use socket stubs to stay deterministic on any host.
74-
75-
## Platform setup tips
76-
77-
Make your Raspberry Pi discoverable:
78-
79-
```
80-
sudo hciconfig hci0 piscan
81-
```
82-
83-
Run the classic inquiry example (optional):
94+
# 4. Replay the captured plan against the same daemon — no LLM, no spend.
95+
UBTD_SOCKET=/tmp/ubtd.sock ./bin/ubtctl plan show /tmp/last.plan.json
96+
UBTD_SOCKET=/tmp/ubtd.sock ./bin/ubtctl plan run /tmp/last.plan.json
8497

98+
# 5. Or expose the same tool registry over MCP for editors / external agents.
99+
./bin/ubtctl mcp --socket /tmp/ubtd.sock # speaks JSON-RPC on stdio
85100
```
86-
sudo python inquiry.py
87-
```
88-
89-
## Known Issues
90-
91-
```
92-
Traceback (most recent call last):
93-
File "/usr/share/doc/python-bluez/examples/simple/rfcomm-server.py", line 20, in <module>
94-
profiles = [ SERIAL_PORT_PROFILE ],
95-
File "/usr/lib/python2.7/dist-packages/bluetooth/bluez.py", line 176, in advertise_service
96-
raise BluetoothError (str (e))
97-
bluetooth.btcommon.BluetoothError: (2, 'No such file or directory')
98-
```
99-
100-
## Possible fixes
101101

102-
Make sure you are using sudo when running the python script
103-
Make sure you have the serial profile loaded. How to enable the serial profile.
102+
For per-command flags and MCP client config, see
103+
[`cli/ubtctl/README.md`](cli/ubtctl/README.md).
104104

105-
As it turns out, the culprit is bluetoothd, the Bluetooth daemon. Using SDP with bluetoothd requires deprecated features for some silly reason, so to fix this, the daemon must be started in compatibility mode with bluetoothd -C (or bluetooth --compat).
106-
107-
You need to run the Bluetooth daemon in 'compatibility' mode. Edit /lib/systemd/system/bluetooth.service and add '-C' after 'bluetoothd'. Reboot.
108-
109-
```
110-
sudo sdptool add SP
111-
```
112-
113-
Or
114-
115-
Find location of bluetooth.service by:
116-
117-
```
118-
systemctl status bluetooth.service
119-
```
120-
Then edit bluetooth.service and look for ExecStart=/usr/libexec/bluetooth/bluetoothd
121-
Append --compat at the end of this line, save, and then run
122-
123-
```
124-
service bluetooth start
125-
```
126-
127-
If all goes well, you should be able to successfully run
128-
129-
```
130-
sudo sdptool browse local
131-
```
105+
## Python SDK (sdk/python)
132106

133-
Finally, reset the adapter:
107+
Production-ready PyBluez RFCOMM client/server. Tested on Raspberry Pi OS and
108+
Ubuntu. Detailed setup, troubleshooting, and the full PyBluez fix-up notes
109+
live in [`sdk/python/README.md`](sdk/python/README.md).
134110

135-
```
136-
sudo hciconfig -a hci0 reset
111+
```bash
112+
cd sdk/python
113+
sudo ./scripts/install_dependencies.sh
114+
sudo python3 run_server.py
115+
sudo python3 run_client.py
137116
```
138117

139118
## Roadmap
140119

141-
- Flesh out Go/Rust SDKs under `sdk/`.
142-
- Build microservices (gRPC + REST) that wrap the SDK for remote control.
143-
- Ship `ubtctl` CLI for device management.
144-
- Provide ready-to-run examples (chat, sensor stream, file transfer).
120+
- **CoreBluetooth (macOS) and WinRT (Windows) drivers** — same `Driver`
121+
interface as `linuxrfcomm`; the daemon already advertises the capability
122+
matrix at runtime.
123+
- **`Listen` / `Reply` RPCs** — bidirectional RFCOMM sessions for chat /
124+
long-lived data streams (foundation for an offline Bluetooth chat app
125+
with a local-AI assist).
126+
- **gRPC v2 wire** — generated from `common/protocol/v1.proto`,
127+
alongside the JSON-over-UDS v1 wire during migration.
128+
- **Native Go and Rust SDKs** — same `protocol` package the daemon and CLI
129+
already use.
130+
- **`microservices/{grpc,rest}-server`** — remote control planes that
131+
re-export the daemon surface.
145132

146-
<!-- CONTRIBUTING -->
147133
## Contributing
148134

149-
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. Please read the [contribution guidelines](https://github.com/sraodev/super-opensource-cheat-sheets/blob/master/contributing.md) first.
150-
151-
## Reference
152-
153-
[Bluetooth Programming with Python 3](http://blog.kevindoran.co/bluetooth-programming-with-python-3)
154-
155-
[Bluetooth programming with Python - PyBluez](https://people.csail.mit.edu/albert/bluez-intro/x232.html)
135+
Issues and PRs welcome. Read the
136+
[contribution guidelines](https://github.com/sraodev/super-opensource-cheat-sheets/blob/master/contributing.md)
137+
first.
156138

157-
[Bluetooth for Programmers](http://people.csail.mit.edu/rudolph/Teaching/Articles/PartOfBTBook.pdf)
139+
## References
158140

159-
[Bluetooth Python extension module](https://github.com/karulis/pybluez)
141+
- [Bluetooth Programming with Python 3](http://blog.kevindoran.co/bluetooth-programming-with-python-3)
142+
- [Bluetooth Programming with Python — PyBluez](https://people.csail.mit.edu/albert/bluez-intro/x232.html)
143+
- [Bluetooth for Programmers](http://people.csail.mit.edu/rudolph/Teaching/Articles/PartOfBTBook.pdf)
144+
- [PyBluez](https://github.com/karulis/pybluez)
145+
- [Model Context Protocol](https://modelcontextprotocol.io/)

cli/ubtctl/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ UBTD_SOCKET=/tmp/ubtd.sock ./bin/ubtctl send --address AA:BB:CC:DD:EE:01 --data
4848
| `send` | Push a payload (`--data`, `--file`, or stdin) |
4949
| `ask` | Natural-language goal → AI planner → daemon RPCs |
5050
| `mcp` | Serve the tool registry over MCP on stdio |
51+
| `plan` | Show / replay a saved AI plan (no LLM) |
5152

5253
Run `ubtctl <command> -h` for per-command flags.
5354

@@ -71,6 +72,33 @@ The system prompt + tool list are kept stable across runs and marked with
7172
`cache_control: ephemeral`, so subsequent invocations read the cached prefix
7273
instead of paying for it.
7374

75+
## Plan record / replay (`ubtctl ask --save` + `ubtctl plan`)
76+
77+
Every `ubtctl ask` run can capture its tool-call trace into a JSON file.
78+
The saved plan is human-readable, version-controllable, and replayable
79+
against the daemon **without going back to the LLM** — useful for
80+
auditing, runbooks, CI smoke-tests, and "I want to do that exact thing
81+
again."
82+
83+
```bash
84+
# 1. Run the AI once and save the trace.
85+
ubtctl ask --save /tmp/morning-check.plan.json \
86+
"ping the daemon, list capabilities, and discover devices"
87+
88+
# 2. Pretty-print the captured plan.
89+
ubtctl plan show /tmp/morning-check.plan.json
90+
91+
# 3. Replay it. Read-only steps run by default; mutating steps require --yes.
92+
ubtctl plan run /tmp/morning-check.plan.json # blocked if the plan
93+
# contains send_payload
94+
ubtctl plan run --yes /tmp/morning-check.plan.json # allowed
95+
ubtctl plan run --dry-run /tmp/morning-check.plan.json # never contacts ubtd
96+
```
97+
98+
Plans are versioned (`format_version: 1`); replay refuses unknown versions
99+
rather than mis-execute. Mutating steps carry a `mutating: true` flag in
100+
the JSON so reviewers can diff for side-effects before approving a script.
101+
74102
## MCP server (`ubtctl mcp`)
75103

76104
Exposes the same tool registry the in-process AI planner uses, but over the

0 commit comments

Comments
 (0)