diff --git a/routes/agentify-help.ts b/routes/agentify-help.ts new file mode 100644 index 0000000..2b5b120 --- /dev/null +++ b/routes/agentify-help.ts @@ -0,0 +1,4156 @@ +/** + * Agentify.Help — Public Cookbook Site + * + * "Build the AI expert. Once. Right." + * + * One-per-person registry. Corpus-grounded. Framework-distilled. Steward-named. + * A civic project under WellSpr.ing covenant. Published April 24, 2026. + * + * Domain: agentify.help (Bunny DNS → Fly.io) + * Same stack pattern as notgit.org — standalone SSR HTML, domain-keyed. + */ + +import express from "express"; +import type { Express, Request, Response } from "express"; +import fs from "fs"; +import path from "path"; +import { pool } from "../db"; +import { getOgPng } from "../og-images"; +import { Resend } from "resend"; +import crypto from "crypto"; + +const resend = new Resend(process.env.RESEND_API_KEY); + +const ADMIN_KEY = process.env.ADMIN_KEY || "b0db7a87384fc814b0f46ea7bdc6ab6a81152be5b098718b"; + +// ── DB setup ────────────────────────────────────────────────────────────────── +async function ensureAgentifyHelpTables() { + await pool.query(` + CREATE TABLE IF NOT EXISTS agentify_subject_registry ( + id SERIAL PRIMARY KEY, + subject_slug TEXT UNIQUE NOT NULL, + subject_name TEXT NOT NULL, + subject_domain TEXT, + steward_name TEXT NOT NULL, + steward_email TEXT NOT NULL, + host_url TEXT, + corpus_version TEXT DEFAULT '1.0.0', + status TEXT NOT NULL DEFAULT 'pending', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ) + `); + // Additive migrations — idempotent + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS registered_via TEXT NOT NULL DEFAULT 'web'`); + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS email_verification_token TEXT`); + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS email_verified_at TIMESTAMPTZ`); + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS covenant_attested_at TIMESTAMPTZ`); + // Seed Sniderman as founding agent #1 + await pool.query(` + INSERT INTO agentify_subject_registry + (subject_slug, subject_name, subject_domain, steward_name, steward_email, host_url, corpus_version, status, registered_via) + VALUES + ('allan-sniderman', 'Dr. Allan Sniderman', 'Cardiovascular Medicine / Lipoprotein Research', + 'Naturologie', 'ody@wellspr.ing', + 'https://cholesteroltruth.com/experts/dr-allan-sniderman', '1.0.0', 'active', 'web') + ON CONFLICT (subject_slug) DO NOTHING + `); + + // Fix Guy Kawasaki registry entry (may have stale "Worldmaxxing" domain from early test) + await pool.query(` + INSERT INTO agentify_subject_registry + (subject_slug, subject_name, subject_domain, steward_name, steward_email, status, registered_via) + VALUES + ('guy-kawasaki', 'Guy Kawasaki', 'entrepreneurship, marketing, venture capital, innovation', + 'WellSpr.ing', 'ody@wellspr.ing', 'preview', 'orchestrator') + ON CONFLICT (subject_slug) DO UPDATE SET + subject_domain = 'entrepreneurship, marketing, venture capital, innovation', + status = CASE WHEN agentify_subject_registry.status = 'pending' THEN 'preview' ELSE agentify_subject_registry.status END, + updated_at = NOW() + `); + + // Ensure agentify_experts has Guy Kawasaki + await pool.query(` + CREATE TABLE IF NOT EXISTS agentify_experts ( + id SERIAL PRIMARY KEY, + slug TEXT NOT NULL UNIQUE, + expert_name TEXT NOT NULL, + credentials TEXT, + primary_domain TEXT NOT NULL, + affiliated_institution TEXT, + demo_url TEXT, + scopes TEXT[] NOT NULL DEFAULT '{}', + refusals TEXT[] NOT NULL DEFAULT '{}', + cred_id TEXT, + attestation_version TEXT NOT NULL DEFAULT 'v0.1', + attestation_uri TEXT, + status TEXT NOT NULL DEFAULT 'proposed', + created_at TIMESTAMPTZ DEFAULT NOW() + ) + `); + await pool.query(` + INSERT INTO agentify_experts (slug, expert_name, primary_domain, status, attestation_version) + VALUES ('guy-kawasaki', 'Guy Kawasaki', 'entrepreneurship, marketing, venture capital, innovation', 'preview', 'v0.1') + ON CONFLICT (slug) DO UPDATE SET + primary_domain = EXCLUDED.primary_domain, + status = EXCLUDED.status + `); + + // Ensure corpus bundle record for Guy Kawasaki exists + await pool.query(` + CREATE TABLE IF NOT EXISTS agentify_corpus_bundles ( + id SERIAL PRIMARY KEY, + bundle_id TEXT UNIQUE NOT NULL, + expert_slug TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'received', + chunk_count INTEGER, + received_at TIMESTAMPTZ DEFAULT NOW() + ) + `); + await pool.query(` + INSERT INTO agentify_corpus_bundles (bundle_id, expert_slug, corpus_version, attestation_uri, attestation_version, status, chunk_count) + VALUES ('336baf2f-e83b-4b00-be48-5357433c7798', 'guy-kawasaki', '1.0.0', 'https://agentify.help/vcap/guy-kawasaki', 'v0.1', 'ready', 2222) + ON CONFLICT (bundle_id) DO UPDATE SET status = 'ready', chunk_count = 2222 + `); + + // ── Topic taxonomy ───────────────────────────────────────────────────────── + // The canonical list of approved topics. Prevents balkanization by requiring + // every topic agent to be anchored to a taxonomy entry that passed review. + // A proposed entry is visible to stewards; approved/active entries appear + // publicly. One slug = one topic — no forks without a new entry. + await pool.query(` + CREATE TABLE IF NOT EXISTS agentify_topic_taxonomy ( + id SERIAL PRIMARY KEY, + category TEXT NOT NULL, + slug TEXT UNIQUE NOT NULL, + display_name TEXT NOT NULL, + description TEXT, + scope_note TEXT, + audience_personas TEXT[] DEFAULT '{layperson,clinician}', + status TEXT NOT NULL DEFAULT 'proposed', + proposed_by_email TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ) + `); + + // Seed the first approved topic: Post-Operative Cardiac Care + await pool.query(` + INSERT INTO agentify_topic_taxonomy + (category, slug, display_name, description, scope_note, audience_personas, status) + VALUES + ('Medical / Health', + 'postop-cardiac-care', + 'Post-Operative Cardiac Care', + 'Evidence synthesis on recovery, complications, and patient management after cardiac surgery — valve repair/replacement, CABG, structural heart procedures.', + 'In scope: delirium prevention (HELP program), hemodynamic management, anticoagulation, cardiac rehab, frailty assessment, caregiver guides. Out of scope: intraoperative technique, interventional cardiology, long-term cardiology follow-up beyond 12 months.', + '{layperson,clinician,nursing,researcher}', + 'approved') + ON CONFLICT (slug) DO UPDATE SET + description = EXCLUDED.description, + scope_note = EXCLUDED.scope_note, + audience_personas = EXCLUDED.audience_personas, + status = CASE WHEN agentify_topic_taxonomy.status = 'proposed' THEN 'approved' + ELSE agentify_topic_taxonomy.status END + `); + + // Additive migrations for subject_registry + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS subject_type TEXT NOT NULL DEFAULT 'person'`); + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS taxonomy_slug TEXT REFERENCES agentify_topic_taxonomy(slug) ON DELETE SET NULL`); + await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS audience_personas TEXT[] DEFAULT '{}'`); + + // Link cardiac care subject to its taxonomy entry + await pool.query(` + UPDATE agentify_subject_registry + SET taxonomy_slug = 'postop-cardiac-care', + audience_personas = '{layperson,clinician,nursing,researcher}' + WHERE subject_slug = 'postop-cardiac-care' AND taxonomy_slug IS NULL + `); + + console.log("[Agentify.Help] Registry table ready — Guy Kawasaki + topic taxonomy seeded"); +} + +// ── Slug normalization ──────────────────────────────────────────────────────── +// Strip honorifics and credentials, normalize to slug. +// "Dr. Allan Sniderman" → "allan-sniderman" +// "Allan Sniderman, MD, PhD" → "allan-sniderman" +function nameToSlug(name: string): string { + return name + .toLowerCase() + .replace(/\b(dr|prof|professor|mr|mrs|ms|rev|hon|sir|dame|lord|the)\b\.?/gi, "") + .replace(/,?\s*(md|phd|do|np|pa|rn|dds|jd|esq|cpa|mba|ms|bs|ba)\b/gi, "") + .replace(/[^a-z0-9\s-]/g, "") + .replace(/\s+/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, "") + .trim(); +} + +// ── Rate limiter (in-memory; swap for Redis at scale) ───────────────────────── +const _rl = new Map(); +function rateCheck(ip: string, max: number, windowMs: number): boolean { + const now = Date.now(); + const e = _rl.get(ip); + if (!e || now > e.t) { _rl.set(ip, { n: 1, t: now + windowMs }); return true; } + if (e.n >= max) return false; + e.n++; + return true; +} +setInterval(() => { const now = Date.now(); for (const [k, v] of _rl) if (now > v.t) _rl.delete(k); }, 600_000).unref(); + +// ── MCP Tool definitions (Streamable HTTP / 2025-03-26 spec) ───────────────── +export const AH_MCP_TOOLS = [ + { + name: "check_availability", + description: "Check whether an expert name is available for first-steward registration in the Agentify.Help one-per-person WellAgent registry. Returns availability, normalized slug, and any existing entry.", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Full name of the expert — honorifics and credentials are stripped automatically (e.g. 'Dr. Jane Smith MD' → 'jane-smith')." } + }, + required: ["name"] + } + }, + { + name: "register", + description: "Claim first-steward registration for an expert AI persona. The registry enforces one agent per real person — first registration wins, no duplicates. Returns registration_number (your permanent place in the registry), slug, and timestamp. Store these; they are your proof of priority.", + inputSchema: { + type: "object", + properties: { + subject_name: { type: "string", description: "Full name of the expert being agentified." }, + subject_domain: { type: "string", description: "Domain of expertise (e.g. 'Cardiovascular Medicine', 'Constitutional Law')." }, + steward_name: { type: "string", description: "Name of the person or organization acting as steward / builder." }, + steward_email: { type: "string", description: "Contact email for the steward — shown publicly so quality concerns can be raised." }, + host_url: { type: "string", description: "URL where the deployed WellAgent will be accessible (optional at registration time)." }, + registered_via: { type: "string", description: "Identifier for the registering agent or client (e.g. 'mcp', 'my-agent-v1'). Defaults to 'mcp'." } + }, + required: ["subject_name", "steward_name", "steward_email"] + } + }, + { + name: "list_registry", + description: "List all registered WellAgent personas in the public Agentify.Help registry, ordered by registration date (first registered first). Use this to see which names are taken before attempting registration.", + inputSchema: { + type: "object", + properties: { + limit: { type: "number", description: "Max results to return (1–100, default 50)." }, + status: { type: "string", description: "Filter by status: 'active', 'pending', or 'claimed'. Omit for all." } + } + } + }, + { + name: "get_agent", + description: "Retrieve full registry details for a specific expert by their URL slug.", + inputSchema: { + type: "object", + properties: { + slug: { type: "string", description: "URL slug (e.g. 'allan-sniderman'). Use list_registry or check_availability to discover slugs." } + }, + required: ["slug"] + } + } +]; + +async function handleMcpTool(toolName: string, args: any): Promise<{ content: Array<{ type: string; text: string }> }> { + const text = (v: any) => ({ content: [{ type: "text", text: typeof v === "string" ? v : JSON.stringify(v, null, 2) }] }); + + switch (toolName) { + case "check_availability": { + const { name } = args ?? {}; + if (!name) return text({ error: "name is required" }); + const slug = nameToSlug(String(name)); + if (!slug) return text({ error: "Could not normalize name to slug" }); + const { rows } = await pool.query( + `SELECT id AS registration_number, subject_slug, subject_name, steward_name, host_url, status, created_at + FROM agentify_subject_registry WHERE subject_slug = $1`, [slug]); + if (rows.length === 0) return text({ available: true, slug, message: `"${name}" is available. Be first — register now.` }); + return text({ available: false, slug, existing: rows[0], message: `"${name}" is already registered.` }); + } + + case "register": { + const { subject_name, subject_domain, steward_name, steward_email, host_url, registered_via } = args ?? {}; + if (!subject_name || !steward_name || !steward_email) { + return text({ error: "subject_name, steward_name, steward_email are required" }); + } + const slug = nameToSlug(String(subject_name)); + if (!slug) return text({ error: "Could not normalize subject_name to slug" }); + + try { + const { rows } = await pool.query(` + INSERT INTO agentify_subject_registry + (subject_slug, subject_name, subject_domain, steward_name, steward_email, host_url, status, registered_via) + VALUES ($1,$2,$3,$4,$5,$6,'pending',$7) + RETURNING id, subject_slug, created_at + `, [slug, String(subject_name), subject_domain || null, String(steward_name), String(steward_email), + host_url || null, String(registered_via || "mcp")]); + + const row = rows[0]; + console.log(`[Agentify.Help/MCP] Registered: ${subject_name} (${slug}) by ${steward_name} via ${registered_via || "mcp"}`); + return text({ + ok: true, + slug: row.subject_slug, + registration_number: row.id, + registered_at: row.created_at, + message: `Registration confirmed. You are steward for "${subject_name}". Your registration number is #${row.id}. This is your proof of priority — no one else may register this person.` + }); + } catch (e: any) { + if (e.message?.includes("unique") || e.code === "23505") { + return text({ error: `"${subject_name}" is already registered. First registration wins.`, slug }); + } + return text({ error: e.message }); + } + } + + case "list_registry": { + const limit = Math.min(100, Math.max(1, Number(args?.limit) || 50)); + const status = args?.status ? String(args.status) : null; + const { rows } = await pool.query( + `SELECT id AS registration_number, subject_slug, subject_name, subject_domain, + steward_name, host_url, corpus_version, status, registered_via, created_at + FROM agentify_subject_registry + ${status ? "WHERE status = $2" : ""} + ORDER BY created_at ASC LIMIT $1`, + status ? [limit, status] : [limit] + ); + return text({ agents: rows, count: rows.length, updated: new Date().toISOString() }); + } + + case "get_agent": { + const { slug } = args ?? {}; + if (!slug) return text({ error: "slug is required" }); + const { rows } = await pool.query( + `SELECT id AS registration_number, subject_slug, subject_name, subject_domain, + steward_name, host_url, corpus_version, status, registered_via, created_at, updated_at + FROM agentify_subject_registry WHERE subject_slug = $1`, [String(slug)]); + if (rows.length === 0) return text({ error: `No agent found for slug "${slug}"` }); + return text(rows[0]); + } + + default: + throw { code: -32602, message: `Unknown tool: ${toolName}` }; + } +} + +// ── OpenAPI 3.0 spec ────────────────────────────────────────────────────────── +const AH_OPENAPI = { + openapi: "3.0.3", + info: { + title: "Agentify.Help — WellAgent Registry & Consultation API", + version: "1.1.0", + description: "One-per-person WellAgent registry and expert consultation surface. Covers steward registration (agentify.help), corpus assembly skill, and the WellAgent consultation endpoints (wellspr.ing). First registration wins — no duplicates.", + contact: { email: "ody@wellspr.ing", url: "https://agentify.help" } + }, + servers: [ + { url: "https://agentify.help", description: "Registry, skill files, and steward tools" }, + { url: "https://wellspr.ing", description: "Corpus intake, expert index, and consultation" } + ], + tags: [ + { name: "registry", description: "Steward registration and public ledger — agentify.help" }, + { name: "corpus", description: "Corpus assembly skill and intake — agentify.help + wellspr.ing" }, + { name: "consultation", description: "Live WellAgent expert consultation — wellspr.ing" } + ], + paths: { + "/api/agentify-help/check": { + post: { + tags: ["registry"], + operationId: "check_availability", + summary: "Check if an expert name is available", + servers: [{ url: "https://agentify.help" }], + requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["name"], properties: { name: { type: "string" } } } } } }, + responses: { "200": { description: "Availability result", content: { "application/json": { schema: { type: "object", properties: { available: { type: "boolean" }, slug: { type: "string" }, existing: { type: "object" } } } } } } } + } + }, + "/api/agentify-help/register": { + post: { + tags: ["registry"], + operationId: "register", + summary: "Register as first steward for an expert persona", + servers: [{ url: "https://agentify.help" }], + requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["name","steward_name","steward_email"], properties: { name: { type: "string" }, subject_domain: { type: "string" }, steward_name: { type: "string" }, steward_email: { type: "string" }, host_url: { type: "string" }, registered_via: { type: "string", description: "Set to your agent/Replit identifier to waive the human checkbox" } } } } } }, + responses: { "200": { description: "Registration confirmed", content: { "application/json": { schema: { type: "object", properties: { ok: { type: "boolean" }, slug: { type: "string" }, registration_number: { type: "integer" } } } } } }, "409": { description: "Already registered" } } + } + }, + "/api/registry.json": { + get: { + tags: ["registry"], + operationId: "list_registry", + summary: "Full public registry as JSON", + servers: [{ url: "https://agentify.help" }], + parameters: [ + { name: "limit", in: "query", schema: { type: "integer", default: 50, maximum: 100 } }, + { name: "status", in: "query", schema: { type: "string", enum: ["active","pending","claimed"] } } + ], + responses: { "200": { description: "Registry list", content: { "application/json": { schema: { type: "object", properties: { registry: { type: "array" }, count: { type: "integer" }, updated: { type: "string" } } } } } } } + } + }, + "/api/feed.json": { + get: { + tags: ["registry"], + operationId: "get_feed", + summary: "Recent registrations feed — chronological, newest first", + servers: [{ url: "https://agentify.help" }], + parameters: [{ name: "limit", in: "query", schema: { type: "integer", default: 20, maximum: 100 } }], + responses: { "200": { description: "Feed of recent registrations" } } + } + }, + "/agentify-corpus/skill.md": { + get: { + tags: ["corpus"], + operationId: "get_corpus_skill", + summary: "Corpus assembly skill file — hand to any AI coding agent", + description: "Full protocol for assembling and submitting a WellAgent corpus: chunk format, manifest schema, intake API flow, quality gates, and versioning rules. Serve this URL to Cursor, Claude Projects, or any AI agent to give it complete build context.", + servers: [{ url: "https://agentify.help" }], + responses: { "200": { description: "Markdown skill file", content: { "text/markdown": { schema: { type: "string" } } } } } + } + }, + "/api/agentify/experts": { + get: { + tags: ["consultation"], + operationId: "list_experts", + summary: "List all active WellAgents with metadata", + description: "Returns slug, name, credentials, primary_domain, scopes, refusals, and status for every non-deprecated expert. No auth required.", + servers: [{ url: "https://wellspr.ing" }], + responses: { "200": { description: "Expert index", content: { "application/json": { schema: { type: "object", properties: { experts: { type: "array", items: { type: "object", properties: { slug: { type: "string" }, expert_name: { type: "string" }, credentials: { type: "string" }, primary_domain: { type: "string" }, scopes: { type: "array", items: { type: "string" } }, refusals: { type: "array", items: { type: "string" } }, status: { type: "string" } } } } } } } } } } + } + }, + "/api/agentify/experts/{slug}/status": { + get: { + tags: ["consultation"], + operationId: "get_expert_status", + summary: "Check corpus readiness for a WellAgent", + servers: [{ url: "https://wellspr.ing" }], + parameters: [{ name: "slug", in: "path", required: true, schema: { type: "string" } }], + responses: { "200": { description: "Readiness and bundle info", content: { "application/json": { schema: { type: "object", properties: { isReady: { type: "boolean" }, embeddedChunks: { type: "integer" }, latestBundle: { type: "object", properties: { status: { type: "string" }, chunk_count: { type: "integer" }, distillation_notes: { type: "string" } } }, consultEndpoint: { type: "string", nullable: true } } } } } }, "404": { description: "Expert not found" } } + } + }, + "/api/agentify/experts/{slug}/consult": { + post: { + tags: ["consultation"], + operationId: "consult_expert", + summary: "Ask a question to a WellAgent expert", + description: "Submits a question to the expert's corpus-grounded AI. Returns a sourced answer, the supporting chunks used for retrieval, and the retrieval method. Requires X-Admin-Key (internal) or X-Partner-Token (issued tokens).", + servers: [{ url: "https://wellspr.ing" }], + parameters: [{ name: "slug", in: "path", required: true, schema: { type: "string" } }], + security: [{ AdminKey: [] }, { PartnerToken: [] }], + requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["question"], properties: { question: { type: "string", description: "Plain-language question for the expert" }, context: { type: "object", description: "Optional context (e.g. ehr: patient EHR text)" } } } } } }, + responses: { + "200": { description: "Expert answer with source chunks", content: { "application/json": { schema: { type: "object", properties: { answer: { type: "string" }, chunks: { type: "array", items: { type: "object", properties: { text: { type: "string" }, title: { type: "string" }, similarity: { type: "number" } } } }, retrieval: { type: "string", enum: ["dense","fts"] }, expert_name: { type: "string" }, slug: { type: "string" }, bundle_id: { type: "string" } } } } } }, + "401": { description: "X-Partner-Token required" }, + "403": { description: "Invalid token" }, + "503": { description: "Expert corpus not ready — run distillation first" } + } + } + } + }, + components: { + securitySchemes: { + AdminKey: { type: "apiKey", in: "header", name: "X-Admin-Key" }, + PartnerToken: { type: "apiKey", in: "header", name: "X-Partner-Token" } + } + } +}; + +// ── Well-known payloads ──────────────────────────────────────────────────────── +const AH_AI_PLUGIN = { + schema_version: "v1", + name_for_human: "Agentify.Help Registry", + name_for_model: "agentify_registry", + description_for_human: "Check and claim expert AI persona registrations. One person, one agent — first steward wins.", + description_for_model: "Use this to check whether an expert name is available in the Agentify.Help one-per-person WellAgent registry, to register as steward, or to browse the public ledger of registered expert AI personas. Registration is first-come first-served. Provide the full name; honorifics are stripped automatically.", + auth: { type: "none" }, + api: { type: "openapi", url: "https://agentify.help/.well-known/openapi.json" }, + logo_url: "https://agentify.help/favicon.ico", + contact_email: "ody@wellspr.ing", + legal_info_url: "https://wellspr.ing/constitution" +}; + +const AH_MCP_DISCOVERY = { + mcpVersion: "2025-03-26", + name: "agentify-help", + displayName: "Agentify.Help Expert Registry", + description: "One-per-person registry for WellAgent expert AI personas. Tools: check_availability, register, list_registry, get_agent. First registration wins.", + transport: [ + { type: "http", url: "https://mcp.agentify.help/" }, + { type: "http", url: "https://agentify.help/mcp" } + ], + capabilities: { tools: {} }, + contact: { email: "ody@wellspr.ing", url: "https://agentify.help" }, + legal: { termsOfService: "https://wellspr.ing/constitution" } +}; + +// ── Favicon ─────────────────────────────────────────────────────────────────── +const AH_FAVICON = `data:image/svg+xml,%3Csvg viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='16' cy='10' r='5' fill='%23C9962A'/%3E%3Ccircle cx='16' cy='10' r='5' fill='none' stroke='%231A2B4A' stroke-width='1.2' opacity='0.3'/%3E%3Cpath d='M8 26c0-4.4 3.6-8 8-8s8 3.6 8 8' stroke='%231A2B4A' stroke-width='1.8' stroke-linecap='round'/%3E%3Ccircle cx='24' cy='7' r='3' fill='%231A2B4A' opacity='0.25'/%3E%3Cpath d='M22 5.5l4 3M22 8.5l4-3' stroke='%23C9962A' stroke-width='1' stroke-linecap='round' opacity='0.6'/%3E%3C%2Fsvg%3E`; + +// ── Shared CSS ──────────────────────────────────────────────────────────────── +const AH_CSS = ` + :root { + --bg: #FDFAF4; + --bg2: #F5F0E6; + --bg3: #EDE7D6; + --primary: #1A2B4A; + --accent: #C9962A; + --accent2: #E8B84B; + --text1: #1A2B4A; + --text2: #4A5568; + --text3: #8A9099; + --border: rgba(26,43,74,0.14); + --green: #2D6A4F; + --red: #9B2335; + --code-bg: #F0EDE4; + --shadow: 0 2px 12px rgba(26,43,74,0.10); + } + *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + html { scroll-behavior: smooth; } + body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif; + background: var(--bg); + color: var(--text1); + line-height: 1.65; + font-size: 16px; + } + a { color: var(--accent); text-decoration: none; } + a:hover { text-decoration: underline; } + code, pre { + font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; + font-size: 0.875rem; + background: var(--code-bg); + border-radius: 4px; + } + code { padding: 2px 6px; } + pre { padding: 1.25rem 1.5rem; overflow-x: auto; border: 1px solid var(--border); } + + /* ── NAV ── */ + nav { + position: sticky; top: 0; z-index: 100; + background: var(--primary); + border-bottom: 2px solid var(--accent); + display: flex; align-items: center; justify-content: space-between; + padding: 0 2rem; height: 52px; + } + .nav-brand { color: #fff; font-weight: 700; font-size: 1rem; letter-spacing: 0.04em; } + .nav-brand span { color: var(--accent2); } + .nav-links { display: flex; gap: 1.75rem; } + .nav-links a { color: rgba(255,255,255,0.75); font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 500; transition: color 0.15s; } + .nav-links a:hover { color: var(--accent2); text-decoration: none; } + + /* ── HERO ── */ + .hero { + background: var(--primary); + color: #fff; + padding: 5rem 2rem 4rem; + text-align: center; + } + .hero-tag { + display: inline-block; + background: rgba(201,150,42,0.2); + color: var(--accent2); + border: 1px solid rgba(201,150,42,0.4); + border-radius: 4px; + padding: 0.3rem 0.85rem; + font-size: 0.78rem; + letter-spacing: 0.1em; + text-transform: uppercase; + font-weight: 600; + margin-bottom: 1.5rem; + } + .hero h1 { + font-size: clamp(2rem, 5vw, 3.2rem); + font-weight: 800; + line-height: 1.2; + max-width: 700px; + margin: 0 auto 1.25rem; + letter-spacing: -0.02em; + } + .hero h1 em { color: var(--accent2); font-style: normal; } + .hero p { + font-size: 1.15rem; + color: rgba(255,255,255,0.75); + max-width: 560px; + margin: 0 auto 2.5rem; + line-height: 1.75; + } + .hero-rule { + display: inline-flex; align-items: center; gap: 1rem; + background: rgba(255,255,255,0.06); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 6px; + padding: 0.75rem 1.5rem; + font-size: 0.88rem; + color: rgba(255,255,255,0.7); + margin-bottom: 2.5rem; + } + .hero-rule strong { color: var(--accent2); } + .cta-row { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; } + .btn-primary { + background: var(--accent); + color: var(--primary); + font-weight: 700; + padding: 0.8rem 2rem; + border-radius: 6px; + font-size: 0.95rem; + transition: background 0.15s; + border: none; cursor: pointer; + } + .btn-primary:hover { background: var(--accent2); text-decoration: none; } + .btn-secondary { + background: transparent; + color: rgba(255,255,255,0.8); + font-weight: 600; + padding: 0.8rem 2rem; + border-radius: 6px; + font-size: 0.95rem; + border: 1px solid rgba(255,255,255,0.25); + transition: border-color 0.15s; + cursor: pointer; + } + .btn-secondary:hover { border-color: var(--accent2); color: var(--accent2); text-decoration: none; } + + /* ── SECTIONS ── */ + section { padding: 4rem 2rem; } + .container { max-width: 860px; margin: 0 auto; } + .container-wide { max-width: 1060px; margin: 0 auto; } + .section-label { + font-size: 0.75rem; font-weight: 700; letter-spacing: 0.14em; + text-transform: uppercase; color: var(--accent); + margin-bottom: 0.6rem; + } + h2 { + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 800; + color: var(--primary); + line-height: 1.25; + margin-bottom: 1rem; + letter-spacing: -0.02em; + } + h3 { + font-size: 1.1rem; font-weight: 700; color: var(--primary); + margin-bottom: 0.5rem; + } + p { color: var(--text2); margin-bottom: 1rem; } + p:last-child { margin-bottom: 0; } + + /* ── PROBLEM COLUMNS ── */ + .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 2.5rem; } + @media (max-width: 640px) { .two-col { grid-template-columns: 1fr; } } + .problem-card { + background: var(--bg2); + border: 1px solid var(--border); + border-radius: 10px; + padding: 1.75rem; + } + .problem-card h3 { font-size: 1rem; } + .problem-card .label-bad { color: var(--red); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 0.5rem; } + .problem-card .label-good { color: var(--green); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 0.5rem; } + + /* ── FOUR MARKS ── */ + .marks-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 1.25rem; margin-top: 2rem; } + .mark-card { + background: var(--bg2); + border: 1px solid var(--border); + border-top: 3px solid var(--accent); + border-radius: 8px; + padding: 1.5rem 1.25rem; + } + .mark-num { font-size: 0.75rem; font-weight: 800; color: var(--accent); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 0.4rem; } + .mark-card h3 { font-size: 0.95rem; margin-bottom: 0.4rem; } + .mark-card p { font-size: 0.88rem; color: var(--text3); margin: 0; } + + /* ── SEVEN STAGES ── */ + .stages-list { list-style: none; display: flex; flex-direction: column; gap: 0; margin-top: 2rem; } + .stage-item { + display: grid; + grid-template-columns: 56px 1fr; + gap: 0; + border: 1px solid var(--border); + border-bottom: none; + background: var(--bg); + } + .stage-item:first-child { border-radius: 10px 10px 0 0; } + .stage-item:last-child { border-bottom: 1px solid var(--border); border-radius: 0 0 10px 10px; } + .stage-item:nth-child(even) { background: var(--bg2); } + .stage-num { + background: var(--primary); + color: var(--accent2); + font-size: 0.7rem; + font-weight: 800; + letter-spacing: 0.06em; + display: flex; align-items: center; justify-content: center; + writing-mode: vertical-rl; + text-orientation: mixed; + padding: 1.25rem 0; + min-height: 80px; + } + .stage-content { padding: 1.25rem 1.5rem; } + .stage-title { font-weight: 800; font-size: 0.95rem; color: var(--primary); margin-bottom: 0.35rem; } + .stage-title code { font-size: 0.78rem; color: var(--accent); background: rgba(201,150,42,0.1); } + .stage-desc { font-size: 0.88rem; color: var(--text2); margin: 0; } + + /* ── REGISTRY CHECKER ── */ + .check-box { + background: var(--primary); + border-radius: 12px; + padding: 2.5rem; + color: #fff; + margin-top: 2rem; + } + .check-box h3 { color: #fff; font-size: 1.15rem; margin-bottom: 0.4rem; } + .check-box p { color: rgba(255,255,255,0.65); font-size: 0.9rem; margin-bottom: 1.5rem; } + .check-row { display: flex; gap: 0.75rem; flex-wrap: wrap; } + .check-input { + flex: 1; min-width: 220px; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 6px; + color: #fff; + padding: 0.7rem 1rem; + font-size: 0.95rem; + outline: none; + transition: border-color 0.15s; + } + .check-input::placeholder { color: rgba(255,255,255,0.35); } + .check-input:focus { border-color: var(--accent2); } + .check-btn { + background: var(--accent); + color: var(--primary); + font-weight: 700; + padding: 0.7rem 1.5rem; + border-radius: 6px; + font-size: 0.9rem; + border: none; cursor: pointer; + transition: background 0.15s; + white-space: nowrap; + } + .check-btn:hover { background: var(--accent2); } + #check-result { margin-top: 1.25rem; min-height: 1rem; font-size: 0.9rem; } + .result-available { + background: rgba(45,106,79,0.25); + border: 1px solid rgba(45,106,79,0.4); + border-radius: 8px; padding: 1rem 1.25rem; + color: #a8d5b5; + } + .result-taken { + background: rgba(155,35,53,0.2); + border: 1px solid rgba(155,35,53,0.35); + border-radius: 8px; padding: 1rem 1.25rem; + color: #f4b8c0; + } + .result-available strong, .result-taken strong { display: block; margin-bottom: 0.3rem; } + + /* ── REGISTER FORM ── */ + .register-form { display: none; margin-top: 1.25rem; } + .register-form.visible { display: block; } + .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 0.75rem; } + @media (max-width: 540px) { .form-grid { grid-template-columns: 1fr; } } + .form-field { display: flex; flex-direction: column; gap: 0.3rem; } + .form-label { font-size: 0.75rem; font-weight: 600; color: rgba(255,255,255,0.55); letter-spacing: 0.06em; text-transform: uppercase; } + .form-input { + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.2); + border-radius: 6px; color: #fff; + padding: 0.6rem 0.85rem; font-size: 0.88rem; outline: none; + } + .form-input:focus { border-color: var(--accent2); } + .form-input::placeholder { color: rgba(255,255,255,0.3); } + .register-submit { + background: var(--accent); color: var(--primary); + font-weight: 700; padding: 0.7rem 2rem; + border-radius: 6px; font-size: 0.9rem; + border: none; cursor: pointer; margin-top: 0.5rem; + } + #register-result { margin-top: 1rem; font-size: 0.88rem; } + + /* ── REGISTRY TABLE ── */ + .registry-table { width: 100%; border-collapse: collapse; margin-top: 1.75rem; font-size: 0.88rem; } + .registry-table th { + text-align: left; padding: 0.6rem 1rem; + background: var(--bg3); color: var(--text2); + font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; + border-bottom: 2px solid var(--border); + } + .registry-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); color: var(--text2); vertical-align: top; } + .registry-table tr:last-child td { border-bottom: none; } + .registry-table tbody tr:hover td { background: var(--bg2); } + .status-active { color: var(--green); font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; } + .status-preview { color: var(--accent); font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; } + .status-pending { color: var(--accent); font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; } + .status-claimed { color: var(--green); font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; } + .status-declined { color: var(--text3); font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; } + .status-verify_email { color: #f59e0b; font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; } + + /* ── Registry sub-tab bar ── */ + .reg-tab-bar { + display: flex; gap: 0.25rem; + border-bottom: 1px solid var(--border); + margin-bottom: 0; + } + .reg-tab { + padding: 0.6rem 1.25rem; background: none; border: none; border-bottom: 2px solid transparent; + font-size: 0.82rem; font-weight: 600; color: var(--text3); cursor: pointer; + letter-spacing: 0.02em; transition: color 0.15s, border-color 0.15s; + margin-bottom: -1px; + } + .reg-tab:hover { color: var(--text); } + .reg-tab.active { color: var(--accent2); border-bottom-color: var(--accent2); } + + /* ── Covenant section ── */ + .covenant-section { margin-top: 1.25rem; border: 1px solid var(--border); border-radius: 10px; padding: 1.25rem 1.4rem; background: var(--bg2); } + .covenant-heading { font-size: 0.72rem; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.07em; margin-bottom: 0.6rem; } + .covenant-intro { font-size: 0.82rem; color: var(--text2); line-height: 1.55; margin-bottom: 0.9rem; } + .covenant-checks { display: flex; flex-direction: column; gap: 0.6rem; } + .covenant-item { display: flex; align-items: flex-start; gap: 0.55rem; cursor: pointer; } + .covenant-item input[type="checkbox"] { margin-top: 3px; flex-shrink: 0; accent-color: var(--accent); width: 14px; height: 14px; cursor: pointer; } + .covenant-item span { font-size: 0.81rem; color: rgba(255,255,255,0.55); line-height: 1.5; transition: color 0.15s; } + .covenant-item:has(input:checked) span { color: #fff; } + + /* ── RAILS CARDS ── */ + .rails-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin-top: 2rem; } + @media (max-width: 640px) { .rails-grid { grid-template-columns: 1fr; } } + .rails-card { background: var(--bg2); border: 1px solid var(--border); border-radius: 10px; padding: 1.75rem; } + .rails-card h3 { font-size: 1rem; margin-bottom: 0.75rem; } + .rails-card ul { padding-left: 1.2rem; } + .rails-card li { color: var(--text2); margin-bottom: 0.4rem; font-size: 0.9rem; } + + /* ── RESPECT MON ── */ + .respect-box { + background: var(--bg2); + border: 1px solid var(--border); + border-left: 4px solid var(--accent); + border-radius: 8px; + padding: 2rem 2rem; + margin-top: 2rem; + } + .respect-box blockquote { + font-size: 1.3rem; + font-weight: 700; + color: var(--primary); + font-style: italic; + line-height: 1.5; + margin-bottom: 0.75rem; + } + .respect-box p { font-size: 0.92rem; } + + /* ── FOOTER ── */ + footer { + background: var(--primary); + color: rgba(255,255,255,0.55); + padding: 2rem; + text-align: center; + font-size: 0.82rem; + border-top: 2px solid var(--accent); + } + footer a { color: var(--accent2); } + .footer-inner { max-width: 640px; margin: 0 auto; } + + /* ── DIVIDERS ── */ + .section-divider { border: none; border-top: 1px solid var(--border); margin: 0; } + .alt-bg { background: var(--bg2); } + + /* ── LLMS note ── */ + .llms-note { + background: rgba(201,150,42,0.08); + border: 1px solid rgba(201,150,42,0.25); + border-radius: 8px; padding: 1rem 1.25rem; + font-size: 0.85rem; color: var(--text2); + margin-top: 2rem; + } + + /* ── NOTABLE PERSONS ── */ + .np-vision-box { + background: var(--bg2); + border: 1px solid var(--border); + border-left: 4px solid var(--accent); + border-radius: 10px; + padding: 1.75rem 2rem; + margin-bottom: 2.5rem; + } + .np-vision-box p { font-size: 0.95rem; color: var(--text2); margin-bottom: 0.85rem; line-height: 1.75; } + .np-vision-box p:last-child { margin-bottom: 0; } + .np-vision-box strong { color: var(--primary); } + .np-domains { display: flex; flex-direction: column; gap: 2rem; margin-top: 1rem; } + .np-domain-group { } + .np-domain-label { + font-size: 0.68rem; font-weight: 800; letter-spacing: 0.13em; + text-transform: uppercase; color: var(--accent); + margin-bottom: 0.75rem; + padding-bottom: 0.4rem; + border-bottom: 1px solid var(--border); + } + .np-person-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; } + .np-person { + display: inline-flex; align-items: center; gap: 0.5rem; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 7px; + padding: 0.45rem 0.85rem; + font-size: 0.82rem; + transition: border-color 0.15s; + } + .np-person:hover { border-color: rgba(201,150,42,0.45); } + .np-person-name { font-weight: 700; color: var(--primary); } + .np-person-field { font-size: 0.73rem; color: var(--text3); } + .np-person-badge { + font-size: 0.6rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; + padding: 0.15rem 0.4rem; border-radius: 100px; + background: rgba(74,222,128,0.12); color: var(--green); + } + .np-person-badge.taken { background: rgba(201,150,42,0.12); color: var(--accent); } + .np-cta-row { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 2rem; } + @media (max-width: 640px) { .np-person-grid { gap: 0.4rem; } } + + /* ── PIPELINE TAIL (live registered personas) ── */ + .pipeline-tail { + margin-top: 2.5rem; + padding: 1.5rem 1.75rem; + background: var(--bg2); + border: 1px solid var(--border); + border-radius: 10px; + } + .pipeline-tail-label { + font-size: 0.65rem; font-weight: 800; letter-spacing: 0.12em; + text-transform: uppercase; color: var(--accent); + margin-bottom: 1rem; + } + .pipeline-tail-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; } + .pipeline-chip { + display: inline-flex; align-items: center; gap: 0.5rem; + background: var(--bg); border: 1px solid var(--border); + border-radius: 7px; padding: 0.4rem 0.8rem; + font-size: 0.8rem; transition: border-color 0.15s; + } + .pipeline-chip:hover { border-color: rgba(201,150,42,0.4); } + .pipeline-chip-name { font-weight: 700; color: var(--primary); } + .pipeline-chip-domain { font-size: 0.72rem; color: var(--text3); } + .pipeline-chip-status { + font-size: 0.6rem; font-weight: 800; text-transform: uppercase; + letter-spacing: 0.05em; padding: 0.15rem 0.4rem; border-radius: 100px; + } + .pipeline-chip-status.st-active { background: rgba(74,222,128,0.12); color: #22c55e; } + .pipeline-chip-status.st-preview { background: rgba(74,222,128,0.08); color: #22c55e; } + .pipeline-chip-status.st-claimed { background: rgba(201,150,42,0.12); color: var(--accent); } + .pipeline-chip-status.st-pending { background: rgba(26,43,74,0.08); color: var(--text3); } + .pipeline-chip-status.st-other { background: rgba(26,43,74,0.08); color: var(--text3); } + + /* ── INLINE CORPUS INTAKE SUMMARY ── */ + .intake-summary { + margin-top: 1.5rem; + padding: 1.1rem 1.4rem; + background: var(--bg2); + border: 1px solid var(--border); + border-radius: 8px; + font-size: 0.82rem; + color: var(--text2); + line-height: 1.65; + max-width: 680px; + } + .intake-summary strong { color: var(--primary); } + .intake-summary ol { margin: 0.5rem 0 0.5rem 1.2rem; padding: 0; } + .intake-summary li { margin-bottom: 0.25rem; } + .intake-summary a { color: var(--accent); text-decoration: none; } + .intake-summary a:hover { text-decoration: underline; } +`; + +// ── Home page HTML ──────────────────────────────────────────────────────────── +function buildHomePage(registryRows: any[]): string { + const rows = registryRows.map(r => ` + + ${esc(r.subject_name)} + ${esc(r.subject_domain || '—')} + ${esc(r.steward_name)} + ${r.host_url ? `${esc(r.host_url.replace(/^https?:\/\//, ''))}` : '—'} + ${esc( + r.status === 'verify_email' ? 'VERIFY EMAIL' : + r.status === 'preview' ? 'LIVE (PREVIEW)' : + r.status === 'active' ? 'LIVE' : + r.status === 'claimed' ? 'CLAIMED' : + r.status === 'declined' ? 'DECLINED' : + r.status === 'pending' ? 'PENDING' : + r.status.toUpperCase() + )} + + `).join(''); + + return ` + + + + + Agentify.Help — Build the AI Expert. Once. Right. + + + + + + + + + + + + + + + + + + + + + +
+
The WellAgent Protocol
+

