leash-harness 0.1.1

Composable local-LLM and robotics harness with MCP, CLI, and safe robot adapters
Documentation

leash

Composable local-LLM and robotics harness. Rust-first, MCP-first, simulation-safe.

Leash is a Rust harness runtime that lets LLM agents control robots through typed modules, safety gates, and a shared capability registry. Run in simulation with zero hardware, or connect physical robots behind explicit actuation gates.

cargo install leash-harness
leash list               # built-in stacks
leash run sim-mcp        # MCP stdio for LLM agents
leash run sim-http       # localhost HTTP + WebSocket
leash serve mcp-http     # localhost MCP JSON control surface

Why Leash

  • Simulation-safe by default. CI, demos, and smoke tests require zero hardware. Physical actuation is an explicit opt-in gate.
  • MCP-native. Agents get 8 typed tools (health, capabilities, modules, observe, invoke_capability, stop, estop, capture) over stdio or localhost MCP HTTP.
  • Safety gates at every layer. Deadman switch, estop, soft odometry limits, physical actuation gate. Policy-gated capability invocation.
  • Feature-gated hardware. Waveshare UGV today, MAVLink drone + manipulator planned. No hardware compiles without explicit --features.
  • Stack catalog. Runnable sim, MCP, HTTP, and compatibility demos. leash list + leash run <stack>.
  • Module graph with typed streams. Modules declare inputs, outputs, lifecycle, health, and selected stream transport. Coordinator manages startup/shutdown order.
  • Local stream transport. Module streams can use local-pubsub for async fan-out or memory for deterministic tests.
  • Deterministic replay. JSONL record/replay fixtures can drive HTTP and MCP observe paths in non-physical replay mode.

Repository Map

flowchart TB
  root["repo root\nCargo crate, package metadata, top-level README"] --> src["src/\nReusable harness library"]
  root --> cli["src/bin/\nleash CLI entrypoint"]
  root --> scripts["scripts/\nSmoke tests and bot installer"]
  root --> docs["docs/\nOperator, release, and source-map docs"]
  root --> examples["examples/\nSafe runnable examples and fixtures"]
  root --> specs["specs/leash/\nDotDog project graph source and compiled DAG"]
  root --> workflows[".github/workflows/\nCI and release automation"]

  src --> runtime["runtime.rs\nHarness state, drivers, telemetry"]
  src --> capability["capability.rs\nSafety classes and invocation policy"]
  src --> http["http.rs\nHTTP, SSE, WebSocket routes"]
  src --> mcp["mcp.rs\nMCP tools and transport"]
  src --> config["config.rs\nProfiles, env, CLI precedence"]

Runtime Map

flowchart LR
  human["Human operator\nCLI or curl"] --> surfaces["Control surfaces\nCLI, HTTP, MCP"]
  agent["LLM agent\nMCP stdio or MCP HTTP"] --> surfaces
  surfaces --> registry["CapabilityRegistry\nschemas, safety class, policy"]
  registry --> harness["Harness runtime\ncommand state, modules, telemetry"]
  harness --> drivers{"Profile driver"}
  drivers --> sim["sim\nno hardware"]
  drivers --> replay["replay\nJSONL fixtures"]
  drivers --> ugv["waveshare-ugv\n/dev/ttyTHS1 serial"]
  harness --> streams["Telemetry streams\nHTTP, SSE, WebSocket, MCP observe"]
  registry --> safety["Safety gates\ntoken, approval, dry-run, deny, estop, deadman"]

Quick Start

# Install
cargo install leash-harness

# List built-in stacks
leash list

# Run MCP in simulation (zero hardware)
leash run sim-mcp

# Run with HTTP + WebSocket
leash run sim-http
leash agent-send "inspect the battery"
leash agent-interactive

# Run as a daemon and inspect JSONL logs
leash run sim-http --daemon
leash log sim-http --json --module http --lines 20

# Run MCP HTTP for agent or CLI tooling
leash serve mcp-http --listen 127.0.0.1:9990

# Check health
leash health --url http://127.0.0.1:8000

# Replay a deterministic fixture
leash replay examples/replay/sim-basic.jsonl --speed 10

MCP Tools

