@tpsdev-ai/flair 0.49.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.
@@ -19,7 +19,7 @@ import { homedir } from "node:os";
19
19
  import { spawn } from "node:child_process";
20
20
  import { fileURLToPath } from "node:url";
21
21
  import { escapeXml } from "../lib/xml-escape.js";
22
- import { detectPlatform as detectPlatformFor, spawnReport, readTemplate as readTemplateFrom, renderTemplateWith, writeFileWithDir, interpretActiveResult, describeLoadFailure as describeLoadFailureFor, describeExitCode, resolveNodeBin, verifyFirstRun, STATUS_CHECK_TIMEOUT_MS, } from "../lib/scheduler-platform.js";
22
+ import { detectPlatform as detectPlatformFor, spawnReport, readTemplate as readTemplateFrom, renderTemplateWith, writeFileWithDir, interpretActiveResult, describeLoadFailure as describeLoadFailureFor, describeExitCode, resolveNodeBin, resolveFlairBin, formatFlairBinWarning, verifyFirstRun, probeUserLingerEnabled, STATUS_CHECK_TIMEOUT_MS, } from "../lib/scheduler-platform.js";
23
23
  // Re-exported so this module's public surface is unchanged by the extraction
24
24
  // into src/lib/scheduler-platform.ts (a second scheduler — `flair federation
25
25
  // sync enable` — needs the identical launchctl/systemctl interpretation, and
