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

## 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
This commit was merged in pull request #14.
This commit is contained in:
2026-09-09 23:29:14 +00:00
co-authored by poimen
parent fff4d59e39
commit 4640935993
13 changed files with 45434 additions and 91 deletions
+292
View File
@@ -0,0 +1,292 @@
'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>
)
}