From 72852c9f3aa3b129f16a14d9dbc8e332a8c64e2d Mon Sep 17 00:00:00 2001 From: Shaela Bello Date: Wed, 25 Mar 2026 17:28:03 -0400 Subject: [PATCH] feat(nexus): add nemotron claw lane and sync p7 docs --- pmoves/config/model_nexus.yaml | 218 ++++++++++++++++++ .../configs/agent-profiles/nemotron_claw.yaml | 59 +++++ pmoves/configs/agent-teams.yaml | 1 + .../configs/claws/opencode-nemotron-claw.json | 41 ++++ .../configs/claws/scopes/nemotron-claw.json | 83 +++++++ pmoves/configs/tac_trees/pinokio-p7.tac.yaml | 29 ++- pmoves/docs/AGENTS/AGNOTE_P7_PLAYGROUND.md | 36 +-- pmoves/docs/MODEL_FABRIC_CONTRACT.md | 25 +- pmoves/docs/NEXUS_PROVIDER_PARITY.md | 111 +++++++++ pmoves/services/common/__init__.py | 19 ++ pmoves/services/common/model_nexus.py | 90 ++++++++ .../services/common/tests/test_model_nexus.py | 60 +++++ 12 files changed, 736 insertions(+), 36 deletions(-) create mode 100644 pmoves/config/model_nexus.yaml create mode 100644 pmoves/configs/agent-profiles/nemotron_claw.yaml create mode 100644 pmoves/configs/claws/opencode-nemotron-claw.json create mode 100644 pmoves/configs/claws/scopes/nemotron-claw.json create mode 100644 pmoves/docs/NEXUS_PROVIDER_PARITY.md create mode 100644 pmoves/services/common/model_nexus.py create mode 100644 pmoves/services/common/tests/test_model_nexus.py diff --git a/pmoves/config/model_nexus.yaml b/pmoves/config/model_nexus.yaml new file mode 100644 index 0000000000..37ded35299 --- /dev/null +++ b/pmoves/config/model_nexus.yaml @@ -0,0 +1,218 @@ +# ============================================================================= +# PMOVES.AI Model Nexus Provider Parity Registry +# ============================================================================= +# Additive contract layer over the existing TensorZero-first model fabric. +# TensorZero remains the default execution gateway; Nexus defines how provider- +# native SDKs, OpenAI-compatible adapters, and local runtimes line up so each +# provider can bring native strengths without fragmenting runtime parity. +# ============================================================================= + +contract_version: 1 +last_updated: 2026-03-25 + +nexus: + canonical_request_shape: nexus.chat.v1 + canonical_embedding_shape: nexus.embed.v1 + default_execution_lane: tensorzero + observability_owner: tensorzero + registry_owner: supabase_model_registry + goals: + - provider parity before provider sprawl + - native strengths stay available behind one PMOVES contract + - TensorZero remains the default route unless a lane is explicitly exempted + - every exemption keeps CHIT, Graphiti, and observability parity + execution_order: + - tensorzero + - native_sdk + - openai_compatible + exemption_rules: + - Native SDK lanes require an explicit owner and validation evidence. + - Native SDK lanes must emit the same logical event and metrics fields as TensorZero-backed lanes. + - If a native SDK lane cannot preserve parity, it must route back through TensorZero. + +adapter_contract: + required_fields: + - provider + - sdk_family + - request_shape + - response_shape + - auth_source + - observability_mode + - fallback_lane + parity_requirements: + - tool_call_shape + - structured_output_shape + - streaming_events + - retry_policy + - trace_context + - cost_usage_capture + - health_readiness + +providers: + tensorzero: + role: default_gateway + sdk_family: openai_compatible + transport: http + request_shape: openai_compatible + response_shape: openai_compatible + observability_mode: authoritative + strengths: + - centralized routing + - unified telemetry + - experiment and evaluation hooks + - shared secrets fence + use_when: + - default for agent and worker traffic + - shared routing for local and cloud providers + - parity fallback for native SDK adapters + + openai: + role: native_provider + sdk_family: openai + transport: responses_api + request_shape: nexus.chat.v1 + response_shape: nexus.chat.v1 + auth_source: OPENAI_API_KEY + observability_mode: mirrored_to_tensorzero + native_strengths: + - reasoning-native APIs + - structured outputs + - tools and multimodal primitives + adapter_strategy: native_sdk_first + fallback_lane: tensorzero + + anthropic: + role: native_provider + sdk_family: anthropic + transport: messages_api + request_shape: nexus.chat.v1 + response_shape: nexus.chat.v1 + auth_source: ANTHROPIC_API_KEY + observability_mode: mirrored_to_tensorzero + native_strengths: + - long-context reasoning + - tool use + - prompt cache and contextual workflows + adapter_strategy: native_sdk_first + fallback_lane: tensorzero + + gemini: + role: native_provider + sdk_family: google_genai + transport: native_generate_content + request_shape: nexus.chat.v1 + response_shape: nexus.chat.v1 + auth_source: GEMINI_API_KEY + observability_mode: mirrored_to_tensorzero + native_strengths: + - multimodal inputs + - long-context and retrieval workflows + - provider-native media flows + adapter_strategy: native_sdk_first + fallback_lane: tensorzero + + nvidia_nim: + role: local_provider + sdk_family: openai_compatible + transport: local_http + request_shape: nexus.chat.v1 + response_shape: nexus.chat.v1 + auth_source: NGC_API_KEY_or_local_assets + observability_mode: mirrored_to_tensorzero + native_strengths: + - nemotron on beefy nvidia gpus + - optimized local throughput and tensor cores + - model-specific or multi-llm nim containers + adapter_strategy: tensorzero_or_direct + fallback_lane: tensorzero + served_models: + - nvidia/llama-3_1-nemotron-nano-8b-v1 + - nvidia/nemotron-3-nano-30b-a3b + - nvidia/llama-3_3-nemotron-super-49b-v1 + + ollama: + role: local_provider + sdk_family: openai_compatible_or_native + transport: local_http + request_shape: nexus.chat.v1 + response_shape: nexus.chat.v1 + auth_source: none + observability_mode: via_tensorzero_preferred + native_strengths: + - local-first inference + - offline development lanes + - low-cost iteration + adapter_strategy: tensorzero_first + fallback_lane: vllm + + vllm: + role: local_provider + sdk_family: openai_compatible + transport: local_http + request_shape: nexus.chat.v1 + response_shape: nexus.chat.v1 + auth_source: none + observability_mode: via_tensorzero_preferred + native_strengths: + - high-throughput serving + - larger local checkpoints + - OpenAI-compatible bridge + adapter_strategy: tensorzero_first + fallback_lane: ollama + +lanes: + agent_mesh: + default_provider: tensorzero + allowed_native_overrides: + - openai + - anthropic + - gemini + - nvidia_nim + requirements: + - deterministic fallback + - NATS-visible traces + - CHIT-safe metadata + + p7_and_skills: + default_provider: tensorzero + allowed_native_overrides: + - openai + - gemini + - nvidia_nim + requirements: + - skill-level capability declaration + - portable request envelope + - mobile-safe timeout policy + + coding_agents: + default_provider: tensorzero + allowed_native_overrides: + - openai + - anthropic + - nvidia_nim + requirements: + - tool parity + - reasoning trace capture + - provider-specific client wrappers stay additive + + nemotron_claw: + default_provider: nvidia_nim + allowed_native_overrides: + - tensorzero + - ollama + requirements: + - nemotron-backed coding and agentic reasoning + - gpu health and nim readiness checks + - nats-visible trace metadata + +review_split: + codex: + owns: + - nexus contract docs and config + - provider adapter scaffolding + - parity tests for request and response envelopes + claude_4090: + owns: + - P7 and remote skill validation + - local model parity on mesh nodes + - multimodal and voice lane validation diff --git a/pmoves/configs/agent-profiles/nemotron_claw.yaml b/pmoves/configs/agent-profiles/nemotron_claw.yaml new file mode 100644 index 0000000000..9b8262e3aa --- /dev/null +++ b/pmoves/configs/agent-profiles/nemotron_claw.yaml @@ -0,0 +1,59 @@ +# Nemotron Claw — Beefy NVIDIA GPU Agentic Claw +# Hybrid: OpenClaw + TensorZero/NIM + NATS mesh participation +# +# Runs on 5090-class or larger NVIDIA GPU nodes for local Nemotron inference, +# coding support, and agentic reasoning with NIM as the preferred substrate. + +name: nemotron_claw +role: gpu-agentic-claw +description: "Beefy NVIDIA GPU claw for Nemotron-backed coding and agentic reasoning" + +node_affinity: + - 5090 + - z890 +hostname_pattern: "nemotron-claw-*" + +model: + provider: nvidia_nim + model: nvidia/llama-3_3-nemotron-super-49b-v1 + endpoint: http://localhost:8000/v1 + fallback: tensorzero + +capabilities: + - local_nemotron_inference + - agentic_reasoning + - coding_assistance + - gpu_monitoring + - nim_container_ops + - model_benchmarking + +nats: + url: nats://nats:pmoves@nats:4222 + subscribe: + - mesh.gpu.command.v1 + - pinokio.agent.session.v1 + publish: + - mesh.gpu.status.v1 + - mesh.gpu.model.loaded.v1 + - mesh.gpu.command.result.v1 + - pinokio.telemetry.v1 + +exec_permissions: + - nvidia-smi + - docker + - python3 + - curl + - git + - make + - nats + +health: + endpoint: http://localhost:8000/health + heartbeat_subject: mesh.gpu.status.v1 + heartbeat_interval_seconds: 10 + +agent_zero: + subordinate: true + profile: nemotron_claw + inherit_context: true + report_to: agent_zero diff --git a/pmoves/configs/agent-teams.yaml b/pmoves/configs/agent-teams.yaml index cb43fcbc32..b0fb9213ce 100644 --- a/pmoves/configs/agent-teams.yaml +++ b/pmoves/configs/agent-teams.yaml @@ -115,6 +115,7 @@ teams: - swarm_attribution # Shape-attribution engine - tensorzero # Port 3030 — LLM gateway (all model calls) - llama_lab # LLM benchmarking + - nemotron_claw # Beefy NVIDIA GPU claw (Nemotron/NIM local reasoning) infra: name: "Infrastructure & Networking" diff --git a/pmoves/configs/claws/opencode-nemotron-claw.json b/pmoves/configs/claws/opencode-nemotron-claw.json new file mode 100644 index 0000000000..12aff9d092 --- /dev/null +++ b/pmoves/configs/claws/opencode-nemotron-claw.json @@ -0,0 +1,41 @@ +{ + "$schema": "KiloCode opencode.json \u2014 Nemotron Claw GPU Node", + "mcpServers": { + "pmoves-cipher": { + "type": "sse", + "url": "http://${TS_Z890}:8096/sse", + "description": "Agent memory via Tailscale" + }, + "agent-zero": { + "type": "http", + "url": "http://${TS_Z890}:8080/mcp", + "description": "Agent Zero orchestrator via Tailscale" + } + }, + "modes": { + "default": "pmoves-code", + "available": [ + "pmoves-code", + "pmoves-architect", + "pmoves-ask", + "pmoves-debug", + "pmoves-review" + ] + }, + "modelOverrides": { + "primary": "nvidia/llama-3_3-nemotron-super-49b-v1", + "provider": "openai-compatible", + "endpoint": "http://localhost:8000/v1", + "fallback": "tensorzero" + }, + "node": { + "identity": "nemotron-claw", + "role": "gpu-agentic", + "services": [ + "nvidia-nim:8000", + "tensorzero:3030", + "agent-zero:8080", + "nats:4222" + ] + } +} diff --git a/pmoves/configs/claws/scopes/nemotron-claw.json b/pmoves/configs/claws/scopes/nemotron-claw.json new file mode 100644 index 0000000000..4636987786 --- /dev/null +++ b/pmoves/configs/claws/scopes/nemotron-claw.json @@ -0,0 +1,83 @@ +{ + "identity": { + "node": "5090", + "role": "gpu-agentic", + "description": "Nemotron Claw \u2014 beefy NVIDIA GPU node for local Nemotron/NIM reasoning", + "hostname": "nemotron-claw" + }, + "openclaw_overrides": { + "agents.defaults.model.primary": "nvidia/llama-3_3-nemotron-super-49b-v1", + "auth.profiles": { + "nvidia_nim:local": { + "provider": "openai-compatible", + "mode": "local", + "baseUrl": "http://localhost:8000/v1" + } + }, + "tools.profile": "full", + "tools.exec.ask": "auto", + "channels": { + "discord": { + "enabled": true + } + } + }, + "exec_approvals": { + "main": { + "allowlist": [ + { + "pattern": "/usr/bin/nvidia-smi", + "note": "GPU monitoring and diagnostics" + }, + { + "pattern": "/usr/bin/docker", + "note": "NIM container lifecycle" + }, + { + "pattern": "/usr/bin/python3", + "note": "Python runtime" + }, + { + "pattern": "/usr/bin/curl", + "note": "Health checks to local services" + }, + { + "pattern": "/usr/bin/git", + "note": "Git operations" + }, + { + "pattern": "/usr/bin/make", + "note": "Make targets" + }, + { + "pattern": "/usr/bin/nats", + "note": "NATS CLI operations" + } + ] + } + }, + "mcp_servers": { + "pmoves-cipher": { + "type": "sse", + "url": "http://${TS_Z890}:8096/sse" + }, + "agent-zero": { + "type": "http", + "url": "http://${TS_Z890}:8080/mcp" + } + }, + "nats_subjects": { + "subscribe": [ + "mesh.gpu.command.v1", + "pinokio.agent.session.v1" + ], + "publish": [ + "mesh.gpu.status.v1", + "mesh.gpu.model.loaded.v1", + "mesh.gpu.command.result.v1", + "pinokio.telemetry.v1" + ] + }, + "damage_control_hooks": true, + "sign_trail": true +} diff --git a/pmoves/configs/tac_trees/pinokio-p7.tac.yaml b/pmoves/configs/tac_trees/pinokio-p7.tac.yaml index b473faff9e..df15673715 100644 --- a/pmoves/configs/tac_trees/pinokio-p7.tac.yaml +++ b/pmoves/configs/tac_trees/pinokio-p7.tac.yaml @@ -61,15 +61,15 @@ root: # Phase 2: Agent Interpreter — SKILL.md Discovery # ========================================================================= - id: p7.interpreter - task: "P7 Agent Interpreter discovers PMOVES services" - context: "P7 'pinokio' skill auto-discovers installed apps via SKILL.md" + task: "P7 Agent Interpreter discovers and launches PMOVES apps" + context: "Official docs: built-in `pinokio` skill auto-discovers installed apps, auto-launches them when needed, waits for readiness, and can generate reusable clients" agent_hint: 5090-claude children: - id: p7.interpreter.tts task: "Ultimate-TTS-Studio discoverable via Agent Interpreter" action: type: manual - expect: "Claude Code 'speak hello' routes through Pinokio to TTS at :7861" + expect: "Claude Code 'speak hello' routes through Pinokio to TTS at :7860 or a fixed HTTPS domain" context: "D:\\pinokio\\api\\Ultimate-TTS-Studio.git\\ — Gradio API at /gradio_api" agent_hint: 5090-claude status: pending @@ -139,7 +139,7 @@ root: # ========================================================================= - id: p7.mesh task: "Cross-machine P7 agent routing via Tailscale" - context: "Tailscale mesh connects 5090, Z890, 4090 laptop — P7 agents should discover remote apps" + context: "Tailscale provides the cross-machine path; Pinokio provides the HTTPS/runtime surface that remote agents consume" agent_hint: 4090-claude status: done notes: "Gate 3 VERIFIED 2026-03-22: 4090→5090 TTS via Tailscale (200 OK, 173ms). All 3 machines on P7. Mobile agent pending." @@ -154,15 +154,14 @@ root: status: done - id: p7.mesh.remote-tts - task: "4090 laptop agent can reach 5090 TTS via Tailscale" + task: "4090 laptop can reach 5090 Pinokio/TTS surface via Tailscale" action: type: manual - expect: "From 4090: 'speak hello' routes via Tailscale to 5090 Ultimate-TTS-Studio" - context: "Requires P7 on both machines + Tailscale mesh + TTS running on 5090" + expect: "From 4090: 5090 Pinokio HTTPS proxy and Ultimate-TTS-Studio endpoints are reachable over Tailscale" + context: "Tailscale provides the WAN path; Pinokio provides the HTTPS/runtime surface on the 5090 node" agent_hint: 4090-claude status: done - depends_on: [p7.upgrade.5090, p7.upgrade.4090, p7.interpreter.tts] - notes: "Verified 2026-03-22: curl https://powerfulmoves-1.tailcad9b4.ts.net/gradio_api/info → 200 OK in 173ms from 4090 laptop. 5090 Pinokio Caddy proxy resolves the localhost binding issue." + notes: "Verified 2026-03-22: curl https://powerfulmoves-1.tailcad9b4.ts.net/gradio_api/info → 200 OK in 173ms from 4090 laptop. Network path is proven; full Agent Interpreter UX remains tracked separately under p7.interpreter.tts." - id: p7.mesh.mobile task: "Mobile agent (Discord/Openclaw) reaches 5090 via mesh" @@ -172,17 +171,17 @@ root: context: "Openclaw/Hermes Agent pattern from P7 announcement" agent_hint: 4090-claude status: pending - notes: "BLOCKED: P7 upgrade done ✓. Depends on remote TTS routing (p7.mesh.remote-tts) working first" + notes: "UNBLOCKED by p7.mesh.remote-tts. Pending full Discord/Openclaw/Hermes end-to-end validation." # ========================================================================= # Phase 6: PMOVES Service SKILL.md Registration # ========================================================================= - id: p7.skillmd - task: "PMOVES services registered with P7 via SKILL.md" - context: "Each service with an API needs SKILL.md for P7 discovery" + task: "PMOVES launchers expose Pinokio metadata, with optional SKILL.md hints" + context: "Official Pinokio docs say Agent Interpreter baseline does not require per-app SKILL.md; PMOVES SKILL.md files remain additive hints/docs" agent_hint: z890-claude status: done - notes: "All 3 children done: format (5 examples), services (PR #1085), TTS (ARTSTUFF). Phase 6 complete." + notes: "All 3 children done: format (5 examples), services (PR #1085), TTS (ARTSTUFF). These hints are additive on top of Pinokio's built-in pinokio/gepeto skills." children: - id: p7.skillmd.format task: "SKILL.md format spec understood" @@ -194,7 +193,7 @@ root: notes: "Format self-evident from 5 shipped SKILL.md files: cipher-beats, holographic-blocks, pmoves-services, pmoves-remote, model-registry (all in pbnj/pinokio/api/*/SKILL.md)" - id: p7.skillmd.tts - task: "Ultimate-TTS-Studio SKILL.md created" + task: "Ultimate-TTS-Studio optional SKILL.md hint created" action: type: manual expect: "SKILL.md in TTS app folder describes Gradio API endpoints" @@ -205,7 +204,7 @@ root: notes: "Shipped at pmoves/docs/ARTSTUFF/Ultimate-TTS-Studio.git/SKILL.md" - id: p7.skillmd.services - task: "PMOVES Services launcher SKILL.md created" + task: "PMOVES Services launcher optional SKILL.md hint created" action: type: manual expect: "SKILL.md in pmoves-services describes Docker profile start/stop" diff --git a/pmoves/docs/AGENTS/AGNOTE_P7_PLAYGROUND.md b/pmoves/docs/AGENTS/AGNOTE_P7_PLAYGROUND.md index 0a8d362559..4cf6de7e1a 100644 --- a/pmoves/docs/AGENTS/AGNOTE_P7_PLAYGROUND.md +++ b/pmoves/docs/AGENTS/AGNOTE_P7_PLAYGROUND.md @@ -138,8 +138,8 @@ The audience says "that's my car." Because it moved like something POWERFUL. | # | Task | Status | |---|------|--------| | 1 | ~~Upgrade Pinokio 7 on laptop~~ | DONE | -| 2 | Test P7 Agent Interpreter → 5090 TTS via Tailscale | Pending | -| 3 | Test mobile agent (Discord/Openclaw) → TTS flow | Pending | +| 2 | Test 4090 → 5090 P7/TTS network path via Tailscale | DONE (Gate 3 verified 2026-03-22 via Pinokio HTTPS proxy) | +| 3 | Test mobile agent (Discord/Openclaw) → TTS flow | NEXT | | 4 | **Claim W1: Agent Theming + Terminal** (foundation lane) | **Recommended** | --- @@ -159,21 +159,27 @@ ls pmoves/configs/tac_trees/ | wc -l # expect: 20 files (including pinokio --- -## Step 5: P7 Strategy Pivot — Pinokio as Mesh Transport Layer (2026-03-18) +## Step 5: P7 Strategy Pivot — Pinokio as Agent Runtime Layer (2026-03-18, docs sync 2026-03-25) ### The Discovery -Security hardening (PR #1014) revealed the real architecture: **P7's Caddy proxy (pmoves-net) is the mesh transport layer**, not raw Tailscale IPs. P7 at `100.73.74.3:42000` serves the full Pinokio UI across the Tailscale mesh, with each app getting a proxied `42XXX` port. +Official Pinokio docs describe P7 as a **Localhost Cloud** with a CLI server, Agent Interpreter, Agent Launcher, LWW discovery, instant HTTPS domains, and remote control from other devices. -Instead of fighting raw port access, P7 becomes the **agent orchestration layer**. +For PMOVES, that means Pinokio is the **agent/runtime surface** and remote operator entry point, while **Tailscale remains the WAN overlay** and **NATS + Supabase + Agent Zero** remain the actual mesh control plane. + +Instead of treating P7's Caddy proxy as the mesh itself, we should treat Pinokio as the edge/runtime layer that: + +1. Exposes stable HTTPS entrypoints for local apps +2. Lets agents discover, start, and wait on installed apps through the built-in `pinokio` skill +3. Persists workspaces, sessions, and selected skills through Agent Launcher sandboxes ### Why P7 > Raw IDE -1. **Isolation:** Each agent instance has its own P7 session — no git contamination (Gemini committed chrome-ext + security changes in one commit — never again) -2. **Discovery:** P7 VPN scan finds apps across mesh automatically -3. **Customization:** SKILL.md composition lets each instance specialize -4. **Resumption:** Agent Launcher tracks sessions — pick up where you left off -5. **Mobile:** Pixel 10 Pro XL (100.103.111.121) can access any P7 instance via Tailscale +1. **Isolation:** Agent Launcher sandboxes let each workspace carry its own provider-native instructions and selected skills. +2. **Discovery:** The built-in `pinokio` skill can search installed apps, rank them, auto-launch them, and wait for readiness. +3. **Customization:** Pinokio launchers (`pinokio.js`, `pinokio.json`, `ENVIRONMENT`, scripts) give us a standard packaging surface for PMOVES tools. +4. **Resumption:** Agent Launcher tracks workspaces and sessions so agents can reopen the same lane later. +5. **Remote control:** Pinokio provides HTTPS surfaces for localhost apps, and we extend that across machines with Tailscale. ### Per-Instance Architecture @@ -202,7 +208,11 @@ The `_BIND` variables are in the working tree but the damage-control hook blocks Restructured z890-claude and 5090-claude dual entries as `alters` array (schema 1.1.0). Both glyphs preserved as intentional persona variants — ▣/⚙ for z890, ◈/♫ for 5090. Primary identity unchanged; alters available for future persona-switching. -### SKILL.md Files Created +### PMOVES App Hints (Additive, Not Required) + +Official Pinokio docs say baseline Agent Interpreter support does **not** require per-app `SKILL.md`; Pinokio ships the built-in `pinokio` and `gepeto` skills under `~/.agents/skills`. + +Our app-local `SKILL.md` files are still useful as PMOVES-specific hints and docs, but they are **additive**, not the primary mechanism that makes Pinokio apps discoverable. | File | Discovery Target | |------|-----------------| @@ -213,8 +223,8 @@ Restructured z890-claude and 5090-claude dual entries as `alters` array (schema - P7 installed on 4090 laptop — Agents tab visible, pmoves-net named - Tailscale mesh: all 3 machines connected -- TTS on 5090: binds `localhost:7861` — needs `--server-name 0.0.0.0` or Caddy proxy route for mesh access -- Flute-Gateway reports `ultimate_tts: false` — TTS service not yet reachable from Flute +- 5090 TTS reachable from 4090 through Pinokio HTTPS/Caddy surface (`/gradio_api/info` → 200 OK, 173ms) +- Remaining gap is full Agent Interpreter UX validation plus mobile-triggered end-to-end flow --- diff --git a/pmoves/docs/MODEL_FABRIC_CONTRACT.md b/pmoves/docs/MODEL_FABRIC_CONTRACT.md index ab7f27ab2c..71f553f985 100644 --- a/pmoves/docs/MODEL_FABRIC_CONTRACT.md +++ b/pmoves/docs/MODEL_FABRIC_CONTRACT.md @@ -28,14 +28,16 @@ This is the execution policy for: - All service routing decisions resolve through this plane. ### 2) Routing Plane (execution) -- TensorZero is the default routing gateway for OpenAI-compatible execution. -- Runtime model calls from agents/workers route through TensorZero unless a lane has explicit exemption. -- TensorZero runs must be visible in observability (ClickHouse + Prometheus/Grafana + model-readiness evidence). +- Nexus is the provider abstraction boundary; TensorZero remains the default execution gateway. +- Runtime model calls from agents/workers route through TensorZero unless a lane has an explicit Nexus exemption. +- Native provider SDKs are allowed only through additive Nexus adapters that preserve PMOVES request/response parity. +- TensorZero runs remain the authoritative observability path (ClickHouse + Prometheus/Grafana + model-readiness evidence); native SDK lanes must mirror equivalent telemetry. - Open Notebook in PMOVES uses provider mode overlay (`tensorzero`, `hybrid`, `native`). - Agent Zero and Archon consume aliases and endpoint contracts, not provider-specific schemas. +- Canonical adapter policy lives in `pmoves/config/model_nexus.yaml`. ### 3) Runtime Plane (inference providers) -- Local providers (default): Ollama local, vLLM, LM Studio/OpenAI-compatible, HuggingFace local endpoints, TTS engines. +- Local providers (default): Ollama local, vLLM, NVIDIA NIM, LM Studio/OpenAI-compatible, HuggingFace local endpoints, TTS engines. - Cloud fallback order (explicit): 1. Ollama Cloud 2. Cloudflare Workers AI free-tier lanes (through TensorZero/OpenAI-compatible bridge) @@ -55,6 +57,7 @@ This is the execution policy for: - `mapping`: service/function -> alias/model selection policy. - `deployment`: where a model is currently loaded and usable. - `persona binding`: persona/model preference + fallback chain. +- `nexus adapter`: additive wrapper that maps provider-native SDK calls onto PMOVES request/response parity. ## Integration Contract (all repos/submodules) 1. Must resolve models via alias/registry mapping. @@ -64,6 +67,8 @@ This is the execution policy for: 5. Must implement fallback priority: local -> Ollama Cloud -> Cloudflare free tier -> coding-plan lanes. 6. Must keep secrets in approved channels (`*_FILE`, GH secrets, vault), never static defaults. 7. Must preserve Graphiti + CHIT rail compliance for agent/worker PRs (`chit-flow-pr-monitor-strict` in merge lane). +8. Provider-native SDK usage must flow through a Nexus adapter or be explicitly exempted with parity evidence. +9. Native SDK lanes must preserve tool, streaming, structured-output, trace, and usage parity with TensorZero-backed lanes. ## Open Notebook Overlay Policy - Keep upstream credential system and provider UX intact. @@ -103,10 +108,11 @@ This is the execution policy for: ## PR Review Lens (Topology + Agents) Every PR touching networking, agents, or model routing should be reviewed for: 1. Local-first fallback ordering is preserved. -2. TensorZero remains primary route and call telemetry is observable. -3. Graphiti/CHIT rails remain intact for worker handoffs. -4. Compose/network namespace/port changes do not break deterministic topology. -5. Evidence includes model-readiness + relevant smokes. +2. TensorZero remains primary route unless a Nexus exemption is explicitly documented. +3. Native SDK adapters preserve request/response parity and mirrored telemetry. +4. Graphiti/CHIT rails remain intact for worker handoffs. +5. Compose/network namespace/port changes do not break deterministic topology. +6. Evidence includes model-readiness + relevant smokes. ## Operator and Coding-Agent Roles - Agent Zero / Archon: runtime orchestration + policy execution. @@ -118,3 +124,6 @@ Every PR touching networking, agents, or model routing should be reviewed for: 2. Normalize model type taxonomy across registry, GPU orchestrator, and creator services. 3. Add one compatibility matrix doc per integration lane (agents, creator, voice, notebook, RL). 4. Gate PRs that alter model routing with mandatory readiness + observability evidence attachments. +5. Add Nexus adapter shims for OpenAI, Anthropic, and Gemini native SDK lanes. +6. Add NVIDIA NIM-backed Nemotron routing for beefy GPU claw lanes. +7. Add parity tests that compare TensorZero-backed responses against native SDK adapter envelopes. diff --git a/pmoves/docs/NEXUS_PROVIDER_PARITY.md b/pmoves/docs/NEXUS_PROVIDER_PARITY.md new file mode 100644 index 0000000000..9a0780b2e9 --- /dev/null +++ b/pmoves/docs/NEXUS_PROVIDER_PARITY.md @@ -0,0 +1,111 @@ +# PMOVES Nexus Provider Parity +_Last updated: 2026-03-25_ + +## Purpose +Nexus is the provider abstraction boundary for PMOVES.AI. + +It does not replace TensorZero today. It defines the rules that let PMOVES use provider-native SDKs where they are strongest while keeping one runtime contract for the mesh. + +That means: +- TensorZero stays the default execution gateway. +- Provider-native SDKs stay additive. +- Every provider must map back onto the same PMOVES envelope for tools, streaming, structured outputs, retries, tracing, and usage capture. + +The authoritative config for this layer is `pmoves/config/model_nexus.yaml`. + +## Why This Exists +The repo currently mixes three realities: +1. TensorZero is the canonical shared gateway. +2. Several services already rely on OpenAI-compatible shapes. +3. Future agent maturity will benefit from provider-native features instead of forcing every provider through the same lowest-common-denominator wrapper. + +Without an explicit boundary, each lane will invent its own adapter logic. That breaks parity, observability, and debugging. + +Nexus fixes that by separating: +- provider culture: use the native SDK when it unlocks real provider capability +- PMOVES contract: keep one request and response shape for the mesh +- execution default: keep TensorZero as the safe default path + +## Architecture Boundary +### Control Plane +- Supabase model registry remains authoritative for providers, aliases, mappings, and deployment state. +- CHIT and Graphiti remain the workflow rails. + +### Routing Plane +- Nexus decides the adapter boundary. +- TensorZero remains the default route. +- Native SDK adapters are explicit exemptions, not ad hoc bypasses. + +### Runtime Plane +- OpenAI uses the official SDK and native request surface where justified. +- Anthropic uses the official SDK and native request surface where justified. +- Gemini uses the provider-native Google client where justified. +- NVIDIA NIM serves Nemotron on beefy GPU nodes where optimized local inference matters more than remote provider access. +- Ollama and vLLM remain local-first lanes, typically routed through TensorZero unless a lane proves a reason not to. + +## Parity Rules +A provider-native lane is acceptable only if it preserves these PMOVES-level behaviors: +- Tool calls normalize into one logical shape. +- Structured outputs normalize into one logical shape. +- Streaming emits a predictable event model. +- Retry and timeout policy are explicit. +- Trace context is preserved for NATS, CHIT, and Graphiti correlation. +- Usage and cost accounting can still be mirrored into shared observability. +- Health and readiness checks stay visible at the service level. + +If a native lane cannot preserve those rules, it should route through TensorZero. + +## Provider Strategy +### OpenAI +- Prefer the official OpenAI SDK for native lanes. +- Use provider-native reasoning, multimodal, tools, and structured output features when they materially improve the lane. +- Mirror request metadata and usage back into the shared PMOVES telemetry path. +- Fall back to TensorZero when parity would otherwise drift. + +### Anthropic +- Prefer the official Anthropic SDK for native reasoning lanes. +- Keep the same PMOVES envelope so upstream agents do not learn Anthropic-specific schemas. +- Fall back to TensorZero for shared or cost-sensitive routes. + +### Gemini +- Prefer the provider-native Google client for multimodal and long-context lanes. +- Keep media and retrieval flows mapped onto the same PMOVES envelope. +- Fall back to TensorZero when the lane needs the shared gateway path. + +### NVIDIA / Nemotron +- Prefer NVIDIA NIM for Nemotron on 5090-class and larger GPU nodes. +- Treat Nemotron as a first-class local reasoning lane for claw and coding workflows. +- Mirror health, usage, and trace metadata back into the shared PMOVES telemetry path. +- Keep AIQ as the workflow/orchestration layer when NVIDIA-native agent composition is needed; keep NIM as the inference substrate. +- Fall back to TensorZero or Ollama when a direct Nemotron lane is unavailable. + +### Local Providers +- Ollama and vLLM remain first-class local providers. +- TensorZero remains the preferred way to route into them for observability and fallback control. +- Direct local adapter use should be the exception, not the default. + +## Implementation Order +1. Keep TensorZero as the default route for all current mesh lanes. +2. Add additive Nexus adapter scaffolding for OpenAI, Anthropic, and Gemini. +3. Add NVIDIA NIM-backed Nemotron support for beefy GPU claw lanes. +4. Normalize one PMOVES request envelope and one response envelope. +5. Add parity tests comparing native adapters to TensorZero-backed results. +6. Expose adapter health and mirrored usage metrics. +7. Expand the P7 and skill lanes only after the agent mesh contract is stable. + +## Review Split +### Codex +- Own the Nexus contract docs and config. +- Build adapter scaffolding and parity tests. +- Keep changes additive to the existing TensorZero-first routing policy. + +### 4090 Claude +- Validate remote P7, skill, and multimodal mesh behavior. +- Pressure-test local model parity and timeout behavior on the actual nodes. +- Verify that voice and mobile-adjacent flows survive the new adapter layer. + +## Immediate Next Files +- `pmoves/config/model_nexus.yaml` +- `pmoves/docs/MODEL_FABRIC_CONTRACT.md` +- native adapter modules under the service lanes that need direct SDK use +- parity tests for request and response normalization diff --git a/pmoves/services/common/__init__.py b/pmoves/services/common/__init__.py index d1cc468109..1a5129d2a8 100644 --- a/pmoves/services/common/__init__.py +++ b/pmoves/services/common/__init__.py @@ -13,11 +13,30 @@ """ from .telemetry import * # noqa: F401,F403 +from .model_nexus import ( # noqa: F401 + NexusConfigError, + get_nexus_lane, + get_nexus_provider, + load_model_nexus, + model_nexus_path, + provider_allowed_for_lane, + provider_requires_nexus_adapter, +) # Ensure __all__ exists before extending (telemetry may not define it) if '__all__' not in globals(): __all__ = [] +__all__ += [ + "NexusConfigError", + "get_nexus_lane", + "get_nexus_provider", + "load_model_nexus", + "model_nexus_path", + "provider_allowed_for_lane", + "provider_requires_nexus_adapter", +] + # CHIT Geometry exports (optional - graceful import) try: from .geometry_models import ( # noqa: F401 diff --git a/pmoves/services/common/model_nexus.py b/pmoves/services/common/model_nexus.py new file mode 100644 index 0000000000..ae0e183a24 --- /dev/null +++ b/pmoves/services/common/model_nexus.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from functools import lru_cache +from pathlib import Path +from typing import Any, Mapping + +import yaml + +_PMOVES_DIR = Path(__file__).resolve().parents[2] +_DEFAULT_NEXUS_PATH = _PMOVES_DIR / "config" / "model_nexus.yaml" + + +class NexusConfigError(RuntimeError): + """Raised when the Nexus provider registry is missing or malformed.""" + + +def model_nexus_path() -> Path: + """Return the canonical Model Nexus config path.""" + + return _DEFAULT_NEXUS_PATH + + +@lru_cache(maxsize=4) +def _load_model_nexus_cached(resolved_path: str) -> dict[str, Any]: + path = Path(resolved_path) + if not path.exists(): + raise NexusConfigError(f"Model Nexus config not found: {path}") + + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + if not isinstance(data, dict) or "providers" not in data or "lanes" not in data: + raise NexusConfigError(f"Model Nexus config is malformed: {path}") + return data + + +def load_model_nexus(path: str | Path | None = None) -> dict[str, Any]: + """Load the Model Nexus registry from disk. + + The config is cached by absolute path because most services read the shared + registry repeatedly during startup and health checks. + """ + + target = Path(path) if path is not None else model_nexus_path() + return _load_model_nexus_cached(str(target.resolve())) + + +def get_nexus_provider(name: str, config: Mapping[str, Any] | None = None) -> Mapping[str, Any]: + """Return a provider definition from the Nexus registry.""" + + data = config or load_model_nexus() + providers = data.get("providers", {}) + if name not in providers: + raise KeyError(f"Unknown Nexus provider: {name}") + return providers[name] + + +def get_nexus_lane(name: str, config: Mapping[str, Any] | None = None) -> Mapping[str, Any]: + """Return a lane definition from the Nexus registry.""" + + data = config or load_model_nexus() + lanes = data.get("lanes", {}) + if name not in lanes: + raise KeyError(f"Unknown Nexus lane: {name}") + return lanes[name] + + +def provider_allowed_for_lane( + lane_name: str, + provider_name: str, + config: Mapping[str, Any] | None = None, +) -> bool: + """Return True when a provider is valid for the given lane. + + The lane default is always allowed. Native overrides must be explicitly + listed in `allowed_native_overrides`. + """ + + lane = get_nexus_lane(lane_name, config=config) + if provider_name == lane.get("default_provider"): + return True + return provider_name in set(lane.get("allowed_native_overrides", [])) + + +def provider_requires_nexus_adapter( + provider_name: str, + config: Mapping[str, Any] | None = None, +) -> bool: + """Return True when the provider is a native SDK lane that must preserve parity.""" + + provider = get_nexus_provider(provider_name, config=config) + return provider.get("role") == "native_provider" diff --git a/pmoves/services/common/tests/test_model_nexus.py b/pmoves/services/common/tests/test_model_nexus.py new file mode 100644 index 0000000000..9e5636770d --- /dev/null +++ b/pmoves/services/common/tests/test_model_nexus.py @@ -0,0 +1,60 @@ +import importlib +import sys +from pathlib import Path + +import pytest + +repo_root = Path(__file__).resolve().parents[4] +if str(repo_root) not in sys.path: + sys.path.append(str(repo_root)) + +sys.modules.setdefault("services", importlib.import_module("pmoves.services")) + +from services.common.model_nexus import ( + NexusConfigError, + get_nexus_lane, + get_nexus_provider, + load_model_nexus, + model_nexus_path, + provider_allowed_for_lane, + provider_requires_nexus_adapter, +) + + +def test_load_model_nexus_default_path(): + data = load_model_nexus() + assert data["contract_version"] == 1 + assert model_nexus_path().name == "model_nexus.yaml" + + +def test_get_nexus_provider_openai(): + provider = get_nexus_provider("openai") + assert provider["sdk_family"] == "openai" + assert provider["fallback_lane"] == "tensorzero" + assert provider_requires_nexus_adapter("openai") is True + + +def test_get_nexus_provider_nvidia_nim(): + provider = get_nexus_provider("nvidia_nim") + assert provider["sdk_family"] == "openai_compatible" + assert provider["fallback_lane"] == "tensorzero" + assert provider_requires_nexus_adapter("nvidia_nim") is False + + +def test_provider_allowed_for_lane_uses_default_and_overrides(): + assert provider_allowed_for_lane("coding_agents", "tensorzero") is True + assert provider_allowed_for_lane("coding_agents", "openai") is True + assert provider_allowed_for_lane("coding_agents", "nvidia_nim") is True + assert provider_allowed_for_lane("nemotron_claw", "nvidia_nim") is True + assert provider_allowed_for_lane("p7_and_skills", "anthropic") is False + + +def test_get_nexus_lane_missing_raises_key_error(): + with pytest.raises(KeyError): + get_nexus_lane("missing-lane") + + +def test_load_model_nexus_missing_path_raises(): + missing = repo_root / "pmoves" / "config" / "does_not_exist.yaml" + with pytest.raises(NexusConfigError): + load_model_nexus(missing)