Skip to main content
Glama
aniruddhaadak80

laneplan-relay

LanePlan Relay

Declare your relay lanes, walk the graph, and get the first handoff that loops, blows the visit budget, or misses its window — deterministically, from every surface.

Live app · Source · CI · Issues

TypeScript monorepo · Python engine · MCP server · MIT


The problem

A freight relay chain is a walk over a graph. Origin, a few lanes, some handoffs, a destination. Two things go wrong in practice, and both are silent:

  1. A lane returns to somewhere the chain has already been. A planner without a visited set will follow it forever, or quietly truncate and call that a route.

  2. The chain outgrows its budget. Every extra handoff is another slot to book, another dwell to absorb, another place for the load to sit until someone notices.

Today you find out at 2am, at a facility, with a load that cannot move. Or you hand-check the graph in a spreadsheet.

LanePlan Relay walks the graph you declared and tells you where it first fails, with the exact path that reached the failure.

Related MCP server: worldbrain-mcp

Why the answer is code, not a model

Whether a chain closes a loop is a graph fact. Whether a departure falls inside its window is arithmetic. Both must be reproducible: the same declared network has to produce the same verdict today, tomorrow, and in front of your auditor.

So the walk is a dependency-free Python engine — no clock, no network, no randomness, no model. It is spawned as a pure function over stdin/stdout, which means there is no server to run and two concurrent calls can never interleave state. Every emitted collection is explicitly sorted, so output ordering cannot depend on dict iteration order.

$ laneplan mcp call walk_relay_graph '{"network":{"facilities":[{"id":"A","name":"PortA","kind":"port"},{"id":"B","name":"HubB","kind":"hub"}],"lanes":[{"id":"ab","fromFacility":"A","toFacility":"B","dwellMinutes":10,"transitMinutes":60,"openMinute":0,"closeMinute":10080},{"id":"ba","fromFacility":"B","toFacility":"A","dwellMinutes":10,"transitMinutes":60,"openMinute":0,"closeMinute":10080}]},"startFacility":"A","maxLegs":4}'
{
  "engine": "1.0.0",
  "plans": [],
  "planCount": 0,
  "legsWalked": 1,
  "branchesPruned": 1,
  "cycle": {
    "laneId": "ba",
    "facility": "A",
    "path": ["A", "B", "A"]
  },
  "budget": null,
  "windowMiss": null,
  "maxLegs": 4,
  "startFacility": "A"
}

Lane ba returns to A, which the walk has already visited. That is the whole answer, and it is the answer you could not get by reading the JSON by eye.

On Windows PowerShell 5.1, escape the inner quotes before passing JSON to a native executable, or write the document to a file first. bash and zsh take the form above as-is.

Quick start

git clone https://github.com/aniruddhaadak80/laneplan-relay.git
cd laneplan-relay
npm install
npm run build
node packages/cli/dist/bin.js doctor

doctor probes the runtime, the skills catalog, the plugins and the config, and prints a fix hint per failing row.

Walkthrough

Every command below was run against this repository.

1. See the capability list

node packages/cli/dist/bin.js tools

Eight tools, each with a declared permission set. walk_relay_graph, find_cycles and score_handoff are the product; the rest are the plumbing this repo is built on.

2. Walk a network

node packages/cli/dist/bin.js mcp call walk_relay_graph '<your network JSON>'

Reports ranked plans, the first cycle with its closing lane, the first visit-budget breach and the first missed window — in one pass.

3. Enumerate every loop, ignoring time

node packages/cli/dist/bin.js mcp call find_cycles '<your network JSON>'

Useful as a regression check on a declared network. Each loop is rotated to a canonical starting facility, so the output is stable enough to diff.

4. Score one handoff

node packages/cli/dist/bin.js mcp call score_handoff '<network + facility>'

A 0–100 score with itemised weighted factors — dwell pressure, window tightness, fanout, inbound criticality — and an actionable recommendation. Read the factors, not just the total.

5. Inspect the extension surfaces

node packages/cli/dist/bin.js skills
node packages/cli/dist/bin.js plugins

Skills are markdown loaded from disk with frontmatter validation; an invalid skill is reported with a reason, never silently skipped. Plugins resolve by priority, and a shadowed plugin is reported rather than dropped.

6. Prove the MCP surface

node scripts/prove-mcp.mjs
MCP proof: stdio transport, real SDK client

  [PASS] initialize — server laneplan-relay v0.1.0
  [PASS] tools/list returns the relay tools — 8 tools: engine_diff, engine_normalize, engine_summarize, find_cycles, list_plugins, list_skills, score_handoff, walk_relay_graph
  [PASS] walk_relay_graph publishes a structured input schema — required: network, startFacility
  [PASS] tools/call find_cycles reaches the Python engine — found 1 loop, closing lane str-ham
  [PASS] tools/call walk_relay_graph reports the closing edge — NLRTM -> DEHAM -> FRAXF -> STRIG -> DEHAM closed by str-ham
  [PASS] the same call is byte-identical across calls — deterministic
  [PASS] tools/call score_handoff returns itemised factors — score 30 (low) from 4 factors
  [PASS] an unknown facility is rejected, with the engine code preserved — NOT_FOUND: UNKNOWN_FACILITY: startFacility 'NOWHERE' is not in the network
  [PASS] a missing network is rejected at the boundary — VALIDATION_FAILED: "network" must be an object

9/9 checks passed

This launches mcp serve as a child process and drives it with the real MCP SDK client. It is not a mock.

7. Connect an agent