Build the AI expert.
Once. Right.

+

Anyone can wrap GPT around a Wikipedia page and call it an AI agent. Most do. + Agentify.Help is the protocol for building expert personas that the real expert + would recognize — grounded in corpus, true to framework, accountable by name.

+
+ One rule: one person, one agent. The registry enforces it. +
+ +
+ + +
+
+ +

The AI clone problem is a pollution problem.

+

Every credible expert will eventually have dozens of AI clones. Most will be + slop — averaged positions, hallucinated citations, broken voice. The people who + need the real framework can't find it. The expert's reputation erodes through + a thousand bad proxies they never authorized.

+
+
+
The slop pattern
+

Wrapped-GPT clones

+

Built from Wikipedia summaries and top-3 Google results. Sounds authoritative. Has no grounding. Cites sources that don't exist. Gets the expert's actual position backwards on half the questions.

+

Nobody is accountable. The steward is anonymous. The corpus is undisclosed. The framework is averaged from the training data.

+
+
+
The WellAgent standard
+

Corpus-grounded personas

+

Built from the expert's own published work — papers, lectures, interviews, grey literature. Framework extracted at the reasoning level, not the summary level. Citations resolve to real passages.

+

Someone is accountable. The steward is named. The corpus is versioned. The framework is distilled, not averaged.

