Build Admix
KINDAreplaces $10/mosaves $120/yrback to the verdict
A multi-model chat client where the engineering is concurrent streaming from providers that behave differently, and the design constraint is that side-by-side comparison must not be presented as a form of verification.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the client and the concurrent streaming.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - accountper token; budget for development being more expensive than you expect
Why The comparison is uninteresting with fewer, and provider differences in streaming, errors and refusals only appear across several.
Get it Accounts with three providers, using the smallest models while developing since every test costs money three times over.
Verify
All three stream a response successfully - have readyfree to obtain
Why The cost display is only useful if it is right, and provider pricing changes without much notice.
Get it Take current published pricing per model, record it with the date you checked and the source.
Verify
A dated pricing record exists per model with a source link - decidefree to decide
Why This is a category where costs multiply by the number of providers and run away quietly, and the limit is enforced in code.
Get it Decide a monthly figure you would be comfortable spending before you have sent anything.
Verify
A figure is written down and configured before the first request - have readyfree
Why The build declines to judge correctness, but you need to see for yourself how often models agree confidently and wrongly, since that is the reason for the constraint.
Get it Write ten prompts with verifiable answers, including two where the answer is obscure enough that models are likely to guess.
Verify
At least one prompt produces confident agreement on a wrong answer
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- conversations(id, title, created_at, providers_json) - turns(id, conversation_id, ordinal, prompt, at) - responses(id, turn_id, provider, model, body, finished, error, latency_ms, tokens_in, tokens_out, cost_minor) - pricing(provider, model, input_per_million_minor, output_per_million_minor, effective_from, checked_on) - differences(turn_id, kind, detail_json) · kind is factual_disagreement, refusal_divergence or format_divergence - budgets(period, limit_minor, spent_minor) · because this is a category where costs run away quietly - There is deliberately no column scoring which response was best, because a stored score would be displayed and it would be invented.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DB_PATH | required | ~/.local/share/admix/chats.db | Conversations and cost records. Contains whatever you asked about. |
KEYS_ENCRYPTION_KEYsecret | required | a keychain reference | Encrypts provider keys at rest. Without it they sit in plain text. |
PRICING_PATH | required | ./config/pricing.json | Dated pricing per model. The file that keeps the cost display honest. |
PRICING_RECHECK_DAYS | required | 30 | After this a pricing entry is surfaced as stale. |
MONTHLY_BUDGET_MINOR | required | 2000 | Checked before issuing. Refuses rather than warns. |
SHOW_RANKING | required | never | Present as a constant so the answer is visible. A ranking would be invented. |
The build, in order
Several providers, one interface
One prompt streams from three providers concurrently.
Thin. Each provider's streaming format and error shape differ.
Files
src/providers/The slowest provider should not set the pace for the others.
Files
src/stream.tsThey mean different things and conflating them hides useful information.
Files
src/normalise.ts
done when · tick each as it passeswatch out- Waiting for all providers before showing anything makes the whole interface as slow as the slowest one, which is often several times the fastest. Stream each independently and let them finish at their own pace, showing which are still running.
Knowing what it cost
Every request shows its actual cost against current pricing.
Both directions. Output is usually the expensive half.
Files
src/tokens.tsHard-coded prices go quietly wrong and are trusted anyway.
Files
config/pricing.jsonA cost display that is wrong is worse than none.
Files
src/pricing.ts
done when · tick each as it passeswatch out- Hard-coding prices means the figures silently become wrong when a provider changes them, and a tool whose cost display is wrong is worse than one with no cost display, because it is trusted. Keep pricing as dated data with a checked date and flag entries that have gone stale.
Showing difference without implying a winner
You can see where models disagree and are not told who is right.
Wording differs always. Substance differing is the interesting case.
Files
src/diff.tsThey agree. That is the whole claim.
Files
src/compare.tsIts absence is the design. A score would be invented.
Files
tests/no-ranking.test.ts
done when · tick each as it passeswatch out- Any score, badge or ordering that suggests one answer is better will be read as a verdict, and there is no basis for one. Show the differences and let the reader judge, and if you cannot resist the urge to rank, remember the ranking would be produced by another model with the same limitations.
Spending that cannot run away
A budget stops requests before the bill surprises you.
Refusing is the point. A warning after the fact is a receipt.
Files
src/budget.tsFour providers is four times. Obvious and still surprising.
Files
src/estimate.tsAnd recorded, so the pattern is visible to you.
Files
src/budget.ts
done when · tick each as it passeswatch out- Sending one prompt to four providers costs four times as much, which is obvious and still surprises people at the end of the month, particularly with long context. Show the multiplied cost before sending, not after.
Keys, storage and getting outproduct builder
Your keys are safe, your conversations are yours, and both can leave.
A key in a log is money someone else can spend.
Files
src/keys.tstests/no-key-in-logs.test.tsThey contain whatever you asked about, which is a lot.
Files
src/search.tsWhich model said what, and what it cost.
Files
src/export.ts
done when · tick each as it passeswatch out- A provider key in a log or an error report is a credential someone can spend your money with, and it gets there through a well-meaning debug line printing a request. Redact at the logging boundary and assert in the suite that no key material can reach a log.
That is the whole plan for Admix. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Any judgement about which answer is correct.
- Model evaluation or benchmarking.
- Automatic routing to the best model.
- Self-hosting models.
- Admix's bundled access to many providers under one subscription
- A maintained model catalog and provider routing
- Cross-device sync and account history
- Built-in image, search, and document workflows
- Usage limits, billing, and support handled for you
- Saved prompt sets you can rerun across models when a new one appears
- Per-provider latency history, which is genuinely useful and honestly measurable
- A cheap model first, expensive model on demand flow, once cost accounting is trustworthy
Need the files? The project pack on the verdict page hands your agent the whole brief · more ai assistants.