Skip to content

Commit 3059702

Browse files
committed
feat(memory-wiki): add agent-facing people wiki metadata
1 parent ccb8472 commit 3059702

14 files changed

Lines changed: 1358 additions & 27 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
182182
## Ops / Footguns
183183

184184
- Remote install docs: `docs/install/{exe-dev,fly,hetzner}.md`. Parallels smoke: `$openclaw-parallels-smoke`; Discord roundtrip: `parallels-discord-roundtrip`.
185-
- Memory wiki: keep prompt digest tiny. The prompt should only say the wiki exists, prefer `wiki_search` / `wiki_get`, start from `reports/maintainer-agent-directory.md` for people routing, and verify contact data before use.
185+
- Memory wiki: keep prompt digest tiny. The prompt should only say the wiki exists, prefer `wiki_search` / `wiki_get`, start from `reports/person-agent-directory.md` for people routing, use search modes (`find-person`, `route-question`, `source-evidence`, `raw-claim`) when useful, and verify contact data before use.
186186
- People wiki provenance: generated identity, social, contact, and "fun detail" notes need explicit source class/confidence (`maintainer-whois`, Discrawl sample/stat, GitHub profile, maintainer repo file). Do not promote inferred details to facts.
187187
- Rebrand/migration/config warnings: run `openclaw doctor`.
188188
- Never edit `node_modules`.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
77
### Changes
88

