A lightweight openHop service plugin that bridges a dedicated Repeater Companion frame server identity to Project N.O.M.A.D.
The plugin intentionally keeps the existing MeshCore Companion protocol client. It does not import Repeater internals and does not require a second REST/SSE bridge layer.
MeshCore user
-> direct message
openHop Repeater Companion identity
-> Companion frame server TCP
NOMAD Bridge plugin
-> Project N.O.M.A.D. HTTP API
NOMAD Bridge plugin
-> Companion frame server TCP reply
MeshCore user
The expected default Companion endpoint is 127.0.0.1:5001. Give NOMAD a dedicated Repeater Companion identity and bind its frame server to localhost when the plugin runs on the same host.
Example Repeater configuration:
identities:
companions:
- name: "NomadBot"
identity_key: "PRIVATE_IDENTITY_KEY"
settings:
node_name: "NOMAD"
tcp_port: 5001
bind_address: "127.0.0.1"
tcp_timeout: 0The plugin manager should provide a persistent data directory with:
OPENHOP_PLUGIN_DATA=/var/lib/openhop/plugins/openhop.nomad/data
The plugin owns everything below that directory. By default it uses:
$OPENHOP_PLUGIN_DATA/
├── config.json
└── nomad_sessions.json
nomad_sessions.json is only used for sender-scoped persistent N.O.M.A.D. conversations when one_shot is disabled.
When OPENHOP_PLUGIN_DATA is not set, existing standalone behaviour is preserved and the session map defaults to ./data/nomad_sessions.json.
With OPENHOP_PLUGIN_DATA set, the plugin reads $OPENHOP_PLUGIN_DATA/config.json if it exists.
Minimum configuration:
{
"nomad_url": "http://192.168.0.170:8080",
"nomad_model": "qwen2.5:3b-instruct"
}Typical configuration:
{
"meshcore_host": "127.0.0.1",
"meshcore_port": 5001,
"nomad_url": "http://192.168.0.170:8080",
"nomad_model": "qwen2.5:3b-instruct",
"nomad_collection": null,
"nomad_timeout_seconds": 120,
"one_shot": true,
"max_concurrent_requests": 2,
"busy_wait_seconds": 5,
"max_reply_chunks": 4,
"max_chunk_bytes": 145,
"max_prompt_bytes": 1000,
"radio_prompt_enabled": true,
"radio_prompt_template": "You are answering a question received over a low-bandwidth MeshCore radio network.\\nGive the most useful answer first.\\n...\\n\\nUser question:\\n{question}",
"duplicate_ttl_seconds": 600,
"log_level": "INFO"
}Configuration precedence is:
built-in defaults
< config.json
< environment variables
This keeps environment variables available for development and existing standalone deployments.
Important environment overrides include:
MESHCORE_HOSTMESHCORE_PORTNOMAD_URLNOMAD_MODELNOMAD_COLLECTIONNOMAD_TIMEOUT_SECONDSONE_SHOTNOMAD_SESSION_MAP_PATHMAX_CONCURRENT_REQUESTSNOMAD_BUSY_WAIT_SECONDSMAX_REPLY_CHUNKSMAX_CHUNK_BYTESMAX_PROMPT_BYTESRADIO_PROMPT_ENABLEDRADIO_PROMPT_TEMPLATEDUPLICATE_TTL_SECONDSLOG_LEVEL
NOMAD_URL and NOMAD_MODEL must be provided by config.json or environment variables.
openhop-plugin.json declares this as a Python service plugin with a dashboard UI for configuration editing:
{
"schema": 1,
"id": "openhop.nomad",
"name": "NOMAD Bridge",
"version": "0.1.2",
"runtime": {
"type": "python",
"entrypoint": "meshcore-nomad-bridge"
},
"ui": {
"type": "application",
"entry": "ui/index.html"
}
}The plugin remains a lightweight service package and does not add a custom permission model, Docker runtime, or Repeater-internal hook.
The plugin remains runnable without the plugin manager:
python3 -m venv .venv
. .venv/bin/activate
pip install -e .[dev]
export NOMAD_URL=http://127.0.0.1:8080
export NOMAD_MODEL=qwen2.5:3b-instruct
meshcore-nomad-bridgepython -m pytestSee release and catalogue automation for immutable publication, manual existing-release retries, credential boundaries, read-only proposal rehearsal, and the outstanding catalogue test gate. Catalogue-owned policy—not this producer—decides automatic merging.
Build a distributable wheel from the plugin root:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip build
python -m build --wheelThe wheel is written to dist/, for example:
dist/openhop_nomad_plugin-0.1.2-py3-none-any.whl
If you want both wheel and source distribution, run:
python -m build