feat: recruiter-scannable cards + Poimen Memory deep dive page (#14)
CI / CI (push) Successful in 5m52s
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:
@@ -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'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user