@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.
- package/README.md +1 -1
- package/dist/bridges/builtins/index.js +1 -1
- package/dist/bridges/discover.js +1 -1
- package/dist/bridges/runtime/load-plugin.js +1 -1
- package/dist/bridges/scaffold.js +3 -3
- package/dist/bridges/types.js +1 -1
- package/dist/cli.js +148 -26
- package/dist/rem/runner.js +2 -2
- package/dist/resources/MemoryReflect.js +1 -1
- package/dist/resources/RecordUsage.js +15 -1
- package/dist/resources/embeddings-provider.js +8 -38
- package/dist/resources/mcp-tools.js +9 -1
- package/dist/resources/memory-reflect-lib.js +1 -1
- package/dist/resources/migrations/embedding-stamp.js +23 -0
- package/dist/resources/migrations/graph-heal.js +250 -0
- package/dist/resources/migrations/registry.js +11 -5
- package/dist/resources/models-dir.js +55 -0
- package/dist/resources/record-types.js +2 -2
- package/dist/resources/rerank-provider.js +244 -36
- package/dist/resources/usage-recording.js +72 -6
- package/dist/resources/version.js +33 -0
- package/docs/assets/flair-cross-orchestrator.cast +1 -1
- package/docs/bridges.md +3 -3
- package/docs/mcp-clients.md +3 -1
- package/docs/n8n.md +1 -2
- package/docs/notes/rem-ux.md +1 -1
- package/docs/rem.md +2 -0
- package/docs/rerank-provisioning.md +40 -6
- package/package.json +4 -1
- package/schemas/memory.graphql +37 -12
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ If you need any of those specifically, use them. If you need crypto-pinned ident
|
|
|
67
67
|
|
|
68
68
|
Anthropic shipped [Claude Dreams](https://platform.claude.com/docs/en/managed-agents/dreams) (research preview, April 2026) — async pipeline that reads a memory store + session transcripts and produces a curated output store: duplicates merged, stale entries replaced, insights surfaced. Validates the category: agent memory accumulates drift and needs cleanup.
|
|
69
69
|
|
|
70
|
-
Flair ships both the on-demand curation surface (`flair rem rapid`) AND the scheduled nightly cycle, per
|
|
70
|
+
Flair ships both the on-demand curation surface (`flair rem rapid`) AND the scheduled nightly cycle, per [`docs/rem.md`](docs/rem.md) and its [distillation UX design note](docs/notes/rem-ux.md). **REM requires a configured generative backend** (Ollama, OpenAI, Anthropic, etc.) — without one, `flair rem rapid` fails with `Reflection error: No generative backend configured`. Config recipe (Ollama zero-key default, hosted-provider egress warning, clustered-deploy rules): [`docs/rem.md`](docs/rem.md).
|
|
71
71
|
|
|
72
72
|
- **`flair rem rapid`** — reflects and distills server-side by default, staging candidates in one bounded call. `--focus {lessons_learned, patterns, decisions, errors}` mirrors Dreams' `instructions` parameter. `--prompt-only` falls back to the bring-your-own-model handoff. Outputs *candidates*, not a wholesale store swap.
|
|
73
73
|
- **`flair rem candidates` / `flair rem promote <id> --rationale "<why>"` / `flair rem reject <id>`** — review and promote distilled candidates with required rationale.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* To add a new built-in:
|
|
11
11
|
* - YAML: write `src/bridges/builtins/<name>.ts` exporting a `YamlBridgeDescriptor`
|
|
12
12
|
* - Code plugin: write `src/bridges/builtins/<name>.ts` exporting a `MemoryBridge`
|
|
13
|
-
* Then register it here. See
|
|
13
|
+
* Then register it here. See docs/bridges.md § Two shapes for shapes A/B.
|
|
14
14
|
*/
|
|
15
15
|
import { agenticStackDescriptor } from "./agentic-stack.js";
|
|
16
16
|
import { claudeProjectMemoryBridge } from "./claude-project.js";
|
package/dist/bridges/discover.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function loadCodePlugin(discovered, opts = {}) {
|
|
|
53
53
|
field: "exports",
|
|
54
54
|
expected: "named `bridge` export or default export implementing MemoryBridge",
|
|
55
55
|
got: typeof mod === "object" && mod !== null ? `exports=${Object.keys(mod).join(",") || "(empty)"}` : typeof mod,
|
|
56
|
-
hint: `flair-bridge-<name> packages must export \`bridge\` (or default-export) a MemoryBridge. See
|
|
56
|
+
hint: `flair-bridge-<name> packages must export \`bridge\` (or default-export) a MemoryBridge. See docs/bridges.md § Shape B`,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
// Validate the shape. Keep this minimal — we trust the plugin author
|
package/dist/bridges/scaffold.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bridge scaffolding — emits a working starter for a new bridge.
|
|
3
3
|
*
|
|
4
|
-
* From
|
|
4
|
+
* From docs/bridges.md § Two shapes: "Scaffold & test in-tree. `flair bridge
|
|
5
5
|
* scaffold <name>` emits a working starter; `flair bridge test <name>`
|
|
6
6
|
* runs a round-trip diff." This file handles the `scaffold` half.
|
|
7
7
|
*
|
|
@@ -34,7 +34,7 @@ function fileDescriptorTemplate(name) {
|
|
|
34
34
|
# Generated by \`flair bridge scaffold ${name} --file\`.
|
|
35
35
|
# Edit to match your foreign system, then run \`flair bridge test ${name}\`.
|
|
36
36
|
#
|
|
37
|
-
# Spec: see
|
|
37
|
+
# Spec: see docs/bridges.md § The memory record and § Shape A.
|
|
38
38
|
name: ${name}
|
|
39
39
|
version: 1
|
|
40
40
|
kind: file
|
|
@@ -90,7 +90,7 @@ function apiIndexTemplate(name) {
|
|
|
90
90
|
* flair-bridge-${name}
|
|
91
91
|
*
|
|
92
92
|
* Generated by \`flair bridge scaffold ${name} --api\`.
|
|
93
|
-
* Spec: see
|
|
93
|
+
* Spec: see docs/bridges.md § The memory record and § Shape B.
|
|
94
94
|
*
|
|
95
95
|
* After editing, run:
|
|
96
96
|
* flair bridge test ${name}
|
package/dist/bridges/types.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the Flair memory bridge plugin system.
|
|
3
3
|
*
|
|
4
|
-
* Mirrors the contract in
|
|
4
|
+
* Mirrors the contract in docs/bridges.md. These types are the
|
|
5
5
|
* public surface a bridge author (human or agent) targets.
|
|
6
6
|
*
|
|
7
7
|
* IMPORTANT: the YAML bridge format (shape A) also conforms to these types
|
package/dist/cli.js
CHANGED
|
@@ -384,6 +384,33 @@ export function detectOpsApiAllInterfacesBind(portValue) {
|
|
|
384
384
|
}
|
|
385
385
|
return { allInterfaces: true, boundHost: null };
|
|
386
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Decide the source `flair init` should use for the admin password when no
|
|
389
|
+
* explicit `--admin-pass` / `--admin-pass-file` / env var was given
|
|
390
|
+
* (flair#827).
|
|
391
|
+
*
|
|
392
|
+
* Before this existed, init ALWAYS generated a fresh random password and
|
|
393
|
+
* overwrote `~/.flair/admin-pass` on every run — including a re-run against
|
|
394
|
+
* an install that was already bootstrapped and working (e.g. following
|
|
395
|
+
* `flair doctor`'s ops-bind finding, whose only prescribed remedy is
|
|
396
|
+
* re-running `flair init`). Harper's `HDB_ADMIN_PASSWORD` env var only seeds
|
|
397
|
+
* a brand-new install's user record — it does NOT rotate an existing user's
|
|
398
|
+
* stored password hash on every boot. So overwriting the file desynced it
|
|
399
|
+
* from what Harper actually had persisted, and the very next ops-API call in
|
|
400
|
+
* that SAME init run (seeding the agent) failed with a 401 "Login failed",
|
|
401
|
+
* breaking working auth on an install that had nothing wrong with its
|
|
402
|
+
* credentials.
|
|
403
|
+
*
|
|
404
|
+
* An admin-pass file that already exists on disk IS the working install's
|
|
405
|
+
* password — `flair init` is the only thing that ever writes it — so reuse
|
|
406
|
+
* it instead of generating a new one. That makes re-init idempotent: safe to
|
|
407
|
+
* run again at any time without risking the instance's auth. Deliberately
|
|
408
|
+
* rotating the admin password is a separate operation, not a `flair init`
|
|
409
|
+
* side effect.
|
|
410
|
+
*/
|
|
411
|
+
export function resolveInitAdminPasswordSource(adminPassFileExists) {
|
|
412
|
+
return adminPassFileExists ? "reuse-existing" : "generate-new";
|
|
413
|
+
}
|
|
387
414
|
// ─── Ops-socket permission posture (flair#763) ─────────────────────────────────
|
|
388
415
|
//
|
|
389
416
|
// The ops API domain socket (dataDir/operations-server) is protected by a
|
|
@@ -1846,6 +1873,39 @@ const __pkgVersion = (() => {
|
|
|
1846
1873
|
return "unknown";
|
|
1847
1874
|
}
|
|
1848
1875
|
})();
|
|
1876
|
+
/**
|
|
1877
|
+
* The `@tpsdev-ai/flair-mcp` spec written into a client's MCP config.
|
|
1878
|
+
*
|
|
1879
|
+
* PINNED, deliberately. A bare `npx -y @tpsdev-ai/flair-mcp` re-resolves to
|
|
1880
|
+
* whatever is currently published on EVERY agent session — so a single bad
|
|
1881
|
+
* publish (stolen credentials, a malicious commit that clears review, or a
|
|
1882
|
+
* compromised dependency of the MCP package) reaches every wired user
|
|
1883
|
+
* silently, with no lockfile and no review step in the path. The postmark-mcp
|
|
1884
|
+
* incident was exactly this shape: a legitimate publish by the legitimate
|
|
1885
|
+
* owner, propagating for 16 days before anyone noticed. Worse, a yank does
|
|
1886
|
+
* not help — unpinned clients keep resolving latest.
|
|
1887
|
+
*
|
|
1888
|
+
* Our publish side is already hardened (OIDC staged publish, human 2FA at the
|
|
1889
|
+
* release gate), but that defends against credential theft, not against a bad
|
|
1890
|
+
* version being published legitimately. The consumer side is where that gap
|
|
1891
|
+
* closes, and pinning is what closes it: a wired client keeps running the
|
|
1892
|
+
* exact version that was current when it was wired, and moving forward
|
|
1893
|
+
* becomes a deliberate act.
|
|
1894
|
+
*
|
|
1895
|
+
* flair and flair-mcp ship in version lockstep from this monorepo, so the
|
|
1896
|
+
* running CLI's own version is the correct pin. `flair init` re-run rewires
|
|
1897
|
+
* to the then-current version; see the upgrade-rewire follow-up issue for
|
|
1898
|
+
* making `flair upgrade` do the same.
|
|
1899
|
+
*
|
|
1900
|
+
* Falls back to the unpinned spec only when the version can't be read, which
|
|
1901
|
+
* is the same condition under which `--version` reports "unknown" — a broken
|
|
1902
|
+
* install, where a working MCP wiring matters more than a precise pin.
|
|
1903
|
+
*/
|
|
1904
|
+
export function mcpServerSpec(version = __pkgVersion) {
|
|
1905
|
+
return version && version !== "unknown"
|
|
1906
|
+
? `@tpsdev-ai/flair-mcp@${version}`
|
|
1907
|
+
: "@tpsdev-ai/flair-mcp";
|
|
1908
|
+
}
|
|
1849
1909
|
const program = new Command();
|
|
1850
1910
|
program.name("flair").version(__pkgVersion, "-v, --version");
|
|
1851
1911
|
// ─── CLI↔server version handshake (flair#695 §B) ────────────────────────────
|
|
@@ -2117,9 +2177,10 @@ program
|
|
|
2117
2177
|
selectedClients.push(clientOpt);
|
|
2118
2178
|
}
|
|
2119
2179
|
// Admin password: determine from opts, env, or generate
|
|
2120
|
-
// Priority: 1) --admin-pass-file, 2) env vars, 3) generate new
|
|
2180
|
+
// Priority: 1) --admin-pass-file, 2) env vars, 3) reuse existing file, 4) generate new
|
|
2121
2181
|
let adminPass;
|
|
2122
2182
|
let passwordSource = "generated";
|
|
2183
|
+
let reusedExistingAdminPass = false;
|
|
2123
2184
|
// Warn if --admin-pass is passed inline (not from env)
|
|
2124
2185
|
if (shouldShowInlineSecretWarning(opts.adminPass, false, new Set(["--admin-pass"]), "--admin-pass")) {
|
|
2125
2186
|
console.error("warning: --admin-pass passed inline. Consider --admin-pass-file <path> or FLAIR_ADMIN_PASS env " +
|
|
@@ -2151,34 +2212,65 @@ program
|
|
|
2151
2212
|
passwordSource = "env"; // Treat same as env for display purposes
|
|
2152
2213
|
}
|
|
2153
2214
|
else {
|
|
2154
|
-
// Generate new password and write to file atomically
|
|
2155
|
-
adminPass = Buffer.from(nacl.randomBytes(18)).toString("base64url");
|
|
2156
|
-
passwordSource = "generated";
|
|
2157
|
-
// Atomic write: create temp file in same dir, then rename
|
|
2158
2215
|
const flairDir = join(homedir(), ".flair");
|
|
2159
|
-
mkdirSync(flairDir, { recursive: true });
|
|
2160
2216
|
const adminPassPath = join(flairDir, "admin-pass");
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2217
|
+
passwordSource = "generated";
|
|
2218
|
+
if (resolveInitAdminPasswordSource(existsSync(adminPassPath)) === "reuse-existing") {
|
|
2219
|
+
// flair#827: an admin-pass file already on disk means a PRIOR `flair
|
|
2220
|
+
// init` already bootstrapped Harper's admin user with this password.
|
|
2221
|
+
// HDB_ADMIN_PASSWORD only seeds a brand-new install — Harper does
|
|
2222
|
+
// NOT rotate an existing user's stored password hash from env on
|
|
2223
|
+
// every boot. Generating and overwriting the file here would desync
|
|
2224
|
+
// it from what Harper actually has persisted, breaking ops-API auth
|
|
2225
|
+
// (401 "Login failed") on THIS SAME init run (the agent-seeding call
|
|
2226
|
+
// below) without fixing whatever the re-run was meant to fix — e.g.
|
|
2227
|
+
// `flair doctor`'s ops-bind finding, whose only prescribed remedy is
|
|
2228
|
+
// re-running `flair init`. Re-init must be idempotent here: reuse
|
|
2229
|
+
// the existing password so it's always safe to re-run against a
|
|
2230
|
+
// working install. Rotating the admin password on purpose is a
|
|
2231
|
+
// separate, deliberate operation (see the ops runbook), not a side
|
|
2232
|
+
// effect of re-init.
|
|
2233
|
+
try {
|
|
2234
|
+
adminPass = readAdminPassFileSecure(adminPassPath);
|
|
2235
|
+
}
|
|
2236
|
+
catch (err) {
|
|
2237
|
+
console.error(`Error: ${err.message}`);
|
|
2238
|
+
process.exit(1);
|
|
2239
|
+
}
|
|
2240
|
+
reusedExistingAdminPass = true;
|
|
2167
2241
|
}
|
|
2168
|
-
|
|
2169
|
-
//
|
|
2242
|
+
else {
|
|
2243
|
+
// Generate new password and write to file atomically
|
|
2244
|
+
adminPass = Buffer.from(nacl.randomBytes(18)).toString("base64url");
|
|
2245
|
+
// Atomic write: create temp file in same dir, then rename
|
|
2246
|
+
mkdirSync(flairDir, { recursive: true });
|
|
2247
|
+
const tempPath = mkdtempSync(join(flairDir, ".admin-pass.tmp-"));
|
|
2248
|
+
const finalTempPath = join(tempPath, "admin-pass");
|
|
2170
2249
|
try {
|
|
2250
|
+
writeFileSync(finalTempPath, adminPass + "\n", { mode: 0o600 });
|
|
2251
|
+
renameSync(finalTempPath, adminPassPath);
|
|
2171
2252
|
rmSync(tempPath, { recursive: true, force: true });
|
|
2172
2253
|
}
|
|
2173
|
-
catch {
|
|
2174
|
-
|
|
2254
|
+
catch (err) {
|
|
2255
|
+
// Clean up temp dir on failure
|
|
2256
|
+
try {
|
|
2257
|
+
rmSync(tempPath, { recursive: true, force: true });
|
|
2258
|
+
}
|
|
2259
|
+
catch { }
|
|
2260
|
+
throw err;
|
|
2261
|
+
}
|
|
2175
2262
|
}
|
|
2176
2263
|
}
|
|
2177
2264
|
const adminUser = DEFAULT_ADMIN_USER;
|
|
2178
|
-
// If we generated the password, report where it
|
|
2265
|
+
// If we generated (or reused) the password, report where it lives
|
|
2179
2266
|
if (passwordSource === "generated") {
|
|
2180
2267
|
const adminPassPath = join(homedir(), ".flair", "admin-pass");
|
|
2181
|
-
|
|
2268
|
+
if (reusedExistingAdminPass) {
|
|
2269
|
+
console.log(`Reusing existing admin password from: ${adminPassPath} (flair#827: re-init never rotates it — see the ops runbook to change it deliberately)`);
|
|
2270
|
+
}
|
|
2271
|
+
else {
|
|
2272
|
+
console.log(`Admin password saved to: ${adminPassPath}`);
|
|
2273
|
+
}
|
|
2182
2274
|
}
|
|
2183
2275
|
// Check Node.js version
|
|
2184
2276
|
const major = parseInt(process.version.slice(1), 10);
|
|
@@ -2515,7 +2607,8 @@ program
|
|
|
2515
2607
|
}
|
|
2516
2608
|
// ── MCP client wiring ────────────────────────────────────────────────
|
|
2517
2609
|
// The full one-command front door: detect installed MCP clients and wire
|
|
2518
|
-
// each to the zero-install `npx -y @tpsdev-ai/flair-mcp
|
|
2610
|
+
// each to the zero-install `npx -y @tpsdev-ai/flair-mcp@<version>` server
|
|
2611
|
+
// (pinned — see mcpServerSpec()). Claude
|
|
2519
2612
|
// Code is auto-wired into ~/.claude.json (the only client the CLI can
|
|
2520
2613
|
// safely modify); other clients get copy-paste snippets. `--no-mcp`
|
|
2521
2614
|
// skips wiring entirely; `--client <name>` targets one client; the
|
|
@@ -2551,7 +2644,7 @@ program
|
|
|
2551
2644
|
const flairMcpConfig = {
|
|
2552
2645
|
type: "stdio",
|
|
2553
2646
|
command: "npx",
|
|
2554
|
-
args: ["-y",
|
|
2647
|
+
args: ["-y", mcpServerSpec()],
|
|
2555
2648
|
// flair#718 authorship-provenance: each client's wired env block
|
|
2556
2649
|
// gets its OWN FLAIR_CLIENT label (never the shared mcpEnv
|
|
2557
2650
|
// object directly — that would stamp the same label into every
|
|
@@ -2640,7 +2733,9 @@ program
|
|
|
2640
2733
|
if (!opts.skipSmoke && !noMcp && clientOpt !== "none" && wiringResults.length > 0) {
|
|
2641
2734
|
console.log("\n Smoke-testing MCP server...");
|
|
2642
2735
|
try {
|
|
2643
|
-
|
|
2736
|
+
// Same spec that gets WIRED above — the smoke test must exercise the
|
|
2737
|
+
// exact version the user will run, not whatever npm resolves latest to.
|
|
2738
|
+
const mcpProc = spawn("npx", ["-y", mcpServerSpec()], {
|
|
2644
2739
|
env: { ...process.env, FLAIR_AGENT_ID: agentId, FLAIR_URL: httpUrl },
|
|
2645
2740
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2646
2741
|
});
|
|
@@ -5863,8 +5958,7 @@ export function describeReflectError(message) {
|
|
|
5863
5958
|
return { kind: "other", text: message };
|
|
5864
5959
|
}
|
|
5865
5960
|
// ─── flair rem rapid ──────────────────────────────────────────────────────────
|
|
5866
|
-
// Executes by default (
|
|
5867
|
-
// issue #707): distills server-side via /ReflectMemories execute:true and
|
|
5961
|
+
// Executes by default (§3C, issue #707): distills server-side via /ReflectMemories execute:true and
|
|
5868
5962
|
// stages MemoryCandidate rows, printing a staged-candidate summary. --prompt-only
|
|
5869
5963
|
// preserves the pre-#710 handoff behavior byte-for-byte, for the bring-your-
|
|
5870
5964
|
// own-model workflow.
|
|
@@ -8873,6 +8967,28 @@ program
|
|
|
8873
8967
|
}
|
|
8874
8968
|
});
|
|
8875
8969
|
// ─── flair reembed ────────────────────────────────────────────────────────────
|
|
8970
|
+
//
|
|
8971
|
+
// ROOT-CAUSE GUARD — recall graph correctness (recall-hnsw-graph-heal).
|
|
8972
|
+
// `flair reembed` replaces the stored embedding of many rows IN PLACE (it
|
|
8973
|
+
// clears embedding/embeddingModel, then re-PUTs through Memory.put()'s regen
|
|
8974
|
+
// branch — the same bulk in-place re-embed path resources/migrations/
|
|
8975
|
+
// embedding-stamp.ts uses). Historically, an OLDER (pre-fix) Harper's
|
|
8976
|
+
// INCREMENTAL HNSW update left stale/asymmetric reverse edges under bulk
|
|
8977
|
+
// re-embed, which collapsed prod recall in July. That engine bug is FIXED in
|
|
8978
|
+
// the Harper this ships against (5.1.22) — its update path reconstructs the
|
|
8979
|
+
// prior vector and does the reverse-edge cleanup the old build skipped — so a
|
|
8980
|
+
// bulk re-embed no longer corrupts the graph. DEFENSE-IN-DEPTH RULE (a
|
|
8981
|
+
// prudent, version-independent default, not a workaround for a live bug): pair
|
|
8982
|
+
// any BULK re-embed with a structural graph REBUILD trigger rather than relying
|
|
8983
|
+
// on incremental HNSW updates to converge (today: the
|
|
8984
|
+
// `@indexed(type:"HNSW", M:16)` descriptor bump in schemas/memory.graphql,
|
|
8985
|
+
// which makes Harper clear + rebuild the graph cleanly from the stored vectors
|
|
8986
|
+
// on the next boot — see resources/migrations/graph-heal.ts). Do NOT use
|
|
8987
|
+
// resources/MemoryReindex.ts's `_reindex` for graph correctness (it re-PUTs the
|
|
8988
|
+
// same vector through the incremental path and rebuilds nothing). If a `flair
|
|
8989
|
+
// reembed` run ever materially changes the vector space (e.g. a model swap),
|
|
8990
|
+
// follow it with a deploy that trips the structural reindex (bump the HNSW
|
|
8991
|
+
// descriptor / restart after a schema change).
|
|
8876
8992
|
program
|
|
8877
8993
|
.command("reembed")
|
|
8878
8994
|
.description("Re-generate embeddings for memories with stale or missing model tags")
|
|
@@ -9685,7 +9801,13 @@ program
|
|
|
9685
9801
|
// 3b. Ops API bind (flair#670) — report-only finding, never auto-fixed.
|
|
9686
9802
|
// Rebinding the ops API requires a Harper restart to take effect, so
|
|
9687
9803
|
// `doctor --fix` deliberately does not touch it here; the fix is
|
|
9688
|
-
// `flair init` (re-run) or a manual
|
|
9804
|
+
// `flair init` (re-run, then `flair restart` to apply it) or a manual
|
|
9805
|
+
// harper-config.yaml edit + restart. flair#827: re-running `flair init`
|
|
9806
|
+
// used to regenerate ~/.flair/admin-pass unconditionally, desyncing it
|
|
9807
|
+
// from Harper's already-persisted credential and breaking admin auth on
|
|
9808
|
+
// the very re-run this remedy prescribed — resolveInitAdminPasswordSource
|
|
9809
|
+
// (see its doc comment) now reuses the existing password instead, so this
|
|
9810
|
+
// remedy is safe to follow on a working install.
|
|
9689
9811
|
try {
|
|
9690
9812
|
const harperConfigPath = join(defaultDataDir(), "harper-config.yaml");
|
|
9691
9813
|
if (existsSync(harperConfigPath)) {
|
|
@@ -9694,7 +9816,7 @@ program
|
|
|
9694
9816
|
const bind = detectOpsApiAllInterfacesBind(opsPortValue);
|
|
9695
9817
|
if (bind.allInterfaces) {
|
|
9696
9818
|
console.log(` ${render.icons.error} Ops API bound to ${render.wrap(render.c.bold, "all interfaces")} (${render.wrap(render.c.dim, String(opsPortValue))})`);
|
|
9697
|
-
console.log(` ${render.wrap(render.c.dim, "Single-host installs don't need this reachable off-box. Fix:")} flair init ${render.wrap(render.c.dim, "(rebinds to loopback + domain socket on
|
|
9819
|
+
console.log(` ${render.wrap(render.c.dim, "Single-host installs don't need this reachable off-box. Fix:")} flair init && flair restart ${render.wrap(render.c.dim, "(rebinds to loopback + domain socket; re-init reuses your existing admin password, so this is safe on a running install — pass --ops-bind for deliberate remote admin)")}`);
|
|
9698
9820
|
issues++;
|
|
9699
9821
|
}
|
|
9700
9822
|
}
|
|
@@ -12060,7 +12182,7 @@ soul.command("list")
|
|
|
12060
12182
|
// Slice 1: discovery + scaffold. Slice 2: YAML runtime + `import` for Shape A
|
|
12061
12183
|
// + agentic-stack reference adapter as a built-in.
|
|
12062
12184
|
// `test` and `export` are still stubbed; Shape B (npm code plugins) too.
|
|
12063
|
-
// See
|
|
12185
|
+
// See docs/bridges.md.
|
|
12064
12186
|
const bridge = program.command("bridge").description("Manage memory bridges (import/export between Flair and foreign systems)");
|
|
12065
12187
|
bridge
|
|
12066
12188
|
.command("list")
|
package/dist/rem/runner.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* - Steps 1, 2 shipped in slice-1 PR-1 (#414).
|
|
21
21
|
* - Step 3 (maintenance) shipped in a prior slice-2 PR — fills
|
|
22
22
|
* `archived`/`expired` in the audit row.
|
|
23
|
-
* - Step 4: per
|
|
24
|
-
*
|
|
23
|
+
* - Step 4: per §3B (issue #707), "Deferred from parent § 4 step 4" —
|
|
24
|
+
* trust tiers aren't derivable yet
|
|
25
25
|
* (that's the emergent-trust arc). The input filter stays as today (own
|
|
26
26
|
* agent, non-archived, non-permanent, scope window); the safety net for
|
|
27
27
|
* un-tiered input is structural — candidates are staged, never
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* reflection prompt for a human/agent to run through their own LLM, or (b)
|
|
6
6
|
* with execute:true, runs the distillation server-side via Harper's models
|
|
7
7
|
* facade and stages the results as MemoryCandidate rows for review. See
|
|
8
|
-
*
|
|
8
|
+
* issue #707 (§3A).
|
|
9
9
|
*
|
|
10
10
|
* Request:
|
|
11
11
|
* agentId string — which agent to reflect on
|
|
@@ -34,6 +34,17 @@
|
|
|
34
34
|
* (and its ownership gate) — its own auth model is verified-agent +
|
|
35
35
|
* within-org + explicitly NO ownership requirement.
|
|
36
36
|
*
|
|
37
|
+
* NO READ-SCOPE GATE — INTENTIONAL ASYMMETRY with citation-on-write
|
|
38
|
+
* (flair#775): this endpoint deliberately does NOT validate reported ids
|
|
39
|
+
* against the caller's read scope. Usage feedback is the cross-agent
|
|
40
|
+
* contract described above (agent B reports using agent A's memory
|
|
41
|
+
* regardless of A's visibility setting), and the no-enumeration response
|
|
42
|
+
* below already denies existence probing on this surface. Citation-on-write
|
|
43
|
+
* (./usage-recording.ts's recordCitations()) DOES scope-gate each cited id —
|
|
44
|
+
* it is a separate write surface with a narrower threat model; see that
|
|
45
|
+
* module's doc. Do not "unify" the two: the difference is a K&S binding
|
|
46
|
+
* condition on the flair#775 locked design, not drift.
|
|
47
|
+
*
|
|
37
48
|
* WHY THIS ISN'T A @table-BACKED RESOURCE: the actual dedup ledger (one row
|
|
38
49
|
* per (agentId, memoryId) contribution) lives in the `MemoryUsage` table
|
|
39
50
|
* (schemas/memory.graphql), guarded by resources/MemoryUsage.ts. But this
|
|
@@ -164,7 +175,10 @@ export class RecordUsage extends Resource {
|
|
|
164
175
|
// ./usage-recording.ts's recordUsageContribution() — a shared,
|
|
165
176
|
// single-implementation extraction (also used by citation-on-write,
|
|
166
177
|
// resources/Memory.ts's post()/put()). Byte-identical behavior to
|
|
167
|
-
// the former private _recordOne() this replaced.
|
|
178
|
+
// the former private _recordOne() this replaced. Deliberately NO
|
|
179
|
+
// read-scope check on memoryId here (flair#775 asymmetry — see the
|
|
180
|
+
// module doc's NO READ-SCOPE GATE paragraph; citation-on-write's
|
|
181
|
+
// recordCitations() is the surface that scope-gates).
|
|
168
182
|
await recordUsageContribution(ctx, agentId, memoryId, attribution, now);
|
|
169
183
|
}
|
|
170
184
|
catch (err) {
|
|
@@ -107,9 +107,6 @@
|
|
|
107
107
|
* still a separate, deliberate step from shipping this code — see the PR
|
|
108
108
|
* description for that distinction.
|
|
109
109
|
*/
|
|
110
|
-
import { join } from "node:path";
|
|
111
|
-
import { existsSync } from "node:fs";
|
|
112
|
-
import { homedir } from "node:os";
|
|
113
110
|
const VALID_INPUT_TYPES = new Set(["document", "query"]);
|
|
114
111
|
/**
|
|
115
112
|
* THE GATE (flair#504 Phase 2 — flipped ON). A module-level constant, not an
|
|
@@ -239,42 +236,15 @@ async function getModelsApi() {
|
|
|
239
236
|
/**
|
|
240
237
|
* Resolve the directory the embeddings model lives in / downloads into.
|
|
241
238
|
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* downloaded into the package dir before this fix;
|
|
250
|
-
* never used as a download target on fresh installs.
|
|
251
|
-
* 4. ~/.flair/data/models — last-resort default when ROOTPATH is unset.
|
|
252
|
-
*
|
|
253
|
-
* The chosen dir is always writable, so the embeddings engine can download the
|
|
254
|
-
* model on first use without hitting EACCES on a root-owned package dir.
|
|
255
|
-
*
|
|
256
|
-
* Called by `resources/embeddings-boot.ts` (flair#694) to build the
|
|
257
|
-
* `modelsDir` it passes to harper-fabric-embeddings' `register()` — both run
|
|
258
|
-
* in the SAME process now (unlike the retired src/cli.ts-side computation,
|
|
259
|
-
* which duplicated this exact default because it ran in a separate process
|
|
260
|
-
* that set the resulting value via an env var). Still exported and tested
|
|
261
|
-
* independently (test/unit/embeddings-models-dir.test.ts) as the single
|
|
262
|
-
* documented source of truth for this default.
|
|
239
|
+
* Implementation moved to `resources/models-dir.ts` (flair#815) so the
|
|
240
|
+
* reranker can share the exact same resolution without importing this module
|
|
241
|
+
* (several unit-isolated tests `mock.module()` this file wholesale — see
|
|
242
|
+
* models-dir.ts's header). Re-exported here so existing consumers keep their
|
|
243
|
+
* import site: `resources/embeddings-boot.ts` (flair#694), which builds the
|
|
244
|
+
* `modelsDir` it passes to harper-fabric-embeddings' `register()`, and
|
|
245
|
+
* test/unit/embeddings-models-dir.test.ts, which pins the resolution order.
|
|
263
246
|
*/
|
|
264
|
-
export
|
|
265
|
-
const override = process.env.FLAIR_MODELS_DIR;
|
|
266
|
-
if (override)
|
|
267
|
-
return override;
|
|
268
|
-
const rootPath = process.env.ROOTPATH;
|
|
269
|
-
if (rootPath)
|
|
270
|
-
return join(rootPath, "models");
|
|
271
|
-
// Backward compat: a prior (writable) install may have the model cached in
|
|
272
|
-
// the package dir. Reuse it rather than re-downloading — but only if present.
|
|
273
|
-
const cwdModels = join(process.cwd(), "models");
|
|
274
|
-
if (existsSync(cwdModels))
|
|
275
|
-
return cwdModels;
|
|
276
|
-
return join(homedir(), ".flair", "data", "models");
|
|
277
|
-
}
|
|
247
|
+
export { resolveModelsDir } from "./models-dir.js";
|
|
278
248
|
// Kern: "Boot probe MUST have a 5-10s timeout ... on timeout set mode='none'
|
|
279
249
|
// + log a warning. A hung probe ... must never block Harper boot."
|
|
280
250
|
const PROBE_TIMEOUT_MS = 8_000;
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
* version bump + a new FlairClient method, out of scope for this query-only
|
|
36
36
|
* slice.
|
|
37
37
|
*/
|
|
38
|
+
import { resolveVersion } from "./version.js";
|
|
38
39
|
const H = {};
|
|
39
40
|
const LOADERS = {
|
|
40
41
|
SemanticSearch: async () => (await import("./SemanticSearch.js")).SemanticSearch,
|
|
@@ -248,7 +249,14 @@ async function bootstrap(agent, args) {
|
|
|
248
249
|
// ONLY when requested so a plain bootstrap delegates a byte-identical body.
|
|
249
250
|
if (args?.abstain === true)
|
|
250
251
|
body.abstain = true;
|
|
251
|
-
|
|
252
|
+
// flair#831 — attach the running Flair version to the RESPONSE (not the
|
|
253
|
+
// delegated request body) so the calling agent learns the server version
|
|
254
|
+
// on its very first call.
|
|
255
|
+
const result = unwrap(await h.post(body));
|
|
256
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
257
|
+
return { ...result, flairVersion: resolveVersion() };
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
252
260
|
}
|
|
253
261
|
async function soulSet(agent, args) {
|
|
254
262
|
const Cls = await handler("Soul");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ─── Memory Reflection — pure logic for /ReflectMemories ────────────────────
|
|
2
2
|
// Pure helpers backing resources/MemoryReflect.ts (FLAIR-NIGHTLY-REM slice 2,
|
|
3
|
-
// §3A — see
|
|
3
|
+
// §3A — see issue #707).
|
|
4
4
|
//
|
|
5
5
|
// Same split as resources/memory-consolidate-lib.ts: importing MemoryReflect.ts
|
|
6
6
|
// pulls in the Harper runtime (`databases`/`Resource`/`models`, storage init),
|
|
@@ -101,6 +101,29 @@
|
|
|
101
101
|
* OTHER, not-yet-understood counting artifact; this lets the runner PROVE
|
|
102
102
|
* (via `.get()`, a primary-key lookup — always live, never index-assisted)
|
|
103
103
|
* that a nonzero countPending() result is real before halting on it.
|
|
104
|
+
*
|
|
105
|
+
* ROOT-CAUSE GUARD — recall graph correctness (recall-hnsw-graph-heal).
|
|
106
|
+
* This migration RE-EMBEDS rows in BULK via `PUT /Memory/:id` — it replaces
|
|
107
|
+
* the stored vector of many rows in place. Historically, an OLDER (pre-fix)
|
|
108
|
+
* Harper's INCREMENTAL HNSW update left stale/asymmetric reverse edges under
|
|
109
|
+
* exactly this kind of bulk re-embed (order-dependent), which collapsed prod
|
|
110
|
+
* recall (p@3 0.25) in July. That engine bug is FIXED in the Harper this ships
|
|
111
|
+
* against (5.1.22): its index() reconstructs the prior vector from the stored
|
|
112
|
+
* node and carries the node's real connections into the reverse-edge cleanup
|
|
113
|
+
* the old build skipped, so a bulk re-embed no longer corrupts the graph.
|
|
114
|
+
* DEFENSE-IN-DEPTH RULE (a prudent, version-independent default — not a
|
|
115
|
+
* workaround for a live bug): pair any BULK re-embed with a structural graph
|
|
116
|
+
* REBUILD trigger rather than relying on incremental HNSW updates to converge
|
|
117
|
+
* — it keeps recall correct regardless of engine version, closes the rare
|
|
118
|
+
* decode-failure edge case, and heals a graph already corrupted on disk by an
|
|
119
|
+
* older engine. Do NOT reach for resources/MemoryReindex.ts's `_reindex` for
|
|
120
|
+
* graph correctness (it re-PUTs the same vector through the incremental path
|
|
121
|
+
* and rebuilds nothing). The lever flair uses is a structural schema-descriptor
|
|
122
|
+
* change: the `@indexed(type:"HNSW", M:16)` bump in schemas/memory.graphql
|
|
123
|
+
* ships alongside this migration precisely so a graph corrupted under an older
|
|
124
|
+
* engine is rebuilt on the upgrade boot (observed/ledgered by
|
|
125
|
+
* resources/migrations/graph-heal.ts). Before enabling any FUTURE bulk
|
|
126
|
+
* re-embed, ship a structural reindex trigger with it.
|
|
104
127
|
*/
|
|
105
128
|
import { databases } from "@harperfast/harper";
|
|
106
129
|
import { getModelId } from "../embeddings-provider.js";
|