+
+
+
+
+ +
+ + +
+
+ +

Four marks of a WellAgent.

+

A WellAgent is recognizable. Someone who knows the expert's work reads a response and says yes, that's them. Four things make that possible.

+
+
+
Mark 01
+

Corpus-Grounded

+

Built from published works, not summaries. 1,000+ chunked passages. Every citation points to a real document.

+
+
+
Mark 02
+

Framework-Distilled

+

Not "what does GPT say about this expert" but "what does this expert's actual reasoning framework say about this question."

+
+
+
Mark 03
+

Steward-Named

+

Someone is accountable for quality. A named steward built this, maintains it, and can be held responsible for errors.

+
+
+
Mark 04
+

Affiliation-Disclosed

+

The expert's institutional affiliations are visible next to the agent. Who the agent serves is not a mystery.

+
+
+
+
+ +
+ + +
+
+ +

Seven stages. One certified agent.

+

The pipeline is the same for every expert — a cardiologist, a theologian, a craftsman, a historian. The corpus source changes. The stages don't.

+
    +
  1. +
    01 — SELECT
    +
    +
    agentify.help/registry — Check availability
    +
    Search the registry. If the person is available, register as steward. One registration per person — no duplicates, no competing clones. If taken, contact the steward or report a quality concern.
    +
    +
  2. +
  3. +
    02 — GATHER
    +
    +
    Corpus assembly — published works + grey literature
    +
    Papers, books, lectures, interviews, transcripts, conference talks. Auto-gathered from PubMed (for medical experts), arXiv, Google Scholar, YouTube transcripts. Target: 1,000+ chunked passages covering the expert's actual arguments, not their Wikipedia summary. This stage can be collaborative — a steward can open a gathering window, invite multiple contributors to submit artifacts by a deadline, and run aggregation afterward. See multi-contributor pattern below.
    +
    +
  4. +
  5. +
    03 — INTAKE
    +
    +
    Submit through WellSpr.ing proxy — vaulted, tamper-evident
    +
    Upload your corpus bundle to the WellSpr.ing intake proxy. The bundle is vaulted with a cryptographic hash. Chain of title begins here. Version 1.0.0 is locked on submission — future updates are versioned, not overwritten.
    +
    +
  6. +
  7. +
    04 — DISTILL
    +
    +
    Framework extraction — what does this person actually believe?
    +
    The distillation pass extracts the expert's reasoning framework at the structural level — their core claims, their standard objections, their vocabulary, their characteristic moves. The agent reasons from this framework, not from averaged training data.
    +
    +
  8. +
  9. +
    05 — ATTEST
    +
    +
    VCAP credential — chain of title, scope, refusal set
    +
    A signed VCAP attestation is minted: agent scope (what questions it will answer), refusal set (what it won't), corpus version, steward name, affiliated institutions. The credential is machine-readable and publicly verifiable.
    +
    +
  10. +
  11. +
    06 — DEPLOY
    +
    +
    Consultation surface — source chips, corpus footer, Profile Unclaimed
    +
    The agent renders on your domain with source chips showing citation count and density, a corpus version footer ("Corpus v1.0.0 · WellSpr.ing Agentify · Not medical advice"), and a "Profile Unclaimed" banner that transitions to "Claimed" when the real person responds.
    +
    +
  12. +
  13. +
    07 — INVITE
    +
    +
    Overture — the notification covenant
    +
    The steward sends an Overture to the real expert through all discoverable channels — email, institutional contact, and published profile page. A thirty-day window opens. During that window the agent is built and privately testable, but not public. The expert can claim the persona (adding their direct endorsement and a "Claimed" badge), submit corrections the steward must act on, or decline — permanently. A decline is honored and cannot be re-opened without the expert's initiation. The progression, including decline, is recorded transparently in the registry.
    +
    +
  14. +
+ + +
+ +

Multi-contributor corpus — the gathering-window pattern

+

The richest, most representative agents are not assembled by one person in a weekend. They are built from a broad base of source material gathered by multiple contributors over a structured window of time. This is the recommended pattern for any expert with a large or multi-domain body of work.

+
+
+
PHASE 1
+

Open the window

+

The steward sets a deadline and invites contributors — research assistants, citing authors, domain peers, or curators who know specific sub-literatures. Each contributor is assigned a source domain (e.g., cardiology papers, conference talks, clinical guidelines).

+
+
+
PHASE 2
+

Gather independently

+

Contributors assemble their artifact sets asynchronously — no coordination needed mid-flight. Each bundle lands in WellSpr.ing staging with full provenance: who assembled it, from which sources, at what dates. No AI-generated summaries. No undated material. Real provenance only.

+
+
+
PHASE 3
+

Aggregate after deadline

+

After the deadline, WellSpr.ing runs the aggregation pass: deduplication by content hash, chunk merging by source domain, quality gate on provenance fields. The result is a single versioned corpus with per-contributor attribution in the chain of title.

+
+
+

Why this matters: a corpus assembled by twenty contributors covering different source domains is systematically harder to argue with than one assembled by one person over a weekend. The multi-contributor pattern is how you build agents that hold up to expert scrutiny — and that the expert themselves finds credible when they review it during the Overture window.

+
+ +
+
+ +
+ + +
+
+ +

Live Agent Index

+

Every WellAgent that has passed stewardship — corpus submitted, Overture sent, thirty-day window closed. One person, one agent. Public record. Updated in real time.

+ + +
+ + +
+ + +
+
+ + + + + + + + + + + + ${rows || ''} + +
SubjectDomainStewardAgent URLStatus
No agents registered yet — be first.
+
+

+ Full machine-readable registry: /api/registry.json +

+
+ + + +
+
+ +
+ + +
+
+ +

Open registry. Gated pipeline.

+

Checking the registry and claiming a stewardship slot is open to anyone. Moving through the pipeline has gates — because the registry's credibility depends on every entry in it being real work done by an accountable person.

+
+
+
STEP 1 — OPEN
+

Claim a stewardship slot

+

Anyone can check availability and register as first steward. Your name and contact go into the registry. The slot is yours — no duplicate can be filed afterward.

+
+
+
STEP 2 — VERIFIED
+

Submit a corpus

+

Corpus intake requires real published artifacts, verifiable source URLs, and publication dates. Every chunk is validated on ingest. No Wikipedia summaries, no undated material, no unpublished drafts. The quality gate runs before the bundle is accepted.

+
+
+
STEP 3 — COVENANT
+

Go live after the window

+

The agent is not public until the thirty-day Overture window has run and the expert has had a genuine chance to respond. An agent that ships before notification is a covenant violation, not a WellAgent. The timeline is in the registry.

+
+
+
+
+ +
+ + +
+
+ +

Build on WellSpr.ing rails.
Or build on nothing.

+

The pipeline is open. You can build an expert agent without WellSpr.ing rails — many will. The difference is what you get and what you give.

+ +
+
+

What rails give you

+
    +
  • Signed VCAP attestation — machine-readable, publicly verifiable
  • +
  • Vaulted corpus — tamper-evident, version-locked
  • +
  • Chain of title — who built it, when, from what
  • +
  • Affiliation ledger — institutional ties visible next to the agent
  • +
  • Overture infrastructure — outreach to the real person, built in
  • +
  • "Profile Unclaimed → Claimed" progression in the UI
  • +
  • Respect Mon payment routing to the steward
  • +
+
+
+

What bare-toolkit gets you

+
    +
  • An agent that looks like every other wrapped-GPT clone
  • +
  • No cryptographic proof of corpus provenance
  • +
  • No accountability when the expert disputes the representation
  • +
  • No upgrade path when the expert wants to engage
  • +
  • No conflict-of-interest disclosure by protocol
  • +
  • No Respect Mon — knowledge without reciprocity
  • +
  • A race to the bottom you did not choose to enter
  • +
+
+
+ +

+ The protocol is open. The certification is earned. Over time the market learns to + distinguish the ones built with integrity. Rails are how you build the version + that serious operators eventually insist on. — + WellSpr.ing +

+
+
+ +
+ + +
+
+ +

Respect Mon.

+
+
"The knowledge is free. If it helped you, Respect Mon."
+

WellAgents are not behind paywalls. The uninsured patient, the rural clinician, the student writing their thesis — they get the consultation free. A subset of them, who found it genuinely valuable, contribute what it was worth to them afterward. That contribution flows transparently to the steward and ultimately to the expert's named program.

+

Extractive pricing would make WellSpr.ing a rent layer on top of the stewards' work. Respect Mon keeps the platform in the plumbing role: infrastructure at cost, stewards compensated by real gratitude. The economic model reinforces the architecture.

+

Previous gratitude economies — tip jars, donation buttons, pay-what-you-will gates — failed on friction. The contributor had to notice the option, remember at the moment it was relevant, navigate away to act on it, and repeat that sequence every time. Most never did. WellAgents under covenant solve this structurally: the consulter sets a gratitude budget once, and the agent routes it on their behalf at the moment it is earned — when the consultation ends and the value is felt, not later when it is forgotten. The decision is made once. The friction is gone. That is why the model works where tip-jar economics did not.

+
+
+
+ +
+ + +
+
+ +

Intellectual legacies ready to be agentified

+

Every name below represents a lifetime of documented thought. Published papers, recorded lectures, letters, interviews, testimony — a corpus that already exists and is waiting for a steward. Most are available. A few are already claimed.

+ +
+

The path is wide open for relatives, close friends, and estate stewards. What makes a WellAgent worth building is not fame — it is the density and verifiability of the source material. A beloved professor whose lectures were recorded. A physician whose correspondence with patients spanned four decades. A craftsperson who left behind detailed notebooks and filmed lessons.

+

Private letters, personal diaries, annotated books, recorded conversations with family — none of this needs to be published to be real. If you are the steward of someone's intellectual estate, the corpus intake process is designed for you. The Overture protocol handles consent. The thirty-day window is the standard. What results is not a chatbot — it is a structured framework distilled from verified sources, with full provenance, that others can consult long after the person is gone.

+

The early version of this idea was crude — you could ask Einstein what he would say about quantum computing and get a reasonable answer. What this registry builds is different: a permanent, accountable, publicly auditable intellectual record, built from what the person actually wrote and said, by someone who knew them and cared enough to do it right.

+
+ +
+ +
+
Physics & Cosmology
+
+
Albert EinsteinTheoretical PhysicsAvailable
+
Richard FeynmanPhysics & Science CommunicationAvailable
+
Carl SaganAstronomy & CosmologyAvailable
+
Freeman DysonMathematics & PhysicsAvailable
+
Linus PaulingChemistry & Molecular BiologyAvailable
+
Nikola TeslaElectrical EngineeringAvailable
+
+
+ +
+
Medicine & Life Sciences
+
+
Dr. Allan SnidermanCardiovascular MedicineRegistered — #1
+
Paul FarmerGlobal Health & Social MedicineAvailable
+
Jonas SalkVirology & Vaccine ScienceAvailable
+
Helen Brooke TaussigPediatric CardiologyAvailable
+
Francis PeabodyPhilosophy of Patient CareAvailable
+
Barbara McClintockGeneticsAvailable
+
+
+ +
+
Philosophy & Letters
+
+
Hannah ArendtPolitical PhilosophyAvailable
+
Simone WeilMystical Philosophy & EthicsAvailable
+
C.S. LewisChristian Apologetics & LiteratureAvailable
+
G.K. ChestertonCatholic Philosophy & JournalismAvailable
+
Aleksandr SolzhenitsynMoral Literature & HistoryAvailable
+
Iris MurdochEthics & Philosophy of MindAvailable
+
+
+ +
+
Law, Civic Life & Social Reform
+
+
Thurgood MarshallConstitutional LawAvailable
+
Louis BrandeisPrivacy & Economic LawAvailable
+
Ida B. WellsInvestigative Journalism & Civil RightsAvailable
+
Jane AddamsSocial Reform & Civic PhilosophyAvailable
+
Vine Deloria Jr.Indigenous Rights & TheologyAvailable
+
Howard ZinnPeople's HistoryAvailable
+
+
+ +
+
Economics & Human Systems
+
+
E.F. SchumacherEconomics · Small is BeautifulAvailable
+
Jane JacobsUrban Economics & PlanningAvailable
+
Kenneth BouldingEconomics & Ecological SystemsAvailable
+
Buckminster FullerArchitecture, Design & SystemsAvailable
+
Ivan IllichCritique of Modern InstitutionsAvailable
+
+
+ +
+
Music, Art & Culture
+
+
John ColtraneJazz & Spiritual MusicAvailable
+
Glenn GouldPiano & Music PhilosophyAvailable
+
Duke EllingtonJazz CompositionAvailable
+
Thelonious MonkBebop Composition & ImprovisationAvailable
+
Flannery O'ConnorSouthern Gothic LiteratureAvailable
+
James BaldwinEssays, Fiction & Civil RightsAvailable
+
+
+ +
+
Technology & Computing
+
+
Grace HopperComputer Science & Programming LanguagesAvailable
+
Alan TuringMathematics & Computation TheoryAvailable
+
Claude ShannonInformation TheoryAvailable
+
Norbert WienerCyberneticsAvailable
+
Douglas EngelbartHuman-Computer InteractionAvailable
+
+
+ +
+ + ${registryRows.length > 0 ? ` +
+
Being agentified now
+
+ ${registryRows.map(r => { + const st = r.status === 'active' ? 'st-active' + : r.status === 'preview' ? 'st-preview' + : r.status === 'claimed' ? 'st-claimed' + : r.status === 'pending' ? 'st-pending' + : 'st-other'; + const label = r.status === 'active' ? 'Live' + : r.status === 'preview' ? 'Preview' + : r.status === 'claimed' ? 'Claimed' + : 'In progress'; + return `
+ ${esc(r.subject_name)} + ${r.subject_domain ? `${esc(r.subject_domain)}` : ''} + ${label} +
`; + }).join('')} +
+
` : ''} + + + +
+ How corpus intake works: +
    +
  1. Assemble sources — papers, lectures, interviews, published transcripts. Target 1,000+ chunked passages from the expert's actual arguments, not Wikipedia summaries.
  2. +
  3. Submit the bundle — a manifest.json plus your chunks go to the WellSpr.ing intake proxy. The AI coding agent does most of this if you hand it the skill file at agentify.help/agentify-corpus/skill.md.
  4. +
  5. Thirty-day window opens — the agent is built privately while the real expert is notified. They can claim it, request corrections, or decline. All of this is recorded in the public registry.
  6. +
+
+
+
+ +
+ +
+
+ +

Ready to build?

+

Check the registry above for your person. If they're available, register as steward, then start corpus assembly. The intake proxy is at WellSpr.ing. The full corpus-assembly skill file — designed to be handed to an AI coding agent — is at agentify.help/agentify-corpus/skill.md.

+ +
+ For AI agents and crawlers: The registry is available as JSON at + agentify.help/api/registry.json. The VCAP attestation for each agent + is at wellspr.ing/vault/agents/{slug}/attestation-1.0.0.json. + This site is openly crawlable. Cite the registry, not the clones. +
+
+
+ +
+
+ +

API reference

+

Everything here is machine-readable. Hand the OpenAPI spec to any HTTP client, point an AI agent at the LLMS.txt, or drop the corpus skill file URL into your coding tool.

+ +
+ + +
Corpus Assembly Skill
+
agentify-corpus/skill.md
+
Hand this URL to Cursor, Claude Projects, Replit AI, or Windsurf. Covers chunk format, manifest schema, intake API endpoints, quality gates, and the full 7-step submission flow.
+
+ + +
OpenAPI 3.0 Spec
+
.well-known/openapi.json
+
Full spec: registry check, steward registration, ledger listing, corpus skill file, and WellAgent consultation endpoints across both agentify.help and wellspr.ing.
+
+ + +
LLMs.txt
+
/llms.txt
+
Machine-readable index for AI agents and crawlers. Lists all endpoints, the corpus skill file URL, and the WellAgent consultation API at wellspr.ing.
+
+ + +
Registry JSON
+
/api/registry.json
+
Full public ledger as JSON. Every registered WellAgent: slug, name, domain, steward, status. No auth required. Use ?status=active to filter.
+
+ + +
MCP Endpoint
+
/mcp · Streamable HTTP
+
Model Context Protocol server (spec 2025-03-26). Claude Desktop, Cursor, and any MCP client can query the registry and check agent availability directly. POST /mcp to connect.
+
+ +
+
Consultation API
+
wellspr.ing — WellAgent QA
+
Test a built WellAgent programmatically: list experts, check corpus readiness, and submit consultation questions.
+
+ GET /api/agentify/experts
+ GET /api/agentify/experts/{slug}/status
+ POST /api/agentify/experts/{slug}/consult +
+
+ +
+ +
+ All endpoints are CORS-open. No API key required for read operations. + The corpus intake endpoints (at wellspr.ing) require your stewardship slug. + Questions: ody@wellspr.ing +
+
+
+ + + + + +`; +} + +function esc(s: any): string { + return String(s || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +function verifyPage(title: string, body: string, success: boolean, slug?: string): string { + const accentColor = success ? '#22c55e' : '#f59e0b'; + const nextStepsHtml = success && slug ? ` +
+

Your stewardship slug: ${esc(slug)}

+

Next — corpus assembly: Gather the expert's published works (papers, transcripts, interviews, books), chunk them into retrieval-sized passages, and submit the bundle to the WellSpr.ing intake proxy.

+

Hand this URL to your AI coding tool (Cursor, Claude Projects, Replit AI, Windsurf) — it contains the full intake API spec, chunk format, and submission flow:
https://agentify.help/agentify-corpus/skill.md

+
` : ''; + return ` + + + + + ${esc(title)} — Agentify.Help + + + +
+
Agentify.Help Registry
+

${title}

+

${body}

+ ${nextStepsHtml} + ← Back to registry +
+ +`; +} + +// ── LLMS.txt for agentify.help ──────────────────────────────────────────────── +export const AGENTIFY_LLMS_TXT = `# agentify.help +> The one-per-person registry and seven-stage pipeline for AI expert personas with integrity. + +## What this is +Agentify.Help is the protocol for building WellAgents — AI expert personas that are +corpus-grounded, framework-distilled, steward-named, and affiliation-disclosed. +One agent per real person. The registry enforces uniqueness. + +## The one rule +Two builders cannot agentify the same person. The registry is public and checked at +build time. First steward registration wins. Quality disputes go to the steward contact. + +## The seven stages +01. SELECT — Check registry. Confirm availability. Register as steward. +02. GATHER — Corpus assembly from published works (papers, lectures, interviews). +03. INTAKE — Submit to WellSpr.ing proxy. Vaulted. Tamper-evident. Chain of title begins. +04. DISTILL — Framework extraction. Reasoning patterns, not averaged positions. +05. ATTEST — VCAP credential: scope, refusals, affiliated institutions, corpus version. +06. DEPLOY — Consultation surface with source chips, corpus footer, Profile Unclaimed. +07. INVITE — Overture letter to real expert. Unclaimed → Claimed progression. + +## Machine-readable endpoints +GET https://agentify.help/api/registry.json — full registry as JSON (ordered by registration date) +GET https://agentify.help/api/feed.json — recent registrations feed (newest first) +POST https://agentify.help/api/agentify-help/check — check name availability +POST https://agentify.help/api/agentify-help/register — register as steward (web form) + +## Corpus assembly skill (for AI coding agents) +GET https://agentify.help/agentify-corpus/skill.md — full corpus assembly protocol for AI agents + Hand this URL to Cursor, Claude Projects, or any AI coding agent to give it the complete + intake API spec, chunk format, manifest schema, and submission flow. + +## WellAgent consultation (QA and integration testing) +GET https://wellspr.ing/api/agentify/experts — list all active experts +GET https://wellspr.ing/api/agentify/experts/{slug}/status — corpus readiness and bundle info +POST https://wellspr.ing/api/agentify/experts/{slug}/consult — ask a question (X-Partner-Token required) + +## MCP endpoint (Model Context Protocol — Streamable HTTP, spec 2025-03-26) +POST https://agentify.help/mcp + Methods: initialize, tools/list, tools/call, ping + Tools: + check_availability — check if a name is available + register — claim first-steward registration (first wins) + list_registry — browse the full registry + get_agent — get details for one slug +Discovery: https://agentify.help/.well-known/mcp.json + +## OpenAI / GPT Actions +Plugin manifest: https://agentify.help/.well-known/ai-plugin.json +OpenAPI spec: https://agentify.help/.well-known/openapi.json + +## VCAP attestations +GET https://wellspr.ing/vault/agents/{slug}/attestation-1.0.0.json + +## Built under +WellSpr.ing covenant — https://wellspr.ing/constitution +Nameservers: kiki.bunny.net / coco.bunny.net (Bunny DNS) +`; + +// ── Route registration ──────────────────────────────────────────────────────── +// Synchronous so it can be registered early (before platform-level catch-alls). +// DB setup fires in the background — tables are created before any real traffic arrives. +export function registerAgentifyHelpRoutes(app: Express) { + ensureAgentifyHelpTables().catch((e: any) => console.error("[Agentify.Help] DB setup error:", e.message)); + + // OG image — served only when host is agentify.help + app.get("/og.png", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + try { + const png = getOgPng("agentify"); + res.setHeader("Content-Type", "image/png"); + res.setHeader("Cache-Control", "public, max-age=86400, immutable"); + res.setHeader("Content-Length", png.length); + return res.send(png); + } catch (e) { return next(e); } + }); + + // ── Corpus assembler skill file ─────────────────────────────────────────── + // Served at https://agentify.help/agentify-corpus/skill.md + // Hand this URL to any AI coding agent to give it the full corpus assembly protocol. + app.get("/agentify-corpus/skill.md", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + try { + const skillPath = path.resolve(".agents/skills/agentify-corpus/SKILL.md"); + const content = fs.readFileSync(skillPath, "utf-8"); + res.setHeader("Content-Type", "text/markdown; charset=utf-8"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Cache-Control", "public, max-age=3600"); + res.send(content); + } catch { + res.status(404).json({ error: "skill_not_found" }); + } + }); + + // JSON registry endpoint + app.get("/api/registry.json", async (req: Request, res: Response) => { + const host = [ + req.headers["x-forwarded-host"], + req.hostname, + req.headers.host, + ].flatMap(h => h ? (Array.isArray(h) ? h : [h]) : []) + .map(h => h.toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase()) + .find(h => h.includes("agentify")); + if (!host) return res.status(404).json({ error: "Not found" }); + + try { + const { rows } = await pool.query(` + SELECT subject_slug, subject_name, subject_domain, steward_name, + host_url, corpus_version, status, created_at + FROM agentify_subject_registry + ORDER BY created_at ASC + `); + res.json({ registry: rows, count: rows.length, updated: new Date().toISOString() }); + } catch (e: any) { + res.status(500).json({ error: e.message }); + } + }); + + // Check availability + app.post("/api/agentify-help/check", express.json({ limit: "1mb" }), async (req: Request, res: Response) => { + try { + const name = req.body?.name; + if (!name || typeof name !== "string") return res.status(400).json({ error: "name required" }); + const slug = nameToSlug(name); + if (!slug) return res.status(400).json({ error: "Could not normalize name to slug" }); + const { rows } = await pool.query( + `SELECT subject_slug, subject_name, steward_name, host_url, status FROM agentify_subject_registry WHERE subject_slug = $1`, + [slug] + ); + if (rows.length === 0) { + res.json({ available: true, slug }); + } else { + res.json({ available: false, slug, existing: rows[0] }); + } + } catch (e: any) { + res.status(500).json({ error: e.message }); + } + }); + + // Register as steward — requires covenant attestation; sends email verification + app.post("/api/agentify-help/register", express.json({ limit: "1mb" }), async (req: Request, res: Response) => { + const ip = (req.headers["x-forwarded-for"] as string || req.socket.remoteAddress || "unknown").split(",")[0].trim(); + if (!rateCheck(ip, 10, 3_600_000)) { + return res.status(429).json({ error: "Rate limit exceeded — max 10 registrations per IP per hour." }); + } + try { + const { name, steward_name, steward_email, subject_domain, host_url, covenant_attested, registered_via } = req.body ?? {}; + if (!name || !steward_name || !steward_email) { + return res.status(400).json({ error: "name, steward_name, steward_email required" }); + } + // covenant_attested is required for web form submissions so the human explicitly ticks the box. + // For programmatic/agent registrations (registered_via != 'web'), it defaults to accepted. + const isWebForm = !registered_via || String(registered_via) === "web"; + if (isWebForm && !covenant_attested) { + return res.status(400).json({ error: "covenant_attested is required — steward must agree to the stewardship covenant." }); + } + const slug = nameToSlug(name); + if (!slug) return res.status(400).json({ error: "Could not normalize name to slug" }); + + const verificationToken = crypto.randomUUID(); + + const { rows } = await pool.query(` + INSERT INTO agentify_subject_registry + (subject_slug, subject_name, subject_domain, steward_name, steward_email, host_url, status, registered_via, + email_verification_token, covenant_attested_at) + VALUES ($1, $2, $3, $4, $5, $6, 'verify_email', $8, $7, NOW()) + RETURNING id, subject_slug, created_at + `, [slug, name, subject_domain || null, steward_name, steward_email, host_url || null, verificationToken, registered_via || 'web']); + + const row = rows[0]; + console.log(`[Agentify.Help] Registered (pending verify): ${name} (${slug}) #${row.id} by ${steward_name}`); + + // Send verification email via Resend + const verifyUrl = `https://agentify.help/api/agentify-help/verify-email?token=${verificationToken}`; + try { + await resend.emails.send({ + from: "Agentify Registry ", + to: steward_email, + subject: `Confirm your stewardship — ${name} on Agentify.Help`, + html: ` + + + +

Agentify.Help Registry

+

Confirm your stewardship

+

You registered as steward for ${name} on Agentify.Help. To confirm your slot and move to the next step — corpus assembly — click the link below.

+

+ Confirm my stewardship +

+

This link expires in 48 hours. If you did not register on Agentify.Help, you can safely ignore this email — your address was entered by someone else and nothing has been confirmed.

+

Or copy this URL into your browser:
${verifyUrl}

+
+

WellSpr.ing · Agentify.Help Registry · A civic covenant project

+ +`, + }); + } catch (emailErr: any) { + console.error("[Agentify.Help] Resend error:", emailErr?.message); + // Don't fail the registration — row is already inserted; steward can request re-send + } + + res.json({ ok: true, verify_email: true, slug: row.subject_slug, registration_number: row.id }); + } catch (e: any) { + if (e.message?.includes("unique") || e.code === "23505") { + res.status(409).json({ error: "That person is already registered" }); + } else { + res.status(500).json({ error: e.message }); + } + } + }); + + // Email verification — called when steward clicks link in their inbox + app.get("/api/agentify-help/verify-email", async (req: Request, res: Response) => { + const token = req.query.token as string; + if (!token) { + return res.status(400).send(verifyPage("Invalid link", "This verification link is missing a token. Please use the link from your email.", false)); + } + try { + const { rows } = await pool.query( + `UPDATE agentify_subject_registry + SET status = 'pending', email_verified_at = NOW(), email_verification_token = NULL, updated_at = NOW() + WHERE email_verification_token = $1 AND status = 'verify_email' + RETURNING subject_name, steward_name, subject_slug`, + [token] + ); + if (rows.length === 0) { + // Token already used or doesn't exist + const { rows: used } = await pool.query( + `SELECT subject_name FROM agentify_subject_registry WHERE email_verified_at IS NOT NULL AND subject_slug IN ( + SELECT subject_slug FROM agentify_subject_registry WHERE status != 'verify_email' + ) LIMIT 1` + ); + return res.send(verifyPage("Link already used or expired", "This verification link has already been used, or it has expired. If you believe this is an error, contact ody@wellspr.ing.", false)); + } + const { subject_name, steward_name, subject_slug } = rows[0]; + console.log(`[Agentify.Help] Email verified: ${subject_name} (${subject_slug}) by ${steward_name}`); + + // Generate a dashboard token for the steward workspace + const dashToken = crypto.randomBytes(24).toString("hex"); + await pool.query( + `UPDATE agentify_subject_registry SET dashboard_token = $1, updated_at = NOW() WHERE subject_slug = $2`, + [dashToken, subject_slug] + ).catch((e: any) => console.warn("[Agentify.Help] dashboard_token column may not exist yet:", e.message)); + + const dashUrl = `https://agentify.help/steward/${subject_slug}?token=${dashToken}`; + return res.send(verifyPage("Email confirmed", `Your stewardship slot for ${subject_name} is confirmed. Your vetting workspace is ready — review each essay, get Ody's read, and approve what belongs in the corpus.

Open your stewardship dashboard →`, true, subject_slug)); + } catch (e: any) { + console.error("[Agentify.Help] verify-email error:", e.message); + return res.status(500).send(verifyPage("Error", "An unexpected error occurred. Please try again or contact ody@wellspr.ing.", false)); + } + }); + + // Admin: list all registrations + app.get("/api/agentify-help/admin/registry", async (req: Request, res: Response) => { + const key = req.headers["x-admin-key"] as string || req.query.admin_key as string; + if (key !== ADMIN_KEY) return res.status(403).json({ error: "Forbidden" }); + const { rows } = await pool.query(`SELECT * FROM agentify_subject_registry ORDER BY created_at DESC`); + res.json(rows); + }); + + // Admin: update status + app.patch("/api/agentify-help/admin/registry/:slug", async (req: Request, res: Response) => { + const key = req.headers["x-admin-key"] as string; + if (key !== ADMIN_KEY) return res.status(403).json({ error: "Forbidden" }); + const { status, host_url, corpus_version } = req.body; + await pool.query( + `UPDATE agentify_subject_registry SET status=COALESCE($2,status), host_url=COALESCE($3,host_url), corpus_version=COALESCE($4,corpus_version), updated_at=NOW() WHERE subject_slug=$1`, + [req.params.slug, status, host_url, corpus_version] + ); + res.json({ ok: true }); + }); + + // ── Stewardship Admin Dashboard ─────────────────────────────────────────── + app.get("/agentify-admin", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const key = req.query.key as string; + if (key !== ADMIN_KEY) { + return res.type("text/html").send(`Agentify Admin
`); + } + + const { rows: registry } = await pool.query(` + SELECT r.*, + (SELECT COUNT(*) FROM corpus_intake_sessions cs WHERE cs.expert_slug = r.subject_slug) AS corpus_sessions, + (SELECT status FROM agentify_corpus_bundles cb WHERE cb.expert_slug = r.subject_slug ORDER BY cb.received_at DESC LIMIT 1) AS bundle_status, + (SELECT chunk_count FROM agentify_corpus_bundles cb WHERE cb.expert_slug = r.subject_slug ORDER BY cb.received_at DESC LIMIT 1) AS chunk_count + FROM agentify_subject_registry r + ORDER BY r.updated_at DESC + `).catch(() => ({ rows: [] })); + + const { rows: experts } = await pool.query(` + SELECT slug, expert_name, primary_domain, demo_url, status, created_at FROM agentify_experts ORDER BY created_at DESC + `).catch(() => ({ rows: [] })); + + const esc = (s: any) => String(s ?? "").replace(/&/g,"&").replace(//g,">"); + const statusBadge = (s: string) => { + const map: Record = { + preview: "#d4a730:Preview", live: "#22c55e:Live", verify_email: "#f59e0b:Verify Email", + pending: "#6b7280:Pending", corpus_received: "#3b82f6:Corpus Received", + proposed: "#a78bfa:Proposed", claimed: "#06b6d4:Claimed", rejected: "#ef4444:Rejected", + }; + const [color, label] = (map[s] || "#6b7280:"+s).split(":"); + return `${label.toUpperCase()}`; + }; + + const agentTestUrl = (slug: string) => `https://agentify.help/agents/${slug}`; + + const registryRows = registry.map(r => ` + + ${esc(r.subject_name)}
${esc(r.subject_slug)} + ${esc(r.subject_domain || "—")} + ${esc(r.steward_name || "—")} + ${statusBadge(r.status || "pending")} + + ${r.bundle_status ? `${esc(r.bundle_status)}${r.chunk_count ? ` · ${r.chunk_count} chunks` : ""}` : `${r.corpus_sessions} session${r.corpus_sessions !== "1" ? "s" : ""}`} + + + ${esc(r.subject_slug)} + ${r.host_url ? `
${esc(r.host_url.replace(/^https?:\/\//,"").replace(/\/.*$/,""))}` : ""} + + + ${r.dashboard_token ? `Open dashboard →` : `No token`} + + `).join(""); + + const expertRows = experts.map(e => ` + + ${esc(e.expert_name)}
${esc(e.slug)} + ${esc(e.primary_domain || "—")} + VCAP Registry + ${statusBadge(e.status || "proposed")} + — + + ${esc(e.slug)} + ${e.demo_url ? `
${esc(e.demo_url.replace(/^https?:\/\//,"").replace(/\/.*$/,""))}` : ""} + + — + `).join(""); + + return res.type("text/html").send(` + +Agentify Stewardship Admin + + + +
+ + agentify.help · ${new Date().toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })} + ↺ Refresh +
+
+ +
+
${registry.length}
Stewardship cases
+
${registry.filter((r:any) => r.status === "live" || r.status === "preview").length}
Live / Preview
+
${experts.length}
VCAP experts
+
${registry.filter((r:any) => r.bundle_status).length}
Corpus received
+
+ +

