@tpsdev-ai/flair 0.27.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,8 +13,8 @@ turning it on (`FLAIR_RERANK_ENABLED=true`) or running the recall-bench A/B.
13
13
 
14
14
  | `FLAIR_RERANK_MODEL` | GGUF filename (under `models/`) | Source | Inference mode |
15
15
  |---|---|---|---|
16
- | `qwen3-reranker-0.6b-q8` (default) | `Qwen3-Reranker-0.6B-q8_0.gguf` | `Mungert/Qwen3-Reranker-0.6B-GGUF` (q8_0) | generative yes/no |
17
- | `jina-reranker-v2` | `jina-reranker-v2-base.Q8_0.gguf` | `gpustack/jina-reranker-v2-base-multilingual-GGUF` (q8_0) | rank-pooling cross-encoder |
16
+ | `jina-reranker-v2` (**default**, working) | `jina-reranker-v2-base.Q8_0.gguf` | `gpustack/jina-reranker-v2-base-multilingual-GGUF` (q8_0) | rank-pooling cross-encoder |
17
+ | `qwen3-reranker-0.6b-q8` (**experimental** — see Known limitation) | `Qwen3-Reranker-0.6B-q8_0.gguf` | `Mungert/Qwen3-Reranker-0.6B-GGUF` (q8_0) | generative yes/no |
18
18
 
19
19
  Download into `models/` next to the embedding GGUF, e.g.:
20
20
 
@@ -36,11 +36,17 @@ vector order — it never blocks or breaks search.
36
36
  | Env var | Default | Meaning |
37
37
  |---|---|---|
38
38
  | `FLAIR_RERANK_ENABLED` | unset (**OFF**) | Master flag. `"true"` to enable. |
39
- | `FLAIR_RERANK_MODEL` | `qwen3-reranker-0.6b-q8` | Model + inference mode (table above). |
39
+ | `FLAIR_RERANK_MODEL` | `jina-reranker-v2` | Model + inference mode (table above). Re-read on every rerank call — changing it takes effect on the NEXT call (that call pays a one-time model-(re)load cost and can itself fall back to vector order if the load exceeds `FLAIR_RERANK_BUDGET_MS`; subsequent calls run at normal latency). |
40
40
  | `FLAIR_RERANK_TOPN` | `50` | Candidate count fed to the reranker; caps the HNSW fetch. |
41
41
  | `FLAIR_RERANK_BUDGET_MS` | `2500` | Hard latency budget; exceeded → vector order. |
42
42
  | `FLAIR_RERANK_MIN_CANDIDATES` | `2` | Skip rerank below this many candidates. |
43
43
 