Tool Description
health Harness health and safety state
capabilities Endpoints, MCP tools, speed modes
modules Module graph and stream metadata
observe Latest telemetry frame (odometry, battery, sensors)
invoke_capability authorize, drive, stop, estop, estop_reset, speed_mode
stop Non-latching zero-speed motor stop
estop Latch emergency stop until reset
capture Deterministic frame capture

MCP HTTP And CLI

leash serve mcp-http --listen 127.0.0.1:9990
leash mcp list-tools
leash mcp status --url http://127.0.0.1:9990
leash mcp modules
leash mcp call health
leash mcp call invoke_capability capability=authorize token=demo ttl_secs=30
leash mcp call invoke_capability --json '{"capability":"speed_mode","speed_mode":"low"}'

/mcp/tools, /mcp/status, /mcp/modules, and /mcp/call return JSON for local agents and automation. Module/tool mapping exposes tool names by module without returning pilot session tokens.

HTTP Endpoints

GET  /                    Local command center dashboard
GET  /dashboard            Local command center dashboard
POST /dashboard/authorize  Authorize pilot token from dashboard form
POST /dashboard/stop       Stop through shared capability registry
POST /dashboard/estop      Latch estop through shared capability registry
POST /dashboard/estop-reset  Clear estop through shared capability registry
POST /dashboard/capture    Capture frame through shared capability registry
GET  /health              Harness health
GET  /capabilities         Endpoints + tools + stream transport
GET  /telemetry            Latest TelemetryFrame
GET  /events/telemetry     Server-sent telemetry stream
GET  /sse/telemetry        Alias for /events/telemetry
GET  /agent                Local web input form
GET  /agent/messages       Recent agent input messages
POST /agent/messages       { source, text }
POST /drive               { token, left, right, speed_mode, approval }
POST /estop                Latch emergency stop
POST /estop/reset          { token, approval } Clear estop
WS   /ws/telemetry         Streaming telemetry envelope frames

Viewer Frames

WS /ws/telemetry, GET /events/telemetry, and GET /sse/telemetry emit TelemetryStreamFrame JSON. Each frame includes a versioned visualization.version = "leash-visualization-v1" payload with:

  • pose: map-frame 2D pose
  • path: viewer-ready pose list
  • occupancy_grid: compact grid metadata + cells
  • point_cloud: metadata only, no viewer dependency
  • detections: generic detection boxes
  • command: motor command overlay for operators

External viewers can subscribe to the stream endpoints and render those fields without linking a viewer SDK into the core crate.

Agent Input

Run a local HTTP stack, then send natural-language text into the runtime without requiring a model provider:

leash run sim-http
leash agent-send "inspect the battery"
printf 'look for obstacles\n/quit\n' | leash agent-interactive

The command center at /dashboard is server-rendered HTML with regular form posts for authorize, stop, estop, reset, and capture actions. Those forms call the same shared capability registry used by JSON HTTP, CLI, and MCP paths. POST /agent/messages records a bounded recent inbox and publishes each message on the agent stream for local subscribers. The web form at /agent is only available when the http feature is built, and Leash binds HTTP to 127.0.0.1:8000 by default.

Agent Model Providers

Leash defaults to deterministic-test, a no-network provider for CI and demos. It returns a stable synthetic response with each agent input acknowledgement. Hosted and local HTTP providers can be configured and validated without printing secrets:

LEASH_AGENT_API_KEY=... \
  leash show-config \
  --agent-provider openai-compatible-http \
  --agent-base-url https://example.test/v1 \
  --agent-model demo-model

leash show-config \
  --agent-provider local-http \
  --agent-base-url http://127.0.0.1:11434/v1

Provider settings are available as --agent-provider, --agent-model, --agent-base-url, --agent-api-key, and --agent-timeout-ms, or via matching LEASH_AGENT_* environment variables. agent_api_key is redacted from show-config fields and omitted from top-level serialized config output.

Safety Policy

Capability invocations carry safety classes: observe-only, sim-control, physical-stop, physical-motion, and physical-high-risk. drive is physical-motion; stop and estop are physical-stop; estop_reset is physical-high-risk.

