Selected work  ·  Applied AI

The model is
the easy part.

Northnode built and runs Delphi, the production AI assistant inside the PrecisionOS VR surgical training platform. Not a chatbot bolted onto a product — one assistant across three surfaces: a mobile chat client, a spoken agent inside the headset, and automated performance assessment, backed by a purpose-built retrieval corpus, a memory system, and an evaluation harness. Everything below is shipped and serving surgeons, residents, and medical students.

1,800+
Voice sessions in VR, from 350+ clinicians
6,500+
Chat messages across 1,700+ conversations
2,900+
AI assessment reports from real sessions
700+
Documents in the clinical retrieval corpus

In production since January 2025 · 900+ medical institutions on the platform

Decisions that
determined whether it worked

[ 01 ]
One workload, one model

Different jobs want different models. Deciding which tools a question needs is fast, cheap work that never writes prose. A streamed answer to a surgeon wants quality. An overnight assessment report wants heavy reasoning and doesn't care about latency.

The platform runs on a provider-neutral abstraction, so each workload sits on the model that suits it — Anthropic Claude and OpenAI models side by side, chosen on latency and quality rather than locked in by the code. Moving a workload between them is a configuration change, not a rewrite.

Choosing the model per workload rather than per product is the single biggest lever on spend, and the abstraction is what makes it a one-line change instead of a migration.

[ 02 ]
Prompts that assemble themselves

A single large system prompt holding every rule for every capability doesn't hold up. Rules bleed across domains — the model applies product-catalogue formatting to a pure anatomy question — and context is spent on rules that don't apply to the question asked.

So the prompt is assembled per request. A fast model decides which tools the question needs, the tools run, and only the domain rules relevant to what actually ran are injected before the answer is generated. Answers stop inheriting rules from unrelated domains, and adding a new capability becomes dropping in a prompt module and a mapping rather than editing a monolith.

The prompt also adapts to who is asking — role, institution, and an inferred experience level drive terminology and depth, so a first-year resident and a practising surgeon get different registers of the same answer. Where that signal is genuinely absent, the model is instructed to ask once rather than guess.

[ 03 ]
Tools, and what the model is allowed to see

Around fifteen tools spanning catalogue search, per-user training telemetry, progress, video, knowledge retrieval, memory, and an admin-only analytics set. Three principles we now build in by default:

  • Gate capability at the schema, not in the prompt. A user without an entitlement never sees those tools and never sees the prompt text describing them. Asking a model nicely not to use something is not access control.
  • Scope results inside the tool, not after. Content a user isn't entitled to never enters the context window in the first place.
  • Never show a surgeon a leaked internal. Models occasionally emit raw tool-call syntax as visible text; that's stripped and logged rather than rendered. Tool progress surfaces as real status instead of a spinner.
[ 04 ]
Retrieval built around concepts, not page breaks

Standard RAG chunks documents on page boundaries, which cuts straight through clinical reasoning. We built a pipeline that restructures licensed source material around clinical concepts — surgical approaches, anatomical structures, danger zones, decision logic — so a retrieval unit matches the shape of the question a trainee actually asks. The corpus runs to over 700 generated documents across seven content types, with 2,200+ figures extracted from source material.

  • Hybrid search. Vector and keyword retrieval merged, because pure vector search kept missing exact anatomical and device terminology.
  • Relationship expansion without a graph database. Documents declare their related concepts and those are pulled alongside a primary hit — most of what teams stand up GraphRAG to get, at a fraction of the infrastructure.
  • Synthesised retrieval units. Comparison tables, decision trees, and pre-op checklists that don't exist in the source text — flagged as synthesised, never passed off as sourced.
  • Human sign-off is non-negotiable. Automated screening handles consistency, completeness, and format; clinical accuracy is signed off by physicians. We ruled out LLM-based validation of clinical content deliberately. Figures are extracted from source, never generated, and every clinical claim stays traceable to its origin.
[ 05 ]
A voice agent that speaks from what happened

Delphi also runs as a spoken assistant inside the headset, spanning the session API, a real-time audio bridge, and the game engine client. Two problems worth naming:

  • Wake-word interaction. In a headset with an open mic, a conventionally configured voice agent answers everything — including the user talking to someone else in the room. Voice detection stays on, automatic response goes off, and the agent replies only when addressed by name, then holds a short follow-up window so requests can be chained. Rolled out per application, so existing behaviour was provably unaffected.
  • Action before narration. The agent can drive the simulation — grab an instrument, start a tutorial, move the imaging equipment. Naively, the model narrates the action it intends; then the action fails, and the assistant has just lied to a trainee. Stateful actions fire first, the automatic spoken reply is suppressed, and the engine's real runtime outcome is what triggers the response. The assistant speaks from what happened, not from what it asked for.

Per-application prompts with live session context are injected at launch, so content authors change behaviour without a deploy.

[ 06 ]
Memory with a clear owner

Per-user memory splits into two zones with strict ownership, so two writers never fight over the same fact. Derived zones belong to a scheduled aggregation over training telemetry — volume, categories practised, competency signals mapped to the relevant accreditation framework. The asserted zone belongs to the user and the model, written only after the user explicitly states or confirms something.

The rules matter as much as the schema: never save a guess as confirmed, never store patient-identifying information, and reuse a stable key when updating a fact so corrections replace rather than stack into contradictions. Acknowledge a save once, naturally, and never otherwise narrate the memory system at the user.

[ 07 ]
Coaching, generated

After a VR training session the platform produces a structured coaching report on a heavy reasoning model — what went well, what went wrong, cognitive coaching, and ranked practice recommendations — returned as schema-constrained output and rendered to both the mobile app and email. Nearly three thousand have been generated from real sessions.

Clinical educators author the per-module assessment context themselves in an internal admin tool, so tuning how the system assesses a procedure doesn't require an engineer or a deploy. Tone calibration was the hard part and took real iteration: a critical safety error must be stated plainly and must gate progression, while a strong session shouldn't receive manufactured criticism.

[ 08 ]
Measured, not vibed

A response depends on which prompt modules were in context, and that's decided at runtime by tool routing — so a test result is meaningless unless you record the routing path that produced it. A rule placed in the always-loaded core prompt was still being leaked past in roughly a third of runs until the same rule was also placed in the module that loads closest to generation. That is not a thing you find by reading the output and nodding.

  • An A/B harness that runs the live pipeline against prompt versions from either the working tree or an earlier revision, with forced tool paths and repeat sampling, so a prompt change can be measured instead of asserted.
  • A weekly self-analysis pass over recent conversations — engagement, drop-off, tool calls that returned nothing, latency outliers, negative feedback — producing ranked prompt-improvement recommendations.
  • A closed feedback loop. In-product thumbs up/down with comments; negative feedback automatically opens a ticket with the full rendered conversation attached.
  • Cost visibility. Per-feature LLM spend is tracked daily rather than discovered on an invoice.

In a clinical setting, a confidently wrong answer is worse than no answer — and almost every design decision here comes back to that.

Stack
Python Anthropic Claude OpenAI GPT & Realtime API Azure Functions MongoDB Atlas Vector Search Azure Document Intelligence Azure Blob Storage Agora RTC Unreal Engine 5 Next.js

Building something that can't afford to be wrong?

Northnode takes a small number of engagements at a time. If you're putting an LLM somewhere the cost of a confident mistake is real — reach out on LinkedIn. No forms, no intermediaries.