@tpsdev-ai/flair 0.25.4 → 0.27.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 +2 -0
- package/dist/cli.js +998 -50
- package/dist/doctor-client.js +47 -0
- package/dist/rem/runner.js +60 -3
- package/dist/resources/MemoryDedupStats.js +161 -0
- package/dist/resources/dedup-cluster.js +145 -0
- package/dist/resources/health.js +33 -1
- package/dist/resources/mcp-oauth.js +4 -2
- package/docs/assets/flair-cross-orchestrator.cast +33 -0
- package/docs/assets/flair-cross-orchestrator.gif +0 -0
- package/docs/assets/flair-demo.cast +18 -0
- package/docs/assets/flair-demo.gif +0 -0
- package/docs/auth.md +178 -0
- package/docs/bridges.md +291 -0
- package/docs/claude-code.md +202 -0
- package/docs/deployment.md +204 -0
- package/docs/entity-vocabulary.md +109 -0
- package/docs/federation.md +179 -0
- package/docs/integrations.md +197 -0
- package/docs/mcp-clients.md +240 -0
- package/docs/n8n-management.md +142 -0
- package/docs/n8n.md +122 -0
- package/docs/notes/adk-spike-findings-2026-05-05.md +331 -0
- package/docs/notes/mcp-agent-auth-consumer.md +229 -0
- package/docs/notes/mcp-oauth-model2.md +132 -0
- package/docs/notes/rem-ux.md +39 -0
- package/docs/openclaw.md +131 -0
- package/docs/quickstart.md +153 -0
- package/docs/releasing.md +173 -0
- package/docs/rem.md +60 -0
- package/docs/rerank-provisioning.md +67 -0
- package/docs/secrets-and-keys.md +173 -0
- package/docs/spoke-bringup.md +303 -0
- package/docs/supply-chain-policy.md +156 -0
- package/docs/system-requirements.md +42 -0
- package/docs/the-team.md +129 -0
- package/docs/troubleshooting.md +187 -0
- package/docs/upgrade.md +416 -0
- package/package.json +2 -1
package/docs/auth.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Authentication & Authorization
|
|
2
|
+
|
|
3
|
+
Flair supports three authentication methods, from simplest to most enterprise-ready.
|
|
4
|
+
|
|
5
|
+
## Auth across surfaces (read this first)
|
|
6
|
+
|
|
7
|
+
Different surfaces authenticate differently. The model in one place:
|
|
8
|
+
|
|
9
|
+
| Surface | Auth | Scope | Notes |
|
|
10
|
+
|---------|------|-------|-------|
|
|
11
|
+
| **CLI / SDK clients** (`flair`, `flair-client`) | **Ed25519 per-agent** | Own writes; org-wide non-private reads | Default, recommended. Signs every request; an agent can never write as another, and reads are scoped to its own memories (any visibility) plus every other agent's **non-private** memories on the instance. |
|
|
12
|
+
| **MCP server** (`@tpsdev-ai/flair-mcp`) | **Ed25519 per-agent** | Own writes; org-wide non-private reads | Same per-agent identity as the CLI — key auto-resolved from `~/.flair/keys/<agent>.key`. |
|
|
13
|
+
| **OpenClaw / pi / Hermes plugins** | **Ed25519 per-agent** | Own writes; org-wide non-private reads | Same secure path; auto-detect agent identity. |
|
|
14
|
+
| **`n8n-nodes-flair`** | **Harper admin-password Basic auth** | ⚠️ **Whole instance, read + write, including `private`** | The admin credential bypasses agent scoping entirely — every workflow gets read/write on the *entire* memory store, including other agents' `private`-marked memories, not just the org-wide non-private pool an Ed25519 identity would see. |
|
|
15
|
+
|
|
16
|
+
**The default, secure path is Ed25519 per-agent** (see below): each agent holds its own key and signs every request. That guarantees **write isolation** — no agent can write as another — and identity-verified reads. It does **not** mean cross-agent reads are refused: within one Flair instance (one org), any verified agent can read any other agent's memory unless that memory is explicitly marked `visibility: private` (owner-only). The hard access boundary is the **federation edge** (a separate Flair instance / org), not reads within an instance. See [SECURITY.md](../SECURITY.md) for the full model. Use Ed25519 per-agent everywhere you can regardless — it's still what makes writes and identity trustworthy.
|
|
17
|
+
|
|
18
|
+
### Known limitation — n8n uses admin-password Basic auth
|
|
19
|
+
|
|
20
|
+
The `n8n-nodes-flair` community node authenticates with the Harper **admin password** (Basic auth), which bypasses agent scoping entirely — not just the org-wide non-private reads an Ed25519 identity already gets. Concretely, an n8n workflow using the admin credential can write memories under *any* agent's identity (no per-agent write isolation) and can read *every* memory including ones marked `visibility: private` (which stay owner-only under normal Ed25519 auth). This is acceptable only when **all** of the following hold:
|
|
21
|
+
|
|
22
|
+
- The n8n instance is single-tenant and operator-controlled.
|
|
23
|
+
- Workflow inputs are trusted (your own CRM, your own webhook source).
|
|
24
|
+
- Write-forgery and full read access (including `private` memories) are acceptable for the use case.
|
|
25
|
+
|
|
26
|
+
If any of those don't hold, use Flair's CLI / SDK clients (which support per-agent Ed25519 today) and wait for the n8n credential to gain Ed25519 per-agent auth (planned). Full guidance in [docs/n8n.md](n8n.md#security).
|
|
27
|
+
|
|
28
|
+
## Ed25519 Agent Auth (Default)
|
|
29
|
+
|
|
30
|
+
Every agent has an Ed25519 key pair. Requests are signed with `agentId:timestamp:nonce:METHOD:/path` and verified against the agent's registered public key. 30-second replay window with nonce deduplication.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Register an agent
|
|
34
|
+
flair agent add myagent
|
|
35
|
+
|
|
36
|
+
# The key is stored at ~/.flair/keys/myagent.key
|
|
37
|
+
# Requests are signed automatically by flair-client and the MCP server
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This is the default and recommended auth for single-instance deployments.
|
|
41
|
+
|
|
42
|
+
## Deployment shapes: personal vs org
|
|
43
|
+
|
|
44
|
+
Flair has no `mode`/`shape` config setting — the shape you get is emergent from *how you provision principals*, not something you declare:
|
|
45
|
+
|
|
46
|
+
- **Personal (the default).** `flair init` mints ONE agent identity and wires that same `FLAIR_AGENT_ID` into every MCP client it configures (Claude Code, Codex, Gemini, Cursor). One human driving several AI clients ends up with one canonical principal and one Ed25519 keypair — all clients share ownership of the same memory. This is intentional, not a limitation: all clients see each other's private rows (one human's memory, one view), a fact re-asserted from two different clients dedups to one memory, and usage counting treats the principal as one contributor.
|
|
47
|
+
- **Org (multiple real agents).** `flair agent add <id>` mints a distinct principal — its own keypair, its own ownership boundary — for each real agent that should be a separate identity. Wire each principal's own `FLAIR_AGENT_ID` into its own client(s).
|
|
48
|
+
|
|
49
|
+
Nothing in flair validates which shape you're in; a personal install that later grows into an org just runs `flair agent add` for the new distinct identities it needs.
|
|
50
|
+
|
|
51
|
+
### Authorship: which client wrote a row
|
|
52
|
+
|
|
53
|
+
The personal shape's one shared principal means `agentId` alone can't answer "was this written by Claude Code or Codex?" — every write it receives has the same owner. `flair init`'s per-client wiring closes that gap by setting an additional env var per client: `FLAIR_CLIENT` (`"claude-code"` / `"codex"` / `"gemini"` / `"cursor"`). `flair-client` and `flair-mcp` forward it on memory writes as `claimedClient`, and the server folds it into the write's `provenance` blob as `claimed.client` — self-reported, unverified metadata recording *which tool* authored a row, alongside the existing `claimed.model`.
|
|
54
|
+
|
|
55
|
+
This is deliberately in `claimed`, never `verified`: it carries **zero authority**. It is never read for access control, read-scope, attribution weighting, or dedup decisions — it exists purely for audit/analysis. Absent on any install that predates this field (no backfill, no migration) and absent whenever a client doesn't set `FLAIR_CLIENT`.
|
|
56
|
+
|
|
57
|
+
The native `/mcp` OAuth surface (see below) doesn't need any client-side wiring at all: the handler stamps `claimed.client` directly from the OAuth token's verified `client_id` claim — the server-generated `flair_cl_...` machine id assigned at Dynamic Client Registration, **not** the user-supplied `client_name` (which the registering client fully controls and could set to anything). Using `client_id` keeps the stamp a stable, server-verified label even though it still only carries `claimed`-level (unverified-by-content) authority.
|
|
58
|
+
|
|
59
|
+
## OAuth 2.1
|
|
60
|
+
|
|
61
|
+
Flair includes a built-in OAuth 2.1 authorization server for client integrations (e.g., Claude connecting to Flair as an MCP server).
|
|
62
|
+
|
|
63
|
+
### Dynamic Client Registration
|
|
64
|
+
|
|
65
|
+
Clients register automatically on first connection:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
POST /OAuthRegister
|
|
69
|
+
Content-Type: application/json
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
"client_name": "Claude Desktop",
|
|
73
|
+
"redirect_uris": ["http://localhost:3000/callback"],
|
|
74
|
+
"grant_types": ["authorization_code"],
|
|
75
|
+
"response_types": ["code"],
|
|
76
|
+
"token_endpoint_auth_method": "none"
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Returns `client_id` and `client_secret` (if applicable).
|
|
81
|
+
|
|
82
|
+
### Authorization Code Flow with PKCE
|
|
83
|
+
|
|
84
|
+
Standard OAuth 2.1 authorization code flow:
|
|
85
|
+
|
|
86
|
+
1. Client generates PKCE `code_verifier` and `code_challenge`
|
|
87
|
+
2. Client redirects user to `GET /OAuthAuthorize?client_id=...&code_challenge=...&redirect_uri=...`
|
|
88
|
+
3. User approves (or auto-approves for trusted clients)
|
|
89
|
+
4. Flair redirects back with authorization code
|
|
90
|
+
5. Client exchanges code for access token at `POST /OAuthToken`
|
|
91
|
+
|
|
92
|
+
### Endpoints
|
|
93
|
+
|
|
94
|
+
| Endpoint | Method | Description |
|
|
95
|
+
|----------|--------|-------------|
|
|
96
|
+
| `/OAuthRegister` | POST | Dynamic client registration |
|
|
97
|
+
| `/OAuthAuthorize` | GET/POST | Authorization endpoint |
|
|
98
|
+
| `/OAuthToken` | POST | Token endpoint |
|
|
99
|
+
| `/.well-known/oauth-authorization-server` | GET | Server metadata |
|
|
100
|
+
|
|
101
|
+
## XAA (Enterprise-Managed Authorization)
|
|
102
|
+
|
|
103
|
+
For organizations using an Identity Provider (IdP), XAA lets the IdP control who accesses Flair and with what scopes.
|
|
104
|
+
|
|
105
|
+
### How It Works
|
|
106
|
+
|
|
107
|
+
1. User authenticates with the organization's IdP (Google, Azure AD, Okta)
|
|
108
|
+
2. IdP issues an ID token (JWT) with user identity and group claims
|
|
109
|
+
3. Client sends the ID token to Flair's token endpoint using the `jwt-bearer` grant type
|
|
110
|
+
4. Flair validates the JWT signature, checks issuer/domain, maps to a Principal, and issues a scoped access token
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
POST /OAuthToken
|
|
114
|
+
Content-Type: application/x-www-form-urlencoded
|
|
115
|
+
|
|
116
|
+
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
|
|
117
|
+
&assertion=<id-token-from-idp>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### IdP Configuration
|
|
121
|
+
|
|
122
|
+
Register an IdP with the CLI:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
flair idp add \
|
|
126
|
+
--name "Google Workspace" \
|
|
127
|
+
--issuer "https://accounts.google.com" \
|
|
128
|
+
--jwks-uri "https://www.googleapis.com/oauth2/v3/certs" \
|
|
129
|
+
--required-domain "yourcompany.com"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Supported IdPs
|
|
133
|
+
|
|
134
|
+
| IdP | Issuer | Domain Claim |
|
|
135
|
+
|-----|--------|-------------|
|
|
136
|
+
| Google Workspace | `https://accounts.google.com` | `hd` (hosted domain) |
|
|
137
|
+
| Azure AD (Entra) | `https://login.microsoftonline.com/{tenant}/v2.0` | `tid` (tenant ID) |
|
|
138
|
+
| Okta / Auth0 | `https://{org}.okta.com` | Issuer-scoped |
|
|
139
|
+
|
|
140
|
+
### Scopes
|
|
141
|
+
|
|
142
|
+
| Scope | Description |
|
|
143
|
+
|-------|-------------|
|
|
144
|
+
| `memory:read` | Read memories and search |
|
|
145
|
+
| `memory:write` | Write and delete memories |
|
|
146
|
+
| `memory:admin` | Memory maintenance operations |
|
|
147
|
+
| `principal:read` | View principal list |
|
|
148
|
+
| `principal:admin` | Create/modify/disable principals |
|
|
149
|
+
| `connector:admin` | Manage OAuth clients and IdPs |
|
|
150
|
+
|
|
151
|
+
### JIT Provisioning
|
|
152
|
+
|
|
153
|
+
First-time IdP users are automatically created as `unverified` principals. An admin can promote them to `verified` or `admin` via:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
flair principal promote <principal-id>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### CLI Reference
|
|
160
|
+
|
|
161
|
+
| Command | Description |
|
|
162
|
+
|---------|-------------|
|
|
163
|
+
| `flair idp add` | Register an IdP |
|
|
164
|
+
| `flair idp list` | List configured IdPs |
|
|
165
|
+
| `flair idp remove <id>` | Remove an IdP |
|
|
166
|
+
| `flair idp test <id>` | Test IdP connectivity |
|
|
167
|
+
|
|
168
|
+
## Web Admin
|
|
169
|
+
|
|
170
|
+
The web admin at `/AdminDashboard` provides a UI for managing:
|
|
171
|
+
|
|
172
|
+
- **Principals:** view, promote, disable agents and users
|
|
173
|
+
- **Connectors:** OAuth clients and active sessions
|
|
174
|
+
- **IdP:** enterprise identity provider configuration
|
|
175
|
+
- **Memory:** browse and search stored memories
|
|
176
|
+
- **Instance:** federation status, peer connections
|
|
177
|
+
|
|
178
|
+
Access requires admin-level authentication (Basic auth with the Harper admin password).
|
package/docs/bridges.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Memory Bridges
|
|
2
|
+
|
|
3
|
+
> Pluggable import/export between Flair and foreign memory systems.
|
|
4
|
+
|
|
5
|
+
Flair stores memories in its own schema. The rest of the agent ecosystem stores memories too — agentic-stack's `.agent/` directory, Mem0, Letta, Zep, Anthropic memory, and more every week. **Bridges** let Flair speak those formats without a core-code change per integration.
|
|
6
|
+
|
|
7
|
+
The design goal is *agent-authorable first*: a capable agent should be able to ship a working bridge by reading this doc and running one command.
|
|
8
|
+
|
|
9
|
+
## Two shapes
|
|
10
|
+
|
|
11
|
+
| Shape | When to use | Artifact |
|
|
12
|
+
|-------|-------------|----------|
|
|
13
|
+
| **File** (YAML descriptor) | Foreign system stores memories as files on disk (`.agent/`, JSONL, Markdown) | `.flair-bridge/<name>.yaml` in a project, or `~/.flair/bridges/<name>.yaml` user-wide |
|
|
14
|
+
| **API** (TypeScript plugin) | Foreign system exposes an HTTP API (Mem0, Letta, Zep, Anthropic memory) | npm package named `flair-bridge-<name>` |
|
|
15
|
+
|
|
16
|
+
Pick one. The runtime normalizes both into the same memory-record shape before import.
|
|
17
|
+
|
|
18
|
+
## The memory record
|
|
19
|
+
|
|
20
|
+
Every bridge deals in `BridgeMemory` objects. Fields:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
interface BridgeMemory {
|
|
24
|
+
// Identity
|
|
25
|
+
id?: string // Flair ID if round-tripping; omit on first import
|
|
26
|
+
foreignId?: string // original ID in the foreign system (preserved)
|
|
27
|
+
|
|
28
|
+
// Content
|
|
29
|
+
content: string // REQUIRED
|
|
30
|
+
subject?: string
|
|
31
|
+
tags?: string[]
|
|
32
|
+
visibility?: "private" | "shared" | "public"
|
|
33
|
+
|
|
34
|
+
// Durability & lifecycle
|
|
35
|
+
durability?: "ephemeral" | "standard" | "persistent" | "permanent"
|
|
36
|
+
createdAt?: string // ISO-8601; defaults to now on import
|
|
37
|
+
validFrom?: string
|
|
38
|
+
validTo?: string
|
|
39
|
+
expiresAt?: string
|
|
40
|
+
|
|
41
|
+
// Ownership
|
|
42
|
+
agentId?: string // required on import unless --agent is passed
|
|
43
|
+
|
|
44
|
+
// Provenance
|
|
45
|
+
source?: string
|
|
46
|
+
derivedFrom?: string[]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Required on import:** `content` and (`agentId` or the `--agent` flag).
|
|
51
|
+
|
|
52
|
+
**Flair-owned, never set by a bridge:** `contentHash`, `embedding`, `embeddingModel`, `retrievalCount`, `lastRetrieved`, `promotionStatus`, `_safetyFlags`, any `*By` audit field. These are computed on ingest; if a bridge emits them they're ignored.
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
flair bridge list # installed bridges
|
|
58
|
+
flair bridge scaffold <name> [--file|--api] # emit starter files
|
|
59
|
+
flair bridge import <name> [src] [opts] # foreign → Flair (Shape A YAML / built-ins)
|
|
60
|
+
flair bridge test <name> [--fixture <path>] # round-trip diff (coming in slice 3)
|
|
61
|
+
flair bridge export <name> <dst> [opts] # Flair → foreign (coming in slice 3)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`list`, `scaffold`, and `import` are live as of 0.6.0+. `test` and `export` are stubbed with pointers to slice 3.
|
|
65
|
+
|
|
66
|
+
Common runtime options for `import`:
|
|
67
|
+
|
|
68
|
+
| Flag | Meaning |
|
|
69
|
+
|------|---------|
|
|
70
|
+
| `--agent <id>` | Default agent ID for memories that don't carry one (or set `FLAIR_AGENT_ID`) |
|
|
71
|
+
| `--cwd <dir>` | Filesystem root the descriptor's relative paths resolve against (default: cwd) |
|
|
72
|
+
| `--dry-run` | Parse + validate + count, don't write to Flair |
|
|
73
|
+
| `--port <port>` | Harper HTTP port |
|
|
74
|
+
| `--url <url>` | Flair base URL (overrides `--port`) |
|
|
75
|
+
| `--key <path>` | Ed25519 private key path (default: resolved from agent) |
|
|
76
|
+
|
|
77
|
+
## Your first import (worked example: agentic-stack)
|
|
78
|
+
|
|
79
|
+
`agentic-stack` ships as a built-in. Drop into a directory that has agentic-stack lessons and run:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
$ flair bridge list
|
|
83
|
+
name kind source description
|
|
84
|
+
agentic-stack file builtin Import agentic-stack lessons.jsonl into Flair persistent memories
|
|
85
|
+
|
|
86
|
+
$ ls .agent/memory/semantic/
|
|
87
|
+
lessons.jsonl
|
|
88
|
+
|
|
89
|
+
$ flair bridge import agentic-stack --agent mybot --dry-run
|
|
90
|
+
agentic-stack: would import 47 memories. Re-run without --dry-run to write to Flair.
|
|
91
|
+
|
|
92
|
+
$ flair bridge import agentic-stack --agent mybot
|
|
93
|
+
47 imported (lesson-47)
|
|
94
|
+
agentic-stack: imported 47/47 memories.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Each lesson lands as a Flair memory tagged `source: "agentic-stack/lessons"` with `durability: persistent`, the foreign `id` preserved as `foreignId` (so re-importing is idempotent on the same source).
|
|
98
|
+
|
|
99
|
+
A few things worth knowing:
|
|
100
|
+
|
|
101
|
+
- **`--agent` is required** unless your descriptor maps an `agentId` column. If you forget, `flair bridge import` errors with a one-line operator-pointer hint plus a structured `BridgeRuntimeError` JSON on stderr.
|
|
102
|
+
- **`--dry-run` is your friend.** Validates the descriptor, parses every record, applies the mapping, but skips the PUT. Use it to confirm the count and check a few records before committing.
|
|
103
|
+
- **Output is throttled** to one progress line every 2 seconds (or every 25 records, whichever comes first), so big imports don't flood your terminal.
|
|
104
|
+
- **Errors are structured.** Every error includes `bridge`, `op`, `path`, `record`, `field`, `expected`, `got`, `hint` (per [§10 of the spec](../specs/FLAIR-BRIDGES.md#-10-error-format)). The `hint` is the part you act on; the rest is for an LLM to self-correct without operator help.
|
|
105
|
+
|
|
106
|
+
## Shape A — Declarative YAML
|
|
107
|
+
|
|
108
|
+
For bridges whose source or target is a directory of files, write a YAML descriptor. No code required.
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
# .flair-bridge/agentic-stack.yaml
|
|
112
|
+
name: agentic-stack
|
|
113
|
+
version: 1
|
|
114
|
+
kind: file
|
|
115
|
+
description: "agentic-stack .agent/memory/semantic/lessons.jsonl"
|
|
116
|
+
|
|
117
|
+
detect:
|
|
118
|
+
anyExists:
|
|
119
|
+
- ".agent/AGENTS.md"
|
|
120
|
+
- ".agent/memory/semantic/lessons.jsonl"
|
|
121
|
+
|
|
122
|
+
import:
|
|
123
|
+
sources:
|
|
124
|
+
- path: ".agent/memory/semantic/lessons.jsonl"
|
|
125
|
+
format: jsonl
|
|
126
|
+
map:
|
|
127
|
+
content: "$.claim"
|
|
128
|
+
subject: "$.topic"
|
|
129
|
+
tags: "$.tags"
|
|
130
|
+
foreignId: "$.id"
|
|
131
|
+
durability: "persistent"
|
|
132
|
+
source: "agentic-stack/lessons"
|
|
133
|
+
|
|
134
|
+
export:
|
|
135
|
+
targets:
|
|
136
|
+
- path: ".agent/memory/semantic/lessons.jsonl"
|
|
137
|
+
format: jsonl
|
|
138
|
+
when: "durability in ['persistent', 'permanent']"
|
|
139
|
+
map:
|
|
140
|
+
id: "foreignId ?? id"
|
|
141
|
+
claim: "content"
|
|
142
|
+
topic: "subject"
|
|
143
|
+
tags: "tags"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **Path expressions:** JSONPath subset — `$.field`, `$.nested.field`, `$.array[*]`.
|
|
147
|
+
- **`when:`** boolean expression over `BridgeMemory` fields. Omit for "always".
|
|
148
|
+
- **Formats supported in 1.0:** `jsonl`, `json`, `yaml`, `markdown-frontmatter`.
|
|
149
|
+
- **What the runtime gives you:** file discovery, format parsing, schema validation, error reporting with `line:column`. You only describe the mapping.
|
|
150
|
+
|
|
151
|
+
## Shape B — Code plugin
|
|
152
|
+
|
|
153
|
+
For bridges that talk to HTTP APIs:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// flair-bridge-mem0/index.ts
|
|
157
|
+
import type { MemoryBridge, BridgeMemory, BridgeContext } from "@tpsdev-ai/flair";
|
|
158
|
+
|
|
159
|
+
export const bridge: MemoryBridge = {
|
|
160
|
+
name: "mem0",
|
|
161
|
+
version: 1,
|
|
162
|
+
kind: "api",
|
|
163
|
+
|
|
164
|
+
options: {
|
|
165
|
+
apiKey: { env: "MEM0_API_KEY", required: true, description: "Mem0 API token" },
|
|
166
|
+
userId: { required: true, description: "Mem0 user ID" },
|
|
167
|
+
baseUrl: { default: "https://api.mem0.ai" },
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async *import(opts, ctx: BridgeContext) {
|
|
171
|
+
const res = await ctx.fetch(`${opts.baseUrl}/v1/memories/?user_id=${opts.userId}`, {
|
|
172
|
+
headers: { Authorization: `Token ${opts.apiKey}` },
|
|
173
|
+
});
|
|
174
|
+
const payload = await res.json();
|
|
175
|
+
for (const m of payload.results ?? []) {
|
|
176
|
+
yield {
|
|
177
|
+
foreignId: String(m.id),
|
|
178
|
+
content: String(m.memory),
|
|
179
|
+
createdAt: m.created_at,
|
|
180
|
+
tags: m.categories,
|
|
181
|
+
source: "mem0",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
async export(memories, opts, ctx) {
|
|
187
|
+
for await (const m of memories) {
|
|
188
|
+
await ctx.fetch(`${opts.baseUrl}/v1/memories/`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { Authorization: `Token ${opts.apiKey}` },
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
messages: [{ role: "user", content: m.content }],
|
|
193
|
+
user_id: opts.userId,
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**`BridgeContext`** gives you:
|
|
202
|
+
|
|
203
|
+
- `ctx.fetch` — instrumented, rate-limited HTTP. **Always use this instead of the global `fetch`** — it's how the runtime applies per-bridge throttling and audit logging.
|
|
204
|
+
- `ctx.log` — structured `{debug, info, warn, error}` logger.
|
|
205
|
+
- `ctx.cache` — per-bridge key/value cache with optional TTL. Useful for pagination cursors, auth refresh, etc.
|
|
206
|
+
|
|
207
|
+
Code plugins have no direct filesystem or network access outside `ctx`. This keeps the surface area small and auditable.
|
|
208
|
+
|
|
209
|
+
## Discovery
|
|
210
|
+
|
|
211
|
+
`flair bridge list` scans, in precedence order:
|
|
212
|
+
|
|
213
|
+
1. **Built-ins** shipped inside `@tpsdev-ai/flair`
|
|
214
|
+
2. **Project YAML** — `.flair-bridge/*.yaml` in the current working directory
|
|
215
|
+
3. **User YAML** — `~/.flair/bridges/*.yaml`
|
|
216
|
+
4. **npm packages** — anything matching `flair-bridge-*` or `@scope/flair-bridge-*` under `node_modules/`
|
|
217
|
+
|
|
218
|
+
Earlier sources win on name conflict. So a built-in adapter can't be accidentally shadowed by a same-named npm package, but a project-local YAML *can* intentionally override an npm bridge for local development.
|
|
219
|
+
|
|
220
|
+
## Distribution
|
|
221
|
+
|
|
222
|
+
Publish bridges to npm as `flair-bridge-<name>`. Example `package.json`:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"name": "flair-bridge-mem0",
|
|
227
|
+
"version": "0.1.0",
|
|
228
|
+
"description": "Flair bridge for Mem0",
|
|
229
|
+
"main": "index.js",
|
|
230
|
+
"flair": { "kind": "api", "version": 1 },
|
|
231
|
+
"peerDependencies": { "@tpsdev-ai/flair": ">=0.6.0" }
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
There's no registry to curate; npm is the registry. `flair bridge list` surfaces everything installed.
|
|
236
|
+
|
|
237
|
+
## Round-trip testing
|
|
238
|
+
|
|
239
|
+
Every bridge ships a fixture. `flair bridge test <name>` will run (slice 2):
|
|
240
|
+
|
|
241
|
+
1. Import the fixture → `BridgeMemory[]`
|
|
242
|
+
2. Export those memories to a tmp target
|
|
243
|
+
3. Re-import the tmp target → `BridgeMemory[]`
|
|
244
|
+
4. Structural diff on the round-trip-stable fields: `content`, `subject`, `tags`, `durability`
|
|
245
|
+
5. Pass iff the diff is empty
|
|
246
|
+
|
|
247
|
+
Pass = ship. Iterate against this signal.
|
|
248
|
+
|
|
249
|
+
## Trust and security
|
|
250
|
+
|
|
251
|
+
Different sources have different blast radii.
|
|
252
|
+
|
|
253
|
+
| Source | Default trust |
|
|
254
|
+
|--------|---------------|
|
|
255
|
+
| Built-in | Allowed |
|
|
256
|
+
| Declarative YAML (no code) | Allowed; remote API calls require `--allow-remote` at invocation |
|
|
257
|
+
| Code plugin from npm | Requires `flair bridge allow <name>` on first use |
|
|
258
|
+
| Remote API calls | Always require `--allow-remote` on the invocation |
|
|
259
|
+
|
|
260
|
+
A code plugin is untrusted JavaScript. The explicit `allow` gate keeps an unreviewed npm package from executing on your machine the first time `flair bridge import` names it. VM isolation is a 1.1 hardening pass.
|
|
261
|
+
|
|
262
|
+
## Error format
|
|
263
|
+
|
|
264
|
+
Every bridge error is structured:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"bridge": "agentic-stack",
|
|
269
|
+
"op": "import",
|
|
270
|
+
"path": ".agent/memory/semantic/lessons.jsonl",
|
|
271
|
+
"record": 42,
|
|
272
|
+
"field": "$.claim",
|
|
273
|
+
"expected": "string",
|
|
274
|
+
"got": "null",
|
|
275
|
+
"hint": "lessons.jsonl row 42 has no 'claim' field — skipped"
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Printed as a single JSON object per line when `--json`; human-formatted otherwise. Field paths, expected/got, and a hint — that's the minimum an LLM needs to self-correct without operator help.
|
|
280
|
+
|
|
281
|
+
## Writing your own — the prompt
|
|
282
|
+
|
|
283
|
+
If you want an agent to write a bridge for you, here's the one-shot prompt:
|
|
284
|
+
|
|
285
|
+
> You are implementing a Flair bridge for **\<FOREIGN_SYSTEM\>**. Read the sections above on the memory record, on Shape A (file-format targets) or Shape B (API targets) depending on \<FOREIGN_SYSTEM\>, and on round-trip testing. Produce either `.flair-bridge/<name>.yaml` (file targets) or an `index.ts` implementing `MemoryBridge` (API targets). Include a fixture at `fixtures/<name>.fixture.json` or `fixtures/<name>.mock.json`. Run `flair bridge test <name>` and iterate until the round-trip diff is empty.
|
|
286
|
+
|
|
287
|
+
That's the bar. If an agent can't ship a working bridge from this doc plus the scaffold, the doc is the bug.
|
|
288
|
+
|
|
289
|
+
## Full spec
|
|
290
|
+
|
|
291
|
+
The authoritative contract is in [`specs/FLAIR-BRIDGES.md`](../specs/FLAIR-BRIDGES.md). This doc is the user-facing view; the spec covers edge cases, future extensions, and design rationale.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Flair + Claude Code
|
|
2
|
+
|
|
3
|
+
Give Claude Code persistent memory across sessions. Works with subagents too.
|
|
4
|
+
|
|
5
|
+
## Setup (5 minutes)
|
|
6
|
+
|
|
7
|
+
### 1. Install Flair
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @tpsdev-ai/flair
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Initialize
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
flair init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This starts a local Flair server (Harper) and creates `~/.flair/`.
|
|
20
|
+
|
|
21
|
+
### 3. Create an agent identity
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# One agent per project, or one shared agent — your call
|
|
25
|
+
flair agent add my-project
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This generates an Ed25519 key pair at `~/.flair/keys/my-project.key` and registers the agent with Flair.
|
|
29
|
+
|
|
30
|
+
### 4. Add to your project's CLAUDE.md
|
|
31
|
+
|
|
32
|
+
Copy this into your project's `CLAUDE.md` (or `.claude/settings.md`, `AGENTS.md`, etc.):
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
> **Start of CLAUDE.md snippet** — copy everything between the lines.
|
|
37
|
+
|
|
38
|
+
## Memory
|
|
39
|
+
|
|
40
|
+
You have persistent memory via Flair. Use it to remember context across sessions.
|
|
41
|
+
|
|
42
|
+
### On session start
|
|
43
|
+
|
|
44
|
+
Run this FIRST, before doing anything else:
|
|
45
|
+
|
|
46
|
+
flair bootstrap --agent my-project --max-tokens 4000
|
|
47
|
+
|
|
48
|
+
Read the output — that's your soul and recent memories.
|
|
49
|
+
|
|
50
|
+
### During work
|
|
51
|
+
|
|
52
|
+
- Remember something: `flair memory add --agent my-project --content "what you learned"`
|
|
53
|
+
- Search memory: `flair search "your query" --agent my-project`
|
|
54
|
+
- Store a lesson: `flair memory add --agent my-project --content "lesson text" --type lesson --durability persistent`
|
|
55
|
+
- Store a decision: `flair memory add --agent my-project --content "decision text" --type decision --durability persistent`
|
|
56
|
+
|
|
57
|
+
### What to remember
|
|
58
|
+
|
|
59
|
+
- Lessons learned (bugs, workarounds, patterns)
|
|
60
|
+
- Decisions made (why we chose X over Y)
|
|
61
|
+
- Project-specific context (architecture, conventions, constraints)
|
|
62
|
+
- User preferences (coding style, review standards)
|
|
63
|
+
|
|
64
|
+
### What NOT to remember
|
|
65
|
+
|
|
66
|
+
- Transient task details (what file am I editing right now)
|
|
67
|
+
- Things already in the codebase (read the code instead)
|
|
68
|
+
- Secrets or credentials (never store these in memory)
|
|
69
|
+
|
|
70
|
+
### Durability levels
|
|
71
|
+
|
|
72
|
+
- persistent — survives indefinitely. Use for lessons, decisions, preferences.
|
|
73
|
+
- standard — default. Good for session context, observations.
|
|
74
|
+
- ephemeral — auto-expires after 24h. Use for temporary notes.
|
|
75
|
+
|
|
76
|
+
> **End of CLAUDE.md snippet.**
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
That's it. Claude Code will now bootstrap context on start and store important things as it works.
|
|
81
|
+
|
|
82
|
+
## Multiple Projects
|
|
83
|
+
|
|
84
|
+
Create a separate agent per project:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
flair agent add project-alpha
|
|
88
|
+
flair agent add project-beta
|
|
89
|
+
flair agent add infra-ops
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Each project's `CLAUDE.md` uses its own agent ID. Memories are fully isolated between projects.
|
|
93
|
+
|
|
94
|
+
## Subagents
|
|
95
|
+
|
|
96
|
+
Claude Code subagents (spawned via `/run` or background tasks) can share the parent's memory:
|
|
97
|
+
|
|
98
|
+
### Subagents
|
|
99
|
+
Subagents share memory with the parent session. Use the same agent ID:
|
|
100
|
+
FLAIR_AGENT_ID=my-project
|
|
101
|
+
|
|
102
|
+
When spawning subagents, pass the agent ID so they can access shared context.
|
|
103
|
+
|
|
104
|
+
Or give subagents their own identity for isolation:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
flair agent add my-project-review # code review subagent
|
|
108
|
+
flair agent add my-project-test # test runner subagent
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Environment Variables
|
|
112
|
+
|
|
113
|
+
Instead of passing `--agent` every time, set environment variables:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# In your shell profile or .envrc
|
|
117
|
+
export FLAIR_AGENT_ID=my-project
|
|
118
|
+
export FLAIR_URL=http://localhost:19926 # default, only needed if custom
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Then the CLAUDE.md simplifies to:
|
|
122
|
+
|
|
123
|
+
## Memory
|
|
124
|
+
- Bootstrap: `flair bootstrap`
|
|
125
|
+
- Remember: `flair memory add --content "what you learned"`
|
|
126
|
+
- Search: `flair search "your query"`
|
|
127
|
+
|
|
128
|
+
## Soul (Personality / Context)
|
|
129
|
+
|
|
130
|
+
Want Claude Code to have consistent personality or project context? Set soul entries:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Project context
|
|
134
|
+
flair soul set --agent my-project --key project \
|
|
135
|
+
--value "E-commerce platform. Rust backend, React frontend. Ship quality over speed."
|
|
136
|
+
|
|
137
|
+
# Coding standards
|
|
138
|
+
flair soul set --agent my-project --key standards \
|
|
139
|
+
--value "Always write tests. Prefer composition over inheritance. No any types in TypeScript."
|
|
140
|
+
|
|
141
|
+
# Review guidelines
|
|
142
|
+
flair soul set --agent my-project --key review \
|
|
143
|
+
--value "Check for: error handling, edge cases, performance implications, security."
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Soul entries are included in every `flair bootstrap` — they're the persistent context that shapes how Claude Code thinks about your project.
|
|
147
|
+
|
|
148
|
+
## Remote Flair
|
|
149
|
+
|
|
150
|
+
If you want to share memory across machines (e.g., work laptop + home setup):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# On your server
|
|
154
|
+
npm install -g @tpsdev-ai/flair
|
|
155
|
+
flair init
|
|
156
|
+
flair agent add my-project
|
|
157
|
+
|
|
158
|
+
# On client machines
|
|
159
|
+
npm install -g @tpsdev-ai/flair # for the CLI
|
|
160
|
+
export FLAIR_URL=http://your-server:19926
|
|
161
|
+
export FLAIR_AGENT_ID=my-project
|
|
162
|
+
# Copy the key from the server:
|
|
163
|
+
scp server:~/.flair/keys/my-project.key ~/.flair/keys/
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or use an SSH tunnel:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
ssh -f -N -L 19926:localhost:19926 your-server
|
|
170
|
+
# Now FLAIR_URL=http://localhost:19926 works
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Programmatic Access
|
|
174
|
+
|
|
175
|
+
For custom tooling, use the lightweight client library:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm install @tpsdev-ai/flair-client
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { FlairClient } from '@tpsdev-ai/flair-client'
|
|
183
|
+
|
|
184
|
+
const flair = new FlairClient({ agentId: 'my-project' })
|
|
185
|
+
|
|
186
|
+
await flair.memory.write('learned that X causes Y', {
|
|
187
|
+
type: 'lesson',
|
|
188
|
+
durability: 'persistent',
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const results = await flair.memory.search('what causes Y')
|
|
192
|
+
const context = await flair.bootstrap({ maxTokens: 4000 })
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Tips
|
|
196
|
+
|
|
197
|
+
- **Bootstrap is cheap.** Run it at the start of every session. It's one HTTP call.
|
|
198
|
+
- **Write lessons immediately.** Don't wait for the session to end — you might not get the chance.
|
|
199
|
+
- **Use durability wisely.** Most things are `standard`. Only promote to `persistent` for things that should survive months.
|
|
200
|
+
- **Search is semantic.** "deployment issues" finds memories about "CI pipeline failures" — you don't need exact keywords.
|
|
201
|
+
- **Temporal queries work.** "What happened today" and "what did we ship recently" are understood.
|
|
202
|
+
- **Dedup is automatic.** Writing the same fact twice won't create duplicates (0.7 similarity threshold).
|