@tpsdev-ai/flair 0.26.0 → 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 +434 -50
- package/dist/doctor-client.js +47 -0
- 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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Flair integrations
|
|
2
|
+
|
|
3
|
+
Where Flair already runs. Each integration shown here is a working surface — the same memory, federated across all of them, scoped per-agent by Ed25519 keys.
|
|
4
|
+
|
|
5
|
+
> **The point.** Memory should follow the agent across orchestrators. Every entry below pulls from the same Flair instance, sees the same `agentId` namespace, respects the same isolation. Pick whichever harness you're shipping in; the memory layer doesn't care.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick install matrix
|
|
10
|
+
|
|
11
|
+
| Surface | Install path | Auth | Notes |
|
|
12
|
+
|---------|--------------|------|-------|
|
|
13
|
+
| **Claude Code** | [`flair-mcp`](#claude-code-cursor-codex-gemini-cli-continuedev-via-flair-mcp) | MCP config | Standard MCP server |
|
|
14
|
+
| **Cursor** | [`flair-mcp`](#claude-code-cursor-codex-gemini-cli-continuedev-via-flair-mcp) | MCP config | Standard MCP server |
|
|
15
|
+
| **Continue.dev** | [`flair-mcp`](#claude-code-cursor-codex-gemini-cli-continuedev-via-flair-mcp) | MCP config | Standard MCP server |
|
|
16
|
+
| **OpenAI Codex CLI** | [`flair-mcp`](#claude-code-cursor-codex-gemini-cli-continuedev-via-flair-mcp) | MCP config | Standard MCP server |
|
|
17
|
+
| **Gemini CLI** | [`flair-mcp`](#claude-code-cursor-codex-gemini-cli-continuedev-via-flair-mcp) | MCP config | Standard MCP server |
|
|
18
|
+
| **Goose** (block/goose) | [`flair-mcp`](#claude-code-cursor-codex-gemini-cli-continuedev-via-flair-mcp) | MCP config | Goose ships native MCP support |
|
|
19
|
+
| **LangGraph (TS)** | [`langgraph-flair`](#langgraph-typescript) | FlairClient | Drop-in `BaseStore` |
|
|
20
|
+
| **OpenClaw** | [`openclaw-flair`](#openclaw) | Ed25519 | Native plugin + context engine |
|
|
21
|
+
| **n8n** | [`n8n-nodes-flair`](#n8n) | FlairApi credential | Three nodes (chat memory, search, store) |
|
|
22
|
+
| **Hermes Agent** | [`hermes-flair`](#hermes-agent) | Ed25519 | Python `MemoryProvider` |
|
|
23
|
+
| **Pi agent** | [`pi-flair`](#pi-agent) | Ed25519 | TS plugin |
|
|
24
|
+
|
|
25
|
+
Don't see your harness? If it speaks **MCP** — Flair already works with `flair-mcp`. If it has a **custom memory protocol** like LangGraph's `BaseStore` or CrewAI's `RAGStorage`, an adapter is a ~200-line package; [open an issue](https://github.com/tpsdev-ai/flair/issues) or [send a PR](https://github.com/tpsdev-ai/flair).
|
|
26
|
+
|
|
27
|
+
**Adjacent: memory bridges** — for moving memories between Flair and another memory product. Five bridges ship today (Mem0, ChatGPT exports, claude-project files, agentic-stack, markdown); see [bridges.md](bridges.md). Bridges are import/export plumbing, not live orchestrator integrations.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Claude Code, Cursor, Codex, Gemini CLI, Continue.dev, Goose — via `flair-mcp`
|
|
32
|
+
|
|
33
|
+
[`@tpsdev-ai/flair-mcp`](https://www.npmjs.com/package/@tpsdev-ai/flair-mcp) is a [Model Context Protocol](https://modelcontextprotocol.io/) server that exposes Flair as a memory tool to any MCP-speaking client. One server, every MCP client.
|
|
34
|
+
|
|
35
|
+
No install step needed — every snippet below uses `npx -y @tpsdev-ai/flair-mcp`, which fetches and runs the server on demand (zero-install). The fastest path is `flair init`, which detects and wires these clients for you. To wire by hand, drop the relevant snippet into each tool's MCP config:
|
|
36
|
+
|
|
37
|
+
**Claude Code** (`~/.config/claude-code/config.toml` or per-project `.claude/config.toml`):
|
|
38
|
+
```toml
|
|
39
|
+
[mcp.servers.flair]
|
|
40
|
+
command = "npx"
|
|
41
|
+
args = ["-y", "@tpsdev-ai/flair-mcp"]
|
|
42
|
+
env = { FLAIR_AGENT_ID = "claude-code" }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Cursor** (`~/.cursor/mcp.json`):
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"flair": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "@tpsdev-ai/flair-mcp"],
|
|
52
|
+
"env": { "FLAIR_AGENT_ID": "cursor" }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Codex CLI** (`~/.codex/config.toml`):
|
|
59
|
+
```toml
|
|
60
|
+
[mcp_servers.flair]
|
|
61
|
+
command = "npx"
|
|
62
|
+
args = ["-y", "@tpsdev-ai/flair-mcp"]
|
|
63
|
+
|
|
64
|
+
[mcp_servers.flair.env]
|
|
65
|
+
FLAIR_AGENT_ID = "codex"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Gemini CLI** (`~/.gemini/settings.json`): same shape as Cursor.
|
|
69
|
+
|
|
70
|
+
**Continue.dev** (`~/.continue/config.json`):
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"experimental": {
|
|
74
|
+
"modelContextProtocolServer": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["-y", "@tpsdev-ai/flair-mcp"],
|
|
77
|
+
"env": { "FLAIR_AGENT_ID": "continue" }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Goose** (`~/.config/goose/profiles.yaml`):
|
|
84
|
+
```yaml
|
|
85
|
+
default:
|
|
86
|
+
extensions:
|
|
87
|
+
flair:
|
|
88
|
+
cmd: npx
|
|
89
|
+
args: ["-y", "@tpsdev-ai/flair-mcp"]
|
|
90
|
+
envs: { FLAIR_AGENT_ID: goose }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Auth.** Set `FLAIR_AGENT_ID` to whatever identifier you want this client to claim. The MCP server will prompt you to register that agent on first call (`flair agent add <id>` writes the Ed25519 keypair). Subsequent calls auto-load the key.
|
|
94
|
+
|
|
95
|
+
Full per-tool walkthrough including troubleshooting: [`docs/mcp-clients.md`](mcp-clients.md).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## LangGraph (TypeScript)
|
|
100
|
+
|
|
101
|
+
[`@tpsdev-ai/langgraph-flair`](https://www.npmjs.com/package/@tpsdev-ai/langgraph-flair) implements LangGraph's `BaseStore`. Drop-in for `InMemoryStore`.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm install @tpsdev-ai/langgraph-flair
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { FlairStore } from "@tpsdev-ai/langgraph-flair";
|
|
109
|
+
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
|
110
|
+
|
|
111
|
+
const store = new FlairStore({ agentId: "my-langgraph-agent" });
|
|
112
|
+
const agent = createReactAgent({ llm, tools, store });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Maps LangGraph namespaces to Flair tags, keys to ids, values to JSON content. Search delegates to Flair's HNSW. Filter operators applied client-side. Full mapping table: [`packages/langgraph-flair/README.md`](../packages/langgraph-flair/README.md).
|
|
116
|
+
|
|
117
|
+
LangGraph **Python** support is on the roadmap (same `BaseStore` shape, Python adapter).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## OpenClaw
|
|
122
|
+
|
|
123
|
+
[`@tpsdev-ai/openclaw-flair`](https://www.npmjs.com/package/@tpsdev-ai/openclaw-flair) is the native OpenClaw plugin. Adds Flair as a memory provider AND registers the `flair` context engine that re-injects PERMANENT-tier rules (SOUL.md, IDENTITY.md, AGENTS.md) every turn.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
openclaw plugins install @tpsdev-ai/openclaw-flair
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Configuration via OpenClaw's standard plugin surface. See [`docs/openclaw.md`](openclaw.md) for the per-agent install pattern, including how to wire SOUL.md so behavioral anchors persist across long sessions without drift.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## n8n
|
|
134
|
+
|
|
135
|
+
[`@tpsdev-ai/n8n-nodes-flair`](https://www.npmjs.com/package/@tpsdev-ai/n8n-nodes-flair) ships three nodes:
|
|
136
|
+
|
|
137
|
+
- **FlairChatMemory** — drop-in chat-memory for n8n's AI Agent / LangChain workflow nodes. Same role as Postgres / Redis chat memory but with cross-orchestrator portability.
|
|
138
|
+
- **FlairSearch** — semantic search over your Flair memories from any workflow.
|
|
139
|
+
- **FlairApi** credential — Ed25519 keypair entry point for the agentId.
|
|
140
|
+
|
|
141
|
+
Install via the standard n8n community-node UI (Settings → Community nodes → `@tpsdev-ai/n8n-nodes-flair`) or:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
cd ~/.n8n && npm install @tpsdev-ai/n8n-nodes-flair
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Walkthrough including a worked example flow: [`docs/n8n.md`](n8n.md).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Hermes Agent
|
|
152
|
+
|
|
153
|
+
[`hermes-flair`](https://github.com/tpsdev-ai/flair/tree/main/packages/hermes-flair) implements Nous Research [Hermes](https://github.com/NousResearch/hermes-agent)'s `MemoryProvider` plugin contract in Python. Bootstrap injection at session start, background prefetch between turns, two tools (`flair_search`, `flair_store`), built-in MEMORY.md mirroring, circuit breaker.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
hermes plugins install path:/path/to/flair/packages/hermes-flair
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Auth: TPS-Ed25519 (the same model the rest of Flair uses) — writes are isolated per agent identity server-side; reads are open within the org for non-private memories, with `visibility: private` staying owner-only. See [SECURITY.md](../SECURITY.md).
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Pi agent
|
|
164
|
+
|
|
165
|
+
[`@tpsdev-ai/pi-flair`](https://www.npmjs.com/package/@tpsdev-ai/pi-flair) is the TS plugin for the [Pi coding agent](https://github.com/mariozechner/pi-coding-agent). Memory + identity for the Pi runtime.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npm install @tpsdev-ai/pi-flair
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Pi resolves the plugin via its standard plugin config; pin `agentId` per host.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Don't see your harness?
|
|
176
|
+
|
|
177
|
+
If it speaks MCP, you're already covered — every MCP client works through `flair-mcp` (the section above lists 6 we've explicitly tested).
|
|
178
|
+
|
|
179
|
+
If it has a custom memory protocol, the adapter pattern is small (~200 lines). LangGraph and Hermes are the reference implementations. **Adapters we'd love to see:**
|
|
180
|
+
|
|
181
|
+
- LangGraph Python (mirror of our TS adapter)
|
|
182
|
+
- CrewAI (Python `BaseRAGStorage` protocol)
|
|
183
|
+
- AG2 / AutoGen (Python)
|
|
184
|
+
- Mastra (TS, denser thread model)
|
|
185
|
+
- ADK (Google, Python + TS)
|
|
186
|
+
|
|
187
|
+
[Open an issue](https://github.com/tpsdev-ai/flair/issues) describing the harness and we'll triage. PRs welcome — see [`packages/langgraph-flair`](../packages/langgraph-flair) as the smallest-shape reference.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## See also
|
|
192
|
+
|
|
193
|
+
- [Quickstart](quickstart.md) — `flair init` to working memory in 30 seconds
|
|
194
|
+
- [Memory bridges](bridges.md) — import/export Flair ↔ Mem0, ChatGPT, claude-project, markdown, agentic-stack (five bridges shipped)
|
|
195
|
+
- [Federation](federation.md) — pair instances peer-to-peer for cross-machine sync
|
|
196
|
+
- [Supply-chain policy](supply-chain-policy.md) — what we do to keep this list of integrations safe
|
|
197
|
+
- [The team](the-team.md) — the multi-agent rig that builds Flair, dogfooded on every harness above
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Flair via MCP — Claude Code, Gemini CLI, OpenAI Codex CLI
|
|
2
|
+
|
|
3
|
+
Flair ships an MCP server (`@tpsdev-ai/flair-mcp`) that any MCP-compatible client can use as its persistent memory + identity layer. One server, three (and counting) integrations. Switch between agent CLIs without losing your agent's memory.
|
|
4
|
+
|
|
5
|
+
This page is the install + config snippet for each of the three major CLIs. The bootstrap is the same:
|
|
6
|
+
|
|
7
|
+
1. Install Flair and create an agent identity (one-time, ~2 min).
|
|
8
|
+
2. Add the MCP server to your CLI of choice (1 command or 1 file).
|
|
9
|
+
3. Verify the agent can call `memory_search` / `memory_store`.
|
|
10
|
+
|
|
11
|
+
If you've never set up Flair before, do step 1 first. If Flair is already running and you have an agent ID, jump to your CLI section.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Step 1 — Install Flair (do once)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install Flair globally
|
|
19
|
+
npm install -g @tpsdev-ai/flair
|
|
20
|
+
|
|
21
|
+
# Initialize the local Harper-backed server
|
|
22
|
+
flair init
|
|
23
|
+
|
|
24
|
+
# Provision an agent identity. Pick a name — typically per-project, per-purpose,
|
|
25
|
+
# or "me" if you want one durable identity across everything.
|
|
26
|
+
flair agent add my-project
|
|
27
|
+
# → writes ~/.flair/keys/my-project.key (Ed25519 PKCS8) and registers the agent
|
|
28
|
+
|
|
29
|
+
# Sanity check
|
|
30
|
+
flair status
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Flair runs as a local server at `http://127.0.0.1:19926` by default. The MCP server connects to it on demand via Ed25519-signed requests; nothing leaves your machine unless you explicitly route to a remote Flair instance.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 2 — Wire the MCP server into your CLI
|
|
38
|
+
|
|
39
|
+
Pick whichever you use. The MCP server is the same package; only the config syntax differs.
|
|
40
|
+
|
|
41
|
+
### Claude Code
|
|
42
|
+
|
|
43
|
+
The canonical approach is the `claude mcp add` CLI (writes to `~/.claude/mcp.json`):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
claude mcp add flair --scope user \
|
|
47
|
+
-e FLAIR_AGENT_ID=my-project \
|
|
48
|
+
-- npx -y @tpsdev-ai/flair-mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Verify:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
claude mcp list
|
|
55
|
+
# → flair (stdio, npx -y @tpsdev-ai/flair-mcp)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or, if you prefer the project-scoped `.mcp.json` checked into your repo:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"flair": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "@tpsdev-ai/flair-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"FLAIR_AGENT_ID": "my-project"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Auto-recall on session start (optional hook)
|
|
75
|
+
|
|
76
|
+
The MCP server gives the agent *pull* access to memory — it calls `bootstrap` /
|
|
77
|
+
`memory_search` when it decides to. If you'd rather have Flair context loaded
|
|
78
|
+
automatically the moment a session opens (no "call the bootstrap tool" nudge),
|
|
79
|
+
register Flair's `SessionStart` hook. It's a separate bin shipped in the same
|
|
80
|
+
package and is entirely optional — it complements the MCP server, it doesn't
|
|
81
|
+
replace it.
|
|
82
|
+
|
|
83
|
+
The one-command way (recommended — idempotent, `--dry-run`-able, and
|
|
84
|
+
symmetric with `flair hook uninstall`/`flair hook status`):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
flair hook install # wires ~/.claude/settings.json for FLAIR_AGENT_ID/FLAIR_URL
|
|
88
|
+
flair hook install --dry-run # prints the exact JSON delta, writes nothing
|
|
89
|
+
flair hook status # wired? correct shape? which agent/instance?
|
|
90
|
+
flair hook uninstall # removes only Flair's hook entry
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--harness claude-code` is the only supported value today (it's also the
|
|
94
|
+
default) — the flag exists so a future harness is an additive registry entry,
|
|
95
|
+
not a breaking change. `flair doctor` already checks for this same hook (see
|
|
96
|
+
below) and recognizes anything `flair hook install` writes.
|
|
97
|
+
|
|
98
|
+
Or wire it by hand — add a `SessionStart` hook to `~/.claude/settings.json`:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"hooks": {
|
|
103
|
+
"SessionStart": [
|
|
104
|
+
{
|
|
105
|
+
"hooks": [
|
|
106
|
+
{
|
|
107
|
+
"type": "command",
|
|
108
|
+
"command": "FLAIR_AGENT_ID=me npx -y @tpsdev-ai/flair-mcp flair-session-start"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Swap `me` for your `FLAIR_AGENT_ID`. The hook reads Claude Code's SessionStart
|
|
118
|
+
payload on stdin, calls Flair's `bootstrap` (soul + relevant memories +
|
|
119
|
+
predicted context, scoped to your project by the session's working directory),
|
|
120
|
+
and emits it as `hookSpecificOutput.additionalContext` — which Claude Code
|
|
121
|
+
injects into the new session's context. The matcher is omitted, so it fires on
|
|
122
|
+
every session start (`startup`, `resume`, `clear`, `compact`); add
|
|
123
|
+
`"matcher": "startup"` to a hook group if you only want it on fresh sessions.
|
|
124
|
+
|
|
125
|
+
It honors the same env as the MCP server (`FLAIR_AGENT_ID`, `FLAIR_URL`,
|
|
126
|
+
`FLAIR_KEY_PATH`), plus `FLAIR_HOOK_TIMEOUT_MS` (default 8000, clamped
|
|
127
|
+
500–30000) for the bootstrap timeout.
|
|
128
|
+
|
|
129
|
+
**It degrades to a no-op, always.** No `FLAIR_AGENT_ID`, Flair down, an auth
|
|
130
|
+
error, or a hung daemon (past the timeout) → the hook prints `{}` and exits 0.
|
|
131
|
+
Claude Code treats that as "no context to add" and starts normally. The hook
|
|
132
|
+
can never block or break session startup. The injected context is clamped to
|
|
133
|
+
≤10,000 characters to keep the session-start payload small.
|
|
134
|
+
|
|
135
|
+
### Gemini CLI
|
|
136
|
+
|
|
137
|
+
Edit `~/.gemini/settings.json` (create it if absent):
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"flair": {
|
|
143
|
+
"command": "npx",
|
|
144
|
+
"args": ["-y", "@tpsdev-ai/flair-mcp"],
|
|
145
|
+
"env": {
|
|
146
|
+
"FLAIR_AGENT_ID": "my-project"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Restart your Gemini CLI session for the config to take effect. Then in chat:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
> @flair memory_search "what did we decide about auth last week?"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### OpenAI Codex CLI
|
|
160
|
+
|
|
161
|
+
Edit `~/.codex/config.toml` (create it if absent):
|
|
162
|
+
|
|
163
|
+
```toml
|
|
164
|
+
[mcp_servers.flair]
|
|
165
|
+
command = "npx"
|
|
166
|
+
args = ["-y", "@tpsdev-ai/flair-mcp"]
|
|
167
|
+
|
|
168
|
+
[mcp_servers.flair.env]
|
|
169
|
+
FLAIR_AGENT_ID = "my-project"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
For project-scoped trust (per Codex's MCP guide), the same block in `.codex/config.toml` at the project root.
|
|
173
|
+
|
|
174
|
+
Restart your Codex CLI session and the `flair_*` tools become available to the agent.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Step 3 — Verify
|
|
179
|
+
|
|
180
|
+
In any of the three CLIs, ask the agent to do this:
|
|
181
|
+
|
|
182
|
+
> Use the bootstrap tool to load my Flair memory context, then store a memory that says "successful first MCP integration test."
|
|
183
|
+
|
|
184
|
+
If you see (a) the agent calling the `bootstrap` tool returning soul + recent memories, and (b) `memory_store` confirming a write — you're wired up. The memory now persists across CLI sessions AND across CLIs. Switch to a different CLI tomorrow and `memory_search "MCP integration test"` will find it.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## What the MCP server exposes
|
|
189
|
+
|
|
190
|
+
Eleven tools, kept deliberately small:
|
|
191
|
+
|
|
192
|
+
| Tool | What it does |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `memory_search` | Semantic search across your agent's memories |
|
|
195
|
+
| `memory_store` | Save a memory with type, durability, tags. Auto-dedups near-duplicates |
|
|
196
|
+
| `memory_update` | Update an existing memory by ID — overwrite in place, or version it with `preserveHistory` |
|
|
197
|
+
| `memory_get` | Fetch a specific memory by ID |
|
|
198
|
+
| `memory_delete` | Remove a memory |
|
|
199
|
+
| `relationship_store` | Record a subject-predicate-object relationship triple (e.g. "nathan manages flair") |
|
|
200
|
+
| `bootstrap` | Get session-start context: soul + recent memories + predicted-relevant context |
|
|
201
|
+
| `soul_set` | Set a personality/project/standards entry — included in every bootstrap |
|
|
202
|
+
| `soul_get` | Get a soul entry |
|
|
203
|
+
| `flair_workspace_set` | Set your agent's current workspace state (ref/branch, phase, task) in the Office Space |
|
|
204
|
+
| `flair_orgevent` | Publish an org-wide coordination event (claim/release/status) to the Office Space |
|
|
205
|
+
|
|
206
|
+
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.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Configuration reference
|
|
211
|
+
|
|
212
|
+
| Env var | Default | Notes |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| `FLAIR_AGENT_ID` | (none — required) | Must match `flair agent add <id>` |
|
|
215
|
+
| `FLAIR_URL` | `http://127.0.0.1:19926` | Override for remote Flair instances |
|
|
216
|
+
| `FLAIR_KEY_PATH` | `~/.flair/keys/<agent>.key` | Ed25519 PKCS8 key — created by `flair agent add` |
|
|
217
|
+
|
|
218
|
+
The MCP server has no client-side flags beyond these env vars; everything else (timeouts, dedup thresholds, error classification) is opinionated defaults from the underlying [`@tpsdev-ai/flair-client`](../packages/flair-client) package.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## What about Hermes (Nous Research)?
|
|
223
|
+
|
|
224
|
+
Hermes uses its own Python-native `MemoryProvider` ABC instead of MCP. It has its own Flair integration in [`packages/hermes-flair/`](../packages/hermes-flair). Same backend, same agent isolation, different plug shape.
|
|
225
|
+
|
|
226
|
+
Future MCP-capable agent CLIs (and there are more landing every month) will work out of the box with the MCP server above — no per-framework adapter required from us.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Troubleshooting
|
|
231
|
+
|
|
232
|
+
**"FLAIR_AGENT_ID is required" on startup.** Set it in the MCP server's `env` block (per snippets above). The CLI's own env doesn't propagate to the spawned MCP subprocess unless declared.
|
|
233
|
+
|
|
234
|
+
**"connection_error: could not reach Flair at http://127.0.0.1:19926".** The Flair server isn't running. Run `flair status` to check; `flair start` to bring it up.
|
|
235
|
+
|
|
236
|
+
**"auth_error: …" on every call.** The agent identity doesn't match a registered key. Re-run `flair agent add <id>` (idempotent on re-add — won't lose existing memories).
|
|
237
|
+
|
|
238
|
+
**Tool calls succeed but the agent doesn't see results in subsequent turns.** Check that the CLI is actually invoking `bootstrap` at session start — most CLIs need an explicit prompt nudge ("call the bootstrap tool now") on first use. Subsequent turns should pick up automatically once the CLI sees the schema.
|
|
239
|
+
|
|
240
|
+
For deeper issues see [`troubleshooting.md`](troubleshooting.md) and the [`@tpsdev-ai/flair-mcp` repo](https://github.com/tpsdev-ai/flair/tree/main/packages/flair-mcp).
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# n8n Workflow Management
|
|
2
|
+
|
|
3
|
+
Programmatic activation/deactivation of n8n workflows — the approach we use for ops automation.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Direct SQLite edits to `workflow_entity.active` (or using `n8n update:workflow` while n8n is running) don't re-register schedule triggers in the in-memory scheduler. The schedule simply stops firing. Only the v1 public API's `POST /api/v1/workflows/:id/activate` (or flipping Active in the UI) properly registers the trigger.
|
|
8
|
+
|
|
9
|
+
**Root cause**: n8n tracks activation via `activeVersionId`, not the `active` boolean alone. The `activeWorkflowManager` in-memory scheduler only picks up workflows where `activeVersionId IS NOT NULL`. Direct SQLite edits that only set `active=1` without setting `activeVersionId` will not be picked up even on restart.
|
|
10
|
+
|
|
11
|
+
## Recommended Approach: v1 Public API (live instance)
|
|
12
|
+
|
|
13
|
+
The **v1 public API** is the correct tool for managing workflows on a **running** n8n instance. It updates the DB AND calls the in-memory `ActiveWorkflowManager.add()` to register triggers immediately — no restart needed.
|
|
14
|
+
|
|
15
|
+
### Endpoints
|
|
16
|
+
|
|
17
|
+
| Action | Method | Endpoint | Body |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| Activate | `POST` | `/api/v1/workflows/:id/activate` | `{}` or `{"versionId": "..."}` |
|
|
20
|
+
| Deactivate | `POST` | `/api/v1/workflows/:id/deactivate` | (none) |
|
|
21
|
+
| List | `GET` | `/api/v1/workflows` | — |
|
|
22
|
+
| Get one | `GET` | `/api/v1/workflows/:id` | — |
|
|
23
|
+
|
|
24
|
+
### Authentication
|
|
25
|
+
|
|
26
|
+
All v1 API calls require `X-N8N-API-KEY` header with a valid API key (see [API Key Generation](#api-key-generation) below).
|
|
27
|
+
|
|
28
|
+
### Examples
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Activate workflow
|
|
32
|
+
curl -X POST "http://127.0.0.1:5678/api/v1/workflows/$WORKFLOW_ID/activate" \
|
|
33
|
+
-H "X-N8N-API-KEY: $N8N_API_KEY" \
|
|
34
|
+
-H "Content-Type: application/json" \
|
|
35
|
+
-d '{}'
|
|
36
|
+
|
|
37
|
+
# Deactivate workflow
|
|
38
|
+
curl -X POST "http://127.0.0.1:5678/api/v1/workflows/$WORKFLOW_ID/deactivate" \
|
|
39
|
+
-H "X-N8N-API-KEY: $N8N_API_KEY"
|
|
40
|
+
|
|
41
|
+
# Check if workflow is active
|
|
42
|
+
curl -s "http://127.0.0.1:5678/api/v1/workflows/$WORKFLOW_ID" \
|
|
43
|
+
-H "X-N8N-API-KEY: $N8N_API_KEY" | python3 -c "import json,sys; print(json.load(sys.stdin)['active'])"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
See `scripts/n8n/activate-workflow.sh` for a reusable wrapper.
|
|
47
|
+
|
|
48
|
+
## Why PATCH Doesn't Work
|
|
49
|
+
|
|
50
|
+
n8n's v1 API docs mention `PATCH /api/v1/workflows/:id` with `{ "active": true }`. **This returns 405 Method Not Allowed on n8n 1.123.38.** The documented activation endpoints are `POST /api/v1/workflows/:id/activate` and `POST /api/v1/workflows/:id/deactivate`.
|
|
51
|
+
|
|
52
|
+
## Alternative Approaches
|
|
53
|
+
|
|
54
|
+
### Approach 2: CLI when n8n is stopped
|
|
55
|
+
|
|
56
|
+
The `n8n update:workflow` CLI command works when n8n is **not running**. It updates both `active` and `activeVersionId` in the DB. On the next `n8n start`, `ActiveWorkflowManager.init()` reads `getAllActiveIds()` and registers all workflows with non-null `activeVersionId`.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Stop n8n
|
|
60
|
+
launchctl stop ai.tpsdev.n8n
|
|
61
|
+
|
|
62
|
+
# Activate/deactivate via CLI
|
|
63
|
+
N8N_PATH="/opt/homebrew/opt/node@24/bin" npx n8n@1.123.38 update:workflow --id=$WORKFLOW_ID --active=true
|
|
64
|
+
# or
|
|
65
|
+
N8N_PATH="/opt/homebrew/opt/node@24/bin" npx n8n@1.123.38 update:workflow --id=$WORKFLOW_ID --active=false
|
|
66
|
+
|
|
67
|
+
# Start n8n
|
|
68
|
+
launchctl start ai.tpsdev.n8n
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Caveats**:
|
|
72
|
+
- `update:workflow` is deprecated (n8n v2 will replace it with `publish:workflow`/`unpublish:workflow`)
|
|
73
|
+
- Requires a full stop/start cycle (downtime for all workflows)
|
|
74
|
+
- `npx` needs node@24 (`/opt/homebrew/opt/node@24/bin/node`)
|
|
75
|
+
|
|
76
|
+
### Approach 3: Internal REST API (editor UI)
|
|
77
|
+
|
|
78
|
+
The `/rest/workflows/:id/activate` endpoint is what the editor UI uses. It requires session-based auth (cookie from `/rest/login`), which needs the admin password. Not practical for automation without storing credentials.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Requires session cookie from /rest/login
|
|
82
|
+
curl -X POST "http://127.0.0.1:5678/rest/workflows/$WORKFLOW_ID/activate" \
|
|
83
|
+
-b "fern-uid=...; n8n.session=..."
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Not recommended** for ops automation — session cookies expire and the password is a higher-value secret than an API key.
|
|
87
|
+
|
|
88
|
+
## API Key Generation
|
|
89
|
+
|
|
90
|
+
The v1 API key is a JWT signed with a secret derived from the encryption key. To create one programmatically:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const crypto = require('crypto');
|
|
94
|
+
const jwt = require('jsonwebtoken');
|
|
95
|
+
const fs = require('fs');
|
|
96
|
+
|
|
97
|
+
// Read encryption key from n8n config
|
|
98
|
+
const config = JSON.parse(fs.readFileSync(process.env.HOME + '/.n8n/config', 'utf8'));
|
|
99
|
+
const encKey = config.encryptionKey;
|
|
100
|
+
|
|
101
|
+
// Derive JWT secret: SHA256 of every-other-character of encryption key
|
|
102
|
+
let baseKey = '';
|
|
103
|
+
for (let i = 0; i < encKey.length; i += 2) {
|
|
104
|
+
baseKey += encKey[i];
|
|
105
|
+
}
|
|
106
|
+
const jwtSecret = crypto.createHash('sha256').update(baseKey).digest('hex');
|
|
107
|
+
|
|
108
|
+
// Sign JWT with user's ID (from `user` table in SQLite)
|
|
109
|
+
const userId = '<user-uuid-from-sqlite>'; // e.g., 'a6ce7ac9-ef6d-4157-835a-4b0f01dc788a'
|
|
110
|
+
const apiKey = jwt.sign(
|
|
111
|
+
{ sub: userId, iss: 'n8n', aud: 'public-api' },
|
|
112
|
+
jwtSecret
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Store the returned key in the user_api_keys table so it persists
|
|
116
|
+
|
|
117
|
+
console.log('API key:', apiKey);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Once stored in the DB, the key is valid until deleted. No expiration by default.
|
|
121
|
+
|
|
122
|
+
**Security note**: The encryption key is needed to derive the JWT secret. This is the same key used to decrypt workflow credentials. If the encryption key is compromised, so are the credentials. Keep it scoped to the n8n user.
|
|
123
|
+
|
|
124
|
+
## Why Direct SQLite Edits Don't Work
|
|
125
|
+
|
|
126
|
+
The `workflow_entity` table has two activation fields:
|
|
127
|
+
- `active` (boolean): UI flag
|
|
128
|
+
- `activeVersionId` (varchar): points to the `workflow_history` record that defines the active version
|
|
129
|
+
|
|
130
|
+
The `ActiveWorkflowManager` only considers workflows where `activeVersionId IS NOT NULL`. If you only flip `active=1` without setting `activeVersionId`, n8n never picks up the workflow — not on restart, not on refresh.
|
|
131
|
+
|
|
132
|
+
Even with both fields set correctly, a live n8n instance has an in-memory scheduler that doesn't re-read the DB. Only the v1 API (or UI toggle) calls `activeWorkflowManager.add()` to register the trigger in-memory.
|
|
133
|
+
|
|
134
|
+
## Quick Reference
|
|
135
|
+
|
|
136
|
+
| Scenario | Method | Restart needed? |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| Activate on running n8n | `POST /api/v1/workflows/:id/activate` | No |
|
|
139
|
+
| Deactivate on running n8n | `POST /api/v1/workflows/:id/deactivate` | No |
|
|
140
|
+
| Activate when stopped | `n8n update:workflow --id=X --active=true` | Yes |
|
|
141
|
+
| Direct DB edit (active + activeVersionId) | SQLite `UPDATE` | Yes, on restart |
|
|
142
|
+
| Direct DB edit (active only) | SQLite `UPDATE` | **Won't work** — needs activeVersionId too |
|