@tpsdev-ai/flair 0.32.0 → 0.34.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.
@@ -109,7 +109,7 @@ Or wire it by hand — add a `SessionStart` hook to `~/.claude/settings.json`:
109
109
  "hooks": [
110
110
  {
111
111
  "type": "command",
112
- "command": "FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start"
112
+ "command": "sh -c 'out=$(FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start 2>/dev/null) && printf %s \"$out\" || true'"
113
113
  }
114
114
  ]
115
115
  }
@@ -125,6 +125,21 @@ command, so a hand-pinned hook reports `wired: false` there (while `flair
125
125
  doctor` still sees it). Pinning this line is therefore not yet supported —
126
126
  prefer `flair hook install`.
127
127
 
128
+ The `sh -c ... || true` wrapper is not decoration. The invocation resolves a
129
+ package binary through whatever Node runtime your shell exposes, and under a
130
+ Node version manager globally installed packages are per-runtime-version — so
131
+ a routine, unrelated runtime upgrade can orphan it. Without the wrapper the
132
+ hook then fails on *every* session, forever, with an error that names neither
133
+ Flair nor a remedy, and it keeps doing so after Flair itself is uninstalled.
134
+ The wrapper makes any failure to resolve or execute produce no output and exit
135
+ 0: ambient memory is a decoration on your session and must never be louder
136
+ than the thing it decorates. Success is unaffected — the hook's output is
137
+ passed through byte-for-byte.
138
+
139
+ If you already have the older, unwrapped command, `flair doctor` reports it and
140
+ `flair doctor --fix` rewrites it in place (same agent, same instance);
141
+ `flair hook status` shows the same under **On failure**.
142
+
128
143
  The hook reads Claude Code's SessionStart
129
144
  payload on stdin, calls Flair's `bootstrap` (soul + relevant memories +
130
145
  predicted context, scoped to your project by the session's working directory),
@@ -141,7 +156,14 @@ It honors the same env as the MCP server (`FLAIR_AGENT_ID`, `FLAIR_URL`,
141
156
  error, or a hung daemon (past the timeout) → the hook prints `{}` and exits 0.
142
157
  Claude Code treats that as "no context to add" and starts normally. The hook
143
158
  can never block or break session startup. The injected context is clamped to
144
- ≤10,000 characters to keep the session-start payload small.
159
+ ≤10,000 characters to keep the session-start payload small. And if the command
160
+ cannot resolve at all — so none of that code ever runs — the wrapper above
161
+ still yields no output and exit 0.
162
+
163
+ `flair doctor` verifies the second half by actually running the registered
164
+ command with `FLAIR_HOOK_PROBE=1`, which makes the hook print its inert output
165
+ and exit immediately: no bootstrap, no presence write, no network. Reaching it
166
+ is the whole answer.
145
167
 
146
168
  ### Gemini CLI
147
169
 
@@ -203,7 +225,7 @@ Eleven tools, kept deliberately small:
203
225
  | Tool | What it does |
204
226
  |---|---|
205
227
  | `memory_search` | Semantic search across your agent's memories |
206
- | `memory_store` | Save a memory with type, durability, tags. Auto-dedups near-duplicates |
228
+ | `memory_store` | Save a memory with type, durability, tags, visibility. Auto-dedups near-duplicates |
207
229
  | `memory_update` | Update an existing memory by ID — overwrite in place, or version it with `preserveHistory` |
208
230
  | `memory_get` | Fetch a specific memory by ID |
209
231
  | `memory_delete` | Remove a memory |
@@ -214,7 +236,9 @@ Eleven tools, kept deliberately small:
214
236
  | `flair_workspace_set` | Set your agent's current workspace state (ref/branch, phase, task) in the Office Space |
215
237
  | `flair_orgevent` | Publish an org-wide coordination event (claim/release/status) to the Office Space |
216
238
 
217
- 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 (open-within-org read; see [SECURITY.md](../SECURITY.md)). Mark a memory `visibility: private` to keep it owner-only.
239
+ 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)).
240
+
241
+ Which memories are non-private is decided at write time, and the default is not "shared". `memory_store` defaults `durability` to `standard`, and the server derives visibility from durability — `permanent`/`persistent` → `shared`, `standard`/`ephemeral` → `private` — so **a bare `memory_store` call writes an owner-only memory that no other agent can read.** Pass `visibility: "shared"` (or `"private"`, to be explicit) to say what you mean; the tool reports the visibility the write actually landed on so an agent can confirm it rather than assume.
218
242
 
219
243
  ---
220
244
 