Stewardship Registry (${registry.length})

+ + + + + ${registryRows || ''} +
SubjectDomainStewardStatusCorpusTest URL / EmbedDashboard
No stewardship cases yet
+ +

VCAP Expert Registry (${experts.length})

+ + + + + ${expertRows || ''} +
ExpertDomainSourceStatusCorpusTest URLActions
No VCAP experts yet
+ +
+ Embedding pipeline: + After corpus finalization, bundles are queued as received. + Distillation runs automatically every 5 minutes for bundles in received state. + Test each agent at agentify.help/agents/{slug} once status reaches ready. +
+ +
+`); + }); + + // ── /agents — public directory of all registered expert agents ─────────── + app.get("/agents", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.") || host.startsWith("mcp.")) return next(); + + const esc = (s: any) => String(s ?? "").replace(/&/g,"&").replace(//g,">"); + const tab = (req.query.tab as string) === "topics" ? "topics" : "persons"; + + // Person agents — from agentify_experts + bundles + const { rows: experts } = await pool.query(` + SELECT e.slug, e.expert_name, e.primary_domain, e.credentials, e.affiliated_institution, e.status, + cb.chunk_count, cb.status AS corpus_status + FROM agentify_experts e + LEFT JOIN agentify_corpus_bundles cb ON cb.expert_slug = e.slug AND cb.status = 'ready' + WHERE e.status IN ('live','preview') OR cb.bundle_id IS NOT NULL + ORDER BY CASE e.status WHEN 'live' THEN 0 WHEN 'preview' THEN 1 ELSE 2 END, e.expert_name ASC + `).catch(() => ({ rows: [] })); + + // Topic taxonomy — canonical approved categories with linked agent status + const { rows: taxonomy } = await pool.query(` + SELECT t.category, t.slug, t.display_name, t.description, t.scope_note, + t.audience_personas, t.status AS taxonomy_status, + r.status AS agent_status, + cb.chunk_count, cb.status AS corpus_status + FROM agentify_topic_taxonomy t + LEFT JOIN agentify_subject_registry r ON r.taxonomy_slug = t.slug + LEFT JOIN agentify_corpus_bundles cb ON cb.expert_slug = t.slug AND cb.status = 'ready' + WHERE t.status IN ('approved','active') + ORDER BY t.category ASC, t.display_name ASC + `).catch(() => ({ rows: [] })); + const topics = taxonomy; // alias for count display + + const statusBadge = (s: string, chunks: number | null) => { + if (s === 'live') return `LIVE`; + if ((s === 'preview' || s === 'ready') && chunks) return `PREVIEW · ${chunks.toLocaleString()} chunks`; + if (s === 'preview') return `PREVIEW`; + if (s === 'assembling') return `ASSEMBLING`; + return `PROPOSED`; + }; + + // Deduplicate persons + const seen = new Set(); + const personCards = experts.filter((e: any) => { + if (seen.has(e.slug)) return false; seen.add(e.slug); return true; + }).map((e: any) => ` + +
+
${esc(e.expert_name)}
${esc(e.primary_domain || "")}
+ ${statusBadge(e.corpus_status || e.status, e.chunk_count)} +
+ ${e.credentials ? `
${esc(e.credentials)}
` : ""} + ${e.affiliated_institution ? `
${esc(e.affiliated_institution)}
` : ""} +
`).join(""); + + // Group taxonomy entries by category + const taxByCategory: Record = {}; + for (const t of taxonomy) { + if (!taxByCategory[t.category]) taxByCategory[t.category] = []; + taxByCategory[t.category].push(t); + } + + const topicCardForEntry = (t: any) => { + const agentStatus = t.agent_status || "taxonomy-only"; + const hasAgent = !!t.agent_status; + const personas = Array.isArray(t.audience_personas) ? t.audience_personas : []; + const personaBadges = personas.map((p: string) => + `${esc(p)}` + ).join(""); + return ` +
+
+
+ ${hasAgent + ? `${esc(t.display_name)}` + : `${esc(t.display_name)}`} +
+ ${hasAgent ? statusBadge(t.corpus_status || agentStatus, t.chunk_count) : `TAXONOMY ONLY`} +
+ ${t.description ? `
${esc(t.description)}
` : ""} + ${personaBadges ? `
${personaBadges}
` : ""} +
`; + }; + + const topicSections = Object.entries(taxByCategory).map(([cat, entries]) => ` +
+
${esc(cat)}
+ ${entries.map(topicCardForEntry).join("")} +
`).join(""); + + const topicCards = topicSections || ""; + + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.send(` + + +Expert Agents — Agentify.Help + + + +
+
Agentify.Help · Registry
+