@@ -180,8 +180,8 @@ export async function queryActiveStateAsync(plat, timeoutMs = STATUS_CHECK_TIMEO
180
180
  * known pattern — the caller already prints the raw stderr, so the operator
181
181
  * still has something to go on.
182
182
  */
183
- export function describeLoadFailure(plat, loadResult) {
184
- return describeLoadFailureFor(plat, loadResult, "flair rem nightly enable");
183
+ export function describeLoadFailure(plat, loadResult, session) {
184
+ return describeLoadFailureFor(plat, loadResult, "flair rem nightly enable", session);
185
185
  }
186
186
  /**
187
187
  * Formats the `flair rem nightly enable` report from an `EnableResult`.
@@ -199,6 +199,15 @@ export function describeLoadFailure(plat, loadResult) {
199
199
  * happen once. A missing `loadResult`/`firstRun` (test-only skipLoad shape)
200
200
  * therefore withholds the headline too, instead of being treated as success.
201
201
  */
202
+ function appendFlairBinWarning(lines, r) {
203
+ if (r.flairBinCanonical !== false || !r.flairBin)
204
+ return;
205
+ const warning = formatFlairBinWarning(r.flairBin, r.flairBinPublic ?? null, "flair rem nightly enable");
206
+ if (warning.length === 0)
207
+ return;
208
+ lines.push("");
209
+ lines.push(...warning);
210
+ }
202
211
  export function formatEnableReport(r, input) {
203
212
  const { hour, minute, agentId, flairUrl } = input;
204
213
  const scheduleTime = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
@@ -216,11 +225,15 @@ export function formatEnableReport(r, input) {
216
225
  ];
217
226
  if (lr.stderr)
218
227
  lines.push(` stderr: ${lr.stderr.trim()}`);
219
- const remedy = describeLoadFailure(r.platform, lr);
228
+ const lingerEnabled = input.lingerEnabled !== undefined
229
+ ? input.lingerEnabled
230
+ : (r.platform === "linux" ? (input.probeLinger ?? probeUserLingerEnabled)() : undefined);
231
+ const remedy = describeLoadFailure(r.platform, lr, { lingerEnabled, env: input.env });
220
232
  lines.push("");
221
233
  lines.push(remedy ? ` ${remedy}` : ` Re-run the activation command above manually to see the full diagnostic.`);
222
234
  lines.push("");
223
235
  lines.push(` Nothing is scheduled until activation succeeds. Check anytime with: flair rem nightly status`);
236
+ appendFlairBinWarning(lines, r);
224
237
  return { lines, ok: false };
225
238
  }
226
239
  if (!r.firstRunVerified) {
@@ -272,6 +285,7 @@ export function formatEnableReport(r, input) {
272
285
  }
273
286
  lines.push("");
274
287
  lines.push(` Check anytime with: flair rem nightly status`);
288
+ appendFlairBinWarning(lines, r);
275
289
  return { lines, ok: false };
276
290
  }
277
291
  const lines = [
@@ -288,6 +302,7 @@ export function formatEnableReport(r, input) {
288
302
  lines.push(` First run: completed through the service manager, exit 0`);
289
303
  lines.push("");
290
304
  lines.push(`Disable with \`flair rem nightly disable\`.`);
305
+ appendFlairBinWarning(lines, r);
291
306
  return { lines, ok: true };
292
307
  }
293
308
  /**
@@ -329,7 +344,8 @@ export function formatStatusReport(s) {
329
344
  */
330
345
  export function enableScheduler(opts) {
331
346
  const plat = detectPlatform(opts.platformOverride);
332
- const flairBin = opts.flairBin ?? process.argv[1] ?? "flair";
347
+ const resolvedFlair = resolveFlairBin(opts.flairBin);
348
+ const flairBin = resolvedFlair.path;
333
349
  const nodeBin = resolveNodeBin(opts.nodeBin);
334
350
  const shimPath = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
335
351
  const templateRoot = opts.templateRootOverride ?? defaultTemplateRoot();
@@ -383,6 +399,7 @@ export function enableScheduler(opts) {
383
399
  return {
384
400
  platform: plat, shimPath, schedulerPath: plistPath, loadCommand, loadResult,
385
401
  firstRunVerified: firstRun?.verified === true, firstRun,
402
+ flairBin, flairBinCanonical: resolvedFlair.canonical, flairBinPublic: resolvedFlair.publicBin,
386
403
  };
387
404
  }
388
405
  // Linux: systemd user units.
@@ -408,6 +425,7 @@ export function enableScheduler(opts) {
408
425
  return {
409
426
  platform: plat, shimPath, schedulerPath: timerPath, loadCommand, loadResult,
410
427
  firstRunVerified: firstRun?.verified === true, firstRun,
428
+ flairBin, flairBinCanonical: resolvedFlair.canonical, flairBinPublic: resolvedFlair.publicBin,
411
429
  };
412
430
  }
413
431
  /**
package/docs/auth.md CHANGED
@@ -39,6 +39,11 @@ flair agent add myagent
39
39
 
40
40
  This is the default and recommended auth for single-instance deployments.
41
41
 
42
+ Adapter or hosted-Flair 404? Identity is three things (keyfile + agent id +
43
+ the `Agent` row on **that** instance). A by-id 404 is fail-closed ownership,
44
+ never an existence signal. The adapter write-up is
45
+ [integrations.md — Hosted Flair auth](integrations.md#hosted-flair-auth--your-agent-got-a-404).
46
+
42
47
  ## Deployment shapes: personal vs org
43
48
 
44
49
  Flair has no `mode`/`shape` config setting — the shape you get is emergent from *how you provision principals*, not something you declare:
@@ -4,7 +4,7 @@ Give DeepSeek Harness (DSH) sessions persistent, portable memory — no plugin c
4
4
 
5
5
  > **Verified against DSH as of 2026-08-20** (`deepseek-ai/deepseek-harness`, branch `master`). DSH is a developer preview and its own README promises compatibility-breaking changes. If wiring fails after a DSH upgrade, re-check the config field names against [their MCP client README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/mcp/mcp-client/README.md) before suspecting Flair.
6
6
 
7
- The same eleven tools every other MCP client gets ([full table in mcp-clients.md](mcp-clients.md#what-the-mcp-server-exposes)) appear to the model under DSH's server-qualified names: `mcp__flair__memory_search`, `mcp__flair__memory_store`, `mcp__flair__bootstrap`, and so on — the same `mcp__<server>__<tool>` convention Claude Code uses.
7
+ The same twelve tools every other MCP client gets ([full table in mcp-clients.md](mcp-clients.md#what-the-mcp-server-exposes)) appear to the model under DSH's server-qualified names: `mcp__flair__memory_search`, `mcp__flair__memory_store`, `mcp__flair__bootstrap`, and so on — the same `mcp__<server>__<tool>` convention Claude Code uses.
8
8
 
9
9
  Two caveats up front, both structural to DSH's bridge (details below):
10
10
 
@@ -138,6 +138,8 @@ flair agent add mybot --target "$FLAIR_URL" --ops-target <ops-url>
138
138
 
139
139
  Auth is the same protocol as standalone: Ed25519 signature of `agentId:timestamp:nonce:METHOD:/path`, 30-second replay window, nonce deduplication. The difference is purely the transport — HTTPS instead of localhost HTTP.
140
140
 
141
+ An adapter that just got a 404 is almost never "Harper wants a different verb." Check the three identity pieces (keyfile, agent id, `Agent` row on **this** instance) and treat by-id 404 as fail-closed ownership, not an existence signal: [integrations.md — Hosted Flair auth](integrations.md#hosted-flair-auth--your-agent-got-a-404).
142
+
141
143
  See [secrets-and-keys.md](secrets-and-keys.md) for the full threat model.
142
144
 
143
145
  ---
@@ -23,6 +23,8 @@ Where Flair already runs. Each integration shown here is a working surface — t
23
23
  | **n8n** | [`n8n-nodes-flair`](#n8n) | FlairApi credential | Three nodes (chat memory, search, store) |
24
24
  | **Hermes Agent** | [`hermes-flair`](#hermes-agent) | Ed25519 | Python `MemoryProvider` |
25
25
  | **Pi agent** | [`pi-flair`](#pi-agent) | Ed25519 | Native pi extension (pi has no MCP support); `flair init --client pi` wires it, `flair doctor` checks it |
26
+ | **Google ADK** (Python) | [`adk-flair`](../packages/adk-flair/README.md) | Ed25519 | `BaseMemoryService`; see [hosted auth](#hosted-flair-auth--your-agent-got-a-404) if you just got a 404 |
27
+ | **Google ADK** (JS/TS) | [`@tpsdev-ai/adk-flair`](../packages/adk-flair-js/README.md) | Ed25519 | Same identity model as the Python package |
26
28
 
27
29
  Don't see your harness? If it speaks **MCP** — Flair already works with `flair-mcp`. If it has a **custom memory protocol** like LangGraph's `BaseStore` or CrewAI's `RAGStorage`, an adapter is a ~200-line package; [open an issue](https://github.com/tpsdev-ai/flair/issues) or [send a PR](https://github.com/tpsdev-ai/flair).
28
30
 
@@ -32,6 +34,56 @@ Don't see your harness? If it speaks **MCP** — Flair already works with `flair
32
34
 
33
35
  ---
34
36
 
37
+ ## Hosted Flair auth — your agent got a 404
38
+
39
+ Written for the person whose adapter just got a 404 against a hosted Flair (Harper Fabric or any non-localhost URL). Laptop `flair init` on `127.0.0.1:19926` is a different machine from the one signing the request.
40
+
41
+ The protocol lives in [auth.md](auth.md). Key lifecycle lives in [secrets-and-keys.md](secrets-and-keys.md). Fabric registration (including the ops port trap) lives in [quickstart-fabric.md](quickstart-fabric.md). This section is only the identity check.
42
+
43
+ ### Identity is three things that must match
44
+
45
+ Ed25519 agent auth is not a password. The server accepts a request only when all three line up:
46
+
47
+ 1. **Agent id** — `FLAIR_AGENT_ID`. This is the string inside the signature.
48
+ 2. **Keyfile on the machine that signs** — `FLAIR_KEYFILE` (adk-flair / adk-flair-js) or `FLAIR_KEY_PATH` (flair-mcp, Hermes, pi). The private key never leaves that host. `flair agent add` writes `~/.flair/keys/<id>.key`.
49
+ 3. **Server-side `Agent` row on the instance at `FLAIR_URL`** whose `publicKey` matches that keyfile. Registration is per instance. A key minted against localhost is not registered on Fabric until you run `flair agent add <id> --target` at that URL.
50
+
51
+ The signed payload is `agentId:timestamp:nonce:METHOD:/path` (30-second replay window).
52
+
53
+ Do not "fix" a 401 or 404 by pasting the Harper admin password into the agent's standing environment. Admin Basic auth is for registration, once. The first signed call against an unregistered id is **401 `unknown_agent`** — that is fail-closed working as designed.
54
+
55
+ ### The three failure shapes
56
+
57
+ | Shape | What is true | What you see | What to do |
58
+ |---|---|---|---|
59
+ | **Record missing** | No `Agent` row for this id on **this** instance | `401 {"error":"unknown_agent"}` on every signed route | Register against the hosted URL: `flair agent add <id> --target "$FLAIR_URL" --ops-target <ops-url> --admin-pass-file <path>`. Fabric ops is not `data-port − 1` — see [quickstart-fabric.md](quickstart-fabric.md). |
60
+ | **Key mismatch** | The id exists; the public key on the server is not the one in your keyfile | `401 {"error":"invalid_signature"}` | Same id, wrong key — copied from another host, rotated on one side only, or the env pointing at a different agent's file. Point the env at the key that matches **this** instance, or re-seed the hosted `Agent` row from the key on this machine: `flair agent add <id> --target "$FLAIR_URL" --ops-target <ops-url> --admin-pass-file <path>` (reuses the local keyfile). `flair agent rotate-key` is localhost-only. Restart the adapter so it reloads the key. |
61
+ | **Config wrong** | Identity may be fine; you are not talking to the Flair you think | Timeouts, connection errors, or **404 from Harper's catch-all** | `FLAIR_URL` must be the origin the **signing process** can open (cloud-agent localhost is the VM, not your laptop). adk-flair also needs `FLAIR_ALLOW_REMOTE_URL=1` and a raised `FLAIR_HTTP_TIMEOUT` (defaults are localhost fail-fast). A `FLAIR_URL` with a path prefix sends every request to a route that does not exist. `/Health` can be 200 while `/Memory` is still 404 if the Flair app is not loaded yet. |
62
+
63
+ Clock skew is a fourth, rarer 401: `timestamp_out_of_window`.
64
+
65
+ `flair agent list` talks to **localhost**. It cannot tell you whether the hosted instance has your Agent row. The discriminator is the 401 body on a signed request.
66
+
67
+ ### 404 on by-id routes is not an existence signal
68
+
69
+ `GET /Memory/{id}`, `PUT /Memory/{id}`, and the adapter/MCP wrappers (`memory_get`, a by-id update) return **404** both when the id is absent **and** when the record exists but your principal may not see it (another agent's `private` memory, or outside your read scope). Same status, same body shape. That is fail-closed ownership ([flair#1264](https://github.com/tpsdev-ai/flair/issues/1264)) — a 403 would confirm the id exists and can name the owner.
70
+
71
+ **Do not treat that 404 as "the record is missing, so create it" or "Harper rejected the verb."** Creates go to `POST /Memory/` (id in the body). A by-id 404 after a write usually means you are not the owner the server thinks you are — go back to the three shapes above — not that you should switch PUT for POST.
72
+
73
+ A verified agent that is not allowed the row gets 404, never 403. Anonymous by-id reads are denied at the gate.
74
+
75
+ ### Per-adapter env
76
+
77
+ | Adapter | URL | Agent id | Keyfile | Hosted extras |
78
+ |---|---|---|---|---|
79
+ | **adk-flair** / **adk-flair-js** | `FLAIR_URL` | `FLAIR_AGENT_ID` | `FLAIR_KEYFILE` | `FLAIR_ALLOW_REMOTE_URL=1`, `FLAIR_HTTP_TIMEOUT` — [adk-flair README](../packages/adk-flair/README.md#hosted-flair) |
80
+ | **flair-mcp** / Cursor plugin | `FLAIR_URL` | `FLAIR_AGENT_ID` | `FLAIR_KEY_PATH` (optional; auto-resolved) | Key must be on the **npx host** |
81
+ | **Hermes / pi / LangGraph** | `FLAIR_URL` | `FLAIR_AGENT_ID` | `FLAIR_KEY_PATH` or client `keyPath` | Same Ed25519 model |
82
+
83
+ n8n still uses Harper admin Basic auth — it is not this path. See [n8n.md](n8n.md#security).
84
+
85
+ ---
86
+
35
87
  ## Claude Code, Cursor, Codex, Gemini CLI, Continue.dev, Goose — via `flair-mcp`
36
88
 
37
89
  [`@tpsdev-ai/flair-mcp`](https://www.npmjs.com/package/@tpsdev-ai/flair-mcp) is a [Model Context Protocol](https://modelcontextprotocol.io/) server that exposes Flair as a memory tool to any MCP-speaking client. One server, every MCP client.
@@ -209,7 +261,6 @@ If it has a custom memory protocol, the adapter pattern is small (~200 lines). L
209
261
  - CrewAI (Python `BaseRAGStorage` protocol)
210
262
  - AG2 / AutoGen (Python)
211
263
  - Mastra (TS, denser thread model)
212
- - ADK (Google, Python + TS)
213
264
 
214
265
  [Open an issue](https://github.com/tpsdev-ai/flair/issues) describing the harness and we'll triage. PRs welcome — see [`packages/langgraph-flair`](../packages/langgraph-flair) as the smallest-shape reference.
215
266
 
@@ -219,6 +270,7 @@ If it has a custom memory protocol, the adapter pattern is small (~200 lines). L
219
270
 
220
271
  - [Quickstart](quickstart.md) — `flair init` to working memory on a laptop
221
272
  - [Fabric Quickstart](quickstart-fabric.md) — `flair deploy` to a reachable Harper Fabric URL
273
+ - [Hosted Flair auth](#hosted-flair-auth--your-agent-got-a-404) — Ed25519 identity, the three 401/404 shapes, why a by-id 404 is not an existence signal
222
274
  - [Embedding in a Harper app](embedding-in-a-harper-app.md) — run Flair as a component of your own Harper instance and call it in-process
223
275
  - [Memory bridges](bridges.md) — import/export Flair ↔ Mem0, ChatGPT, claude-project, markdown, agentic-stack (five bridges shipped)
224
276
  - [Federation](federation.md) — pair instances peer-to-peer for cross-machine sync
@@ -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
 
@@ -298,7 +350,7 @@ Future MCP-capable agent CLIs (and there are more landing every month) will work
298
350
 
299
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.
300
352
 
301
- **"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).
302
354
 
303
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.
304
356
 
@@ -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
 
@@ -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.49.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",