my prefect server setup prefect-metrics.waow.tech
python orchestration
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add /api/agents/discovery-pool endpoint — recently-liked authors as a generic agent signal

new generic endpoint exposing authors of operator-liked posts (last N days,
default 7) with sample posts. consumers (any agent) can use this as a
high-signal pool of attention; per-consumer filtering happens client-side.

- web/src/lib/server/discovery.ts: loadDiscoveryPool() queries raw_liked_posts,
groups by author, returns top-N with sample posts (default top 30, 3 samples each)
- web/src/routes/api/agents/discovery-pool/+server.ts: GET endpoint with
optional ?window_days, ?max_authors, ?samples_per_author params
- web/src/lib/types.ts: DiscoveryPoolEntry / DiscoveryPoolPost types

not phi-specific by design — any consumer agent can use it. coupling kept
at the JSON schema, not at duckdb columns or per-consumer filtering logic.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>

+132
+88
web/src/lib/server/discovery.ts
··· 1 + import { query } from '$lib/server/db'; 2 + import type { DiscoveryPoolEntry, DiscoveryPoolPost } from '$lib/types'; 3 + 4 + /** A single liked-post row as it comes back from the DB. */ 5 + interface LikedRow { 6 + author_handle: string; 7 + author_did: string; 8 + subject_uri: string; 9 + text: string; 10 + liked_at: string; 11 + } 12 + 13 + /** 14 + * Discovery pool: authors whose posts the operator has liked recently, 15 + * grouped with up to N sample posts each. Generic — not specific to 16 + * any one consumer agent. 17 + * 18 + * @param windowDays how many days back to look at likes 19 + * @param maxAuthors cap on distinct authors returned 20 + * @param samplesPerAuthor cap on sample posts per author 21 + */ 22 + export async function loadDiscoveryPool( 23 + windowDays = 7, 24 + maxAuthors = 30, 25 + samplesPerAuthor = 3 26 + ): Promise<DiscoveryPoolEntry[]> { 27 + const rows = await query<LikedRow>(` 28 + SELECT author_handle, author_did, subject_uri, text, liked_at 29 + FROM raw_liked_posts 30 + WHERE liked_at >= (now() - INTERVAL '${windowDays} days')::VARCHAR 31 + AND author_handle != '' 32 + ORDER BY liked_at DESC 33 + `); 34 + 35 + const byAuthor = new Map<string, { did: string; posts: DiscoveryPoolPost[] }>(); 36 + for (const r of rows) { 37 + const existing = byAuthor.get(r.author_handle); 38 + if (existing) { 39 + if (existing.posts.length < samplesPerAuthor) { 40 + existing.posts.push({ 41 + uri: r.subject_uri, 42 + text: r.text ?? '', 43 + liked_at: r.liked_at 44 + }); 45 + } 46 + } else { 47 + byAuthor.set(r.author_handle, { 48 + did: r.author_did, 49 + posts: [ 50 + { 51 + uri: r.subject_uri, 52 + text: r.text ?? '', 53 + liked_at: r.liked_at 54 + } 55 + ] 56 + }); 57 + } 58 + } 59 + 60 + // We need the FULL liked count per author (not just the samples we kept). 61 + const counts = await query<{ author_handle: string; n: number }>(` 62 + SELECT author_handle, count(*)::INT AS n 63 + FROM raw_liked_posts 64 + WHERE liked_at >= (now() - INTERVAL '${windowDays} days')::VARCHAR 65 + AND author_handle != '' 66 + GROUP BY author_handle 67 + `); 68 + const countByHandle = new Map(counts.map((c) => [c.author_handle, c.n])); 69 + 70 + const entries: DiscoveryPoolEntry[] = Array.from(byAuthor.entries()).map( 71 + ([handle, { did, posts }]) => ({ 72 + handle, 73 + did, 74 + likes_in_window: countByHandle.get(handle) ?? posts.length, 75 + last_liked_at: posts[0]?.liked_at ?? '', 76 + sample_posts: posts 77 + }) 78 + ); 79 + 80 + entries.sort((a, b) => { 81 + if (b.likes_in_window !== a.likes_in_window) { 82 + return b.likes_in_window - a.likes_in_window; 83 + } 84 + return b.last_liked_at.localeCompare(a.last_liked_at); 85 + }); 86 + 87 + return entries.slice(0, maxAuthors); 88 + }
+14
web/src/lib/types.ts
··· 16 16 with_reactions: number; 17 17 repos: number; 18 18 } 19 + 20 + export interface DiscoveryPoolPost { 21 + uri: string; 22 + text: string; 23 + liked_at: string; 24 + } 25 + 26 + export interface DiscoveryPoolEntry { 27 + handle: string; 28 + did: string; 29 + likes_in_window: number; 30 + last_liked_at: string; 31 + sample_posts: DiscoveryPoolPost[]; 32 + }
+30
web/src/routes/api/agents/discovery-pool/+server.ts
··· 1 + import { json } from '@sveltejs/kit'; 2 + import { loadDiscoveryPool } from '$lib/server/discovery'; 3 + 4 + /** 5 + * Generic discovery pool: authors whose posts the operator has liked recently. 6 + * Consumers (any agent) can use this as a high-signal pool of people to 7 + * pay attention to. Filtering by per-consumer relevance happens client-side. 8 + * 9 + * Query params: 10 + * window_days — how many days back to consider (default 7) 11 + * max_authors — cap on distinct authors (default 30) 12 + * samples_per_author — cap on sample posts per author (default 3) 13 + */ 14 + export async function GET({ url }) { 15 + const windowDays = Math.min( 16 + 90, 17 + Math.max(1, Number(url.searchParams.get('window_days') ?? 7)) 18 + ); 19 + const maxAuthors = Math.min( 20 + 100, 21 + Math.max(1, Number(url.searchParams.get('max_authors') ?? 30)) 22 + ); 23 + const samplesPerAuthor = Math.min( 24 + 10, 25 + Math.max(1, Number(url.searchParams.get('samples_per_author') ?? 3)) 26 + ); 27 + 28 + const entries = await loadDiscoveryPool(windowDays, maxAuthors, samplesPerAuthor); 29 + return json(entries); 30 + }