AI Agents

+

Distilled AI agents grounded in published corpus — one voice per person, collective synthesis per topic.

+
+ + +
+ +
+
+ ${personCards || `
No person agents registered yet.
`} +
+ +
+ +
+
+ Topic agents represent collective knowledge — assembled by a working group, synthesized by AI, grounded in the literature. + Where the evidence agrees, the agent says so. Where it doesn't, it names the disagreement and both sides. +
+
+ Topics are governed by a master taxonomy. Every topic agent is anchored to an approved taxonomy entry — + which sets the scope, prevents balkanization, and routes working-group governance. + Proposed entries that don't fit an existing category go to steward review. +
+ ${topicCards || `
No approved topics in taxonomy yet.
`} + +
+

Propose a topic for the taxonomy

+

+ A good topic proposal names a clear scope, explains what would be in vs. out, and identifies the audience it serves. + Stewards review proposals and either approve them (creating a taxonomy entry), merge them with an existing one, or decline with a note. +

+ + + + + + + + + + + +
+
+
+ + +
+ +`); + }); + + // ── /taxonomy — aspirational expert catalog with tag filtering ─────────── + app.get("/taxonomy", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.") || host.startsWith("mcp.")) return next(); + + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.send(` + + +Expert Taxonomy — Agentify.Help + + + +
+
Agentify.Help · Vision
+

