@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.
- package/README.md +64 -64
- package/SECURITY.md +7 -0
- package/config.yaml +34 -0
- package/dist/cli.js +659 -111
- package/dist/component-env.js +286 -0
- package/dist/deploy.js +190 -3
- package/dist/doctor-client.js +357 -7
- package/dist/hook-install.js +39 -9
- package/dist/lib/auth-resolve.js +85 -2
- package/dist/lib/launchd-management.js +328 -0
- package/dist/lib/mcp-enable.js +19 -0
- package/dist/resources/AdminInstance.js +20 -2
- package/dist/resources/Memory.js +24 -2
- package/dist/resources/OAuth.js +41 -25
- package/dist/resources/auth-middleware.js +26 -0
- package/dist/resources/dcr-gate.js +194 -0
- package/dist/resources/in-process-api.js +5 -1
- package/dist/resources/mcp-handler.js +91 -4
- package/dist/resources/mcp-oauth.js +89 -7
- package/dist/resources/mcp-tools.js +40 -0
- package/dist/resources/oauth-discovery.js +242 -0
- package/dist/resources/oauth-wellknown.js +111 -0
- package/dist/resources/rate-limit.js +400 -0
- package/docs/auth.md +122 -5
- package/docs/deploying-on-fabric.md +35 -2
- package/docs/deployment.md +1 -1
- package/docs/embedding-in-a-harper-app.md +6 -1
- package/docs/hosted-on-fabric.md +1 -1
- package/docs/mcp-clients.md +28 -4
- package/docs/quickstart.md +29 -4
- package/docs/the-team.md +8 -4
- package/docs/troubleshooting.md +37 -1
- package/package.json +1 -1
package/docs/mcp-clients.md
CHANGED
|
@@ -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)).
|
|
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
|
|
package/docs/quickstart.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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`:**
|
|
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.
|
|
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",
|