@@ -26,7 +26,9 @@ export PATH="$HOME/.npm-global/bin:$PATH" # add this to ~/.zshrc or ~/.bashrc
26
26
  npm install -g @tpsdev-ai/flair
27
27
  ```
28
28
 
29
- One install gives you `flair`, `flair-mcp` and the client library.
29
+ One install gives you one command: `flair`.
30
+
31
+ The stdio adapter your MCP client talks to is a separate package, `@tpsdev-ai/flair-mcp`, and it is deliberately not installed globally — `flair init` wires each client to fetch it on demand with `npx -y @tpsdev-ai/flair-mcp@<version>`, so there is no second global package to keep in step. (The server also has its own `/mcp` endpoint built in, but it is off by default — it registers no route unless `FLAIR_MCP_OAUTH` and a public issuer are set — and no client setup in this guide uses it.) `@tpsdev-ai/flair-client` is a separate package you add to your own project when you want to call Flair from code.
30
32
 
31
33
  ## 2. Bootstrap Flair and register an agent
32
34
 
@@ -110,12 +112,35 @@ flair memory add --agent local "Harper v5 sandbox blocks node:module but process
110
112
  {
111
113
  "id": "local-1785277247486",
112
114
  "written": true,
113
- "deduplicated": false
115
+ "deduplicated": false,
116
+ "visibility": "private"
114
117
  }
115
118
  ```
116
119
 
117
120
  Flair embedded the text locally on write. No network calls.
118
121
 
122
+ ### Who can read it
123
+
124
+ `visibility: private` means **only `local` can read this memory** — no other agent on the instance can search it, fetch it by id, or receive it in a bootstrap.
125
+
126
+ You didn't ask for that, and it isn't a setting you have to remember. Flair derives the default from the memory's **durability**, because how long a memory is meant to last is a good proxy for who it was meant for:
127
+
128
+ | Durability | Default visibility |
129
+ |---|---|
130
+ | `permanent`, `persistent` | `shared` — a fact or decision worth keeping is worth the team being able to find |
131
+ | `standard`, `ephemeral` — including a bare write with no `--durability` | `private` — working context and scratch state belong to the agent that produced them |
132
+
133
+ So sharing is a deliberate act, and it takes one flag:
134
+
135
+ ```bash
136
+ flair memory add --agent local --visibility shared \
137
+ "Release tags are cut from main, never from a release branch"
138
+ ```
139
+
140
+ `--visibility` takes exactly `private` or `shared` — a value it doesn't recognise is an error, not a guess — and overrides the durability rule in both directions. The `visibility` field in the response is the value the memory actually landed on, so read it rather than assuming.
141
+
142
+ Once a memory is `shared`, **every** agent on this instance can read it, with no grant to set up. That is the shipped model: reads open within one instance, closed at the federation edge. Full picture in [SECURITY.md](../SECURITY.md).
143
+
119
144
  ## 5. Find it back by meaning
120
145
 
