more docs review

This commit is contained in:
Dobromir Popov
2026-07-13 18:37:07 +02:00
parent 95245be512
commit c938d38031
4 changed files with 242 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "Distributed Inference Network",
"description": "Build a distributed inference network with node, gateway, tracker, SDK, contracts, and P2P shard distribution components from the grill session PRD.",
"description": "Distributed inference network: base program US-001…US-035 complete; post-alpha friends-test and LAN-serving arc US-036…US-047 tracked here. Active scratch features (alpha hardening, NCA, distributed GGUF) have separate prd.json files under .scratch/.",
"branchName": "ralph/distributed-inference-network",
"userStories": [
{
@@ -814,10 +814,247 @@
"US-033"
],
"completionNotes": "GET /dashboard served from embedded dashboard.html (package-data, no build step) by any tracker. Panels: hive/leader (raft status), nodes+coverage grouped by model, client balances, node pending + protocol cut, settlement history with devnet explorer links, strikes/bans/forfeitures (GET /v1/registry/wallets + snapshot forfeits), RPM stats. 4s auto-refresh via fetch polling. 3 tests in tests/test_dashboard.py."
},
{
"id": "US-036",
"title": "36 — Streamed chat completions over the relay RPC path",
"description": "Public NAT deployments proxy every chat request tracker → relay → head node. Implement true multi-frame SSE streaming over the relay WebSocket so clients see live tokens and relayed streams bill through the same SSE accounting loop as direct proxy streams. Inter-node /forward activation hops stay single-frame (ADR-0014).",
"acceptanceCriteria": [
"stream: true chat via relay delivers SSE chunks incrementally (≥2 distinct frame arrivals before [DONE])",
"Relayed streamed request records nonzero billed tokens and node work credit",
"Non-streamed relayed requests and /forward binary hops behave exactly as before (single frame, body_base64 intact)",
"Legacy single-frame response from an old node is accepted as terminal",
"Idle stream (no frame for 120 s) returns 504 and cleans up the relay-side queue",
"Extend tests/test_gossip_and_relay.py alongside test_relay_rpc_round_trips_http_request_to_peer",
"python -m pytest passes from repo root"
],
"priority": 36,
"status": "needs-review",
"notes": "Source issue: docs/issues/36-relay-streamed-chat.md. Implemented via _stream_relayed_frames in server.py; verify on public NAT relay before friends-test.",
"dependsOn": [
"US-029",
"US-031"
],
"completionNotes": "Multi-frame relay-http-response protocol; node relay_bridge line-by-line SSE emit; relay server per-request asyncio.Queue; tracker _stream_relayed_frames with SSE billing parity. Client mid-stream disconnect accepted limitation for alpha."
},
{
"id": "US-037",
"title": "37 — Concurrent request handling in the node relay bridge",
"description": "RelayHttpBridge currently handles relay-http-request envelopes serially, blocking up to 300 s per request. Off-LAN a node can be head of one route and downstream hop of another — overlapping routes through a shared node break. Dispatch on a bounded ThreadPoolExecutor (default 8, configurable) with per-frame WS send locking compatible with US-036 streaming.",
"acceptanceCriteria": [
"While one relayed request is in flight, a second relay-http-request to the same node completes without waiting for the first",
"Responses are correctly matched by request_id when they complete out of order",
"More than N simultaneous requests queue and all eventually complete; thread count never exceeds N workers",
"Bridge survives a relay reconnect with workers still in flight (no crash, no deadlock; orphaned responses dropped)",
"Configurable via meshnet-node start --relay-concurrency N (env MESHNET_RELAY_CONCURRENCY)",
"Extend tests/test_gossip_and_relay.py",
"python -m pytest passes from repo root"
],
"priority": 37,
"status": "open",
"notes": "Source issue: docs/issues/37-relay-bridge-concurrency.md. Critical for public friends-test; blocks concurrent head + hop on same node.",
"dependsOn": [
"US-036"
]
},
{
"id": "US-038",
"title": "38 — Tracker cluster join via a single seed peer",
"description": "Tracker cluster membership is static today — a newcomer configured with only one existing peer is never learned by the rest of the hive and quorum math diverges. A joining tracker configured with any one live seed announces via hive-HMAC-signed POST /v1/cluster/join; membership changes replicate through the Raft log and persist across restarts.",
"acceptanceCriteria": [
"Start trackers A+B; start C with only A as seed → within one election timeout A, B, and C report the same 3-peer membership on GET /v1/cluster/peers, and a value proposed on C commits on A and B",
"Join without a valid hive signature is rejected with 403; join to a follower is forwarded to the leader",
"Restarting C with its seed offline rejoins from persisted membership",
"Standalone tracker (no seeds) behaves exactly as today",
"python -m pytest passes from repo root"
],
"priority": 38,
"status": "open",
"notes": "Source issue: docs/issues/38-tracker-seed-join.md. Out of scope: peer removal, joint consensus, automatic seed retry.",
"dependsOn": [
"US-013",
"US-017"
]
},
{
"id": "US-039",
"title": "39 — Caller Credit granted once per account; chat requires account keys",
"description": "DEFAULT_STARTING_CREDIT=0 and no grant path leaves every fresh public tracker request at 402. Grant Caller Credit once per account on first API key creation via deterministic event id caller-credit-{account_id}; chat on accounts-enabled trackers requires a real active sk-mesh- key (401 for invented bearers).",
"acceptanceCriteria": [
"Fresh account → first key → key has --starting-credit balance; chat succeeds",
"Second key on the same account → no additional credit",
"Revoke-and-recreate keys → still no additional credit (deterministic event id)",
"Random bearer string on an accounts-enabled tracker → 401, never 402/free work",
"Tracker without accounts store: gate behavior unchanged",
"--starting-credit 0 disables the grant entirely (mainnet posture)",
"python -m pytest passes from repo root"
],
"priority": 39,
"status": "open",
"notes": "Source issue: docs/issues/39-caller-credit-account-keys.md. Critical for friends-test inference.",
"dependsOn": [
"US-031",
"US-035"
]
},
{
"id": "US-040",
"title": "40 — Devnet top-up button on the dashboard",
"description": "After Caller Credit (US-039) is spent, devnet friends need a dashboard faucet refill without on-chain USDT deposits. POST /v1/account/topup (session-authenticated) credits a configured fixed amount per click; flag off returns 404 and hides the button.",
"acceptanceCriteria": [
"Flag off: endpoint 404s, dashboard shows no top-up button",
"Flag on: logged-in user tops up own key, balance rises by exactly N",
"Topping up another account's key → 403",
"python -m pytest passes from repo root"
],
"priority": 40,
"status": "open",
"notes": "Source issue: docs/issues/40-devnet-dashboard-topup.md. Mainnet deployments set --devnet-topup 0.",
"dependsOn": [
"US-039"
]
},
{
"id": "US-041",
"title": "41 — Account wallet: browser-extension signing, in-browser generation, export-only",
"description": "Accounts need a visible wallet for deposit attribution without the tracker ever holding private keys. Dashboard integrates Solana wallet-adapter connect+nonce proof, or in-browser keypair generation with one-time export; no private-key import endpoint.",
"acceptanceCriteria": [
"Connect-extension flow stores a verified pubkey (rejects unsigned/mismatched nonce proofs)",
"Generate flow: pubkey lands on the account; private key is never sent to the tracker, export works",
"No endpoint or UI accepts a private key",
"Deposits to the shown address credit the account's keys via the existing watcher",
"Address visible on the account panel after either flow",
"python -m pytest passes from repo root"
],
"priority": 41,
"status": "open",
"notes": "Source issue: docs/issues/41-account-wallet-keypair.md. Not needed for devnet friends test; needed before mainnet.",
"dependsOn": [
"US-032",
"US-039"
]
},
{
"id": "US-042",
"title": "42 — GGUF/llama.cpp node backend (phase C whole-model first)",
"description": "Node backend is transformers-only today; large MoE models on consumer hardware require GGUF via llama.cpp. Phase C: whole-model GGUF nodes (single-hop routes) first; partial-layer distributed GGUF deferred to ADR-0024. Also: GGUF catalog entries, Strix Halo/Vulkan hardware detection, download dir applies to GGUF files.",
"acceptanceCriteria": [
"A node with --gguf <repo-or-path> --quant IQ3_XXS serves /v1/chat/completions via llama.cpp with GPU offload where available",
"Tracker treats it as a full-coverage node (single-hop routes, billing works)",
"Streamed responses work through the tracker proxy and the relay (US-036)",
"python -m pytest passes from repo root (llama.cpp behind an optional extra)"
],
"priority": 42,
"status": "in-design",
"notes": "Source issue: docs/issues/42-gguf-llamacpp-node-backend.md. Distributed native path: ADR-0024. Sequencing: phase C before A/B investigation.",
"dependsOn": [
"US-036"
]
},
{
"id": "US-043",
"title": "43 — Dashboard model search and model cards",
"description": "Dashboard lacks model-centric discovery. Add server-side HF search proxy merged with tracker presets and live coverage; model cards show architecture, coverage gaps, pricing, memory per quant, and a request-this-model action. Featured section driven by CURATED_MODELS including GGUF once US-042 lands.",
"acceptanceCriteria": [
"Searching a HF repo id or free text returns results without the browser calling HF directly",
"A served model's card shows live coverage and a working chat-now state",
"An unserved model's card shows the request action and estimated memory per quant",
"python -m pytest passes from repo root"
],
"priority": 43,
"status": "open",
"notes": "Source issue: docs/issues/43-dashboard-model-search-cards.md. Post-deploy polish.",
"dependsOn": [
"US-035"
]
},
{
"id": "US-044",
"title": "44 — Tracker as model-file source; nodes download only their shard",
"description": "Second nodes joining a fleet today download entire HF snapshots even for small shard assignments. Tracker --models-dir advertises layer-scoped safetensors subsets; nodes race tracker/peer sources before HF allow_patterns fallback. Hard half remaining: meta-device partial model materialization so resident memory scales with shard size, not full model size.",
"acceptanceCriteria": [
"Tracker started with --models-dir / MESHNET_MODELS_DIR advertises local model-file sources in assignment responses",
"Tracker serves a tar stream (or per-file API) containing only safetensors files for the assigned layer range plus config/tokenizer/index metadata",
"Node downloader tries exact-shard peers, then tracker/peer file subsets, then HF snapshot_download with allow_patterns — never silently full-repo when layer index is available",
"Two-machine test: machine B receives only its assigned range from machine A — nothing fetched from HF",
"Machine B resident memory scales with its shard size, not model size",
"Checksums verified end-to-end; corrupted transfer falls back cleanly",
"Single-node/full-model flows unchanged",
"python -m pytest passes from repo root"
],
"priority": 44,
"status": "in-progress",
"notes": "Source issue: docs/issues/44-tracker-shard-source-partial-download.md. Download path largely implemented 2026-07-06; partial LOAD (meta-device materialization) and two-machine acceptance remain.",
"dependsOn": [
"US-004",
"US-012"
],
"completionNotes": "Tracker models-dir indexing, layer-scoped tar stream, HF allow_patterns client-side from remote index, per-file download API with retries, symlink dereference in tar writers. Remaining: true partial model load and live two-machine verification."
},
{
"id": "US-045",
"title": "45 — Dual-rate billing: separate input and output token prices",
"description": "Ledger has one price_per_1k_tokens and stream vs non-stream paths disagree on input vs output counting. Charge both input and output tokens at separate rates per model; HF pricing refresher applies 80% of each marketplace side separately.",
"acceptanceCriteria": [
"Streamed and non-streamed requests for the same exchange bill the same split (input charged in both)",
"A model with asymmetric provider rates bills input and output differently; usage_for / billing events expose the split",
"Old persisted billing events replay byte-identically (balances unchanged)",
"HF refresh sets both rates from the marketplace row, not the average",
"Spend cap (--max-charge-per-request) uses the dual rates",
"python -m pytest passes from repo root"
],
"priority": 45,
"status": "in-progress",
"notes": "Source issue: docs/issues/45-dual-rate-billing.md. Billing correctness before friends test.",
"dependsOn": [
"US-031"
]
},
{
"id": "US-046",
"title": "46 — Tracker .env awareness + first-node auto-join bootstrap",
"description": "Fresh trackers return 503 on auto-join because deployability ignores the joining caller's hardware, and meshnet-tracker ignores .env MESHNET_DOWNLOAD_DIR. Fix empty-registry bootstrap, tracker env loading parity with node CLI, models-dir fallback chain, and tar dereference for HF symlink snapshots.",
"acceptanceCriteria": [
"Fresh tracker (empty registry) + caller with enough memory for a recommended preset → /v1/network/assign returns 200 with model_sources populated when tracker holds a local snapshot",
"Fresh tracker + caller too small for any recommended preset → still 503",
"meshnet-tracker start in a directory with .env setting MESHNET_DOWNLOAD_DIR serves /v1/model-files/download from that dir with no extra flags",
"Explicit --models-dir and MESHNET_MODELS_DIR still take precedence",
"python -m pytest passes from repo root"
],
"priority": 46,
"status": "needs-review",
"notes": "Source issue: docs/issues/46-tracker-env-and-first-node-autojoin.md. Verified live 2026-07-06.",
"dependsOn": [
"US-044"
],
"completionNotes": "Empty-registry synthesizes caller as candidate node; tracker loads .env; models_dir falls back MESHNET_MODELS_DIR → MESHNET_DOWNLOAD_DIR; tar dereference=True. Pytest passes aside from known port-7000 env conflict."
},
{
"id": "US-047",
"title": "47 — Tracker-first model downloads: visibility, sane timeouts, RAM-based sizing",
"description": "Explicit --model startup should skip pointless auto-join; tracker/peer sources preferred over HF with visible progress and 30 s socket timeouts; client abort during tar stream logs one line; CPU nodes size shards from RAM not phantom GPU VRAM; per-file downloads for robustness over fragile multi-GB tar streams.",
"acceptanceCriteria": [
"Node started with explicit --model never queries /v1/network/assign and never prints auto-join unavailable",
"Tracker/peer model source preferred; HF only when no source, all sources fail, or --tracker-source-disabled",
"Tracker-source downloads print progress every 512 MB and print exception + URL on failure",
"A ≥2 s read stall no longer aborts a tracker model-source download (30 s socket timeout)",
"Client disconnect during /v1/model-files/download logs one line on the tracker, no traceback",
"CPU node with big RAM gets a RAM-sized shard: sizing paths ignore VRAM unless device=cuda",
"Live two-machine retest: Windows node downloads from tracker at LAN speed with RAM-sized shard assignment",
"python -m pytest passes from repo root"
],
"priority": 47,
"status": "in-progress",
"notes": "Source issue: docs/issues/47-model-source-download-visibility.md. Engineering largely complete 2026-07-06; live two-machine retest pending.",
"dependsOn": [
"US-044",
"US-046"
],
"completionNotes": "Skip auto-join when model explicit; sequential source try with progress; 30 s model-source timeout; assignment_vram_mb for CPU; per-file /v1/model-files/download with manifest and retries. Remaining: live Windows two-machine retest."
}
],
"metadata": {
"updatedAt": "2026-07-01T00:00:00.000Z",
"updatedAt": "2026-07-13T16:15:00.000Z",
"statusVocabulary": {
"open": "Not started",
"in-design": "Decisions pending before implementation can begin",