@tpsdev-ai/flair 0.31.1 → 0.33.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 CHANGED
@@ -6,43 +6,11 @@
6
6
 
7
7
  > **The identity and memory substrate for AI agents. Crypto-pinned. Federated. Self-hosted.**
8
8
 
9
- Every agent framework gives you chat history. None give you *identity*. Flair gives an agent three things that survive a restart and follow it between orchestrators:
10
-
11
- - **Identity** — an Ed25519 keypair. The agent signs every request. No shared secrets.
12
- - **Memory** — persistent knowledge with semantic search, embedded in-process. No API calls.
13
- - **Soul** — the personality, values and procedures that make it *that* agent.
14
-
15
- Self-hosted on [Harper](https://harper.fast) as a single process. No sidecars, no vector database, no embedding API.
16
-
17
- ```
18
- ┌──────────────────────────────────────────────────────────────────┐
19
- │ same agent, same memory, every harness │
20
- │ │
21
- │ Claude Code ─┐ │
22
- │ Cursor ─┤ │
23
- │ Codex CLI ─┼─[ flair-mcp ]─┐ │
24
- │ Gemini CLI ─┤ │ │
25
- │ Continue.dev ─┤ │ ┌──────────────────────┐ │
26
- │ Goose ─┘ ├─▶ │ Flair (self-hosted) │ │
27
- │ LangGraph ─[ langgraph-flair ]──│ Ed25519 / HNSW / │ │
28
- │ OpenClaw ─[ openclaw-flair ]──│ Soul + Memory │ │
29
- │ n8n ─[ n8n-nodes-flair ]──└──────────┬───────────┘ │
30
- │ Hermes ─[ hermes-flair ]─┘ │ federation │
31
- │ Pi agent ─[ pi-flair ]─┘ │ (hub/spoke) │
32
- │ ▼ │
33
- │ ┌──────────────────────┐ │
34
- │ │ Flair (Fabric hub) │ │
35
- │ └──────────────────────┘ │
36
- └──────────────────────────────────────────────────────────────────┘
37
- ```
38
-
39
- 11 harness surfaces today. Pick whichever you're shipping in; the memory layer doesn't care. **[Full integrations catalog →](docs/integrations.md)**
9
+ Every agent framework gives you chat history. None give you *identity*. Flair gives an agent three things that survive a restart and follow it between orchestrators: an **identity** it proves with an Ed25519 keypair, **memory** it searches by meaning rather than by keyword, and a **soul** — the personality, values and procedures that make it *that* agent.
40
10
 
41
11
  ## Quick start
42
12
 
43
- Needs **Node.js 22+** and a **user-writable npm global prefix**.
44
-
45
- > ⚠️ **Never `sudo npm install -g @tpsdev-ai/flair`.** A root-owned install can't write the embedding model into its own package directory, so semantic search silently degrades to keyword-only. `flair init` and `flair doctor` will warn you loudly. Use `nvm`, or point npm at your home directory: `npm config set prefix ~/.npm-global` and add `~/.npm-global/bin` to `PATH`.
13
+ Runs on a laptop, a VPS, or anywhere Node does. Needs **Node.js 22+**.
46
14
 
47
15
  ```bash
48
16
  # 1. Install the CLI (no sudo)
@@ -67,12 +35,31 @@ Step 4 finds the memory you never keyword-matched:
67
35
 
68
36
  That trailing figure is a rank score, normalized so the top hit is always near 100% — ordering, not confidence.
69
37
 
70
- `flair init` installs and starts Harper, creates the agent's Ed25519 keypair, verifies semantic search actually works, wires every MCP client it detects (Claude Code, Cursor, Codex CLI, Gemini CLI) to `npx -y @tpsdev-ai/flair-mcp`, and runs a smoke test. Restart your MCP client afterwards, then ask the agent *"what do you remember about me?"*
38
+ `flair init` installs and starts Harper, creates the agent's Ed25519 keypair, verifies semantic search actually works, wires every MCP client it detects (Claude Code, Cursor, Codex CLI, Gemini CLI), and runs a smoke test. Restart your MCP client afterwards, then ask the agent *"what do you remember about me?"*
71
39
 
72
40
  > **Pass `--agent`.** A bare `flair init` bootstraps the instance and stops there — no agent, no keypair, no MCP wiring.
73
41
 
74
42
  Full walkthrough with expected output at every step: **[docs/quickstart.md](docs/quickstart.md)**.
75
43
 
44
+ ### One install, one binary
45
+
46
+ `npm install -g @tpsdev-ai/flair` puts a single command on your `PATH`: `flair`. That is the whole install, and it needs a **user-writable npm global prefix** — which is why step 1 says *no sudo*. A root-owned install can't write the embedding model into its own package directory, so semantic search silently degrades to keyword-only. Use `nvm`, or point npm at your home directory — `npm config set prefix ~/.npm-global`, then add `~/.npm-global/bin` to `PATH`. `flair init` and `flair doctor` both check for this and say so loudly.
47
+
48
+ Two different things get called "MCP" here, and you get them differently:
49
+
50
+ - **The server has an MCP surface built in.** `/mcp` is a JSON-RPC endpoint exposing 12 curated tools, guarded by OAuth bearer tokens. It ships inside the package — and it is **off by default**: until you set `FLAIR_MCP_OAUTH` *and* a public issuer (`FLAIR_MCP_ISSUER`, falling back to `FLAIR_PUBLIC_URL`), no `/mcp` route is registered and the path returns 404. No documented client setup uses it today.
51
+ - **What your MCP client actually talks to is a separate package** — `@tpsdev-ai/flair-mcp`, a stdio adapter — and that one is deliberately not installed globally. `flair init` writes `npx -y @tpsdev-ai/flair-mcp@<version>` into each client's config, pinned to the CLI's own version, so the client fetches it on demand and there is no second global package to keep in step.
52
+
53
+ `@tpsdev-ai/flair-client` is likewise its own package: add it to a project when you want to call Flair from your own code ([JavaScript / TypeScript](#javascript--typescript)).
54
+
55
+ ### Where the agent's key lives
56
+
57
+ `flair init --agent mybot` writes the private key to `~/.flair/keys/mybot.key` (mode `0600`) and the public half beside it as `mybot.pub`. Only the **public** key is registered on the instance; the private key never leaves the machine. `--keys-dir` writes both somewhere else.
58
+
59
+ **Back that file up — it is the agent's identity, and there is one copy.** Memories are not encrypted with it, so losing it costs the identity, not the data: the agent can no longer sign, and every HTTP call it makes fails. Recovery is `flair agent rotate-key mybot`, which mints a new pair and re-registers the public half — it needs the admin password `flair init` wrote to `~/.flair/admin-pass`, so back that up too. Otherwise treat the key like an SSH key: one per agent per host, never copied between machines ([docs/secrets-and-keys.md](docs/secrets-and-keys.md)).
60
+
61
+ Keys are how an agent *outside* the process proves who it is. Code running inside the same Harper instance needs no key at all — see [Embedded in a Harper app](#embedded-in-a-harper-app-in-process).
62
+
76
63
  ### Useful flags
77
64
 
78
65
  ```bash
@@ -118,9 +105,35 @@ Write a memory, then find it by meaning. The same memory is visible to every har
118
105
 
119
106
  One Ed25519 identity, one memory store, three MCP-capable CLIs. A memory written from Claude Code is retrievable from Codex CLI and Gemini CLI a moment later. Identity and history aren't bound to one orchestrator's runtime.
120
107
 
108
+ ## One agent, every harness
109
+
110
+ ```
111
+ ┌──────────────────────────────────────────────────────────────────┐
112
+ │ same agent, same memory, every harness │
113
+ │ │
114
+ │ Claude Code ─┐ │
115
+ │ Cursor ─┤ │
116
+ │ Codex CLI ─┼─[ flair-mcp ]─┐ │
117
+ │ Gemini CLI ─┤ │ │
118
+ │ Continue.dev ─┤ │ ┌──────────────────────┐ │
119
+ │ Goose ─┘ ├─▶ │ Flair (self-hosted) │ │
120
+ │ LangGraph ─[ langgraph-flair ]──│ Ed25519 / HNSW / │ │
121
+ │ OpenClaw ─[ openclaw-flair ]──│ Soul + Memory │ │
122
+ │ n8n ─[ n8n-nodes-flair ]──└──────────┬───────────┘ │
123
+ │ Hermes ─[ hermes-flair ]─┘ │ federation │
124
+ │ Pi agent ─[ pi-flair ]─┘ │ (hub/spoke) │
125
+ │ ▼ │
126
+ │ ┌──────────────────────┐ │
127
+ │ │ Flair (Fabric hub) │ │
128
+ │ └──────────────────────┘ │
129
+ └──────────────────────────────────────────────────────────────────┘
130
+ ```
131
+
132
+ 11 harness surfaces today. Pick whichever you're shipping in; the memory layer doesn't care. **[Full integrations catalog →](docs/integrations.md)**
133
+
121
134
  ## How it works
122
135
 
123
- Flair is a native [Harper v5](https://harper.fast) application. Harper handles HTTP, persistence (RocksDB), and application logic in one process.
136
+ Flair is a native [Harper v5](https://harper.fast) application. Harper handles HTTP, persistence (RocksDB), and application logic in one process — self-hosted, with no sidecars, no vector database and no embedding API.
124
137
 
125
138
  ```
126
139
  Agent ──[Ed25519-signed request]──▶ Flair (Harper)
@@ -158,24 +171,22 @@ See **[DESIGN.md](DESIGN.md)** for the invariants behind the three primitives
158
171
  | **Web admin** | Server-rendered UI for principals, connectors, IdPs and instance config. No separate dashboard service. |
159
172
  | **Benchmarks** | [`flair-bench`](packages/flair-bench/README.md) scores candidate embedding models against the same recall corpus Flair's CI gates on. Runs standalone — no Flair install, no server. |
160
173
 
161
- Trust-graded recall reaches the authenticated HTTP API and the native `/mcp` tools today; CLI, `@tpsdev-ai/flair-client` and `flair-mcp` exposure is a follow-up. REM needs a configured generative backend (Ollama, OpenAI, Anthropic, …) — without one, `flair rem rapid` fails with `Reflection error: No generative backend configured`.
174
+ Trust-graded recall reaches the authenticated HTTP API and the built-in `/mcp` tools today — the latter being off by default, see [One install, one binary](#one-install-one-binary). CLI, `@tpsdev-ai/flair-client` and `flair-mcp` exposure is a follow-up. REM needs a configured generative backend (Ollama, OpenAI, Anthropic, …) — without one, `flair rem rapid` fails with `Reflection error: No generative backend configured`.
162
175
 
163
176
  ## How Flair compares
164
177
 
165
- | | Flair | Mem0 | Honcho | Letta (MemGPT) | Built-ins (OAI/Anthropic/Google) |
166
- |---|---|---|---|---|---|
167
- | **Identity model** | **Ed25519 per agent (crypto-pinned)** | tenant-isolation | per-user soft tenant | runtime-bound | account-scoped |
168
- | **Federation (peer-to-peer)** | **yes — hub/spoke validated** | no | no | no | no |
169
- | **Cross-orchestrator** | **11+ harnesses, same memory** | several | several | runtime-bound | vendor-locked |
170
- | **Soul / persistent character** | **first-class** | optional | persona-shaped | optional | no |
171
-
172
- Parity rows are omitted: Mem0, Honcho and Letta are all open-source, self-hostable, and do semantic search. Those are table stakes here.
178
+ Every product here does semantic recall over stored memories. These are the dimensions they actually differ on.
173
179
 
174
- The honest gaps if you need one of these specifically, use that tool:
180
+ | | Flair | Mem0 | Honcho | Letta (MemGPT) | [SageOx](https://sageox.ai) | Built-ins (OAI/Anthropic/Google) |
181
+ |---|---|---|---|---|---|---|
182
+ | **Where memories live** | infrastructure you run | self-host or Mem0 Cloud | self-host or hosted API | self-host or Letta Cloud | SageOx cloud | vendor cloud |
183
+ | **Memory is scoped to** | the agent, via an Ed25519 keypair | tenant / user | per-user tenant | the runtime | the team | the account |
184
+ | **Reaches other orchestrators** | 11 harnesses, incl. workflow and agent frameworks | several | several | Letta's runtime | 14+ coding agents and editors, via hooks, plugins and instruction files | no |
185
+ | **Sync between instances you run** | hub/spoke federation | no | no | no | one hosted service | one hosted service |
186
+ | **Captures in-person conversation** | no | no | no | no | yes — Ox Dot | no |
187
+ | **Per-agent persistent character** | first-class (Soul) | optional | persona-shaped | optional | team context, not per-agent | no |
175
188
 
176
- - Mem0's **cloud sync UX** is more polished, if you're happy with their hosting.
177
- - Honcho's **persona model** is more developed, if rich personality modeling is the priority.
178
- - Letta's **runtime integration** is tighter, if you're building on their agent loop.
189
+ **Where Flair loses.** [SageOx's Ox Dot](https://sageox.ai) records in-person meetings, standups and whiteboard sessions and pipes them into shared context; Flair has no ambient capture of anything that isn't already text in a tool. Mem0's hosted sync is more polished. Honcho's persona model is more developed. Letta's integration with its own agent loop is tighter than anything Flair offers. And there is no Flair-operated cloud — running it is your job.
179
190
 
180
191
  ## Integration
181
192
 
@@ -278,24 +289,38 @@ Sign `agentId:timestamp:nonce:METHOD:/path` with the agent's private key. Protoc
278
289
 
279
290
  ### Embedded in a Harper app (in-process)
280
291
 
281
- Flair *is* a Harper component. If your application already runs on Harper, load Flair into the same instance and call its resources directly a method call instead of an HTTP round trip.
292
+ The second front door, and the one to take if your code already runs on Harper.
293
+
294
+ Flair *is* a Harper component. Deploy it into the instance your application already runs in and call its resources directly — `await h.post({ agentId, content })` is a **method call**, not a network call. No second service to operate, no HTTP round trip, and no key to distribute: a caller in the same process is already inside the trust boundary and names the agent it is acting as, per call.
295
+
296
+ Adding it takes nothing away. The HTTP surface keeps serving MCP clients and remote agents exactly as before.
282
297
 
283
298
  ```javascript
284
299
  import { server } from "harper";
300
+ import { agentContext, collectionResource } from "@tpsdev-ai/flair/dist/resources/in-process.js";
285
301
 
286
- const Memory = server.resources.get("Memory").Resource; // the resource, not the table
287
- const h = new Memory(undefined, { request: { tpsAgent: "mybot" } });
302
+ // The RESOURCE auth, read-scoping, visibility, embedding.
303
+ // Registry keys carry no leading slash: get("Memory"), never get("/Memory").
304
+ const Memory = server.resources.get("Memory").Resource;
305
+
306
+ const h = await collectionResource(Memory, agentContext("mybot"));
288
307
  await h.post({ agentId: "mybot", content: "...", durability: "standard" });
289
308
  ```
290
309
 
291
- `databases.flair.Memory` is the **table** (raw storage); the exported `Memory` class is the **resource**, where auth, read-scoping, visibility and embedding live. A context-less call runs unfiltered. Full guide: **[docs/embedding-in-a-harper-app.md](docs/embedding-in-a-harper-app.md)**.
310
+ `databases.flair.Memory` is the **table** (raw storage); the exported `Memory` class is the **resource**, where auth, read-scoping, visibility and embedding live. `new Memory(...)` is not a substitute for `collectionResource` — a create needs a collection-bound instance only Harper can produce. Both helpers refuse a missing agent id rather than defaulting it, because a resource invoked with no context resolves to Flair's trusted `internal` verdict and runs unfiltered across every agent.
311
+
312
+ **This path needs no key at all.** Keys are how an agent *outside* the process proves who it is. Code running inside the same Harper instance asserts identity through the call context instead — `agentContext("mybot")` — which Flair reads and acts on with no signature, no `Agent`-table lookup and no registration. That is deliberate: same-process code could write the storage tables directly, so demanding a signature from it would be theatre. It is also why that id must come from your own server-side state and never from request data.
313
+
314
+ **→ [Embedding Flair in a Harper app](docs/embedding-in-a-harper-app.md)** — the whole in-process contract, measured against a real instance: resolving the resource, the table-vs-resource distinction that decides whether your memories are scoped at all, N agents in one process, and registering agents with no shell on the node.
292
315
 
293
316
  ### Auth across surfaces
294
317
 
295
- The default everywhere is **Ed25519 per-agent**: each agent holds its own key at `~/.flair/keys/<agent>.key` and signs every request. That gives write isolation — no agent can write as another — and identity-verified reads. It does *not* refuse cross-agent reads: within one instance, any verified agent can read any other agent's non-private memory by design. The hard boundary is the federation edge, not intra-instance reads. See [SECURITY.md](SECURITY.md).
318
+ For every caller that reaches Flair over the network the default is **Ed25519 per-agent**: each agent holds its own key at `~/.flair/keys/<agent>.key` and signs every request. That gives write isolation — no agent can write as another — and identity-verified reads. It does *not* refuse cross-agent reads: within one instance, any verified agent can read any other agent's non-private memory by design. The hard boundary is the federation edge, not intra-instance reads. See [SECURITY.md](SECURITY.md).
296
319
 
297
320
  One exception: the **`n8n-nodes-flair`** node authenticates with the Harper **admin password** (Basic auth), which bypasses agent scoping entirely — it can read other agents' `visibility: private` memories and write as anyone. That is acceptable only on a single-tenant, operator-controlled n8n with trusted workflow inputs. Otherwise prefer the Ed25519 path. Full breakdown in **[docs/auth.md](docs/auth.md#auth-across-surfaces-read-this-first)**.
298
321
 
322
+ In-process callers are a different model, not an exception to this one: they never sign, because identity is asserted through the call context rather than proven. Co-location *is* the grant — which is why Flair beside untrusted co-tenants on a shared instance is a different proposition to Flair inside your own app.
323
+
299
324
  ## Deployment
300
325
 
301
326
  ### Local (default)
@@ -331,6 +356,7 @@ Full model, threat analysis and recommendations in [SECURITY.md](SECURITY.md).
331
356
  - Ed25519 cryptographic identity — agents sign every request.
332
357
  - Writes are always agent-scoped. An agent can only write its own records.
333
358
  - Reads are open within the org: any agent can read any other agent's non-private memory, no grant required. `private` is the one owner-only exception ([DESIGN.md](DESIGN.md#access-model-open-within-the-org-closed-at-the-federation-edge)).
359
+ - Which memories are non-private is decided at write time, from durability: `permanent`/`persistent` default to `shared`, `standard`/`ephemeral` to `private`. A write that names neither is `standard`, so it lands `private`. Say what you mean with `--visibility shared|private` (CLI) or `visibility` (MCP / SDK); a write response names the visibility the record landed on, so it never has to be inferred.
334
360
  - The admin password is generated by `flair init` and written to `~/.flair/admin-pass` (mode 0600). The CLI prints the path, never the value. Prefer `--admin-pass-file` over `--admin-pass` so it stays out of `ps` and shell history.
335
361
  - Key rotation via `flair agent rotate-key`.
336
362
 
package/SECURITY.md CHANGED
@@ -69,6 +69,13 @@ centralized read-scope rule), not application logic:
69
69
  **Private memories are strictly owner-only** — a memory written with
70
70
  `visibility: private` is returned only to its author, never to another agent.
71
71
 
72
+ **A write that does not name a visibility gets one anyway.** The server derives
73
+ it from the memory's durability: `permanent`/`persistent` → `shared`,
74
+ `standard`/`ephemeral` → `private`. Durability itself defaults to `standard`,
75
+ so a write naming neither is stamped `private` and is owner-only. An explicit
76
+ `visibility` on the write always overrides the rule, and the response to a
77
+ create names the value the record actually landed on.
78
+
72
79
  ### Cross-Agent Access (within an org)
73
80
 
74
81
  Within an org, an agent reads every non-private memory directly — no grant
package/config.yaml CHANGED
@@ -6,6 +6,40 @@ rest: true
6
6
  # http:
7
7
  # port: 19926
8
8
 
9
+ # Harper does not read a component's `.env` implicitly — it only loads env
10
+ # files a component ASKS for, via this plugin. Without this block a `.env`
11
+ # sitting next to config.yaml is inert: the file is present and its values
12
+ # never reach `process.env`. That is exactly what a deployed instance hit —
13
+ # `FLAIR_PUBLIC_URL` was set in the deployed component's `.env` and OAuth
14
+ # discovery kept advertising a loopback issuer (flair#1005, #1000).
15
+ #
16
+ # MUST STAY FIRST. Config keys are iterated in file order by Harper's
17
+ # component loader, and each plugin's initial entry load is awaited before
18
+ # the next key is processed — so declaring this above `jsResource` is what
19
+ # guarantees `process.env` is populated before `dist/resources/*.js` are
20
+ # imported. Most consumers read `process.env` per request and would not care
21
+ # (resources/OAuth.ts, resources/AdminInstance.ts, resources/XAA.ts,
22
+ # resources/a2a-url.ts), but `resources/mcp-oauth.ts` decides at MODULE LOAD
23
+ # whether to mount `/mcp`; move this below `jsResource` and that decision is
24
+ # made against an env that has not been loaded yet.
25
+ #
26
+ # No `.env` is required, which is the case for essentially every local
27
+ # install: when the glob matches nothing the plugin never fires and emits
28
+ # nothing. Measured — a boot log with this block and no `.env` differs from
29
+ # one without the block only in the PID and in non-deterministic table-init
30
+ # ordering. (A MALFORMED declaration is loud, not silent: a pattern
31
+ # containing '..' produced both an `Ignoring invalid loadEnv files pattern`
32
+ # warning and a `Could not load component 'loadEnv'` error, which is the
33
+ # positive control for that silence.)
34
+ #
35
+ # Application variables only. Harper composes its OWN configuration before
36
+ # component `.env` files load, so Harper-level settings cannot be set this
37
+ # way; `HARPER_CONFIG` / `HARPER_DEFAULT_CONFIG` / `HARPER_SET_CONFIG` are
38
+ # refused at the injection point and warned about (harper#1513). Those
39
+ # belong in the process environment or harper-config.yaml.
40
+ loadEnv:
41
+ files: '.env'
42
+
9
43
  graphqlSchema:
10
44
  files: schemas/*.graphql
11
45