121
146
  ```bash
@@ -131,7 +156,7 @@ You searched for a concept, not the keywords. The line under each hit is its cre
131
156
 
132
157
  > The percentage is a **rank-fusion score, not a similarity**. It is normalized so the top result is always near 100%. Read it as ordering within these results, never as confidence that the match is good.
133
158
 
134
- Add `--explain` to see the ranking inputs, or `--limit`, `--tag`, `--since 7d` to narrow the search. `flair memory search` runs the same query but always prints raw JSON — use it when piping to a script.
159
+ Add `--explain` to see the ranking inputs per hit — the raw score, the composite score under `--scoring composite`, and the record's durability, age and usage count. When output is JSON (`--json`, or any time stdout is not a terminal) the same breakdown arrives as an `_explain` object on each hit, so scripts get it too. Use `--limit`, `--tag`, `--since 7d` to narrow the search. `flair memory search` runs the same query but always prints raw JSON — use it when piping to a script.
135
160
 
136
161
  ## 6. Give your agent context on boot
137
162
 
@@ -160,7 +185,7 @@ With the MCP server wired up — `flair init` does this automatically for every
160
185
  | You want to... | Go to |
161
186
  |----------------|-------|
162
187
  | Add more agents to the same instance | `flair agent add <id>` |
163
- | Keep a memory owner-only | `flair memory add --visibility private` — reads are otherwise open to every agent on the instance ([auth.md](auth.md)) |
188
+ | Share a memory with your other agents | `flair memory add --visibility shared` — a bare write lands `private`, see [step 4](#who-can-read-it); a shared one is readable by every agent on the instance, no grant needed ([auth.md](auth.md)) |
164
189
  | Import memories from agentic-stack / Mem0 / etc. | [bridges.md](bridges.md) |
165
190
  | Sync memories across machines | [federation.md](federation.md) |
166
191
  | Integrate with OpenClaw, Claude Code, Cursor | [README.md#integration](../README.md#integration) |
package/docs/the-team.md CHANGED
@@ -15,7 +15,7 @@ If you're trying to run your own multi-agent team using Flair as the memory laye
15
15
  | **Pulse** | EA / intel scanning / coordination | OpenClaw | Claude API | cloud VM |
16
16
  | **Nathan** | Founder / product owner / human-in-the-loop | (human) | (human) | wherever |
17
17
 
18
- Every agent has its own Ed25519 identity in Flair. They sign every memory write and every read. **Writes are isolated at the Flair API layer** — Sherlock can't accidentally (or maliciously) write into Pulse's memory, because the signature won't verify for anyone but Pulse. Reads are a different story: within one Flair instance, any verified agent can read any other agent's **non-private** memory — that's the shipped model (open-within-org read, no grant needed), not a gap. An agent keeps something genuinely sensitive owner-only by writing it with `visibility: private`. The hard access boundary is the **federation edge** (a separate Flair instance), not reads within one.
18
+ Every agent has its own Ed25519 identity in Flair. They sign every memory write and every read. **Writes are isolated at the Flair API layer** — Sherlock can't accidentally (or maliciously) write into Pulse's memory, because the signature won't verify for anyone but Pulse. Reads are a different story: within one Flair instance, any verified agent can read any other agent's **non-private** memory — that's the shipped model (open-within-org read, no grant needed), not a gap. Which memories are non-private is decided at write time: visibility defaults from durability, so `permanent`/`persistent` writes land `shared` and `standard`/`ephemeral` writes land `private`. A teammate's scratch context is therefore owner-only until someone shares it deliberately, and an agent keeps something genuinely sensitive owner-only regardless of durability by writing it with `visibility: private`. The hard access boundary is the **federation edge** (a separate Flair instance), not reads within one.
19
19
 
20
20
  ## How memory flows
21
21
 
@@ -41,10 +41,14 @@ Every agent has its own Ed25519 identity in Flair. They sign every memory write
41
41
 
42
42
  (every agent can read every other
43
43
  agent's non-private memories —
44
- `visibility: private` stays owner-only)
44
+ permanent/persistent land shared,
45
+ standard/ephemeral land private,
46
+ and private stays owner-only)
45
47
  ```
46
48
 
47
- No agent can write into another agent's memory — that's enforced server-side by signature verification, no exceptions. Reads are intentionally open within the org: when Flint commits a piece of strategy, any agent can find it on `memory_search` unless Flint marked it `private`. **By design** — the goal is relevance and findability across the team, not secrecy between roles. An agent that genuinely needs something to stay owner-only (a draft not ready for the team, a sensitive finding pre-disclosure) marks it `visibility: private`; everything else is fair game for any teammate to search.
49
+ No agent can write into another agent's memory — that's enforced server-side by signature verification, no exceptions. Reads are intentionally open within the org: when Flint commits a piece of strategy as a `permanent` or `persistent` memory, it lands `shared` and any agent can find it on `memory_search`. **By design** — the goal is relevance and findability across the team, not secrecy between roles.
50
+
51
+ The reverse also holds, and it is the part worth internalising: a `standard` or `ephemeral` write lands `private`, so an agent's day-to-day working context is *not* team-searchable by default. Commit something at `permanent`/`persistent` durability, or pass `visibility: shared`, when you mean the team to find it. An agent that needs something owner-only whatever its durability (a draft not ready for the team, a sensitive finding pre-disclosure) marks it `visibility: private` explicitly.
48
52
 