44
+ Candidate document (and query) text is truncated to a per-model context budget
45
+ before it ever reaches the engine — see `resources/rerank-provider.ts`'s file
46
+ header ("Context-budget truncation"). This is not operator-configurable
47
+ (the budgets are derived from each mode's fixed context size); flagged here
48
+ only so a truncated rerank input isn't a surprise.
49
+
44
50
  ## Why this serving path (not Ollama, not a microservice)
45
51
 
46
52
  - **In-process node-llama-cpp** is the same engine the embedding engine already
@@ -62,6 +68,34 @@ detects this (`out.length === 0`), throws, and **fails open to vector order** (i
62
68
  never writes corrupt scores). Net effect today: with `FLAIR_RERANK_MODEL=qwen3-...`
63
69
  the rerank stage cleanly no-ops inside Harper.
64
70
 
65
- The `jina-reranker-v2` rank-API path (`createRankingContext()` / `rankAll`) works
66
- inside Harper. See the integration PR for the live recall-bench A/B numbers and the
67
- go/no-go read.
71
+ flair#811 (the live-corpus Phase-1 gate) found the qwen3 path erroring on every
72
+ call in production and root-caused two compounding issues, fixed in that PR:
73
+
74
+ 1. **Context overflow on real documents.** The offline pilot's 16-doc fixture was
75
+ short synthetic prose; real memory content routinely exceeds either model's
76
+ small context window (1024 tokens for the generative context, 2048 for the
77
+ rank context). `resources/rerank-provider.ts` now truncates every (query, doc)
78
+ pair to a budget derived from each mode's actual context size before it ever
79
+ reaches the engine (see that file's header). This doesn't fix the dual-backend
80
+ empty-logits limitation above, but it removes overflow as a SEPARATE, more
81
+ easily hit failure mode — and may have been a contributing cause of the
82
+ empty-logits symptom itself (an overflowing `controlledEvaluate` call doesn't
83
+ throw the way `rankAll` does; it's plausible an oversized prompt silently
84
+ context-shifted rather than decoding cleanly, though we couldn't confirm
85
+ this without a live repro).
86
+ 2. **Config not re-read after first init.** The provider used to cache which
87
+ model was loaded PERMANENTLY on first successful (or failed) init — a later
88
+ change to `FLAIR_RERANK_MODEL` had no effect until the process restarted, so
89
+ "configured model X, served model Y" could persist silently for the life of
90
+ the process. Now re-validated on every call (`needsReinit()`); a config
91
+ change is picked up on the next rerank.
92
+
93
+ **Given the above, `jina-reranker-v2` is now the DEFAULT model** — its rank-API
94
+ path (`createRankingContext()` / `rankAll`) completes inside Harper. `qwen3` stays
95
+ selectable and documented for whoever revisits the dual-backend isolation
96
+ question; it is not the default until the empty-logits limitation is actually
97
+ fixed (truncation alone doesn't fix it — see point 1 above, it only removes a
98
+ compounding cause).
99
+
100
+ See the integration PR / flair#811 for the live recall-bench A/B numbers and the
101
+ go/no-go read on `jina-reranker-v2` as the default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.27.1",
3
+ "version": "0.29.0",
4
4
  "packageManager": "bun@1.3.10",
5
5
  "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
6
6
  "type": "module",
@@ -76,6 +76,9 @@
76
76
  "@node-llama-cpp/win-arm64": "3.18.1",
77
77
  "@node-llama-cpp/win-x64": "3.18.1"
78
78
  },
79
+ "overrides": {
80
+ "react-native-fs": "npm:empty-npm-package@1.0.0"
81
+ },
79
82
  "devDependencies": {
80
83
  "@playwright/test": "1.59.1",
81
84
  "@types/node": "24.11.0",
@@ -4,7 +4,25 @@ type Memory @table(database: "flair") {
4
4
  content: String!
5
5
  contentHash: String @indexed
6
6
  visibility: String
7
- embedding: [Float] @indexed(type: "HNSW")
7
+ embedding: [Float] @indexed(type: "HNSW", M: 16) # M:16 is Harper's own HNSW default
8
+ # (HierarchicalNavigableSmallWorld M default) — declaring it explicitly is a
9
+ # ZERO behavior / graph-quality change. Its ONLY effect is the recall-heal
10
+ # trigger (resources/migrations/graph-heal.ts): Harper persists each HNSW
11
+ # attribute's option descriptor and, on boot, structurally diffs it against
12
+ # the schema (databases.ts canonicalizeIndexOptions — sorts keys, coerces
13
+ # numeric strings, but does NOT inject defaults, so `{type:"HNSW"}` and
14
+ # `{type:"HNSW",M:16}` are DIFFERENT canonical keys). A store whose descriptor
15
+ # was persisted WITHOUT options (every pre-this-change install) therefore sees
16
+ # a structural diff on the first boot after upgrade → runIndexing clears the
17
+ # graph store and rebuilds it CLEANLY from the already-correct stored vectors
18
+ # (lastIndexedKey reset to undefined). This heals graphs left stale by Harper's
19
+ # incremental HNSW update after a bulk in-place re-embed (the July embedding-
20
+ # stamp re-embed corrupted prod's reverse edges; a fresh index of the SAME
21
+ # vectors finds the true neighbors, the incrementally-updated one didn't).
22
+ # NO re-embed — the stored vectors are untouched; only the graph is rebuilt.
23
+ # Fires once per instance (the descriptor is then persisted to match); brief
24
+ # vector-search 503 window during the rebuild (~seconds; BM25/direct keep
25
+ # serving). Do NOT remove the `M: 16` — reverting it removes the upgrade heal.
8
26
  embeddingModel: String @indexed # ops-1l18: previously written (resources/Memory.ts stamps every
9
27
  # write) but never DECLARED in this schema, so it was storable but not
10
28
  # queryable — Harper's Table.search({conditions}) rejects a condition on
@@ -26,13 +44,17 @@ type Memory @table(database: "flair") {
26
44
  usageCount: Int # flair#683: verified-USE signal, distinct from retrievalCount (a
27
45
  # search HIT). Additive/nullable — absent reads as 0, existing rows
28
46
  # unaffected. NEVER auto-incremented on search/retrieval; the ONLY
29
- # writer is the dedicated usage-feedback endpoint (resources/
30
- # MemoryUsage.ts), which does a targeted patchRecord increment on
31
- # this field alone (never through Memory.put() see that
32
- # resource's module doc for why: usage-feedback writes to ANOTHER
33
- # agent's memory, and Memory's ownership check would 403 every
34
- # legit call). Drives usageBoost in resources/scoring.ts, which
35
- # replaces retrievalBoost in compositeScore (retrievalCount was the
47
+ # writer is resources/usage-recording.ts's recordUsageContribution()
48
+ # the shared ledger core behind BOTH usage surfaces: POST
49
+ # /RecordUsage (resources/RecordUsage.ts) and citation-on-write
50
+ # (resources/Memory.ts post()/put() recordCitations(), which also
51
+ # validates each cited id against the writer's read scope,
52
+ # flair#775). It does a targeted get-then-put increment on this
53
+ # field alone (never through Memory.put() see RecordUsage.ts's
54
+ # module doc for why: usage feedback writes to ANOTHER agent's
55
+ # memory, and Memory's ownership check would 403 every legit
56
+ # call). Drives usageBoost in resources/scoring.ts, which replaces
57
+ # retrievalBoost in compositeScore (retrievalCount was the
36
58
  # contaminated "retrieval counted as usage" signal root-caused in
37
59
  # flair#623).
38
60
  promotionStatus: String # null | "pending" | "approved" | "rejected"
@@ -137,9 +159,12 @@ type MemoryGrant @table(database: "flair") @export {
137
159
  # contributed to this memory's usageCount" is a single point-lookup, not a
138
160
  # query. This is what makes the anti-gaming dedup rule ("each (agent, memory)
139
161
  # pair contributes at most 1 to usageCount" — Sherlock) enforceable: the
140
- # dedicated endpoint (resources/MemoryUsage.ts) only bumps Memory.usageCount
141
- # when a NEW row is created here; a repeat call from the same agent for the
142
- # same memory is a no-op against a row that already exists.
162
+ # shared ledger core (resources/usage-recording.ts's recordUsageContribution(),
163
+ # reached via POST /RecordUsage and via citation-on-write's recordCitations())
164
+ # only bumps Memory.usageCount when a NEW row is created here; a repeat call
165
+ # from the same agent for the same memory is a no-op against a row that
166
+ # already exists. resources/MemoryUsage.ts guards this table's own HTTP
167
+ # surface (it is not the writer).
143
168
  #
144
169
  # `attribution` is OPAQUE — never parsed, never fed to an LLM, never rendered
145
170
  # — a free-text "what used this" hint the caller supplies, sanitized
@@ -154,7 +179,7 @@ type MemoryUsage @table(database: "flair") {
154
179
  }
155
180
 
156
181
  # MemoryCandidate — staged distillations from the FLAIR-NIGHTLY-REM cycle.
157
- # Per specs/FLAIR-NIGHTLY-REM.md § 7. Slice 1 of ops-2qq adds the schema +
182
+ # Per docs/rem.md. Slice 1 of ops-2qq adds the schema +
158
183
  # `flair rem candidates` listing command. Future slices wire the nightly
159
184
  # cycle that populates this table and the promote/reject endpoints.
160
185
  #