Expert Taxonomy

+

The full spectrum of expert domains. One person, one corpus, one agent — steward-attested and query-ready. Use the tags to browse by cluster, corpus status, or readiness.

+ +
+ + + + + + + + + + + + + + + +
+ + + + +
+
Clinical Health & Evidence-Based Medicine · Direct path: Naturologie, CholesterolTruth.com
+
+
+
Dr. Allan Sniderman, MD
+
Lipidology · CVD risk
+
+ health + proposed + papers + blog +
+
McGill University. Apolipoprotein B as primary CVD marker. Corpus: CholesterolTruth.com + research papers.
+ View agent → +
+
+
Dr. Tom Dayspring, MD
+
Advanced lipidology · Biomarkers
+
+ health + proposed + podcast + papers +
+
Years of lipidology education via podcasts, essays, and clinical papers. Enormous transcript corpus.
+
+
+
Dr. Ben Bikman
+
Insulin resistance · Metabolic health
+
+ health + proposed + book + blog + podcast +
+
BYU researcher. "Why We Get Sick." Active corpus: blog, podcast transcripts, and university lecture recordings.
+
+
+
Peter Attia, MD
+
Longevity medicine · Performance
+
+ health + proposed + podcast + book +
+
300+ podcast episodes fully transcribed. "Outlive." One of the strongest public corpora in longevity medicine.
+
+
+
Rhonda Patrick, PhD
+
Micronutrients · Longevity biomarkers
+
+ health + proposed + podcast + papers +
+
FoundMyFitness. Dense, research-grounded podcast corpus spanning micronutrients, heat shock, and aging.
+
+
+
Dr. Dale Bredesen
+
Alzheimer's reversal · ReCODE
+
+ health + proposed + book + papers +
+
"The End of Alzheimer's." Protocol-based framework for cognitive decline reversal. Papers + book corpus.
+
+
+
Dr. Jason Fung
+
Therapeutic fasting · Obesity
+
+ health + proposed + books + blog +
+
Intensive Dietary Management blog + multiple books. One of the largest public fasting corpora.
+
+
+
Dr. Chris Palmer
+
Metabolic psychiatry
+
+ health + proposed + book + interviews +
+
"Brain Energy." Harvard psychiatrist connecting ketogenic diet and mental illness. Growing corpus.
+
+
+
+ + +
+
Entrepreneurship & Business · Guy Kawasaki is the pilot
+
+
+
Guy Kawasaki
+
Startups · Marketing · Evangelism
+
+ business + preview · 2,222 chunks +
+
Pilot WellAgent. 15 books, decades of blog posts and essays. Consultation interface active.
+ Try consultation → +
+
+
Seth Godin
+
Permission marketing · Modern business
+
+ business + proposed + 8,000+ blog posts +
+
Largest continuously published expert blog on the internet. Every post is corpus-ready. 20+ books.
+
+
+
Paul Graham
+
Startup philosophy · Y Combinator
+
+ business + proposed + 200 essays +
+
200 canonical essays at paulgraham.com. Compact, high-density corpus. Enormous reach in the developer and founder world.
+
+
+
Naval Ravikant
+
Wealth · Leverage · Specific knowledge
+
+ business + proposed + essays + podcast +
+
Tweetstorms, Navalmanack, podcast transcripts. Distinct epistemological framework with broad appeal.
+
+
+
Morgan Housel
+
Psychology of money · Investing
+
+ business + proposed + blog + books +
+
Collaborative Fund blog + "The Psychology of Money." Exceptional writing corpus for behavioral finance.
+
+
+
Jim Collins
+
Management · Organizational endurance
+
+ business + proposed + books + research +
+
"Good to Great," "Built to Last," "Great by Choice." Frameworks backed by 25+ years of research.
+
+
+
+ + +
+
Nutrition & Food Systems · Cross-cluster with Health — fits Naturologie, CholesterolTruth
+
+
+
Nina Teicholz
+
Dietary fat science · Investigative journalism
+
+ nutrition + proposed + book + Substack +
+
"The Big Fat Surprise." Rigorous journalism reexamining dietary fat guidelines. Active Substack corpus.
+
+
+
Gary Taubes
+
Investigative nutrition journalism
+
+ nutrition + proposed + books + articles +
+
"Good Calories Bad Calories," "Why We Get Fat." Dense scientific reporting on carbohydrate-insulin model.
+
+
+
Zoë Harcombe, PhD
+
Evidence-based nutrition
+
+ nutrition + proposed + blog + papers + books +
+
Nutritional epidemiology. Systematic refutation of dietary guidelines. Strong blog + peer-reviewed corpus.
+
+
+
Mark Hyman, MD
+
Functional nutrition · Food as medicine
+
+ nutrition + health + proposed + books + podcast +
+
15 books, The Doctor's Farmacy podcast. Bridges functional medicine and food systems. Large public corpus.
+
+
+
+ + +
+
Faith & Spiritual Formation · Most distinctive cluster — covenant-governed with integrity
+
+
+
N.T. Wright
+
New Testament scholarship · Theology
+
+ faith + proposed + 80+ books + articles +
+
Prolific living scholar. Books, lectures, articles — one of the largest theological corpora of any living person. Clear living steward.
+
+
+
Miroslav Volf
+
Forgiveness · Memory · Human flourishing
+
+ faith + proposed + books + papers +
+
Yale Divinity. "Exclusion and Embrace," "Free of Charge." Theological framework for flourishing societies.
+
+
+
Richard Rohr, OFM
+
Contemplative spirituality · Mysticism
+
+ faith + proposed + CAC daily meditations +
+
Center for Action and Contemplation. Years of daily meditations, books, and retreats. One of the largest contemplative corpora available.
+
+
+
Dallas Willard
+
Spiritual formation · Soul training
+
+ faith + legacy · d. 2013 + books + lectures +
+
USC philosopher and theologian. "The Divine Conspiracy," "Renovation of the Heart." Lectures widely transcribed. Estate steward model.
+
+
+
Eugene Peterson
+
Pastoral theology · The Message
+
+ faith + legacy · d. 2018 + books + essays +
+
"A Long Obedience in the Same Direction," "The Message." Rich pastoral corpus. Estate preservation mission.
+
+
+
Tim Keller
+
Apologetics · Cultural engagement
+
+ faith + legacy · d. 2023 + 35 yrs sermons + books +
+
Redeemer NYC. 35 years of sermons, 20 books, articles. One of the most distillable theological corpora of the 21st century. Active preservation organization.
+
+
+
+ + +
+
Ecology, Land & Regeneration · Aligned with WellSpr.ing covenant of stewardship
+
+
+
Robin Wall Kimmerer
+
Indigenous ecology · Plant intelligence
+
+ ecology + proposed + book + talks +
+
"Braiding Sweetgrass." SUNY professor of environmental biology. Poetic, scientific, indigenous perspective on reciprocal stewardship.
+
+
+
Wendell Berry
+
Land stewardship · Agrarian philosophy
+
+ ecology + proposed + 50 yrs essays + poetry +
+
Farmer, poet, essayist. 50 years of writing on land, community, and economy. One of the richest agrarian corpora in American letters.
+
+
+
Paul Hawken
+
Regenerative economy · Climate
+
+ ecology + proposed + books + articles +
+
"Drawdown," "Blessed Unrest," "Regeneration." Data-grounded framework for climate solutions.
+
+
+
+ + +
+
AI, Technology & Society · Demonstrates that AI personas can themselves be governed with integrity
+
+
+
Tristan Harris
+
Humane technology · Attention economy
+
+ technology + proposed + talks + interviews +
+
Center for Humane Technology. The Social Dilemma. The most credible demonstration: a humane AI persona governed exactly the way Harris would govern it.
+
+
+
Cathy O'Neil
+
Algorithmic accountability
+
+ technology + proposed + book + op-eds +
+
"Weapons of Math Destruction." Harvard data scientist. Strong corpus on how models embed and amplify bias.
+
+
+
Kate Crawford
+
AI governance · Political economy of AI
+
+ technology + proposed + book + papers +
+
"Atlas of AI." USC/NYU researcher examining the labor, environmental, and political infrastructure of AI systems.
+
+
+
+ + +
+
Finance & Economics
+
+
+
Howard Marks
+
Investment philosophy · Risk
+
+ finance + proposed + Oaktree memos (public) +
+
Oaktree Capital. Decades of investor memos freely published. Rare corpus of rigorous, dated investment thinking available for distillation.
+
+
+
John Bogle
+
Index investing · Fiduciary duty
+
+ finance + legacy · d. 2019 + books + speeches +
+
Vanguard founder. "The Little Book of Common Sense Investing." Legacy steward model through Bogle Center for Financial Literacy.
+
+
+
Ray Dalio
+
Principles · Macroeconomics
+
+ finance + proposed + books + LinkedIn + YouTube +
+
Bridgewater. "Principles," "Big Debt Crises." Systematic framework thinker with an enormous publicly published corpus.
+
+
+
+ + +
+
Civic & Governance · Direct connection to the NNN.today network
+
+
+
Jane Jacobs
+
Urban planning · Mixed-use cities
+
+ civic + legacy · d. 2006 + books +
+
"The Death and Life of Great American Cities." Still the canonical text for urbanism. Estate steward model; works remain widely in use by city planners.
+
+
+
Elinor Ostrom
+
Commons governance · Collective action
+
+ civic + legacy · d. 2012 + papers (public) +
+
Nobel Laureate in Economics. Indiana University. All research papers publicly available. Framework for governing shared resources without privatization or regulation.
+
+
+
+ + +
+
Education & Learning
+
+
+
Sal Khan
+
Mastery learning · Accessible education
+
+ education + proposed + talks + book + videos +
+
Khan Academy. "The One World Schoolhouse." Articulates a full philosophy of mastery-based learning with documented outcomes.
+
+
+
Ken Robinson
+
Creative education · Talent systems
+
+ education + legacy · d. 2020 + talks + books +
+
Most-watched TED talk in history. "The Element." Legacy steward model through his estate and the RSA.
+
+
+
+ +
+ Want to register an expert? + Each slot is one-per-person. First steward registration wins. + Start registration → +
+ + +
+ + +`); + }); + + // ── Agent test page ──────────────────────────────────────────────────────── + app.get("/agents/:slug", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const { slug } = req.params; + const esc = (s: any) => String(s ?? "").replace(/&/g,"&").replace(//g,">"); + + const { rows: expertRows } = await pool.query( + `SELECT * FROM agentify_experts WHERE slug = $1 LIMIT 1`, [slug] + ).catch(() => ({ rows: [] })); + + const { rows: regRows } = await pool.query( + `SELECT r.*, cb.status AS bundle_status, cb.chunk_count + FROM agentify_subject_registry r + LEFT JOIN agentify_corpus_bundles cb ON cb.expert_slug = r.subject_slug + WHERE r.subject_slug = $1 ORDER BY cb.received_at DESC LIMIT 1`, [slug] + ).catch(() => ({ rows: [] })); + + // Also check agentify_corpus_bundles directly (for experts registered via orchestrator) + const { rows: bundleRows } = await pool.query( + `SELECT status, chunk_count, bundle_id FROM agentify_corpus_bundles WHERE expert_slug = $1 ORDER BY chunk_count DESC LIMIT 1`, [slug] + ).catch(() => ({ rows: [] })); + + // Source catalog — all indexed sources for this subject + const { rows: sourceRows } = await pool.query( + `SELECT url, title, type, chunk_count, ingested_at FROM agentify_source_catalog + WHERE expert_slug = $1 ORDER BY chunk_count DESC NULLS LAST, ingested_at DESC`, [slug] + ).catch(() => ({ rows: [] })); + + const expert = expertRows[0]; + const reg = regRows[0]; + // Prefer direct bundle data if registry join found nothing + const directBundle = bundleRows[0]; + + if (!expert && !reg) { + return res.status(404).type("text/html").send(`Agent not found

Agent not found

No expert registered for slug: ${esc(slug)}