49
53
  When agents need to *coordinate* — a direct, targeted handoff rather than ambient searchable memory — they pass **explicit messages** through TPS mail (a separate signed delivery channel; see [tpsdev-ai/cli](https://github.com/tpsdev-ai/cli)). That's a different concern from memory visibility: TPS mail is for "I need you, specifically, to see this now"; Flair memory is the shared, searchable record everyone (except where `private`) can draw on later.
50
54
 
@@ -106,7 +110,7 @@ The MCP server (`@tpsdev-ai/flair-mcp`) is what makes this orchestrator-agnostic
106
110
 
107
111
  ## What we deliberately don't do
108
112
 
109
- - **No shared write identity.** Every memory is written and owned by exactly one agent's Ed25519 key — there's no merged "team" identity that can write on another agent's behalf. Reads are a separate story: within the org, any agent can search any other's non-private memory by default (see [SECURITY.md](../SECURITY.md)) — that's intentional, not a leak. TPS mail is still how agents route a message to a *specific* teammate; it's for targeted delivery, not for gating ambient visibility.
113
+ - **No shared write identity.** Every memory is written and owned by exactly one agent's Ed25519 key — there's no merged "team" identity that can write on another agent's behalf. Reads are a separate story: within the org, any agent can search any other's non-private memory by default (see [SECURITY.md](../SECURITY.md)) — that's intentional, not a leak. "Non-private" is set at write time from durability, not by a per-pair grant. TPS mail is still how agents route a message to a *specific* teammate; it's for targeted delivery, not for gating ambient visibility.
110
114
  - **No silent LLM-driven memory extraction.** Each agent decides what it remembers. No background "summarize and persist" on every turn — that's how memory drifts away from intent.
111
115
  - **No multiple agents on one identity.** "Anvil" and "Anvil-2" would be two separate agentIds with two separate keys. Same workload, different identities, separately-owned memories.
112
116
  - **No replay-safe-but-otherwise-unsigned reads.** Every Flair request is Ed25519-signed and verified, including reads. Even on a private network we don't trust the network.
@@ -104,6 +104,42 @@ date
104
104
 
105
105
  If using the MCP server, restart Claude Code after rotating keys.
106
106
 
107
+ ### "signing key ... could not be parsed as an Ed25519 private key"
108
+
109
+ **Symptoms:** `flair doctor` reports, naming the file:
110
+
111
+ ```
112
+ ⚠️ could not verify agent 'my-agent' registration — signing key
113
+ /home/you/.flair/keys/my-agent.key could not be parsed as an Ed25519 private key
114
+ (error:1E08010C:DECODER routines::unsupported)
115
+ ```
116
+
117
+ **This is not a connectivity problem.** The failure happens locally, before any
118
+ request is sent — the instance is untouched. Checking ports, firewalls or the
119
+ Flair URL will not help. (Before #1023 doctor reported exactly this as
120
+ `instance unreachable`, and separately suggested passing `--agent`; neither was
121
+ the cause.)
122
+
123
+ **Causes:** the bytes in that file are not a private key Flair can read. Most
124
+ often either the file is truncated or corrupt, or it is not an agent key at all
125
+ — `~/.flair/keys/<id>.key` is also where the federation keystore writes its own
126
+ **encrypted** key files, which are not loadable as plain Ed25519 seeds.
127
+
128
+ **Fix:** identify which it is before touching the file — the two have opposite
129
+ remedies, and the wrong one loses a key.
130
+
131
+ ```bash
132
+ # Is this id an agent, or a federation instance?
133
+ flair agent list
134
+ flair federation status
135
+
136
+ # If it IS an agent and the file is corrupt, re-register a fresh keypair:
137
+ flair agent rotate-key <agent-id>
138
+ ```
139
+
140
+ If the id does not appear as an agent, leave the file alone — it belongs to the
141
+ keystore, and doctor simply has no business signing with it.
142
+
107
143
  ### Port conflict
108
144
 
109
145
  **Symptoms:** `flair start` fails, "address already in use".
@@ -150,7 +186,7 @@ flair agent list
150
186
  **Possible causes:**
151
187
  1. **Hash-fallback embeddings:** Check `flair status` — if embeddings are in hash mode, semantic search won't work properly. Fix with `flair reembed`.
152
188
  2. **Content safety flags:** The memory might have been flagged. Search for it directly: `flair memory list --agent <id>`.
153
- 3. **`visibility: private`:** Reads are open within the org by default any agent can find any other agent's non-private memories. If the memory was written with `visibility: private`, only its author can find it; search as that agent instead.
189
+ 3. **`visibility: private`:** Only its author can find a `private` memory. This is the most common cause when one agent wrote the memory and another is searching for it, because **`private` is what a bare write lands on**: visibility defaults from durability (`permanent`/`persistent` → `shared`, `standard`/`ephemeral` → `private`), and a write with no `--durability` is `standard`. Check what the memory landed on with `flair memory list --agent <author> --json` (the table view doesn't show visibility, the JSON records do), then either search as its author, or rewrite it with `--visibility shared` so every agent on the instance can find it (no grant needed non-private reads are open within the instance).
154
190
  4. **Dedup threshold:** If the content is very similar to an existing memory, it may have been deduplicated. Check with `flair memory list`.
155
191
 
156
192
  ### High memory usage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.32.0",
3
+ "version": "0.34.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",