Noviz platform architecture diagram

How Noviz is built — one relay engine, every ERPNext install

Three diagrams, from three different angles: the components that make up the system, what happens when a tenant is provisioned or an operator reviews the platform, and the full path a single chat prompt takes through a customer's own ERPNext and back. Each one ends the same way — one shared Relay Engine serves every tenant, so a fix made once reaches every customer's very next turn, with no per-tenant redeploy and no plugin update required.

Diagrams are wide — scroll horizontally on a narrow screen, or view on a larger one for the full picture at once.

INSIDE THE TENANT'S OWN ERPNEXT — ONE OF MANY 💬 Noviz AI Chat Native Frappe desk page — no separate login Response renderer — tables, cards, PDF export Open-source plugin, zero business logic inside ⚙️ Noviz AI Settings Relay URL + API key (encrypted) Enable / disable toggle Set by the tenant's OWN admin, not us HTTPS · tenant API key · zero ERP credential sent BACKEND — NOVIZ AI RELAY (shared across every tenant) 🔑 Tenant Registry + Auth API-key hash resolves exactly ONE tenant Issues a relay session token (JWT), scoped to this tenant + turn only 🗂️ Turn State Persists conversation state across separate HTTP calls, so a multi-step exchange can pause here and resume → stateless, not a socket 🧠 Relay Reasoning Engine Builds the prompt + this person's real allowed-tool list, drives the tool-call loop, renders tables/cards/ PDF server-side 🔀 Call Translator + Gateway Reduces the tool call to a generic get_list/get_doc spec — zero business vocabulary crosses Gate: tenant tier × real ERPNext role, neither ever widens the other 📤 Response Composer Assembles the call spec or final answer sent back down to the plugin — never a raw internal tool name or a stored ERP credential, ever HTTPS, generic spec 🔌 Plugin Dispatcher Back in the tenant's own ERPNext — runs the spec via Frappe's ORM, AS that user 🎚️ Trial Token Budget Whichever comes first — the token budget or the day count ends the trial checked before another model call is spent 🔁 Bounded Round-Trip Loop Multiple tool calls per question, capped at a fixed limit before a final answer is forced 📊 Interaction Logger Every turn + tool call + which gate fired, across every tenant — feeds the review loop below ↓ 📈 Per-Tenant Usage Every model call's token cost, recorded per tenant — today, this month, all-time BUSINESS MODULES — ONE CONFIG FOLDER EACH (ENTITIES · RULES · TOOLS) CRM Selling Buying Stock Accounting Assets HR & Payroll Manufacturing Quality Projects Support Analytics Every module here is one folder of plain config — entities, rules, schema — plus named reports (trial balance, stock ledger…): which entities exist, which fields map to ERPNext's real fields, which actions need which role, what validation applies. Shared across every tenant. The Call Translator + Gateway read this on every call; nothing is hardcoded per-tenant in the engine itself. read by the Gateway on every tool call DATA STORE 🗄️ Postgres Tenant registry · turn state (pause/resume) Per-tenant token usage · trial budget · access level interaction_log — the shared review source below One shared database serving every tenant reads / writes EXTERNAL SERVICES CROSSES THE NETWORK BOUNDARY 🌐 AI Relay Gateway Noviz's own gateway to the model layer — the ONLY thing the Relay calls out to, one swappable interface 📧 Email (SMTP) Platform-admin only — password resets, billing key delivery never part of the per-turn chat path reasoning calls only 🔁 ONE SHARED ENGINE — EVERY TENANT GETS SHARPER AT ONCE Sourced from the Interaction Logger above, across every tenant — nothing here changes the pipeline's shape, only what's loaded into it. 1 · Every tenant's turns logged Prompt, tools called, which gate fired, latency — always on, every tenant 2 · Reviewed across tenants One shared engine means a bad pattern is visible platform-wide, not buried in one customer's logs 3 · Fixed once, centrally A system-prompt rule, a module's schema, or the query engine itself — one change, not one per tenant 4 · Redeployed to the Relay Same Relay Reasoning Engine interface — no per-tenant redeploy, no plugin update 5 · Every tenant, sharper The very next request from ANY tenant already gets the fix — same turn ↻ continuous — one fix reaches every tenant's very next turn, not just the next release

What this shows: a native chat page and a settings screen live inside each tenant's own ERPNext and talk only to one shared Relay over an authenticated session. That Relay — tenant registry and auth, turn state, reasoning, a call translator and role/tenant gateway — is configured entirely by a flat grid of per-module folders (no per-module code inside the engine itself), backed by one shared Postgres database, and reaches exactly one external system by design: an AI Relay Gateway to the model layer, swappable behind one interface. Execution against real ERP data never happens here — it happens back inside the tenant's own ERPNext, under that person's own session, through the Plugin Dispatcher. The band at the bottom is what makes this a platform rather than one static integration per customer: a turn logged from ANY tenant can surface a fix that gets made once, centrally, and reaches every tenant's very next turn — no per-tenant redeploy, no plugin update.

