@tpsdev-ai/flair 0.48.0 → 0.50.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.
Files changed (46) hide show
  1. package/README.md +2 -0
  2. package/dist/bridges/runtime/roundtrip.js +91 -2
  3. package/dist/build-info.json +3 -3
  4. package/dist/cli.js +903 -226
  5. package/dist/component-env.js +52 -4
  6. package/dist/deploy.js +20 -3
  7. package/dist/doctor-client.js +105 -32
  8. package/dist/federation/scheduler.js +24 -3
  9. package/dist/hook-install.js +96 -16
  10. package/dist/install/clients.js +318 -9
  11. package/dist/lib/auth-resolve.js +34 -3
  12. package/dist/lib/mcp-enable.js +134 -26
  13. package/dist/lib/scheduler-platform.js +132 -10
  14. package/dist/lib/scratch-owner.js +49 -0
  15. package/dist/rem/scheduler.js +23 -5
  16. package/dist/resources/AgentSeed.js +2 -0
  17. package/dist/resources/Memory.js +24 -5
  18. package/dist/resources/MemoryBootstrap.js +8 -4
  19. package/dist/resources/MemoryFeed.js +3 -0
  20. package/dist/resources/MemoryMaintenance.js +11 -2
  21. package/dist/resources/bm25-index-service.js +257 -0
  22. package/dist/resources/bm25-index.js +631 -0
  23. package/dist/resources/bm25.js +31 -1
  24. package/dist/resources/embeddings-boot.js +45 -3
  25. package/dist/resources/health.js +52 -7
  26. package/dist/resources/mcp-tools.js +1 -0
  27. package/dist/resources/memory-read-scope.js +2 -0
  28. package/dist/resources/search-readiness.js +100 -0
  29. package/dist/resources/semantic-retrieval-core.js +102 -23
  30. package/dist/resources/sort-comparators.js +45 -0
  31. package/dist/src/lib/scheduler-platform.js +132 -10
  32. package/dist/src/rem/scheduler.js +23 -5
  33. package/dist/version-check.js +59 -13
  34. package/docs/auth.md +5 -0
  35. package/docs/claude-code.md +10 -3
  36. package/docs/deepseek-harness.md +1 -1
  37. package/docs/deployment.md +11 -1
  38. package/docs/hosted-on-fabric.md +2 -0
  39. package/docs/integrations.md +78 -5
  40. package/docs/mcp-clients.md +85 -15
  41. package/docs/notes/mcp-oauth-model2.md +31 -13
  42. package/docs/quickstart-fabric.md +1 -1
  43. package/docs/quickstart.md +9 -9
  44. package/docs/standalone-local.md +3 -0
  45. package/docs/troubleshooting.md +25 -0
  46. package/package.json +4 -3
@@ -96,10 +96,10 @@ flair hook status # wired? correct shape? which agent/instance?
96
96
  flair hook uninstall # removes only Flair's hook entry
97
97
  ```
98
98
 
99
- `--harness claude-code` is the only supported value today (it's also the
100
- default) — the flag exists so a future harness is an additive registry entry,
101
- not a breaking change. `flair doctor` already checks for this same hook (see
102
- below) and recognizes anything `flair hook install` writes.
99
+ `--harness` defaults to `claude-code`. `codex` is also supported (writes
100
+ `~/.codex/hooks.json`see the Codex section below). `flair doctor` checks
101
+ the same hook for each detected harness and recognizes anything
102
+ `flair hook install` writes.
103
103
 
104
104
  Or wire it by hand — add a `SessionStart` hook to `~/.claude/settings.json`:
105
105
 
@@ -111,7 +111,7 @@ Or wire it by hand — add a `SessionStart` hook to `~/.claude/settings.json`:
111
111
  "hooks": [
112
112
  {
113
113
  "type": "command",
114
- "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y -p @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'"
114
+ "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y -p @tpsdev-ai/flair-mcp@<version> flair-session-start 2>/dev/null) && printf %s \"$out\" || true'"
115
115
  }
116
116
  ]
117
117
  }
@@ -120,12 +120,23 @@ Or wire it by hand — add a `SessionStart` hook to `~/.claude/settings.json`:
120
120
  }
121
121
  ```
