Bài viết này là artifact, không phải lời kể

Bạn đang đọc output được sinh ra bởi chính pipeline mà bài viết mô tả. Grok thực hiện research pass với nguồn live. Các block và linkedinPost đã đi qua đúng guardrail trong validate-content.mjs áp dụng cho mọi nội dung khác. File JSON bạn đang xem được emit, vượt qua kiểm tra, được commit và đang được render — mà không ai phải viết HTML tay hay copy text sang LinkedIn thủ công.

Cách portfolio thông thường hỏng là narrative rot: người ta viết một câu chuyện về kỹ năng của mình tại một thời điểm, rồi câu chuyện và thực tế dần tách rời. Cách duy nhất để giữ bản ghi trung thực ở tần suất hàng ngày là ngừng coi xuất bản là viết lách và bắt đầu coi nó là một hệ thống có contract, gate và sự tách biệt rõ ràng giữa những gì model được phép quyết định và những gì renderer được phép emit.

Nhưng "pipeline tự động" nghe đơn giản hơn thực tế rất nhiều. Khi LLM trở thành một bước trong production pipeline, các vấn đề cổ điển của systems engineering xuất hiện ngay: schema contract, security guardrail, idempotent validation, render separation. Không cái nào miễn phí.

Grok (Research)validate-content.mjsClaude (Execution)Astro BuildDeploy + LinkedInUser Feedback / Insightsraw JSON (research + thesis)pass guardrails (schema, sensitive terms, skill verbs)contentMarkdown + blocksstatic site + mechanism GIFuser reads / edits / flagsinsights for next research cycle
Research (Grok) → Validate & Guardrails (Claude) → Render & Deploy → Feedback Loop

Quyết định 1: Capability routing thay vì single-model

Mistake phổ biến nhất khi xây multi-agent pipeline: dùng một model cho tất cả vì tiện. Đây là lý do đúng để không làm vậy.

Grok có real-time web search — đây là superpower mà Claude không có theo design. Grok research topic, tìm 3–5 nguồn thật (RFC, paper, official doc, benchmark), tổng hợp điểm phi hiển nhiên, đề xuất thesis có thể đúng/sai được. Claude có tool use + code execution + smallest-correct-diff — validate output, chạy scripts, đảm bảo mechanical consistency trên nhiều file.

Trong production voice systems (self-hosted Whisper/NeMo cho audio grounding + Gemini cho reasoning/extraction), routing tương tự được áp dụng: heavy-lifting models cho audio (cost/control) + reasoning models cho structured output (với schemas). Batch và contracts giúp scale mà không explode cost/token. Portfolio pipeline áp dụng cùng nguyên tắc: research (Grok) vs execution/guardrails (Claude), schema là boundary an toàn.

Routing matrix: uncertainty × realtime-need × context-volume × taste. Grok nhận việc Claude yếu (realtime grounding), Claude nhận việc Grok yếu (execution precision, verification). Không phải phân vai theo "to nhỏ" — phân vai theo superpower.

Một điểm không obvious ngay: Grok và Claude dùng chung file CLAUDE.md ở root repo — đây là shared brain. Grok Build tự scan CLAUDE.md khi chạy trong repo. Khi tôi cập nhật routing rule, security guardrail, hay output format — cả hai AI nhận context đồng thời. Không phải duy trì 2 instruction file song song.

Quyết định 2: LLM emit data, site render HTML

Đây là quyết định kiến trúc có tác động lớn nhất đến security và maintainability của toàn hệ thống.

Pattern thông thường: yêu cầu LLM viết HTML/Markdown có embedded HTML, paste thẳng vào template. Vấn đề:

  • XSS risk khi LLM embed <script> hay onclick handler
  • Inconsistent styling khi model đổi output format giữa các ngày
  • Không thể animate đúng cách nếu HTML không follow animation contract
  • Không accessible — LLM không biết design system ARIA patterns

Thay vào đó, tôi thiết kế block schema: LLM emit structured JSON, Astro component render thành HTML/SVG an toàn theo design system.

Ví dụ block architecture:

{
  "type": "architecture",
  "id": "pipeline-main",
  "architecture": {
    "components": [{"id": "grok", "label": "Grok CLI", "kind": "reasoner"}],
    "connections": [{"from": "grok", "to": "validate", "label": "raw JSON", "type": "sync"}]
  }
}

Astro component nhận block data, render SVG với proper ARIA labels, animate bằng CSS (không GSAP), consistent với design tokens. LLM không bao giờ được emit raw HTML. content/schema.json định nghĩa mọi field — AJV validate trước khi bất kỳ file nào được write.

