@tpsdev-ai/flair 0.51.1 → 0.52.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 +10 -5
- package/dist/build-info.json +3 -3
- package/dist/cli.js +575 -547
- package/dist/doctor-client.js +35 -0
- package/dist/hook-install.js +74 -0
- package/dist/install/global-bin-path.js +14 -0
- package/dist/lib/auth-resolve.js +15 -0
- package/dist/lib/doctor-run.js +28 -15
- package/dist/lib/upgrade-exec-path.js +257 -0
- package/dist/lib/upgrade-plain-tree.js +558 -0
- package/dist/rem/promote-policy.js +204 -0
- package/dist/rem/restore.js +55 -15
- package/dist/rem/runner.js +203 -20
- package/dist/resources/AdminMemory.js +2 -1
- package/dist/resources/AgentSeed.js +26 -10
- package/dist/resources/Asset.js +203 -0
- package/dist/resources/AutoPromoteCandidates.js +2 -4
- package/dist/resources/Credential.js +14 -0
- package/dist/resources/Federation.js +80 -0
- package/dist/resources/Integration.js +12 -0
- package/dist/resources/Memory.js +158 -60
- package/dist/resources/MemoryBootstrap.js +63 -20
- package/dist/resources/MemoryCandidate.js +12 -0
- package/dist/resources/MemoryConsolidate.js +2 -1
- package/dist/resources/MemoryDedupStats.js +17 -2
- package/dist/resources/MemoryFeed.js +30 -0
- package/dist/resources/MemoryGrant.js +14 -0
- package/dist/resources/MemoryReflect.js +75 -17
- package/dist/resources/Message.js +190 -0
- package/dist/resources/OrgEvent.js +12 -0
- package/dist/resources/PromoteMemoryCandidate.js +76 -0
- package/dist/resources/RecordUsage.js +1 -1
- package/dist/resources/Relationship.js +12 -0
- package/dist/resources/SemanticSearch.js +45 -13
- package/dist/resources/Soul.js +54 -18
- package/dist/resources/WorkspaceState.js +12 -0
- package/dist/resources/auth-middleware.js +17 -44
- package/dist/resources/authority-field-guard.js +37 -0
- package/dist/resources/bm25-index-service.js +1 -1
- package/dist/resources/bm25-index.js +50 -11
- package/dist/resources/embedding-space-guard.js +238 -0
- package/dist/resources/embeddings-provider.js +32 -5
- package/dist/resources/federation-classify.js +23 -1
- package/dist/resources/health.js +11 -2
- package/dist/resources/hit-tracking.js +244 -0
- package/dist/resources/mcp-tools.js +272 -7
- package/dist/resources/memory-reflect-lib.js +111 -0
- package/dist/resources/migrations/embedding-stamp.js +22 -4
- package/dist/resources/owner-field-guard.js +62 -0
- package/dist/resources/promotion-stamp.js +29 -0
- package/dist/resources/record-owner-guard.js +71 -5
- package/dist/resources/record-types.js +30 -7
- package/dist/resources/relay-lib.js +205 -0
- package/dist/resources/relay-ops.js +294 -0
- package/dist/resources/skill-write.js +120 -0
- package/dist/resources/soul-adk-guard.js +68 -0
- package/dist/resources/soul-write-policy.js +63 -0
- package/dist/resources/table-helpers.js +2 -0
- package/dist/resources/usage-recording.js +3 -3
- package/dist/src/rem/promote-policy.js +204 -0
- package/docs/api-reference.md +374 -0
- package/docs/auth.md +52 -0
- package/docs/federation.md +4 -0
- package/docs/integrations.md +6 -6
- package/docs/mcp-clients.md +16 -1
- package/docs/releasing.md +11 -8
- package/docs/rem.md +20 -2
- package/docs/upgrade.md +47 -2
- package/package.json +13 -8
- package/schemas/memory.graphql +51 -2
- package/schemas/message.graphql +74 -0
package/docs/integrations.md
CHANGED
|
@@ -90,13 +90,13 @@ n8n still uses Harper admin Basic auth — it is not this path. See [n8n.md](n8n
|
|
|
90
90
|
|
|
91
91
|
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:
|
|
92
92
|
|
|
93
|
-
**Claude Code** (`~/.
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
env = { FLAIR_AGENT_ID = "claude-code" }
|
|
93
|
+
**Claude Code** (user scope lives in `~/.claude.json` — the file `claude mcp add`, `flair init`, and `flair doctor` all read and write):
|
|
94
|
+
```bash
|
|
95
|
+
claude mcp add flair --scope user \
|
|
96
|
+
-e FLAIR_AGENT_ID=claude-code \
|
|
97
|
+
-- npx -y @tpsdev-ai/flair-mcp
|
|
99
98
|
```
|
|
99
|
+
Or, for a project-scoped server checked into the repo, add the same `mcpServers.flair` block (Cursor's shape below) to `.mcp.json`.
|
|
100
100
|
|
|
101
101
|
**Cursor** (`~/.cursor/mcp.json`):
|
|
102
102
|
```json
|
package/docs/mcp-clients.md
CHANGED
|
@@ -284,7 +284,7 @@ Twelve tools, kept deliberately small:
|
|
|
284
284
|
| `memory_delete` | Remove a memory |
|
|
285
285
|
| `relationship_store` | Record a subject-predicate-object relationship triple (e.g. "nathan manages flair") |
|
|
286
286
|
| `bootstrap` | Get session-start context: soul + recent memories + predicted-relevant context |
|
|
287
|
-
| `soul_set` |
|
|
287
|
+
| `soul_set` | Runtime writes are refused; use operator credentials through `flair soul set` |
|
|
288
288
|
| `soul_get` | Get a soul entry |
|
|
289
289
|
| `flair_workspace_set` | Set your agent's current workspace state (ref/branch, phase, task) in the Office Space |
|
|
290
290
|
| `flair_orgevent` | Publish an org-wide coordination event (claim/release/status) to the Office Space |
|
|
@@ -355,3 +355,18 @@ Future MCP-capable agent CLIs (and there are more landing every month) will work
|
|
|
355
355
|
**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.
|
|
356
356
|
|
|
357
357
|
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).
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
### Task-aware bootstrap budgets
|
|
361
|
+
|
|
362
|
+
Pass `currentTask` when the session's task is known. Bootstrap reserves 30% of
|
|
363
|
+
`maxTokens` for task-relevant memories after Soul admission, protecting recall
|
|
364
|
+
from large pinned sets. Task retrieval can also spend any other remaining space;
|
|
365
|
+
unused space returns to pinned memories. This changes admission priority, not
|
|
366
|
+
retrieval ranking or visibility. Individual records still must fit, including
|
|
367
|
+
trust metadata when requested. A record larger than the available allowance
|
|
368
|
+
can still be omitted.
|
|
369
|
+
|
|
370
|
+
Without a task (including blank text), bootstrap retains its existing admission
|
|
371
|
+
policy. `taskRetrievalHint`, when present, explains unavailable embeddings, an
|
|
372
|
+
empty candidate pool, exhausted budget, or candidates already included/too large.
|
package/docs/releasing.md
CHANGED
|
@@ -87,14 +87,17 @@ tag you pushed is the trigger). Watch the run; when it's green, the packages are
|
|
|
87
87
|
but **not yet live**.
|
|
88
88
|
|
|
89
89
|
In parallel — and **independent of the npm staging approval** — a `github-release` job
|
|
90
|
-
auto-cuts a [GitHub release](https://github.com/tpsdev-ai/flair/releases) for the tag
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
auto-cuts a [GitHub release](https://github.com/tpsdev-ai/flair/releases) for the tag.
|
|
91
|
+
Release notes are a **lede + links** rendering of the matching `## [X.Y.Z]` section
|
|
92
|
+
(`scripts/changelog-release-notes.mjs`): each entry keeps its bold lede, up to three
|
|
93
|
+
issue links, and any `> **Heads-up:**` operator lines. The deep record stays in
|
|
94
|
+
`CHANGELOG.md` and is linked from the footer at that tag. It is idempotent: re-running
|
|
95
|
+
the workflow or re-pushing the tag updates the existing release rather than failing.
|
|
96
|
+
The GitHub release documents the tagged commit immediately; it does not wait on the
|
|
97
|
+
npm 2FA gate. If the CHANGELOG has no section for the version, this job fails loudly
|
|
98
|
+
rather than cutting an empty release — which is why phase 1's fragment assembly
|
|
99
|
+
refuses to produce an empty section rather than letting the failure surface here,
|
|
100
|
+
after the tag is already pushed.
|
|
98
101
|
|
|
99
102
|
> `workflow_dispatch` with a `version` input remains as a manual fallback (needs
|
|
100
103
|
> `Actions: write`), but the tag push is the normal path.
|
package/docs/rem.md
CHANGED
|
@@ -52,28 +52,46 @@ Selects which `models.generative.<logicalName>` entry a REM call uses. Unset →
|
|
|
52
52
|
|
|
53
53
|
`flair rem nightly enable` installs a platform-native timer (launchd / systemd) **on the host it runs on**. In a multi-node or Fabric deploy, enabling it on every node would run the cycle N times and scatter N sets of pre-cycle snapshots. The v1 rule: **exactly one node gets the timer** — pick it deliberately, the same way you'd pick a cron owner for any single-writer job. This is a v1 constraint, not a permanent one; see #709 for the roadmap toward a coordinated multi-node story.
|
|
54
54
|
|
|
55
|
-
Snapshot locality follows from this: a nightly cycle's pre-run snapshot (`~/.flair/snapshots/<agent>/`) lands on **the node that ran that cycle** — `flair rem restore <date>` and `flair rem snapshot list` only see local snapshots. If you move which node owns the timer, snapshot history doesn't move with it.
|
|
55
|
+
Snapshot locality follows from this: a nightly cycle's pre-run snapshot (`~/.flair/snapshots/<agent>/`) lands on **the node that ran that cycle** — `flair rem restore <date>` and `flair rem snapshot list` only see local snapshots. If you move which node owns the timer, snapshot history doesn't move with it. Live replay (`flair rem restore <date> --apply`) rewrites Soul with operator credentials (`--admin-pass` / `--admin-pass-file`); an agent key is refused. Leftover MemoryCandidate rows for that agent are deleted first so their claim text cannot block Soul restore.
|
|
56
56
|
|
|
57
57
|
## Interactive vs nightly
|
|
58
58
|
|
|
59
59
|
- **Interactive (`flair rem rapid`):** one bounded, synchronous distillation call — gather cap 50 memories, bounded output tokens, seconds not minutes. Executes by default, staging candidates and printing a summary; `--prompt-only` returns the reflection prompt instead, for the bring-your-own-model handoff.
|
|
60
60
|
- **Nightly (`flair rem nightly enable` / `run-once`):** fully detached — the scheduler runs the full cycle (snapshot → maintenance → distillation), candidates land as pending rows, and an audit row lands in `~/.flair/logs/rem-nightly.jsonl`. The operator reviews in the morning via `flair rem candidates`.
|
|
61
61
|
|
|
62
|
+
### Safety bounds (a large backlog must not take the instance down)
|
|
63
|
+
|
|
64
|
+
A first run over thousands of unreflected memories used to hold the Harper main thread long enough that `/Health` and search timed out. Nightly distillation is now bounded:
|
|
65
|
+
|
|
66
|
+
- **Refuse to start** when `GET /Health` cannot be served within 2 seconds. The cycle logs `status: "refused"` and prints the reason; the scheduler will try again next night. Restore `/Health` before retrying, or `flair rem pause` to stop the timer.
|
|
67
|
+
- **Per-run cap** — at most 50 memories per `/ReflectMemories` call (`FLAIR_REM_MAX_MEMORIES`, hard ceiling 200), oldest-unreflected first so a 3k backlog drains across nights instead of one blocking run. Already-reflected rows fill leftover slots only when fewer than N unreflected matches remain. Nightly uses `scope: "all"` for ordinary (non-ADK) agents so the cap sees the whole backlog, not only the last 24 hours.
|
|
68
|
+
- **Yield + abort** — the gather scan yields so `/Health` and reads keep serving. `flair rem pause` (or `flair rem abort`) writes `~/.flair/rem.paused`; an in-flight gather on the same host stops at the next yield without restarting Harper.
|
|
69
|
+
|
|
70
|
+
`flair rem rapid` uses the same gather cap, oldest-unreflected selection, yield, and abort checks. It still defaults to `scope: "recent"` (last 24 hours) unless you pass `--since` / a wider scope.
|
|
71
|
+
|
|
62
72
|
Either path, the review loop is the same: `flair rem candidates` lists pending rows, `flair rem promote <id> --rationale "<why>"` / `flair rem reject <id> --reason "<why>"` decide them. Nothing self-promotes except the narrow ADK per-user path ([Auto-promote](#auto-promote-adk-only)) — see [`docs/notes/rem-ux.md`](notes/rem-ux.md) for why that gate is load-bearing and how the surface is expected to evolve.
|
|
63
73
|
|
|
64
74
|
### ADK agents — per-user (per-tag) distillation
|
|
65
75
|
|
|
66
76
|
adk-flair collapses every `(app, user)` into **one** Flair agentId, separating users only by a per-user tag `adk:<app>:<user>`. Distilling such an agentId with the default `scope:"recent"` would mix every user's sessions into shared claims — cross-user bleed. The nightly cycle therefore detects the agent's active `adk:<app>:<user>` tags (from the memories it already loads for the snapshot, with a recency cutoff that skips idle users and is scoped to the agent's own records) and runs distillation **once per tag** under `scope:"tagged"`, so each user's candidates come only from that user's own sessions. Agents with no `adk:` tags distill agentId-wide exactly as before.
|
|
67
77
|
|
|
78
|
+
Memory promotion runs through `POST /PromoteMemoryCandidate` with `candidateId` and a nonempty `rationale`. The server reads the candidate and its lineage, writes the persistent memory through the normal safety/provenance path, and stamps `promotionStatus`, `promotedAt` and `promotedBy`. Direct Memory writes cannot create, change or clear these workflow fields, including administrator REST writes. Full-row updates may echo unchanged stamps; omitted stamps are preserved. Creating a successor with `memory_update(preserveHistory=true)` leaves the verdict on the predecessor; the new content starts without an approval stamp. Non-admin reviewers are attributed to their authenticated identity; only administrators may name another reviewer. `flair rem promote --to memory` uses this endpoint and requires an updated server; it does not fall back to direct verdict writes.
|
|
79
|
+
|
|
68
80
|
A candidate distilled under a tag records that tag in its `scopeTag` field. `flair rem promote` reads `scopeTag` as the authoritative per-user lineage tag and propagates it onto the promoted memory — so the promoted claim stays in that user's retrieval scope even if the source episodes are later archived or deleted. The single-node timer rule above is unchanged; the per-tag loop runs inside the one cycle on the one node. The non-thinking-model requirement (above) still holds — the per-tag path calls the same `models.generate()` route.
|
|
69
81
|
|
|
70
82
|
#### Auto-promote (ADK only)
|
|
71
83
|
|
|
72
84
|
For ADK agents, the nightly cycle **auto-promotes** these `scopeTag`-bearing candidates to the user's own persistent memory immediately after distillation — the one place REM does not wait for a human `rem promote`. The safety argument is blast-radius, not identity: the claim is distilled from a user's own sessions into that same user's own tag scope, so no cross-agent or Soul trust boundary is crossed. The promotion is enforced entirely server-side (`POST /AutoPromoteCandidates`), never by a CLI flag a compromised agent key could flip, and holds four invariants:
|
|
73
85
|
|
|
74
|
-
- **Memory only, never Soul.** The target is hard-locked to `memory`; there is no Soul code path (Soul is agentId-scoped and cannot carry a per-user tag, so an ADK-sourced Soul promotion would be cross-user by construction).
|
|
86
|
+
- **Memory only, never Soul.** The target is hard-locked to `memory`; there is no Soul code path (Soul is agentId-scoped and cannot carry a per-user tag, so an ADK-sourced Soul promotion would be cross-user by construction). Soul mutations require operator credentials and refuse values matching any stored Memory or MemoryCandidate. This also prevents scripted promotion of learned claims, regardless of connector. See [Soul authorship](auth.md#soul-authorship).
|
|
75
87
|
- **Fail-closed tag lineage.** A candidate is promoted only if it carries an authoritative `adk:<app>:<user>` scope tag, which the promoted memory then carries. The promoted memory is written `visibility:"private"` (owner-only) — not the org-open `shared` default a `persistent` write would otherwise get — so it is reachable only through the app agent's own tag-filtered search (which re-verifies the tag), invisible both to another user's tag filter and to every other agent on the instance. A candidate whose scope tag is absent or blank is left pending, never promoted tagless into the shared agentId namespace.
|
|
76
88
|
- **Content-safety, strict.** The claim is scanned for prompt injection and refused on a flag regardless of `FLAIR_CONTENT_SAFETY` — an unattended write does not fall back to warn-and-tag.
|
|
77
89
|
- **Non-impersonating reviewer.** The promoted memory and its candidate record `machine:adk-auto-promote`, never a value mistakable for a human or agent reviewer.
|
|
78
90
|
|
|
79
91
|
Anything ineligible (no scope tag, flagged content, already decided) is left pending for the human `rem promote` path. The step is bounded per cycle and non-fatal; `flair rem nightly run-once` reports the count auto-promoted. **Non-ADK candidates never auto-promote** — the human review gate below is unchanged for them.
|
|
92
|
+
|
|
93
|
+
## Memory retention and deletion
|
|
94
|
+
|
|
95
|
+
Durability is owner-controlled, including `permanent`: owners may create, change, archive and delete their own memories at every tier. Administrators may delete any owner’s memory. The permanent tier controls retention and bootstrap priority; it does not prevent an explicit owner deletion.
|
|
96
|
+
|
|
97
|
+
Soul is operator-authored identity, not a promotion target for learned candidates. `flair rem promote --to soul` is refused for stored candidate text even with operator credentials; promote learned claims to Memory instead.
|
package/docs/upgrade.md
CHANGED
|
@@ -8,8 +8,9 @@ target version before upgrading anything you depend on in production.
|
|
|
8
8
|
|
|
9
9
|
There are two things you might be upgrading:
|
|
10
10
|
|
|
11
|
-
1. **A local
|
|
12
|
-
|
|
11
|
+
1. **A local install** — the common case: `flair` on your own machine or a VPS,
|
|
12
|
+
either via `npm install -g @tpsdev-ai/flair` or a plain extracted tree
|
|
13
|
+
(`npm pack` + `npm install --omit=dev` under systemd).
|
|
13
14
|
2. **A Flair component deployed to a Harper Fabric cluster** — a different mechanism
|
|
14
15
|
(`flair deploy` / `flair upgrade --target`), covered separately below.
|
|
15
16
|
|
|
@@ -43,6 +44,48 @@ transitive dependency). **Other integrations upgrade in their own ecosystem, not
|
|
|
43
44
|
`flair upgrade`:** `pi-flair` (pi's plugin manager), `langgraph-flair` / `hermes-flair`
|
|
44
45
|
(pip / your Python package manager), `n8n-nodes-flair` (n8n's Community Nodes UI).
|
|
45
46
|
|
|
47
|
+
If the running instance's exec path is a **plain extracted tree** (npm pack +
|
|
48
|
+
`npm install --omit=dev`, typically under systemd — no git checkout, not the
|
|
49
|
+
npm-global prefix), `flair upgrade` takes that lane: it fetches the published
|
|
50
|
+
tarball, swaps the tree in place, keeps operator launchers that are not in the
|
|
51
|
+
pack, and restarts the systemd unit that points at the tree (or `flair restart`
|
|
52
|
+
when no unit is found). Pass `--tree <dir>` to select the tree explicitly;
|
|
53
|
+
`--flair-version <semver>` pins the tarball.
|
|
54
|
+
|
|
55
|
+
A leftover npm-global relic next to a tree that is **not** a packed install
|
|
56
|
+
still gets the mismatch warning: the listing is the npm-global surface and the
|
|
57
|
+
warning names both paths so the relic is not reported as "the" install. Git
|
|
58
|
+
checkouts and source trees are never tarball-swapped.
|
|
59
|
+
|
|
60
|
+
### Plain-tree / npm-pack install
|
|
61
|
+
|
|
62
|
+
Spokes that extract the published tarball and run `npm install --omit=dev`
|
|
63
|
+
under a systemd unit — no git checkout, no `npm install -g` — use the same
|
|
64
|
+
command. `flair upgrade` detects the serving tree (or takes `--tree <dir>`)
|
|
65
|
+
and:
|
|
66
|
+
|
|
67
|
+
1. Fetches `@tpsdev-ai/flair@<version>` with `npm pack`
|
|
68
|
+
2. Extracts it next to the live tree and runs `npm install --omit=dev`
|
|
69
|
+
3. Copies operator files at the tree root that are **not** in the published
|
|
70
|
+
pack (a `flair` wrapper, `.env`, anything else you added)
|
|
71
|
+
4. Renames the live tree to `<tree>.upgrade-prev` and the staging dir into
|
|
72
|
+
place
|
|
73
|
+
5. Restarts the systemd unit whose `WorkingDirectory` / `ExecStart` names the
|
|
74
|
+
tree (`FLAIR_SYSTEMD_UNIT=flair.service` adds an explicit unit). If no unit
|
|
75
|
+
is found, it falls back to `flair restart`
|
|
76
|
+
6. Verifies, then removes `.upgrade-prev`. On a failed restart or verify the
|
|
77
|
+
previous tree is swapped back
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
flair upgrade --check --tree /opt/flair
|
|
81
|
+
flair upgrade --tree /opt/flair
|
|
82
|
+
flair upgrade --tree /opt/flair --flair-version 0.50.0
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`--check` prints the plan (versions, preserved names, which unit will restart)
|
|
86
|
+
without touching the tree. A git checkout or a path that *is* the npm-global
|
|
87
|
+
install is refused rather than overwritten.
|
|
88
|
+
|
|
46
89
|
### Upgrade is a transaction
|
|
47
90
|
|
|
48
91
|
As of flair#635, `flair upgrade` is install → restart → verify →
|
|
@@ -557,3 +600,5 @@ you haven't personally tested.
|
|
|
557
600
|
shows up when it does.
|
|
558
601
|
- [`docs/deployment.md`](deployment.md) — initial install / deployment, as opposed to
|
|
559
602
|
upgrading an existing one.
|
|
603
|
+
- [`docs/api-reference.md`](api-reference.md) — HTTP endpoints, auth per resource, and
|
|
604
|
+
the Presence / Memory / Soul / Agent / Federation schemas.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"packageManager": "bun@1.3.10",
|
|
5
5
|
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
|
|
6
6
|
"type": "module",
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"build": "tsc -p tsconfig.json --noCheck && node scripts/write-build-info.mjs",
|
|
51
51
|
"build:cli": "tsc -p tsconfig.cli.json --noCheck && node scripts/write-build-info.mjs",
|
|
52
52
|
"prepublishOnly": "npm run build && npm run build:cli",
|
|
53
|
-
"test": "bun test",
|
|
53
|
+
"test": "bun run test:unit",
|
|
54
|
+
"test:unit": "bun scripts/test-unit.ts",
|
|
54
55
|
"test:e2e": "playwright test",
|
|
55
56
|
"release": "./scripts/release.sh",
|
|
56
57
|
"postinstall": "node -e \"try{require('./dist/postinstall.cjs')}catch(e){}\""
|
|
@@ -65,10 +66,10 @@
|
|
|
65
66
|
"@harperfast/oauth": "2.5.0",
|
|
66
67
|
"@types/js-yaml": "4.0.9",
|
|
67
68
|
"commander": "14.0.3",
|
|
68
|
-
"harper": "5.2.
|
|
69
|
+
"harper": "5.2.8",
|
|
69
70
|
"harper-fabric-embeddings": "^0.5.0",
|
|
70
71
|
"jose": "6.2.2",
|
|
71
|
-
"js-yaml": "^4.3.
|
|
72
|
+
"js-yaml": "^4.3.2",
|
|
72
73
|
"tar": "^7.5.22",
|
|
73
74
|
"tweetnacl": "1.0.3"
|
|
74
75
|
},
|
|
@@ -76,15 +77,19 @@
|
|
|
76
77
|
"react-native-fs": "npm:empty-npm-package@1.0.0",
|
|
77
78
|
"brace-expansion": "^5.0.9",
|
|
78
79
|
"undici": "^8.9.0",
|
|
79
|
-
"fast-uri": "^4.1.
|
|
80
|
-
"hono": "^4.
|
|
81
|
-
"js-yaml": "^4.3.
|
|
80
|
+
"fast-uri": "^4.1.3",
|
|
81
|
+
"hono": "^4.13.5",
|
|
82
|
+
"js-yaml": "^4.3.2",
|
|
82
83
|
"adm-zip": "^0.6.0",
|
|
83
84
|
"@opentelemetry/core": "^2.8.0",
|
|
84
85
|
"uuid": "^11.1.1",
|
|
85
86
|
"tar": "^7.5.22",
|
|
86
87
|
"@tootallnate/once": "^2.0.1",
|
|
87
|
-
"form-data": "^4.0.6"
|
|
88
|
+
"form-data": "^4.0.6",
|
|
89
|
+
"mysql2": "^3.22.0",
|
|
90
|
+
"mariadb": "~3.4.7",
|
|
91
|
+
"qs": "^6.16.0",
|
|
92
|
+
"fastify": "^5.12.1"
|
|
88
93
|
},
|
|
89
94
|
"devDependencies": {
|
|
90
95
|
"@playwright/test": "1.59.1",
|
package/schemas/memory.graphql
CHANGED
|
@@ -3,6 +3,12 @@ type Memory @table(database: "flair") {
|
|
|
3
3
|
agentId: String! @indexed
|
|
4
4
|
content: String!
|
|
5
5
|
contentHash: String @indexed
|
|
6
|
+
trigger: String @indexed # flair#1542: the "when to use" text for a skill-tagged row. Skill-tagged
|
|
7
|
+
# rows embed from `trigger` (the recall signal), NOT from `content` (the full
|
|
8
|
+
# procedure) — see resources/skill-write.ts's skillEmbedText. Nullable/additive:
|
|
9
|
+
# non-skill rows carry no trigger and embed from `content` exactly as before
|
|
10
|
+
# (clean-upgrade-path gate). @indexed so the later skill recall tools can filter
|
|
11
|
+
# by trigger without a full-corpus scan.
|
|
6
12
|
visibility: String
|
|
7
13
|
embedding: [Float] @indexed(type: "HNSW", M: 16) # M:16 is Harper's own HNSW default
|
|
8
14
|
# (HierarchicalNavigableSmallWorld M default) — declaring it explicitly is a
|
|
@@ -39,8 +45,11 @@ type Memory @table(database: "flair") {
|
|
|
39
45
|
createdAt: String! @indexed
|
|
40
46
|
updatedAt: String
|
|
41
47
|
expiresAt: String @indexed
|
|
42
|
-
retrievalCount: Int
|
|
43
|
-
|
|
48
|
+
retrievalCount: Int # Observable search-hit counter. Authoritative increments live on
|
|
49
|
+
# MemoryHitStat (resources/hit-tracking.ts, flair#1528); GET/search
|
|
50
|
+
# overlay that row so this field still reads as it always has.
|
|
51
|
+
# The Memory row itself is no longer rewritten per hit.
|
|
52
|
+
lastRetrieved: String # Overlay companion to retrievalCount — see MemoryHitStat.
|
|
44
53
|
usageCount: Int # flair#683: verified-USE signal, distinct from retrievalCount (a
|
|
45
54
|
# search HIT). Additive/nullable — absent reads as 0, existing rows
|
|
46
55
|
# unaffected. NEVER auto-incremented on search/retrieval; the ONLY
|
|
@@ -149,6 +158,7 @@ type Soul @table(database: "flair") {
|
|
|
149
158
|
value: String!
|
|
150
159
|
priority: String # critical | high | standard | low (skill governance)
|
|
151
160
|
metadata: String # JSON blob (skill governance: source, version, hash, etc.)
|
|
161
|
+
provenance: String # Server-derived operator/internal source class and authenticated author.
|
|
152
162
|
durability: String @indexed
|
|
153
163
|
createdAt: String!
|
|
154
164
|
updatedAt: String
|
|
@@ -191,6 +201,26 @@ type MemoryUsage @table(database: "flair") {
|
|
|
191
201
|
createdAt: String!
|
|
192
202
|
}
|
|
193
203
|
|
|
204
|
+
# MemoryHitStat — search hit-tracking ledger (flair#1528 remaining slice).
|
|
205
|
+
# SemanticSearch used to patchRecord the full Memory row per returned hit so
|
|
206
|
+
# retrievalCount/lastRetrieved stayed on the memory itself. That is a full-row
|
|
207
|
+
# Harper put (embeddings included), feeds BM25/HNSW, and loses increments when
|
|
208
|
+
# concurrent searches read-modify-write the same five hits (Dex: 310 vs 320).
|
|
209
|
+
#
|
|
210
|
+
# This table is the increment target: one small row per memory id, coalesced
|
|
211
|
+
# per-id so concurrent hits add rather than race. No @export — no REST surface.
|
|
212
|
+
# resources/hit-tracking.ts is the only writer. Readers that expose the
|
|
213
|
+
# historical Memory.retrievalCount / lastRetrieved contract overlay this row
|
|
214
|
+
# (Memory.get/search, AdminMemory detail, MemoryConsolidate). Existing Memory
|
|
215
|
+
# field values seed the first increment so a store that already had counts
|
|
216
|
+
# does not reset. Agents have no role grant; internal raw-table writes bypass
|
|
217
|
+
# the flair_agent role the same way usage-recording writes MemoryUsage.
|
|
218
|
+
type MemoryHitStat @table(database: "flair") {
|
|
219
|
+
id: ID @primaryKey # Memory.id
|
|
220
|
+
retrievalCount: Int
|
|
221
|
+
lastRetrieved: String
|
|
222
|
+
}
|
|
223
|
+
|
|
194
224
|
# MemoryCandidate — staged distillations from the FLAIR-NIGHTLY-REM cycle.
|
|
195
225
|
# Per docs/rem.md. Slice 1 of ops-2qq adds the schema +
|
|
196
226
|
# `flair rem candidates` listing command. Future slices wire the nightly
|
|
@@ -245,3 +275,22 @@ type MemoryCandidate @table(database: "flair") @export {
|
|
|
245
275
|
# visibilityRuling — the auditable "why shared" recorded on the candidate.
|
|
246
276
|
# Nullable/additive, same clean-upgrade contract as scopeTag above.
|
|
247
277
|
}
|
|
278
|
+
|
|
279
|
+
# Asset — binary blobs (screenshots, images) stored out-of-record via Harper's
|
|
280
|
+
# Blob type, referenced from a Memory by `memoryId`. Slice 1: storage only
|
|
281
|
+
# (no MCP exposure). owner-only + identity-gated + federation "excluded" per
|
|
282
|
+
# resources/record-types.ts (RECORD_TYPES.Asset) — a new type must not enter
|
|
283
|
+
# federation sync (Federation.ts's table list is unchanged).
|
|
284
|
+
# Lifecycle: retained until the owner deletes the Asset row. Deleting the
|
|
285
|
+
# parent Memory does not sweep linked blobs (no GC this slice). Slice 2
|
|
286
|
+
# serving 404s dangling refs; GC + memoryId exist-and-owned check deferred.
|
|
287
|
+
# updatedAt is nullable/additive so a later sweep can key on recency.
|
|
288
|
+
type Asset @table(database: "flair") @export {
|
|
289
|
+
id: ID @primaryKey
|
|
290
|
+
agentId: String! @indexed # owner (no-forge attribution / read scope)
|
|
291
|
+
memoryId: String @indexed # the Memory record this asset belongs to
|
|
292
|
+
contentType: String # MIME type, e.g. image/jpeg
|
|
293
|
+
data: Blob # binary content, stored out-of-record
|
|
294
|
+
createdAt: String! @indexed
|
|
295
|
+
updatedAt: String # stamped on write; later sweep key (nullable/additive)
|
|
296
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Message table — Flair Relay S1 (point-to-point signed messaging).
|
|
2
|
+
#
|
|
3
|
+
# A message is a DIFFERENT primitive from memory and from OrgEvent: it is
|
|
4
|
+
# addressed to a recipient PRINCIPAL, ordered per-sender, durably queued, and
|
|
5
|
+
# explicitly acked, with a VISIBLE dead-letter the sender can see. OrgEvent's
|
|
6
|
+
# `expiresAt` is a silent dead-letter; Message never drops silently.
|
|
7
|
+
#
|
|
8
|
+
# The FULL envelope lands in S1 (not just the fields S1 exercises) so that S2
|
|
9
|
+
# (cross-host) is not a schema migration — `orgScope`, `seq` and `contentHash`
|
|
10
|
+
# are covered by the signature and S2's cross-host correctness needs them.
|
|
11
|
+
# See the Flair Relay S1 design (flair#1521) §4.2 and §12.
|
|
12
|
+
type Message @table(database: "flair") @export {
|
|
13
|
+
id: ID @primaryKey
|
|
14
|
+
|
|
15
|
+
# Server-resolved from the auth/pairing context, never trusted from the body
|
|
16
|
+
# (single host: the instance IS the org). Inside the signed canonical body.
|
|
17
|
+
orgScope: String @indexed
|
|
18
|
+
|
|
19
|
+
# Owning principal = the SENDER. Stamped from the authenticated identity
|
|
20
|
+
# (no-forge), and the per-table federation owner field (federation-classify).
|
|
21
|
+
from: String @indexed
|
|
22
|
+
to: String @indexed
|
|
23
|
+
|
|
24
|
+
# Sender-minted, principal-prefixed, globally unique (↔ A2A contextId later).
|
|
25
|
+
threadId: String @indexed
|
|
26
|
+
# Per (from, threadId), monotonic, SIGNED. The recipient sorts by seq at read
|
|
27
|
+
# (createdAt collides at ms resolution). See §12 P1-5.
|
|
28
|
+
seq: Int
|
|
29
|
+
|
|
30
|
+
# "message" for S1 (the degenerate one-shot). "task"/"task-update" are S3.
|
|
31
|
+
kind: String
|
|
32
|
+
# Opaque to Flair.
|
|
33
|
+
body: String
|
|
34
|
+
|
|
35
|
+
# Reply linkage (design §4.2). Lands in S1 so S2/S3 are not a schema
|
|
36
|
+
# migration, though "understood" reply semantics are S3. Both are CONTENT —
|
|
37
|
+
# covered by contentHash AND the signature (relay-lib SIGNED_BODY_FIELDS) —
|
|
38
|
+
# never trusted unsigned. `inReplyTo` is the parent message id; the redundant
|
|
39
|
+
# `parentContentHash` binds the reply to the parent's exact content.
|
|
40
|
+
inReplyTo: String
|
|
41
|
+
parentContentHash: String
|
|
42
|
+
|
|
43
|
+
createdAt: String @indexed
|
|
44
|
+
# Sender-set fail-by. When it passes on an UNCONSUMED message the row goes to
|
|
45
|
+
# a VISIBLE `failed` state (reason "deadline") — the inverse of a silent drop.
|
|
46
|
+
deadline: String
|
|
47
|
+
|
|
48
|
+
# submitted -> delivered -> consumed | failed. `consumed` is ABSORBING —
|
|
49
|
+
# no later write (including a deadline sweep) regresses it (§12 P0-3).
|
|
50
|
+
state: String @indexed
|
|
51
|
+
deliveredAt: String
|
|
52
|
+
consumedAt: String @indexed
|
|
53
|
+
# Dead-letter reason on a `failed` row ("deadline" | "inbox_full" | ...),
|
|
54
|
+
# queryable by the sender.
|
|
55
|
+
failureReason: String
|
|
56
|
+
|
|
57
|
+
# Over the signed canonical CONTENT — retry-send dedup + a second integrity
|
|
58
|
+
# check under the signature (§12 P0-4).
|
|
59
|
+
contentHash: String
|
|
60
|
+
|
|
61
|
+
# Provenance (shared with flair#1520) — "which model sent this".
|
|
62
|
+
senderModel: String
|
|
63
|
+
senderProvider: String
|
|
64
|
+
senderRunId: String
|
|
65
|
+
|
|
66
|
+
# Ed25519 over the canonical SIGNED body — the single source of truth is
|
|
67
|
+
# resources/relay-lib.ts SIGNED_BODY_FIELDS; keep this list in sync with it.
|
|
68
|
+
# It COVERS: the content (from, to, threadId, seq, kind, body, orgScope,
|
|
69
|
+
# deadline, inReplyTo, parentContentHash, senderModel/Provider/RunId) PLUS
|
|
70
|
+
# id, createdAt and contentHash. It EXCLUDES the mutable server/lifecycle
|
|
71
|
+
# fields (state, deliveredAt, consumedAt, failureReason) and this `signature`
|
|
72
|
+
# field itself. (Not "every field above" — the lifecycle fields are NOT signed.)
|
|
73
|
+
signature: String
|
|
74
|
+
}
|