feat: add routes/agentify.ts

This commit is contained in:
gitadmin 2026-05-02 13:07:34 +00:00
parent e18d6cc1aa
commit 9f36274926

844
routes/agentify.ts Normal file
View file

@ -0,0 +1,844 @@
/**
* Agentify Expert Embodiment Permission Layer
*
* Registers expert agents with VCAP-scoped credentials so that any
* application (expert embodiment, appliance assistant, domain tutor, etc.)
* can receive a signed attestation whose scope is enforced by the PTP system.
*
* Public endpoints:
* GET /api/agentify/experts list all active expert agents
* GET /api/agentify/experts/:slug single expert profile + scopes
* GET /vault/agents/:slug/attestation-:version.json machine-readable signed attestation
* GET /api/agentify/corpus/:expertSlug list finalized corpus bundles
*
* Admin endpoints (x-admin-key required):
* POST /api/agentify/experts/register register expert + mint VCAP credential
*
* Corpus intake and bundle listing are handled by corpus-routes.ts (with Vultr S3 proxy).
*/
import type { Express, Request, Response } from "express";
import { pool } from "../db";
import crypto from "crypto";
import { validatePartnerToken } from "../auth";
const ADMIN_KEY = process.env.ADMIN_KEY || "b0db7a87384fc814b0f46ea7bdc6ab6a81152be5b098718b";
// ── SGS scope validation ─────────────────────────────────────────────────────
// Agentify action verbs and their PTP stakes level. These extend the base SGS
// vocabulary defined in ptp-routes.ts with domain-consultation actions.
export const AGENTIFY_SCOPE_ACTIONS: Record<string, string> = {
consult: "medium", // expert consults a care-seeker on a clinical/domain question
cite: "low", // agent cites a passage from the expert's published corpus
receive: "medium", // agent receives a structured intake (WCES or equivalent)
correct: "medium", // expert submits a correction to an agent response
compile: "high", // platform compiles a new agent version from updated corpus
};
function sgsValid(scope: string): boolean {
const parts = scope.split(":");
return parts.length === 4 && parts.every(p => p.length > 0);
}
function mintAttestationPayload(expert: {
slug: string;
expertName: string;
credentials: string | null;
primaryDomain: string;
affiliatedInstitution: string | null;
demoUrl: string | null;
scopes: string[];
refusals: string[];
credId: string;
attestationVersion: string;
}): object {
const now = new Date().toISOString();
return {
"@context": "https://wellspr.ing/protocols/vcap/v0.10",
"@type": "VCAPAttestation",
"attestation_id": `agentify-${expert.slug}-${expert.attestationVersion}`,
"issued_at": now,
"issued_by": "wellspr.ing",
"protocol": "VCAP",
"protocol_version": "0.10",
"agent_class": "expert-embodiment",
"agent_slug": expert.slug,
"agent_label": expert.expertName,
"credentials": expert.credentials,
"primary_domain": expert.primaryDomain,
"affiliated_institution": expert.affiliatedInstitution,
"demo_url": expert.demoUrl,
"scope": expert.scopes,
"refusals": expert.refusals,
"scope_grammar": "SGS 0.9 — {action}:{recipient-category}:{geography}:{purpose-category}",
"scope_actions_vocabulary": Object.keys(AGENTIFY_SCOPE_ACTIONS),
"cred_id": expert.credId,
"cred_profile_uri": `https://wellspr.ing/api/v1/agent-credentials/${expert.credId}`,
"attestation_uri": `https://wellspr.ing/vault/agents/${expert.slug}/attestation-${expert.attestationVersion}.json`,
"transparency_log": "https://wellspr.ing/vcap-log",
"signing_keys_uri": "https://wellspr.ing/.well-known/vcap-signing-keys.json",
"required_disclosure": "This agent reasons within the expert's published framework and cites only retrieved corpus passages. It is not the expert in person and is not a substitute for the patient's physician.",
"identity_disclosure": `This is an AI agent grounded in ${expert.expertName}'s published corpus and reasoning framework. It operates under the WellSpr.ing VCAP protocol. It is not ${expert.expertName} in person.`,
};
}
export function registerAgentifyRoutes(app: Express) {
// Ensure agentify_experts table exists (idempotent, runs once at startup)
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()
)
`).catch(err => console.error("[agentify] table init error:", err));
// ── GET /api/agentify/experts ──────────────────────────────────────────────
app.get("/api/agentify/experts", async (_req: Request, res: Response) => {
try {
const rows = await pool.query(
`SELECT slug, expert_name, credentials, primary_domain, affiliated_institution,
demo_url, scopes, refusals, attestation_version, attestation_uri, status, created_at
FROM agentify_experts WHERE status != 'deprecated' ORDER BY created_at ASC`
);
res.json({ experts: rows.rows });
} catch (err) {
console.error("[agentify] list error:", err);
res.status(500).json({ error: "internal_error" });
}
});
// ── GET /api/agentify/experts/:slug ───────────────────────────────────────
// Returns full expert profile + corpus stats + api block for portal integration.
app.get("/api/agentify/experts/:slug", async (req: Request, res: Response) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Partner-Token, X-Admin-Key");
try {
const { slug } = req.params;
const rows = await pool.query(
`SELECT * FROM agentify_experts WHERE slug = $1`, [slug]
);
if (!rows.rows.length) return res.status(404).json({ error: "expert_not_found" });
const e = rows.rows[0];
// Attach corpus stats from the best available bundle
const bundleRows = await pool.query(
`SELECT bundle_id, corpus_version, status, chunk_count, received_at, updated_at
FROM agentify_corpus_bundles
WHERE expert_slug = $1
ORDER BY chunk_count DESC NULLS LAST, received_at DESC
LIMIT 1`,
[slug]
);
const bundle = bundleRows.rows[0] || null;
// Embed count (if distilled)
let embeddingCount = 0;
if (bundle) {
const embRow = await pool.query(
`SELECT COUNT(*) FROM corpus_embeddings WHERE bundle_id = $1`, [bundle.bundle_id]
);
embeddingCount = parseInt(embRow.rows[0].count || "0");
}
const base = "https://wellspr.ing";
const agentBase = "https://agentify.help";
res.json({
slug: e.slug,
expert_name: e.expert_name,
credentials: e.credentials,
primary_domain: e.primary_domain,
affiliated_institution: e.affiliated_institution,
demo_url: e.demo_url,
scopes: e.scopes,
refusals: e.refusals,
attestation_version: e.attestation_version,
attestation_uri: e.attestation_uri,
cred_profile_uri: e.cred_id
? `${base}/api/v1/agent-credentials/${e.cred_id}`
: null,
status: e.status,
created_at: e.created_at,
corpus: bundle ? {
bundle_id: bundle.bundle_id,
corpus_version: bundle.corpus_version,
status: bundle.status,
chunk_count: bundle.chunk_count,
embedding_count: embeddingCount,
ready: bundle.status === "ready" && embeddingCount > 0,
received_at: bundle.received_at,
updated_at: bundle.updated_at,
} : null,
api: {
spec_url: `${base}/api/agentify/portal-api`,
profile_url: `${base}/api/agentify/experts/${slug}`,
status_url: `${base}/api/agentify/experts/${slug}/status`,
consult_url: `${base}/api/agentify/experts/${slug}/consult`,
submit_url_url: `${base}/api/agentify/experts/${slug}/corpus/submit-url`,
attestation_url: `${base}/vault/agents/${slug}/attestation-${e.attestation_version}.json`,
public_page: `${agentBase}/agents/${slug}`,
auth: "X-Partner-Token or X-Admin-Key header required for consult and submit-url",
auth_docs: `${base}/api/agentify/portal-api`,
},
});
} catch (err) {
console.error("[agentify] get expert error:", err);
res.status(500).json({ error: "internal_error" });
}
});
// ── GET /vault/agents/:slug/attestation-:version.json ────────────────────
// Machine-readable signed VCAP attestation for an expert agent.
// Anti-vaporware gate: Sniderman's attestation must be fetchable at
// wellspr.ing/vault/agents/sniderman/attestation-v0-1.json
app.get("/vault/agents/:slug/attestation-:version.json", async (req: Request, res: Response) => {
try {
const { slug, version } = req.params;
const rows = await pool.query(
`SELECT * FROM agentify_experts WHERE slug = $1`, [slug]
);
if (!rows.rows.length) return res.status(404).json({ error: "expert_not_found" });
const e = rows.rows[0];
const payload = mintAttestationPayload({
slug: e.slug,
expertName: e.expert_name,
credentials: e.credentials,
primaryDomain: e.primary_domain,
affiliatedInstitution: e.affiliated_institution,
demoUrl: e.demo_url,
scopes: e.scopes,
refusals: e.refusals,
credId: e.cred_id || "pending",
attestationVersion: version || e.attestation_version,
});
// Sign the payload if the signing key is available
const privKeyB64 = process.env.ODY_SIGNING_KEY_B64;
let signature: string | null = null;
if (privKeyB64) {
try {
const privKey = crypto.createPrivateKey({
key: Buffer.from(privKeyB64, "base64"), format: "der", type: "pkcs8",
});
const canonical = JSON.stringify(payload, Object.keys(payload as object).sort());
const sig = crypto.sign(null, Buffer.from(canonical), privKey);
signature = sig.toString("base64");
} catch (sigErr) {
console.error("[agentify] attestation signing error:", sigErr);
}
}
const attestation = { ...payload, ...(signature ? { signature } : {}) };
res.setHeader("Content-Type", "application/json");
res.setHeader("Access-Control-Allow-Origin", "*");
res.json(attestation);
} catch (err) {
console.error("[agentify] attestation error:", err);
res.status(500).json({ error: "internal_error" });
}
});
// ── POST /api/agentify/experts/register ───────────────────────────────────
// Admin-only. Registers an expert agent, issues a VCAP credential, and
// returns the attestation URI. Designed to be called once per expert at
// corpus compile time (Stage 6 of the Agentify pipeline).
//
// Body:
// slug — url-safe identifier, e.g. "sniderman"
// expert_name — display name
// credentials? — professional suffix, e.g. "MD, FRCP(C)"
// primary_domain — e.g. "lipidology"
// affiliated_institution? — e.g. "McGill University"
// demo_url? — live consultation URL
// scopes — array of SGS scope strings
// refusals? — array of out-of-scope declarations
// attestation_version? — default "v0.1"
// status? — default "proposed"
app.post("/api/agentify/experts/register", async (req: Request, res: Response) => {
const adminKey = req.headers["x-admin-key"] || req.body?.admin_key;
const isAdmin = adminKey === ADMIN_KEY;
if (!isAdmin) {
// Also accept partner tokens with agentify.register permission
const partnerCtx = await validatePartnerToken(req.headers.authorization);
if (!partnerCtx || !partnerCtx.permissions.includes("agentify.register")) {
return res.status(403).json({
error: "forbidden",
hint: "Provide x-admin-key header, or a Bearer partner token with 'agentify.register' permission.",
});
}
}
const {
slug, expert_name, credentials, primary_domain,
affiliated_institution, demo_url, scopes = [], refusals = [],
attestation_version = "v0.1", status = "proposed",
} = req.body;
if (!slug || !expert_name || !primary_domain)
return res.status(400).json({ error: "slug, expert_name, and primary_domain are required" });
// Validate scopes follow SGS 4-part format
const badScopes = (scopes as string[]).filter(s => !sgsValid(s));
if (badScopes.length)
return res.status(400).json({
error: "invalid_scope_format",
detail: `Scopes must follow SGS grammar: {action}:{recipient-category}:{geography}:{purpose}`,
bad_scopes: badScopes,
hint: "Each scope must be exactly 4 colon-separated parts. Single words like 'research' or 'clinical' are not valid.",
valid_examples: {
healthcare: [
"converse:individuals:global:healthcare-coordination",
"query:academic-institutions:global:academic-research",
"publish:individuals:global:academic-research",
],
civic: [
"message:covenanted-persons:425:civic-outreach",
"query:individuals:us-wa:daily-assistance",
],
actions: ["message","converse","query","transact","receive","broker","publish"],
recipient_categories: ["individuals","healthcare-providers","academic-institutions","covenanted-persons","merchants","civic-bodies","nonprofit-orgs"],
purpose_categories: ["healthcare-coordination","academic-research","civic-outreach","daily-assistance","first-contact","commercial-inquiry"],
geography_examples: ["global","us","us-wa","425","206","seattle-wa"],
spec: "https://wellspr.ing/protocols/sgs",
},
});
const privKeyB64 = process.env.ODY_SIGNING_KEY_B64;
// 1. Mint a VCAP credential in covenant_agent_creds
let credId: string | null = null;
if (privKeyB64) {
try {
const now = new Date().toISOString();
credId = crypto.randomBytes(16).toString("hex");
const credPayload = {
cred_id: credId,
agent_name: `agentify-${slug}`,
display_name: expert_name,
entity_name: expert_name,
entity_type: "expert-agent",
description: `Agentify expert embodiment agent for ${expert_name} (${primary_domain})`,
scope: scopes,
issued_at: now,
issued_by: "agentify-pipeline",
};
const sortedKeys = Object.keys(credPayload).sort();
const canonical = JSON.stringify(credPayload, sortedKeys);
const privKey = crypto.createPrivateKey({
key: Buffer.from(privKeyB64, "base64"), format: "der", type: "pkcs8",
});
const sig = crypto.sign(null, Buffer.from(canonical), privKey);
const cred_signature = sig.toString("base64");
await pool.query(
`INSERT INTO covenant_agent_creds
(cred_id, agent_name, display_name, entity_name, entity_type,
entity_uri, contact_email, description, scope, issued_by, cred_signature)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)`,
[credId, `agentify-${slug}`, expert_name, expert_name, "expert-agent",
demo_url || null, null,
`Agentify expert embodiment — ${primary_domain}`,
scopes, "agentify-pipeline", cred_signature]
);
} catch (credErr) {
console.error("[agentify] cred mint error:", credErr);
credId = null;
}
}
const attestationUri = `https://wellspr.ing/vault/agents/${slug}/attestation-${attestation_version}.json`;
// 2. Insert or update agentify_experts row
try {
await pool.query(
`INSERT INTO agentify_experts
(slug, expert_name, credentials, primary_domain, affiliated_institution,
demo_url, scopes, refusals, cred_id, attestation_version, attestation_uri, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
ON CONFLICT (slug) DO UPDATE SET
expert_name = EXCLUDED.expert_name,
credentials = EXCLUDED.credentials,
primary_domain = EXCLUDED.primary_domain,
affiliated_institution = EXCLUDED.affiliated_institution,
demo_url = EXCLUDED.demo_url,
scopes = EXCLUDED.scopes,
refusals = EXCLUDED.refusals,
cred_id = COALESCE(EXCLUDED.cred_id, agentify_experts.cred_id),
attestation_version = EXCLUDED.attestation_version,
attestation_uri = EXCLUDED.attestation_uri,
status = EXCLUDED.status`,
[slug, expert_name, credentials || null, primary_domain,
affiliated_institution || null, demo_url || null,
scopes, refusals, credId,
attestation_version, attestationUri, status]
);
} catch (dbErr) {
console.error("[agentify] expert insert error:", dbErr);
return res.status(500).json({ error: "database_error" });
}
console.log(`[agentify] registered expert: ${slug} (${scopes.length} scopes, credId=${credId})`);
res.status(201).json({
"@type": "AgentifyExpertRegistration",
slug,
expert_name,
scopes,
refusals,
attestation_version,
attestation_uri: attestationUri,
cred_id: credId,
cred_profile_uri: credId ? `https://wellspr.ing/api/v1/agent-credentials/${credId}` : null,
status,
note: "Fetch the attestation_uri to verify scopes. Use cred_profile_uri to inspect the signed credential.",
});
});
// ── GET /api/agentify/scope-vocabulary ────────────────────────────────────
// Documents the known Agentify SGS action verbs and their stakes levels.
// Applications defining new agents should declare their actions here.
app.get("/api/agentify/scope-vocabulary", (_req: Request, res: Response) => {
res.json({
"@type": "AgentifyScopeVocabulary",
grammar: "SGS 0.9 — {action}:{recipient-category}:{geography}:{purpose-category}",
spec_uri: "https://wellspr.ing/protocols/vcap",
actions: AGENTIFY_SCOPE_ACTIONS,
example_scopes: [
{
scope: "consult:care-seekers:*:cholesterol-framework-application",
description: "Expert consults a care-seeker using the cholesterol reasoning framework",
stakes: "medium",
ptp_ttl_hours: 48,
},
{
scope: "receive:wces-intakes:*:peer-response-generation",
description: "Agent receives a WCES-formatted patient intake for response generation",
stakes: "medium",
ptp_ttl_hours: 48,
},
{
scope: "cite:published-corpus:*:evidence-based-recommendation",
description: "Agent cites a passage from the expert's published corpus",
stakes: "low",
ptp_ttl_hours: 72,
},
],
recipient_categories: [
"care-seekers", "wces-intakes", "published-corpus",
"covenanted-persons", "merchants", "civic-bodies", "faith-organizations",
],
purpose_categories: [
"cholesterol-framework-application", "peer-response-generation",
"evidence-based-recommendation", "civic-outreach", "commercial-inquiry",
"emergency", "covenant-invitation",
],
note: "To register a new scope action verb, POST to /api/agentify/experts/register with scopes using your verb. Novel verbs default to 'medium' stakes in the PTP system.",
});
});
// NOTE: Corpus intake endpoints (/api/agentify/corpus/intake/*)
// and bundle listing (/api/agentify/corpus/:slug) are handled by corpus-routes.ts
// which also manages Vultr S3 uploads and the agentify_corpus_bundles table.
// ── GET /api/agentify/portal-api — machine-readable spec for portal integrators
app.get("/api/agentify/portal-api", (_req: Request, res: Response) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.json({
"@type": "AgentifyPortalAPI",
version: "1.0",
description: "Agentify.Help portal integration API. Use these endpoints to embed expert AI personas in your portal. One expert = one corpus = one agent. No local embedding required.",
base_url: "https://wellspr.ing",
auth: {
type: "header",
options: [
{ header: "X-Partner-Token", description: "Issued partner token (contact ody@wellspr.ing to request one)", example: "ws_a94673b4..." },
{ header: "X-Admin-Key", description: "Admin key (internal use only)" },
],
public_endpoints: ["GET /api/agentify/experts", "GET /api/agentify/experts/:slug", "GET /api/agentify/experts/:slug/status"],
authenticated_endpoints: ["POST /api/agentify/experts/:slug/consult", "POST /api/agentify/experts/:slug/corpus/submit-url"],
},
endpoints: [
{
method: "GET",
path: "/api/agentify/experts",
auth: "none",
description: "List all registered expert agents",
response: { experts: "array of expert profile objects" },
example_curl: `curl https://wellspr.ing/api/agentify/experts`,
},
{
method: "GET",
path: "/api/agentify/experts/:slug",
auth: "none",
description: "Full expert profile including corpus stats and API endpoint URLs",
params: { slug: "Expert slug, e.g. 'guy-kawasaki'" },
response: {
slug: "string",
expert_name: "string",
primary_domain: "string",
credentials: "string | null",
affiliated_institution: "string | null",
scopes: "string[]",
refusals: "string[]",
status: "proposed | preview | live | deprecated",
corpus: {
bundle_id: "string",
chunk_count: "integer",
embedding_count: "integer",
ready: "boolean — true when consultation is available",
received_at: "ISO8601 timestamp",
},
api: "object with all endpoint URLs for this expert",
},
example_curl: `curl https://wellspr.ing/api/agentify/experts/guy-kawasaki`,
},
{
method: "GET",
path: "/api/agentify/experts/:slug/status",
auth: "none",
description: "Readiness check + detailed corpus stats. Useful for polling after submitting new content.",
example_curl: `curl https://wellspr.ing/api/agentify/experts/guy-kawasaki/status`,
},
{
method: "POST",
path: "/api/agentify/experts/:slug/consult",
auth: "X-Partner-Token or X-Admin-Key",
description: "Ask the expert a question. Returns an answer grounded in the expert's corpus using dense-vector retrieval + Claude.",
body: {
question: "string (required) — the user's question",
context: {
ehr: "string (optional) — patient EHR text to include in reasoning context (medical portals only)",
},
},
response: {
expert: "slug",
expertName: "string",
answer: "string — expert-voice response grounded in corpus",
chunksUsed: "integer — number of corpus chunks retrieved",
retrieval: "'dense' | 'fts' — retrieval mode used",
corpusVersion: "string",
hasEhrContext: "boolean",
},
example_curl: `curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/consult \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"question": "What is the most important thing a startup founder should know about pitching to investors?"}'`,
},
{
method: "POST",
path: "/api/agentify/experts/:slug/corpus/submit-url",
auth: "X-Partner-Token or X-Admin-Key",
description: "Submit a URL to expand the expert's corpus. Fetches the page, chunks the content, embeds it, and adds it to the expert's bundle. Useful for adding new articles, transcripts, or publications.",
body: {
url: "string (required) — publicly accessible URL",
title: "string (optional) — override detected title",
type: "string (optional) — 'blog_post' | 'article' | 'transcript' | 'paper' (default: 'article')",
date: "string (optional) — ISO8601 or YYYY-MM format for the publication date",
},
response: {
ok: "boolean",
chunk_count_added: "integer — new chunks added from this URL",
new_total: "integer — total chunk count after addition",
artifact_id: "string — stable content-hash UUID for this document",
title: "string — detected or provided title",
url: "string",
},
example_curl: `curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/corpus/submit-url \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"url": "https://guykawasaki.com/essays/the-top-ten-lies-of-entrepreneurs/", "type": "blog_post"}'`,
},
{
method: "POST",
path: "/api/agentify/experts/:slug/corpus/submit-source",
auth: "X-Partner-Token or X-Admin-Key",
description: "The vacuum endpoint. Point it at a blog index, RSS feed, or domain and Agentify discovers all content there, cross-references the source catalog to find what it doesn't already have, and ingests only the new pieces. Handles deduplication automatically — the same URL is never fetched twice. Cloudflare-protected pages are detected and skipped gracefully.",
body: {
url: "string (required) — blog index URL, RSS feed URL, single article URL, or domain root",
type: "string (optional) — 'article' | 'blog' | 'rss' | 'auto' (default: 'auto'). 'auto' detects from URL pattern.",
max_articles: "integer (optional, default 50, max 200) — how many new articles to ingest in this call",
},
response: {
ok: "boolean",
source: "the URL you submitted",
discovered: "integer — total URLs found at source",
already_known: "integer — URLs already in the source catalog (skipped)",
newly_ingested: "integer — new URLs successfully fetched and embedded",
chunk_count_added: "integer — total new chunks added across all new articles",
new_total: "integer — expert's total chunk count after this run",
skipped: "integer — URLs that failed fetch or returned no useful content",
ingested_urls: "array of { url, title, chunks } for each newly ingested article",
},
example_curl: `curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/corpus/submit-source \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"url": "https://guykawasaki.com/blog/", "max_articles": 20}'`,
note: "Call this on a schedule (daily/weekly) with the same source URL. It will always find and ingest only new content — nothing is re-fetched.",
},
{
method: "POST",
path: "/api/agentify/experts/:slug/corpus/submit-url",
auth: "X-Partner-Token or X-Admin-Key",
description: "Submit a single URL to expand the expert's corpus. Fetches the page, chunks the content, embeds it, and adds it to the expert's bundle. Also records the URL in the source catalog so it won't be re-fetched by submit-source.",
body: {
url: "string (required) — publicly accessible URL",
title: "string (optional) — override detected title",
type: "string (optional) — 'blog_post' | 'article' | 'transcript' | 'paper' (default: 'article')",
date: "string (optional) — ISO8601 or YYYY-MM format",
},
response: {
ok: "boolean",
chunk_count_added: "integer",
new_total: "integer",
artifact_id: "string",
title: "string",
cataloged: "boolean — true, this URL is now in the source catalog",
},
example_curl: `curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/corpus/submit-url \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"url": "https://guykawasaki.com/essays/the-top-ten-lies-of-entrepreneurs/"}'`,
},
{
method: "GET",
path: "/api/agentify/experts/:slug/corpus/catalog",
auth: "none",
description: "Returns all URLs already ingested for this expert — the source catalog. Use this to audit coverage, see which domains have been ingested, and check before submitting a source.",
params: {
limit: "integer (default 200, max 1000)",
offset: "integer (default 0)",
domain: "string (optional) — filter by domain substring, e.g. '?domain=guykawasaki.com'",
},
response: {
expert_slug: "string",
total_cataloged: "integer — total URLs in catalog",
items: "array of { url, canonical, title, type, artifact_id, chunk_count, ingested_at }",
has_more: "boolean",
},
example_curl: `curl "https://wellspr.ing/api/agentify/experts/guy-kawasaki/corpus/catalog?domain=guykawasaki.com"`,
},
{
method: "GET",
path: "/vault/agents/:slug/attestation-:version.json",
auth: "none",
description: "Machine-readable VCAP attestation for the expert agent. Signed with ODY signing key.",
example_curl: `curl https://wellspr.ing/vault/agents/guy-kawasaki/attestation-v0.1.json`,
},
],
test_case: {
expert: "guy-kawasaki",
status: "ready",
corpus_chunks: 2210,
note: "Guy Kawasaki's corpus is live. Partners with a valid token can test consultation now.",
quick_test: `curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/consult -H "Content-Type: application/json" -H "X-Partner-Token: YOUR_PARTNER_TOKEN" -d '{"question":"What makes a great product person?"}'`,
},
partner_onboarding: {
contact: "ody@wellspr.ing",
subject: "Portal API token request",
body: "Include your portal domain and the experts you plan to query.",
},
});
});
// ── GET /api/agentify/portal-api.html — human-readable API docs ────────────
app.get("/api/agentify/portal-api.html", (_req: Request, res: Response) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.type("text/html").send(`<!DOCTYPE html>
<html lang="en"><head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Agentify Portal API WellSpr.ing</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#07102a;color:#d4cfbb;font-family:"IBM Plex Mono","Fira Mono",monospace;font-size:.88rem;line-height:1.65}
.wrap{max-width:860px;margin:0 auto;padding:40px 24px}
h1{font-size:1.5rem;color:#f0e8d0;margin-bottom:4px;font-family:"IBM Plex Sans",sans-serif}
h2{font-size:1rem;color:#c9b87e;margin:32px 0 10px;text-transform:uppercase;letter-spacing:.07em}
h3{font-size:.88rem;color:#8ab8ff;margin:20px 0 6px}
.method{display:inline-block;padding:2px 8px;border-radius:3px;font-size:.72rem;font-weight:700;margin-right:6px}
.get{background:#0d3a2a;color:#22c55e;border:1px solid #22c55e44}
.post{background:#2a1a0d;color:#f59e0b;border:1px solid #f59e0b44}
.path{color:#8ab8ff;font-size:.88rem}
pre{background:#060e1c;border:1px solid #1a2e44;border-radius:6px;padding:14px;overflow-x:auto;font-size:.75rem;margin:8px 0;white-space:pre-wrap;word-break:break-all}
.badge{background:#1a2e4a;color:#c9b87e;border:1px solid #c9b87e33;padding:2px 10px;border-radius:20px;font-size:.7rem;margin-left:6px}
.badge.open{background:#0d2a1a;color:#22c55e;border-color:#22c55e44}
.desc{color:#8899bb;margin-bottom:8px;font-family:"IBM Plex Sans",sans-serif}
.divider{border:none;border-top:1px solid #1a2e44;margin:28px 0}
a{color:#c9b87e;text-decoration:none}
a:hover{text-decoration:underline}
</style>
</head><body>
<div class="wrap">
<div style="font-size:.75rem;color:#6b7f99;margin-bottom:16px;text-transform:uppercase;letter-spacing:.08em">WellSpr.ing · Agentify.Help</div>
<h1>Agentify Portal API</h1>
<p style="color:#8899bb;margin-top:6px;font-family:'IBM Plex Sans',sans-serif">Embed expert AI personas in your portal no local embedding, no model infrastructure needed.</p>
<h2>Authentication</h2>
<p class="desc">Public endpoints need no auth. Consultation and content submission require a header:</p>
<pre>X-Partner-Token: YOUR_PARTNER_TOKEN</pre>
<p style="color:#6b7f99;font-size:.8rem">Partner tokens are issued privately. Contact <a href="mailto:ody@wellspr.ing">ody@wellspr.ing</a> to request one for your portal.</p>
<hr class="divider">
<h2>Endpoints</h2>
<h3><span class="method get">GET</span><span class="path">/api/agentify/experts</span> <span class="badge open">public</span></h3>
<p class="desc">List all registered expert agents and their status.</p>
<pre>curl https://wellspr.ing/api/agentify/experts</pre>
<h3><span class="method get">GET</span><span class="path">/api/agentify/experts/:slug</span> <span class="badge open">public</span></h3>
<p class="desc">Full expert profile with corpus stats and all endpoint URLs for this expert.</p>
<pre>curl https://wellspr.ing/api/agentify/experts/guy-kawasaki</pre>
<h3><span class="method post">POST</span><span class="path">/api/agentify/experts/:slug/consult</span> <span class="badge">auth required</span></h3>
<p class="desc">Ask the expert a question. Answer is grounded in their corpus (dense-vector retrieval + Claude).</p>
<pre>curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/consult \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"question": "What makes a great startup pitch?"}'</pre>
<p class="desc" style="margin-top:8px">Response includes: <code>answer</code>, <code>chunksUsed</code>, <code>retrieval</code> (dense/fts), <code>expertName</code>.</p>
<p class="desc">For medical portals, include <code>context.ehr</code> with patient EHR text for grounded clinical responses.</p>
<h3><span class="method post">POST</span><span class="path">/api/agentify/experts/:slug/corpus/submit-url</span> <span class="badge">auth required</span></h3>
<p class="desc">Submit a URL to expand the expert's corpus. Fetches, chunks, embeds, and adds to the live bundle.</p>
<pre>curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/corpus/submit-url \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"url": "https://guykawasaki.com/essays/the-top-ten-lies-of-entrepreneurs/", "type": "blog_post"}'</pre>
<h3><span class="method get">GET</span><span class="path">/vault/agents/:slug/attestation-:version.json</span> <span class="badge open">public</span></h3>
<p class="desc">Signed VCAP attestation document machine-readable, verifiable, citable.</p>
<pre>curl https://wellspr.ing/vault/agents/guy-kawasaki/attestation-v0.1.json</pre>
<hr class="divider">
<h2>Live test case: Guy Kawasaki</h2>
<p class="desc">2,210 chunks ready. Substitute your partner token to try it now:</p>
<pre>curl -X POST https://wellspr.ing/api/agentify/experts/guy-kawasaki/consult \\
-H "Content-Type: application/json" \\
-H "X-Partner-Token: YOUR_PARTNER_TOKEN" \\
-d '{"question":"What is your one rule for hiring?"}'</pre>
<hr class="divider">
<p style="color:#6b7f99;font-size:.78rem">Agentify Portal API v1.0 · <a href="/api/agentify/portal-api">JSON spec</a> · <a href="https://agentify.help">agentify.help</a> · Contact: <a href="mailto:ody@wellspr.ing">ody@wellspr.ing</a></p>
</div>
</body></html>`);
});
console.log("[agentify] routes registered");
}
// ── Seed the Sniderman registration (called at startup) ────────────────────
export async function seedAgentifySniderman(): Promise<void> {
try {
// Ensure table exists before seeding (the route init is async, this is safer)
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()
)
`);
const existing = await pool.query(
`SELECT id FROM agentify_experts WHERE slug = 'sniderman' LIMIT 1`
);
if (existing.rows.length) return; // already seeded
// Use the register route's logic directly
const scopes = [
"consult:care-seekers:*:cholesterol-framework-application",
"receive:wces-intakes:*:peer-response-generation",
"cite:published-corpus:*:evidence-based-recommendation",
];
const refusals = [
"diagnose:any:*:clinical-diagnosis",
"prescribe:any:*:treatment-prescription",
"advise:any:*:outside-lipidology-domain",
];
const credPayload: Record<string, unknown> = {
cred_id: "agentify-sniderman-v0-1",
agent_name: "agentify-sniderman",
display_name: "Dr. Allan Sniderman, MD — Agentify Agent v0.1",
entity_name: "Dr. Allan Sniderman, MD",
entity_type: "expert-agent",
description: "Expert embodiment agent for Dr. Allan Sniderman, lipidologist at McGill University. Reasons within his published framework on ApoB, LDL-C discordance, and cardiovascular risk.",
scope: scopes,
issued_at: new Date().toISOString(),
issued_by: "agentify-pipeline",
};
let credId: string | null = "agentify-sniderman-v0-1";
const privKeyB64 = process.env.ODY_SIGNING_KEY_B64;
if (privKeyB64) {
try {
const sortedKeys = Object.keys(credPayload).sort();
const canonical = JSON.stringify(credPayload, sortedKeys);
const privKey = crypto.createPrivateKey({
key: Buffer.from(privKeyB64, "base64"), format: "der", type: "pkcs8",
});
const sig = crypto.sign(null, Buffer.from(canonical), privKey);
const cred_signature = sig.toString("base64");
await pool.query(
`INSERT INTO covenant_agent_creds
(cred_id, agent_name, display_name, entity_name, entity_type,
entity_uri, contact_email, description, scope, issued_by, cred_signature)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
ON CONFLICT (cred_id) DO NOTHING`,
[credId, "agentify-sniderman", "Dr. Allan Sniderman, MD — Agentify Agent v0.1",
"Dr. Allan Sniderman, MD", "expert-agent",
"https://cholesteroltruth.com/consult/sniderman", null,
"Expert embodiment agent — lipidology / ApoB framework",
scopes, "agentify-pipeline", cred_signature]
);
} catch (e) {
console.error("[agentify] sniderman cred seed error:", e);
credId = null;
}
}
const attestationVersion = "v0.1";
const attestationUri = `https://wellspr.ing/vault/agents/sniderman/attestation-${attestationVersion}.json`;
await pool.query(
`INSERT INTO agentify_experts
(slug, expert_name, credentials, primary_domain, affiliated_institution,
demo_url, scopes, refusals, cred_id, attestation_version, attestation_uri, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
ON CONFLICT (slug) DO NOTHING`,
["sniderman", "Dr. Allan Sniderman, MD", "MD, FRCP(C), FACC", "lipidology",
"McGill University", "https://cholesteroltruth.com/consult/sniderman",
scopes, refusals, credId,
attestationVersion, attestationUri, "proposed"]
);
console.log("[agentify] seeded sniderman v0.1 expert registration");
} catch (err) {
console.error("[agentify] sniderman seed error:", err);
}
}