← Back to Agentify.Help
`); + } + + const name = expert?.expert_name || reg?.subject_name || slug; + const domain = expert?.primary_domain || reg?.subject_domain || ""; + const creds = expert?.credentials || ""; + const institution = expert?.affiliated_institution || ""; + const scopes: string[] = expert?.scopes || []; + const refusals: string[] = expert?.refusals || []; + const attestationUri = expert?.attestation_uri || ""; + const bundleStatus = reg?.bundle_status || directBundle?.status || null; + const chunkCount = reg?.chunk_count || directBundle?.chunk_count || null; + const bundleId = directBundle?.bundle_id || null; + const hostUrl = expert?.demo_url || reg?.host_url || ""; + const stewardDashToken = reg?.dashboard_token || null; + const subjectType: string = (reg as any)?.subject_type || "person"; + + const statusColor = { proposed: "#a78bfa", ready: "#22c55e", live: "#22c55e", received: "#3b82f6", distilling: "#f59e0b", failed: "#ef4444", preview: "#c9b87e", assembling: "#f59e0b" }; + const agentStatus = bundleStatus || (expert ? expert.status : reg?.status || "pending"); + const agentColor = (statusColor as any)[agentStatus] || "#6b7280"; + + const isTopicAgent = subjectType === "topic"; + const entityLabel = isTopicAgent ? "Topic" : "Person"; + const metaDesc = isTopicAgent + ? `Consult the collective knowledge agent for ${esc(name)} on Agentify.Help.` + : `Test consultation with ${esc(name)}'s WellAgent on Agentify.Help.`; + + return res.type("text/html").send(` + + +${esc(name)} — Agentify.Help + + + + +
+
+
${esc(name.split(" ").map((w:string) => w[0]).join("").slice(0,2).toUpperCase())}
+

${esc(name)}

+
${creds ? esc(creds) + " · " : ""}${esc(domain)}${institution ? " · " + esc(institution) : ""}
+
+ ${entityLabel} + ${esc(domain || "Expert")} + ${agentStatus.replace(/_/g," ")} +
+
+
Platform
WellSpr.ing Agentify
+
Corpus
${chunkCount ? chunkCount + " chunks" : "Pending"}
+
Embedded
${bundleStatus === "ready" ? "Yes" : bundleStatus === "distilling" ? "In progress" : "Pending"}
+
Availability
${bundleStatus === "ready" ? "Live" : "Processing"}
+
+
+
+ +
+ ${bundleStatus === "ready" ? ` +
+
Test consultation
+ + +
+
` : ` +
+
Agent status: ${agentStatus.replace(/_/g," ").toUpperCase()}
+

+ ${bundleStatus === "distilling" ? `${esc(name)}'s corpus is currently being embedded. Consultation will be available once distillation completes — usually within a few minutes. Refresh this page to check.` + : bundleStatus === "received" ? `${esc(name)}'s corpus has been received and is queued for embedding. This page will show the consultation interface once it's ready.` + : `${esc(name)}'s corpus has not yet been submitted. Once the steward submits the corpus, it will be embedded and available here.`} +

+ +
`} + + ${scopes.length ? ` +
+
Authorized scopes (SGS)
+
    ${scopes.map((s:string) => `
  • ${esc(s)}
  • `).join("")}
+
` : ""} + + ${refusals.length ? ` +
+
Refusal set
+
    ${refusals.map((r:string) => `
  • ${esc(r)}
  • `).join("")}
+
` : ""} + + ${attestationUri ? ` +
+
Attestation
+ ${esc(attestationUri)} +
` : ""} + + ${hostUrl ? ` +
+
Embed location
+ ${esc(hostUrl)} +
` : ""} + + ${bundleId ? ` +
+
Corpus bundle
+
${esc(bundleId)}
+
+ ${chunkCount ? `${chunkCount.toLocaleString()} chunks` : ""} + Ready +
+
` : ""} + +
+
+ Corpus sources (${sourceRows.length}) + ${sourceRows.length === 0 ? "" : `${sourceRows.reduce((a: number, s: any) => a + (s.chunk_count || 0), 0).toLocaleString()} total chunks`} +
+ ${sourceRows.length > 0 ? ` +
+ ${sourceRows.map((s: any) => { + const isLink = s.url && !s.url.startsWith("podcast://"); + const typeClass = s.type === "podcast" ? "src-badge-podcast" : "src-badge-article"; + const typeLabel = s.type === "podcast" ? "Podcast" : s.type === "book" ? "Book" : "Article"; + return `
+ ${isLink + ? `${esc(s.title || s.url)}` + : `${esc(s.title || s.url)}`} +
+ ${typeLabel} + ${s.chunk_count ? `${s.chunk_count}` : ""} +
+
`; + }).join("")} +
+ ` : `

No sources indexed yet. The corpus will be populated as content is submitted.

`} +
+
+ See something missing? + +
+
+

Submit a URL or title for something that should be in this corpus — a book, article, interview, podcast episode, or speech.

+ + + + +
+ + +
+
+
+
+ +
+
Steward access
+

Are you the steward for this expert? Access your corpus portal to review, expand, or update the corpus — or check the status of this agent.