122
122
 
123
- Swap `me` for your `FLAIR_AGENT_ID`. Unlike the MCP-server snippets above, this
124
- one is shown **unpinned**, because that is what `flair hook install` writes and
125
- what the tooling recognises: `flair hook status` matches the exact unpinned
126
- command, so a hand-pinned hook reports `wired: false` there (while `flair
127
- doctor` still sees it). Pinning this line is therefore not yet supported
128
- prefer `flair hook install`.
123
+ Swap `me` for your `FLAIR_AGENT_ID` and `<version>` for `flair --version`. This
124
+ is the same pin `flair init` writes into client MCP configs (`mcpServerSpec()`,
125
+ flair#907): a wired hook should not self-update to a freshly published
126
+ `flair-mcp` any more than a wired MCP client should. `flair hook install`,
127
+ `flair init`, and `flair doctor --fix` (when adding a missing hook) write that
128
+ pin; re-run `flair hook install` to advance a stale or pre-#1143 unpinned hook
129
+ to the running CLI's version.
130
+
131
+ That is a different surface from public plugin `mcp.json` files, which stay
132
+ **unpinned** on purpose (flair#1308) so directory listings that scrape them do
133
+ not freeze on a shipped version. User-local wiring is pinned; catalog
134
+ manifests are not.
135
+
136
+ `flair hook status` recognises both the current pinned `-p` form and an older
137
+ unpinned `-p` invocation as `correctShape`. The pre-#1166 form (no `-p`, which
138
+ runs the MCP shim) is still flagged. Prefer `flair hook install` over
139
+ hand-editing.
129
140
 
130
141
  The `sh -c ... || true` wrapper is not decoration. The invocation resolves a
131
142
  package binary through whatever Node runtime your shell exposes, and under a
@@ -208,6 +219,46 @@ For project-scoped trust (per Codex's MCP guide), the same block in `.codex/conf
208
219
 
209
220
  Restart your Codex CLI session and the `flair_*` tools become available to the agent.
210
221
 
222
+ #### Auto-recall on session start (optional hook)
223
+
224
+ Wiring the MCP server alone does not load memory at session start — Codex
225
+ then has pull tools it never thinks to call. The same `flair-session-start`
226
+ command Claude Code uses writes Codex's SessionStart hook (same JSON schema,
227
+ into `~/.codex/hooks.json`):
228
+
229
+ ```bash
230
+ flair hook install --harness codex
231
+ flair hook status --harness codex
232
+ flair hook uninstall --harness codex
233
+ ```
234
+
235
+ `flair doctor` reports this hook when Codex is detected. After install, trust
236
+ the new command in Codex with `/hooks` — untrusted hooks are listed and
237
+ skipped.
238
+
239
+ ---
240
+
241
+ ## Hookless harnesses (Gemini, Cursor, and anything without SessionStart)
242
+
243
+ Some clients have no session-start hook Flair can write. Wiring the MCP
244
+ server is necessary but not sufficient — the model still has to choose to
245
+ call `bootstrap`. Add a short instruction block to the file that client
246
+ already loads (`AGENTS.md`, `GEMINI.md`, or the equivalent):
247
+
248
+ ```markdown
249
+ ## Flair memory
250
+
251
+ At the start of every session, call the Flair `bootstrap` tool before
252
+ responding. Before a deep-dive, `memory_search` for related prior work.
253
+ At wrap, `memory_store` durable lessons.
254
+ ```
255
+
256
+ Without that, a config that looks wired is the known failure shape
257
+ ([#989](https://github.com/tpsdev-ai/flair/issues/989),
258
+ [#908](https://github.com/tpsdev-ai/flair/issues/908)): tools exist, memory
259
+ never enters the working set. Use `flair hook install` when the client has
260
+ a SessionStart hook; use this static block when it does not.
261
+
211
262
  ---
212
263
 
213
264
  ## Step 3 — Verify
@@ -222,13 +273,13 @@ If you see (a) the agent calling the `bootstrap` tool returning soul + recent me
222
273
 
223
274
  ## What the MCP server exposes
224
275
 
225
- Eleven tools, kept deliberately small:
276
+ Twelve tools, kept deliberately small:
226
277
 
227
278
  | Tool | What it does |
228
279
  |---|---|
229
280
  | `memory_search` | Semantic search across your agent's memories |
230
- | `memory_store` | Save a memory with type, durability, tags, visibility. Auto-dedups near-duplicates |
231
- | `memory_update` | Update an existing memory by ID — overwrite in place, or version it with `preserveHistory` |
281
+ | `memory_store` | Save a memory with type, durability, tags, visibility. Auto-dedups near-duplicates. Optional `usedMemoryIds` cites memories that informed the write |
282
+ | `memory_update` | Update an existing memory by ID — overwrite in place, or version it with `preserveHistory`. Optional `usedMemoryIds` for citation-on-write |
232
283
  | `memory_get` | Fetch a specific memory by ID |
233
284
  | `memory_delete` | Remove a memory |
234
285
  | `relationship_store` | Record a subject-predicate-object relationship triple (e.g. "nathan manages flair") |
@@ -237,6 +288,7 @@ Eleven tools, kept deliberately small:
237
288
  | `soul_get` | Get a soul entry |
238
289
  | `flair_workspace_set` | Set your agent's current workspace state (ref/branch, phase, task) in the Office Space |
239
290
  | `flair_orgevent` | Publish an org-wide coordination event (claim/release/status) to the Office Space |
291
+ | `record_usage` | Report that recalled memories were actually used (id + optional one-line how-it-was-used). Drives `usageCount` / `usageBoost` |
240
292
 
241
293
  Writes are scoped per-agent (your `FLAIR_AGENT_ID`) and enforced by Flair's server, not by client convention — you can't write as another agent. Reads are more open by design: any agent on the same Flair instance can read any other agent's **non-private** memories, with no grant to set up (open-within-org read; see [SECURITY.md](../SECURITY.md)).
242
294
 
@@ -266,6 +318,24 @@ The MCP server has no client-side flags beyond these env vars; everything else (
266
318
 
267
319
  ---
268
320
 
321
+ ## What about pi?
322
+
323
+ pi has no MCP client support, so Flair ships a **native pi extension** instead: [`@tpsdev-ai/pi-flair`](../packages/pi-flair/README.md). Same backend, same agent isolation, zero MCP in the path.
324
+
325
+ Wiring is a `packages` entry in pi's own settings (`~/.pi/agent/settings.json`), not an `mcpServers` block:
326
+
327
+ ```json
328
+ {
329
+ "packages": ["npm:@tpsdev-ai/pi-flair@<version>"]
330
+ }
331
+ ```
332
+
333
+ `flair init --client pi` writes exactly that (pinned), or use pi's own installer: `pi install npm:@tpsdev-ai/pi-flair`. `flair doctor` detects pi and checks the wiring — including the one known trap: **an `npm:` spec under the `extensions` settings key is silently ignored by pi** (`extensions` takes local file paths only; package sources belong under `packages` — [#1346](https://github.com/tpsdev-ai/flair/issues/1346)). Doctor calls that misconfiguration out by name, and `flair doctor --fix` moves the entry.
334
+
335
+ One difference from the MCP clients above: pi settings carry no per-package `env` block, so `FLAIR_AGENT_ID` (and `FLAIR_URL` when non-default) must be exported in the environment that launches pi. Doctor reports what it sees in its own shell and says so — it cannot observe the environment of every pi launch.
336
+
337
+ ---
338
+
269
339
  ## What about Hermes (Nous Research)?
270
340
 
271
341
  Hermes uses its own Python-native `MemoryProvider` ABC instead of MCP. It has its own Flair integration in [`packages/hermes-flair/`](../packages/hermes-flair). Same backend, same agent isolation, different plug shape.
@@ -280,7 +350,7 @@ Future MCP-capable agent CLIs (and there are more landing every month) will work
280
350
 
281
351
  **"connection_error: could not reach Flair at http://127.0.0.1:19926".** The Flair server isn't running. Run `flair status` to check; `flair start` to bring it up.
282
352
 
283
- **"auth_error: …" on every call.** The agent identity doesn't match a registered key. Re-run `flair agent add <id>` (idempotent on re-add — won't lose existing memories).
353
+ **"auth_error: …" on every call.** The agent identity doesn't match a registered key. Re-run `flair agent add <id>` (idempotent on re-add — won't lose existing memories). Against a hosted instance the same three shapes apply (record missing / key mismatch / config wrong), and a by-id 404 is fail-closed ownership, not an existence signal — [Hosted Flair auth](integrations.md#hosted-flair-auth--your-agent-got-a-404).
284
354
 
285
355
  **Tool calls succeed but the agent doesn't see results in subsequent turns.** Check that the CLI is actually invoking `bootstrap` at session start — most CLIs need an explicit prompt nudge ("call the bootstrap tool now") on first use. Subsequent turns should pick up automatically once the CLI sees the schema.
286
356
 
@@ -61,25 +61,43 @@ opt-in, never something the server infers. The practical consequences:
61
61
  agent id to attach the sub to it; the step's output states the resulting
62
62
  `sub → Agent` mapping in as many words.
63
63
  - **Linking a sub to an existing Agent (the same-identity opt-in).** Re-run
64
- `flair mcp enable` with the SAME `--idp-provider`/`--idp-subject` and
65
- `--principal <your-cli-agent-id>`. The existing `(provider, subject)`
66
- Credential is RE-POINTED to that principal — one Credential row per subject,
67
- so resolution stays deterministic. The link *replaces* the mapping; it does
68
- not merge the two agents' memories.
64
+ `flair mcp enable` with the SAME `--idp-subject` and `--principal
65
+ <your-cli-agent-id>`. The existing Credential for that subject is RE-POINTED
66
+ to that principal — one ACTIVE Credential row per subject, so resolution stays
67
+ deterministic. The link *replaces* the mapping; it does not merge the two
68
+ agents' memories.
69
69
  - **First diagnostic: ask the server who you are.** The `bootstrap` tool's
70
70
  response always carries the resolved `agentId` and a `scope` descriptor
71
71
  (`scope.agentId` / `scope.isAdmin` / `scope.reads`, flair#1182). "My memory
72
72
  is empty over the connector" + a `bootstrap.agentId` you don't recognize =
73
73
  the sub resolved to a different (often JIT-provisioned) Agent — link it as
74
74
  above.
75
- - **JIT caveat.** A JIT-provisioned mapping (`FLAIR_MCP_JIT_PROVISION=1`)
76
- stamps `idpProvider: "mcp-oauth"`. Runtime resolution matches on
77
- `(kind, idpSubject)` only, but the *linking* upsert matches on
78
- `(kind, idpProvider, idpSubject)` — so when re-linking a JIT-provisioned
79
- sub, pass `--idp-provider mcp-oauth` (matching the JIT stamp), or first
80
- revoke the JIT credential (`status: "revoked"`). Linking under a different
81
- provider name creates a SECOND active credential for the same subject, and
82
- which one wins resolution is unspecified.
75
+ - **Re-linking under a different provider name SUPERSEDES (flair#1317).** A
76
+ JIT-provisioned mapping (`FLAIR_MCP_JIT_PROVISION=1`) stamps `idpProvider:
77
+ "mcp-oauth"`, so re-linking that sub as, say, `github` is a *provider change*.
78
+ The invariant is **at most one ACTIVE `Credential(kind:"idp", idpSubject)` per
79
+ subject, regardless of provider** the same key runtime resolution uses. So
80
+ the link revokes the prior credential (terminal `status: "revoked"`, the row
81
+ retained for audit) and writes the new one, in a single batched write. You do
82
+ NOT need to match `--idp-provider` to the JIT stamp, and you do not need to
83
+ revoke anything by hand first. `provisionIdpIdentityMapping` returns
84
+ `credentialSuperseded: true` with the revoked ids, and the `flair mcp enable`
85
+ identity-mapping step prints them.
86
+
87
+ Read `credentialSuperseded` as **"the prior credential for this subject is now
88
+ dead"**, not "a duplicate was tidied up". `idpProvider` is audit/diagnostic
89
+ metadata on the row; it does not namespace the subject. Residual risk, ruled
90
+ acceptable (Sherlock, #1317): anyone who can run the link for a subject can
91
+ revoke that subject's existing credential, so two genuinely different people
92
+ sharing one subject string across providers would evict each other. IdP
93
+ subjects are opaque per-IdP identifiers, so this is remote — and the
94
+ alternative, duplicate active credentials resolved by iteration order, is
95
+ strictly worse.
96
+
97
+ Before the fix, the linking upsert deduped on `(kind, idpProvider,
98
+ idpSubject)` while resolution read `(kind, idpSubject)`, so a cross-provider
99
+ re-link silently created a SECOND active credential and which one won was
100
+ unspecified.
83
101
 
84
102
  The two-identity contract (a distinct connector agent sees other agents'
85
103
  org-non-private rows, never their private rows, 404-never-403 by id; a linked
@@ -119,7 +119,7 @@ Same connector, different panel. This is the field-verified sequence — includi
119
119
 
120
120
  Keep the pass file mode `0600`. Newer releases add `--admin-pass-file <path>`, which reads the file in-process (invisible to `ps`) — check `flair agent add --help` and prefer it when present.
121
121
 
122
- 5. **Restart the MCP process** if it started before the key existed it does not pick the key up mid-session. Still 401 with the key on disk? Set the key path explicitly in the agent's MCP env: `FLAIR_KEY_PATH=~/.flair/keys/grok-cos.key`. Known papercut, tracked in [flair#1271](https://github.com/tpsdev-ai/flair/issues/1271).
122
+ 5. The next tool call picks up the key — a miss is not cached. A 401 names the agent and the paths that were looked in. If those are not where `flair agent add` wrote the file, the MCP process's home differs from the shell; set `FLAIR_KEY_PATH` to the absolute path of the `.key` file.
123
123
 
124
124
  6. Verify: ask the agent to "load my Flair bootstrap". You should get soul + memories **including shared org context** — findings written by teammate agents. A shared-visibility write from this agent is now readable by every org agent.
125
125
 
@@ -164,6 +164,14 @@ Add `--explain` to see the ranking inputs per hit — the raw score, the composi
164
164
 
165
165
  ## 6. Give your agent context on boot
166
166
 
167
+ With MCP wired (`flair init` does this for every client it detects), the recommended session-start is the `bootstrap` tool. In Claude Code that appears as `mcp__flair__bootstrap` (Claude Code's namespaced name for the server's `bootstrap` tool). Add this to your `CLAUDE.md`:
168
+
169
+ ```
170
+ At the start of every session, run mcp__flair__bootstrap before responding.
171
+ ```
172
+
173
+ Use the CLI variant — `flair bootstrap --agent <id>` — when MCP is not wired: previewing context yourself, a script, or any agent that can run a shell command.
174
+
167
175
  ```bash
168
176
  flair bootstrap --agent local --max-tokens 2000
169
177
  ```
@@ -174,15 +182,7 @@ flair bootstrap --agent local --max-tokens 2000
174
182
  ✓ budget 20/2000 tokens (1%) · ✓ 1 included · ✓ 0 truncated
175
183
  ```
176
184
 
177
- Soul entries and relevant memories, in one block sized to a token budget. Paste it into any LLM session Claude Code, Codex, Cursor, an API call — to hand the agent its identity and memory in one shot.
178
-
179
- Using Claude Code? Add this to your `CLAUDE.md`:
180
-
181
- ```
182
- At the start of every session, run mcp__flair__bootstrap before responding.
183
- ```
184
-
185
- With the MCP server wired up — `flair init` does this automatically for every client it detects — Claude Code runs bootstrap on every new session. See the [integration section in README.md](../README.md#integration).
185
+ Soul entries and relevant memories, in one block sized to a token budget. Paste that CLI output into any LLM session that does not have the MCP server — Codex, Cursor, an API call — to hand the agent its identity and memory in one shot. See the [integration section in README.md](../README.md#integration).
186
186
 
187
187
  ## What's next
188
188
 
@@ -107,6 +107,9 @@ logging:
107
107
  | `HDB_ADMIN_PASSWORD` | Bootstrap password for the embedded Harper. After first start, the persisted user record is the source of truth. | Set at install time. See [secrets-and-keys.md](secrets-and-keys.md) for rotation. |
108
108
  | `FLAIR_KEY_PASSPHRASE` | Passphrase for AES-256-GCM encryption of federation private-key seeds. | Set explicitly for production federation deployments. |
109
109
  | `FLAIR_URL` | Override the Flair base URL for CLI commands (points to a remote instance). | When connecting from a different machine. |
110
+ | `FLAIR_EMBED_THREADS` | CPU threads for in-process embedding. Default is `max(1, availableParallelism() − 1)` — host-aware, one core left for Harper. | Pin a positive integer on a dedicated ingest host, or when the default leaves cores idle / oversubscribed. See [deployment.md](deployment.md#performance-related-environment-variables). |
111
+ | `FLAIR_HYBRID_RETRIEVAL` | Hybrid BM25 + vector retrieval (default on). | Set `false` to revert to HNSW-only. |
112
+ | `FLAIR_MODELS_DIR` | Directory the embedding GGUF is loaded from. | When the model lives outside `<data-dir>/models`. |
110
113
 
111
114
  ---
112
115
 
@@ -105,6 +105,31 @@ date
105
105
 
106
106
  If using the MCP server, restart Claude Code after rotating keys.
107
107
 
108
+ `flair agent list` is localhost-only. Against a hosted instance, the
109
+ discriminator is the 401 body on a signed request (`unknown_agent` vs
110
+ `invalid_signature`), not a local agent list. Adapter-shaped walkthrough:
111
+ [integrations.md — Hosted Flair auth](integrations.md#hosted-flair-auth--your-agent-got-a-404).
112
+
113
+ ### 404 on `GET`/`PUT /Memory/{id}`
114
+
115
+ **Symptoms:** `memory_get`, a by-id update, or an adapter write/read against
116
+ `/Memory/{id}` returns 404. The agent reports "not found" or "Harper rejected
117
+ the verb."
118
+
119
+ **This is not an existence signal.** By-id routes return the same 404 when
120
+ the id is absent and when the record exists but the caller may not see it
121
+ (fail-closed ownership, [flair#1264](https://github.com/tpsdev-ai/flair/issues/1264)).
122
+ A 403 would confirm the id and can name the owner; Flair refuses that.
123
+
124
+ **Also not this:** `401 unknown_agent` / `401 invalid_signature` (identity —
125
+ see above). Harper's catch-all 404 when the Flair app is not loaded yet
126
+ (`/Health` can already be 200). A `FLAIR_URL` with a path prefix.
127
+
128
+ Creates go to `POST /Memory/` (id in the body). Do not treat a by-id 404 as
129
+ a reason to paste admin credentials into the agent's environment.
130
+
131
+ Full adapter guide: [integrations.md — Hosted Flair auth](integrations.md#hosted-flair-auth--your-agent-got-a-404).
132
+
108
133
  ### "signing key ... could not be parsed as an Ed25519 private key"
109
134
 
110
135
  **Symptoms:** `flair doctor` reports, naming the file:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.48.0",
3
+ "version": "0.50.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",
@@ -59,7 +59,7 @@
59
59
  "access": "public"
60
60
  },
61
61
  "engines": {
62
- "node": ">=22"
62
+ "node": "^22.18.0 || >=24.0.0"
63
63
  },
64
64
  "dependencies": {
65
65
  "@harperfast/oauth": "2.5.0",
@@ -83,7 +83,8 @@
83
83
  "@opentelemetry/core": "^2.8.0",
84
84
  "uuid": "^11.1.1",
85
85
  "tar": "^7.5.22",
86
- "@tootallnate/once": "^2.0.1"
86
+ "@tootallnate/once": "^2.0.1",
87
+ "form-data": "^4.0.6"
87
88
  },
88
89
  "devDependencies": {
89
90
  "@playwright/test": "1.59.1",