🛠️ Admin actions tenant admin (1–2) · Zeetech operator (3–4) 1 · TENANT CONFIGURES THE PLUGIN ⚙️ Noviz AI Settings form Relay URL, API key, enable / disable toggle Set inside their own ERPNext 🔑 Relay — tenant lookup API-key hash resolves this tenant on every request resolved on every request 2 · TENANT SETS ITS COMPANY POLICY 📄 Company Policy (per tenant) Rules THIS tenant wants the agent to follow — scoped to that tenant's own row, only 🧠 Read by the Relay Loaded alongside this tenant's access level on every turn 🗄️ Postgres — tenants table Stored alongside this tenant's own settings row — one shared table read again on every future turn 3 · ZEETECH PROVISIONS THE TENANT 💳 Signup — trial or paid A billing endpoint, its own secret, creates a real tenant row + a real API key 🗄️ Postgres — tenants table access_level, trial_expires_at, hashed key — no ERP credential the key the tenant enters into Settings 4 · 🔁 ZEETECH REVIEWS & IMPROVES 🚩 Flagged across tenants Any 👎 feedback, or a gate that blocked/warned — from ANY tenant's real turns 🛠️ Zeetech patches it, once A prompt rule, schema fix, or query-engine change — centrally the fix becomes new config closes the loop — same console, no redeploy Same shared Relay Tenant Registry + Auth · Turn State · Relay Reasoning Engine · Call Translator + Gateway · Plugin Dispatcher nothing here needed a code deploy or a restart — and flow 4 means EVERY tenant arrives here a little sharper than yesterday

What this shows: two different admins, two different surfaces. A tenant's own admin, inside their own ERPNext, only ever touches flows 1–2: the plugin's connection settings and that tenant's own Company Policy, both just rows the shared Relay re-reads on the next request. Zeetech's side, flows 3–4, is provisioning (a new tenant + API key, never an ERP credential) and review: interactions flagged by a 👎 or a blocked gate, from any tenant, get fixed once, centrally — no separate "admin path" per tenant, no redeploy, just a sharper Relay on the very next turn, for everyone.

💬 User in Noviz AI Chat types a message — native page, no separate login 🔌 Plugin → Relay One HTTPS POST, tenant API key — this person's real Frappe roles travel with the prompt, every time arrives at the Relay 1 · 🔑 Relay resolves the tenant API-key hash → exactly ONE tenant, turn state loaded if this continues an earlier exchange (stateless HTTP) 2 · 🧠 Relay Reasoning Engine Prompt + this tenant/role's real allowed-tool list, sent to the AI Relay Gateway → picks a tool, or answers directly the ONE external call 🌐 AI Relay Gateway (external) tool call chosen → a direct answer skips straight to rendering 3 · 🔀 Call Translator + Gateway Tenant tier × this person's real ERPNext role — neither ever widens the other Reduces the call to a generic get_list/get_doc spec, outcome logged not allowed → refused, before the plugin sees it passes 4 · 🔌 Plugin Dispatcher Back in the tenant's OWN ERPNext — executes the spec via Frappe's ORM, AS the signed-in user — never a stored key native permissions apply exactly as-is same process — not a network call 🏢 ERPNext's own database result posted back via /continue 5 · ↩️ Result returns to the Relay Turn resumes from where it paused. More tool calls may follow, bounded by a fixed round-trip limit ↺ loops back to step 2 for another tool call 6 · 🖥️ Rendered in the chat Via frappe.markdown() — plain text, a table/card list, or a Download PDF link (ERPNext's own real print format) 7 · 📊 Logged Prompt, tools used, which gate fired — this tenant's interaction_log 8 · 🔁 Feeds the shared review Curated across every tenant, then a prompt or schema fix ships once to the one shared Relay Engine → steps 2 & 3 sharper for EVERY tenant, next turn review loop — improves steps 2 & 3 for every tenant, continuously 💬 Asked a follow-up? The plugin carries this turn's id — picked up right where it left off real cross-message memory — no re-fetch needed

What this shows: every hop a single chat turn takes, start to finish — the Relay resolves exactly one tenant, reasons in a loop that can call more than one tool, but every single tool call is reduced to a generic spec and re-checked against tenant tier and that person's real ERPNext role before it's allowed anywhere near the plugin, which then executes it in-process, inside that tenant's own ERPNext, under their own session — never a stored credential, never a network hop to get there. Steps 7–8 are what makes this a platform rather than one static integration per customer: the turn isn't just answered and forgotten, it's logged, and a fix curated from ANY tenant's history rolls back into steps 2–3 for every tenant's next turn — not just the one that hit it first.