--policy-mode and LEASH_POLICY_MODE support:

  • require-token (default): physical motion and high-risk calls need a token.
  • require-approval: physical motion and high-risk calls need approval=true.
  • dry-run: physical motion and high-risk calls return a dry-run result without changing command state.
  • deny: physical motion and high-risk calls are blocked.

stop and estop remain available under restrictive policy modes. Approved and denied physical actions are emitted as structured log events. Agent-origin physical motion always requires approval=true; physical profiles still require --allow-physical-actuation or LEASH_ALLOW_PHYSICAL_ACTUATION=1 before the harness starts.

Features

Feature Description Default
sim Simulation driver (no hardware)
http HTTP server + WebSocket
mcp MCP stdio server
waveshare-ugv Waveshare UGV physical adapter opt-in
bridge-compat Legacy robot bridge compatibility opt-in

Smoke Tests

scripts/smoke-all.sh

scripts/smoke-all.sh runs the no-hardware release proof and prints a JSON summary covering HTTP routes and policy denial, stdio MCP, physical-gate refusal, daemon lifecycle, graph export, MCP HTTP + CLI calls, replay HTTP/MCP observe paths, and config preflight checks.

Stream Transport

stream_transport selects the module-stream backend used by WebSocket and SSE telemetry streams and shown in graph and capability output:

  • local-pubsub is the default runtime backend. It uses bounded async broadcast channels, supports fan-out across local tasks, drops oldest messages under receiver lag, and reports lag to subscribers.
  • memory is an unbounded in-process backend for deterministic tests and local harness checks. Dropped subscribers are pruned on publish.

Use --stream-transport memory or LEASH_STREAM_TRANSPORT=memory when a run needs deterministic in-process delivery.

/ws/telemetry, /events/telemetry, and /sse/telemetry emit the same transport-backed envelope: latest telemetry, health/module state, command state, and safety state.

Run Logs and Resource Samples

Daemon runs write structured JSONL logs under the Leash state directory. Each entry includes timestamp, run_id, module, event, level, and any structured fields emitted with the tracing event:

leash run sim-http --daemon
leash log sim-http --json --module http --lines 20

Runtime telemetry keeps process resource sampling off by default. Enable it for development or field debugging with --resource-sampling or LEASH_RESOURCE_SAMPLING=1; use --no-resource-sampling to force it off when an environment or config file enables it.

leash_harness::stream_processing provides generic helpers for high-rate streams: latest-value backpressure, per-key rate limiting, quality filtering, and timestamp pairing within a tolerance window.

Replay

leash record writes compact leash-replay-v1 JSONL events for telemetry, sensors, camera status, and command state. leash replay emits those events with original timing, scaled by --speed:

leash record --output /tmp/leash-demo.jsonl --samples 10 --interval-ms 50
leash replay /tmp/leash-demo.jsonl --speed 20

Use a replay source to serve deterministic observations through the normal HTTP and MCP surfaces without hardware:

leash serve http --replay-source examples/replay/sim-basic.jsonl
leash serve mcp --replay-source examples/replay/sim-basic.jsonl

Replay mode resolves to profile: replay, mode: replay, replay: true, and physical: false / physical_actuation_enabled: false in config, health, and capability output.

Run narrower checks when you need to isolate one surface:

scripts/smoke-http.sh
scripts/smoke-mcp.sh
scripts/smoke-mcp-http.sh
scripts/smoke-replay-http.sh
scripts/smoke-replay-mcp.sh
scripts/smoke-physical-gate.sh
scripts/smoke-daemon.sh

Roadmap

See issues for the full plan. Highlights:

  • Module graph with typed streams, lifecycle, and health aggregation
  • Stack catalog: leash list + leash run
  • Replay engine: deterministic sensor record + playback
  • Transport abstraction: memory + local async pubsub
  • Stream processing helpers: latest-value backpressure, quality filters, timestamp pairing
  • Agent input channels: one-shot CLI, interactive CLI, and localhost web input
  • Cross-process and network transports
  • MAVLink drone + manipulator adapters
  • Localhost command center dashboard
  • Viewer-ready visualization frames
  • Spatial memory and perception primitives
  • Patrol and exploration in simulation
  • Full no-hardware smoke suite

License

MIT