@tpsdev-ai/flair 0.53.0 → 0.54.2
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 +4 -1
- package/dist/build-info.json +3 -3
- package/dist/cli.js +1791 -15648
- package/dist/commands/agent.js +453 -0
- package/dist/commands/attention.js +121 -0
- package/dist/commands/backup.js +115 -0
- package/dist/commands/bootstrap.js +91 -0
- package/dist/commands/bridge.js +608 -0
- package/dist/commands/deploy.js +180 -0
- package/dist/commands/doctor.js +1665 -0
- package/dist/commands/export.js +110 -0
- package/dist/commands/federation.js +1575 -0
- package/dist/commands/fleet.js +73 -0
- package/dist/commands/grant.js +109 -0
- package/dist/commands/hook.js +193 -0
- package/dist/commands/idp.js +193 -0
- package/dist/commands/import.js +134 -0
- package/dist/commands/init.js +1203 -0
- package/dist/commands/inspect.js +45 -0
- package/dist/commands/keys.js +187 -0
- package/dist/commands/mcp.js +707 -0
- package/dist/commands/memory.js +501 -0
- package/dist/commands/migrate-harness-memory.js +270 -0
- package/dist/commands/orgevent.js +138 -0
- package/dist/commands/presence.js +76 -0
- package/dist/commands/principal.js +338 -0
- package/dist/commands/quality.js +1164 -0
- package/dist/commands/reembed.js +296 -0
- package/dist/commands/relationship.js +76 -0
- package/dist/commands/rem.js +1048 -0
- package/dist/commands/restore.js +130 -0
- package/dist/commands/search.js +244 -0
- package/dist/commands/service.js +315 -0
- package/dist/commands/session.js +184 -0
- package/dist/commands/soul.js +155 -0
- package/dist/commands/status.js +931 -0
- package/dist/commands/test.js +93 -0
- package/dist/commands/uninstall.js +143 -0
- package/dist/commands/upgrade.js +1628 -0
- package/dist/commands/workspace.js +114 -0
- package/dist/deploy.js +24 -0
- package/dist/engine-version.js +12 -4
- package/dist/fabric-npm-install.js +87 -0
- package/dist/fabric-upgrade.js +30 -15
- package/dist/federation-verify.js +498 -0
- package/dist/fleet-verify.js +144 -21
- package/dist/install/clients.js +167 -0
- package/dist/lib/auth-resolve.js +76 -1
- package/dist/lib/daemon-liveness.js +131 -2
- package/dist/lib/doctor-config-path.js +61 -0
- package/dist/lib/doctor-federation-driver.js +189 -0
- package/dist/lib/doctor-run.js +40 -0
- package/dist/lib/entity-vocab-cli.js +3 -3
- package/dist/lib/federation-pair-identity.js +47 -0
- package/dist/lib/launchd-repair.js +5 -4
- package/dist/lib/npm-registry.js +578 -0
- package/dist/lib/ops-api-bind.js +115 -0
- package/dist/lib/owned-pins.js +219 -0
- package/dist/lib/uninstall-purge.js +218 -0
- package/dist/rem/restore.js +8 -10
- package/dist/resources/AgentReadPosition.js +74 -0
- package/dist/resources/Federation.js +8 -2
- package/dist/resources/Memory.js +4 -3
- package/dist/resources/MemoryBootstrap.js +41 -25
- package/dist/resources/MemoryCandidate.js +5 -6
- package/dist/resources/OrgEventCatchup.js +126 -47
- package/dist/resources/agent-read-position-lib.js +83 -0
- package/dist/resources/agent-read-position.js +120 -0
- package/dist/resources/embeddings-boot.js +32 -0
- package/dist/resources/federation-peer-liveness.js +73 -0
- package/dist/resources/health.js +68 -19
- package/dist/resources/mcp-tools.js +48 -279
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +59 -18
- package/dist/resources/migrations/embedding-stamp.js +20 -1
- package/dist/resources/migrations/recheck.js +43 -0
- package/dist/resources/migrations/runner.js +6 -1
- package/dist/resources/migrations/stamp-outstanding.js +171 -0
- package/dist/resources/migrations/visibility-backfill.js +2 -2
- package/dist/resources/org-event-catchup-lib.js +47 -0
- package/dist/resources/record-owner-guard.js +1 -0
- package/dist/resources/tool-descriptors/index.js +669 -0
- package/dist/stamp-migration-verify.js +163 -0
- package/dist/stamp-outstanding.js +144 -0
- package/dist/version-check.js +29 -8
- package/docs/api-reference.md +4 -2
- package/docs/deploying-on-fabric.md +11 -10
- package/docs/deployment.md +3 -1
- package/docs/federation.md +19 -0
- package/docs/hosted-on-fabric.md +3 -3
- package/docs/quickstart.md +2 -1
- package/docs/releasing.md +20 -6
- package/docs/spoke-bringup.md +10 -5
- package/docs/standalone-local.md +3 -1
- package/docs/upgrade.md +25 -6
- package/package.json +4 -4
- package/schemas/agent.graphql +15 -0
|
@@ -26,15 +26,17 @@
|
|
|
26
26
|
* the resolved agent, never from the tool arguments — an agent can only act as
|
|
27
27
|
* itself (no forging of agentId / authorId in the body).
|
|
28
28
|
*
|
|
29
|
-
* NOTE (flair#
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
29
|
+
* NOTE (flair#1580, vendored by flair#1683): MCP-facing metadata (name,
|
|
30
|
+
* description, inputSchema, output shape) lives in the descriptor module
|
|
31
|
+
* vendored at build time into `./tool-descriptors/` (source of truth:
|
|
32
|
+
* packages/flair-tool-descriptors/src/index.ts — see
|
|
33
|
+
* scripts/vendor-tool-descriptors.mjs). It is imported by RELATIVE PATH so it
|
|
34
|
+
* resolves inside the packed tarball with no registry dependency on the
|
|
35
|
+
* private descriptor package. This registry
|
|
36
|
+
* binds each *native* descriptor to its Harper impl. The stdio adapter binds
|
|
37
|
+
* the same list (stdio surface) to FlairClient HTTP — tool-set drift is
|
|
38
|
+
* impossible by construction. One-sided tools (`attention`, archive verbs,
|
|
39
|
+
* `relationship_store`) are flagged on the descriptor, not hand-exempted.
|
|
38
40
|
*/
|
|
39
41
|
/**
|
|
40
42
|
* The delegated handler classes, held in a mutable registry, LAZILY loaded on
|
|
@@ -54,8 +56,9 @@
|
|
|
54
56
|
import { AUTHORITY_FIELDS } from "./authority-field-guard.js";
|
|
55
57
|
import { resolveVersion } from "./version.js";
|
|
56
58
|
import { agentContext, adminContext, collectionResource } from "./in-process.js";
|
|
57
|
-
import {
|
|
59
|
+
import { unionUsageMemoryIds } from "./usage-ids.js";
|
|
58
60
|
import { SKILL_TAG, isSkillWrite } from "./skill-write.js";
|
|
61
|
+
import { NATIVE_TOOL_DESCRIPTORS, toMcpToolDef, } from "./tool-descriptors/index.js";
|
|
59
62
|
const H = {};
|
|
60
63
|
const LOADERS = {
|
|
61
64
|
SemanticSearch: async () => (await import("./SemanticSearch.js")).SemanticSearch,
|
|
@@ -405,7 +408,8 @@ async function skillSearch(agent, args) {
|
|
|
405
408
|
* returns the same 404 as an unreadable id. Returning it would (a) make
|
|
406
409
|
* skill_get an alias for memory_get, and (b) reveal a readable non-skill
|
|
407
410
|
* memory's existence through a skill-shaped call; a uniform 404 does neither.
|
|
408
|
-
* The embedding fields are stripped
|
|
411
|
+
* The embedding fields are always stripped (flair#1593) — there is no
|
|
412
|
+
* includeEmbedding opt-in. memory_get keeps that flag (flair#1188).
|
|
409
413
|
*/
|
|
410
414
|
async function skillGet(agent, args) {
|
|
411
415
|
const Cls = await handler("Memory");
|
|
@@ -419,7 +423,9 @@ async function skillGet(agent, args) {
|
|
|
419
423
|
// returned (see the doc above).
|
|
420
424
|
if (!isSkillWrite(result))
|
|
421
425
|
return { error: "skill not found", status: 404 };
|
|
422
|
-
|
|
426
|
+
// flair#1593 — always strip. includeEmbedding used to bypass this and
|
|
427
|
+
// ride the raw vector; nothing in the skill_get contract needs it.
|
|
428
|
+
return stripInternalFields(result);
|
|
423
429
|
}
|
|
424
430
|
/**
|
|
425
431
|
* memory_update — id-targeted, dedup-BYPASSED overwrite/version path (memory-
|
|
@@ -850,24 +856,33 @@ export function mcpToolName(table, toolPrefix, verb) {
|
|
|
850
856
|
const override = TOOL_NAME_OVERRIDES[table]?.[verb];
|
|
851
857
|
return override ?? `${toolPrefix}_${verb}`;
|
|
852
858
|
}
|
|
853
|
-
|
|
859
|
+
/**
|
|
860
|
+
* Bind native descriptors to Harper impls + conformance contracts.
|
|
861
|
+
* A descriptor without a binding (or a binding without a native descriptor)
|
|
862
|
+
* throws at module load — the tool set is derived, not hand-copied.
|
|
863
|
+
*/
|
|
864
|
+
function bindNativeTools(bindings) {
|
|
865
|
+
const tools = {};
|
|
866
|
+
const missing = [];
|
|
867
|
+
for (const d of NATIVE_TOOL_DESCRIPTORS) {
|
|
868
|
+
const b = bindings[d.name];
|
|
869
|
+
if (!b) {
|
|
870
|
+
missing.push(d.name);
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
tools[d.name] = { def: toMcpToolDef(d), impl: b.impl, contract: b.contract };
|
|
874
|
+
}
|
|
875
|
+
if (missing.length > 0) {
|
|
876
|
+
throw new Error(`TOOLS missing Harper bindings for native descriptors: ${missing.join(", ")}`);
|
|
877
|
+
}
|
|
878
|
+
const extra = Object.keys(bindings).filter((n) => !Object.prototype.hasOwnProperty.call(tools, n)).sort();
|
|
879
|
+
if (extra.length > 0) {
|
|
880
|
+
throw new Error(`TOOLS bindings have no native descriptor: ${extra.join(", ")}`);
|
|
881
|
+
}
|
|
882
|
+
return tools;
|
|
883
|
+
}
|
|
884
|
+
export const TOOLS = bindNativeTools({
|
|
854
885
|
memory_search: {
|
|
855
|
-
def: {
|
|
856
|
-
name: "memory_search",
|
|
857
|
-
description: "Search memories by meaning. Understands temporal queries like 'what happened today'. Scoped to your agent's own + granted memories.",
|
|
858
|
-
annotations: { readOnlyHint: true },
|
|
859
|
-
inputSchema: {
|
|
860
|
-
type: "object",
|
|
861
|
-
properties: {
|
|
862
|
-
query: { type: "string", description: "Search query — natural language, semantic matching" },
|
|
863
|
-
limit: { type: "number", description: "Max results (default 5)" },
|
|
864
|
-
includeTrust: { type: "boolean", description: "Attach a per-result trust-evidence block (provenance, author, usage, freshness, supersession). Default false." },
|
|
865
|
-
abstain: { type: "boolean", description: "Opt into first-class abstention: when the best match is below a global confidence threshold, return { abstained: true, reason, bestScore } with no weak matches instead of the N weakest results. Default false." },
|
|
866
|
-
includeArchived: { type: "boolean", description: "Include basemented (archived) memories in results. Default false — archived memories are excluded from normal search. When true, archived memories are returned under the SAME read-scope gate as a normal search (never a wider scope)." },
|
|
867
|
-
},
|
|
868
|
-
required: ["query"],
|
|
869
|
-
},
|
|
870
|
-
},
|
|
871
886
|
impl: memorySearch,
|
|
872
887
|
contract: {
|
|
873
888
|
summary: "{ results: MemoryRecord[] } — semantic hits scoped to the caller's own + granted memories; each hit carries content, never the raw embedding.",
|
|
@@ -881,30 +896,6 @@ export const TOOLS = {
|
|
|
881
896
|
},
|
|
882
897
|
},
|
|
883
898
|
memory_store: {
|
|
884
|
-
def: {
|
|
885
|
-
name: "memory_store",
|
|
886
|
-
description: "Save information to persistent memory. Use for lessons, decisions, preferences, facts. Attributed to your authenticated agent.",
|
|
887
|
-
inputSchema: {
|
|
888
|
-
type: "object",
|
|
889
|
-
properties: {
|
|
890
|
-
content: { type: "string", description: "What to remember" },
|
|
891
|
-
type: { type: "string", enum: ["session", "lesson", "decision", "preference", "fact", "goal"], description: "Memory type (default session)" },
|
|
892
|
-
durability: { type: "string", enum: ["permanent", "persistent", "standard", "ephemeral"], description: "permanent > persistent > standard > ephemeral (default standard)" },
|
|
893
|
-
tags: { type: "array", items: { type: "string" }, description: "Tag strings" },
|
|
894
|
-
visibility: {
|
|
895
|
-
type: "string",
|
|
896
|
-
enum: ["private", "shared"],
|
|
897
|
-
description: "Writer-controlled sharing intent. Omit to use the server's durability-keyed default: " +
|
|
898
|
-
"permanent/persistent -> shared, standard/ephemeral -> private. " +
|
|
899
|
-
"private — owner-only, never visible to another agent, even one holding a memory grant. " +
|
|
900
|
-
"shared — visible to the owner and every other agent on this instance. " +
|
|
901
|
-
"The visibility the write actually landed on is returned in the result.",
|
|
902
|
-
},
|
|
903
|
-
usedMemoryIds: { type: "array", items: { type: "string" }, description: "IDs of memories that informed this write (citation-on-write). Credited via the same deduped usage ledger as record_usage. Optional." },
|
|
904
|
-
},
|
|
905
|
-
required: ["content"],
|
|
906
|
-
},
|
|
907
|
-
},
|
|
908
899
|
impl: memoryStore,
|
|
909
900
|
contract: {
|
|
910
901
|
summary: "Write echo { id, written:true, deduplicated } — the new id + confirmation. No internal embedding fields; round-trips via memory_get.",
|
|
@@ -916,24 +907,6 @@ export const TOOLS = {
|
|
|
916
907
|
},
|
|
917
908
|
},
|
|
918
909
|
skill_store: {
|
|
919
|
-
def: {
|
|
920
|
-
name: "skill_store",
|
|
921
|
-
description: "Write a skill (a reusable capability/procedure) as a skill-tagged memory. " +
|
|
922
|
-
"The `trigger` text is what the skill embeds from (the recall signal — 'when to use this'), " +
|
|
923
|
-
"and `content` is the full procedure. Skills are forced durability=persistent and are " +
|
|
924
|
-
"SkillScan-gated before the embed (a dangerous shell/network payload is rejected).",
|
|
925
|
-
inputSchema: {
|
|
926
|
-
type: "object",
|
|
927
|
-
properties: {
|
|
928
|
-
content: { type: "string", description: "The full procedure (markdown body of the SKILL.md)" },
|
|
929
|
-
trigger: { type: "string", description: "The 'when to use' text — the recall signal the skill embeds from" },
|
|
930
|
-
name: { type: "string", description: "Skill name (SKILL.md frontmatter; stored in metadata)" },
|
|
931
|
-
description: { type: "string", description: "Skill description (SKILL.md frontmatter; stored in metadata)" },
|
|
932
|
-
tags: { type: "array", items: { type: "string" }, description: "Additional tags (the 'skill' tag is added automatically)" },
|
|
933
|
-
},
|
|
934
|
-
required: ["content"],
|
|
935
|
-
},
|
|
936
|
-
},
|
|
937
910
|
impl: skillStore,
|
|
938
911
|
contract: {
|
|
939
912
|
summary: "Write echo { id, written:true, deduplicated } for the skill-tagged memory. No internal embedding fields; round-trips via memory_get.",
|
|
@@ -945,23 +918,6 @@ export const TOOLS = {
|
|
|
945
918
|
},
|
|
946
919
|
},
|
|
947
920
|
skill_search: {
|
|
948
|
-
def: {
|
|
949
|
-
name: "skill_search",
|
|
950
|
-
description: "Find skills (reusable capabilities/procedures) that apply to a task. " +
|
|
951
|
-
"Ranks skill-tagged memories by their `trigger` ('when to use') against your task text. " +
|
|
952
|
-
"Returns a lightweight CATALOG — id, name, trigger, description, tags, agentId — NOT the full " +
|
|
953
|
-
"procedure (fetch that with skill_get). Scoped to your own + shared skills; another agent's " +
|
|
954
|
-
"private skill is never returned.",
|
|
955
|
-
annotations: { readOnlyHint: true },
|
|
956
|
-
inputSchema: {
|
|
957
|
-
type: "object",
|
|
958
|
-
properties: {
|
|
959
|
-
task: { type: "string", description: "The task/context to match skills against — natural language; ranked against each skill's trigger" },
|
|
960
|
-
limit: { type: "number", description: "Max skills to return (default 5)" },
|
|
961
|
-
},
|
|
962
|
-
required: ["task"],
|
|
963
|
-
},
|
|
964
|
-
},
|
|
965
921
|
impl: skillSearch,
|
|
966
922
|
contract: {
|
|
967
923
|
summary: "{ results: SkillCard[] } — the skill catalog (lightweight id/name/trigger/description/tags/agentId, " +
|
|
@@ -979,25 +935,10 @@ export const TOOLS = {
|
|
|
979
935
|
},
|
|
980
936
|
},
|
|
981
937
|
skill_get: {
|
|
982
|
-
def: {
|
|
983
|
-
name: "skill_get",
|
|
984
|
-
description: "Retrieve a full skill by ID — the complete procedure (`content`) plus trigger and metadata. " +
|
|
985
|
-
"The disclosure step after skill_search's catalog. Read-scoped: you can only get your own or a " +
|
|
986
|
-
"shared skill, never another agent's private skill. A non-skill id returns not-found.",
|
|
987
|
-
annotations: { readOnlyHint: true },
|
|
988
|
-
inputSchema: {
|
|
989
|
-
type: "object",
|
|
990
|
-
properties: {
|
|
991
|
-
id: { type: "string", description: "Skill (memory) ID" },
|
|
992
|
-
includeEmbedding: { type: "boolean", description: "Include the raw embedding vector (large, rarely useful). Default false." },
|
|
993
|
-
},
|
|
994
|
-
required: ["id"],
|
|
995
|
-
},
|
|
996
|
-
},
|
|
997
938
|
impl: skillGet,
|
|
998
939
|
contract: {
|
|
999
940
|
summary: "The full skill record { id, agentId, content, trigger, tags, durability, metadata, createdAt, ... } for a " +
|
|
1000
|
-
"skill readable under the caller's read-scope — embedding + embeddingModel stripped
|
|
941
|
+
"skill readable under the caller's read-scope — embedding + embeddingModel always stripped. A non-owner " +
|
|
1001
942
|
"cannot read another agent's private skill, and a readable non-skill id is not found (both 404).",
|
|
1002
943
|
requiredFields: ["id", "agentId", "content", "createdAt"],
|
|
1003
944
|
fieldTypes: { id: "string", agentId: "string", content: "string" },
|
|
@@ -1007,21 +948,6 @@ export const TOOLS = {
|
|
|
1007
948
|
},
|
|
1008
949
|
},
|
|
1009
950
|
memory_update: {
|
|
1010
|
-
def: {
|
|
1011
|
-
name: "memory_update",
|
|
1012
|
-
description: "Update an existing memory by ID. Dedup-bypassed (this is an intentional overwrite, not a new write). " +
|
|
1013
|
-
"Default: overwrites the same id in place. Pass preserveHistory=true to instead write a new version " +
|
|
1014
|
-
"linked via `supersedes`, closing the old one's validity window.",
|
|
1015
|
-
inputSchema: {
|
|
1016
|
-
type: "object",
|
|
1017
|
-
properties: {
|
|
1018
|
-
id: { type: "string", description: "ID of the memory to update" },
|
|
1019
|
-
content: { type: "string", description: "New content" },
|
|
1020
|
-
preserveHistory: { type: "boolean", description: "Write a new version (supersedes-linked) instead of overwriting in place (default false)" },
|
|
1021
|
-
},
|
|
1022
|
-
required: ["id", "content"],
|
|
1023
|
-
},
|
|
1024
|
-
},
|
|
1025
951
|
impl: memoryUpdate,
|
|
1026
952
|
contract: {
|
|
1027
953
|
summary: "Write echo { id, written:true } for the in-place overwrite (or supersede). No internal embedding fields; the change round-trips via memory_get.",
|
|
@@ -1033,20 +959,6 @@ export const TOOLS = {
|
|
|
1033
959
|
},
|
|
1034
960
|
},
|
|
1035
961
|
memory_basement: {
|
|
1036
|
-
def: {
|
|
1037
|
-
name: "memory_basement",
|
|
1038
|
-
description: "Send a memory to the basement (archive it). Sets archived=true and stamps archivedAt. " +
|
|
1039
|
-
"The memory is removed from bootstrap and default search but remains retrievable via " +
|
|
1040
|
-
"memory_get and memory_search(includeArchived:true). Deliberate and GLOBAL — this is a " +
|
|
1041
|
-
"visibility flag, not a deletion: provenance and history are untouched. Scoped to your own memories only.",
|
|
1042
|
-
inputSchema: {
|
|
1043
|
-
type: "object",
|
|
1044
|
-
properties: {
|
|
1045
|
-
id: { type: "string", description: "ID of the memory to basement (archive)" },
|
|
1046
|
-
},
|
|
1047
|
-
required: ["id"],
|
|
1048
|
-
},
|
|
1049
|
-
},
|
|
1050
962
|
impl: memoryBasement,
|
|
1051
963
|
contract: {
|
|
1052
964
|
summary: "Write echo of the archived record { id, archived:true, archivedAt, ... }. No internal embedding fields; the flip round-trips via memory_get.",
|
|
@@ -1058,19 +970,6 @@ export const TOOLS = {
|
|
|
1058
970
|
},
|
|
1059
971
|
},
|
|
1060
972
|
memory_restore: {
|
|
1061
|
-
def: {
|
|
1062
|
-
name: "memory_restore",
|
|
1063
|
-
description: "Restore a basemented (archived) memory. Clears archived and archivedAt. Deliberate and GLOBAL — " +
|
|
1064
|
-
"this un-retires the memory for EVERY session, not a session-local view (per-session reuse is " +
|
|
1065
|
-
"drawers, which do not exist yet). Scoped to your own memories only.",
|
|
1066
|
-
inputSchema: {
|
|
1067
|
-
type: "object",
|
|
1068
|
-
properties: {
|
|
1069
|
-
id: { type: "string", description: "ID of the memory to restore (un-archive)" },
|
|
1070
|
-
},
|
|
1071
|
-
required: ["id"],
|
|
1072
|
-
},
|
|
1073
|
-
},
|
|
1074
973
|
impl: memoryRestore,
|
|
1075
974
|
contract: {
|
|
1076
975
|
summary: "Write echo of the restored record { id, archived:false, ... }. No internal embedding fields; the flip round-trips via memory_get.",
|
|
@@ -1082,20 +981,6 @@ export const TOOLS = {
|
|
|
1082
981
|
},
|
|
1083
982
|
},
|
|
1084
983
|
memory_get: {
|
|
1085
|
-
def: {
|
|
1086
|
-
name: "memory_get",
|
|
1087
|
-
description: "Retrieve a specific memory by ID. The record's raw embedding vector is omitted by default (it is large and not useful to a caller); pass includeEmbedding=true to include it.",
|
|
1088
|
-
annotations: { readOnlyHint: true },
|
|
1089
|
-
inputSchema: {
|
|
1090
|
-
type: "object",
|
|
1091
|
-
properties: {
|
|
1092
|
-
id: { type: "string", description: "Memory ID" },
|
|
1093
|
-
includeTrust: { type: "boolean", description: "Attach a trust-evidence block (provenance, author, usage, freshness, supersession) to the record. Default false." },
|
|
1094
|
-
includeEmbedding: { type: "boolean", description: "Include the raw embedding vector (hundreds of floats) in the returned record. Omitted by default because it is large and rarely useful to a caller. Default false." },
|
|
1095
|
-
},
|
|
1096
|
-
required: ["id"],
|
|
1097
|
-
},
|
|
1098
|
-
},
|
|
1099
984
|
impl: memoryGet,
|
|
1100
985
|
contract: {
|
|
1101
986
|
summary: "The full memory record { id, agentId, content, durability, createdAt, ... } for the caller's own id — embedding + embeddingModel stripped by default.",
|
|
@@ -1107,16 +992,6 @@ export const TOOLS = {
|
|
|
1107
992
|
},
|
|
1108
993
|
},
|
|
1109
994
|
memory_delete: {
|
|
1110
|
-
def: {
|
|
1111
|
-
name: "memory_delete",
|
|
1112
|
-
description: "Delete a memory by ID. You can only delete your own memories.",
|
|
1113
|
-
annotations: { destructiveHint: true },
|
|
1114
|
-
inputSchema: {
|
|
1115
|
-
type: "object",
|
|
1116
|
-
properties: { id: { type: "string", description: "Memory ID to delete" } },
|
|
1117
|
-
required: ["id"],
|
|
1118
|
-
},
|
|
1119
|
-
},
|
|
1120
995
|
impl: memoryDelete,
|
|
1121
996
|
contract: {
|
|
1122
997
|
summary: "Deletes the caller's own memory at any durability tier (success echo is thin). Cross-owner deletion returns { error, status:403 } for a non-admin; a deleted row round-trips as gone via memory_get.",
|
|
@@ -1125,31 +1000,6 @@ export const TOOLS = {
|
|
|
1125
1000
|
},
|
|
1126
1001
|
},
|
|
1127
1002
|
bootstrap: {
|
|
1128
|
-
def: {
|
|
1129
|
-
name: "bootstrap",
|
|
1130
|
-
description: "Get session context: soul + memories + predicted context. Run at session start. Pass subjects for predictive loading.",
|
|
1131
|
-
annotations: { readOnlyHint: true },
|
|
1132
|
-
inputSchema: {
|
|
1133
|
-
type: "object",
|
|
1134
|
-
properties: {
|
|
1135
|
-
maxTokens: { type: "number", description: "Content-selection budget in tokens (default 4000): the hard cap on how much soul/memory/finding CONTENT is selected. The actual serialized response (reported by tokenEstimate) may exceed this by the structured-container JSON scaffolding — maxTokens bounds what is selected, not the raw output size. Raise it to include more content." },
|
|
1136
|
-
currentTask: { type: "string", description: "Current task — enables semantic search for relevant memories" },
|
|
1137
|
-
channel: { type: "string", description: "Channel name (discord, tps-mail, claude-code)" },
|
|
1138
|
-
surface: { type: "string", description: "Surface name (tps-build, tps-review, cli-session)" },
|
|
1139
|
-
subjects: { type: "array", items: { type: "string" }, description: "Entity names to preload context for" },
|
|
1140
|
-
entities: {
|
|
1141
|
-
type: "array",
|
|
1142
|
-
items: { type: "string" },
|
|
1143
|
-
description: "Your declared attention-plane vocabulary strings (e.g. \"issue:owner/repo#123\") for collision surfacing's 'Others in the room' block — teammates with overlapping active work. Falls back to your own most-recent workspace-state entities when omitted.",
|
|
1144
|
-
},
|
|
1145
|
-
includeTrust: { type: "boolean", description: "Also return a `trust` array with a per-included-memory trust-evidence block (provenance, author, usage, freshness, supersession). Default false." },
|
|
1146
|
-
abstain: { type: "boolean", description: "Opt into a task-relevance abstention verdict: also return an `abstention` object ({ abstained, bestScore, threshold }) reporting whether any memory covered `currentTask` above a global confidence threshold. Default false." },
|
|
1147
|
-
includeContext: { type: "boolean", description: "Also return the prose `context` string — a human-readable mirror of the structured soul/memories/predicted/teammateFindings containers (which are the canonical payload). Default false here: the structured fields already carry everything, so shipping the prose too would double the payload." },
|
|
1148
|
-
maxEvents: { type: "number", description: "Cap on how many recent org events to return (default 10). Events are counted against maxTokens like every other content section." },
|
|
1149
|
-
includeEventDetail: { type: "boolean", description: "Also include each org event's verbose `detail` JSON (migration internals, etc.). Default false: bootstrap ships lean events (id/kind/summary/createdAt/targetIds/scope); `detail` mostly restates the summary and is pure bloat for a connector." },
|
|
1150
|
-
},
|
|
1151
|
-
},
|
|
1152
|
-
},
|
|
1153
1003
|
impl: bootstrap,
|
|
1154
1004
|
contract: {
|
|
1155
1005
|
summary: "Session context: { agentId, soul, memories, predicted, teammateFindings, events, sections, tokenEstimate, memoriesIncluded, ..., context, flairVersion }. "
|
|
@@ -1159,6 +1009,7 @@ export const TOOLS = {
|
|
|
1159
1009
|
"sections", "tokenEstimate", "maxTokens", "memoriesIncluded", "memoriesAvailable",
|
|
1160
1010
|
"memoriesTruncated", "teammateFindingsIncluded", "teammateFindingsTruncated",
|
|
1161
1011
|
"teammateFindingsMatched", "context", "flairVersion",
|
|
1012
|
+
"eventWatermark", "eventsHasMore", "eventsRemaining",
|
|
1162
1013
|
// flair#1270 — the payload token LEDGER: every token-charged content
|
|
1163
1014
|
// class has a counter, so tokenEstimate ≈ scaffoldTokens + soulTokens +
|
|
1164
1015
|
// memoryTokens + trustTokens + eventsTokens decomposes from the payload
|
|
@@ -1174,6 +1025,7 @@ export const TOOLS = {
|
|
|
1174
1025
|
teammateFindingsMatched: "number", context: "string", flairVersion: "string",
|
|
1175
1026
|
soulTokens: "number", memoryTokens: "number", trustTokens: "number",
|
|
1176
1027
|
eventsTokens: "number", scaffoldTokens: "number",
|
|
1028
|
+
eventWatermark: "string", eventsHasMore: "boolean", eventsRemaining: "number",
|
|
1177
1029
|
},
|
|
1178
1030
|
invariants: {
|
|
1179
1031
|
// count == delivered — the historical count/charge/deliver drift.
|
|
@@ -1262,18 +1114,6 @@ export const TOOLS = {
|
|
|
1262
1114
|
},
|
|
1263
1115
|
},
|
|
1264
1116
|
soul_set: {
|
|
1265
|
-
def: {
|
|
1266
|
-
name: "soul_set",
|
|
1267
|
-
description: "Soul changes require operator credentials through the REST API or CLI; runtime tool calls are refused.",
|
|
1268
|
-
inputSchema: {
|
|
1269
|
-
type: "object",
|
|
1270
|
-
properties: {
|
|
1271
|
-
key: { type: "string", description: "Entry key (e.g. 'role', 'standards', 'project')" },
|
|
1272
|
-
value: { type: "string", description: "Entry value" },
|
|
1273
|
-
},
|
|
1274
|
-
required: ["key", "value"],
|
|
1275
|
-
},
|
|
1276
|
-
},
|
|
1277
1117
|
impl: soulSet,
|
|
1278
1118
|
contract: {
|
|
1279
1119
|
summary: "Refuses runtime Soul writes, including admin-agent delegation, with { error, status:403 }. Operators use the authenticated REST or CLI path.",
|
|
@@ -1282,16 +1122,6 @@ export const TOOLS = {
|
|
|
1282
1122
|
},
|
|
1283
1123
|
},
|
|
1284
1124
|
soul_get: {
|
|
1285
|
-
def: {
|
|
1286
|
-
name: "soul_get",
|
|
1287
|
-
description: "Get a personality or project context entry.",
|
|
1288
|
-
annotations: { readOnlyHint: true },
|
|
1289
|
-
inputSchema: {
|
|
1290
|
-
type: "object",
|
|
1291
|
-
properties: { key: { type: "string", description: "Entry key" } },
|
|
1292
|
-
required: ["key"],
|
|
1293
|
-
},
|
|
1294
|
-
},
|
|
1295
1125
|
impl: soulGet,
|
|
1296
1126
|
contract: {
|
|
1297
1127
|
summary: "The soul entry { id, agentId, key, value, createdAt } for the caller's own `${agentId}:${key}`.",
|
|
@@ -1301,22 +1131,6 @@ export const TOOLS = {
|
|
|
1301
1131
|
},
|
|
1302
1132
|
},
|
|
1303
1133
|
flair_workspace_set: {
|
|
1304
|
-
def: {
|
|
1305
|
-
name: "flair_workspace_set",
|
|
1306
|
-
description: "Set your agent's current workspace state in the Office Space coordination layer. Attributed to you — you can only write your own state.",
|
|
1307
|
-
inputSchema: {
|
|
1308
|
-
type: "object",
|
|
1309
|
-
properties: {
|
|
1310
|
-
ref: { type: "string", description: "Workspace ref — branch, worktree, or task ref" },
|
|
1311
|
-
label: { type: "string", description: "Human-readable label" },
|
|
1312
|
-
provider: { type: "string", description: "Provider/runtime (default mcp)" },
|
|
1313
|
-
task: { type: "string", description: "Task/issue id" },
|
|
1314
|
-
phase: { type: "string", description: "Current phase (design, implement, review)" },
|
|
1315
|
-
summary: { type: "string", description: "Short summary of current state" },
|
|
1316
|
-
},
|
|
1317
|
-
required: ["ref"],
|
|
1318
|
-
},
|
|
1319
|
-
},
|
|
1320
1134
|
impl: workspaceSet,
|
|
1321
1135
|
contract: {
|
|
1322
1136
|
summary: "Writes the caller's workspace state keyed `${agentId}:${ref}`, attributed to the caller (never the body). The echo is thin; persistence is verified in storage.",
|
|
@@ -1324,21 +1138,6 @@ export const TOOLS = {
|
|
|
1324
1138
|
},
|
|
1325
1139
|
},
|
|
1326
1140
|
flair_orgevent: {
|
|
1327
|
-
def: {
|
|
1328
|
-
name: "flair_orgevent",
|
|
1329
|
-
description: "Publish an org-wide coordination event (claim/release/status) to the Office Space. Attributed to you — you cannot publish as another agent.",
|
|
1330
|
-
inputSchema: {
|
|
1331
|
-
type: "object",
|
|
1332
|
-
properties: {
|
|
1333
|
-
kind: { type: "string", description: "Event kind (coord.claim, coord.release, status)" },
|
|
1334
|
-
summary: { type: "string", description: "Short summary of the event" },
|
|
1335
|
-
detail: { type: "string", description: "Longer detail payload" },
|
|
1336
|
-
scope: { type: "string", description: "Scope (an agent id, repo, or 'org')" },
|
|
1337
|
-
targets: { type: "array", items: { type: "string" }, description: "Recipient agent ids" },
|
|
1338
|
-
},
|
|
1339
|
-
required: ["kind", "summary"],
|
|
1340
|
-
},
|
|
1341
|
-
},
|
|
1342
1141
|
impl: orgEvent,
|
|
1343
1142
|
contract: {
|
|
1344
1143
|
summary: "Publishes an org event attributed to the caller (authorId from identity, never the body). The echo is thin; persistence is verified in storage.",
|
|
@@ -1346,21 +1145,6 @@ export const TOOLS = {
|
|
|
1346
1145
|
},
|
|
1347
1146
|
},
|
|
1348
1147
|
attention: {
|
|
1349
|
-
def: {
|
|
1350
|
-
name: "attention",
|
|
1351
|
-
description: "What's touching entity E in the last N days? A unified, grouped-by-source view across memories, " +
|
|
1352
|
-
"relationships, active work (WorkspaceState), teammate presence, and org events. Entity must be a " +
|
|
1353
|
-
"vocabulary string (e.g. 'repo:owner/name', 'issue:owner/repo#123', 'subsystem:embeddings').",
|
|
1354
|
-
annotations: { readOnlyHint: true },
|
|
1355
|
-
inputSchema: {
|
|
1356
|
-
type: "object",
|
|
1357
|
-
properties: {
|
|
1358
|
-
entity: { type: "string", description: "Vocabulary string, exact match (type:value — e.g. 'repo:tpsdev-ai/flair')" },
|
|
1359
|
-
days: { type: "number", description: "Window size in days (default 7)" },
|
|
1360
|
-
},
|
|
1361
|
-
required: ["entity"],
|
|
1362
|
-
},
|
|
1363
|
-
},
|
|
1364
1148
|
impl: attention,
|
|
1365
1149
|
contract: {
|
|
1366
1150
|
summary: "Grouped-by-source view { entity, windowDays, since, groups:{memory,relationship,workspaceState,presence,orgEvent}, counts } for entity E over N days.",
|
|
@@ -1373,21 +1157,6 @@ export const TOOLS = {
|
|
|
1373
1157
|
},
|
|
1374
1158
|
},
|
|
1375
1159
|
record_usage: {
|
|
1376
|
-
def: {
|
|
1377
|
-
name: "record_usage",
|
|
1378
|
-
description: "Report that one or more memories were actually USED — cited or relied on to ground an answer or decision. " +
|
|
1379
|
-
"Distinct from search (surfacing a memory is not usage). Drives the recall-quality usage signal; dedup'd " +
|
|
1380
|
-
"(you can only count once per memory) and rate-limited. " +
|
|
1381
|
-
RECORD_USAGE_ID_MERGE_CONTRACT,
|
|
1382
|
-
inputSchema: {
|
|
1383
|
-
type: "object",
|
|
1384
|
-
properties: {
|
|
1385
|
-
memoryIds: { type: "array", items: { type: "string" }, description: "IDs of the memories that were used (max 20 per call). Merged with memoryId when both are supplied." },
|
|
1386
|
-
memoryId: { type: "string", description: "Convenience alias for a single memory id. Merged with memoryIds when both are supplied — not dropped." },
|
|
1387
|
-
attribution: { type: "string", description: "Optional free-text note on what used it (opaque — stored for audit only, max 500 chars)" },
|
|
1388
|
-
},
|
|
1389
|
-
},
|
|
1390
|
-
},
|
|
1391
1160
|
impl: recordUsage,
|
|
1392
1161
|
contract: {
|
|
1393
1162
|
summary: "Invariant acknowledgement { recorded:true } — byte-identical regardless of how many ids counted (no id enumeration, Sherlock).",
|
|
@@ -1396,7 +1165,7 @@ export const TOOLS = {
|
|
|
1396
1165
|
invariants: { fullyResolved: true },
|
|
1397
1166
|
},
|
|
1398
1167
|
},
|
|
1399
|
-
};
|
|
1168
|
+
});
|
|
1400
1169
|
/** The tool definitions for a tools/list response (exactly the 12 curated tools). */
|
|
1401
1170
|
export function listToolDefs() {
|
|
1402
1171
|
return Object.values(TOOLS).map((t) => t.def);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - resources/memory-read-scope.ts's resolveReadScope() — the cross-agent
|
|
7
7
|
* READ scope every read path (Memory.search/get, SemanticSearch,
|
|
8
8
|
* MemoryBootstrap, the by-id auth-middleware guard) resolves through.
|
|
9
|
-
* - src/
|
|
9
|
+
* - src/commands/federation.ts's runFederationSyncOnce() — the federation-sync PUSH filter
|
|
10
10
|
* that must not replicate `private` memories to peer instances.
|
|
11
11
|
*
|
|
12
12
|
* Deliberately has ZERO imports — not even "harper". That is
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* imports `databases` from "harper", and that package's
|
|
17
17
|
* top-level init eagerly resolves storage paths and THROWS when there is no
|
|
18
18
|
* live Harper runtime backing it (confirmed empirically — it takes down
|
|
19
|
-
* even `flair --help`). So src/cli.ts
|
|
20
|
-
* resources/memory-read-scope.ts (or anything else that drags that
|
|
19
|
+
* even `flair --help`). So the CLI (src/cli.ts / src/commands/federation.ts)
|
|
20
|
+
* must never import resources/memory-read-scope.ts (or anything else that drags that
|
|
21
21
|
* side-effecting import in) directly. This module is the safe seam: a pure
|
|
22
22
|
* function + constant that both sides can import without dragging in
|
|
23
23
|
* "harper".
|
|
@@ -61,6 +61,7 @@ import { buildRegistry } from "./migrations/registry.js";
|
|
|
61
61
|
import { runMigrationCycle } from "./migrations/runner.js";
|
|
62
62
|
import { markIdleMigrationsFailed, seedIdleProgress, setCyclePhase } from "./migrations/progress.js";
|
|
63
63
|
import { describeUnresolvableDataDir, resolveWritableMigrationDataDir, } from "./migrations/data-dir.js";
|
|
64
|
+
import { scheduleFollowUpCycles } from "./migrations/recheck.js";
|
|
64
65
|
import { getMode } from "./embeddings-provider.js";
|
|
65
66
|
/** Same "resolve the running package's own version" idiom as resources/health.ts. */
|
|
66
67
|
export function resolveRunningVersion() {
|
|
@@ -147,6 +148,50 @@ function reportBootFailure(registry, reason) {
|
|
|
147
148
|
markIdleMigrationsFailed(registry.list().map((m) => m.id), reason);
|
|
148
149
|
}
|
|
149
150
|
let scheduled = false;
|
|
151
|
+
let followUp = null;
|
|
152
|
+
/**
|
|
153
|
+
* Runs one cycle against an already-resolved writable data dir. Shared by
|
|
154
|
+
* the boot-keyed first pass and the flair#1073 follow-up rechecks. Never
|
|
155
|
+
* throws. Returns whether a follow-up series should be armed (true unless
|
|
156
|
+
* this invocation is itself a follow-up, or the cycle was a hard failure
|
|
157
|
+
* that needs an operator — those retry on the next restart).
|
|
158
|
+
*/
|
|
159
|
+
async function invokeCycle(registry, dataDir, opts = { followUp: false }) {
|
|
160
|
+
try {
|
|
161
|
+
const result = await runMigrationCycle({
|
|
162
|
+
registry,
|
|
163
|
+
getTable,
|
|
164
|
+
dataDir,
|
|
165
|
+
runningVersion: resolveRunningVersion(),
|
|
166
|
+
});
|
|
167
|
+
// `nothing pending` is the healthy no-op; `single-flight` is the
|
|
168
|
+
// lock guard working as designed on a multi-threaded boot. Anything
|
|
169
|
+
// else is a cycle that WANTED to run and couldn't, and must be loud.
|
|
170
|
+
if (!result.ran && result.reason && !isBenignSkip(result.reason)) {
|
|
171
|
+
if (!opts.followUp) {
|
|
172
|
+
reportBootFailure(registry, `migration cycle did not run: ${result.reason}`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.error(`[flair-migrations] follow-up cycle did not run: ${result.reason}`);
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (opts.followUp && result.ran) {
|
|
180
|
+
console.info("[flair-migrations] follow-up cycle ran — embedding-stamp (or another pending migration) had work the boot pass missed");
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
// Defense-in-depth only — runMigrationCycle is documented to never
|
|
186
|
+
// throw. A boot-path exception must never surface here regardless.
|
|
187
|
+
const reason = `unexpected error from runMigrationCycle: ${err?.message ?? String(err)}`;
|
|
188
|
+
if (!opts.followUp)
|
|
189
|
+
reportBootFailure(registry, reason);
|
|
190
|
+
else
|
|
191
|
+
console.error(`[flair-migrations] ${reason}`);
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
150
195
|
export function scheduleMigrationBoot() {
|
|
151
196
|
if (scheduled)
|
|
152
197
|
return;
|
|
@@ -170,28 +215,22 @@ export function scheduleMigrationBoot() {
|
|
|
170
215
|
// failure at that point is reported back rather than thrown, which is
|
|
171
216
|
// precisely how flair#812 stayed invisible. See data-dir.ts.
|
|
172
217
|
const resolved = resolveWritableMigrationDataDir();
|
|
173
|
-
|
|
218
|
+
const dataDir = resolved.dataDir;
|
|
219
|
+
if (!dataDir) {
|
|
174
220
|
reportBootFailure(registry, describeUnresolvableDataDir(resolved.tried));
|
|
175
221
|
return;
|
|
176
222
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
223
|
+
const ok = await invokeCycle(registry, dataDir, { followUp: false });
|
|
224
|
+
// flair#1073: even a clean "nothing pending" can be a Fabric race
|
|
225
|
+
// (tables ready, rows not yet visible). Arm delayed rechecks so a
|
|
226
|
+
// split corpus heals in-process instead of sitting until the next
|
|
227
|
+
// restart. A hard boot failure does not arm them — those need an
|
|
228
|
+
// operator or a restart (same as before).
|
|
229
|
+
if (ok) {
|
|
230
|
+
followUp?.cancel();
|
|
231
|
+
followUp = scheduleFollowUpCycles({
|
|
232
|
+
run: () => invokeCycle(registry, dataDir, { followUp: true }).then(() => undefined),
|
|
183
233
|
});
|
|
184
|
-
// `nothing pending` is the healthy no-op; `single-flight` is the
|
|
185
|
-
// lock guard working as designed on a multi-threaded boot. Anything
|
|
186
|
-
// else is a cycle that WANTED to run and couldn't, and must be loud.
|
|
187
|
-
if (!result.ran && result.reason && !isBenignSkip(result.reason)) {
|
|
188
|
-
reportBootFailure(registry, `migration cycle did not run: ${result.reason}`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
catch (err) {
|
|
192
|
-
// Defense-in-depth only — runMigrationCycle is documented to never
|
|
193
|
-
// throw. A boot-path exception must never surface here regardless.
|
|
194
|
-
reportBootFailure(registry, `unexpected error from runMigrationCycle: ${err?.message ?? String(err)}`);
|
|
195
234
|
}
|
|
196
235
|
})();
|
|
197
236
|
});
|
|
@@ -209,5 +248,7 @@ export function isBenignSkip(reason) {
|
|
|
209
248
|
// ever boots once).
|
|
210
249
|
export function _resetMigrationBootForTests() {
|
|
211
250
|
scheduled = false;
|
|
251
|
+
followUp?.cancel();
|
|
252
|
+
followUp = null;
|
|
212
253
|
}
|
|
213
254
|
scheduleMigrationBoot();
|