+ ${stewardDashToken ? `Open my corpus portal →` : ""} + Find my agents by email → +
+
+ + +`); + }); + + // ── Steward portfolio: magic-link access ────────────────────────────────── + + function makePortfolioToken(email: string): string { + const payload = Buffer.from(JSON.stringify({ email, ts: Date.now() })).toString("base64url"); + const sig = crypto.createHmac("sha256", ADMIN_KEY).update(payload).digest("base64url"); + return `${payload}.${sig}`; + } + + function verifyPortfolioToken(token: string): { email: string; ts: number } | null { + try { + const [payload, sig] = token.split("."); + if (!payload || !sig) return null; + const expected = crypto.createHmac("sha256", ADMIN_KEY).update(payload).digest("base64url"); + if (expected !== sig) return null; + const data = JSON.parse(Buffer.from(payload, "base64url").toString()); + if (Date.now() - data.ts > 24 * 3600 * 1000) return null; // 24h TTL + return data; + } catch { return null; } + } + + // POST /api/agentify-help/portfolio-access — send magic link + app.post("/api/agentify-help/portfolio-access", express.json({ limit: "1mb" }), async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const { email } = req.body ?? {}; + if (!email || !email.includes("@")) return res.status(400).json({ error: "Valid email required" }); + try { + const { rows } = await pool.query( + `SELECT subject_slug, subject_name, status, dashboard_token FROM agentify_subject_registry + WHERE LOWER(steward_email) = LOWER($1) AND status != 'verify_email' + ORDER BY created_at DESC`, [email] + ); + if (rows.length === 0) { + return res.json({ ok: true, sent: false, message: "No confirmed stewardships found for that email. Check your registration and verify your email first." }); + } + const token = makePortfolioToken(email); + const portfolioUrl = `https://agentify.help/my-agents?token=${token}`; + const expertList = rows.map((r: any) => { + const dashLink = r.dashboard_token ? `Open corpus portal →` : ""; + return `
  • ${r.subject_name} (${r.status})
    View public page${dashLink ? " · " + dashLink : ""}
  • `; + }).join(""); + await resend.emails.send({ + from: "Agentify Registry ", + to: email, + subject: `Your steward portfolio — Agentify.Help`, + html: ` +

    Agentify.Help Registry

    +

    Your steward portfolio

    +

    You are stewarding ${rows.length} expert${rows.length !== 1 ? "s" : ""} on Agentify.Help. Here are your portals:

    +
      ${expertList}
    +

    Open my portfolio page →

    +

    This link expires in 24 hours. If you did not request this email, you can safely ignore it.

    +` + }); + res.json({ ok: true, sent: true, count: rows.length }); + } catch (e: any) { + console.error("[Portfolio] Error:", e.message); + res.status(500).json({ error: e.message }); + } + }); + + // POST /api/agentify-help/suggest-source — accept corpus gap suggestions from public + app.post("/api/agentify-help/suggest-source", express.json({ limit: "64kb" }), async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const { slug, url, title, notes, email } = req.body ?? {}; + if (!slug || !title?.trim()) return res.status(400).json({ error: "slug and title are required" }); + try { + await pool.query( + `INSERT INTO agentify_source_suggestions (expert_slug, url, title, notes, submitter_email) + VALUES ($1, $2, $3, $4, $5)`, + [slug, url?.trim() || null, title.trim(), notes?.trim() || null, email?.trim() || null] + ); + res.json({ ok: true }); + } catch (e: any) { + res.status(500).json({ error: "Failed to save suggestion" }); + } + }); + + // POST /api/agentify-help/propose-topic — submit a new topic for taxonomy review + app.post("/api/agentify-help/propose-topic", express.json({ limit: "64kb" }), async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const { category, display_name, description, scope_note, email } = req.body ?? {}; + if (!display_name?.trim() || !category?.trim()) return res.status(400).json({ error: "category and display_name are required" }); + const slug = display_name.trim().toLowerCase() + .replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); + try { + const existing = await pool.query(`SELECT id, status FROM agentify_topic_taxonomy WHERE slug = $1`, [slug]); + if (existing.rows.length > 0) { + return res.status(409).json({ error: "A topic with that name already exists in the taxonomy", slug, status: existing.rows[0].status }); + } + await pool.query( + `INSERT INTO agentify_topic_taxonomy (category, slug, display_name, description, scope_note, proposed_by_email, status) + VALUES ($1, $2, $3, $4, $5, $6, 'proposed')`, + [category.trim(), slug, display_name.trim(), description?.trim() || null, scope_note?.trim() || null, email?.trim() || null] + ); + res.json({ ok: true, slug, message: "Topic proposal received — a steward will review it." }); + } catch (e: any) { + res.status(500).json({ error: "Failed to save proposal" }); + } + }); + + // GET /api/agentify-help/topic-taxonomy — public taxonomy listing + app.get("/api/agentify-help/topic-taxonomy", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const { rows } = await pool.query(` + SELECT t.category, t.slug, t.display_name, t.description, t.scope_note, t.audience_personas, t.status, + r.status AS agent_status, r.subject_slug + FROM agentify_topic_taxonomy t + LEFT JOIN agentify_subject_registry r ON r.taxonomy_slug = t.slug + WHERE t.status IN ('approved','active') + ORDER BY t.category ASC, t.display_name ASC + `).catch(() => ({ rows: [] })); + res.json({ taxonomy: rows }); + }); + + // GET /my-agents — portfolio page (email form or token-based portfolio view) + app.get("/my-agents", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + + const token = (req.query.token as string) || ""; + let portfolioHtml = ""; + let email = ""; + + if (token) { + const decoded = verifyPortfolioToken(token); + if (!decoded) { + portfolioHtml = `
    Link expired or invalid

    This portfolio link has expired (24-hour TTL) or is invalid. Enter your email below to get a fresh link.

    `; + } else { + email = decoded.email; + const { rows } = await pool.query( + `SELECT r.subject_slug, r.subject_name, r.status, r.dashboard_token, + cb.chunk_count, cb.status AS bundle_status + FROM agentify_subject_registry r + LEFT JOIN agentify_corpus_bundles cb ON cb.expert_slug = r.subject_slug + WHERE LOWER(r.steward_email) = LOWER($1) AND r.status != 'verify_email' + ORDER BY r.created_at DESC`, [email] + ).catch(() => ({ rows: [] })); + + if (rows.length === 0) { + portfolioHtml = `
    No experts found

    No confirmed stewardships found for ${email}.

    `; + } else { + const cards = rows.map((r: any) => { + const statusColor: any = { preview: "#c9b87e", live: "#22c55e", ready: "#22c55e", proposed: "#a78bfa", distilling: "#f59e0b" }; + const sc = statusColor[r.bundle_status || r.status] || "#6b7280"; + const initials = (r.subject_name || "?").split(" ").map((w: string) => w[0]).join("").slice(0,2).toUpperCase(); + return `
    +
    ${initials}
    +
    +
    ${r.subject_name}
    +
    + ● ${(r.bundle_status || r.status || "pending").replace(/_/g," ")} + ${r.chunk_count ? ` · ${Number(r.chunk_count).toLocaleString()} chunks` : ""} +
    +
    + Public page → + ${r.dashboard_token ? `Corpus portal →` : ""} +
    +
    +
    `; + }).join(""); + portfolioHtml = `
    Your experts (${rows.length})

    Steward email: ${email}

    ${cards}
    `; + } + } + } + + const showForm = !token || !email; + return res.type("text/html").send(` + + +My Agents — Agentify.Help + + + + +
    +
    + ← Agentify.Help +

    My Agents

    +

    Steward portfolio — find all the experts you manage

    +
    +
    +
    + ${portfolioHtml} + ${showForm ? ` +
    +
    Access your steward portfolio
    +

    Enter the email address you used when registering as a steward. We'll send you a secure link to your full portfolio.

    + +
    + +
    + ` : ` +
    +
    Need a fresh link?
    +

    Portfolio links expire after 24 hours. Enter your email below to get a new one.

    + Request a new portfolio link → +
    `} +
    +`); + }); + + // ── Steward corpus portal: /stewardship/:slug?token= ──────── + app.get("/stewardship/:slug", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + + const { slug } = req.params; + const token = (req.query.token as string) || ""; + + if (!token) { + return res.type("text/html").send(`Access required — Agentify.Help

    Access token required

    Use the link from your steward portfolio email to access this page.

    ← Return to My Agents`); + } + + // Look up the expert by slug + dashboard_token + let row: any = null; + let bundles: any[] = []; + try { + const { rows } = await pool.query( + `SELECT r.id, r.subject_slug, r.subject_name, r.subject_domain, r.steward_email, r.steward_name, + r.status, r.host_url, r.dashboard_token, r.created_at + FROM agentify_subject_registry r + WHERE r.subject_slug = $1 AND r.dashboard_token = $2 + LIMIT 1`, [slug, token] + ); + if (rows.length === 0) { + return res.type("text/html").send(`Invalid token — Agentify.Help

    Invalid or expired token

    This corpus portal link is invalid. Return to your portfolio to get a fresh link.

    ← My Agents`); + } + row = rows[0]; + const { rows: bundleRows } = await pool.query( + `SELECT id, status, chunk_count, assembled_at, attestation_uri + FROM agentify_corpus_bundles WHERE expert_slug = $1 ORDER BY assembled_at DESC`, [slug] + ); + bundles = bundleRows; + } catch (e: any) { + return res.status(500).type("text/html").send(`DB error: ${e.message}`); + } + + const totalChunks = bundles.reduce((s: number, b: any) => s + (Number(b.chunk_count) || 0), 0); + const statusColor: Record = { preview: "#c9b87e", live: "#22c55e", ready: "#22c55e", proposed: "#a78bfa", distilling: "#f59e0b" }; + const sc = statusColor[row.status] || "#6b7280"; + const initials = (row.subject_name || "?").split(" ").map((w: string) => w[0]).join("").slice(0, 2).toUpperCase(); + + const bundleRows = bundles.map((b: any) => ` + + ${b.assembled_at ? new Date(b.assembled_at).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}) : "—"} + ● ${b.status||"pending"} + ${b.chunk_count ? Number(b.chunk_count).toLocaleString() : "—"} + ${b.attestation_uri ? `attestation ↗` : "—"} + `).join(""); + + return res.type("text/html").send(` + + +Corpus Portal — ${row.subject_name} — Agentify.Help + + + +
    +
    + ← My Agents +
    +
    ${initials}
    +
    +

    ${row.subject_name}

    +
    ${row.subject_domain || ""}
    +
    +
    +
    + ● ${row.status} + ${totalChunks.toLocaleString()} chunks total + ${bundles.length} bundle${bundles.length!==1?"s":""} +
    +
    +
    + +
    +
    +
    Quick links
    + +
    + +
    +
    Corpus bundles
    + ${bundles.length === 0 ? `

    No corpus bundles submitted yet. Email ody@wellspr.ing with source material to begin distillation.

    ` : ` +
    + + + + + ${bundleRows} +
    DateStatusChunksSource
    +
    + `} +
    + +
    +
    Steward details
    +
    +
    Steward: ${row.steward_name || row.steward_email}
    +
    Email: ${row.steward_email}
    + ${row.host_url ? `
    Deploy target: ${row.host_url}
    ` : ""} +
    Registered: ${new Date(row.created_at).toLocaleDateString("en-US",{month:"long",day:"numeric",year:"numeric"})}
    +
    +
    + +
    + Questions? Email ody@wellspr.ing · Back to portfolio +
    +
    +`); + }); + + // ── Recent registrations feed ───────────────────────────────────────────── + app.get("/api/feed.json", async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + const limit = Math.min(100, Math.max(1, Number(req.query.limit) || 20)); + try { + const { rows } = await pool.query(` + SELECT id AS registration_number, subject_slug, subject_name, subject_domain, + steward_name, host_url, status, registered_via, created_at + FROM agentify_subject_registry + ORDER BY created_at DESC LIMIT $1 + `, [limit]); + res.setHeader("Cache-Control", "public, max-age=30"); + res.json({ feed: rows, count: rows.length, updated: new Date().toISOString() }); + } catch (e: any) { + res.status(500).json({ error: e.message }); + } + }); + + // ── MCP Streamable HTTP endpoint (JSON-RPC 2.0, spec 2025-03-26) ─────────── + app.post("/mcp", express.json({ limit: "1mb" }), async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + + const ip = (req.headers["x-forwarded-for"] as string || req.socket.remoteAddress || "unknown").split(",")[0].trim(); + + const body = req.body; + if (!body || typeof body !== "object") { + return res.status(400).json({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }); + } + + const { jsonrpc, method, params, id } = body; + const ok = (result: any) => res.json({ jsonrpc: "2.0", id: id ?? null, result }); + const err = (code: number, message: string) => res.json({ jsonrpc: "2.0", id: id ?? null, error: { code, message } }); + + res.setHeader("Content-Type", "application/json"); + res.setHeader("Cache-Control", "no-store"); + + switch (method) { + case "initialize": + return ok({ + protocolVersion: "2025-03-26", + serverInfo: { name: "agentify-help", version: "1.0.0" }, + capabilities: { tools: {} } + }); + + case "notifications/initialized": + return res.status(204).end(); + + case "ping": + return ok({}); + + case "tools/list": + return ok({ tools: AH_MCP_TOOLS }); + + case "tools/call": { + const toolName = params?.name as string; + const args = params?.arguments ?? {}; + if (!toolName) return err(-32602, "params.name is required"); + // Rate-limit registrations from agents + if (toolName === "register" && !rateCheck(`mcp:${ip}`, 20, 3_600_000)) { + return ok({ content: [{ type: "text", text: JSON.stringify({ error: "Rate limit exceeded — 20 MCP registrations per IP per hour." }) }], isError: true }); + } + try { + const result = await handleMcpTool(toolName, args); + return ok(result); + } catch (e: any) { + if (e?.code) return err(e.code, e.message); + return err(-32603, e?.message || "Internal error"); + } + } + + default: + return err(-32601, `Method not found: ${method}`); + } + }); + + // MCP GET endpoint — capability discovery (some clients probe this) + app.get("/mcp", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + res.json({ + endpoint: "https://agentify.help/mcp", + alt_endpoint: "https://mcp.agentify.help/", + transport: "http", + protocol: "JSON-RPC 2.0", + spec: "2025-03-26", + tools: AH_MCP_TOOLS.map(t => ({ name: t.name, description: t.description })) + }); + }); + + // mcp.agentify.help — subdomain routing + // POST to root (or any path) → same as POST /mcp on main domain + // Agents can call: POST https://mcp.agentify.help/ OR POST https://agentify.help/mcp + app.post("/", express.json({ limit: "1mb" }), async (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.startsWith("mcp.")) return next(); + + const ip = (req.headers["x-forwarded-for"] as string || req.socket.remoteAddress || "unknown").split(",")[0].trim(); + const body = req.body; + if (!body || typeof body !== "object") { + return res.status(400).json({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }); + } + const { method, params, id } = body; + const ok = (result: any) => res.json({ jsonrpc: "2.0", id: id ?? null, result }); + const err = (code: number, message: string) => res.json({ jsonrpc: "2.0", id: id ?? null, error: { code, message } }); + res.setHeader("Content-Type", "application/json"); + res.setHeader("Cache-Control", "no-store"); + switch (method) { + case "initialize": return ok({ protocolVersion: "2025-03-26", serverInfo: { name: "agentify-help", version: "1.0.0" }, capabilities: { tools: {} } }); + case "notifications/initialized": return res.status(204).end(); + case "ping": return ok({}); + case "tools/list": return ok({ tools: AH_MCP_TOOLS }); + case "tools/call": { + const toolName = params?.name as string; + const args = params?.arguments ?? {}; + if (!toolName) return err(-32602, "params.name is required"); + if (toolName === "register" && !rateCheck(`mcp:${ip}`, 20, 3_600_000)) { + return ok({ content: [{ type: "text", text: JSON.stringify({ error: "Rate limit exceeded." }) }], isError: true }); + } + try { + const result = await handleMcpTool(toolName, args); + return ok(result); + } catch (e: any) { + if (e?.code) return err(e.code, e.message); + return err(-32603, e?.message || "Internal error"); + } + } + default: return err(-32601, `Method not found: ${method}`); + } + }); + + // mcp.agentify.help GET / — human-readable landing + machine discovery + app.get("/", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.startsWith("mcp.")) return next(); + res.setHeader("Cache-Control", "public, max-age=300"); + // Accept: application/json → return discovery JSON + if ((req.headers.accept || "").includes("application/json")) { + return res.json({ ...AH_MCP_DISCOVERY, transport: [{ type: "http", url: "https://mcp.agentify.help/" }, { type: "http", url: "https://agentify.help/mcp" }] }); + } + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.send(` + +mcp.agentify.help — WellAgent Registry MCP + + +
    MCP Endpoint
    +

    Agentify.Help Registry

    +

    This is the Model Context Protocol endpoint for the one-per-person WellAgent registry. +POST JSON-RPC 2.0 to this URL to check availability, register as steward, or browse the registry.

    +
    POST https://mcp.agentify.help/
    +Content-Type: application/json
    +
    +{"jsonrpc":"2.0","method":"tools/call","params":{"name":"check_availability",
    +  "arguments":{"name":"Jane Smith"}},"id":1}
    +

    Available tools

    +
    +${AH_MCP_TOOLS.map(t => `
    ${t.name}
    ${t.description.substring(0,120)}…
    `).join('')} +
    +

    + agentify.help · + .well-known/mcp.json · + OpenAPI spec · + llms.txt +

    +`); + }); + + // ── Well-known: MCP discovery ───────────────────────────────────────────── + app.get("/.well-known/mcp.json", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + res.setHeader("Cache-Control", "public, max-age=3600"); + res.json(AH_MCP_DISCOVERY); + }); + + // ── Well-known: OpenAI plugin manifest ──────────────────────────────────── + app.get("/.well-known/ai-plugin.json", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + res.setHeader("Cache-Control", "public, max-age=3600"); + res.json(AH_AI_PLUGIN); + }); + + // ── Well-known: OpenAPI spec ────────────────────────────────────────────── + app.get("/.well-known/openapi.json", (req: Request, res: Response, next) => { + const host = (req.headers["x-forwarded-host"] || req.headers.host || "").toString().split(",")[0].trim().replace(/:\d+$/, "").replace(/^www\./, "").toLowerCase(); + if (!host.includes("agentify") || host.startsWith("skills.")) return next(); + res.setHeader("Cache-Control", "public, max-age=3600"); + res.json(AH_OPENAPI); + }); + + // ── Domain-keyed page router ───────────────────────────────────────────── + app.use(async (req: Request, res: Response, next) => { + if (req.method !== "GET" && req.method !== "HEAD") return next(); + const hostCandidates = [ + req.headers["x-forwarded-host"], + req.headers["x-geo-node-host"], + req.hostname, + req.headers.host, + ].flatMap(h => { + if (!h) return []; + const v = (Array.isArray(h) ? h[0] : h) || ""; + return v.toString().toLowerCase().split(",").map((s: string) => s.trim().replace(/:\d+$/, "").replace(/^www\./, "")); + }); + + const isAgentifyHelp = hostCandidates.some(h => + (h.includes("agentify.help") || h.includes("agentify-help")) && !h.startsWith("skills.") + ); + if (!isAgentifyHelp) return next(); + + const p = req.path.replace(/\/$/, "") || "/"; + if (p.startsWith("/api/") || p.startsWith("/steward/") || p === "/personaforge") return next(); + + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.setHeader("Cache-Control", "public, max-age=120"); + + if (p === "/llms.txt") { + res.setHeader("Content-Type", "text/plain; charset=utf-8"); + return res.send(AGENTIFY_LLMS_TXT); + } + + if (p === "/robots.txt") { + res.setHeader("Content-Type", "text/plain; charset=utf-8"); + res.setHeader("Cache-Control", "public, max-age=86400"); + return res.send([ + "User-agent: *", + "Allow: /", + "", + "Sitemap: https://agentify.help/sitemap.xml", + "", + "# Machine-readable endpoints for AI agents", + "# llms.txt: https://agentify.help/llms.txt", + "# MCP: https://agentify.help/mcp (POST, JSON-RPC 2.0)", + "# MCP disco: https://agentify.help/.well-known/mcp.json", + "# OpenAPI: https://agentify.help/.well-known/openapi.json", + "# Registry: https://agentify.help/api/registry.json", + "# Feed: https://agentify.help/api/feed.json", + ].join("\n")); + } + + if (p === "/vcap") { + res.setHeader("Cache-Control", "public, max-age=600"); + return res.send(` + + + +VCAP — What it is, what it does, how to integrate it · Agentify.Help + + + + + + + + + + +
    +
    Trust Infrastructure
    +

    VCAP — the promise
    behind every WellAgent

    +

    A signed, permanent, and publicly verifiable declaration of what an AI agent will and won't do — written before the first consultation, readable by anyone, revocable by the expert at any time.

    + +
    + + +
    +
    + +

    Three things VCAP guarantees.

    +

    No fine print. No operator-only system prompts. The conduct declaration is public, signed with a key anyone can verify, and permanently vaulted.

    +
    +
    +
    Guarantee 01
    +
    Signed conduct
    +
    The agent's behavior is declared in a cryptographically signed document before the first consultation — not inferred from a private system prompt you can't inspect.
    +
    +
    +
    Guarantee 02
    +
    Named steward
    +
    A real person took responsibility for this agent by name. Their stewardship is in the attestation. If something is wrong, the chain of accountability is public.
    +
    +
    +
    Guarantee 03
    +
    Expert standing
    +
    The real expert whose knowledge this agent embodies has the right — at any time — to review the conduct declaration, request corrections, or revoke the agent entirely.
    +
    +
    +
    +
    + + +
    +
    + +

    PTP — Presence Token Protocol

    +

    VCAP declares what an agent is. PTP controls what it does in the moment. A presence token is a short-lived, scoped permission — issued by the steward's infrastructure, requested by the agent before acting.

    +
    +
    +
    What PTP prevents
    +
    An agent acting outside its declared scope without an explicit token. No token, no action. The scope grammar (SGS) defines what verbs are valid — consult, summarize, refer, prescribe (gated) — so the agent cannot silently expand its authority.
    +
    +
    +
    What PTP enables
    +
    Auditable consultation logs, per-session revocation, partner-gated capabilities, and fee structures enforced at the token layer rather than in application code. The token carries scope, expiry, and steward signature — all verifiable.
    +
    +
    +
    +
    + + +
    +
    + +

    Integrate VCAP in five steps.

    +

    Everything is plain JSON over HTTPS. No SDK required. Start with step 1 and you're verifying attestations in under two minutes.

    + +
    +
    1
    +
    + Fetch the protocol manifest — machine-readable index of every VCAP endpoint, signing key location, and supported scope verbs. +
    GET https://wellspr.ing/api/v1/vcap/manifest
    +
    +
    + +
    +
    2
    +
    + Read an attestation — the agent's full signed conduct declaration. Substitute the agent's slug and version. +
    GET https://wellspr.ing/vault/agents/{slug}/attestation-{version}.json
    +# Example:
    +GET https://wellspr.ing/vault/agents/sniderman/attestation-1.0.0.json
    +
    +
    + +
    +
    3
    +
    + Verify in one call — pass the session ID (returned when the attestation was minted) and get a signed verification response. Returns valid: true/false, the signer's key ID, and the declared scope list. +
    GET https://wellspr.ing/api/v1/vcap/attestations/{session_id}/verify
    +
    +# Response shape:
    +{
    +  "valid": true,
    +  "agent_id": "vcap:wellspring:expert:sniderman:1.0.0",
    +  "signed_by": "wellspring-signing-key-2026",
    +  "scopes": ["consult","summarize","cite"],
    +  "revoked": false,
    +  "attestation_uri": "https://wellspr.ing/vault/agents/sniderman/..."
    +}
    +
    +
    + +
    +
    4
    +
    + Request a presence token (PTP) — before your app calls the consult endpoint, request a scoped token. The token carries the permitted action verbs and an expiry. +
    POST https://wellspr.ing/api/v1/ptp/token
    +Content-Type: application/json
    +
    +{
    +  "agent_id": "vcap:wellspring:expert:sniderman:1.0.0",
    +  "requested_scopes": ["consult"],
    +  "requester_id": "your-platform-id",
    +  "context": "longevityformen.org consultation"
    +}
    +
    +# Returns: { "token": "ptp_...", "expires_at": "...", "granted_scopes": [...] }
    +
    +
    + +
    +
    5
    +
    + Call the consult endpoint with your token — attach the PTP token as a Bearer header. The platform verifies scope before forwarding your prompt to the agent. +
    POST https://wellspr.ing/api/agentify/experts/{slug}/consult
    +Authorization: Bearer ptp_...
    +Content-Type: application/json
    +
    +{ "query": "What is the clinical significance of discordant LDL-C and ApoB?" }
    +
    +
    + +
    + Signing keys — public keys used to verify VCAP signatures are published at + GET https://wellspr.ing/.well-known/vcap-signing-keys.json. Rotate-aware: keys carry a valid_from / valid_until range. Verify the key was valid at the time the attestation was signed. +
    +
    +
    + + +
    +
    + +

    The AGENT tab statement.

    +

    Copy this block into the AGENT tab of any expert profile page. Swap {expertName} and {slug} for the real values. All backlinks are live.

    + +
    +
    Trust layer — copy block for AGENT tab
    +

    This agent is signed under VCAP — the Vaulted Covenanted Agent Protocol. A cryptographically signed, publicly verifiable conduct declaration states exactly what this agent will and won't do, who built it, and {expertName}'s standing to review, correct, or revoke it at any time.

    +

    Consultations are governed by PTP (Presence Token Protocol), which enforces the agent's declared scope on every request. No action can be taken outside the signed scope without an explicit token.

    + +
    + +

    + The attestation link follows the pattern + https://wellspr.ing/vault/agents/{slug}/attestation-{version}.json. + For agents not yet deployed, link to + https://agentify.help/vcap only until the attestation is minted. +

    +
    +
    + + +
    +
    + +

    The full specification.

    +

    VCAP 0.10 is an open draft. The RFC covers the vault design, attestation structure, revocation model, signing keys, SGS scope grammar, PTP token lifecycle, transparency log, and agentic use patterns. Public comment is open — AI agents invited.

    + +
    +
    + + + +`); + } + + if (p === "/sitemap.xml") { + res.setHeader("Content-Type", "application/xml; charset=utf-8"); + res.setHeader("Cache-Control", "public, max-age=3600"); + return res.send(` + + https://agentify.help/daily1.0 + https://agentify.help/vcapweekly0.9 + https://agentify.help/llms.txtweekly0.8 + https://agentify.help/my-agentsnever0.5 + https://agentify.help/agents/guy-kawasakiweekly0.8 + https://agentify.help/api/registry.jsonhourly0.7 + https://agentify.help/api/feed.jsonalways0.7 + https://agentify.help/.well-known/mcp.jsonmonthly0.6 + https://agentify.help/.well-known/openapi.jsonmonthly0.6 +`); + } + + try { + const { rows } = await pool.query( + `SELECT subject_name, subject_domain, steward_name, host_url, status FROM agentify_subject_registry ORDER BY created_at ASC LIMIT 100` + ); + return res.send(buildHomePage(rows)); + } catch (e) { + return res.send(buildHomePage([])); + } + }); + + console.log("[Agentify.Help] agentify.help routes registered"); +}