Engineering notes

How ALICE is actually built.

The product page covers what she does for a pilot. This one covers how — the graph, the server, the guardrails against acting on bad information, the stack, and what is not built yet.

18 tools, one agent Prompts held server-side Every tool arg Pydantic-validated
Architecture

Not a wrapper around one API call.

A topic gate sends each utterance to respond ⇄ tools, a short social reply, or an in-character decline — see app/graph.py.

User — push-to-talk voiceInput
↓ speech → text · faster-whisper, on the pilot's machine
classify_topicRequest, social or off-topic · small decision model, chat-model fallback
↓ request  ·  greeting  ·  off-topic
respond ⇄ toolsLoop
declineIn character
18 tools · UEX Corp market data · Star Citizen Wiki (reverse-engineered) · trade-run ledger · timers
↕ HTTPS + JWT · ledger server: Postgres, UEX/wiki cache, LLM & TTS gateways
↓ text → speech · Kokoro, via the TTS gateway
Reply — spokenOutput

Thread vs. Run

A thread (thread_id, MemorySaver) is the conversation a pilot is having with ALICE, held in memory for the session. A run is one graph.ainvoke() cycle inside it — one LangSmith trace when tracing is on. Trade-run state lives in the ledger, not in the thread.

18 tools, and optional routing

Tool-selection accuracy is reported to degrade past 10–15 tools in rotation; RAG-MCP's retrieval approach took it from 13.6% to 43.1% in that paper. ALICE runs 18, chosen by the chat model. Behind a flag, a small decision model picks which tool groups to bind for each request. In a measurement over 79 phrasings it cut tool tokens by about 55% and missed a needed group once.

One server between the pilot and everything else

The desktop client holds no API keys and no database connection. A small FastAPI server is the only thing that talks to Postgres, the model box, the TTS engine, or the prompt store — every request authenticated by a JWT from a one-time Discord login. It also holds the prompts: pilots never receive the persona text, and a Hub tag is the release gate.

"ALICE surfaces computed facts. The pilot makes the judgment calls."

Design principle, applied twice — the same rule that keeps Finalize Leg / Finalize Run manual-only.
Guardrails

How it avoids acting on bad information.

What each safeguard does, as built in the current release.

Topic gate Built

Every utterance is classified as a request, social talk, or off-topic before the main model runs. Off-topic input gets an in-character decline; greetings get a short separate reply.

Actions only through tools Built

Only a tool call changes anything. The persona forbids claiming an action no tool performed, and a failing tool tells the model the action did not happen.

Typed arguments Built

Every tool argument is a validated Pydantic field. Spoken prices, quantities and names are converted before they reach a tool.

Manual finalize Built

Finalizing a leg or a run is never done by voice or by the model. The pilot confirms it in the overlay.

Hedged name matching Built

Ship, terminal and commodity names are resolved through aliases and known mishearings. A weak match produces a question back to the pilot instead of a guess.

Spend and rate limits Built

The server counts upstream market-data calls per day and serves cached data when the budget is spent. Hosted-model fallback is capped by a daily spend limit and a per-pilot request limit.

Tech stack

Every choice, with a reason.

LayerChoiceWhy
LLM orchestrationLangChainStructured output, provider-agnostic model binding
Conversational graphLangGraphExplicit guardrail/tool-calling loop as a graph, not prompt logic
ObservabilityLangSmithFull pipeline tracing; also hosts the persona & classifier prompts
SchemasPydanticStructured LLM output and tool args
Trade-run storagePostgres + SQLAlchemy + AlembicDurable, queryable ledger — not a semantic-retrieval problem
Desktop overlayPySide6LGPL licensing vs. PyQt6; real layout power vs. tkinter
Speech-to-textfaster-whisperRuns locally on the pilot's machine; model size set by one env var
Text-to-speechKokoro (self-hosted), ElevenLabs fallback~0.1 s per reply on a home GPU and no per-character bill; the hosted voice only steps in when the box is down
Model servingOllama (gemma4) + Claude Haiku 5.5 fallbackChosen on eval results; about 2–3 s per turn through the graph on the home box (October 2026 measurements); hosted fallback capped by daily spend
Topic decisionTev1, with chat-model fallbackA small local model decides request, social or off-topic in about 30–40 ms; the chat model only decides if it is unavailable
ServerFastAPI + Discord OAuth/JWTSole Postgres client and gateway; no secrets ever ship to a pilot's machine
EvalsCustom harness + Claude Haiku 5.5 judgeReal graph, deterministic fake world — results reproducible regardless of today's market
PackagingPyInstaller + Inno SetupA one-folder Windows build: a one-file executable unpacks itself on every launch and is the shape antivirus tools flag most
DeployDocker Compose on HetznerOne box, one command; the pilot's client is never containerized
What's next

What is not built yet.

Nothing here is claimed as built unless it ships in the current release.

Evals Built

Every phrasing of every case runs through the real graph against a deterministic fake world with real Stanton geography. Scored per dimension — topic gate, tool, arguments, location, price, unit, unsupported claims — with Claude Haiku 5.5 judging free text. Used to choose the serving model across ten-odd local candidates. A separate voice evaluation scores character, naturalness and restraint on a fixed judge model.

Tool routing Experimental

A small decision model picks which tool groups ALICE binds for each request. Off by default. Measured on 79 phrasings: about 55% fewer tool tokens, one missed group. Turning it on is the next step.

Security hardening Next

Prompt-injection and jailbreak defense, beyond topic filtering. Every tool argument is already Pydantic-validated — a baseline, not a solution.

Memory Partly evaluated

Within a session ALICE sees the last six pilot turns, with older tool results replaced by a placeholder. A bounded session recall was evaluated — recall of older facts went from 0 of 6 to 5 of 6 in a 36-turn test — and is off by default. Durable memory across sessions is not built.

On-screen feedback Planned

A presence layer — mini tracker, timer and route cards — that shows what ALICE logged and lets options be compared by eye instead of by ear. Designed, not built.