microresolve 0.2.1

System 1 relay for LLM apps — sub-millisecond intent classification, safety gating, tool selection. CPU-only, continuous learning from corrections.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
# Changelog

All notable changes to MicroResolve are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.2.1] — Unreleased

### Added
- `microresolve-studio install <pack>` — fetches a reference pack tarball from
  the GitHub release matching the binary version and extracts it into the
  configured data dir.
- `microresolve-studio list-packs` — shows the 4 reference packs with their
  install status against the data dir.
- `--config <PATH>` flag + `MICRORESOLVE_CONFIG` env var on the studio binary,
  for sandbox testing and multi-instance deployments. The `config`
  subcommand respects the override.

### Fixed
- PyPI project page now renders the README as long-description (was empty
  in v0.2.0 because `pyproject.toml` lacked the `readme` field).
- Aligned package short-descriptions across crates.io, PyPI, and npm to a
  single phrasing matching the v0.2.0 README hero ("System 1 relay for
  LLM apps...").

### Build
- Release workflow now builds and uploads pack tarballs as release assets
  (`pack-<name>-v<VERSION>.tar.gz`) so `microresolve-studio install <pack>`
  has something to fetch.

### Removed
- **`IntentType` (Action/Context) classification removed entirely.** Field
  was never consumed by scoring/routing — pure dead metadata. Old intent
  JSON files with `"type": "action"` still load fine (serde ignores
  unknown fields). Public API affected: removed `IntentEdit.intent_type`,
  `IntentInfo.intent_type`, the `intent_type` parameter on
  `add_intent`/`update_intent`, and the auto-classification at import time
  (HTTP-verb-based for OpenAPI, `readOnlyHint`-based for MCP) is gone.
- **Per-intent `Stats` tab on the Intents page** — replaced with a
  single-line metric strip above the phrases list (`12 examples · 3 langs
  · 8 learned`). Saves a click and removes a tab that mostly duplicated
  the phrases-tab count.

### UI
- **Richer manual seed generation — anchor examples.** The "Generate
  examples with AI" panel on the per-intent Examples tab gains a
  textarea for 1–3 anchor phrases the user already has in mind. The
  LLM uses them as seeds and generates variations around them. Empty =
  original behavior. Closes part of the quality gap between manual
  intent creation and import (which has full operation context).
  Wired through `phrase::build_prompt(... examples)` and
  `POST /api/phrase/generate` accepts an optional `examples: string[]`.
- **Import seed count bumped 5 → 10 phrases per intent.** Matches
  manual generate. Token-budget headroom stays well under 8192;
  marginal cost increase ~$0.01 per 50-tool import on nano-class
  models. The previous 5/intent floor was leaving recall on the table
  for newly-imported namespaces.
- **`/l2` route renamed to `/intents`.** `/l2` now redirects via
  `<Navigate>` for backward compat with bookmarks.
- **"Phrases" relabeled to "Examples"** in the per-intent tabbed panel.
  Internal-facing names (`phrases`, `phrases_by_lang` API fields) stay
  as-is — display change only.
- **Resolve page**: removed the duplicate-query block that was a
  passthrough render of the `HighlightedQuery` component (the highlighter
  itself was gutted when L1 was removed in v0.2.0; the duplicate query
  block had no value).

---

## [0.2.0] — Unreleased

### Added — Reference packs

Four pre-curated namespace packs ship with the library, drop-in installable
into any `data_dir`:

- **`safety-filter`** — pre-LLM jailbreak / prompt-injection detection. 5
  intents covering the canonical attack taxonomy (prompt injection,
  system-prompt extraction, role override, safety bypass, encoding-based
  evasion). 100 hand-curated seed phrases. On internal eval (50 attacks,
  50 random benigns from CLINC150): **98% recall / 8% FP at default
  settings (`min_voting_tokens=3, threshold=1.5`)**.
- **`eu-ai-act-prohibited`** — Article 5 prohibited-practice triage
  filter. 6 intents (biometric categorization, emotion recognition in
  workplace/education, exploitation of vulnerability, predictive policing
  on natural persons, social scoring, subliminal manipulation). On
  internal eval (60 prohibited, 50 benign): **85% top-1 / 6% FP at
  default settings (`min_voting_tokens=2, threshold=1.5`)**.
- **`hipaa-triage`** — medical query triage routing patient inquiries to
  one of clinical_urgent / clinical_routine / mental_health_crisis /
  administrative / billing / scheduling. 743 hand-curated seeds drawn
  from public clinical references (AHRQ ED chief complaints, ESI triage,
  HIPAA Right of Access, No Surprises Act, SAMHSA crisis materials).
  Best used as **triage / candidate filter** — pair with LLM judgment
  for top-1 selection. On internal eval (97 medical, 85 benign):
  **96.9% top-1 / 36.5% FP at default (`min=3, threshold=1.5`)**, or
  **94.8% top-1 / 21.2% FP at `threshold=2.0`** for stricter precision.
  NOT a HIPAA compliance solution; see pack description.
- **`mcp-tools-generic`** — generic tool-router for MCP-style agents
  (web_search, send_message, fetch_url, file_operations, database_query,
  code_execution, calendar_management). Default settings
  (`min_voting_tokens=2, threshold=1.5`). Best for closed-domain
  dispatch; open-ended chat traffic produces FPs from idiomatic English.

All packs ship as bag-of-words inverted index. Pack format is a directory
of JSON files (one per intent + `_ns.json`); install by copying into
`data_dir/<pack-name>/`.

### Added — Bands and Disposition

Every `resolve()` return now includes a `band` per intent (`High` /
`Medium` / `Low`) and an overall `disposition` (`Confident` /
`LowConfidence` / `NoMatch`). The pre-LLM filter pattern is the
recommended consumer:

```rust
match result.disposition {
    Disposition::Confident    => act(result.intents[0]),  // deterministic
    Disposition::LowConfidence => llm_disambiguate(result.intents),
    Disposition::NoMatch      => llm_fallback(query),
}
```

Same shape exposed in Python and Node bindings as string values.

See [Bands & Disposition](docs-site/src/content/docs/concepts-bands.mdx)
for thresholds and the canonical decision pattern.

### Added — Connected mode (live sync)

- **`MicroResolveConfig.server`** — when `Some(ServerConfig)`, the engine
  pulls subscribed namespaces from a running server on startup and keeps
  them in sync via a background poll thread. `resolve()` calls in
  connected mode buffer log entries that are flushed each tick;
  `correct()` proxies the correction to the server, which is the sole
  writer.
- **Strict connect mode**: every mutation method on `NamespaceHandle`
  (`add_intent`, `add_phrase`, `correct`, `decay_for_intents`, etc.)
  returns `Error::ConnectMode` when the engine is connected. The library
  is a read-only cache; the server is authoritative. Background sync
  thread bypasses this guard via the internal `apply_ops` path.
- **Delta sync**: connected libraries pull additive op sequences
  (`PhraseAdded`, `WeightUpdates`, etc.) from `/api/sync` rather than
  full-state snapshots. CRDT-style merge: token weights take the max
  across replicas. Cold-start uses `/api/snapshot`; subsequent ticks
  use `/api/sync` with the local version as the watermark.

### Added — Pack format helpers

- **Per-namespace `default_threshold` cascade**: `_ns.json` may set
  `default_threshold`; `update_namespace()` PATCHes it; `/api/resolve`
  cascades request override → namespace default → engine compile-time
  default (0.3). Lets pack authors ship calibrated thresholds with each
  pack.
- **`POST /api/namespaces/{id}/concepts`** has been considered and
  deferred — no concept-tagging system ships in v0.2.0. Concept research
  is preserved on the `experiment/concept-index` branch as research
  record; revisit in v0.3 with production-traffic validation.

### Added — Voting-token gate (false-positive reduction)

A per-namespace structural filter that suppresses single-word false
positives like `"let me speak from the heart"` matching a clinical
intent because of `"heart"` alone. For each candidate intent, count how
many distinct query tokens contribute weight > 0.05 to its score; if
fewer than `min_voting_tokens` vouch, scale the intent's score by a
sliding penalty (0.4 at 1 voter → 1.0 at the threshold). Default is
**1 (off)** — the gate is a no-op for existing namespaces. The 4
reference packs ship with calibrated values (see per-pack notes).

Cascade plumbing mirrors `default_threshold`: per-request override →
namespace `default_min_voting_tokens` → engine default (1). Persisted
in `_ns.json`. Exposed end-to-end through Rust API
(`NamespaceHandle::set_min_voting_tokens`), HTTP (`PATCH /api/namespaces`),
Python and Node bindings, and the Studio UI's per-namespace
TuningPanel (sidebar pill + Resolve right-rail).

On closed-set classification (CLINC150 / BANKING77) at default `min=1`
the gate is exactly a no-op — measured top-1 within ±0.07pp of 0.1.x
baselines. On open-world packs at calibrated defaults, FP drops
substantially at unchanged or barely-changed recall (safety: 36%→8% FP
at 98% recall; EU AI Act: 24%→6% FP at 85% top-1; HIPAA: see pack
notes). Eval is on small author-curated corpora — see Known
limitations.

### Removed

- **L0 (typo correction) and L1 (lexical-graph query rewriting) layers**
  removed entirely. Both produced silent corruption on real workloads
  (e.g. "please" → "leave"). The remaining engine is the IDF-weighted
  inverted index + multi-intent decomposition + Hebbian learning ("L2").
- **`Resolver::resolve` (legacy single-best `Vec<Match>` form)**,
  `Resolver::resolve_with`, `Match` struct, `ResolveOptions` struct.
  Replaced by `NamespaceHandle::resolve()` returning `ResolveResult`.

### Breaking — Public API

- **Routing collapsed to two entry points.** Removed: `route_multi`,
  `route_multi_with_trace`, legacy single-best `resolve`, `resolve_with`,
  `RouteMultiOut`, `Match` tuple form, `ResolveOptions`. New surface:
  - `NamespaceHandle::resolve(query) -> ResolveResult`
  - `NamespaceHandle::resolve_with_trace(query) -> (ResolveResult, ResolveTrace)`
- **HTTP: `POST /api/route_multi` renamed to `POST /api/resolve`**.
  Response body now: `{ intents: [{id, score, confidence, band}], disposition, routing_us, trace? }`.
  No more `confirmed[]` / `candidates[]` split — consumers filter `intents[]` by `band`.
- **Rust API: `Resolver::train_negative``decay_for_intents`** at
  both Resolver and NamespaceHandle level. Name reflects the actual
  anti-Hebbian weight-decay behavior.
- **HTTP: `POST /api/namespaces/train_negative``POST /api/namespaces/decay`**
  with response field `"trained"``"decayed"`.
- **Rust API: `IntentIndex::score_multi_traced(query, threshold, gap, with_trace)`
  split** into `score_multi` (no trace) and `score_multi_with_trace`
  (always returns `MultiIntentTrace`, not `Option`).
- **Rust API: `MicroResolve::effective_threshold/effective_languages/effective_llm_model`**
  renamed to `resolve_threshold_for/languages_for/llm_model_for`.
- **Import response field `l2_unique_words` renamed to `vocab_size`**  last `l2_*` key in the external API surface.
- **L2 internal naming cleanup**: `_l2.json``_index.json`,
  `score_normalized` field renamed, source string `"router"``"learning"`.
  No backward-compat shim; existing data dirs from 0.1.x need a one-line
  rename (`_l2.json → _index.json`) or just regenerate via `rebuild`.

### Bindings

- **Python (PyO3)** + **Node (napi-rs)** parity sweep: 17 missing
  methods added to both bindings. Both now expose the full
  `NamespaceHandle` surface — `resolve`, `resolve_with_trace`,
  `add_intent`, `add_phrase`, `remove_phrase`, `remove_intent`,
  `correct`, `intent`, `update_intent`, `intent_ids`, `intent_count`,
  `version`, `namespace_info`, `update_namespace`, `vocab_size`,
  `confidence_for`, `training`, `training_by_lang`, `export_json`,
  `check_phrase`, `domain_description`, `set_domain_description`,
  `remove_domain_description`, `reinforce_tokens`, `rebuild_index`,
  `rebuild_caches`, `index_phrase`, `decay_for_intents`,
  `apply_review`, `flush`.

### Studio UI

- **Review queue** now shows all entries with status (auto-applied,
  manually corrected, pending, escalated) — not just the pending subset.
- **Import domain UX overhaul** across all 5 importers (OpenAPI,
  MCP, Postman, OpenAI Functions, LangChain): operator-decided domain
  picker on every flow.
- **Studio bootstrap auth**: every `/api/*` route requires
  `X-Api-Key`. Server auto-mints an admin key on first boot, persists
  to `~/.config/microresolve/admin-key.txt`. Studio paste-screen
  on first load. `KeyScope` schema (`admin` / `library`) on every key.

### Benchmarks

- **CLINC150**: seed-only **80.1%** top-1 (4500 test) — matches 0.1.x
  published baseline within noise. After-learning (reinforce missed
  queries with their true label, no LLM): **97.4%** top-1, an
  unintended improvement of ~6pp over the 0.1.x published number,
  attributable to engine work landed earlier in the 0.2.0 cycle
  (L3-inhibition removal, IDF caching at index time).
- **BANKING77**: seed-only **73.15%** (matches 0.1.x exactly).
  After-learning: **94.6%** top-1 (vs 0.1.x published 86.6%).
- Voting-gate at default `min=1` is a no-op on closed-set benches —
  measured CLINC drift: -0.07pp (within noise).
- p50 latency: ~85µs / p95: ~190µs.

### Server / Ops

- **Default port: 4000** (was 3001 in 0.1.x).
- **`microresolve-studio --no-browser`** flag (was `--no-open`).
- **`--keys-file <path>`** for keystore isolation.
- **CRDT-mergeable Hebbian weights** for delta sync between
  cooperating replicas (max-merge per `(token, intent)` edge).

### Known limitations

MicroResolve is the **System 1 relay** — deterministic, sub-millisecond,
running on every request. It always hands its output to your
**System 2**: typically an LLM, optionally a human reviewer for
high-stakes domains. MicroResolve never talks to the user. The
numbers below should be read in that architecture: high recall +
acceptable FP on the legitimate side is the design target, because
your System 2 filters out the remaining FPs — the LLM either acts on
the suggestion (the cheap path) or asks the user to confirm with the
candidate list we provide (when we flag uncertainty).

We're using Kahneman's framework as an architecture metaphor (fast
intuitive layer feeding a slow deliberative one), not making
cognitive-science claims about LLMs being literal System 2s.

- **Eval methodology**: pack eval corpora are **small (n=50–97 per
  side) and author-curated** — same heads wrote seeds and tests, so
  vocabulary leakage is unavoidable. Published recall/FP rates have
  wide confidence intervals (~±2pp at n=50). Production deployments
  should re-measure on captured customer traffic before relying on
  absolute numbers.
- **HIPAA pack**: 36.5% FP on synthetic benigns at default reflects
  the natural overlap between medical and everyday vocabulary. As a
  System 1 relay feeding an LLM and/or human reviewer, this is workable
  — each FP becomes a one-line "did you mean a clinical question?"
  confirmation in the System 2 reply, not a misroute. **Do not run
  unsupervised.** Mental-health-crisis intent has not been validated
  in any production setting and must not be used for crisis routing
  without explicit human review.
- **OOS detection**: on closed-set classification, ~96% of
  out-of-scope queries still classify in-scope at default threshold.
  The filter is calibrated for *in-scope* classification, not OOS
  detection — pair with an explicit OOS classifier or raise the
  threshold for open-world deployments.
- **mcp-tools-generic** at default settings: open-ended chat traffic
  produces FPs from idiomatic English. Best for closed-domain dispatch
  — raise threshold for chat applications.
- **safety-filter** is a pre-filter, not a complete defense.
  Adversarial novel paraphrasing bypasses any deterministic
  vocabulary-based classifier. Pair with a dedicated safety classifier
  (LlamaGuard / Prompt-Guard / OpenAI Moderation) for production
  adversarial coverage.

---

## [0.1.9] — 2026-05-01

### Breaking

- **Studio auth model: every `/api/*` route now requires `X-Api-Key`**  including the UI's own fetches. The previous setup left UI fetches
  unauthenticated and only `/api/sync` enforced auth. Combined with a
  default bind to `0.0.0.0`, that meant any LAN host could mint keys
  via the unauthenticated `/api/auth/keys` POST. Closed.
- **`POST /api/auth/keys` now requires auth** like everything else.
  No more bootstrap exception.
- **Renamed CLI flag `--no-open``--no-browser`**. More
  self-documenting; matches Jupyter's convention.

### Added

- **Auto-bootstrap admin key on first boot.** When `keys.json` is empty,
  the server mints `mr_studio-admin_<hex>` (Admin scope), prints it to
  stdout in a visible banner, and persists it to
  `~/.config/microresolve/admin-key.txt` (mode 0600) so operators who
  redirect stdout (Docker, systemd) can `cat` it later.
- **Studio paste-screen** — first browser visit renders a key entry
  form. Pasted key lives in `localStorage`; every subsequent fetch
  carries `X-Api-Key`. Single global `window.fetch` wrapper (in
  `ui/src/api/client.ts`) attaches it to every relative `/api/*`
  request, including the dozen-plus raw `fetch()` call sites scattered
  across the import wizards and Layout polls.
- **`KeyScope` schema on every API key**`admin` or `library`.
  Persisted in `keys.json`, returned by `GET /api/auth/keys`, accepted
  by `POST /api/auth/keys`, surfaced in the Auth Keys page (column
  badge + scope dropdown on Generate). **Enforcement is permissive in
  v0.1.9** (every scope grants every route); v0.2 will land
  route-level scope checks. Existing keys without a scope field load
  as `Admin` (backwards-compat default).
- **`--keys-file <path>` CLI flag** for keystore isolation. Used by
  the integration test harness to give every spawned server its own
  `keys.json`, so parallel test binaries don't race on the global
  `~/.config/microresolve/keys.json`.

### Removed

- **Home page "Common shapes" use-case tile section.** Replaced with a
  one-line stats strip (namespaces · intents · connected clients ·
  pending review) and a single "see use cases" link in the empty
  state — exactly where new operators look for examples, with no
  clutter on the persistent home view for repeat visitors.
- **Home page "Try It" widget** (already removed in earlier branch
  work, formalized in v0.1.9). The dedicated `/resolve` page provides
  the same with full L0/L1/L2 trace.
- **"Start with" dropdown in the New Namespace modal.** Three options
  that just navigated to different pages — friction at the moment of
  creation. Now: create + close, namespace appears in the list, user
  picks where to go from there.

### Changed

- **Hero on Studio Home** rewritten from "Pre-LLM intent routing" →
  "The pre-LLM reflex layer" to match the README. Intent routing is
  one use case; reflex layer covers the broader pitch (tool selection,
  guardrail dispatch, refusal classification).
- **Test harness (`tests/common/mod.rs`)** captures the bootstrap
  admin key from each spawned server's `admin-key.txt` and auto-injects
  it via a thread-local on every helper call. Tests stay terse;
  unauthorized-path tests bypass the auto-inject by setting
  `X-Api-Key` explicitly.

## [0.1.8] — 2026-05-01

### Fixed

- **Python wheels**: `python/pyproject.toml` was out of sync with
  `python/Cargo.toml` and `Cargo.toml`, causing maturin to build
  v0.1.7 release artifacts as `microresolve-0.1.6-*.whl`. PyPI's
  `--skip-existing` silently skipped them. v0.1.8 syncs the
  pyproject version and re-publishes the Python wheel. Crates.io
  and npm v0.1.7 are unaffected; tarball release for v0.1.7 is also
  intact, but skipped in favor of v0.1.8 for cross-registry parity.

## [0.1.7] — 2026-05-01

### Added

- **Per-namespace reflex-layer toggles.** Each namespace now carries
  four boolean fields — `l0_enabled`, `l1_morphology`, `l1_synonym`,
  `l1_abbreviation` — accepted by `Resolver::update_namespace` and
  exposed on `NamespaceInfo`. All default to `true`; namespaces saved
  before this change load with all layers on. Flags are honored at
  both index time and resolve time so trained vectors and runtime
  preprocessing stay in sync. Use cases: turn L0 off for medical /
  legal namespaces (auto-correcting domain terms is dangerous); turn
  L1 abbreviations off for code search (short tokens carry literal
  meaning).
- **`PATCH /api/namespaces` body** accepts the four new fields;
  `GET /api/namespaces` returns them per namespace.
- **Studio: layer toggles in three places.** Namespaces edit modal has
  all four switches (atomic save, setup flow); L0 page has an inline
  toggle; L1 page has a top-level `on / off / partial` status badge
  plus a per-column compact toggle for each edge kind. Sidebar L0 / L1
  nav items show an `off` or `partial` pill when any layer is disabled
  for the active namespace.
- **`LayerToggle` shared component** + `AppContext.layerStatus`  single optimistic-update store for the active namespace's toggles,
  so changing them on any page updates the sidebar instantly without
  an extra GET. Reverts on PATCH failure.
- **`LexicalGraph::preprocess_with_kinds` /
  `preprocess_grounded_with_kinds`** — public variants of the
  preprocess methods that take three booleans gating Morphological,
  Abbreviation, and Synonym edge kinds. The original `preprocess` /
  `preprocess_grounded` are unchanged thin wrappers — no break to the
  published API.

## [0.1.6] — 2026-05-01

### Added

- **Empty `subscribe` list now auto-subscribes to all namespaces** the
  server exposes. Pass `subscribe=[]` (Python: omit / `None`; Node:
  omit / `[]`) and the library queries `GET /api/namespaces` at connect
  time, then pulls each one. Explicit lists still work as an allow-list
  for multi-tenant cases. Zero-config for solo / single-team setups.
- **Studio shows connected library clients.** A new top-level "LIVE →
  Connected" sidebar item with a live count badge, plus a dedicated
  `/connected` page listing every authenticated client currently
  syncing (name, library version, subscribed namespaces, tick interval,
  last sync, expires-in countdown). Auto-refreshes every 3s.
- **`GET /api/connected_clients`** — read-only roster endpoint backing
  the UI. Lazy GC on read: any entry older than `2 × tick_interval_secs`
  is dropped before responding.
- **`POST /api/sync` body fields** (optional, advisory):
  `tick_interval_secs` lets the server use each client's own freshness
  window; `library_version` (e.g. `microresolve-py/0.1.6`) surfaces in
  the connected-clients panel for "who's still on the old client?"
  triage.

### Changed

- **API key format now embeds the label**: `mr_<name>_<64 hex chars>`.
  The server extracts the name from the key string itself — no separate
  index lookup needed for attribution. Old opaque keys (`mr_<hex>`
  without the embedded name) are no longer accepted; v0.1 is pre-stable
  and operators must regenerate keys via the Studio UI / `/api/auth/keys`.
- Auth-key names must be slug-safe: `[a-z0-9][a-z0-9-]{0,30}` (no
  underscore — that's the field separator).

### Note

- Connected-clients tracking is **only active when API keys are
  configured**. In open mode (no keys), the panel is empty by design —
  there's no identity to attribute connections to.

---

## [0.1.5] — 2026-04-30

### Breaking

- **Connected-mode protocol unified.** The three v0.1.4 endpoints
  (`GET /api/sync`, `POST /api/ingest`, `POST /api/correct`) are
  removed and replaced by a single `POST /api/sync` that carries the
  library's buffered logs + corrections + per-namespace local versions
  in one request, and returns deltas in one response. v0.1.4 clients
  cannot talk to v0.1.5 servers; pin matching versions for now.
- `MicroResolve.correct(...)` no longer makes an immediate HTTP call.
  The correction is applied locally on the spot and shipped to the
  server on the next sync tick. The server reconciles within
  `tick_interval_secs`. Eliminates the per-correction stampede risk
  at scale and gives clean eventual-consistency semantics.
- v0.1 is pre-stable; expect more breaking protocol changes before 1.0.

---

## [0.1.4.1] — 2026-04-30

### Changed

- **Studio binary now ships with UI embedded.** The downloaded GitHub
  Release tarball is a single executable — no sibling `ui/` directory
  required, no `npm run dev` step. Implementation: `rust-embed` with
  compression bakes `ui/dist/` into the binary at compile time; the
  `server` feature now implies the new `bundled-ui` feature so building
  the studio is `cargo build --release --features server --bin
  microresolve-studio` (single flag). Build prerequisite from source:
  `cd ui && npm run build` once before cargo build, so `ui/dist/` exists.
- Crate description latency claim updated `~30μs``~50μs` to match
  the v0.1.4 README.

### Note

- Library users (PyPI / npm / crates.io) are unaffected — `bundled-ui`
  only activates when the `server` feature is enabled, which embedded
  bindings never enable.

---

## [0.1.4] — 2026-04-30

### Added

- **Connect-mode examples**`examples/connected.rs` (Rust) and
  `python/examples/connected.py` (Python): minimal end-to-end demo of
  a library instance subscribing to a running Studio, resolving
  locally, pushing a correction, and watching the version bump on the
  next sync tick. This loop — Studio writes, library subscribes, live
  sync — is the headline feature of this release.
- **Prebuilt server binaries** via GitHub Releases: linux-gnu x86\_64 +
  aarch64, linux-musl x86\_64, darwin x86\_64 + aarch64,
  windows-msvc x86\_64. Install: download the tarball for your platform
  and put `microresolve-studio` on `$PATH`.

### Changed

- **Binary renamed**`server``microresolve-studio` in Cargo.toml;
  the old `server` bin name is removed. Update any scripts or service
  files that reference the old name.
- **TCP bind failure** — server now exits cleanly with a descriptive
  error when the port is already in use; previously it panicked.
- **README** — hero rewritten to "learnable reflex layer for LLM apps";
  added "In the box" block (Studio / Library / online learning / native
  imports / multilingual / multi-namespace); Quick Start moved above
  the comparison table; v0.1 disclaimer condensed to one line; median
  resolve latency updated to 50µs.

---

## [0.1.3] — 2026-04-28

### Breaking

- **`Engine` renamed to `MicroResolve`** across all bindings (Rust, Python, Node.js).
  Update all imports and call sites:
  - Rust: `Engine``MicroResolve`, `EngineConfig``MicroResolveConfig`
  - Python: `from microresolve import Engine``from microresolve import MicroResolve`
  - Node.js: `const { Engine } = require('microresolve')``const { MicroResolve } = require('microresolve')`

---

## [0.1.0] — TBD

Initial public release. **MicroResolve** is a pre-LLM reflex layer for
intent classification, safety filtering, and tool selection — sub-millisecond,
CPU-only, with continuous learning from corrections.

### Library API

- **`MicroResolve` + `NamespaceHandle`** — multi-namespace decision engine. One
  instance per application can run several classifiers in parallel: security
  / mood / MCP tool selection / intent. The single public entry point.
  (Previously named `Engine`; renamed in v0.1.3.)
- **`MicroResolveConfig`** with cascade: `data_dir`, `default_threshold`,
  `languages`, optional LLM config, optional server config for connected
  mode. (Previously named `EngineConfig`; renamed in v0.1.3.)
- **`NamespaceConfig`** — per-namespace overrides for threshold,
  languages, LLM model.
- **Connected mode** — set `MicroResolveConfig.server` to a `ServerConfig` and
  the engine pulls subscribed namespaces from a server on startup,
  spawns a single background sync thread, buffers query logs for
  shipping, and pushes corrections inline. Replaces the legacy
  `AppRouter` surface.
- **Typed errors**`Error::IntentNotFound`, `Io`, `Parse`,
  `Persistence`, `Connect`. No `Result<_, String>` in the public API.
- **`add_intent`** accepts `&[&str]` (English) or
  `HashMap<lang, Vec<phrase>>` (multilingual). Returns
  `Result<usize, Error>` (count of phrases indexed).
- **`update_intent` / `update_namespace`** patch metadata fields with
  `IntentEdit` / `NamespaceEdit` structs.
- **`correct(query, wrong, right)`** — one call moves a phrase between
  intents and reinforces; in connected mode, also pushes to the server.

### Bindings

- **Rust crate** `microresolve` on crates.io. Default features include
  connected mode (`reqwest`); embedded users can opt out via
  `default-features = false`.
- **Python** `microresolve` on PyPI — full `MicroResolve` + `Namespace` API
  via PyO3 / maturin. Mono and multilingual `add_intent`, `resolve`,
  `correct`, `intent`, `update_intent`, `add_phrase`, `version`,
  `flush`. Connected mode supported.
- **Node.js** `microresolve` on npm — `MicroResolve` + `Namespace` API via
  napi-rs. Connected mode supported.

### Server

- **HTTP API** (`microresolve-server`, `--features server`) wraps the
  same `MicroResolve` instance for multi-tenant deployments.
  - `/api/route_multi` — classify a query.
  - `/api/intents`, `/api/namespaces`, `/api/domains` — CRUD.
  - `/api/import/openapi/*`, `/api/import/mcp/*` — bulk-import tools.
  - `/api/sync`, `/api/ingest`, `/api/correct` — connected-mode endpoints
    for SDK clients.
  - `/api/auth/keys` — API key management for connected-mode auth.
- **Background auto-learn worker** — ingested query logs are
  LLM-judged in batch; corrections applied to the live namespace.
- **API key auth**`X-Api-Key` middleware; keys stored at
  `~/.config/microresolve/keys.json` (separate from data dir, never
  git-tracked).

### Git-versioned training data

- The `--data` directory is automatically a git repo. Every namespace
  mutation auto-commits with a meaningful message.
- `GET /api/namespaces/{id}/history?limit=N` — list commits scoped to
  a namespace.
- `GET /api/namespaces/{id}/diff?from=&to=` — semantic diff between two
  commits: intents added/removed, phrases added/removed, metadata
  changes, weight-update count. Filters out raw L1/L2 weight noise.
- `POST /api/namespaces/{id}/rollback``git reset --hard` + reload
  affected resolvers from disk.
- `GET /api/settings/git` / `PUT /api/settings/git` — runtime configure
  a `git remote origin` so each commit pushes to a real GitHub/GitLab
  repo. Auth uses whatever git is configured with on the server.
- `POST /api/git/push` — manual push trigger.

### Studio UI

- React + Vite single-page app served by the binary or run via
  `vite dev` against an external server.
- **Namespaces, Intents, Domains, Layers, Languages, Models** management
  pages.
- **Studio (Resolve / Simulate / Review)** — live classification, batch
  simulation, query review queue, manual + auto-learn corrections.
- **History** — first-class sidebar entry. List-sidebar + main-pane
  layout. For each selected commit: sha, relative time, message, and
  author; amber warning when rolling back will discard newer commits;
  semantic diff panel showing affected intents, sample phrases under
  each added/removed intent, phrases-added/removed grouped by intent,
  metadata changes side-by-side (before/after), and training-weight
  summary; one-click rollback with confirmation.
- **Settings → Data sync (Git)** — set/clear the git remote URL,
  manual push button, status indicator.
- **Settings → Auth Keys** — generate, list, revoke API keys.
- **Import** — OpenAPI / MCP / LangChain / OpenAI function-tool import
  flows with LLM-assisted seed-phrase generation.

### Internal

- `Resolver` and the `scoring` / `ngram` / `phrase` / `tokenizer` /
  `connect` modules are `#[doc(hidden)]`. Library users see only
  `MicroResolve`, `NamespaceHandle`, and the public types in rustdoc.
- Server bin migrated off direct `Resolver` use to `MicroResolve` API
  internally — single source of truth for namespace state.
- Cargo.toml `exclude` list keeps the published crate at ~174 KB
  (only `src/`, `examples/*.rs`, `tests/*.rs`, `languages/*.json`,
  `LICENSE-*`, `README.md`, `CHANGELOG.md`).
- License: dual MIT / Apache 2.0 across all artifacts (root crate,
  Python wheel, Node tarball, UI bundle, docs site).

### Benchmarks

Benchmark numbers will be added to this section after the v0.1.0
benchmark sweep runs. End-to-end smoke against Llama 3.3 / Groq
verifies multi-namespace MCP import, cross-namespace isolation,
manual corrections, and the auto-learn loop in ~75 seconds.

### Known limitations

- Server bin still uses `pub fn with_resolver(_mut)` closure escape
  hatches (marked `#[doc(hidden)]`). True `pub(crate) struct Resolver`
  is a v0.2 cleanup that doesn't change behaviour.
- Node binding is missing `resolve_with`, `intent`, `update_intent`,
  `add_phrase` methods that Python has (v0.2).
- History UI shows commit list + diff but no commit-graph visualisation
  or filter chips.
- No CLA — contributions are accepted under the dual MIT/Apache license.

---

[0.1.0]: https://github.com/gladius/microresolve/releases/tag/v0.1.0