@tpsdev-ai/flair 0.29.0 → 0.31.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 +194 -377
- package/dist/cli-shim.cjs +1 -1
- package/dist/cli.js +1567 -366
- package/dist/deploy.js +241 -43
- package/dist/fabric-upgrade.js +77 -24
- package/dist/federation/scheduler.js +500 -0
- package/dist/fleet-verify.js +3 -3
- package/dist/install/clients.js +111 -53
- package/dist/lib/mcp-enable.js +2 -2
- package/dist/lib/mcp-spec.js +128 -0
- package/dist/lib/safe-snapshot-extract.js +231 -0
- package/dist/lib/scheduler-platform.js +128 -0
- package/dist/lib/xml-escape.js +54 -0
- package/dist/rem/scheduler.js +212 -55
- package/dist/rem/snapshot.js +13 -0
- package/dist/replication-convergence.js +505 -0
- package/dist/resources/A2AAdapter.js +1 -1
- package/dist/resources/Admin.js +1 -1
- package/dist/resources/AdminConnectors.js +1 -1
- package/dist/resources/AdminDashboard.js +1 -1
- package/dist/resources/AdminIdp.js +1 -1
- package/dist/resources/AdminInstance.js +1 -1
- package/dist/resources/AdminMemory.js +1 -1
- package/dist/resources/AdminPrincipals.js +1 -1
- package/dist/resources/Agent.js +1 -1
- package/dist/resources/AgentCard.js +1 -1
- package/dist/resources/AgentSeed.js +1 -1
- package/dist/resources/AttentionQuery.js +1 -1
- package/dist/resources/Credential.js +1 -1
- package/dist/resources/Federation.js +1 -1
- package/dist/resources/Instance.js +1 -1
- package/dist/resources/Integration.js +1 -1
- package/dist/resources/MCPClientMetadata.js +1 -1
- package/dist/resources/Memory.js +1 -1
- package/dist/resources/MemoryBootstrap.js +8 -9
- package/dist/resources/MemoryCandidate.js +168 -0
- package/dist/resources/MemoryConsolidate.js +1 -1
- package/dist/resources/MemoryDedupStats.js +1 -1
- package/dist/resources/MemoryFeed.js +1 -1
- package/dist/resources/MemoryGrant.js +1 -1
- package/dist/resources/MemoryMaintenance.js +1 -1
- package/dist/resources/MemoryReflect.js +2 -2
- package/dist/resources/MemoryReindex.js +1 -1
- package/dist/resources/MemoryUsage.js +1 -1
- package/dist/resources/OAuth.js +1 -1
- package/dist/resources/OAuthClient.js +1 -1
- package/dist/resources/OrgEvent.js +1 -1
- package/dist/resources/OrgEventCatchup.js +1 -1
- package/dist/resources/OrgEventMaintenance.js +1 -1
- package/dist/resources/PairingToken.js +1 -1
- package/dist/resources/Peer.js +1 -1
- package/dist/resources/Presence.js +3 -3
- package/dist/resources/RecordUsage.js +1 -1
- package/dist/resources/Relationship.js +1 -1
- package/dist/resources/SemanticSearch.js +18 -46
- package/dist/resources/SkillScan.js +1 -1
- package/dist/resources/Soul.js +1 -1
- package/dist/resources/SoulFeed.js +1 -1
- package/dist/resources/WorkspaceLatest.js +1 -1
- package/dist/resources/WorkspaceState.js +1 -1
- package/dist/resources/XAA.js +1 -1
- package/dist/resources/a2a-url.js +1 -1
- package/dist/resources/abstention.js +1 -1
- package/dist/resources/agent-auth.js +1 -1
- package/dist/resources/agentcard-fields.js +1 -1
- package/dist/resources/auth-middleware.js +1 -1
- package/dist/resources/embeddings-boot.js +14 -16
- package/dist/resources/embeddings-provider.js +15 -12
- package/dist/resources/federation-cleanup.js +5 -5
- package/dist/resources/federation-nonce-store.js +5 -5
- package/dist/resources/health.js +53 -25
- package/dist/resources/in-process.js +225 -0
- package/dist/resources/instance-identity.js +1 -1
- package/dist/resources/mcp-client-metadata-fields.js +1 -1
- package/dist/resources/mcp-handler.js +1 -1
- package/dist/resources/mcp-oauth.js +2 -2
- package/dist/resources/mcp-tools.js +23 -17
- package/dist/resources/memory-read-scope.js +1 -1
- package/dist/resources/memory-reflect-lib.js +3 -3
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +81 -11
- package/dist/resources/migrations/data-dir.js +205 -0
- package/dist/resources/migrations/embedding-stamp.js +3 -3
- package/dist/resources/migrations/graph-heal.js +2 -2
- package/dist/resources/migrations/ledger.js +1 -1
- package/dist/resources/migrations/progress.js +33 -0
- package/dist/resources/migrations/registry.js +14 -5
- package/dist/resources/migrations/runner.js +93 -13
- package/dist/resources/migrations/state.js +13 -2
- package/dist/resources/migrations/synthetic-test-migration.js +1 -1
- package/dist/resources/migrations/visibility-backfill.js +232 -0
- package/dist/resources/models-dir.js +18 -9
- package/dist/resources/record-types.js +28 -0
- package/dist/resources/semantic-retrieval-core.js +6 -5
- package/dist/resources/usage-recording.js +1 -1
- package/dist/src/lib/scheduler-platform.js +128 -0
- package/dist/src/lib/xml-escape.js +54 -0
- package/dist/src/rem/scheduler.js +397 -0
- package/docs/deploying-on-fabric.md +267 -0
- package/docs/deployment.md +6 -0
- package/docs/embedding-in-a-harper-app.md +299 -0
- package/docs/federation.md +61 -4
- package/docs/integrations.md +3 -0
- package/docs/mcp-clients.md +16 -7
- package/docs/quickstart.md +80 -54
- package/docs/releasing.md +72 -38
- package/docs/supply-chain-policy.md +40 -4
- package/docs/troubleshooting.md +24 -0
- package/docs/upgrade.md +100 -5
- package/package.json +4 -14
- package/schemas/memory.graphql +9 -1
- package/templates/bin/flair-federation-sync.sh.tmpl +28 -0
- package/templates/launchd/dev.flair.federation.sync.plist.tmpl +47 -0
- package/templates/systemd/flair-federation-sync.service.tmpl +21 -0
- package/templates/systemd/flair-federation-sync.timer.tmpl +16 -0
- package/dist/resources/rerank-provider.js +0 -569
- package/docs/rerank-provisioning.md +0 -101
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Deploying on Harper Fabric
|
|
2
|
+
|
|
3
|
+
Run Flair as a component on [Harper Fabric](https://www.harperdb.io/) — managed, hosted,
|
|
4
|
+
multi-region — instead of a local process you own. The hosted counterpart to
|
|
5
|
+
[deployment.md](deployment.md); two things define it: you *deploy* rather than install,
|
|
6
|
+
and **you get no shell on the node.**
|
|
7
|
+
|
|
8
|
+
## When to use it
|
|
9
|
+
|
|
10
|
+
| | Standalone local | Harper Fabric |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| You run | `flair init` | `flair deploy` |
|
|
13
|
+
| Shell on the node | yes | **no** |
|
|
14
|
+
| Multi-region / failover | no | yes |
|
|
15
|
+
| Upgrade | `flair upgrade` | `flair upgrade --target <url>` |
|
|
16
|
+
|
|
17
|
+
Choose Fabric for an always-on hub in more than one region. For a single instance on a VPS
|
|
18
|
+
you already have, [Remote Server](../README.md#remote-server) is simpler and keeps your shell.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
### 1. Deploy the component
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export FABRIC_USER=<admin> FABRIC_PASSWORD=<pass>
|
|
28
|
+
|
|
29
|
+
# Validate args and package layout without deploying
|
|
30
|
+
flair deploy --fabric-org <org> --fabric-cluster <cluster> --dry-run
|
|
31
|
+
|
|
32
|
+
flair deploy --fabric-org <org> --fabric-cluster <cluster>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Target defaults to `https://<cluster>.<org>.harperfabric.com`; override with `--target`.
|
|
36
|
+
|
|
37
|
+
Credentials go via the environment, not argv, so they stay out of `ps`. Use
|
|
38
|
+
`--fabric-password-file <path>` (mode `0600`) when scripting; inline flags leak to history.
|
|
39
|
+
|
|
40
|
+
Deploy verifies the served API (Harper reports success for a component serving
|
|
41
|
+
nothing; `--no-verify` opts out), waits 600s for replication (Harper's 120s default
|
|
42
|
+
aborts mid-replicate on Fabric), and polls for convergence before reporting one.
|
|
43
|
+
|
|
44
|
+
> `--fabric-token` is accepted but **fails** — `deploy_component` is Basic-auth only.
|
|
45
|
+
|
|
46
|
+
### 2. Provision
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
flair init --target https://<cluster>.<org>.harperfabric.com \
|
|
50
|
+
--ops-target <ops-url> \
|
|
51
|
+
--cluster-admin-user <user> --cluster-admin-pass <pass> \
|
|
52
|
+
--remote --force
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `--force` is required — this writes to a live instance.
|
|
56
|
+
- `--remote` marks it a federation **hub** and creates the `flair_pair_initiator` role;
|
|
57
|
+
without it, pairing later fails role-not-found.
|
|
58
|
+
- Generated admin password lands in `~/.tps/secrets/flair-fabric-hdb` (mode `0600`);
|
|
59
|
+
`--flair-admin-pass` to choose your own.
|
|
60
|
+
|
|
61
|
+
Run **once**, before serving multi-region traffic — see
|
|
62
|
+
[Multi-region](#multi-region-replication-vs-federation).
|
|
63
|
+
|
|
64
|
+
### 3. Verify
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl -sf https://<cluster>.<org>.harperfabric.com/Health
|
|
68
|
+
flair fleet verify --target https://<cluster>.<org>.harperfabric.com
|
|
69
|
+
flair status --target https://<cluster>.<org>.harperfabric.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. Connect a client
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export FLAIR_URL=https://<cluster>.<org>.harperfabric.com
|
|
76
|
+
|
|
77
|
+
# Register an agent — --ops-target is required, see Ports below
|
|
78
|
+
flair agent add mybot --target "$FLAIR_URL" --ops-target <ops-url>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Also set `FLAIR_PUBLIC_URL` to that URL in the component's environment — OAuth metadata
|
|
82
|
+
and A2A discovery advertise it, or clients see a loopback address.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Ports: the derivation trap
|
|
87
|
+
|
|
88
|
+
Locally, Flair serves data on `19926` and the ops API on `19925`. The CLI derives
|
|
89
|
+
**ops = data − 1** everywhere.
|
|
90
|
+
|
|
91
|
+
A managed endpoint is HTTPS on 443 with no port, so derivation produces **`:442`** —
|
|
92
|
+
where nothing answers:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# --target https://<cluster>.<org>.harperfabric.com → ops derived as :442 ✗
|
|
96
|
+
# --target https://<fabric-node>:19926/<instance> → ops derived as :19925 ✓
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Pass `--ops-target <url>` explicitly** (or set `FLAIR_OPS_TARGET`) on any command that
|
|
100
|
+
touches the ops API: `init --target`, `agent add --target`, `federation token --target`.
|
|
101
|
+
|
|
102
|
+
> **Gap — needs a Fabric account to verify.** This guide does not state the correct
|
|
103
|
+
> ops-API URL for a managed `*.harperfabric.com` instance, or whether the ops API is
|
|
104
|
+
> reachable remotely there at all. The derivation above is certain (read from source);
|
|
105
|
+
> the right value to pass is not.
|
|
106
|
+
|
|
107
|
+
Precedence: `--target` > `--url` > `FLAIR_TARGET` > `FLAIR_URL` > localhost. For ops:
|
|
108
|
+
`--ops-target` > `FLAIR_OPS_TARGET` > derived > localhost.
|
|
109
|
+
|
|
110
|
+
## Auth
|
|
111
|
+
|
|
112
|
+
Flair ships `authorizeLocal: false` — it only governs loopback, so it changes nothing
|
|
113
|
+
here; remote callers always need real credentials.
|
|
114
|
+
|
|
115
|
+
- Admin ops use Basic auth; agents use Ed25519 ([auth.md](auth.md)).
|
|
116
|
+
- `/Health` is public — your liveness check.
|
|
117
|
+
- Don't debug with raw `curl` — on Fabric the auth gate fires before the resource
|
|
118
|
+
handler, so a hand-rolled request 401s in a way that looks like a Flair bug and isn't.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Pairing a spoke to a hosted hub
|
|
123
|
+
|
|
124
|
+
No shell on the hub, so mint the token remotely rather than over `ssh`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# On any machine — note --ops-target, this hits the ops API
|
|
128
|
+
FLAIR_ADMIN_PASS=<hub-admin-password> flair federation token \
|
|
129
|
+
--target https://<cluster>.<org>.harperfabric.com \
|
|
130
|
+
--ops-target <ops-url> > ./pair-triple.json
|
|
131
|
+
|
|
132
|
+
# On the spoke
|
|
133
|
+
flair federation pair https://<cluster>.<org>.harperfabric.com \
|
|
134
|
+
--token-from ./pair-triple.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`federation token` takes `--admin-pass`, **not** `--admin-pass-file`. The triple holds a
|
|
138
|
+
one-time credential — delete it after pairing (60-min TTL, `--ttl`).
|
|
139
|
+
|
|
140
|
+
### Federation is push-only
|
|
141
|
+
|
|
142
|
+
**A spoke pushes up. It cannot pull down.** `POST /FederationSync` is one-directional per
|
|
143
|
+
call and there is no pull endpoint anywhere.
|
|
144
|
+
|
|
145
|
+
For both directions, each side pairs **as a spoke of the other**: two pairings, two
|
|
146
|
+
tokens, two syncs. No setting makes sync bidirectional.
|
|
147
|
+
|
|
148
|
+
Syncs `Memory`, `Soul`, `Agent`, `Relationship`. **`Presence` is not federated** — no
|
|
149
|
+
cross-spoke roster.
|
|
150
|
+
|
|
151
|
+
### Keeping a spoke synced
|
|
152
|
+
|
|
153
|
+
`flair federation sync` is one-shot; `watch` dies with its terminal. Use the scheduled
|
|
154
|
+
driver ([federation.md](federation.md#keeping-it-synced)):
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
flair federation sync enable --interval 300
|
|
158
|
+
flair federation sync status
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> **The driver is local-only.** It writes a launchd job or systemd timer **on the machine
|
|
162
|
+
> running the CLI**; `--target` just points that local job at a remote instance. You
|
|
163
|
+
> cannot install a driver on a Fabric node.
|
|
164
|
+
|
|
165
|
+
A periodic one-shot, not a supervised watcher; `--interval` is the latency knob.
|
|
166
|
+
`flair federation status` omits its driver verdict for a remote `--target`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Multi-region: replication vs federation
|
|
171
|
+
|
|
172
|
+
Fabric gives you N regional nodes running one component — **not** N Flair instances.
|
|
173
|
+
Instance identity lives in the `Instance` table (`schemas/federation.graphql`); it is a
|
|
174
|
+
Harper table, so **it replicates** — every node shares one Flair identity.
|
|
175
|
+
|
|
176
|
+
| Layer | Handles | Scope |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| Harper replication | your app across its own regions | intra-app, automatic |
|
|
179
|
+
| Flair federation | this app ↔ another Flair instance | instance-to-instance, you pair it |
|
|
180
|
+
|
|
181
|
+
**You do not federate your own regions to each other** — Harper already did. Use
|
|
182
|
+
`flair federation pair` only to reach a *separate* Flair instance.
|
|
183
|
+
|
|
184
|
+
One asymmetry: identity replicates, but `flair-instance.yaml` (port config, per data
|
|
185
|
+
directory) does not. A node reporting a different **port** is expected; a different
|
|
186
|
+
**instance id** is not.
|
|
187
|
+
|
|
188
|
+
Provision once, from one place, before serving multi-region traffic: step 2's
|
|
189
|
+
`flair init … --remote` seeds the identity row so it exists before any node needs it.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Operating it
|
|
194
|
+
|
|
195
|
+
### What works remotely
|
|
196
|
+
|
|
197
|
+
| Command | Gives you |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `GET /Health` | liveness, no auth |
|
|
200
|
+
| `flair status --target <url>` | subsystem rollups; the only byte counts available remotely |
|
|
201
|
+
| `flair quality --target <url>` | recall/coverage; halted-migration reasons |
|
|
202
|
+
| `flair fleet verify --target <url>` | health, auth, version across origin + Flair peers |
|
|
203
|
+
| `flair federation status\|verify\|reachability --target <url>` | peer table, sync recency, probes |
|
|
204
|
+
|
|
205
|
+
`fleet verify` exit codes: 1 origin failed, 2 peer version skew, 3 peer unverifiable.
|
|
206
|
+
|
|
207
|
+
> **A credential mismatch renders as an empty section, not an error.** `flair status`
|
|
208
|
+
> reads `/HealthDetail` with `FLAIR_ADMIN_PASS` / `HDB_ADMIN_PASSWORD` / a pinned agent
|
|
209
|
+
> key — **not** the `FABRIC_*` credentials `deploy` and `fleet verify` use. On failure it
|
|
210
|
+
> renders blank — a blank Disk section means "couldn't authenticate" as often as
|
|
211
|
+
> "nothing to report".
|
|
212
|
+
|
|
213
|
+
### What doesn't
|
|
214
|
+
|
|
215
|
+
**`flair doctor` takes no `--target`** — it hardcodes localhost, reads a local PID file
|
|
216
|
+
and shells out to `lsof`. The command you'd reach for when something breaks is unavailable
|
|
217
|
+
here. Unavailable too: `start`, `stop`, `restart`, `snapshot`, `reembed`, `rem`, `bridge`.
|
|
218
|
+
|
|
219
|
+
**Fabric's own cluster topology is invisible.** `fleet verify` sweeps *Flair's* federation
|
|
220
|
+
peer table, not Harper's cluster nodes — `cluster_status` is harper-pro-only and absent
|
|
221
|
+
from the OSS `harper` build. **`0 peers known` means "0 on file", never "0 exist."**
|
|
222
|
+
|
|
223
|
+
**There is no disk or quota telemetry.** `flair status` reports usage for two directories:
|
|
224
|
+
no free space, no total, no quota, no warning threshold, walk capped at six levels, no
|
|
225
|
+
per-component size. `system_information` is never called — an instance can hit its quota
|
|
226
|
+
with nothing saying so. The one indirect signal is a migration halting for space.
|
|
227
|
+
|
|
228
|
+
> **On `get_components`.** A source comment claims Harper excludes `node_modules`
|
|
229
|
+
> server-side. That is uncited and unverified, so this guide doesn't rely on it — and it
|
|
230
|
+
> wouldn't matter: Flair calls it only as a post-failure convergence oracle and discards
|
|
231
|
+
> the `size` field. Component disk usage is invisible structurally.
|
|
232
|
+
|
|
233
|
+
### Known hazard: unbounded npm cache
|
|
234
|
+
|
|
235
|
+
**Open — [flair#886](https://github.com/tpsdev-ai/flair/issues/886).** Every deploy runs a
|
|
236
|
+
server-side `npm install` using the node's default cache. npm never evicts it, so it
|
|
237
|
+
grows until it fills the quota.
|
|
238
|
+
|
|
239
|
+
No in-product mitigation: no cache flag, no alternate location, no cleanup. One install
|
|
240
|
+
per deploy bounds the *rate*, not the total. Harper consults `install_command` only when
|
|
241
|
+
`node_modules` is absent and `deploy_component` has no force-reinstall option, so the
|
|
242
|
+
obvious fix isn't available — and clearing the cache needs node access this shape
|
|
243
|
+
doesn't give you.
|
|
244
|
+
|
|
245
|
+
### Backup and rollback
|
|
246
|
+
|
|
247
|
+
`flair snapshot` is local-only. Back up before every upgrade — `flair backup` takes
|
|
248
|
+
**`--url`**, not `--target`:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
flair backup --url https://<cluster>.<org>.harperfabric.com \
|
|
252
|
+
--admin-pass-file <path> --output ./flair-backup.json
|
|
253
|
+
|
|
254
|
+
# --check shows the version diff and plan without deploying
|
|
255
|
+
flair upgrade --target https://<cluster>.<org>.harperfabric.com --check
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Full path: [upgrade.md](upgrade.md#upgrading-a-fabric-deployed-instance).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## See also
|
|
263
|
+
|
|
264
|
+
- [deployment.md](deployment.md) — the standalone local shape
|
|
265
|
+
- [upgrade.md](upgrade.md#upgrading-a-fabric-deployed-instance) — Fabric upgrades, fleet verify
|
|
266
|
+
- [federation.md](federation.md) — pairing, sync driver, conflict resolution
|
|
267
|
+
- [spoke-bringup.md](spoke-bringup.md) · [auth.md](auth.md) · [secrets-and-keys.md](secrets-and-keys.md)
|
package/docs/deployment.md
CHANGED
|
@@ -116,6 +116,11 @@ Note: embeddings run on CPU in Docker (no Metal acceleration). Performance is ac
|
|
|
116
116
|
|
|
117
117
|
Deploying to a Harper Fabric cluster is a different mechanism from the installs above — `flair deploy` pushes Flair as a cluster component instead of `npm install -g`. To upgrade an already-deployed Fabric instance in place, use `FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> flair upgrade --target <fabric-url>` (or `--fabric-password-file <path>` in place of the env var), not the local upgrade path. Inline `--fabric-user`/`--fabric-password` flags also work but are discouraged — both leak to shell history and `ps`. See [`docs/upgrade.md` — Upgrading a Fabric-deployed instance](upgrade.md#upgrading-a-fabric-deployed-instance) for the full walkthrough, including the automatic post-deploy fleet-convergence sweep.
|
|
118
118
|
|
|
119
|
+
For the hosted shape end to end — when to choose it, ports and auth against a managed
|
|
120
|
+
Fabric endpoint, pairing local spokes to a hosted hub, and what you can and cannot
|
|
121
|
+
observe without a shell on the node — see
|
|
122
|
+
[`docs/deploying-on-fabric.md`](deploying-on-fabric.md).
|
|
123
|
+
|
|
119
124
|
---
|
|
120
125
|
|
|
121
126
|
## Remote Access
|
|
@@ -179,6 +184,7 @@ Set these in the Flair process environment (`~/Library/LaunchAgents/ai.tpsdev.fl
|
|
|
179
184
|
| `HDB_ADMIN_PASSWORD` | Bootstrap password for the embedded Harper. After first start, the persisted user record is the source of truth; rotate via the Harper ops API, not by changing this env var. | Set at install time. See [secrets-and-keys.md](secrets-and-keys.md) for rotation. |
|
|
180
185
|
| `FLAIR_KEY_PASSPHRASE` | Passphrase used to derive the AES-256-GCM key that wraps federation private-key seeds at rest. Auto-generated to `~/.flair/keys/.passphrase` if unset. | Set explicitly for production federation deployments so the passphrase isn't auto-generated and lost on disk wipe. |
|
|
181
186
|
| `HTTP_PORT` | Override the Harper HTTP port. Useful for sandboxes; production deployments should configure the port in `config.yaml` instead. | Rare. |
|
|
187
|
+
| `FLAIR_OPS_BIND` | Bind address for the Harper **ops API**. Resolution order: `flair init --ops-bind` > this variable > the `opsBind` key `flair init` persists in `~/.flair/config.yaml` > `127.0.0.1`. Every Flair-managed Harper start re-asserts the resolved value, so the persisted key is what makes a choice survive `flair restart` / `flair upgrade`. | Only for deployments that genuinely need remote ops admin (multi-host / Fabric) — set it to `0.0.0.0`, or record it once with `flair init --ops-bind 0.0.0.0`. Single-host installs want the loopback default. |
|
|
182
188
|
|
|
183
189
|
---
|
|
184
190
|
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Embedding Flair in a Harper app
|
|
2
|
+
|
|
3
|
+
Flair is a Harper component. If your app already runs on Harper, load Flair into the same instance and call it **in-process** — no HTTP, no second process, and **no shell on the node**.
|
|
4
|
+
|
|
5
|
+
Everything below is code you run inside your own component. The CLI is an alternative for local development, not a requirement — see [If you have shell access](#if-you-have-shell-access).
|
|
6
|
+
|
|
7
|
+
| | Embedded | Standalone ([deployment.md](deployment.md)) |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Latency | A method call | HTTP round trip |
|
|
10
|
+
| Agent identity | You assert it per call | Ed25519 signature, verified server-side |
|
|
11
|
+
| Trust boundary | Callers are **inside** it | Callers are outside it, and must authenticate |
|
|
12
|
+
|
|
13
|
+
Embedding *adds* the in-process path; `rest: true` keeps serving MCP clients and remote agents as before.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
**1. Add Flair to your instance.** Deploy `@tpsdev-ai/flair` as a component the way you deploy your own — Fabric's component deploy, Studio, or your pipeline. Its tables are declared `@table(database: "flair")`, so they never collide with yours.
|
|
20
|
+
|
|
21
|
+
**2. Resolve the resource and write a memory.**
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import { server } from "harper";
|
|
25
|
+
// Flair ships these two helpers so you do not have to get either of them right
|
|
26
|
+
// by hand. They are the whole in-process contract.
|
|
27
|
+
import { agentContext, internalContext, collectionResource } from "@tpsdev-ai/flair/dist/resources/in-process.js";
|
|
28
|
+
|
|
29
|
+
// The RESOURCE — carries auth, scoping, visibility, embedding.
|
|
30
|
+
// NOT databases.flair.Memory: that is the raw table, and enforces none of it.
|
|
31
|
+
// Keys carry NO leading slash: get("Memory"), never get("/Memory").
|
|
32
|
+
const flair = (path) => server.resources.get(path).Resource;
|
|
33
|
+
|
|
34
|
+
export async function remember(agentId, content, opts = {}) {
|
|
35
|
+
// A create needs a COLLECTION-bound instance. `new Cls(...)` does not give
|
|
36
|
+
// you one, and cannot be made to — see the note below.
|
|
37
|
+
const h = await collectionResource(flair("Memory"), agentContext(agentId));
|
|
38
|
+
return h.post({
|
|
39
|
+
agentId, // required — an absent one is never filled in
|
|
40
|
+
content,
|
|
41
|
+
durability: opts.durability ?? "standard",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> ### Why `collectionResource`, and not `new Memory()`
|
|
47
|
+
>
|
|
48
|
+
> A resource's `post()` only works on an instance Harper has marked as a **collection**, and that mark is a *private* field only Harper's own `getResource()` can set. The public `isCollection` is a getter with no setter, so the obvious spelling fails two different ways, neither of which names the cause:
|
|
49
|
+
>
|
|
50
|
+
> ```javascript
|
|
51
|
+
> const h = new (flair("Memory"))(undefined, agentContext(agentId));
|
|
52
|
+
> h.isCollection = true; // TypeError: Cannot set property isCollection ... which has only a getter
|
|
53
|
+
> h.post({ ... }); // without the line above: 405 "The Memory does not have a post method implemented"
|
|
54
|
+
> ```
|
|
55
|
+
>
|
|
56
|
+
> `collectionResource(Cls, context)` is a two-line wrapper over the supported call — `Cls.getResource({}, context, { isCollection: true })` — and exists so this is written once. **Reads do not need it:** `Cls.get(id, context)` and `Cls.search(query, context)` thread the context themselves.
|
|
57
|
+
|
|
58
|
+
> ### ⚠️ A resource with no context is an administrator
|
|
59
|
+
>
|
|
60
|
+
> A resource built without a context resolves to Flair's trusted `internal` verdict and runs **unfiltered** — every read unscoped, every write unowned. Silently. No error, no warning, no trace.
|
|
61
|
+
>
|
|
62
|
+
> Measured, not inferred: a context-less `Memory.search()` returns every agent's `private` records, and so does a context-less `SemanticSearch`.
|
|
63
|
+
>
|
|
64
|
+
> Correct for Flair's own maintenance passes. In your app it is a data leak you find months later.
|
|
65
|
+
>
|
|
66
|
+
> **Make `agentId` a required argument, as above.** Never export a version that defaults it.
|
|
67
|
+
|
|
68
|
+
**3. Read it back, scoped to that agent.**
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
export async function recall(agentId, query, limit = 5) {
|
|
72
|
+
const h = await collectionResource(flair("SemanticSearch"), agentContext(agentId));
|
|
73
|
+
return h.post({ q: query, limit });
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**4. Verify it worked, still in-process.**
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
await remember("agent-alpha", "deploy runs at 0200 UTC");
|
|
81
|
+
console.log(await recall("agent-alpha", "deploy schedule"));
|
|
82
|
+
console.log([...server.resources.keys()].sort()); // what Flair registered
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> ### What we measured, so you do not have to
|
|
86
|
+
>
|
|
87
|
+
> Run end to end on **Harper 5.1.22**, from a second component loaded into the same instance — the exact shape above. `test/integration/in-process-agents.test.ts` in the Flair repo is that run, and `test/fixtures/inproc-app` is the component it drives.
|
|
88
|
+
>
|
|
89
|
+
> | Claim | Result |
|
|
90
|
+
> |---|---|
|
|
91
|
+
> | `server.resources.get("Memory")` from another component | Returns an **entry object** `{ Resource, path, exportTypes, hasSubPaths, relativeURL }` — `.Resource` is required, it is not the class itself |
|
|
92
|
+
> | `.Resource` is Flair's resource, not the raw table | Confirmed: prototype chain `Memory → Memory → Resource`, and it is **not** `databases.flair.Memory` |
|
|
93
|
+
> | Key format | **No leading slash.** `get("Memory")` hits; `get("/Memory")` returns `undefined` |
|
|
94
|
+
> | `getMatch` | `getMatch("Memory")` hits. **`getMatch("/Memory")` misses** — do not use the slashed form |
|
|
95
|
+
> | When the lookup becomes valid | Flair's resources were already registered at the app component's **module top level** (55 entries, `Memory` and `Agent` present). The only entry missing at that moment was the app's *own*, still mid-registration. Resolving lazily, as above, is still the advice — it costs nothing and does not depend on component load order |
|
|
96
|
+
> | Per-agent scoping through `SemanticSearch` | Holds. Querying as `agent-beta` for a topic only `agent-alpha` has written returns **beta's own** memory, never alpha's private one — with real 768-dim embeddings attached, not a degraded path |
|
|
97
|
+
> | Cross-agent by-id read | `Memory.get(<beta's private id>)` as alpha returns **404**, never 403 — a denied caller cannot enumerate ids |
|
|
98
|
+
> | Context-less call | Unfiltered across all agents, via both `search` and `SemanticSearch` (see the warning above) |
|
|
99
|
+
|
|
100
|
+
Handlers return a `Response` for `401`/`403`/`400` rather than throwing — check for one. `Memory.post()` is in-process only; over HTTP the schema exposes `PUT`.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## N agents in one process
|
|
105
|
+
|
|
106
|
+
**Acting as an agent needs nothing but the context.** Identity resolves per call from `request.tpsAgent`, so one process serves any number of agents — no client to construct, no key to load, no per-agent setup:
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
for (const id of ["planner", "researcher", "reviewer"]) {
|
|
110
|
+
await remember(id, `${id} came online`);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`tpsAgent` is **not** checked against the `Agent` table, so this works with no registration at all. Register agents anyway — the admin UI, federation, and the HTTP path all read those records.
|
|
115
|
+
|
|
116
|
+
### Registering agents, no CLI
|
|
117
|
+
|
|
118
|
+
Go through the `Agent` **resource**, with no context — provisioning is infrastructure work your app has already authorised, and `Agent.post()` fills in the whole Principal shape for you:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
export async function registerAgent(id, { publicKey = "pending", admin = false } = {}) {
|
|
122
|
+
const h = await collectionResource(flair("Agent"), internalContext()); // provisioning is infrastructure, not an agent's write
|
|
123
|
+
return h.post({
|
|
124
|
+
id, name: id, displayName: id,
|
|
125
|
+
publicKey, // a placeholder is fine — see below
|
|
126
|
+
runtime: "headless",
|
|
127
|
+
...(admin ? { role: "admin", admin: true } : {}), // role is what actually grants admin
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Verified against a real instance: that lands `kind: "agent"`, `status: "active"`, `displayName`, `admin: false`, `defaultTrustTier: "unverified"`, `type: "agent"`, `createdAt`/`updatedAt` and the federation `originatorInstanceId` stamp — without you naming any of them.
|
|
133
|
+
|
|
134
|
+
> **Prefer this to `databases.flair.Agent.put()`.** The raw table applies **no** defaults, so a hand-written literal has to reproduce every field above and then stay in step with Flair as the Principal model grows. Records written that way are missing `kind`/`status`/`defaultTrustTier` and read as under-specified Principals in the admin surfaces.
|
|
135
|
+
|
|
136
|
+
> **`isAdmin()` reads `role === "admin"`, not the `admin` boolean.** They are separate fields and only `role` grants admin rights; set both to keep the record self-consistent. Admin lookups are cached for 60 seconds, so a newly-created admin is not effective immediately.
|
|
137
|
+
|
|
138
|
+
`publicKey` is non-nullable in the schema, but it does not have to be a real key. An agent that only ever acts in-process never authenticates, and Flair's own paths write placeholders — `"pending"` when seeding, `mcp-oauth:<sub>` for token-authenticated agents. Give an agent a real key only if it must also authenticate **over HTTP**, which your app can do without any CLI:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
142
|
+
|
|
143
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
144
|
+
const raw = publicKey.export({ format: "der", type: "spki" }).subarray(-32);
|
|
145
|
+
await registerAgent("remote-worker", { publicKey: raw.toString("hex") });
|
|
146
|
+
// keep `privateKey` in your own secret store — Flair never sees it
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Flair accepts the public key as 64-char hex, or base64 of the raw 32 bytes.
|
|
150
|
+
|
|
151
|
+
Both verified end to end: an agent registered this way, with a key its app minted, then authenticated over HTTP with a real `TPS-Ed25519` signature — and a request signed with the *wrong* key was rejected.
|
|
152
|
+
|
|
153
|
+
> **Two traps.** `Agent.put()` on the *resource* silently strips `publicKey`, so set the key when you **create** the record; to rotate one later, write `databases.flair.Agent.put({ ...existing, publicKey })` on the raw table (which is what `flair agent rotate-key` does through the admin ops API — there is no dedicated endpoint today). And do **not** call `AgentSeed` in-process: although its `allowCreate()` explicitly permits the trusted `internal` verdict, its `post()` then re-checks for a named admin and returns `403 forbidden: admin only` — confirmed by running it.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## The table is not the resource
|
|
158
|
+
|
|
159
|
+
Flair declares `Memory` as a table, then exports a **subclass** as the resource. Harper's loader registers exported classes in the routing map only, never back into `databases`, so the two stay distinct:
|
|
160
|
+
|
|
161
|
+
| Import | What you get |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `server.resources.get("Memory").Resource` | **The resource.** Auth, read-scoping, private-memory exclusion, no-forge attribution, embedding generation, default visibility, rate limiting. |
|
|
164
|
+
| `databases.flair.Memory` | **The table.** Raw storage. None of the above. |
|
|
165
|
+
|
|
166
|
+
Flair uses the raw table where it *wants* to bypass its own rules — the federation merge path writes through it so a synced record's origin stamp survives. Use it only for work you have authorised yourself: provisioning, migrations, admin sweeps, reporting. Never for a memory written on an agent's behalf.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Identity
|
|
171
|
+
|
|
172
|
+
| Context | Verdict | Effect |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `{ request: { tpsAnonymous: true } }` | `anonymous` | Denied everywhere |
|
|
175
|
+
| `{ request: { tpsAgent: "mybot" } }` | `agent` | Scoped to that agent — **use this** |
|
|
176
|
+
| `{ request: { tpsAgent: "mybot", tpsAgentIsAdmin: true } }` | admin | Unfiltered reads, cross-agent writes |
|
|
177
|
+
| **Nothing**, or an empty/missing `tpsAgent` | `internal` | **Trusted. Unfiltered.** See the warning above. |
|
|
178
|
+
|
|
179
|
+
Build these with `agentContext(id)`, `adminContext(id)` and `internalContext()` rather than by hand — see [below](#the-api-is-built-so-omission-cannot-happen-quietly) for why the hand-written form is a trap.
|
|
180
|
+
|
|
181
|
+
### The context object is a security boundary
|
|
182
|
+
|
|
183
|
+
**In-process identity is asserted, not verified.** Flair reads `request.tpsAgent` and acts as that agent. There is no signature check, no lookup against the `Agent` table, and no registration requirement — an id that has never been registered acts as an agent immediately. `tpsAgentIsAdmin: true` is asserted exactly the same way, and nothing checks that the named agent is really an admin.
|
|
184
|
+
|
|
185
|
+
That is deliberate. A co-located caller is already inside the trust boundary and could write `databases.flair.Memory` directly, so demanding a signature from same-process code would be theatre. Ed25519 is how agents *outside* the process prove identity.
|
|
186
|
+
|
|
187
|
+
The consequence is the single most important line in this guide:
|
|
188
|
+
|
|
189
|
+
> **Build the context from your own server-side state. Never from request data.**
|
|
190
|
+
>
|
|
191
|
+
> If an agent id can reach `agentContext()` from user input — a body field, a query param, a header you did not verify yourself — that is privilege escalation with **no error, no 403 and no trace**. Authenticate the caller with your own mechanism first, then map the identity *you* established onto `tpsAgent`.
|
|
192
|
+
|
|
193
|
+
There are exactly two ways to lose the model, from opposite ends. Both are pinned as tests in the Flair repo:
|
|
194
|
+
|
|
195
|
+
| | |
|
|
196
|
+
|---|---|
|
|
197
|
+
| **By omission** | No usable agent id ⇒ `internal` ⇒ admin-equivalent, unfiltered. |
|
|
198
|
+
| **By assertion** | An attacker-influenced `agentId` is honoured verbatim. |
|
|
199
|
+
|
|
200
|
+
### The API is built so omission cannot happen quietly
|
|
201
|
+
|
|
202
|
+
`resolveAgentAuth` tests `tpsAgent` for *truthiness*, so a missing or empty id is indistinguishable from "no identity supplied" — which is the trusted, unfiltered verdict. Measured:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
resolveAgentAuth({ request: { tpsAgent: undefined } }) -> { kind: "internal" }
|
|
206
|
+
allowAdmin({ request: { tpsAgent: undefined } }) -> true
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
That would turn the most ordinary bug there is — `agentContext(session.agentId)` where the field came back undefined — into silent administrator access. So the helpers refuse rather than default:
|
|
210
|
+
|
|
211
|
+
| | |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `agentContext(id)` | **Throws** `InProcessContextError` on a missing, empty or blank id. Takes **no options**, so no object spread into it can escalate. |
|
|
214
|
+
| `adminContext(id)` | The *only* way to get admin authority. Same id guard. |
|
|
215
|
+
| `internalContext()` | The *only* way to get the unfiltered verdict. |
|
|
216
|
+
| `collectionResource(Cls, context)` | Context is **required**; omitting it throws rather than granting `internal`. |
|
|
217
|
+
|
|
218
|
+
The privileged paths are now the longest ones to type, and `git grep "adminContext\|internalContext"` enumerates every deliberate escalation in your codebase. These are runtime guards, not type annotations — a plain-JavaScript embedder gets exactly the same protection.
|
|
219
|
+
|
|
220
|
+
### Individual identities, not one app identity
|
|
221
|
+
|
|
222
|
+
Give every agent its own. A per-agent context costs nothing — no client to construct, no key to load, no per-agent setup, not even a registration. Collapsing N agents onto one shared identity buys you nothing and loses the two things that make the memory model work: **per-agent attribution**, which is what trust grading and provenance are computed from, and **N separate blast radii**, which become one.
|
|
223
|
+
|
|
224
|
+
### In a cluster
|
|
225
|
+
|
|
226
|
+
Harper replicates every table in a replicated database unless the table opts out with `@table(replicate: false)`. None of Flair's do. So:
|
|
227
|
+
|
|
228
|
+
- **The registry replicates.** An agent registered on node A is visible on node B with no coordination. (Replication comes from the *database* being replicated — not from `@export`, which only controls REST exposure. `Memory` carries no `@export` and still replicates.)
|
|
229
|
+
- **Authority is local.** The context is constructed per call, in whichever process handles it. No node asks another who a caller is.
|
|
230
|
+
- **Attribution travels.** `agentId` is a field *on the record*, so a memory written on node A reads back correctly attributed — and correctly scoped — wherever it lands. Verified as far as this can be without a cluster: a fresh Harper process over the same storage resolves identical per-agent scope, so none of it lives in the process that did the writing.
|
|
231
|
+
|
|
232
|
+
The consequence, stated plainly because someone will ask: **every node running the app is equally trusted**, since each one can assert any identity. That is fine for one application spread across regions — it is a single trust domain by construction. It is **not** fine for running this component beside untrusted co-tenants on the same instance. Co-location *is* the grant.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Coexisting with your components
|
|
237
|
+
|
|
238
|
+
Flair's instance-wide middleware runs first but is **non-rejecting** — unrecognised requests pass through rather than 401'ing, so your component's auth keeps working. Two collisions to check:
|
|
239
|
+
|
|
240
|
+
- **`/Admin` is a prefix match.** Flair 401s any unauthenticated path *starting with* `/Admin` — including an app route like `/AdminPanel`. Rename yours.
|
|
241
|
+
- **Top-level paths.** Flair claims ~53, including `Memory`, `Agent`, `Instance`, `Integration`, `Credential`, `Health`, `Presence`, `Relationship`, `Soul`, `SemanticSearch`. `server.resources.keys()` lists them.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Federation
|
|
246
|
+
|
|
247
|
+
Pairing an embedded instance to an external hub as a spoke **requires shell access today** — the pairing and sync commands are CLI-only. If your deployment has no shell, federation is not available to it yet.
|
|
248
|
+
|
|
249
|
+
> **Sync is push-only — one direction per call.** A spoke pushes its records up and **receives nothing back**. There is no pull endpoint, and nothing initiates a hub-to-spoke push. Do not plan on reading another instance's memories through the hub.
|
|
250
|
+
>
|
|
251
|
+
> For records both ways, each instance must pair **as a spoke of the other** — two pairings, each side running its own sync.
|
|
252
|
+
|
|
253
|
+
Private memories are never pushed. Full walkthrough: [federation.md](federation.md).
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## If you have shell access
|
|
258
|
+
|
|
259
|
+
Local development and self-hosted installs can drive the same things from the CLI:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
flair doctor
|
|
263
|
+
flair agent add mybot # registers + generates and stores a keypair
|
|
264
|
+
flair agent list
|
|
265
|
+
flair memory search "deploy schedule" --agent mybot
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Federation, which has no in-process equivalent:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
flair federation token --admin-pass <hub-admin-pass> > triple.json
|
|
272
|
+
flair federation pair <hub-url> --token-from ./triple.json
|
|
273
|
+
flair federation sync enable --interval 300
|
|
274
|
+
flair federation status
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`sync enable` landed **after 0.30.0**; on an older install, schedule the one-shot yourself.
|
|
278
|
+
|
|
279
|
+
A `SyncLog` row reads `direction: "pull"` — the receiver's label for a push it accepted. `"push"` is never written. Not a pull path.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Choosing a surface
|
|
284
|
+
|
|
285
|
+
| Surface | Use when |
|
|
286
|
+
|---|---|
|
|
287
|
+
| **In-process resource** | Your app's agents, same instance. No round trip — and no auth safety net. |
|
|
288
|
+
| **[`@tpsdev-ai/flair-client`](https://www.npmjs.com/package/@tpsdev-ai/flair-client)** | Out of process: another host, runtime or sidecar. One client per `agentId`. |
|
|
289
|
+
| **Native `/mcp`** | MCP clients against this instance. Identity from the OAuth token. |
|
|
290
|
+
| **`@tpsdev-ai/flair-mcp`** (stdio) | Local MCP clients. Talks HTTP via `FlairClient`. |
|
|
291
|
+
| **`flair` CLI** | Pairing and key generation. Needs a shell. |
|
|
292
|
+
|
|
293
|
+
**Trust-graded recall (`includeTrust`)** — provenance, author, usage, freshness, supersession — works in-process (`SemanticSearch.post({ q, includeTrust: true })`, `Memory.get(id, { includeTrust: true })`), over REST, and on the native `/mcp` handler. It is **not** exposed by the CLI, the stdio MCP server, or `FlairClient`'s typed methods.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## See also
|
|
298
|
+
|
|
299
|
+
[Integrations](integrations.md) · [Deployment](deployment.md) · [Federation](federation.md) · [Auth](auth.md) · [Architecture](../DESIGN.md)
|