Files
rockandpoimen 4640935993
CI / CI (push) Successful in 5m52s
feat: recruiter-scannable cards + Poimen Memory deep dive page (#14)
## Summary

   Rewrite all portfolio cards for 6-second HR scannability and add a dedicated Poimen Memory deep dive page.

   ## Changes

   ### Timeline Cards (all 6 rewritten)
   - **Poimen (Ποιμήν)** — NEW card. Graph-RAG memory + Temporal workflow pillars
   - **riotpiao.com** — 5 sections: Infrastructure, GitOps, Security, Observability, AI/ML
   - **RBC** — State migration (500+ files, zero corruption), drift detection (3wk → <24hr), 3× velocity
   - **AWS** — Distributed-Map ownership (57+ regions, sub-100ms P99), Redrive Execution launch
   - **Titus** — 97.8% accuracy, 28% P99 improvement, 5× deployment speed
   - **NAV Canada** — ATC weather briefing (ReactJS), Django, SonarQube

   ### Project Cards
   - Reorder: **Poimen Orchestration → Memory → Homelab** → RBC → AWS
   - Homelab: production-grade framing, "Explore the Architecture →"
   - Poimen: etymology intro ("shepherd"), connected narrative across cards
   - All cards use `dangerouslySetInnerHTML` for **bold** framework names

   ### Poimen Memory Page (`/poimen/memory`)
   - Hero: "Teaching the Shepherd to Remember"
   - 🏗️ Architecture diagram — Rust microservice, actix-web, Authentik JWT, pgvector
   - 🔄 Dataflow diagram — episode → entity/fact extraction → temporal graph
   - ⚡ Sequence diagram — async ingest lifecycle (202 accepted, background LLM pipeline)
   - Three-tier retrieval cards: signature match (50ms), graph-boosted hybrid, Obsidian fallback
   - Technology stack grid (Rust, pgvector, Ollama, TEI, Authentik, K8s)

   ### CI & Build Fixes
   - API allowlist updated for `riotpiao-poimen/` org repos
   - Commit SHA: `NEXT_PUBLIC_COMMIT_SHA` in Dockerfile + `--build-arg` in CI workflow
   - ExperienceTimeline CI badges wired for Poimen + Homelab repos

   ## Files Changed
   - `lib/translations.json` — all card content (EN + ZH)
   - `app/page.tsx` — project order, deepDive links, ciRepos mapping
   - `app/poimen/memory/page.tsx` — NEW deep dive page
   - `components/ExperienceTimeline.tsx` — Poimen card, HTML rendering, CI badges
   - `app/api/ci-status/route.ts` — allowlist for riotpiao-poimen org
   - `Dockerfile` + `.gitea/workflows/build-push.yml` — SHA fix
   - `public/poimen-memory/` — 3 archify diagrams (architecture, dataflow, sequence)

---------

Co-authored-by:  poimen <[email protected]>
Reviewed-on: #14
2026-09-09 23:29:14 +00:00

293 lines
13 KiB
TypeScript

'use client'
import Link from 'next/link'
import { ArrowLeft } from 'lucide-react'
import { motion } from 'framer-motion'
const skillCategories = [
{
title: 'Core Runtime',
skills: ['Rust', 'actix-web', 'tokio', 'serde'],
},
{
title: 'Storage & Search',
skills: ['PostgreSQL (CloudNativePG)', 'pgvector (HNSW)', 'Cosine Similarity', 'BFS Graph Traversal'],
},
{
title: 'LLM & Embeddings',
skills: ['Ollama (ornith:35b)', 'TEI (nomic-embed)', '768-dim Embeddings'],
},
{
title: 'Identity & Security',
skills: ['Authentik OIDC', 'JWT Verification', 'SOPS Encrypted Secrets', 'RBAC'],
},
{
title: 'Data Pipeline',
skills: ['Fixed-Window Chunking', 'Entity Extraction', 'Fact Extraction', 'Reflection Filtering'],
},
{
title: 'Infrastructure',
skills: ['Kubernetes', 'ArgoCD', 'Kustomize', 'cert-manager'],
},
]
const sectionVariants = {
hidden: { opacity: 0, y: 40 },
visible: { opacity: 1, y: 0, transition: { duration: 0.6 } },
}
export default function PoimenMemoryPage() {
return (
<main className="min-h-screen bg-white dark:bg-gray-950">
<div className="max-w-6xl mx-auto px-6 py-20">
<Link
href="/#project-poimen-memory"
className="inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:underline mb-8"
>
<ArrowLeft size={16} />
Back to Projects
</Link>
{/* Hero */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="mb-16"
>
<div className="text-sm font-semibold text-purple-600 dark:text-purple-400 uppercase tracking-wide mb-2">
Poimen (Ποιμήν) · Memory System
</div>
<h1 className="text-5xl font-bold text-gray-900 dark:text-white mb-4">
Teaching the Shepherd to Remember
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 mb-6">
A Graph-RAG memory system that gives AI agents long-term recall, semantic search, and self-compacting knowledge.
</p>
<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
Agents forget. Every conversation starts from zero unless you build memory into the system.
Poimen Memory is a <strong>Rust-based Graph-RAG service</strong> that ingests conversations,
extracts entities and relationships via LLM, stores them in a <strong>temporal knowledge graph</strong> backed
by <strong>pgvector</strong>, and serves them back through <strong>three-tier retrieval</strong> — signature
match at 50ms, graph-boosted hybrid search, and Obsidian fallback. The cache self-compacts:
stale embeddings are evicted, frequently-accessed chunks are promoted, and the graph auto-reconciles
on every write.
</p>
</motion.div>
{/* Section 1: Architecture Overview */}
<motion.section
variants={sectionVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="mb-16 border-l-4 border-purple-500 pl-8"
>
<div className="mb-6">
<div className="text-sm font-semibold text-purple-600 dark:text-purple-400 uppercase tracking-wide mb-2">
System Design
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Architecture Overview
</h2>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none mb-6">
<p className="text-gray-700 dark:text-gray-300">
The memory system runs as a <strong>Rust microservice</strong> inside the Kubernetes <code>poimen</code> namespace.
An <strong>actix-web</strong> API server handles HTTP requests, authenticates via <strong>Authentik OIDC JWT</strong>,
and routes to either the <strong>search path</strong> (direct pgvector query) or the <strong>ingest path</strong> (async
pipeline through an internal queue → LLM extraction → graph persistence). All data lives in a
<strong> CloudNativePG</strong> cluster with <strong>HNSW indexes</strong> for vector similarity.
</p>
</div>
{/* Architecture Diagram */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">
🏗️ System Architecture
</h3>
<div className="rounded-xl overflow-hidden border border-gray-200 dark:border-gray-700 shadow-lg">
<iframe
src="/poimen-memory/architecture.html"
className="w-full bg-white dark:bg-gray-900"
style={{ height: '700px', border: 'none' }}
title="Poimen Memory Architecture"
/>
</div>
</div>
</motion.section>
{/* Section 2: Ingest Pipeline */}
<motion.section
variants={sectionVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="mb-16 border-l-4 border-emerald-500 pl-8"
>
<div className="mb-6">
<div className="text-sm font-semibold text-emerald-600 dark:text-emerald-400 uppercase tracking-wide mb-2">
Data Flow
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Ingest Pipeline
</h2>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none mb-6">
<p className="text-gray-700 dark:text-gray-300">
Conversations arrive as <strong>episodes</strong> — raw message sequences. The pipeline splits into two
parallel LLM extraction paths: <strong>entity extraction</strong> (people, tools, concepts with type + summary)
and <strong>fact extraction</strong> (relationships between entity pairs as directed edges). A <strong>reflection
pass</strong> filters hallucinated entities before persistence. The result is a <strong>temporal knowledge
graph</strong> where every edge carries <code>t_valid</code> and <code>t_invalid</code> timestamps — knowledge
that knows when it was true.
</p>
</div>
{/* Dataflow Diagram */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">
🔄 Ingest Data Flow
</h3>
<div className="rounded-xl overflow-hidden border border-gray-200 dark:border-gray-700 shadow-lg">
<iframe
src="/poimen-memory/dataflow.html"
className="w-full bg-white dark:bg-gray-900"
style={{ height: '700px', border: 'none' }}
title="Poimen Ingest Pipeline"
/>
</div>
</div>
</motion.section>
{/* Section 3: Request Lifecycle */}
<motion.section
variants={sectionVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="mb-16 border-l-4 border-cyan-500 pl-8"
>
<div className="mb-6">
<div className="text-sm font-semibold text-cyan-600 dark:text-cyan-400 uppercase tracking-wide mb-2">
Sequence
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Ingest Request Lifecycle
</h2>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none mb-6">
<p className="text-gray-700 dark:text-gray-300">
The ingest path is <strong>fully async</strong>. The agent sends a <code>POST /memory/ingest</code> and
gets a <strong>202 Accepted</strong> immediately — no blocking on LLM latency. A background worker
polls jobs from the queue, runs two sequential LLM calls (entity extraction → fact extraction),
cleans JSON responses of thinking tags and fences, then persists entities, edges, and embeddings
to pgvector. The agent can query the graph within seconds of ingestion completing.
</p>
</div>
{/* Sequence Diagram */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">
⚡ Request Sequence
</h3>
<div className="rounded-xl overflow-hidden border border-gray-200 dark:border-gray-700 shadow-lg">
<iframe
src="/poimen-memory/sequence.html"
className="w-full bg-white dark:bg-gray-900"
style={{ height: '700px', border: 'none' }}
title="Poimen Ingest Request Lifecycle"
/>
</div>
</div>
</motion.section>
{/* Section 4: Retrieval Strategy */}
<motion.section
variants={sectionVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="mb-16 border-l-4 border-orange-500 pl-8"
>
<div className="mb-6">
<div className="text-sm font-semibold text-orange-600 dark:text-orange-400 uppercase tracking-wide mb-2">
Search
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Three-Tier Retrieval
</h2>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 not-prose">
<div className="bg-emerald-50 dark:bg-emerald-950/30 rounded-xl p-6 border border-emerald-200 dark:border-emerald-800">
<div className="text-2xl mb-2">⚡</div>
<h3 className="font-bold text-emerald-700 dark:text-emerald-400 mb-2">Tier 1: Signature Match</h3>
<p className="text-sm text-gray-700 dark:text-gray-300">
<strong>50ms</strong> exact entity lookup by name hash. Instant recall for known entities —
no embedding computation needed.
</p>
</div>
<div className="bg-cyan-50 dark:bg-cyan-950/30 rounded-xl p-6 border border-cyan-200 dark:border-cyan-800">
<div className="text-2xl mb-2">🔍</div>
<h3 className="font-bold text-cyan-700 dark:text-cyan-400 mb-2">Tier 2: Graph-Boosted Hybrid</h3>
<p className="text-sm text-gray-700 dark:text-gray-300">
<strong>HNSW cosine similarity</strong> + <strong>BFS graph traversal</strong>. RRF fusion ranks
results across vector matches and graph neighbors. Context-aware retrieval that follows relationships.
</p>
</div>
<div className="bg-orange-50 dark:bg-orange-950/30 rounded-xl p-6 border border-orange-200 dark:border-orange-800">
<div className="text-2xl mb-2">📚</div>
<h3 className="font-bold text-orange-700 dark:text-orange-400 mb-2">Tier 3: Obsidian Fallback</h3>
<p className="text-sm text-gray-700 dark:text-gray-300">
When graph search yields low confidence, falls back to <strong>wiki-link indexed</strong> Obsidian
vault. Bidirectional link traversal surfaces related notes the graph hasn&apos;t captured yet.
</p>
</div>
</div>
</div>
</motion.section>
{/* Skills Grid */}
<motion.section
variants={sectionVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="mb-16"
>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-8">
Technology Stack
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{skillCategories.map((category) => (
<div
key={category.title}
className="bg-gray-50 dark:bg-gray-900 rounded-xl p-6 border border-gray-200 dark:border-gray-800"
>
<h3 className="text-base font-bold text-gray-900 dark:text-white mb-3">
{category.title}
</h3>
<div className="flex flex-wrap gap-2">
{category.skills.map((skill) => (
<span
key={skill}
className="text-sm px-3 py-1.5 rounded-full bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700"
>
{skill}
</span>
))}
</div>
</div>
))}
</div>
</motion.section>
</div>
</main>
)
}