Dirac architecture
Dirac is not a custom model. It is the Claude API — called directly from the app with the user’s own Anthropic key — wrapped in a tutor system prompt, a context-injection pipeline, and tool definitions. There is no Nuclei server in the loop.
Key handling
Section titled “Key handling”- Entered in Settings → Dirac AI (validated against the
sk-ant-prefix), stored in WebView localStorage undernuclei-dirac-api-key— details and the keychain caveat are on the configuration page. - A build-time
VITE_CLAUDE_API_KEYenv var is an optional fallback for development. Because Vite inlines it statically, a key set at build time is baked into the shipped JS bundle — never set it for distribution builds. - The key is sent to exactly one place:
api.anthropic.com, as thex-api-keyheader on each call. (“Never leaves your machine” would be imprecise — it leaves with every request, to Anthropic only.)
Direct browser calls — no middleman
Section titled “Direct browser calls — no middleman”All five Anthropic call sites POST to https://api.anthropic.com/v1/messages
with anthropic-version: 2023-06-01 and
anthropic-dangerous-direct-browser-access: 'true'. No proxy, no Nuclei
server, no logging of prompts in transit.
Model routing
Section titled “Model routing”Model IDs live in src/config/dirac.ts — plus two duplicated local
constants (claudeClient.ts:3-4, ghostCompletions.ts:14-15), so a model
bump touches three files. OPUS_MODEL is exported but never used; routing
is Haiku/Sonnet only.
| Surface | Model | max_tokens | Source |
|---|---|---|---|
| Chat — short, simple Q&A | claude-haiku-4-5-20251001 | 4096 | diracRouting.ts:52-84 |
| Chat — >100 chars, explain-keywords, or action-keywords (tools) | claude-sonnet-4-6 | 4096 | diracRouting.ts:38-84 |
Chat — /think or reasoning keywords | claude-sonnet-4-6 + thinking: {budget_tokens: 10000} | 16000 | diracRouting.ts:23-35,87-126 |
Compose (/compose or intent-classified) | claude-sonnet-4-6 | 2048 | compose.ts:94-95 |
| Cmd+K inline edit | claude-sonnet-4-6 | 2048 | InlineEditWidget.tsx:92-94 |
| Ghost completions | claude-haiku-4-5-20251001 (local constant) | 150 | ghostCompletions.ts:15,109 |
| Narration (ambient one-liners) | Haiku (claudeClient default) | 120 | narration.ts:44,66 |
| Error rewrite | Haiku (claudeClient default) | 500 | errorRewrite.ts:42 |
The chat rows describe the default settings. Chat routing is a keyword/length heuristic, not a strict rule: common verbs like “make”, “check”, “try” escalate to Sonnet with tools. Two Settings → Dirac AI knobs adjust the chat rows only — the other surfaces in the table are fixed by design (ghost completions, narration, and error rewrite stay on Haiku for latency; compose and Cmd+K stay on Sonnet for generation quality):
- Preferred Model (
auto, default):haiku/sonnetforce that model for chat Q&A. Tool-use and/thinkturns still run Sonnet even with a Haiku preference — capability wins. - Extended Thinking (default on): when off, reasoning keywords no
longer auto-escalate to the thinking variant; an explicit
/thinkalways does.
Context injection — what each surface sends
Section titled “Context injection — what each surface sends”This table doubles as the privacy statement: per surface, exactly this data accompanies the request to Anthropic.
| Surface | Data sent | Source |
|---|---|---|
| Chat | Full editor code, circuit summary (framework, qubits, classical bits, depth, gate types), top-8 probabilities + per-qubit Bloch coords + exec time, active exercise metadata, last 3 stderr lines, selected hardware backend + last completed hardware-job probabilities, active challenge metadata — wrapped in <ide_context> around the last user message; full conversation history including prior tool calls | useDirac.ts:216-280,484-510 |
| Chat system prompt | Tutor persona + student model (skill level, mastered/struggling concepts, common errors — only after first code execution), Learn Mode track/lesson progress, active capstone milestone, selected backend specs | useDirac.ts:53-95 |
| Compose | Framework, the request, full editor code (language-aware fence) | compose.ts:71-81 |
| Cmd+K | Framework, snapshot qubits/depth, full file, selected code (or current line), the instruction | InlineEditWidget.tsx:75-81 |
| Ghost completions | Framework, circuit summary, last 2 stderr lines, the entire buffer split around a [CURSOR] marker | ghostCompletions.ts:69-86 |
| Narration | Framework, qubit/depth/gate-type summary, top-3 probabilities (result), code sliced to 1500 chars | narration.ts:34-66 |
| Error rewrite | Framework, code sliced to 2000 chars, traceback sliced to 2500 chars | errorRewrite.ts:32-40 |
The Chat row describes Context Depth: Standard (the default). The third Settings knob, Context Depth, trims what chat sends — code and the circuit summary are always included:
- Minimal: code + circuit summary + recent errors only (no simulation results, Bloch coords, exercise, hardware, or challenge context).
- Standard (default): everything in the Chat row above.
- Full: same sections with deeper detail — top-16 probabilities and the last 10 stderr lines.
Per-feature toggles: narration and autoExplainErrors default on,
ghostCompletions defaults off (beginner default). With no API key,
every surface silently no-ops.
The insert_code forced-tool pattern
Section titled “The insert_code forced-tool pattern”Compose never asks Claude to “reply with code” — it forces a tool call:
tool_choice: { type: 'tool', name: 'insert_code' }, and the tool’s
contract is a complete, runnable file (the full next editor buffer,
never a diff). The prompt embeds the current code in a language-aware
fence (python or qsharp), and the one-sentence explanation rides along
as a separate text block. Cmd+K uses the same complete-file contract
without the tool wrapper.
When the framework is Q#, every generating surface (compose, Cmd+K)
appends QSHARP_STYLE_GUIDE to its system prompt so output is modern
QDK 1.x rather than legacy Microsoft.Quantum dialect; ghost completions
carry a manually-synced distilled subset to fit Haiku’s budget.
What leaves your machine
Section titled “What leaves your machine”Verified against the codebase (not just asserted):
- Dirac calls — to
api.anthropic.comonly, with your key and the context listed above. The only AI endpoint in the app. - Hardware submissions — circuit source/QIR and credentials go to the provider you connect (IBM/IonQ/Azure/…); credentials persist in the OS keyring, not in browser storage. See hardware overview.
- Auto-updater — the desktop shell polls
github.com/calelamb/nuclei/releases/latest/download/latest.json. - App telemetry: none is sent. The Settings toggle “Anonymous
Telemetry” (default off) writes a flag that no code reads; the
TelemetryServiceinsrc/services/telemetry.tshas zero importers and itsflush()clears the queue without any network call. The Rust shell makes no HTTP requests beyond the updater. (Vercel analytics run on the marketing website, not in the app.)
Chat passes nine tool definitions when action keywords are detected:
insert_code, run_simulation, highlight_gate, step_to,
create_exercise, verify_solution, submit_hardware, challenge_hint,
glossary_lookup. Read-only/teaching tools (highlight_gate, step_to,
create_exercise, verify_solution) auto-execute; everything that touches
the editor, kernel, or hardware stays pending until the user accepts.
Tools are omitted in reasoning mode.
Adding tools and prompt fragments is covered in Extending Dirac.