Metadata-Version: 2.4
Name: thoxmesh-node
Version: 0.1.0
Summary: Thin registration helper for THOX cloud mesh nodes (MeshStack v2)
Author-email: Tommy <tommy@thox.ai>
License: Apache-2.0
Project-URL: Homepage, https://github.com/ttracx/thoxllm-cloud
Project-URL: Documentation, https://github.com/ttracx/thoxllm-cloud/blob/main/templates/README.md
Keywords: thox,meshstack,llm,mesh,colab
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx<0.29,>=0.27
Requires-Dist: cryptography<47,>=43
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"

# ThoxLLM Cloud — self-serve mesh templates

Deploy your own colab mesh stack. **You supply your own Hugging Face token.
That is the only requirement.**

Included with **Tier 1 — ThoxLLM**. Your stack runs in your namespace, on your
compute, under your bill.

| | |
|---|---|
| **HF Space template** | [`hf-space/`](hf-space/) — duplicate a Space, it serves a model and joins your mesh |
| **Colab notebook** | [`colab/THOX_Mesh_Node.ipynb`](colab/THOX_Mesh_Node.ipynb) — run top to bottom |
| **Registration helper** | [`thoxmesh_node/`](thoxmesh_node/) — the thin client both templates use |
| **Deploy-flow contract** | [`DEPLOY_FLOW.md`](DEPLOY_FLOW.md) — what the portal wizard calls |
| **Trust model** | [`SECURITY.md`](SECURITY.md) — where your token lives and why |

---

## Before you start

You need:

1. **A Hugging Face account and token** — https://huggingface.co/settings/tokens.
   `read` scope is enough; the templates only download weights. **This is the
   only hard requirement.**
2. **A ThoxLLM Cloud API key** with the `mesh:register` scope, from the portal.
   Needed to register your node. **This is not your HF token.**
3. *(optional)* **A mesh URL and join key**, if you also want the node paired as
   a MeshStack device.

With no API key the templates still serve a model — they just do not register it.

---

## Path A — Hugging Face Space

1. Open the template Space and press **⋮ → Duplicate this Space**. The copy is
   created under **your** account on the free CPU tier.
2. Fill in, when prompted:
   - `HF_TOKEN` — **secret**, yours.
   - `THOX_CLOUD_URL` — variable.
   - `THOX_API_KEY` — **secret**, scope `mesh:register`.
3. Wait for the build (it compiles llama.cpp, so allow several minutes), then:

```bash
curl https://<your-user>-thox-mesh-node.hf.space/health
```

The Space works out its own public URL from `SPACE_ID` — you do not set one.

Full detail, including every variable: [`hf-space/README.md`](hf-space/README.md).

## Path B — Colab notebook

1. Open `colab/THOX_Mesh_Node.ipynb` in Colab.
2. **Runtime → Run all.**
3. Paste your HF token and API key when prompted (`getpass` — they are not
   echoed and are not saved into the notebook).
4. The notebook starts a tunnel to get a public URL, registers, and verifies.

Leave the last cell running to keep the node up.

---

## Reachability — the one thing to understand

There are two registration paths and they want **opposite** addresses. Pick with
`THOX_REGISTRAR`.

| Registrar | Advertises | Because |
|---|---|---|
| `spine` *(default)* | your node's **public https** origin | the ThoxLLM Cloud gateway proxies to it from THOX infrastructure |
| `device_v2` | a **private** mesh/LAN/loopback address | the node calls the mesh directly, and `validateLocalBaseUrl` refuses arbitrary hosts |

MeshStack's private-only rule is an **anti-SSRF invariant**: `resolve_route`
hands endpoints to other devices, so they must not be reachable from the public
internet. A Hugging Face Space necessarily is. The gateway therefore does not
mirror public endpoints into MeshStack at all — a public node is addressable by
**alias through the gateway**, but is absent from MeshStack and is not selected
by `resolve_route`. A private node registered via `device_v2` is in MeshStack
and is. `MeshRegistration.mirrored` tells you which you got.

`THOX_ATTACH_MODE` picks the address within a path:

| Mode | Advertises | Valid for |
|---|---|---|
| `public` *(default)* | public https origin (derived from `SPACE_ID` in a Space) | `spine` |
| `mesh` | assigned `100.96.x.x` | `device_v2`, with WireGuard up |
| `lan` | `THOX_ADVERTISE_HOST` | `device_v2` |
| `loopback` | `http://127.0.0.1:<port>` | `device_v2`, same host only |

`attach.py` refuses a mismatch locally with an actionable message, rather than
letting a registrar reject it — or worse, accept an address nobody can reach.

**Still open (not this lane):** node-to-node traffic on the `device_v2` path
needs a WireGuard client for cloud VMs. The peer contract exists
(`meshstack-peer-v2`); no Python client does. See
[`DEPLOY_FLOW.md`](DEPLOY_FLOW.md#cloud-node-reachability).

---

## Models

Defaults are chosen to start fast on a free CPU runtime.

| Model | File | Size | Notes |
|---|---|---|---|
| `Thox-ai/ThoxMini-3B` | `thoxmini-3b-Q4_K_M.gguf` | 2.0 GB | **default** |
| `Thox-ai/thox-micro-125m-GGUF` | `thox-micro-125m.q4_k_m.gguf` | 85 MB | fast cold start, but output is degenerate — for plumbing tests only |

Override with `THOX_MODEL_REPO` / `THOX_MODEL_FILE` / `THOX_MODEL_ID`.

---

## Development

```bash
cd templates
pip install -e ".[dev]"

pytest                        # 76 tests: protocol conformance, gateway, agent
thoxmesh-smoke --offline                        # gateway path
thoxmesh-smoke --offline --registrar device_v2   # direct mesh path
```

The offline smoke test runs the agent against in-process harnesses — a fake
gateway and a fake MeshStack control plane — that **verify real Ed25519
signatures**, enforce the timestamp window, reject replayed nonces, apply the
address policy and refuse Hugging Face tokens. Passing means satisfying the real
contracts.

Against a live mesh:

```bash
cp .env.example .env && $EDITOR .env
set -a && . ./.env && set +a
thoxmesh-smoke --registrar spine
```

### Protocol conformance

`thoxmesh_node/protocol.py` is a port of the canonical JavaScript reference
(`ttracx/sadie-mesh`, `tools/meshstack-conformance/protocol.mjs`). The tests
assert byte-parity against vectors generated by executing that reference,
including the Ed25519 signature itself — Ed25519 is deterministic, so a
matching signature proves the port.

If a vector fails, fix the port; do not edit the fixture.

---

## Layout

```text
templates/
├── README.md              this file
├── DEPLOY_FLOW.md         portal wizard contract (deliverable for the web lane)
├── SECURITY.md            trust model
├── .env.example           every variable, with placeholders
├── pyproject.toml         installable helper + test config
├── thoxmesh_node/         the registration helper
│   ├── protocol.py        signing, canonical payload, address rules
│   ├── identity.py        Ed25519 node identity
│   ├── client.py          pair + signed device API
│   ├── agent.py           pair -> register -> heartbeat lifecycle
│   ├── spine.py           registration with the ThoxLLM Cloud gateway
│   ├── attach.py          which address this node advertises, to which registrar
│   ├── smoke.py           register + heartbeat smoke test
│   └── tests/             conformance vectors + fake control plane
├── hf-space/              duplicatable Space (Docker + FastAPI)
└── colab/                 one-click notebook
```

---

## Provenance

The serving image and its hard-won build constraints come from the mesh-colab
POC lane's Space,
[`Thox-ai/thox-mesh-node`](https://huggingface.co/spaces/Thox-ai/thox-mesh-node):
`GGML_NATIVE=OFF` (an AVX-512 build SIGILLs on the runtime CPU), `libgomp1` in
the runtime stage, bookworm on both stages, and the fact that
`llama-cpp-python` has no manylinux wheel. Each was found by a build that looked
green and then failed at runtime. They are preserved here rather than
rediscovered.

`thoxmesh_node/protocol.py` is a port of the canonical JavaScript reference in
[`ttracx/sadie-mesh`](https://github.com/ttracx/sadie-mesh).