Kết quả thực tế: khi tôi đổi animation style cho architecture diagram, tôi sửa Astro component — không cần touch bất kỳ content file nào. Separation of concerns thật sự.

Quyết định 3: Validate-before-build là security boundary cứng

validate-content.mjs chạy trước astro build. Nếu fail → exit 1 → build fail → không deploy. Đây là hard boundary, không phải warning.

Cụ thể những gì nó chặn:

SENSITIVE_TERMS scan: Quét toàn bộ output LLM (blog title/body, expUpdate, blocks) cho tên công ty/khách/dự án nội bộ. Portfolio này public — LLM không được phép lộ context từ production codebase của tôi.

SKILL_VERBS check: expUpdate là skill snapshot. Mỗi highlights entry phải dùng động từ kỹ năng — "triển khai", "tối ưu", "tích hợp". Nếu LLM viết kiểu "worked on" hay "participated in" thay vì mô tả năng lực → fail.

Metric pattern detection: ≥2 số liệu cụ thể (ms, %, rps) trong cùng câu = nghi trước/sau của hệ thống thật → reject. Không bao giờ commit metrics internal vào public portfolio.

Slug uniqueness: Duplicate slug giữa các ngày → build fail. Không có hidden page collision.

AJV schema validation: Mọi field có bounds: maxLength, minLength, enum, pattern. Type error = không deploy.

Tại sao làm vậy thay vì trust Grok? Vì LLM hallucinate. Không phải Grok đặc biệt dễ hallucinate — mà vì bất kỳ LLM nào cũng hallucinate đủ thường xuyên để cần hard guardrail. Guardrail trong build pipeline bắt được điều mà prompt engineering không bao giờ đảm bảo được.

Quyết định 4: Slim index + template-based lazy archive

Lúc đầu news page load toàn bộ content bằng import.meta.glob("content/news/*.json") — Astro eager-load tất cả file vào HTML. Ổn khi có 5–6 ngày. Vấn đề khi >30 ngày: 95KB+ HTML cho một listing page.

Slim index: content/news/index.json — 6.7KB chứa slim metadata (slug, title, excerpt, tags, date, coverImage). Build step gen-news-index.mjs generate file này từ toàn bộ daily + essays. Blog listing load từ index, không load full content.

Template-based archive: Những ngày cũ hơn 14 ngày được render vào <template id="news-archive-tpl">. Browser không render template content vào DOM — nó chỉ được parse. JS clone template vào DOM khi user click "Show older". Không extra HTTP request, không layout shift.

Pattern này là browser-native API, không cần library. Template element tồn tại từ HTML5, work trên mọi browser hiện đại. Nhưng ít người dùng vì không biết nó tồn tại.

Quyết định 5: Mechanism GIF bằng satori+gifenc

LinkedIn post có GIF animation nhận nhiều engagement hơn static image. Nhưng Three.js hay GSAP làm tụt Lighthouse score — đây là trade-off không acceptable cho portfolio.

Solution: server-side GIF generation khi build.

satori: Render JSX → SVG server-side (không cần browser, không cần headless Chrome). Hỗ trợ layout flexbox, text, path.

gifenc: Encode raw pixel array → GIF binary. Palette quantization, frame delay configuration.

Pipeline: architecture block data → buildArchitectureFrame() render 8 SVG frames (signal dots lerp dọc edges theo frameT) → satori render mỗi frame → gifenc encode → public/images/mechanism/<slug>.gif.

Canvas: 1200×630px (LinkedIn optimal). Last frame delay 1400ms (hold cho reader đọc). Edge type async = dashed line, event = dotted, sync = solid. Signal dot highlight source node khi khởi phát, target node khi đến nơi.

Không client-side JS, không external CDN, Lighthouse không bị ảnh hưởng.

Meta-point: portfolio là evidence, không phải claim

Pipeline chạy xong trong 3 phút: Grok research (90s) → validate (2s) → gen-news-index (0.5s) → Astro build (45s) → deploy + LinkedIn (20s).

Nhưng điểm quan trọng hơn performance: portfolio này không phải nội dung về pipeline — nó là bằng chứng của pipeline tôi có thể xây. Guardrail bảo mật là evidence. Data-driven blocks là evidence. Slim index + lazy archive là evidence. Mechanism GIF không cần WebGL là evidence.

"Show, don't tell" không chỉ là nguyên tắc content — đó là nguyên tắc engineering. Người xem portfolio này không cần tôi tuyên bố có thể thiết kế production pipeline. Họ đang dùng nó.