agentix-cli 0.123.1 → 0.125.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/dist/{App-NOCRE5SN.js → App-BUYK4EGC.js} +2 -2
- package/dist/{ChatApp-SETNTMED.js → ChatApp-ZS6EPDDK.js} +2 -2
- package/dist/{agent-YII7XBL4.js → agent-IQV3P3CK.js} +2 -2
- package/dist/{board-dashboard-BBLEP7OA.js → board-dashboard-H5CKFP4E.js} +4 -4
- package/dist/{chunk-ODGGGU5J.js → chunk-326G2KKL.js} +5 -5
- package/dist/{chunk-ODGGGU5J.js.map → chunk-326G2KKL.js.map} +1 -1
- package/dist/{chunk-M6WY53GO.js → chunk-44A5JGRM.js} +2 -2
- package/dist/{chunk-TEXZEXQZ.js → chunk-46JKX6CT.js} +18 -18
- package/dist/chunk-46JKX6CT.js.map +1 -0
- package/dist/{chunk-LRMUK6DC.js → chunk-5DFUUYEA.js} +2 -2
- package/dist/{chunk-GOCYZJMS.js → chunk-5RE2B5YQ.js} +2 -2
- package/dist/{chunk-W3DCOYAE.js → chunk-6GLJIA25.js} +2 -2
- package/dist/{chunk-PGXACYDY.js → chunk-6PJ6XHG4.js} +2 -2
- package/dist/{chunk-TJLTPONY.js → chunk-BN5QMNON.js} +2 -2
- package/dist/{chunk-27FYNGDW.js → chunk-BSTIA4M6.js} +2 -2
- package/dist/{chunk-WXABFPX7.js → chunk-CY6ABHF2.js} +2 -2
- package/dist/{chunk-LJ6ORTLS.js → chunk-FHBPKMCF.js} +2 -2
- package/dist/{chunk-JA6MVY7J.js → chunk-INLKJEEN.js} +2 -2
- package/dist/{chunk-EYIQVQMA.js → chunk-ITATBKUL.js} +2 -2
- package/dist/{chunk-DTSKSYEO.js → chunk-LB4ZIQX3.js} +2 -2
- package/dist/chunk-NW7RVE57.js +49 -0
- package/dist/chunk-NW7RVE57.js.map +1 -0
- package/dist/{chunk-EV2WPHHS.js → chunk-QQYXYJOG.js} +2 -2
- package/dist/{chunk-FQQHUCFN.js → chunk-VHT7HESZ.js} +2 -2
- package/dist/{chunk-UNWXKBRF.js → chunk-WMTGAVPW.js} +2 -2
- package/dist/{chunk-KQW2U5MY.js → chunk-YSIRJZWF.js} +2 -2
- package/dist/{chunk-AIYI2MFV.js → chunk-ZCUIBKKI.js} +17 -17
- package/dist/{chunk-AIYI2MFV.js.map → chunk-ZCUIBKKI.js.map} +1 -1
- package/dist/{chunk-XUKBXHTV.js → chunk-ZTHSA7H3.js} +235 -234
- package/dist/chunk-ZTHSA7H3.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{client-S67ZQ5FC.js → client-PO5LDFS4.js} +2 -2
- package/dist/config-L2RQ6EJX.js +2 -0
- package/dist/config-mutator-44OAOZXM.js +2 -0
- package/dist/{doctor-KFZLZRUN.js → doctor-AU5TCOSG.js} +2 -2
- package/dist/{follow-up-tool-ZVEYUJ6U.js → follow-up-tool-AIOLNUF7.js} +2 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +1 -1
- package/dist/{program-J7ETQWYX.js → program-J2X6TR34.js} +255 -240
- package/dist/program-J2X6TR34.js.map +1 -0
- package/dist/{promote-BQTJNRTG.js → promote-W2BSSTRI.js} +2 -2
- package/dist/query-RV6SHTRN.js +2 -0
- package/dist/{registry-B7OS4AOM.js → registry-5PMZ3ZCT.js} +2 -2
- package/dist/{request-planner-BLULDFMJ.js → request-planner-2G73AJ4S.js} +2 -2
- package/dist/{review-candidates-HMQ6ZXBT.js → review-candidates-S34UICQD.js} +2 -2
- package/dist/{routing-WW56ZXGS.js → routing-K2LAUFRL.js} +2 -2
- package/dist/schedule-tool-YRANRFHO.js +2 -0
- package/dist/scheduler-L4P4Z55R.js +2 -0
- package/dist/{seat-XGANYYMU.js → seat-ZJT5T2NO.js} +2 -2
- package/dist/{select-2HW6Q7UX.js → select-Y23SKHCJ.js} +2 -2
- package/dist/{set-enabled-N5AGU5AH.js → set-enabled-YWXWSKME.js} +2 -2
- package/dist/{tool-XRV6IQ2J.js → tool-5JTCSRTW.js} +2 -2
- package/dist/{tool-RJHDRV4X.js → tool-FZEC5FQX.js} +2 -2
- package/dist/wiki-VP4LSMR5.js +2 -0
- package/package.json +1 -1
- package/dist/chunk-22SWFBSL.js +0 -49
- package/dist/chunk-22SWFBSL.js.map +0 -1
- package/dist/chunk-TEXZEXQZ.js.map +0 -1
- package/dist/chunk-XUKBXHTV.js.map +0 -1
- package/dist/config-3AX5MHPN.js +0 -2
- package/dist/config-mutator-BYFMZQBT.js +0 -2
- package/dist/program-J7ETQWYX.js.map +0 -1
- package/dist/query-6YBM4TPV.js +0 -2
- package/dist/schedule-tool-Y6RMN6ZT.js +0 -2
- package/dist/scheduler-4GB47SDH.js +0 -2
- package/dist/wiki-ZA4FU3PW.js +0 -2
- /package/dist/{App-NOCRE5SN.js.map → App-BUYK4EGC.js.map} +0 -0
- /package/dist/{ChatApp-SETNTMED.js.map → ChatApp-ZS6EPDDK.js.map} +0 -0
- /package/dist/{agent-YII7XBL4.js.map → agent-IQV3P3CK.js.map} +0 -0
- /package/dist/{board-dashboard-BBLEP7OA.js.map → board-dashboard-H5CKFP4E.js.map} +0 -0
- /package/dist/{chunk-M6WY53GO.js.map → chunk-44A5JGRM.js.map} +0 -0
- /package/dist/{chunk-LRMUK6DC.js.map → chunk-5DFUUYEA.js.map} +0 -0
- /package/dist/{chunk-GOCYZJMS.js.map → chunk-5RE2B5YQ.js.map} +0 -0
- /package/dist/{chunk-W3DCOYAE.js.map → chunk-6GLJIA25.js.map} +0 -0
- /package/dist/{chunk-PGXACYDY.js.map → chunk-6PJ6XHG4.js.map} +0 -0
- /package/dist/{chunk-TJLTPONY.js.map → chunk-BN5QMNON.js.map} +0 -0
- /package/dist/{chunk-27FYNGDW.js.map → chunk-BSTIA4M6.js.map} +0 -0
- /package/dist/{chunk-WXABFPX7.js.map → chunk-CY6ABHF2.js.map} +0 -0
- /package/dist/{chunk-LJ6ORTLS.js.map → chunk-FHBPKMCF.js.map} +0 -0
- /package/dist/{chunk-JA6MVY7J.js.map → chunk-INLKJEEN.js.map} +0 -0
- /package/dist/{chunk-EYIQVQMA.js.map → chunk-ITATBKUL.js.map} +0 -0
- /package/dist/{chunk-DTSKSYEO.js.map → chunk-LB4ZIQX3.js.map} +0 -0
- /package/dist/{chunk-EV2WPHHS.js.map → chunk-QQYXYJOG.js.map} +0 -0
- /package/dist/{chunk-FQQHUCFN.js.map → chunk-VHT7HESZ.js.map} +0 -0
- /package/dist/{chunk-UNWXKBRF.js.map → chunk-WMTGAVPW.js.map} +0 -0
- /package/dist/{chunk-KQW2U5MY.js.map → chunk-YSIRJZWF.js.map} +0 -0
- /package/dist/{client-S67ZQ5FC.js.map → client-PO5LDFS4.js.map} +0 -0
- /package/dist/{config-3AX5MHPN.js.map → config-L2RQ6EJX.js.map} +0 -0
- /package/dist/{config-mutator-BYFMZQBT.js.map → config-mutator-44OAOZXM.js.map} +0 -0
- /package/dist/{doctor-KFZLZRUN.js.map → doctor-AU5TCOSG.js.map} +0 -0
- /package/dist/{follow-up-tool-ZVEYUJ6U.js.map → follow-up-tool-AIOLNUF7.js.map} +0 -0
- /package/dist/{promote-BQTJNRTG.js.map → promote-W2BSSTRI.js.map} +0 -0
- /package/dist/{query-6YBM4TPV.js.map → query-RV6SHTRN.js.map} +0 -0
- /package/dist/{registry-B7OS4AOM.js.map → registry-5PMZ3ZCT.js.map} +0 -0
- /package/dist/{request-planner-BLULDFMJ.js.map → request-planner-2G73AJ4S.js.map} +0 -0
- /package/dist/{review-candidates-HMQ6ZXBT.js.map → review-candidates-S34UICQD.js.map} +0 -0
- /package/dist/{routing-WW56ZXGS.js.map → routing-K2LAUFRL.js.map} +0 -0
- /package/dist/{schedule-tool-Y6RMN6ZT.js.map → schedule-tool-YRANRFHO.js.map} +0 -0
- /package/dist/{scheduler-4GB47SDH.js.map → scheduler-L4P4Z55R.js.map} +0 -0
- /package/dist/{seat-XGANYYMU.js.map → seat-ZJT5T2NO.js.map} +0 -0
- /package/dist/{select-2HW6Q7UX.js.map → select-Y23SKHCJ.js.map} +0 -0
- /package/dist/{set-enabled-N5AGU5AH.js.map → set-enabled-YWXWSKME.js.map} +0 -0
- /package/dist/{tool-XRV6IQ2J.js.map → tool-5JTCSRTW.js.map} +0 -0
- /package/dist/{tool-RJHDRV4X.js.map → tool-FZEC5FQX.js.map} +0 -0
- /package/dist/{wiki-ZA4FU3PW.js.map → wiki-VP4LSMR5.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wiki/promote.ts","../src/wiki/prompts.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { AgentMemory, type MemoryRecord, type MemoryType } from \"../agents/agent-memory\"\nimport { WikiStore } from \"./store\"\nimport { buildMemoryPromotePrompt } from \"./prompts\"\nimport { isWikiArticleType, type WikiIndex } from \"./types\"\nimport { newProposalId, readProposal, saveProposal, type PromotionProposal, type ProposalSource } from \"./proposals\"\n\n// --- Memory → wiki promotion ---\n//\n// Finishes the architecture documented in agent-memory.ts: memory is\n// experiential / per-agent; the wiki is authoritative / cross-agent. This\n// module reads per-agent memories, decides (via an LLM judge) which are\n// durable and fleet-relevant, and PROPOSES them as [[wikilinked]] articles\n// for the SHARED wiki store. Nothing reaches the wiki until an operator\n// approves the proposal (proposals.ts): a wrong or planted lesson would\n// otherwise reach every agent overnight.\n//\n// Idempotency has two layers, both keyed on the versioned stamp\n// `memory:<agentId>/<type>_<name>@<updatedAt>`:\n//\n// 1. Promoted memories: the stamp is recorded in the target article's\n// `sources[]` frontmatter (same pattern as getUnabsorbedEntries).\n// 2. Skipped memories: recorded in `.agentx/wiki/_memory-promotions.json`\n// so permanently-irrelevant memories aren't re-judged every night.\n//\n// A bumped `updatedAt` beats both — an edited memory re-enters the\n// pipeline, and mergeSources() replaces the stale stamp for its key so\n// frontmatter stays bounded (one stamp per memory, not per edit).\n\nexport const PROMOTER_OWNER = \"memory-promoter\"\nexport const DEFAULT_PROMOTE_TYPES: MemoryType[] = [\"project\", \"reference\", \"feedback\"]\nexport const PROMOTION_LEDGER_FILE = \"_memory-promotions.json\"\n\nconst STAMP_PREFIX = \"memory:\"\nconst FAILURE_PREFIX = \"failure:\"\n\n/** `failure:<signature digest>@<recurrence bucket>` without the bucket. */\nfunction failureIdentity(stamp: string): string {\n const at = stamp.lastIndexOf(\"@\")\n return at === -1 ? stamp : stamp.slice(0, at)\n}\nconst MEMORY_TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface MemoryCandidate {\n agentId: string\n memory: MemoryRecord\n /** Identity without version: `<agentId>/<type>_<name>` */\n key: string\n /** Identity + version: `memory:<key>@<updatedAt>`. Other sources use\n * their own namespace (`review:`, `failure:`). */\n stamp: string\n /** Distinct sessions a review finding or failure recurred in. More\n * occurrences rank first. */\n occurrences?: number\n /** Some of those sessions, kept as proposal evidence. */\n sessions?: string[]\n /** Some of the runs (task ids) behind a failure, kept as evidence. */\n tasks?: string[]\n /** What a recurring failure has in common: its signature and run count. */\n failure?: { tool: string; errorClass: string; runs: number }\n}\n\nexport interface PromotionLedgerEntry {\n stamp: string\n /** proposed: waiting for an operator; rejected: an operator said no. Both\n * keep the memory from being judged again until it changes. */\n decision: \"promoted\" | \"skipped\" | \"proposed\" | \"rejected\"\n /** Article path when promoted or proposed. */\n article?: string\n /** Proposal id when proposed or decided through one. */\n proposal?: string\n /** LLM skip reason. */\n reason?: string\n /** ISO run timestamp. */\n at: string\n}\n\nexport type PromotionLedger = PromotionLedgerEntry[]\n\n// --- Stamps -------------------------------------------------------------\n\nexport function memoryKey(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\">): string {\n return `${agentId}/${m.type}_${m.name}`\n}\n\nexport function memoryStamp(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\" | \"updatedAt\">): string {\n return `${STAMP_PREFIX}${memoryKey(agentId, m)}@${m.updatedAt}`\n}\n\n/** Parse a `memory:<agentId>/<type>_<name>@<updatedAt>` source stamp.\n * Returns null for anything else (raw entry ids, malformed stamps) so\n * callers can filter article `sources[]` safely. */\nexport function parseMemoryStamp(s: string): {\n agentId: string\n type: MemoryType\n name: string\n updatedAt: string\n key: string\n} | null {\n if (!s.startsWith(STAMP_PREFIX)) return null\n const rest = s.slice(STAMP_PREFIX.length)\n const slash = rest.indexOf(\"/\")\n const at = rest.lastIndexOf(\"@\")\n if (slash <= 0 || at <= slash) return null\n const agentId = rest.slice(0, slash)\n const typeName = rest.slice(slash + 1, at)\n const updatedAt = rest.slice(at + 1)\n if (!updatedAt) return null\n const underscore = typeName.indexOf(\"_\")\n if (underscore <= 0) return null\n const type = typeName.slice(0, underscore) as MemoryType\n const name = typeName.slice(underscore + 1)\n if (!MEMORY_TYPES.includes(type) || !name) return null\n return { agentId, type, name, updatedAt, key: `${agentId}/${typeName}` }\n}\n\n/** Merge new promotion stamps into an article's existing sources: a new\n * stamp REPLACES any prior stamp sharing its key (one stamp per memory,\n * not one per edit); everything else (raw entry ids, other memories'\n * stamps) is kept. Deduped, order-stable. */\nexport function mergeSources(existing: string[] | undefined, newStamps: string[]): string[] {\n const newKeys = new Set<string>()\n for (const s of newStamps) {\n const parsed = parseMemoryStamp(s)\n if (parsed) newKeys.add(parsed.key)\n }\n const out: string[] = []\n for (const s of existing ?? []) {\n const parsed = parseMemoryStamp(s)\n if (parsed && newKeys.has(parsed.key)) continue // superseded\n if (!out.includes(s)) out.push(s)\n }\n for (const s of newStamps) {\n if (!out.includes(s)) out.push(s)\n }\n return out\n}\n\n// --- Cross-agent memory enumeration --------------------------------------\n\n/** List every agent's memories. AgentMemory has no cross-agent API — we\n * enumerate the agent subdirs of `.agentx/agent-memory/` ourselves.\n * `memoryRoot` is the `.agentx` directory (AgentMemory's baseDir root). */\nexport function listAllAgentMemories(\n memoryRoot: string = resolve(process.cwd(), \".agentx\"),\n): Array<{ agentId: string; memory: MemoryRecord }> {\n const store = new AgentMemory({ baseDir: memoryRoot })\n if (!existsSync(store.baseDir)) return []\n const out: Array<{ agentId: string; memory: MemoryRecord }> = []\n for (const entry of readdirSync(store.baseDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith(\"_\") || entry.name.startsWith(\".\")) continue\n for (const memory of store.list(entry.name)) {\n out.push({ agentId: entry.name, memory })\n }\n }\n return out\n}\n\n// --- Skip ledger ----------------------------------------------------------\n\nconst LEDGER_DECISIONS = new Set([\"promoted\", \"skipped\", \"proposed\", \"rejected\"])\n\nexport function readPromotionLedger(wikiBaseDir: string): PromotionLedger {\n const path = resolve(wikiBaseDir, PROMOTION_LEDGER_FILE)\n if (!existsSync(path)) return []\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\"))\n if (!Array.isArray(parsed)) return []\n return parsed.filter(\n (e): e is PromotionLedgerEntry =>\n e && typeof e.stamp === \"string\" && LEDGER_DECISIONS.has(e.decision),\n )\n } catch {\n // Corrupt ledger → treat as empty. Worst case: skipped memories are\n // re-judged once. Promoted memories stay deduped via article sources.\n return []\n }\n}\n\n/** Append entries and compact: keep only the newest entry per memory key\n * (per exact stamp for `review:` and `failure:` sources, where the later\n * entry wins) so the ledger stays bounded. */\nexport function appendPromotionLedger(wikiBaseDir: string, entries: PromotionLedgerEntry[]): void {\n const merged = [...readPromotionLedger(wikiBaseDir), ...entries]\n const byKey = new Map<string, PromotionLedgerEntry>()\n for (const e of merged) {\n const parsed = parseMemoryStamp(e.stamp)\n const key = parsed?.key ?? e.stamp\n const prev = byKey.get(key)\n // A non-memory stamp has no version to compare: the later decision\n // (rejected after proposed) must replace the earlier one.\n if (!prev || !parsed || (parseMemoryStamp(prev.stamp)?.updatedAt ?? \"\") <= parsed.updatedAt) {\n byKey.set(key, e)\n }\n }\n writeFileSync(\n resolve(wikiBaseDir, PROMOTION_LEDGER_FILE),\n JSON.stringify([...byKey.values()], null, 2),\n )\n}\n\n// --- Clustering -------------------------------------------------------------\n\nexport interface PromotionCluster {\n /** ≥1 candidate; >1 when the same `<type>_<name>` exists on several agents. */\n candidates: MemoryCandidate[]\n corroboratingAgents: string[]\n /** 0.5 base + 0.15 per extra corroborating agent + 0.05 for\n * project|reference types, capped at 0.95. Advisory only — shown to\n * the LLM judge, never a hard gate (mirrors clusterWorkflowCandidates). */\n confidence: number\n}\n\n/** Group candidates that share `<type>_<name>` across agents. Exact-match\n * clustering only — two agents naming a memory identically is treated as\n * corroboration; fuzzy matching is future work. */\nexport function groupCandidates(cands: MemoryCandidate[]): PromotionCluster[] {\n const byName = new Map<string, MemoryCandidate[]>()\n for (const c of cands) {\n const k = `${c.memory.type}_${c.memory.name}`\n const list = byName.get(k) ?? []\n list.push(c)\n byName.set(k, list)\n }\n return [...byName.values()].map((candidates) => {\n const corroboratingAgents = [...new Set(candidates.map((c) => c.agentId))].sort()\n const typeBoost = [\"project\", \"reference\"].includes(candidates[0].memory.type) ? 0.05 : 0\n const confidence = Math.min(0.95, 0.5 + 0.15 * (corroboratingAgents.length - 1) + typeBoost)\n return { candidates, corroboratingAgents, confidence }\n })\n}\n\n// --- LLM response parsing ----------------------------------------------------\n\nexport interface PromotedArticle {\n path: string\n title: string\n type?: string\n related?: string[]\n tags: string[]\n content: string\n /** Validated stamps of the memories this article was promoted from. */\n promotedFrom: string[]\n}\n\nexport interface PromotionResponse {\n articles: PromotedArticle[]\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n /** Stamp refs the LLM emitted that weren't in the offered set, plus\n * articles dropped for having zero valid stamps. For the report. */\n warnings: string[]\n}\n\n/** Parse the promotion LLM's reply. Balanced-JSON extraction (adapted from\n * the wiki absorb CLI): find the outermost `{...}`, depth-scan to its\n * close, JSON.parse, then validate. Stamps not present in `offeredStamps`\n * are hallucinations — dropped; an article left with zero valid stamps is\n * dropped entirely (nothing to dedupe against on the next run). Any\n * failure returns `{error}` — the caller must write nothing. */\nexport function parsePromotionResponse(\n text: string,\n offeredStamps: Set<string>,\n): PromotionResponse | { error: string } {\n const start = text.indexOf(\"{\")\n if (start === -1) return { error: \"no JSON object in response\" }\n let depth = 0\n let end = -1\n let inString = false\n let escaped = false\n for (let i = start; i < text.length; i++) {\n const ch = text[i]\n if (escaped) { escaped = false; continue }\n if (ch === \"\\\\\") { escaped = true; continue }\n if (ch === '\"') { inString = !inString; continue }\n if (inString) continue\n if (ch === \"{\") depth++\n else if (ch === \"}\") { depth--; if (depth === 0) { end = i + 1; break } }\n }\n if (end === -1) return { error: \"unbalanced JSON in response\" }\n\n let parsed: any\n try {\n parsed = JSON.parse(text.slice(start, end))\n } catch (e: any) {\n return { error: `JSON parse error: ${e.message}` }\n }\n if (!parsed || typeof parsed !== \"object\" || !Array.isArray(parsed.articles)) {\n return { error: \"response JSON missing articles array\" }\n }\n\n const warnings: string[] = []\n const articles: PromotedArticle[] = []\n for (const a of parsed.articles) {\n if (!a || typeof a.path !== \"string\" || typeof a.title !== \"string\" || typeof a.content !== \"string\") {\n warnings.push(`article missing path/title/content — dropped`)\n continue\n }\n const raw = Array.isArray(a.promotedFrom) ? a.promotedFrom.filter((s: unknown) => typeof s === \"string\") : []\n const promotedFrom = raw.filter((s: string) => offeredStamps.has(s))\n for (const s of raw) {\n if (!offeredStamps.has(s)) warnings.push(`article \"${a.path}\" references unknown stamp \"${s}\" — dropped`)\n }\n if (promotedFrom.length === 0) {\n warnings.push(`article \"${a.path}\" has no valid promotedFrom stamps — dropped`)\n continue\n }\n articles.push({\n path: a.path,\n title: a.title,\n type: typeof a.type === \"string\" ? a.type : undefined,\n related: Array.isArray(a.related) ? a.related.filter((r: unknown) => typeof r === \"string\") : undefined,\n tags: Array.isArray(a.tags) ? a.tags.filter((t: unknown) => typeof t === \"string\") : [],\n content: a.content,\n promotedFrom,\n })\n }\n\n const skipped: Array<{ stamp: string; reason: string }> = []\n for (const s of Array.isArray(parsed.skipped) ? parsed.skipped : []) {\n const stamp = typeof s?.memory === \"string\" ? s.memory : typeof s?.stamp === \"string\" ? s.stamp : \"\"\n if (!offeredStamps.has(stamp)) {\n if (stamp) warnings.push(`skip references unknown stamp \"${stamp}\" — dropped`)\n continue\n }\n skipped.push({ stamp, reason: typeof s.reason === \"string\" ? s.reason : \"\" })\n }\n\n const gaps = Array.isArray(parsed.gaps) ? parsed.gaps.filter((g: unknown) => typeof g === \"string\") : []\n return { articles, skipped, gaps, warnings }\n}\n\n// --- Candidate selection ---------------------------------------------------\n\nexport interface UnpromotedOptions {\n /** Memory types to consider. Default: project, reference, feedback. */\n types?: MemoryType[]\n /** Only memories updated within this window (ms before `now`). */\n sinceMs?: number\n /** Only this agent's memories. */\n agentFilter?: string\n /** Reference clock for `sinceMs` (injectable for tests). Default Date.now(). */\n now?: number\n /** Cap on returned candidates (newest first). Default 20. */\n max?: number\n}\n\n/** Diff all agent memories against what's already been promoted (article\n * `sources[]` stamps across the shared index) or deliberately skipped\n * (ledger). A memory is a candidate iff its key is unseen OR its\n * `updatedAt` is newer than the recorded one — ISO-8601 strings compare\n * lexicographically, so plain `>` is safe. */\nexport function getUnpromotedMemories(\n all: Array<\n // `stamp` is supplied by non-memory sources so their namespace\n // survives; the evidence fields travel through to the proposal.\n Pick<MemoryCandidate, \"agentId\" | \"memory\"> & Partial<Omit<MemoryCandidate, \"agentId\" | \"memory\" | \"key\">>\n >,\n index: WikiIndex,\n ledger: PromotionLedger,\n opts: UnpromotedOptions = {},\n): MemoryCandidate[] {\n const types = opts.types ?? DEFAULT_PROMOTE_TYPES\n const now = opts.now ?? Date.now()\n const max = opts.max ?? 20\n\n // key → newest updatedAt already handled (promoted or skipped)\n const seen = new Map<string, string>()\n // Exact stamps already handled. Non-memory sources (`review:`,\n // `failure:`) have no parseable key/version, so this is how they are\n // recognised as proposed, promoted, skipped or rejected instead of\n // being offered again every night.\n const handledStamps = new Set<string>()\n const record = (stamp: string) => {\n handledStamps.add(stamp)\n const parsed = parseMemoryStamp(stamp)\n if (!parsed) return\n const prev = seen.get(parsed.key)\n if (!prev || parsed.updatedAt > prev) seen.set(parsed.key, parsed.updatedAt)\n }\n for (const article of index.articles) {\n for (const s of article.sources ?? []) record(s)\n }\n for (const e of ledger) record(e.stamp)\n // A failure's stamp moves on as it recurs more (failure-candidates.ts);\n // while one proposal for it waits for review, don't open a second.\n const pendingFailures = new Set(ledger\n .filter((e) => e.decision === \"proposed\" && e.stamp.startsWith(FAILURE_PREFIX))\n .map((e) => failureIdentity(e.stamp)))\n\n const candidates: MemoryCandidate[] = []\n for (const { agentId, memory, stamp, occurrences, sessions, tasks, failure } of all) {\n if (!types.includes(memory.type)) continue\n if (opts.agentFilter && agentId !== opts.agentFilter) continue\n if (opts.sinceMs !== undefined) {\n const updated = Date.parse(memory.updatedAt)\n if (!Number.isFinite(updated) || updated < now - opts.sinceMs) continue\n }\n if (stamp && handledStamps.has(stamp)) continue\n if (stamp?.startsWith(FAILURE_PREFIX) && pendingFailures.has(failureIdentity(stamp))) continue\n const key = memoryKey(agentId, memory)\n const handled = seen.get(key)\n if (handled && memory.updatedAt <= handled) continue\n // A caller-supplied stamp is kept as-is. Re-deriving it would\n // rewrite a `review:` stamp into the `memory:` namespace and lose\n // the provenance the ledger needs to tell the two sources apart.\n candidates.push({\n agentId, memory, key, stamp: stamp ?? memoryStamp(agentId, memory), occurrences,\n ...(sessions?.length ? { sessions } : {}),\n ...(tasks?.length ? { tasks } : {}),\n ...(failure ? { failure } : {}),\n })\n }\n\n // Recurrence first where a source provides it, then recency. Memory\n // candidates carry no count, so their ordering is unchanged.\n return candidates\n .sort((a, b) =>\n (b.occurrences ?? 0) - (a.occurrences ?? 0) ||\n b.memory.updatedAt.localeCompare(a.memory.updatedAt))\n .slice(0, max)\n}\n\n// --- LLM transport -----------------------------------------------------------\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\"\nconst ANTHROPIC_VERSION = \"2023-06-01\"\n\nexport interface PromotionLlmOptions {\n /** Route through the daemon: POST /task to this agent. No API key needed —\n * the agent's own session handles auth (mirrors architectWorkflowViaAgent). */\n viaAgent?: string\n /** Direct Anthropic API model. Requires ANTHROPIC_API_KEY. */\n model?: string\n daemonUrl?: string\n timeoutMs?: number\n fetchImpl?: typeof fetch\n /** Stable chat id so a retry's --resume replay sees the first reply. */\n chatId?: string\n}\n\n/** Run the promotion prompt through an LLM and return the raw reply text.\n * Throws on transport failure — the caller decides whether to retry.\n * `feedback` turns the call into a correction round: the via-agent branch\n * resumes the same session; the direct branch replays the conversation. */\nexport async function callPromotionLlm(\n prompt: string,\n opts: PromotionLlmOptions,\n feedback: string | null = null,\n): Promise<string> {\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 180_000\n const ctrl = new AbortController()\n const timer = setTimeout(() => ctrl.abort(), timeoutMs)\n try {\n if (opts.viaAgent) {\n const daemonUrl = (opts.daemonUrl ?? \"http://127.0.0.1:18800\").replace(/\\/+$/, \"\")\n const message = feedback\n ? `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object — same structure, fixed issues, valid JSON only.`\n : prompt\n const res = await fetchImpl(`${daemonUrl}/task`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n agent: opts.viaAgent,\n message,\n // First call gets a fresh session; the retry resumes it so the\n // agent can fix its own output instead of starting over.\n freshSession: feedback === null,\n context: { channel: \"api\", chatId: opts.chatId ?? \"memory-promote\" },\n }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`agent /task ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n if (body.error) throw new Error(`agent reported error: ${body.error}`)\n const content = String(body.content ?? \"\").trim()\n if (!content) throw new Error(\"agent returned empty content\")\n return content\n }\n\n if (!opts.model) throw new Error(\"callPromotionLlm: viaAgent or model is required\")\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) throw new Error(\"ANTHROPIC_API_KEY not set; use --via <agentId> instead\")\n const messages: Array<{ role: string; content: string }> = [{ role: \"user\", content: prompt }]\n if (feedback) {\n messages.push({ role: \"assistant\", content: \"(previous attempt; will retry with corrections)\" })\n messages.push({ role: \"user\", content: `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object.` })\n }\n const res = await fetchImpl(ANTHROPIC_API, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_VERSION,\n },\n body: JSON.stringify({ model: opts.model, max_tokens: 8000, messages }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`anthropic API ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n const blocks = Array.isArray(body.content) ? body.content : []\n const text = blocks.filter((b: any) => b?.type === \"text\").map((b: any) => b.text).join(\"\\n\").trim()\n if (!text) throw new Error(\"anthropic API returned no text content\")\n return text\n } finally {\n clearTimeout(timer)\n }\n}\n\n// --- Orchestrator ------------------------------------------------------------\n\nexport interface RunPromotionOptions extends PromotionLlmOptions {\n /** Wiki base dir (the shared store). Default `.agentx/wiki`. */\n wikiDir?: string\n /** The `.agentx` dir holding `agent-memory/`. Default `.agentx`. */\n memoryRoot?: string\n sinceMs?: number\n agentFilter?: string\n types?: MemoryType[]\n max?: number\n /** Candidates from somewhere other than agent memory — session-monitor\n * reviews and recurring failures. They join the same pool so they inherit\n * the ledger, the dedupe and the judge rather than getting a second\n * pipeline that drifts from this one. */\n extraCandidates?: MemoryCandidate[]\n /** Write proposals + ledger. Default false (dry-run). */\n commit?: boolean\n /** Upper bound on the judge prompt, in estimated tokens. Clusters are\n * dropped from the end (lowest ranked) until it fits; they stay\n * unledgered, so the next run picks them up. Default 60 000. */\n budgetTokens?: number\n log?: (msg: string) => void\n /** Injectable clock (tests). */\n now?: number\n}\n\nexport interface PromotionReport {\n candidates: MemoryCandidate[]\n clusters: PromotionCluster[]\n /** Proposals written this run; nothing reaches the wiki until approved. */\n proposed: Array<{ id: string; path: string; title: string; type?: string; stamps: string[]; agents: string[]; occurrences: number }>\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n warnings: string[]\n errors: string[]\n dryRun: boolean\n}\n\n/** End-to-end promotion run: enumerate memories → diff against promoted/\n * skipped → cluster → (dry-run stops here) → LLM judge → write articles\n * with merged sources → ledger → rebuild index. Parses the full response\n * before any write — a bad reply writes nothing, and idempotency makes\n * the next scheduled run retry for free. */\nexport async function runPromotion(opts: RunPromotionOptions = {}): Promise<PromotionReport> {\n const wikiDir = opts.wikiDir ?? resolve(process.cwd(), \".agentx\", \"wiki\")\n const memoryRoot = opts.memoryRoot ?? resolve(process.cwd(), \".agentx\")\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now()\n const report: PromotionReport = {\n candidates: [], clusters: [], proposed: [], skipped: [],\n gaps: [], warnings: [], errors: [], dryRun: !opts.commit,\n }\n\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const index = store.rebuildIndex()\n const ledger = readPromotionLedger(wikiDir)\n const all = [...listAllAgentMemories(memoryRoot), ...(opts.extraCandidates ?? [])]\n\n report.candidates = getUnpromotedMemories(all, index, ledger, {\n types: opts.types,\n sinceMs: opts.sinceMs,\n agentFilter: opts.agentFilter,\n now,\n max: opts.max,\n })\n if (report.candidates.length === 0) {\n log(\"no unpromoted memories in window\")\n return report\n }\n report.clusters = groupCandidates(report.candidates)\n if (report.dryRun) return report\n\n const articleList = index.articles.map((a) => ({ title: a.title, path: a.path, type: a.type }))\n const worldview = store.getWorldview() ?? \"\"\n const budget = opts.budgetTokens ?? DEFAULT_BUDGET_TOKENS\n let clusters = report.clusters\n let prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n while (estimateTokens(prompt) > budget && clusters.length > 1) {\n clusters = clusters.slice(0, -1)\n prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n }\n if (estimateTokens(prompt) > budget) {\n report.errors.push(`judge prompt (~${estimateTokens(prompt)} tokens) exceeds the ${budget}-token budget even with one cluster`)\n return report\n }\n if (clusters.length < report.clusters.length) {\n report.warnings.push(`token budget: judged ${clusters.length} of ${report.clusters.length} clusters; the rest wait for the next run`)\n }\n const judged = clusters.flatMap((c) => c.candidates)\n const offered = new Set(judged.map((c) => c.stamp))\n const chatId = opts.chatId ?? `memory-promote-${new Date(now).toISOString().slice(0, 10)}`\n\n let parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }), offered)\n if (\"error\" in parsed) {\n log(`parse failed (${parsed.error}) — retrying with feedback`)\n parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }, parsed.error), offered)\n }\n if (\"error\" in parsed) {\n report.errors.push(`LLM response unusable after retry: ${parsed.error}`)\n return report\n }\n report.warnings.push(...parsed.warnings)\n report.gaps = parsed.gaps\n\n const at = new Date(now).toISOString()\n const ledgerEntries: PromotionLedgerEntry[] = []\n\n const byStamp = new Map(judged.map((c) => [c.stamp, c]))\n for (const article of parsed.articles) {\n const existing = store.readArticle(article.path)\n if (existing && !store.canWrite(existing.meta, PROMOTER_OWNER)) {\n // Owned by another agent — a human resolves; stamps stay unledgered\n // so the memory is retried after the standoff clears.\n report.errors.push(`write denied for \"${article.path}\" (owned by ${existing.meta.owner ?? \"?\"})`)\n continue\n }\n const evidence = evidenceFor(article.promotedFrom, byStamp)\n const proposal: PromotionProposal = {\n id: newProposalId(now, article.path),\n createdAt: at,\n status: \"pending\",\n article,\n evidence,\n ...(existing ? { replaces: { lastUpdated: existing.meta.lastUpdated, contentHash: contentHash(existing.content) } } : {}),\n }\n saveProposal(wikiDir, proposal)\n report.proposed.push({\n id: proposal.id, path: article.path, title: article.title, type: article.type,\n stamps: article.promotedFrom, agents: evidence.agents, occurrences: evidence.occurrences,\n })\n for (const stamp of article.promotedFrom) {\n ledgerEntries.push({ stamp, decision: \"proposed\", article: article.path, proposal: proposal.id, at })\n }\n }\n\n for (const s of parsed.skipped) {\n ledgerEntries.push({ stamp: s.stamp, decision: \"skipped\", reason: s.reason, at })\n report.skipped.push(s)\n }\n\n if (ledgerEntries.length) appendPromotionLedger(wikiDir, ledgerEntries)\n return report\n}\n\nconst DEFAULT_BUDGET_TOKENS = 60_000\n\n/** Rough token count (4 characters per token) — enough for a budget. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4)\n}\n\nfunction contentHash(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\nconst EXCERPT_CHARS = 600\n\n/** Which pipeline a stamp came from. */\nexport function sourceKind(stamp: string): ProposalSource[\"kind\"] {\n if (stamp.startsWith(\"review:\")) return \"review\"\n if (stamp.startsWith(FAILURE_PREFIX)) return \"failure\"\n return \"memory\"\n}\n\n/** What a reviewer needs to judge a proposal: each source memory, review\n * finding or recurring failure, who wrote it and in which task, how often\n * it recurred, and some of the sessions and runs it came from. */\nexport function evidenceFor(stamps: string[], byStamp: Map<string, MemoryCandidate>): PromotionProposal[\"evidence\"] {\n const sources: ProposalSource[] = []\n for (const stamp of stamps) {\n const c = byStamp.get(stamp)\n if (!c) continue\n sources.push({\n stamp,\n kind: sourceKind(stamp),\n agentId: c.agentId,\n type: c.memory.type,\n name: c.memory.name,\n description: c.memory.description,\n ...(c.occurrences ? { occurrences: c.occurrences } : {}),\n ...(c.sessions?.length ? { sessions: c.sessions } : {}),\n ...(c.tasks?.length ? { tasks: c.tasks } : {}),\n ...(c.failure ? { failure: c.failure } : {}),\n ...(c.memory.author ? { author: c.memory.author } : {}),\n ...(c.memory.taskId ? { taskId: c.memory.taskId } : {}),\n updatedAt: c.memory.updatedAt,\n excerpt: c.memory.body.slice(0, EXCERPT_CHARS),\n })\n }\n return {\n agents: [...new Set(sources.map((s) => s.agentId))].sort(),\n occurrences: Math.max(1, ...sources.map((s) => s.occurrences ?? 1)),\n sources,\n }\n}\n\n// --- Review: approve or reject a proposal ------------------------------------\n\nexport type ProposalDecisionResult =\n | { ok: true; proposal: PromotionProposal }\n | { ok: false; error: string }\n\n/** Write the proposed article into the shared wiki. Refuses when the\n * article changed (or appeared) since the proposal was made, unless\n * `force`, so an approval can't silently overwrite someone's edit. */\nexport function approveProposal(\n wikiDir: string, id: string, opts: { by?: string; force?: boolean; now?: number; log?: (m: string) => void } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n\n const log = opts.log ?? (() => {})\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const { article } = proposal\n const existing = store.readArticle(article.path)\n if (!opts.force) {\n if (existing && !proposal.replaces) {\n return { ok: false, error: `\"${article.path}\" was created after this proposal; review it, then approve with --force` }\n }\n if (existing && proposal.replaces && contentHash(existing.content) !== proposal.replaces.contentHash) {\n return { ok: false, error: `\"${article.path}\" changed since this proposal; review it, then approve with --force` }\n }\n }\n const now = opts.now ?? Date.now()\n const today = new Date(now).toISOString().slice(0, 10)\n const ok = store.writeArticle(\n article.path,\n {\n title: article.title,\n type: isWikiArticleType(article.type) ? article.type : undefined,\n related: article.related,\n tags: article.tags,\n owner: PROMOTER_OWNER,\n access: \"public\",\n created: existing?.meta.created ?? today,\n lastUpdated: today,\n sources: mergeSources(existing?.meta.sources, article.promotedFrom),\n },\n article.content,\n PROMOTER_OWNER,\n )\n if (!ok) return { ok: false, error: `write denied for \"${article.path}\" (owned by ${existing?.meta.owner ?? \"?\"})` }\n\n const at = new Date(now).toISOString()\n appendPromotionLedger(wikiDir, article.promotedFrom.map((stamp) => ({\n stamp, decision: \"promoted\" as const, article: article.path, proposal: id, at,\n })))\n store.rebuildIndex()\n const decided: PromotionProposal = { ...proposal, status: \"approved\", decidedBy: opts.by ?? \"operator\", decidedAt: at }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n\n/** Decline a proposal. Its memories aren't judged again until they change. */\nexport function rejectProposal(\n wikiDir: string, id: string, opts: { by?: string; reason?: string; now?: number } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n const at = new Date(opts.now ?? Date.now()).toISOString()\n appendPromotionLedger(wikiDir, proposal.article.promotedFrom.map((stamp) => ({\n stamp, decision: \"rejected\" as const, article: proposal.article.path, proposal: id, reason: opts.reason, at,\n })))\n const decided: PromotionProposal = {\n ...proposal, status: \"rejected\", decidedBy: opts.by ?? \"operator\", decidedAt: at,\n ...(opts.reason ? { reason: opts.reason } : {}),\n }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n","import { fieldChecklistMarkdown, tierLadderMarkdown } from \"@/decisions/seats/article-fields\"\nimport type { WikiMode } from \"./hub\"\nimport type { PromotionCluster } from \"./promote\"\n\n/**\n * Farzapedia-faithful absorb prompt.\n *\n * Replaces the legacy three-mode design (flat/graph/unified) with one prompt\n * that mirrors what Karpathy and Farza describe: article `type` is the\n * organizational spine, `[[wikilinks]]` are the navigation surface, tags are\n * a secondary hint. The LLM chooses paths and cross-references; it does not\n * produce a sprawling per-article tag cloud.\n *\n * The mode parameter is kept in the signature for backwards compatibility but\n * all values resolve to the same prompt — retained callers (hub.ts, wiki CLI)\n * still pass a mode but it no longer changes behavior.\n */\nexport function buildAbsorbPrompt(\n _mode: WikiMode,\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n): string {\n return buildFarzapediaPrompt(agentId, worldview, existingArticles, entryTexts, entryCount, factsBlock)\n}\n\nfunction buildFarzapediaPrompt(\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n // Group the existing index by type so the LLM sees the structure.\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const existingList = existingArticles.length === 0 ? \"\" :\n \"\\n## Existing Articles (the current catalog)\\n\\n\" +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n return `You are compiling a personal wiki for the \"${agentId}\" agent in the Karpathy / Farzapedia pattern.\n\n## The Pattern (read this first — it shapes every decision below)\n\nThe wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \\`type\\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.\n\n**Three rules, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` frontmatter field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint, not a retrieval spine.** 2–4 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \\`event\\`. Do NOT tag with generic terms (\"deploy\", \"work\") that will match half the corpus.\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## How to process entries\n\nFor each of the ${entryCount} raw entries below, ask in this order:\n\n1. **Does it extend an existing article?** If a person/project/concept mentioned in the entry already has an article in the catalog, produce an UPDATE with the full merged content. Prefer merging over proliferating.\n2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.\n3. **Does it belong in an existing \\`event\\` or \\`decision\\`?** Most work entries fold into one of these.\n4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere — skip. The wiki is curated, not exhaustive.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, and their actual contact details — not a log of every interaction. History goes last, and only where it still bears on the present.\n- **Length: 20–100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.\n- **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.\n\n## Build in layers\n\nFacts are not equally load-bearing. Write them in this order, and never\nspend length on a lower layer while a higher one is missing:\n\n${tierLadderMarkdown()}\n\nAn article is a building, not a transcript. The common failure is to\nfurnish a slab: a person's page that records an invoice thread in detail\nand never states their role, their organisation, or how to reach them.\nThat article cannot answer \"who is this\" or \"how do I reach them\" — the\ntwo questions it exists for. History is the last layer, and it is the\nonly one you can safely leave thin.\n\n## Identity before history\n\nAn entity article must first establish **what the entity is**, and only then\nwhat happened to it.\n\nBefore the narrative, every entity article carries an **Identity** section\nwith the fields for its type. State a field even when the entries do not\nanswer it, as \\`unknown\\`, so the gap is visible and a later pass can fill it.\nA field silently omitted is indistinguishable from a field that does not\napply.\n\n${fieldChecklistMarkdown()}\n\nFields in **bold** are the ones an article of that type is unusable without.\nEvery article of any type also states what it is, why it matters to our work,\nand which named people, projects or systems it connects to.\n\nExtract identifiers **verbatim** from the entries. A phone number, an\naccount number, a URL or a path is the single most useful thing an article\ncan carry, and it is exactly what summarising discards. Never paraphrase or\nredact one that appears in the source; if a value appears only in part, say\nso rather than dropping it.\n\n## Wikilink discipline\n\n- Use \\`[[Article Title]]\\` **inside the body** every time another tracked entity is referenced.\n- List those same targets in the \\`related\\` frontmatter field.\n- If you reference something that has no article yet (\\`[[New Thing]]\\`), add \"New Thing\" to the output \\`gaps\\` array so the next pass can create it.\n\n## Article frontmatter (exact shape — do not invent fields)\n\n\\`\\`\\`yaml\n---\ntitle: \"Article Title\"\ntype: person | project | place | concept | event | decision | pattern\nrelated: [\"Other Article\", \"Another Article\"]\ntags: [\"2-4-specific-tags\", \"no-dates-unless-event\"]\nowner: ${agentId}\naccess: public | shared | private\ncreated: YYYY-MM-DD\nlast_updated: YYYY-MM-DD\nsources: [\"entry-id-1\", \"entry-id-2\"]\n---\n\\`\\`\\`\n\nAccess guidance: default \\`public\\`; \\`private\\` only for sensitive credentials or agent-specific learnings; \\`shared\\` with specific agent IDs when the article matters only to a subset.\n${worldviewSection}${existingList}${factsBlock}\n## Gap Detection\n\nAfter compiling, populate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific:\n\n- Good: \"Karim Rahmouni — coach for Tunis Padel club, mentioned twice but no \\`people/\\` article\"\n- Bad: \"We need more content about deployments\"\n\n## Output — valid JSON only, no markdown fencing\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"projects/globex-system-v2.md\",\n \"title\": \"Globex System V2\",\n \"type\": \"project\",\n \"related\": [\"Alex Rivera\", \"Laravel\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"laravel\", \"react\"],\n \"content\": \"Globex System V2 is [[Alex Rivera]]'s production Laravel + React app for …\",\n \"sources\": [\"entry-id-1\", \"entry-id-2\"]\n }\n ],\n \"gaps\": [\n \"Karim Rahmouni — Tunis Padel coach, mentioned in entries but no people/ article\"\n ]\n}\n\\`\\`\\`\n\nENTRIES (${entryCount}):\n\n${entryTexts}`\n}\n\n/** Max chars of a memory body shown to the promotion judge. */\nexport const PROMOTE_BODY_LIMIT = 2000\n\n/** Past this catalog size, list titles only (no paths) to bound the prompt. */\nconst PROMOTE_CATALOG_LIMIT = 300\n\n/**\n * Memory→wiki promotion prompt. Mirrors the Farzapedia absorb prompt's\n * skeleton (type spine, path convention, wikilink discipline, gaps) but\n * judges per-agent MEMORIES instead of raw entries: promote only durable,\n * cross-agent-relevant facts into the shared wiki.\n *\n * The LLM does NOT emit frontmatter — the caller sets owner/access/dates/\n * sources itself, which removes a whole class of hallucination. Candidates\n * are referenced only by their exact stamp strings.\n */\nexport function buildMemoryPromotePrompt(\n ownerId: string,\n clusters: PromotionCluster[],\n existingArticles: Array<{ title: string; path: string; type?: string }>,\n worldview: string,\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const titlesOnly = existingArticles.length > PROMOTE_CATALOG_LIMIT\n const existingList = existingArticles.length === 0 ? \"\" :\n `\\n## Existing Articles (the current catalog${titlesOnly ? `, ${existingArticles.length} articles — titles only` : \"\"})\\n\\n` +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => titlesOnly ? `- [[${a.title}]]` : `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n const candidateCount = clusters.reduce((n, c) => n + c.candidates.length, 0)\n const candidateBlocks = clusters.map(cluster => {\n const corroboration = cluster.corroboratingAgents.length > 1\n ? `corroborated by: ${cluster.corroboratingAgents.join(\", \")} (${cluster.corroboratingAgents.length} agents)`\n : `single agent: ${cluster.corroboratingAgents[0]}`\n return cluster.candidates.map(c => {\n const body = c.memory.body.length > PROMOTE_BODY_LIMIT\n ? c.memory.body.slice(0, PROMOTE_BODY_LIMIT) + \"\\n[… truncated]\"\n : c.memory.body\n return [\n `--- CANDIDATE ${c.stamp} ---`,\n `type: ${c.memory.type} | agent: ${c.agentId} | ${corroboration} | confidence: ${cluster.confidence.toFixed(2)}`,\n `description: ${c.memory.description}`,\n body,\n `--- END CANDIDATE ---`,\n ].join(\"\\n\")\n }).join(\"\\n\\n\")\n }).join(\"\\n\\n\")\n\n return `You are promoting per-agent experiential memories into the shared, authoritative AgentX wiki (owner: \"${ownerId}\").\n\n## The Job\n\nMemory is \"what one agent learned\"; the wiki is \"what every agent should know.\" For each candidate memory below, decide: promote it into a wiki article, or skip it with a reason.\n\n**The promotion test — apply in order, skip on the first \"no\":**\n\n1. **Durable?** Will this still be true in 3 months? Transient state, one-off task context, and dated status pings fail here.\n2. **Cross-agent?** Would a DIFFERENT agent benefit from knowing this? Facts about shared infrastructure, projects, people, and conventions pass; one agent's private workflow habits fail.\n3. **A fact, not a preference?** Personal style preferences private to one agent's operator relationship fail.\n4. **Never a secret.** Credentials, tokens, keys, and passwords are NEVER promoted — skip with reason \"contains secret\". Pointers to where a secret lives (e.g. \"SSH config is in X\") are fine; the secret itself is not.\n\nCorroboration across agents (shown per candidate) is a confidence signal, not a requirement — a single-agent memory that passes the test above should be promoted.\n\nCandidates stamped \\`failure:\\` are not memories: they are the same failure recurring across several sessions, with example runs. The recurrence is what makes them durable. Promote one only when the examples share a cause, as a \\`pattern\\` article saying what fails, why, and what to do instead (including a tool or configuration change, if that is the fix). Never copy task text or error output verbatim beyond what the lesson needs.\n\n**Three rules for the articles you write, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, skip the memory instead.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` output field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint.** 2–4 specific tags max. No dates unless the article is an \\`event\\`. No generic terms (\"deploy\", \"work\").\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## Merge over create\n\nIf a candidate extends an article in the catalog below, emit an UPDATE at the EXISTING path with the full merged content. Prefer merging over proliferating. Several candidates about the same entity should fold into ONE article.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** Write about the entity's role in our work, not a diary of how the memory was learned.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Length: 20–100 lines.**\n- **Every paragraph earns its place.**\n\n## Gap Detection\n\nPopulate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific (\"Karim Rahmouni — coach mentioned twice, no people/ article\"), not vague (\"need more deploy content\").\n${worldviewSection}${existingList}\n## Output — valid JSON only, no markdown fencing\n\nReference candidates ONLY by their exact stamp strings (the \\`memory:...\\` line in each CANDIDATE header). Every candidate must appear in exactly one of \\`articles[].promotedFrom\\` or \\`skipped\\`. Do NOT emit frontmatter, owner, access, dates, or sources — the caller sets those.\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"concepts/globex-staging-deploy.md\",\n \"title\": \"Globex Staging Deploy\",\n \"type\": \"concept\",\n \"related\": [\"Globex System V2\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"deploy\"],\n \"content\": \"Deploys to [[Globex System V2]] staging go through …\",\n \"promotedFrom\": [\"memory:coder-agent/project_globex_deploy@2026-07-01T22:14:03.000Z\"]\n }\n ],\n \"skipped\": [\n { \"memory\": \"memory:cx/feedback_short_replies@2026-06-01T00:00:00.000Z\", \"reason\": \"agent-specific style preference, not a cross-agent fact\" }\n ],\n \"gaps\": []\n}\n\\`\\`\\`\n\nCANDIDATES (${candidateCount} memories in ${clusters.length} clusters):\n\n${candidateBlocks}`\n}\n"],"mappings":"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","names":["createHash","existsSync","readdirSync","readFileSync","writeFileSync","resolve","buildAbsorbPrompt","_mode","agentId","worldview","existingArticles","entryTexts","entryCount","factsBlock","buildFarzapediaPrompt","worldviewSection","byType","a","t","list","existingList","type","arr","x","y","tierLadderMarkdown","fieldChecklistMarkdown","PROMOTE_BODY_LIMIT","PROMOTE_CATALOG_LIMIT","buildMemoryPromotePrompt","ownerId","clusters","titlesOnly","candidateCount","n","c","candidateBlocks","cluster","corroboration","body","PROMOTER_OWNER","DEFAULT_PROMOTE_TYPES","PROMOTION_LEDGER_FILE","STAMP_PREFIX","FAILURE_PREFIX","failureIdentity","stamp","at","MEMORY_TYPES","memoryKey","agentId","m","memoryStamp","parseMemoryStamp","s","rest","slash","typeName","updatedAt","underscore","type","name","mergeSources","existing","newStamps","newKeys","parsed","out","listAllAgentMemories","memoryRoot","resolve","store","AgentMemory","existsSync","entry","readdirSync","memory","LEDGER_DECISIONS","readPromotionLedger","wikiBaseDir","path","readFileSync","e","appendPromotionLedger","entries","merged","byKey","key","prev","writeFileSync","groupCandidates","cands","byName","c","k","list","candidates","corroboratingAgents","typeBoost","confidence","parsePromotionResponse","text","offeredStamps","start","depth","end","inString","escaped","i","ch","warnings","articles","a","raw","promotedFrom","r","t","skipped","gaps","g","getUnpromotedMemories","all","index","ledger","opts","types","now","max","seen","handledStamps","record","article","pendingFailures","occurrences","sessions","tasks","failure","updated","handled","b","ANTHROPIC_API","ANTHROPIC_VERSION","callPromotionLlm","prompt","feedback","fetchImpl","timeoutMs","ctrl","timer","daemonUrl","message","res","errBody","body","content","apiKey","messages","runPromotion","wikiDir","log","report","WikiStore","args","articleList","worldview","budget","DEFAULT_BUDGET_TOKENS","clusters","buildMemoryPromotePrompt","estimateTokens","judged","offered","chatId","ledgerEntries","byStamp","evidence","evidenceFor","proposal","newProposalId","contentHash","saveProposal","createHash","EXCERPT_CHARS","sourceKind","stamps","sources","approveProposal","id","readProposal","today","isWikiArticleType","decided","rejectProposal"]}
|
|
1
|
+
{"version":3,"sources":["../src/wiki/promote.ts","../src/wiki/prompts.ts"],"sourcesContent":["import { createHash } from \"crypto\"\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { AgentMemory, type MemoryRecord, type MemoryType } from \"../agents/agent-memory\"\nimport { WikiStore } from \"./store\"\nimport { buildMemoryPromotePrompt } from \"./prompts\"\nimport { isWikiArticleType, type WikiIndex } from \"./types\"\nimport { newProposalId, readProposal, saveProposal, type PromotionProposal, type ProposalSource } from \"./proposals\"\n\n// --- Memory → wiki promotion ---\n//\n// Finishes the architecture documented in agent-memory.ts: memory is\n// experiential / per-agent; the wiki is authoritative / cross-agent. This\n// module reads per-agent memories, decides (via an LLM judge) which are\n// durable and fleet-relevant, and PROPOSES them as [[wikilinked]] articles\n// for the SHARED wiki store. Nothing reaches the wiki until an operator\n// approves the proposal (proposals.ts): a wrong or planted lesson would\n// otherwise reach every agent overnight.\n//\n// Idempotency has two layers, both keyed on the versioned stamp\n// `memory:<agentId>/<type>_<name>@<updatedAt>`:\n//\n// 1. Promoted memories: the stamp is recorded in the target article's\n// `sources[]` frontmatter (same pattern as getUnabsorbedEntries).\n// 2. Skipped memories: recorded in `.agentx/wiki/_memory-promotions.json`\n// so permanently-irrelevant memories aren't re-judged every night.\n//\n// A bumped `updatedAt` beats both — an edited memory re-enters the\n// pipeline, and mergeSources() replaces the stale stamp for its key so\n// frontmatter stays bounded (one stamp per memory, not per edit).\n\nexport const PROMOTER_OWNER = \"memory-promoter\"\nexport const DEFAULT_PROMOTE_TYPES: MemoryType[] = [\"project\", \"reference\", \"feedback\"]\nexport const PROMOTION_LEDGER_FILE = \"_memory-promotions.json\"\n\nconst STAMP_PREFIX = \"memory:\"\nconst FAILURE_PREFIX = \"failure:\"\n\n/** `failure:<signature digest>@<recurrence bucket>` without the bucket. */\nfunction failureIdentity(stamp: string): string {\n const at = stamp.lastIndexOf(\"@\")\n return at === -1 ? stamp : stamp.slice(0, at)\n}\nconst MEMORY_TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface MemoryCandidate {\n agentId: string\n memory: MemoryRecord\n /** Identity without version: `<agentId>/<type>_<name>` */\n key: string\n /** Identity + version: `memory:<key>@<updatedAt>`. Other sources use\n * their own namespace (`review:`, `failure:`). */\n stamp: string\n /** Distinct sessions a review finding or failure recurred in. More\n * occurrences rank first. */\n occurrences?: number\n /** Some of those sessions, kept as proposal evidence. */\n sessions?: string[]\n /** Some of the runs (task ids) behind a failure, kept as evidence. */\n tasks?: string[]\n /** What a recurring failure has in common: its signature and run count. */\n failure?: { tool: string; errorClass: string; runs: number }\n}\n\nexport interface PromotionLedgerEntry {\n stamp: string\n /** proposed: waiting for an operator; rejected: an operator said no. Both\n * keep the memory from being judged again until it changes. */\n decision: \"promoted\" | \"skipped\" | \"proposed\" | \"rejected\"\n /** Article path when promoted or proposed. */\n article?: string\n /** Proposal id when proposed or decided through one. */\n proposal?: string\n /** LLM skip reason. */\n reason?: string\n /** ISO run timestamp. */\n at: string\n}\n\nexport type PromotionLedger = PromotionLedgerEntry[]\n\n// --- Stamps -------------------------------------------------------------\n\nexport function memoryKey(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\">): string {\n return `${agentId}/${m.type}_${m.name}`\n}\n\nexport function memoryStamp(agentId: string, m: Pick<MemoryRecord, \"type\" | \"name\" | \"updatedAt\">): string {\n return `${STAMP_PREFIX}${memoryKey(agentId, m)}@${m.updatedAt}`\n}\n\n/** Parse a `memory:<agentId>/<type>_<name>@<updatedAt>` source stamp.\n * Returns null for anything else (raw entry ids, malformed stamps) so\n * callers can filter article `sources[]` safely. */\nexport function parseMemoryStamp(s: string): {\n agentId: string\n type: MemoryType\n name: string\n updatedAt: string\n key: string\n} | null {\n if (!s.startsWith(STAMP_PREFIX)) return null\n const rest = s.slice(STAMP_PREFIX.length)\n const slash = rest.indexOf(\"/\")\n const at = rest.lastIndexOf(\"@\")\n if (slash <= 0 || at <= slash) return null\n const agentId = rest.slice(0, slash)\n const typeName = rest.slice(slash + 1, at)\n const updatedAt = rest.slice(at + 1)\n if (!updatedAt) return null\n const underscore = typeName.indexOf(\"_\")\n if (underscore <= 0) return null\n const type = typeName.slice(0, underscore) as MemoryType\n const name = typeName.slice(underscore + 1)\n if (!MEMORY_TYPES.includes(type) || !name) return null\n return { agentId, type, name, updatedAt, key: `${agentId}/${typeName}` }\n}\n\n/** Merge new promotion stamps into an article's existing sources: a new\n * stamp REPLACES any prior stamp sharing its key (one stamp per memory,\n * not one per edit); everything else (raw entry ids, other memories'\n * stamps) is kept. Deduped, order-stable. */\nexport function mergeSources(existing: string[] | undefined, newStamps: string[]): string[] {\n const newKeys = new Set<string>()\n for (const s of newStamps) {\n const parsed = parseMemoryStamp(s)\n if (parsed) newKeys.add(parsed.key)\n }\n const out: string[] = []\n for (const s of existing ?? []) {\n const parsed = parseMemoryStamp(s)\n if (parsed && newKeys.has(parsed.key)) continue // superseded\n if (!out.includes(s)) out.push(s)\n }\n for (const s of newStamps) {\n if (!out.includes(s)) out.push(s)\n }\n return out\n}\n\n// --- Cross-agent memory enumeration --------------------------------------\n\n/** List every agent's memories. AgentMemory has no cross-agent API — we\n * enumerate the agent subdirs of `.agentx/agent-memory/` ourselves.\n * `memoryRoot` is the `.agentx` directory (AgentMemory's baseDir root). */\nexport function listAllAgentMemories(\n memoryRoot: string = resolve(process.cwd(), \".agentx\"),\n): Array<{ agentId: string; memory: MemoryRecord }> {\n const store = new AgentMemory({ baseDir: memoryRoot })\n if (!existsSync(store.baseDir)) return []\n const out: Array<{ agentId: string; memory: MemoryRecord }> = []\n for (const entry of readdirSync(store.baseDir, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith(\"_\") || entry.name.startsWith(\".\")) continue\n for (const memory of store.list(entry.name)) {\n out.push({ agentId: entry.name, memory })\n }\n }\n return out\n}\n\n// --- Skip ledger ----------------------------------------------------------\n\nconst LEDGER_DECISIONS = new Set([\"promoted\", \"skipped\", \"proposed\", \"rejected\"])\n\nexport function readPromotionLedger(wikiBaseDir: string): PromotionLedger {\n const path = resolve(wikiBaseDir, PROMOTION_LEDGER_FILE)\n if (!existsSync(path)) return []\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\"))\n if (!Array.isArray(parsed)) return []\n return parsed.filter(\n (e): e is PromotionLedgerEntry =>\n e && typeof e.stamp === \"string\" && LEDGER_DECISIONS.has(e.decision),\n )\n } catch {\n // Corrupt ledger → treat as empty. Worst case: skipped memories are\n // re-judged once. Promoted memories stay deduped via article sources.\n return []\n }\n}\n\n/** Append entries and compact: keep only the newest entry per memory key\n * (per exact stamp for `review:` and `failure:` sources, where the later\n * entry wins) so the ledger stays bounded. */\nexport function appendPromotionLedger(wikiBaseDir: string, entries: PromotionLedgerEntry[]): void {\n const merged = [...readPromotionLedger(wikiBaseDir), ...entries]\n const byKey = new Map<string, PromotionLedgerEntry>()\n for (const e of merged) {\n const parsed = parseMemoryStamp(e.stamp)\n const key = parsed?.key ?? e.stamp\n const prev = byKey.get(key)\n // A non-memory stamp has no version to compare: the later decision\n // (rejected after proposed) must replace the earlier one.\n if (!prev || !parsed || (parseMemoryStamp(prev.stamp)?.updatedAt ?? \"\") <= parsed.updatedAt) {\n byKey.set(key, e)\n }\n }\n writeFileSync(\n resolve(wikiBaseDir, PROMOTION_LEDGER_FILE),\n JSON.stringify([...byKey.values()], null, 2),\n )\n}\n\n// --- Clustering -------------------------------------------------------------\n\nexport interface PromotionCluster {\n /** ≥1 candidate; >1 when the same `<type>_<name>` exists on several agents. */\n candidates: MemoryCandidate[]\n corroboratingAgents: string[]\n /** 0.5 base + 0.15 per extra corroborating agent + 0.05 for\n * project|reference types, capped at 0.95. Advisory only — shown to\n * the LLM judge, never a hard gate (mirrors clusterWorkflowCandidates). */\n confidence: number\n}\n\n/** Group candidates that share `<type>_<name>` across agents. Exact-match\n * clustering only — two agents naming a memory identically is treated as\n * corroboration; fuzzy matching is future work. */\nexport function groupCandidates(cands: MemoryCandidate[]): PromotionCluster[] {\n const byName = new Map<string, MemoryCandidate[]>()\n for (const c of cands) {\n const k = `${c.memory.type}_${c.memory.name}`\n const list = byName.get(k) ?? []\n list.push(c)\n byName.set(k, list)\n }\n return [...byName.values()].map((candidates) => {\n const corroboratingAgents = [...new Set(candidates.map((c) => c.agentId))].sort()\n const typeBoost = [\"project\", \"reference\"].includes(candidates[0].memory.type) ? 0.05 : 0\n const confidence = Math.min(0.95, 0.5 + 0.15 * (corroboratingAgents.length - 1) + typeBoost)\n return { candidates, corroboratingAgents, confidence }\n })\n}\n\n// --- LLM response parsing ----------------------------------------------------\n\nexport interface PromotedArticle {\n path: string\n title: string\n type?: string\n related?: string[]\n tags: string[]\n content: string\n /** Validated stamps of the memories this article was promoted from. */\n promotedFrom: string[]\n}\n\nexport interface PromotionResponse {\n articles: PromotedArticle[]\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n /** Stamp refs the LLM emitted that weren't in the offered set, plus\n * articles dropped for having zero valid stamps. For the report. */\n warnings: string[]\n}\n\n/** Parse the promotion LLM's reply. Balanced-JSON extraction (adapted from\n * the wiki absorb CLI): find the outermost `{...}`, depth-scan to its\n * close, JSON.parse, then validate. Stamps not present in `offeredStamps`\n * are hallucinations — dropped; an article left with zero valid stamps is\n * dropped entirely (nothing to dedupe against on the next run). Any\n * failure returns `{error}` — the caller must write nothing. */\nexport function parsePromotionResponse(\n text: string,\n offeredStamps: Set<string>,\n): PromotionResponse | { error: string } {\n const start = text.indexOf(\"{\")\n if (start === -1) return { error: \"no JSON object in response\" }\n let depth = 0\n let end = -1\n let inString = false\n let escaped = false\n for (let i = start; i < text.length; i++) {\n const ch = text[i]\n if (escaped) { escaped = false; continue }\n if (ch === \"\\\\\") { escaped = true; continue }\n if (ch === '\"') { inString = !inString; continue }\n if (inString) continue\n if (ch === \"{\") depth++\n else if (ch === \"}\") { depth--; if (depth === 0) { end = i + 1; break } }\n }\n if (end === -1) return { error: \"unbalanced JSON in response\" }\n\n let parsed: any\n try {\n parsed = JSON.parse(text.slice(start, end))\n } catch (e: any) {\n return { error: `JSON parse error: ${e.message}` }\n }\n if (!parsed || typeof parsed !== \"object\" || !Array.isArray(parsed.articles)) {\n return { error: \"response JSON missing articles array\" }\n }\n\n const warnings: string[] = []\n const articles: PromotedArticle[] = []\n for (const a of parsed.articles) {\n if (!a || typeof a.path !== \"string\" || typeof a.title !== \"string\" || typeof a.content !== \"string\") {\n warnings.push(`article missing path/title/content — dropped`)\n continue\n }\n const raw = Array.isArray(a.promotedFrom) ? a.promotedFrom.filter((s: unknown) => typeof s === \"string\") : []\n const promotedFrom = raw.filter((s: string) => offeredStamps.has(s))\n for (const s of raw) {\n if (!offeredStamps.has(s)) warnings.push(`article \"${a.path}\" references unknown stamp \"${s}\" — dropped`)\n }\n if (promotedFrom.length === 0) {\n warnings.push(`article \"${a.path}\" has no valid promotedFrom stamps — dropped`)\n continue\n }\n articles.push({\n path: a.path,\n title: a.title,\n type: typeof a.type === \"string\" ? a.type : undefined,\n related: Array.isArray(a.related) ? a.related.filter((r: unknown) => typeof r === \"string\") : undefined,\n tags: Array.isArray(a.tags) ? a.tags.filter((t: unknown) => typeof t === \"string\") : [],\n content: a.content,\n promotedFrom,\n })\n }\n\n const skipped: Array<{ stamp: string; reason: string }> = []\n for (const s of Array.isArray(parsed.skipped) ? parsed.skipped : []) {\n const stamp = typeof s?.memory === \"string\" ? s.memory : typeof s?.stamp === \"string\" ? s.stamp : \"\"\n if (!offeredStamps.has(stamp)) {\n if (stamp) warnings.push(`skip references unknown stamp \"${stamp}\" — dropped`)\n continue\n }\n skipped.push({ stamp, reason: typeof s.reason === \"string\" ? s.reason : \"\" })\n }\n\n const gaps = Array.isArray(parsed.gaps) ? parsed.gaps.filter((g: unknown) => typeof g === \"string\") : []\n return { articles, skipped, gaps, warnings }\n}\n\n// --- Candidate selection ---------------------------------------------------\n\nexport interface UnpromotedOptions {\n /** Memory types to consider. Default: project, reference, feedback. */\n types?: MemoryType[]\n /** Only memories updated within this window (ms before `now`). */\n sinceMs?: number\n /** Only this agent's memories. */\n agentFilter?: string\n /** Reference clock for `sinceMs` (injectable for tests). Default Date.now(). */\n now?: number\n /** Cap on returned candidates (newest first). Default 20. */\n max?: number\n}\n\n/** Diff all agent memories against what's already been promoted (article\n * `sources[]` stamps across the shared index) or deliberately skipped\n * (ledger). A memory is a candidate iff its key is unseen OR its\n * `updatedAt` is newer than the recorded one — ISO-8601 strings compare\n * lexicographically, so plain `>` is safe. */\nexport function getUnpromotedMemories(\n all: Array<\n // `stamp` is supplied by non-memory sources so their namespace\n // survives; the evidence fields travel through to the proposal.\n Pick<MemoryCandidate, \"agentId\" | \"memory\"> & Partial<Omit<MemoryCandidate, \"agentId\" | \"memory\" | \"key\">>\n >,\n index: WikiIndex,\n ledger: PromotionLedger,\n opts: UnpromotedOptions = {},\n): MemoryCandidate[] {\n const types = opts.types ?? DEFAULT_PROMOTE_TYPES\n const now = opts.now ?? Date.now()\n const max = opts.max ?? 20\n\n // key → newest updatedAt already handled (promoted or skipped)\n const seen = new Map<string, string>()\n // Exact stamps already handled. Non-memory sources (`review:`,\n // `failure:`) have no parseable key/version, so this is how they are\n // recognised as proposed, promoted, skipped or rejected instead of\n // being offered again every night.\n const handledStamps = new Set<string>()\n const record = (stamp: string) => {\n handledStamps.add(stamp)\n const parsed = parseMemoryStamp(stamp)\n if (!parsed) return\n const prev = seen.get(parsed.key)\n if (!prev || parsed.updatedAt > prev) seen.set(parsed.key, parsed.updatedAt)\n }\n for (const article of index.articles) {\n for (const s of article.sources ?? []) record(s)\n }\n for (const e of ledger) record(e.stamp)\n // A failure's stamp moves on as it recurs more (failure-candidates.ts);\n // while one proposal for it waits for review, don't open a second.\n const pendingFailures = new Set(ledger\n .filter((e) => e.decision === \"proposed\" && e.stamp.startsWith(FAILURE_PREFIX))\n .map((e) => failureIdentity(e.stamp)))\n\n const candidates: MemoryCandidate[] = []\n for (const { agentId, memory, stamp, occurrences, sessions, tasks, failure } of all) {\n if (!types.includes(memory.type)) continue\n if (opts.agentFilter && agentId !== opts.agentFilter) continue\n if (opts.sinceMs !== undefined) {\n const updated = Date.parse(memory.updatedAt)\n if (!Number.isFinite(updated) || updated < now - opts.sinceMs) continue\n }\n if (stamp && handledStamps.has(stamp)) continue\n if (stamp?.startsWith(FAILURE_PREFIX) && pendingFailures.has(failureIdentity(stamp))) continue\n const key = memoryKey(agentId, memory)\n const handled = seen.get(key)\n if (handled && memory.updatedAt <= handled) continue\n // A caller-supplied stamp is kept as-is. Re-deriving it would\n // rewrite a `review:` stamp into the `memory:` namespace and lose\n // the provenance the ledger needs to tell the two sources apart.\n candidates.push({\n agentId, memory, key, stamp: stamp ?? memoryStamp(agentId, memory), occurrences,\n ...(sessions?.length ? { sessions } : {}),\n ...(tasks?.length ? { tasks } : {}),\n ...(failure ? { failure } : {}),\n })\n }\n\n // Recurrence first where a source provides it, then recency. Memory\n // candidates carry no count, so their ordering is unchanged.\n return candidates\n .sort((a, b) =>\n (b.occurrences ?? 0) - (a.occurrences ?? 0) ||\n b.memory.updatedAt.localeCompare(a.memory.updatedAt))\n .slice(0, max)\n}\n\n// --- LLM transport -----------------------------------------------------------\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\"\nconst ANTHROPIC_VERSION = \"2023-06-01\"\n\nexport interface PromotionLlmOptions {\n /** Route through the daemon: POST /task to this agent. No API key needed —\n * the agent's own session handles auth (mirrors architectWorkflowViaAgent). */\n viaAgent?: string\n /** Direct Anthropic API model. Requires ANTHROPIC_API_KEY. */\n model?: string\n daemonUrl?: string\n timeoutMs?: number\n fetchImpl?: typeof fetch\n /** Stable chat id so a retry's --resume replay sees the first reply. */\n chatId?: string\n}\n\n/** Run the promotion prompt through an LLM and return the raw reply text.\n * Throws on transport failure — the caller decides whether to retry.\n * `feedback` turns the call into a correction round: the via-agent branch\n * resumes the same session; the direct branch replays the conversation. */\nexport async function callPromotionLlm(\n prompt: string,\n opts: PromotionLlmOptions,\n feedback: string | null = null,\n): Promise<string> {\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 180_000\n const ctrl = new AbortController()\n const timer = setTimeout(() => ctrl.abort(), timeoutMs)\n try {\n if (opts.viaAgent) {\n const daemonUrl = (opts.daemonUrl ?? \"http://127.0.0.1:18800\").replace(/\\/+$/, \"\")\n const message = feedback\n ? `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object — same structure, fixed issues, valid JSON only.`\n : prompt\n const res = await fetchImpl(`${daemonUrl}/task`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n agent: opts.viaAgent,\n message,\n // First call gets a fresh session; the retry resumes it so the\n // agent can fix its own output instead of starting over.\n freshSession: feedback === null,\n context: { channel: \"api\", chatId: opts.chatId ?? \"memory-promote\" },\n }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`agent /task ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n if (body.error) throw new Error(`agent reported error: ${body.error}`)\n const content = String(body.content ?? \"\").trim()\n if (!content) throw new Error(\"agent returned empty content\")\n return content\n }\n\n if (!opts.model) throw new Error(\"callPromotionLlm: viaAgent or model is required\")\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) throw new Error(\"ANTHROPIC_API_KEY not set; use --via <agentId> instead\")\n const messages: Array<{ role: string; content: string }> = [{ role: \"user\", content: prompt }]\n if (feedback) {\n messages.push({ role: \"assistant\", content: \"(previous attempt; will retry with corrections)\" })\n messages.push({ role: \"user\", content: `Your previous response failed validation: ${feedback}\\nRe-emit the FULL corrected JSON object.` })\n }\n const res = await fetchImpl(ANTHROPIC_API, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_VERSION,\n },\n body: JSON.stringify({ model: opts.model, max_tokens: 8000, messages }),\n signal: ctrl.signal,\n })\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`anthropic API ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n const blocks = Array.isArray(body.content) ? body.content : []\n const text = blocks.filter((b: any) => b?.type === \"text\").map((b: any) => b.text).join(\"\\n\").trim()\n if (!text) throw new Error(\"anthropic API returned no text content\")\n return text\n } finally {\n clearTimeout(timer)\n }\n}\n\n// --- Orchestrator ------------------------------------------------------------\n\nexport interface RunPromotionOptions extends PromotionLlmOptions {\n /** Wiki base dir (the shared store). Default `.agentx/wiki`. */\n wikiDir?: string\n /** The `.agentx` dir holding `agent-memory/`. Default `.agentx`. */\n memoryRoot?: string\n sinceMs?: number\n agentFilter?: string\n types?: MemoryType[]\n max?: number\n /** Candidates from somewhere other than agent memory — session-monitor\n * reviews and recurring failures. They join the same pool so they inherit\n * the ledger, the dedupe and the judge rather than getting a second\n * pipeline that drifts from this one. */\n extraCandidates?: MemoryCandidate[]\n /** Write proposals + ledger. Default false (dry-run). */\n commit?: boolean\n /** Upper bound on the judge prompt, in estimated tokens. Clusters are\n * dropped from the end (lowest ranked) until it fits; they stay\n * unledgered, so the next run picks them up. Default 60 000. */\n budgetTokens?: number\n log?: (msg: string) => void\n /** Injectable clock (tests). */\n now?: number\n}\n\nexport interface PromotionReport {\n candidates: MemoryCandidate[]\n clusters: PromotionCluster[]\n /** Proposals written this run; nothing reaches the wiki until approved. */\n proposed: Array<{ id: string; path: string; title: string; type?: string; stamps: string[]; agents: string[]; occurrences: number }>\n skipped: Array<{ stamp: string; reason: string }>\n gaps: string[]\n warnings: string[]\n errors: string[]\n dryRun: boolean\n}\n\n/** End-to-end promotion run: enumerate memories → diff against promoted/\n * skipped → cluster → (dry-run stops here) → LLM judge → write articles\n * with merged sources → ledger → rebuild index. Parses the full response\n * before any write — a bad reply writes nothing, and idempotency makes\n * the next scheduled run retry for free. */\nexport async function runPromotion(opts: RunPromotionOptions = {}): Promise<PromotionReport> {\n const wikiDir = opts.wikiDir ?? resolve(process.cwd(), \".agentx\", \"wiki\")\n const memoryRoot = opts.memoryRoot ?? resolve(process.cwd(), \".agentx\")\n const log = opts.log ?? (() => {})\n const now = opts.now ?? Date.now()\n const report: PromotionReport = {\n candidates: [], clusters: [], proposed: [], skipped: [],\n gaps: [], warnings: [], errors: [], dryRun: !opts.commit,\n }\n\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const index = store.rebuildIndex()\n const ledger = readPromotionLedger(wikiDir)\n const all = [...listAllAgentMemories(memoryRoot), ...(opts.extraCandidates ?? [])]\n\n report.candidates = getUnpromotedMemories(all, index, ledger, {\n types: opts.types,\n sinceMs: opts.sinceMs,\n agentFilter: opts.agentFilter,\n now,\n max: opts.max,\n })\n if (report.candidates.length === 0) {\n log(\"no unpromoted memories in window\")\n return report\n }\n report.clusters = groupCandidates(report.candidates)\n if (report.dryRun) return report\n\n const articleList = index.articles.map((a) => ({ title: a.title, path: a.path, type: a.type }))\n const worldview = store.getWorldview() ?? \"\"\n const budget = opts.budgetTokens ?? DEFAULT_BUDGET_TOKENS\n let clusters = report.clusters\n let prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n while (estimateTokens(prompt) > budget && clusters.length > 1) {\n clusters = clusters.slice(0, -1)\n prompt = buildMemoryPromotePrompt(PROMOTER_OWNER, clusters, articleList, worldview)\n }\n if (estimateTokens(prompt) > budget) {\n report.errors.push(`judge prompt (~${estimateTokens(prompt)} tokens) exceeds the ${budget}-token budget even with one cluster`)\n return report\n }\n if (clusters.length < report.clusters.length) {\n report.warnings.push(`token budget: judged ${clusters.length} of ${report.clusters.length} clusters; the rest wait for the next run`)\n }\n const judged = clusters.flatMap((c) => c.candidates)\n const offered = new Set(judged.map((c) => c.stamp))\n const chatId = opts.chatId ?? `memory-promote-${new Date(now).toISOString().slice(0, 10)}`\n\n let parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }), offered)\n if (\"error\" in parsed) {\n log(`parse failed (${parsed.error}) — retrying with feedback`)\n parsed = parsePromotionResponse(await callPromotionLlm(prompt, { ...opts, chatId }, parsed.error), offered)\n }\n if (\"error\" in parsed) {\n report.errors.push(`LLM response unusable after retry: ${parsed.error}`)\n return report\n }\n report.warnings.push(...parsed.warnings)\n report.gaps = parsed.gaps\n\n const at = new Date(now).toISOString()\n const ledgerEntries: PromotionLedgerEntry[] = []\n\n const byStamp = new Map(judged.map((c) => [c.stamp, c]))\n for (const article of parsed.articles) {\n const existing = store.readArticle(article.path)\n if (existing && !store.canWrite(existing.meta, PROMOTER_OWNER)) {\n // Owned by another agent — a human resolves; stamps stay unledgered\n // so the memory is retried after the standoff clears.\n report.errors.push(`write denied for \"${article.path}\" (owned by ${existing.meta.owner ?? \"?\"})`)\n continue\n }\n const evidence = evidenceFor(article.promotedFrom, byStamp)\n const proposal: PromotionProposal = {\n id: newProposalId(now, article.path),\n createdAt: at,\n status: \"pending\",\n article,\n evidence,\n ...(existing ? { replaces: { lastUpdated: existing.meta.lastUpdated, contentHash: contentHash(existing.content) } } : {}),\n }\n saveProposal(wikiDir, proposal)\n report.proposed.push({\n id: proposal.id, path: article.path, title: article.title, type: article.type,\n stamps: article.promotedFrom, agents: evidence.agents, occurrences: evidence.occurrences,\n })\n for (const stamp of article.promotedFrom) {\n ledgerEntries.push({ stamp, decision: \"proposed\", article: article.path, proposal: proposal.id, at })\n }\n }\n\n for (const s of parsed.skipped) {\n ledgerEntries.push({ stamp: s.stamp, decision: \"skipped\", reason: s.reason, at })\n report.skipped.push(s)\n }\n\n if (ledgerEntries.length) appendPromotionLedger(wikiDir, ledgerEntries)\n return report\n}\n\nconst DEFAULT_BUDGET_TOKENS = 60_000\n\n/** Rough token count (4 characters per token) — enough for a budget. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4)\n}\n\nfunction contentHash(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\nconst EXCERPT_CHARS = 600\n\n/** Which pipeline a stamp came from. */\nexport function sourceKind(stamp: string): ProposalSource[\"kind\"] {\n if (stamp.startsWith(\"review:\")) return \"review\"\n if (stamp.startsWith(FAILURE_PREFIX)) return \"failure\"\n return \"memory\"\n}\n\n/** What a reviewer needs to judge a proposal: each source memory, review\n * finding or recurring failure, who wrote it and in which task, how often\n * it recurred, and some of the sessions and runs it came from. */\nexport function evidenceFor(stamps: string[], byStamp: Map<string, MemoryCandidate>): PromotionProposal[\"evidence\"] {\n const sources: ProposalSource[] = []\n for (const stamp of stamps) {\n const c = byStamp.get(stamp)\n if (!c) continue\n sources.push({\n stamp,\n kind: sourceKind(stamp),\n agentId: c.agentId,\n type: c.memory.type,\n name: c.memory.name,\n description: c.memory.description,\n ...(c.occurrences ? { occurrences: c.occurrences } : {}),\n ...(c.sessions?.length ? { sessions: c.sessions } : {}),\n ...(c.tasks?.length ? { tasks: c.tasks } : {}),\n ...(c.failure ? { failure: c.failure } : {}),\n ...(c.memory.author ? { author: c.memory.author } : {}),\n ...(c.memory.taskId ? { taskId: c.memory.taskId } : {}),\n updatedAt: c.memory.updatedAt,\n excerpt: c.memory.body.slice(0, EXCERPT_CHARS),\n })\n }\n return {\n agents: [...new Set(sources.map((s) => s.agentId))].sort(),\n occurrences: Math.max(1, ...sources.map((s) => s.occurrences ?? 1)),\n sources,\n }\n}\n\n// --- Review: approve or reject a proposal ------------------------------------\n\nexport type ProposalDecisionResult =\n | { ok: true; proposal: PromotionProposal }\n | { ok: false; error: string }\n\n/** Write the proposed article into the shared wiki. Refuses when the\n * article changed (or appeared) since the proposal was made, unless\n * `force`, so an approval can't silently overwrite someone's edit. */\nexport function approveProposal(\n wikiDir: string, id: string, opts: { by?: string; force?: boolean; now?: number; log?: (m: string) => void } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n\n const log = opts.log ?? (() => {})\n const store = new WikiStore(wikiDir, (...args: unknown[]) => log(args.map(String).join(\" \")))\n const { article } = proposal\n const existing = store.readArticle(article.path)\n if (!opts.force) {\n if (existing && !proposal.replaces) {\n return { ok: false, error: `\"${article.path}\" was created after this proposal; review it, then approve with --force` }\n }\n if (existing && proposal.replaces && contentHash(existing.content) !== proposal.replaces.contentHash) {\n return { ok: false, error: `\"${article.path}\" changed since this proposal; review it, then approve with --force` }\n }\n }\n const now = opts.now ?? Date.now()\n const today = new Date(now).toISOString().slice(0, 10)\n const ok = store.writeArticle(\n article.path,\n {\n title: article.title,\n type: isWikiArticleType(article.type) ? article.type : undefined,\n related: article.related,\n tags: article.tags,\n owner: PROMOTER_OWNER,\n access: \"public\",\n created: existing?.meta.created ?? today,\n lastUpdated: today,\n sources: mergeSources(existing?.meta.sources, article.promotedFrom),\n },\n article.content,\n PROMOTER_OWNER,\n )\n if (!ok) return { ok: false, error: `write denied for \"${article.path}\" (owned by ${existing?.meta.owner ?? \"?\"})` }\n\n const at = new Date(now).toISOString()\n appendPromotionLedger(wikiDir, article.promotedFrom.map((stamp) => ({\n stamp, decision: \"promoted\" as const, article: article.path, proposal: id, at,\n })))\n store.rebuildIndex()\n const decided: PromotionProposal = { ...proposal, status: \"approved\", decidedBy: opts.by ?? \"operator\", decidedAt: at }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n\n/** Decline a proposal. Its memories aren't judged again until they change. */\nexport function rejectProposal(\n wikiDir: string, id: string, opts: { by?: string; reason?: string; now?: number } = {},\n): ProposalDecisionResult {\n const proposal = readProposal(wikiDir, id)\n if (!proposal) return { ok: false, error: `no proposal \"${id}\"` }\n if (proposal.status !== \"pending\") return { ok: false, error: `proposal \"${id}\" is already ${proposal.status}` }\n const at = new Date(opts.now ?? Date.now()).toISOString()\n appendPromotionLedger(wikiDir, proposal.article.promotedFrom.map((stamp) => ({\n stamp, decision: \"rejected\" as const, article: proposal.article.path, proposal: id, reason: opts.reason, at,\n })))\n const decided: PromotionProposal = {\n ...proposal, status: \"rejected\", decidedBy: opts.by ?? \"operator\", decidedAt: at,\n ...(opts.reason ? { reason: opts.reason } : {}),\n }\n saveProposal(wikiDir, decided)\n return { ok: true, proposal: decided }\n}\n","import { fieldChecklistMarkdown, tierLadderMarkdown } from \"@/decisions/seats/article-fields\"\nimport type { WikiMode } from \"./hub\"\nimport type { PromotionCluster } from \"./promote\"\n\n/**\n * Farzapedia-faithful absorb prompt.\n *\n * Replaces the legacy three-mode design (flat/graph/unified) with one prompt\n * that mirrors what Karpathy and Farza describe: article `type` is the\n * organizational spine, `[[wikilinks]]` are the navigation surface, tags are\n * a secondary hint. The LLM chooses paths and cross-references; it does not\n * produce a sprawling per-article tag cloud.\n *\n * The mode parameter is kept in the signature for backwards compatibility but\n * all values resolve to the same prompt — retained callers (hub.ts, wiki CLI)\n * still pass a mode but it no longer changes behavior.\n */\nexport function buildAbsorbPrompt(\n _mode: WikiMode,\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n coveringBlock = \"\",\n): string {\n return buildFarzapediaPrompt(agentId, worldview, existingArticles, entryTexts, entryCount, factsBlock, coveringBlock)\n}\n\nfunction buildFarzapediaPrompt(\n agentId: string,\n worldview: string,\n existingArticles: Array<{ title: string; path: string; type?: string; tags?: string[] }>,\n entryTexts: string,\n entryCount: number,\n factsBlock = \"\",\n coveringBlock = \"\",\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n // Group the existing index by type so the LLM sees the structure.\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const existingList = existingArticles.length === 0 ? \"\" :\n \"\\n## Existing Articles (the current catalog)\\n\\n\" +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n return `You are compiling a personal wiki for the \"${agentId}\" agent in the Karpathy / Farzapedia pattern.\n\n## The Pattern (read this first — it shapes every decision below)\n\nThe wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \\`type\\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.\n\n**Three rules, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` frontmatter field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint, not a retrieval spine.** 2–4 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \\`event\\`. Do NOT tag with generic terms (\"deploy\", \"work\") that will match half the corpus.\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## How to process entries\n\nFor each of the ${entryCount} raw entries below, ask in this order:\n\n1. **Does it extend an existing article?** If a person/project/concept/event mentioned in the entry already has an article in the catalog, produce an UPDATE at that article's path with the full merged content. Prefer merging over proliferating. When the article is shown in full below, keep every fact it already has.\n2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.\n3. **Does it belong in an existing \\`event\\` or \\`decision\\`?** Most work entries fold into one of these.\n4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere — skip. The wiki is curated, not exhaustive.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, and their actual contact details — not a log of every interaction. History goes last, and only where it still bears on the present.\n- **Length: 20–100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.\n- **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.\n\n## Build in layers\n\nFacts are not equally load-bearing. Write them in this order, and never\nspend length on a lower layer while a higher one is missing:\n\n${tierLadderMarkdown()}\n\nAn article is a building, not a transcript. The common failure is to\nfurnish a slab: a person's page that records an invoice thread in detail\nand never states their role, their organisation, or how to reach them.\nThat article cannot answer \"who is this\" or \"how do I reach them\" — the\ntwo questions it exists for. History is the last layer, and it is the\nonly one you can safely leave thin.\n\n## Identity before history\n\nAn entity article must first establish **what the entity is**, and only then\nwhat happened to it.\n\nBefore the narrative, every entity article carries an **Identity** section\nwith the fields for its type. State a field even when the entries do not\nanswer it, as \\`unknown\\`, so the gap is visible and a later pass can fill it.\nA field silently omitted is indistinguishable from a field that does not\napply.\n\n${fieldChecklistMarkdown()}\n\nFields in **bold** are the ones an article of that type is unusable without.\nEvery article of any type also states what it is, why it matters to our work,\nand which named people, projects or systems it connects to.\n\nExtract identifiers **verbatim** from the entries. A phone number, an\naccount number, a URL or a path is the single most useful thing an article\ncan carry, and it is exactly what summarising discards. Never paraphrase or\nredact one that appears in the source; if a value appears only in part, say\nso rather than dropping it.\n\n## Wikilink discipline\n\n- Use \\`[[Article Title]]\\` **inside the body** every time another tracked entity is referenced.\n- List those same targets in the \\`related\\` frontmatter field.\n- If you reference something that has no article yet (\\`[[New Thing]]\\`), add \"New Thing\" to the output \\`gaps\\` array so the next pass can create it.\n\n## Article frontmatter (exact shape — do not invent fields)\n\n\\`\\`\\`yaml\n---\ntitle: \"Article Title\"\ntype: person | project | place | concept | event | decision | pattern\nrelated: [\"Other Article\", \"Another Article\"]\ntags: [\"2-4-specific-tags\", \"no-dates-unless-event\"]\nowner: ${agentId}\naccess: public | shared | private\ncreated: YYYY-MM-DD\nlast_updated: YYYY-MM-DD\nsources: [\"entry-id-1\", \"entry-id-2\"]\n---\n\\`\\`\\`\n\nAccess guidance: default \\`public\\`; \\`private\\` only for sensitive credentials or agent-specific learnings; \\`shared\\` with specific agent IDs when the article matters only to a subset.\n${worldviewSection}${existingList}${coveringBlock}${factsBlock}\n## Gap Detection\n\nAfter compiling, populate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific:\n\n- Good: \"Karim Rahmouni — coach for Tunis Padel club, mentioned twice but no \\`people/\\` article\"\n- Bad: \"We need more content about deployments\"\n\n## Output — valid JSON only, no markdown fencing\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"projects/globex-system-v2.md\",\n \"title\": \"Globex System V2\",\n \"type\": \"project\",\n \"related\": [\"Alex Rivera\", \"Laravel\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"laravel\", \"react\"],\n \"content\": \"Globex System V2 is [[Alex Rivera]]'s production Laravel + React app for …\",\n \"sources\": [\"entry-id-1\", \"entry-id-2\"]\n }\n ],\n \"gaps\": [\n \"Karim Rahmouni — Tunis Padel coach, mentioned in entries but no people/ article\"\n ]\n}\n\\`\\`\\`\n\nENTRIES (${entryCount}):\n\n${entryTexts}`\n}\n\n/** Max chars of a memory body shown to the promotion judge. */\nexport const PROMOTE_BODY_LIMIT = 2000\n\n/** Past this catalog size, list titles only (no paths) to bound the prompt. */\nconst PROMOTE_CATALOG_LIMIT = 300\n\n/**\n * Memory→wiki promotion prompt. Mirrors the Farzapedia absorb prompt's\n * skeleton (type spine, path convention, wikilink discipline, gaps) but\n * judges per-agent MEMORIES instead of raw entries: promote only durable,\n * cross-agent-relevant facts into the shared wiki.\n *\n * The LLM does NOT emit frontmatter — the caller sets owner/access/dates/\n * sources itself, which removes a whole class of hallucination. Candidates\n * are referenced only by their exact stamp strings.\n */\nexport function buildMemoryPromotePrompt(\n ownerId: string,\n clusters: PromotionCluster[],\n existingArticles: Array<{ title: string; path: string; type?: string }>,\n worldview: string,\n): string {\n const worldviewSection = worldview ? `\\n## Worldview\\n\\n${worldview}\\n` : \"\"\n\n const byType = new Map<string, typeof existingArticles>()\n for (const a of existingArticles) {\n const t = a.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a)\n byType.set(t, list)\n }\n const titlesOnly = existingArticles.length > PROMOTE_CATALOG_LIMIT\n const existingList = existingArticles.length === 0 ? \"\" :\n `\\n## Existing Articles (the current catalog${titlesOnly ? `, ${existingArticles.length} articles — titles only` : \"\"})\\n\\n` +\n Array.from(byType.entries()).sort()\n .map(([type, arr]) =>\n `**${type}**\\n` +\n arr.sort((x, y) => x.title.localeCompare(y.title))\n .map(a => titlesOnly ? `- [[${a.title}]]` : `- [[${a.title}]] — ${a.path}`)\n .join(\"\\n\")\n ).join(\"\\n\\n\") + \"\\n\"\n\n const candidateCount = clusters.reduce((n, c) => n + c.candidates.length, 0)\n const candidateBlocks = clusters.map(cluster => {\n const corroboration = cluster.corroboratingAgents.length > 1\n ? `corroborated by: ${cluster.corroboratingAgents.join(\", \")} (${cluster.corroboratingAgents.length} agents)`\n : `single agent: ${cluster.corroboratingAgents[0]}`\n return cluster.candidates.map(c => {\n const body = c.memory.body.length > PROMOTE_BODY_LIMIT\n ? c.memory.body.slice(0, PROMOTE_BODY_LIMIT) + \"\\n[… truncated]\"\n : c.memory.body\n return [\n `--- CANDIDATE ${c.stamp} ---`,\n `type: ${c.memory.type} | agent: ${c.agentId} | ${corroboration} | confidence: ${cluster.confidence.toFixed(2)}`,\n `description: ${c.memory.description}`,\n body,\n `--- END CANDIDATE ---`,\n ].join(\"\\n\")\n }).join(\"\\n\\n\")\n }).join(\"\\n\\n\")\n\n return `You are promoting per-agent experiential memories into the shared, authoritative AgentX wiki (owner: \"${ownerId}\").\n\n## The Job\n\nMemory is \"what one agent learned\"; the wiki is \"what every agent should know.\" For each candidate memory below, decide: promote it into a wiki article, or skip it with a reason.\n\n**The promotion test — apply in order, skip on the first \"no\":**\n\n1. **Durable?** Will this still be true in 3 months? Transient state, one-off task context, and dated status pings fail here.\n2. **Cross-agent?** Would a DIFFERENT agent benefit from knowing this? Facts about shared infrastructure, projects, people, and conventions pass; one agent's private workflow habits fail.\n3. **A fact, not a preference?** Personal style preferences private to one agent's operator relationship fail.\n4. **Never a secret.** Credentials, tokens, keys, and passwords are NEVER promoted — skip with reason \"contains secret\". Pointers to where a secret lives (e.g. \"SSH config is in X\") are fine; the secret itself is not.\n\nCorroboration across agents (shown per candidate) is a confidence signal, not a requirement — a single-agent memory that passes the test above should be promoted.\n\nCandidates stamped \\`failure:\\` are not memories: they are the same failure recurring across several sessions, with example runs. The recurrence is what makes them durable. Promote one only when the examples share a cause, as a \\`pattern\\` article saying what fails, why, and what to do instead (including a tool or configuration change, if that is the fix). Never copy task text or error output verbatim beyond what the lesson needs.\n\n**Three rules for the articles you write, in priority order:**\n\n1. **\\`type\\` is the organizational spine.** Every article has exactly one of: \\`person | project | place | concept | event | decision | pattern\\`. Choose the type BEFORE writing; if you can't pick one, skip the memory instead.\n2. **\\`related\\` is the navigation surface.** Every article cross-references 2–5 other articles via \\`[[Article Title]]\\` wikilinks in the body AND lists them in the \\`related\\` output field. Links the reader could follow to learn more.\n3. **Tags are a secondary hint.** 2–4 specific tags max. No dates unless the article is an \\`event\\`. No generic terms (\"deploy\", \"work\").\n\n## Path convention\n\nPath reflects type: \\`<type>s/<slug>.md\\` where slug is a kebab-case title.\n\n- People: \\`people/jane-doe.md\\`\n- Projects: \\`projects/globex-system-v2.md\\`\n- Concepts: \\`concepts/staging-deployment.md\\`\n- Events: \\`events/YYYY-MM-DD-<slug>.md\\` (date in path)\n- Decisions: \\`decisions/<slug>.md\\`\n- Patterns: \\`patterns/<slug>.md\\`\n- Places: \\`places/<slug>.md\\`\n\n## Merge over create\n\nIf a candidate extends an article in the catalog below, emit an UPDATE at the EXISTING path with the full merged content. Prefer merging over proliferating. Several candidates about the same entity should fold into ONE article.\n\n## Writing standards\n\n- **Wikipedia-style, flat, factual, encyclopedic.** Write about the entity's role in our work, not a diary of how the memory was learned.\n- **Synthesize, don't quote.** At most 2 short quoted lines per article.\n- **Length: 20–100 lines.**\n- **Every paragraph earns its place.**\n\n## Gap Detection\n\nPopulate a \\`gaps\\` array: wikilink targets you referenced but for which no article exists yet. Be specific (\"Karim Rahmouni — coach mentioned twice, no people/ article\"), not vague (\"need more deploy content\").\n${worldviewSection}${existingList}\n## Output — valid JSON only, no markdown fencing\n\nReference candidates ONLY by their exact stamp strings (the \\`memory:...\\` line in each CANDIDATE header). Every candidate must appear in exactly one of \\`articles[].promotedFrom\\` or \\`skipped\\`. Do NOT emit frontmatter, owner, access, dates, or sources — the caller sets those.\n\n\\`\\`\\`\n{\n \"articles\": [\n {\n \"path\": \"concepts/globex-staging-deploy.md\",\n \"title\": \"Globex Staging Deploy\",\n \"type\": \"concept\",\n \"related\": [\"Globex System V2\", \"Staging Deployment\"],\n \"tags\": [\"globex\", \"deploy\"],\n \"content\": \"Deploys to [[Globex System V2]] staging go through …\",\n \"promotedFrom\": [\"memory:coder-agent/project_globex_deploy@2026-07-01T22:14:03.000Z\"]\n }\n ],\n \"skipped\": [\n { \"memory\": \"memory:cx/feedback_short_replies@2026-06-01T00:00:00.000Z\", \"reason\": \"agent-specific style preference, not a cross-agent fact\" }\n ],\n \"gaps\": []\n}\n\\`\\`\\`\n\nCANDIDATES (${candidateCount} memories in ${clusters.length} clusters):\n\n${candidateBlocks}`\n}\n"],"mappings":"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","names":["createHash","existsSync","readdirSync","readFileSync","writeFileSync","resolve","buildAbsorbPrompt","_mode","agentId","worldview","existingArticles","entryTexts","entryCount","factsBlock","coveringBlock","buildFarzapediaPrompt","worldviewSection","byType","a","t","list","existingList","type","arr","x","y","tierLadderMarkdown","fieldChecklistMarkdown","PROMOTE_BODY_LIMIT","PROMOTE_CATALOG_LIMIT","buildMemoryPromotePrompt","ownerId","clusters","titlesOnly","candidateCount","n","c","candidateBlocks","cluster","corroboration","body","PROMOTER_OWNER","DEFAULT_PROMOTE_TYPES","PROMOTION_LEDGER_FILE","STAMP_PREFIX","FAILURE_PREFIX","failureIdentity","stamp","at","MEMORY_TYPES","memoryKey","agentId","m","memoryStamp","parseMemoryStamp","s","rest","slash","typeName","updatedAt","underscore","type","name","mergeSources","existing","newStamps","newKeys","parsed","out","listAllAgentMemories","memoryRoot","resolve","store","AgentMemory","existsSync","entry","readdirSync","memory","LEDGER_DECISIONS","readPromotionLedger","wikiBaseDir","path","readFileSync","e","appendPromotionLedger","entries","merged","byKey","key","prev","writeFileSync","groupCandidates","cands","byName","c","k","list","candidates","corroboratingAgents","typeBoost","confidence","parsePromotionResponse","text","offeredStamps","start","depth","end","inString","escaped","i","ch","warnings","articles","a","raw","promotedFrom","r","t","skipped","gaps","g","getUnpromotedMemories","all","index","ledger","opts","types","now","max","seen","handledStamps","record","article","pendingFailures","occurrences","sessions","tasks","failure","updated","handled","b","ANTHROPIC_API","ANTHROPIC_VERSION","callPromotionLlm","prompt","feedback","fetchImpl","timeoutMs","ctrl","timer","daemonUrl","message","res","errBody","body","content","apiKey","messages","runPromotion","wikiDir","log","report","WikiStore","args","articleList","worldview","budget","DEFAULT_BUDGET_TOKENS","clusters","buildMemoryPromotePrompt","estimateTokens","judged","offered","chatId","ledgerEntries","byStamp","evidence","evidenceFor","proposal","newProposalId","contentHash","saveProposal","createHash","EXCERPT_CHARS","sourceKind","stamps","sources","approveProposal","id","readProposal","today","isWikiArticleType","decided","rejectProposal"]}
|