99
- Providers/NVIDIA: add the NVIDIA provider with API-key onboarding, setup docs, static catalog metadata, and literal model-ref picker support so NVIDIA hosted models can be selected with their provider prefix intact. (#71204) Thanks @eleqtrizit.
10+
- Memory/wiki: add agent-facing people wiki metadata, canonical aliases, person cards, relationship graphs, privacy/provenance reports, evidence-kind drilldown, and search modes for person lookup, question routing, source evidence, and raw claims. Thanks @vincentkoc.
1011
- Messages: add global `messages.visibleReplies` so operators can require visible output to go through `message(action=send)` for any source chat, while `messages.groupChat.visibleReplies` stays available as the group/channel override. Thanks @scoootscooob.
1112
- Gateway/dev: run `pnpm gateway:watch` through a named tmux session by default, with `gateway:watch:raw` and `OPENCLAW_GATEWAY_WATCH_TMUX=0` for foreground mode, so repeated starts respawn an inspectable watcher without trapping the invoking agent shell. Thanks @vincentkoc.
1213
- Plugin SDK: mark remaining legacy alias exports and diffs tool/config aliases with deprecation metadata, and add a guard so future legacy alias comments require `@deprecated` tags. Thanks @vincentkoc.

docs/cli/wiki.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ openclaw wiki ingest ./notes/alpha.md
3838
openclaw wiki compile
3939
openclaw wiki lint
4040
openclaw wiki search "alpha"
41+
openclaw wiki search "who should I ask about Teams?" --mode route-question
4142
openclaw wiki get entity.alpha --from 1 --lines 80
4243

4344
openclaw wiki apply synthesis "Alpha Summary" \
@@ -135,11 +136,34 @@ Behavior depends on config:
135136

136137
- `search.backend`: `shared` or `local`
137138
- `search.corpus`: `wiki`, `memory`, or `all`
139+
- `--mode`: `auto`, `find-person`, `route-question`, `source-evidence`, or
140+
`raw-claim`
138141

139142
Use `wiki search` when you want wiki-specific ranking or provenance details.
140143
For one broad shared recall pass, prefer `openclaw memory search` when the
141144
active memory plugin exposes shared search.
142145

146+
Search modes help the agent choose the right surface:
147+
148+
- `find-person`: aliases, handles, socials, canonical IDs, and person pages
149+
- `route-question`: ask-for/best-used-for hints and relationship context
150+
- `source-evidence`: source pages and structured evidence fields
151+
- `raw-claim`: structured claim text with claim/evidence metadata
152+
153+
Examples:
154+
155+
```bash
156+
openclaw wiki search "bgroux" --mode find-person
157+
openclaw wiki search "who knows Teams rollout?" --mode route-question
158+
openclaw wiki search "maintainer-whois" --mode source-evidence
159+
openclaw wiki search "strong route Teams" --mode raw-claim --json
160+
```
161+
162+
Text output includes `Claim:` and `Evidence:` lines when a result matches a
163+
structured claim. JSON output additionally exposes `matchedClaimId`,
164+
`matchedClaimStatus`, `matchedClaimConfidence`, `evidenceKinds`, and
165+
`evidenceSourceIds` for agent-side drilldown.
166+
143167
### `wiki get <lookup>`
144168

145169
Read a wiki page by id or relative path.

docs/plugins/memory-wiki.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,90 @@ Each claim can include:
150150

151151
Evidence entries can include:
152152

153+
- `kind`
153154
- `sourceId`
154155
- `path`
155156
- `lines`
156157
- `weight`
158+
- `confidence`
159+
- `privacyTier`
157160
- `note`
158161
- `updatedAt`
159162

160163
This is what makes the wiki act more like a belief layer than a passive note
161164
dump. Claims can be tracked, scored, contested, and resolved back to sources.
162165

166+
## Agent-facing entity metadata
167+
168+
Entity pages can also carry routing metadata for agent use. This is generic
169+
frontmatter, so it works for people, teams, systems, projects, or any other
170+
entity type.
171+
172+
Common fields include:
173+
174+
- `entityType`: for example `person`, `team`, `system`, or `project`
175+
- `canonicalId`: stable identity key used across aliases and imports
176+
- `aliases`: names, handles, or labels that should resolve to the same page
177+
- `privacyTier`: `public`, `local-private`, `sensitive`, or `confirm-before-use`
178+
- `bestUsedFor` / `notEnoughFor`: compact routing hints
179+
- `lastRefreshedAt`: source-refresh timestamp separate from page edit time
180+
- `personCard`: optional person-specific routing card with handles, socials,
181+
emails, timezone, lane, ask-for, avoid-asking-for, confidence, and privacy
182+
- `relationships`: typed edges to related pages with target, kind, weight,
183+
confidence, evidence kind, privacy tier, and note
184+
185+
For a people wiki, the agent should usually start with
186+
`reports/person-agent-directory.md`, then open the person page with `wiki_get`
187+
before using contact details or inferred facts.
188+
189+
Example:
190+
191+
```yaml
192+
pageType: entity
193+
entityType: person
194+
id: entity.brad-groux
195+
canonicalId: maintainer.brad-groux
196+
aliases:
197+
- Brad
198+
- bgroux
199+
privacyTier: local-private
200+
bestUsedFor:
201+
- Microsoft Teams and Azure routing
202+
notEnoughFor:
203+
- legal approval
204+
lastRefreshedAt: "2026-04-29T00:00:00.000Z"
205+
personCard:
206+
handles:
207+
- "@bgroux"
208+
socials:
209+
- "https://x.example/bgroux"
210+
emails:
211+
- brad@example.com
212+
timezone: America/Chicago
213+
lane: Microsoft ecosystem
214+
askFor:
215+
- Teams rollout questions
216+
avoidAskingFor:
217+
- unrelated billing decisions
218+
confidence: 0.8
219+
privacyTier: confirm-before-use
220+
relationships:
221+
- targetId: entity.alice
222+
targetTitle: Alice
223+
kind: collaborates-with
224+
confidence: 0.7
225+
evidenceKind: discrawl-stat
226+
claims:
227+
- id: claim.brad.teams
228+
text: Brad is useful for Microsoft Teams routing.
229+
status: supported
230+
confidence: 0.9
231+
evidence:
232+
- kind: maintainer-whois
233+
sourceId: source.maintainers
234+
privacyTier: local-private
235+
```
236+
163237
## Compile pipeline
164238
165239
The compile step reads wiki pages, normalizes summaries, and emits stable
@@ -190,6 +264,10 @@ Built-in reports include:
190264
- `reports/low-confidence.md`
191265
- `reports/claim-health.md`
192266
- `reports/stale-pages.md`
267+
- `reports/person-agent-directory.md`
268+
- `reports/relationship-graph.md`
269+
- `reports/provenance-coverage.md`
270+
- `reports/privacy-review.md`
193271

194272
These reports track things like:
195273

@@ -199,6 +277,10 @@ These reports track things like:
199277
- low-confidence pages and claims
200278
- stale or unknown freshness
201279
- pages with unresolved questions
280+
- person/entity routing cards
281+
- structured relationship edges
282+
- evidence class coverage
283+
- non-public privacy tiers that need review before use
202284

203285
## Search and retrieval
204286

@@ -219,13 +301,31 @@ Important behavior:
219301
- claim ids can resolve back to the owning page
220302
- contested/stale/fresh claims influence ranking
221303
- provenance labels can survive into results
304+
- search mode can bias ranking for person lookup, question routing, source
305+
evidence, or raw claims
222306

223307
Practical rule:
224308

225309
- use `memory_search corpus=all` for one broad recall pass
226310
- use `wiki_search` + `wiki_get` when you care about wiki-specific ranking,
227311
provenance, or page-level belief structure
228312

313+
Search modes:
314+
315+
- `auto`: balanced default
316+
- `find-person`: boost person-like entities, aliases, handles, socials, and
317+
canonical IDs
318+
- `route-question`: boost agent cards, ask-for hints, best-used-for hints, and
319+
relationship context
320+
- `source-evidence`: boost source pages and structured evidence metadata
321+
- `raw-claim`: boost matching structured claims and return claim/evidence
322+
metadata in results
323+
324+
When a result matches a structured claim, `wiki_search` can return
325+
`matchedClaimId`, `matchedClaimStatus`, `matchedClaimConfidence`,
326+
`evidenceKinds`, and `evidenceSourceIds` in its details payload. Text output
327+
also includes compact `Claim:` and `Evidence:` lines when available.
328+
229329
## Agent tools
230330

231331
The plugin registers these tools:
@@ -239,7 +339,9 @@ The plugin registers these tools:
239339
What they do:
240340

241341
- `wiki_status`: current vault mode, health, Obsidian CLI availability
242-
- `wiki_search`: search wiki pages and, when configured, shared memory corpora
342+
- `wiki_search`: search wiki pages and, when configured, shared memory corpora;
343+
accepts `mode` for person lookup, question routing, source evidence, or raw
344+
claim drilldown
243345
- `wiki_get`: read a wiki page by id/path or fall back to shared memory corpus
244346
- `wiki_apply`: narrow synthesis/metadata mutations without freeform page surgery
245347
- `wiki_lint`: structural checks, provenance gaps, contradictions, open questions

extensions/memory-wiki/src/cli.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ import {
2626
runObsidianOpen,
2727
runObsidianSearch,
2828
} from "./obsidian.js";
29-
import { getMemoryWikiPage, searchMemoryWiki } from "./query.js";
29+
import {
30+
getMemoryWikiPage,
31+
searchMemoryWiki,
32+
WIKI_SEARCH_MODES,
33+
type WikiSearchMode,
34+
} from "./query.js";
3035
import { syncMemoryWikiImportedSources } from "./source-sync.js";
3136
import type { MemoryWikiImportedSourceSyncResult } from "./source-sync.js";
3237
import {
@@ -81,6 +86,7 @@ type WikiSearchCommandOptions = {
8186
maxResults?: number;
8287
backend?: ResolvedMemoryWikiConfig["search"]["backend"];
8388
corpus?: ResolvedMemoryWikiConfig["search"]["corpus"];
89+
mode?: WikiSearchMode;
8490
};
8591

8692
type WikiGetCommandOptions = {
@@ -567,9 +573,13 @@ export async function runWikiSearch(params: {
567573
maxResults?: number;
568574
searchBackend?: ResolvedMemoryWikiConfig["search"]["backend"];
569575
searchCorpus?: ResolvedMemoryWikiConfig["search"]["corpus"];
576+
mode?: WikiSearchMode;
570577
json?: boolean;
571578
stdout?: Pick<NodeJS.WriteStream, "write">;
572579
}) {
580+
if (params.mode && !(WIKI_SEARCH_MODES as readonly string[]).includes(params.mode)) {
581+
throw new Error(`wiki search --mode must be one of: ${WIKI_SEARCH_MODES.join(", ")}.`);
582+
}
573583
await syncMemoryWikiImportedSources({ config: params.config, appConfig: params.appConfig });
574584
const results = await searchMemoryWiki({
575585
config: params.config,
@@ -578,6 +588,7 @@ export async function runWikiSearch(params: {
578588
maxResults: params.maxResults,
579589
searchBackend: params.searchBackend,
580590
searchCorpus: params.searchCorpus,
591+
mode: params.mode,
581592
});
582593
const summary = params.json
583594
? JSON.stringify(results, null, 2)
@@ -586,7 +597,7 @@ export async function runWikiSearch(params: {
586597
: results
587598
.map(
588599
(result, index) =>
589-
`${index + 1}. ${result.title} (${result.corpus}/${result.kind})\nPath: ${result.path}${typeof result.startLine === "number" && typeof result.endLine === "number" ? `\nLines: ${result.startLine}-${result.endLine}` : ""}${result.provenanceLabel ? `\nProvenance: ${result.provenanceLabel}` : ""}\nSnippet: ${result.snippet}`,
600+
`${index + 1}. ${result.title} (${result.corpus}/${result.kind})\nPath: ${result.path}${typeof result.startLine === "number" && typeof result.endLine === "number" ? `\nLines: ${result.startLine}-${result.endLine}` : ""}${result.provenanceLabel ? `\nProvenance: ${result.provenanceLabel}` : ""}${result.matchedClaimId ? `\nClaim: ${result.matchedClaimId}` : ""}${result.evidenceKinds && result.evidenceKinds.length > 0 ? `\nEvidence: ${result.evidenceKinds.join(", ")}` : ""}\nSnippet: ${result.snippet}`,
590601
)
591602
.join("\n\n");
592603
writeOutput(summary, params.stdout);
@@ -935,7 +946,8 @@ export function registerWikiCli(
935946
.command("search")
936947
.description("Search wiki pages and, when configured, the active memory corpus")
937948
.argument("<query>", "Search query")
938-
.option("--max-results <n>", "Maximum results", (value: string) => Number(value)),
949+
.option("--max-results <n>", "Maximum results", (value: string) => Number(value))
950+
.option("--mode <mode>", `Search mode (${WIKI_SEARCH_MODES.join(", ")})`),
939951
)
940952
.option("--json", "Print JSON")
941953
.action(async (query: string, opts: WikiSearchCommandOptions) => {
@@ -946,6 +958,7 @@ export function registerWikiCli(
946958
maxResults: opts.maxResults,
947959
searchBackend: opts.backend,
948960
searchCorpus: opts.corpus,
961+
mode: opts.mode,
949962
json: opts.json,
950963
});
951964
});

extensions/memory-wiki/src/compile.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,101 @@ describe("compileMemoryWikiVault", () => {
353353
await expect(fs.access(path.join(rootDir, "reports", "open-questions.md"))).rejects.toThrow();
354354
});
355355

356+
it("writes agent directory, relationship, provenance, and privacy reports", async () => {
357+
const { rootDir, config } = await createVault({
358+
rootDir: nextCaseRoot(),
359+
initialize: true,
360+
});
361+
362+
await fs.writeFile(
363+
path.join(rootDir, "entities", "brad.md"),
364+
renderWikiMarkdown({
365+
frontmatter: {
366+
pageType: "entity",
367+
entityType: "person",
368+
id: "entity.brad",
369+
title: "Brad Groux",
370+
canonicalId: "maintainer.brad-groux",
371+
aliases: ["brad"],
372+
privacyTier: "local-private",
373+
bestUsedFor: ["Microsoft routing"],
374+
lastRefreshedAt: "2026-04-29T00:00:00.000Z",
375+
personCard: {
376+
handles: ["@bgroux"],
377+
lane: "Microsoft Teams",
378+
askFor: ["Teams and Azure questions"],
379+
privacyTier: "confirm-before-use",
380+
},
381+
relationships: [
382+
{
383+
targetId: "entity.alice",
384+
targetTitle: "Alice",
385+
kind: "collaborates-with",
386+
evidenceKind: "discrawl-stat",
387+
privacyTier: "local-private",
388+
},
389+
],
390+
claims: [
391+
{
392+
id: "claim.brad.teams",
393+
text: "Brad is useful for Microsoft Teams routing.",
394+
status: "supported",
395+
confidence: 0.9,
396+
evidence: [
397+
{
398+
kind: "maintainer-whois",
399+
sourceId: "source.maintainers",
400+
privacyTier: "local-private",
401+
},
402+
],
403+
},
404+
],
405+
},
406+
body: "# Brad Groux\n",
407+
}),
408+
"utf8",
409+
);
410+
411+
await compileMemoryWikiVault(config);
412+
413+
await expect(
414+
fs.readFile(path.join(rootDir, "reports", "person-agent-directory.md"), "utf8"),
415+
).resolves.toContain("Microsoft Teams");
416+
await expect(
417+
fs.readFile(path.join(rootDir, "reports", "relationship-graph.md"), "utf8"),
418+
).resolves.toContain("collaborates-with");
419+
await expect(
420+
fs.readFile(path.join(rootDir, "reports", "provenance-coverage.md"), "utf8"),
421+
).resolves.toContain("maintainer-whois: 1");
422+
await expect(
423+
fs.readFile(path.join(rootDir, "reports", "privacy-review.md"), "utf8"),
424+
).resolves.toContain("confirm-before-use");
425+
426+
const agentDigest = JSON.parse(
427+
await fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "agent-digest.json"), "utf8"),
428+
) as {
429+
pages: Array<{
430+
path: string;
431+
canonicalId?: string;
432+
aliases?: string[];
433+
personCard?: { lane?: string };
434+
relationshipCount?: number;
435+
}>;
436+
};
437+
expect(agentDigest.pages).toContainEqual(
438+
expect.objectContaining({
439+
path: "entities/brad.md",
440+
canonicalId: "maintainer.brad-groux",
441+
aliases: ["brad"],
442+
personCard: expect.objectContaining({ lane: "Microsoft Teams" }),
443+
relationshipCount: 1,
444+
}),
445+
);
446+
await expect(
447+
fs.readFile(path.join(rootDir, ".openclaw-wiki", "cache", "claims.jsonl"), "utf8"),
448+
).resolves.toContain('"evidenceKinds":["maintainer-whois"]');
449+
});
450+
356451
it("ignores generated related links when computing backlinks on repeated compile", async () => {
357452
const { rootDir, config } = await createVault({
358453
rootDir: nextCaseRoot(),

0 commit comments

Comments
 (0)