{
  "mcpServers": {
    "laneplan-relay": {
      "command": "node",
      "args": ["packages/cli/dist/bin.js", "mcp", "serve"]
    }
  }
}

LanePlan Relay is not only an MCP client — it runs a server, which makes it a tool provider for other agents.

8. Run the whole gate

npm run check

The exact command CI runs, in the same order.

Architecture

flowchart LR
  subgraph surfaces["front ends"]
    CLI["CLI"]
    WEB["Web workspace"]
    MCP["MCP server"]
  end
  CLI --> REG["ToolRegistry<br/>packages/core"]
  WEB --> REG
  MCP --> REG
  REG --> ENGINE["Deterministic engine<br/>services/engine (Python)"]
  REG --> SKILLS["skills"]
  REG --> PLUGINS["plugins"]

One registry, one Tool interface. A surface is a transport, never a second implementation — which is why the web workspace calls the same Python engine the CLI does instead of reimplementing the walk in TypeScript. There is no TypeScript copy of the walk to drift.

flowchart TB
  NET["declared network"] --> WALK["walk_relay_graph"]
  START["start facility + minute"] --> WALK
  WALK --> CYCLE{"lane returns to<br/>a visited facility?"}
  WALK --> BUDGET{"leg budget spent?"}
  WALK --> WINDOW["departure outside<br/>open/close window?"]
  CYCLE --> OUT["first cycle + path prefix"]
  BUDGET --> OUT2["first breach + pruned lanes"]
  WINDOW --> OUT3["first miss"]

The engine stops a branch for exactly three reasons and records the first occurrence of each.

Invariants

  1. Tools are stateless. State lives in packages/memory.

  2. Input is validated before the handler runs.

  3. Permissions are declared, and a call exceeding the granted set is refused.

  4. A duplicate tool name throws, naming both registrants.

  5. No cross-package deep imports — check:boundaries fails otherwise.

The footprint ladder

Where new capability goes, in order: extend an existing tool → CLI command plus a skill → service-gated tool → plugin → MCP server tool → new core tool (last resort). Every core tool is paid for in context on every request forever; plugins are free.

What ships

Surface

Status

What it is

CLI

shipped

doctor, tools, skills, plugins, mcp serve, mcp call

Deterministic engine

shipped

pure Python over stdin/stdout; 50 tests incl. property tests

MCP server

shipped

8 tools over stdio, proven against a real client

Web workspace

shipped

Next.js, /workspace and /api/walk

MCP client

shipped

connects to configured servers

Skills catalog

shipped

SKILL.md discovery, validation, version gating

Plugin registry

shipped

manifest validation, priority conflict resolution

Memory

shipped

SQLite, WAL, numbered migrations, FTS5

Channels

scaffolded

one Channel interface with a local adapter; not load-bearing

Desktop shell

scaffolded

Electron around the web app; not load-bearing

The last two are inherited from the architecture this repo is built on and are real and tested, but nothing in the relay workflow needs them. They are listed as scaffolded rather than claimed as load-bearing.

Known limitation

The web workspace needs a Python interpreter on the host. /api/walk spawns the engine; on a serverless Node runtime there is no python, so the route returns 503 ENGINE_UNAVAILABLE and the UI shows that as an explicit, designed error state. It never substitutes a guess — a plausible-looking answer from the wrong engine would be worse than no answer. Self-host, or use the CLI or MCP server, which are the load-bearing surfaces.

Project map

apps/web/            Next.js app: /, /workspace, /surfaces, /health, /api/health, /api/walk
apps/desktop/        Electron shell around the web build
packages/core/       Tool interface, registry, permissions, error taxonomy
packages/engine-client/ typed bridge to the Python engine + error-code translation
packages/mcp/        MCP server (stdio) and MCP client
packages/skills/     SKILL.md loader, frontmatter parsing, catalog
packages/plugins/    manifest loader, schema validation, conflict resolution
packages/memory/     SQLite store, WAL, numbered migrations, FTS
packages/cli/        commander CLI, the tool registry assembly, doctor
services/engine/     the deterministic engine and its pytest suite
skills/              the skill catalog
scripts/             policy gates and prove-mcp.mjs

Gates

npm run check          # format, lint, typecheck, 6 policy gates, tests, pytest, build
python -m pytest services/engine -q
python -m ruff check services/engine
node scripts/prove-mcp.mjs

CI runs the same aggregate gate in named jobs: Format, Lint, Typecheck, Test, Python test, Build, Secret scan.

Documentation

Page

Read it when

getting-started

you have just cloned this

architecture

you need the map before changing anything

cli

you are scripting the CLI

mcp

you are connecting an agent

skills

you are writing or editing a skill

plugins

you are adding an extension

ci

you are adding a gate

adr/

you want the reasoning behind a decision

Contributing: CONTRIBUTING.md. The rules that are not negotiable are in AGENTS.md.

License

MIT — see LICENSE. Third-party notices in THIRD_PARTY_NOTICES.md.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables deterministic dependency reasoning over declared graphs, allowing users and AI agents to validate acyclicity, resolve targets, identify runnable parallel layers, assess impact, and explain blockers.
    Apache 2.0
  • A
    license
    Not graded
    quality
    F
    maintenance
    Enables AI agents to perform auditable A* reasoning, import world graphs, incorporate physical carrier constraints, and maintain a learning knowledge base through a zero-dependency local MCP stdio service.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Read-only CLI and stdio MCP server that inventories the MCP servers an agent can reach, compiles a least-privilege policy with a reason for every allow/approve/deny verdict, and emits a delivery the recipient re-verifies in their own browser using a sha256 manifest.
    Apache 2.0