[{"content":"If your Go service relies on one LLM provider, two failures hurt the most, timeout spikes and billing spikes. A real production setup is not just “add another provider”, it is a single control plane for routing, timeout tiers, cost caps, and fallback.\nThis guide gives you a practical OpenAI + Claude dual-provider pattern with one priority, keep uptime first, then optimize quality.\nTarget SLOs and constraints Success rate beats single-request latency Per-minute cost must have a hard cap Provider failures must trigger fast traffic shift Routing model, classify then decide Use a 3-layer flow.\nAdmission layer (scene tags: chat, code, summary) Policy layer (SLO + budget based provider priority) Execution layer (timeout budget + retry policy) type Provider string const ( OpenAI Provider = \u0026#34;openai\u0026#34; Claude Provider = \u0026#34;claude\u0026#34; ) type RouteInput struct { Scene string MaxLatency time.Duration MaxCostUSD float64 } func PickProvider(in RouteInput, health map[Provider]float64) Provider { if in.MaxCostUSD \u0026lt; 0.003 { return Claude } if health[OpenAI] \u0026gt;= 0.98 { return OpenAI } return Claude } Timeout tiers, never one global timeout Split timeout into connect, first-byte, and total budget.\nclient := \u0026amp;http.Client{ Timeout: 12 * time.Second, Transport: \u0026amp;http.Transport{ DialContext: (\u0026amp;net.Dialer{Timeout: 2 * time.Second}).DialContext, TLSHandshakeTimeout: 2 * time.Second, ResponseHeaderTimeout: 4 * time.Second, MaxIdleConnsPerHost: 64, }, } Cost cap with minute-level hard gate type BudgetGate struct { LimitPerMin float64 UsedPerMin atomic.Float64 } func (g *BudgetGate) Allow(cost float64) bool { if g.UsedPerMin.Load()+cost \u0026gt; g.LimitPerMin { return false } g.UsedPerMin.Add(cost) return true } When over budget.\nHigh-value requests use shorter context and lower temperature Low-priority requests move to async queue Non-critical traffic returns cached results Fallback behavior, make degradation explicit { \u0026#34;status\u0026#34;: \u0026#34;degraded\u0026#34;, \u0026#34;provider\u0026#34;: \u0026#34;claude\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;openai_timeout_budget_exceeded\u0026#34;, \u0026#34;trace_id\u0026#34;: \u0026#34;rt_20260408_xxx\u0026#34; } This keeps incident response fast and debuggable.\nMetrics and alerts you actually need provider_success_rate provider_p95_latency provider_timeout_ratio token_cost_per_min fallback_trigger_count degrade_response_ratio - alert: LLMProviderTimeoutSpike expr: provider_timeout_ratio{provider=\u0026#34;openai\u0026#34;} \u0026gt; 0.08 for: 5m - alert: LLMBudgetNearLimit expr: token_cost_per_min \u0026gt; (budget_limit_per_min * 0.9) for: 3m Deployment checklist Health probes feed routing decisions Timeout tiers enabled (no one global timeout) Minute-level budget gate in production Degraded responses include reason + trace_id Dashboards and alerts for the 6 core metrics Summary Dual-provider routing is about controllability, not feature collecting.\nYour minimum viable production loop is, health probes + timeout tiers + budget gate + explicit degradation. Build this first, then tune model quality.\n","permalink":"https://www.mfun.ink/en/2026/04/08/go-dual-model-routing-openai-claude-timeout-cost-fallback/","summary":"\u003cp\u003eIf your Go service relies on one LLM provider, two failures hurt the most, timeout spikes and billing spikes. A real production setup is not just “add another provider”, it is a single control plane for routing, timeout tiers, cost caps, and fallback.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical OpenAI + Claude dual-provider pattern with one priority, keep uptime first, then optimize quality.\u003c/p\u003e","title":"Go Dual-Provider LLM Routing (OpenAI + Claude): Timeout Tiers, Cost Caps, and Fallback Control"},{"content":"If your CI keeps failing and engineers keep babysitting logs, you\u0026rsquo;re paying an invisible velocity tax. A production-grade AI self-healing pipeline is not \u0026ldquo;let the agent edit anything\u0026rdquo;. It\u0026rsquo;s a controlled loop: attribution, patching, approval, rollback.\nThis post gives you a deployable baseline: Claude Code proposes a minimal fix patch, GitHub Actions enforces risk gates and regression checks, and humans only approve at high-impact checkpoints.\n1) Define the boundary first Without hard guardrails, automation scales failure.\nOnly handle regression-testable CI failures (unit tests, lint, type checks) Exclude schema migrations, prod config, major dependency upgrades Cap patch size (for example \u0026lt;= 120 LOC) Require root-cause + fix rationale + regression evidence # .github/ai-heal-policy.yml max_patch_lines: 120 allowed_jobs: - unit-test - lint - type-check blocked_paths: - infra/prod/** - migrations/** require_human_approval: true 2) Classify failures before you patch No classification means blind guessing.\nA practical 3-bucket model:\nTransient retryable: network jitter, registry 429, cache timeout Deterministic code issue: failed assertion, type mismatch, lint violation Environment/policy issue: expired credentials, permission denied, workflow misconfig - name: Classify CI failure run: | python3 scripts/ci/classify_failure.py \\ --workflow-run \u0026#34;${{ github.run_id }}\u0026#34; \\ --out /tmp/failure.json Sample output:\n{ \u0026#34;class\u0026#34;: \u0026#34;deterministic_code\u0026#34;, \u0026#34;confidence\u0026#34;: 0.86, \u0026#34;job\u0026#34;: \u0026#34;unit-test\u0026#34;, \u0026#34;root_cause\u0026#34;: \u0026#34;Null pointer in user profile mapper\u0026#34; } 3) Generate the smallest possible patch Treat Claude Code as a constrained patch generator, not a repo-wide refactor engine.\nclaude code run \\ --task \u0026#34;Fix failing unit-test only; keep patch \u0026lt;=120 LOC\u0026#34; \\ --context /tmp/failure.json \\ --allow-path src/ tests/ \\ --deny-path migrations/ infra/prod/ \\ --output /tmp/patch.diff Then enforce hard checks:\ngit apply --check /tmp/patch.diff path allowlist validation patch line-count threshold touched modules must match failed jobs 4) Keep one-button human approval for risk Auto-fix should not mean auto-merge.\nLow risk: small patch + green regression + no sensitive path -\u0026gt; one-click maintainer approval Medium risk: core module involved -\u0026gt; code-owner approval High risk: broad diff or low confidence -\u0026gt; escalate to manual fix environment: name: ai-heal-approval url: ${{ steps.report.outputs.pr_url }} 5) Regression and rollback are first-class Define success as observability, not a single green run.\nsame failure recurrence within 24h after merge First-Fix Rate MTTR False-Fix Rate Store classification and patch outcomes in one audit stream. That\u0026rsquo;s your policy training data.\n6) GitHub Actions skeleton you can start with name: ci-self-heal on: workflow_run: workflows: [\u0026#34;CI\u0026#34;] types: [completed] jobs: heal: if: ${{ github.event.workflow_run.conclusion == \u0026#39;failure\u0026#39; }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Classify run: python3 scripts/ci/classify_failure.py --workflow-run \u0026#34;${{ github.event.workflow_run.id }}\u0026#34; --out /tmp/failure.json - name: Generate patch with Claude Code run: bash scripts/ci/generate_patch.sh /tmp/failure.json /tmp/patch.diff - name: Validate patch run: bash scripts/ci/validate_patch.sh /tmp/patch.diff - name: Open fix PR run: bash scripts/ci/open_fix_pr.sh /tmp/patch.diff Summary A reliable CI self-healing loop is not about a \u0026ldquo;smarter model\u0026rdquo;. It\u0026rsquo;s about clear boundaries, risk tiers, and human checkpoints.\nIf you only have one week, implement three things first: failure classification, minimal patch generation, and human approval gates.\n","permalink":"https://www.mfun.ink/en/2026/04/06/claude-code-github-actions-ci-self-healing-pipeline/","summary":"\u003cp\u003eIf your CI keeps failing and engineers keep babysitting logs, you\u0026rsquo;re paying an invisible velocity tax. A production-grade AI self-healing pipeline is not \u0026ldquo;let the agent edit anything\u0026rdquo;. It\u0026rsquo;s a controlled loop: \u003cstrong\u003eattribution, patching, approval, rollback\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThis post gives you a deployable baseline: Claude Code proposes a minimal fix patch, GitHub Actions enforces risk gates and regression checks, and humans only approve at high-impact checkpoints.\u003c/p\u003e","title":"Claude Code + GitHub Actions CI Self-Healing Pipeline: Error Attribution, Minimal Patches, and Human Approval Gates"},{"content":"When Claude API starts returning 429 under high load, most systems don\u0026rsquo;t just slow down—they collapse: queue buildup, retry storms, upstream timeout chains, and pager noise.\nThis guide gives you a production-ready approach: adaptive concurrency + exponential backoff with full jitter + quota isolation. The goal is not “zero 429”, but keeping throttling inside a recoverable zone while preserving SLO.\nSet Targets First: Optimize for Recovery, Not Zero Errors Use three guardrail metrics:\n429 ratio: \u0026lt; 2% (5-minute window) P95 end-to-end latency: \u0026lt; 8s (including queue wait) Retry amplification: \u0026lt; 1.3x (average attempts per original request) If these hold, users usually don\u0026rsquo;t feel the incident.\n1) Replace Fixed Concurrency with Adaptive Concurrency Why fixed limits fail A hard-coded cap (for example, always 64) becomes dangerous when traffic spikes or provider quota changes suddenly.\nAIMD pattern Increase slowly on healthy windows (Additive Increase) Cut fast on 429/5xx bursts (Multiplicative Decrease) type Gate struct { max int64 // atomic } func (g *Gate) OnHealthyWindow() { cur := atomic.LoadInt64(\u0026amp;g.max) atomic.StoreInt64(\u0026amp;g.max, min(cur+1, 128)) } func (g *Gate) OnThrottle() { cur := atomic.LoadInt64(\u0026amp;g.max) next := int64(float64(cur) * 0.7) if next \u0026lt; 4 { next = 4 } atomic.StoreInt64(\u0026amp;g.max, next) } Production defaults:\nUpper bound: no more than 1.2x proven stable throughput Lower bound: keep at least 4~8 Allow increase only every 30~60s to avoid oscillation 2) Smart Retry Only: Exponential Backoff + Full Jitter Immediate retry after 429 is the #1 amplifier of incidents.\nSafer policy Initial retry base: 200~400ms Backoff: base * 2^attempt Jitter: rand(0, backoff) (Full Jitter) Max retries: 2~3 for interactive traffic func backoff(attempt int, base, capMs int) time.Duration { max := base * (1 \u0026lt;\u0026lt; attempt) if max \u0026gt; capMs { max = capMs } return time.Duration(rand.Intn(max+1)) * time.Millisecond } Tie retry budget to timeout budget. If request budget is 8 seconds, don\u0026rsquo;t schedule a third retry at second 9.\n3) Quota Isolation: One Tenant Must Not Sink Everyone For multi-tenant gateways, shared global pools are risky.\nMinimal viable isolation Global pool to protect provider quota Per-tenant pool to cap bursts Priority lanes: interactive \u0026gt; batch Example split:\nGlobal concurrency: 60 Guaranteed premium capacity: 20 Shared regular capacity: 40 Batch cap: 15 (preemptible) Now batch spikes won\u0026rsquo;t drown critical online traffic.\n4) Circuit Breaker + Half-Open Recovery When 429/5xx crosses a threshold, short-circuit briefly:\nOpen if 30s error rate \u0026gt; 25% Open for 10~20s Half-open probes at 5~10% traffic Gradual ramp-up only after probe success Pair this with graceful degradation (cached summary, delayed response notice) instead of blind timeouts.\n5) Observability: Without These Graphs, You\u0026rsquo;re Flying Blind Track at least:\nrequests_total{model,status} retry_attempts_histogram queue_wait_ms_p95 inflight_by_tenant circuit_breaker_state Debug order:\nCheck if 429 is concentrated in one model/tenant Verify queue wait inflation trend Confirm retry amplification is under control 6) Drop-In Emergency Config Use this to stop the bleeding first:\nllm_gateway: timeout_ms: 8000 max_inflight_global: 48 max_inflight_per_tenant: 12 retry: max_attempts: 3 base_backoff_ms: 250 cap_backoff_ms: 2500 jitter: full circuit_breaker: error_rate_threshold: 0.25 open_seconds: 15 half_open_probe_ratio: 0.1 Stabilize first, then tune against real traffic profiles.\nCommon Mistakes Mistake 1: Treating 429 as random network noise → infinite retry loop Mistake 2: Flat priority for all requests → critical traffic starved by batch jobs Mistake 3: Global limit only, no tenant isolation → one noisy tenant hurts all others Summary 429 from Claude API is normal. Uncontrolled retry dynamics are not.\nThree controls reduce incident risk dramatically:\nAdaptive concurrency Exponential backoff with full jitter Quota isolation plus circuit-breaker recovery Build for graceful degradation, fast recovery, and observability. Stable systems monetize better than spiky systems.\n","permalink":"https://www.mfun.ink/en/2026/04/03/claude-api-rate-limit-storm-adaptive-concurrency-backoff-quota-isolation/","summary":"\u003cp\u003eWhen Claude API starts returning 429 under high load, most systems don\u0026rsquo;t just slow down—they collapse: queue buildup, retry storms, upstream timeout chains, and pager noise.\u003c/p\u003e","title":"Claude API Rate-Limit Storm Playbook: Adaptive Concurrency, Jittered Backoff, and Quota Isolation"},{"content":"Running both Claude and OpenAI in production sounds resilient—until a slow failure hits: latency climbs, 429s spike, quality drifts, and everything still looks “up.”\nThis guide gives you a practical dual-stack degradation runbook: timeout probing first, circuit-based cutover second, and an error-budget dashboard to keep business impact bounded.\nTL;DR Put all traffic behind one provider gateway and tag every request with provider/model/tenant. Timeout before retry. Per-request timeout should stay within 60% of user-facing SLA. Use a 3-phase cutover: soft shift → hard cut → canary return. Manage incidents with a rolling 30-day error budget, not pure intuition. 1) Timeout probing: detect “slow” before “down” Split timeout telemetry into connect_timeout, ttfb_timeout, and total_timeout. Looking at only total timeout is too late.\n# provider probe every 30s curl -sS http://gateway.internal/health/providers | jq \u0026#39;.providers[] | {name,p95_latency,error_rate,timeout_rate}\u0026#39; # 15m timeout ratio by provider curl -G \u0026#39;http://prometheus.internal/api/v1/query\u0026#39; --data-urlencode \u0026#39;query=sum(rate(llm_requests_total{status=\u0026#34;timeout\u0026#34;}[15m])) by (provider) / sum(rate(llm_requests_total[15m])) by (provider)\u0026#39; Suggested alert thresholds:\ntimeout_rate \u0026gt; 3% for 5 minutes: enter soft-degrade watch mode timeout_rate \u0026gt; 8% or p95 \u0026gt; SLA x 1.8: candidate for hard cut 2) Circuit-based cutover in 3 phases 2.1 Soft shift Route 20% traffic to backup provider and watch for 10 minutes:\nroutes: - tenant: default policy: primary: claude secondary: openai weights: claude: 80 openai: 20 failover: on_timeout: true on_5xx: true max_retries: 1 2.2 Hard cut If primary trips circuit conditions (for example, 5-minute error rate \u0026gt; 12%), fully switch and freeze rollback briefly:\ngatewayctl circuit open --provider claude --reason \u0026#39;timeout_storm_5m\u0026#39; gatewayctl route set --tenant default --primary openai --secondary claude 2.3 Canary return Recover gradually: 10% → 30% → 50%, with at least 15 minutes per phase.\n3) Error-budget dashboard: your operational brake line For 99.5% monthly availability, the 30-day downtime budget is about 216 minutes. Split by tenant tier:\nTier-1 paid tenants: if budget usage \u0026gt; 70%, disable expensive fallback paths. Tier-2 standard tenants: if usage \u0026gt; 85%, enforce short context + single retry. Internal tooling: throttle first when budget is burned. SELECT provider, tenant, SUM(error_requests) / NULLIF(SUM(total_requests),0) AS error_rate, SUM(timeout_requests) / NULLIF(SUM(total_requests),0) AS timeout_rate FROM llm_sli_5m WHERE ts \u0026gt;= now() - interval \u0026#39;30 days\u0026#39; GROUP BY provider, tenant; 4) Common failure modes Cost spikes after failover: backup model has larger context by default + retries left open. Cap max_tokens to 70% baseline and retries to 1. Both providers are slow: this is request-shape debt, not routing debt. Split long prompts, enable semantic cache, move non-realtime jobs to batch. Circuit flapping: threshold too sensitive. Add cooldown (8–15 min) and minimum sample size. 5) MVP you can ship today Add three gateway metrics: timeout rate, p95 latency, and 5xx rate. Turn on soft shift (80/20). Add hard-cut command + 10-minute rollback freeze. Build and alert on a 30-day error-budget panel. Start with observability + soft shift. That alone usually stabilizes your incident curve fast.\n","permalink":"https://www.mfun.ink/en/2026/04/01/claude-openai-dual-stack-degrade-runbook/","summary":"\u003cp\u003eRunning both Claude and OpenAI in production sounds resilient—until a \u003cstrong\u003eslow failure\u003c/strong\u003e hits: latency climbs, 429s spike, quality drifts, and everything still looks “up.”\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical dual-stack degradation runbook: timeout probing first, circuit-based cutover second, and an error-budget dashboard to keep business impact bounded.\u003c/p\u003e","title":"Claude 3.7 + OpenAI Responses Dual-Stack Degradation Playbook: Timeout Probing, Circuit Cutover, and Error-Budget Dashboard"},{"content":"If your production stack calls both Claude and OpenAI, the hard part is not API integration. The hard part is keeping user experience stable when one provider starts throwing 429/5xx spikes, regional latency, or timeout storms.\nThis guide gives you a practical dual-provider gateway playbook: health probes, circuit breaking, SLA-aware fallback, and observability loops. The goal is not “never fail.” The goal is controlled failure with controlled cost and controlled latency.\nTL;DR: Three defense layers Layer 1: Active health probes (minute-level) Track p95 latency, error rate, and 429 ratio per model Layer 2: Circuit breaker + half-open probing (second-level) Open on consecutive failures, then probe recovery in half-open mode Layer 3: SLA fallback policy (request-level) Route by tier: quality-first / cost-first / latency-first Without these three, “multi-provider” usually means two invoices and one outage.\nArchitecture flow Client sends x-sla-tier (gold/silver/bronze) and x-intent (chat/code/summarize) Gateway picks a primary model (for example, claude-sonnet or gpt-4.1) Before sending, it checks circuit state and last-minute health score On failure, apply fallback path: lower model in same provider → switch provider Log every decision to metrics/events for audit and replay 1) Health probes: do not stop at “is alive” A simple /healthz=200 check is not enough. You need serviceability health.\nSuggested metrics provider_request_success_ratio_1m provider_p95_latency_ms_1m provider_429_ratio_1m provider_5xx_ratio_1m provider_timeout_ratio_1m Example score formula health_score = 100 - (p95_latency_ms / 1000) * 8 - timeout_ratio * 60 - ratio_5xx * 80 - ratio_429 * 40 if health_score \u0026lt; 65 =\u0026gt; unhealthy 2) Circuit breaking and failover: prefer graceful degradation over cascades Starter circuit breaker settings Consecutive failure threshold: 5 Open duration: 30s Half-open probe requests: 3 Half-open success threshold: 2/3 Go-style pseudocode type CircuitState string const ( Closed CircuitState = \u0026#34;closed\u0026#34; Open CircuitState = \u0026#34;open\u0026#34; HalfOpen CircuitState = \u0026#34;half_open\u0026#34; ) func Route(req Request) Target { candidates := rankedTargets(req) // rank by intent + quality + cost for _, t := range candidates { if breaker[t].Allow() \u0026amp;\u0026amp; health[t] \u0026gt;= 65 { return t } } return emergencyFallback(req) } 3) SLA fallback policy: define it up front Tiering suggestion gold (critical requests) cross-provider failover enabled + one retry + high-quality model floor silver (default requests) same-provider downgrade first, then one cross-provider switch bronze (cost-sensitive requests) no expensive fallback; return degraded result on budget cap YAML policy example sla: gold: max_retries: 1 allow_cross_provider_failover: true quality_floor: high silver: max_retries: 1 allow_cross_provider_failover: true quality_floor: medium bronze: max_retries: 0 allow_cross_provider_failover: false quality_floor: basic token_budget_per_req: 4000 4) MVP command checklist Start gateway with hot-reload policy export ROUTER_CONFIG=/etc/llm-router/policy.yaml export ROUTER_REFRESH_SEC=15 ./llm-gateway --listen :8080 Load test with fallback behavior hey -z 30s -c 20 -m POST \\ -H \u0026#39;x-sla-tier: silver\u0026#39; \\ -H \u0026#39;content-type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;intent\u0026#34;:\u0026#34;chat\u0026#34;,\u0026#34;prompt\u0026#34;:\u0026#34;explain circuit breaker\u0026#34;}\u0026#39; \\ http://127.0.0.1:8080/v1/respond Trigger a controlled breaker drill curl -X POST http://127.0.0.1:8080/admin/breakers/open \\ -d \u0026#39;{\u0026#34;provider\u0026#34;:\u0026#34;claude\u0026#34;,\u0026#34;model\u0026#34;:\u0026#34;sonnet\u0026#34;}\u0026#39; Common failures and fixes Failure 1: route flapping Symptom: traffic bounces rapidly across providers; latency gets worse.\nRoot cause: no minimum dwell time.\nFix: add hold_for: 20s; only severe higher-priority failures can interrupt.\nFailure 2: immediate re-failure after half-open Symptom: service fails again as soon as half-open starts.\nRoot cause: too much probe traffic.\nFix: limit half-open concurrency and use a tighter timeout budget (for example, 2s).\nFailure 3: cost explosion during incidents Symptom: failover shifts too much traffic to expensive models.\nRoot cause: no degraded-mode budget guardrail.\nFix: enforce degraded_mode_cost_cap; force silver/bronze path over cap.\nObservability and alerts Keep three dashboards first:\nAvailability: success/error ratio by provider/model Experience: p50/p95 latency by SLA tier Cost: token usage and cost per 1k requests Start with two high-signal alerts:\ngold tier success_ratio_5m \u0026lt; 99% failover_rate_5m \u0026gt; 15% Summary Dual-provider routing is not “two API keys.” It is a reliability system. Implement health scoring, circuit breaking, and SLA fallback first, then optimize with A/B routing, semantic caching, and adaptive budgets.\nIf you need a production-safe starting point this week, ship this MVP:\n1-minute health scoring threshold-based circuit breaker gold/silver/bronze fallback policy That alone removes most 2am incidents.\n","permalink":"https://www.mfun.ink/en/2026/03/30/claude-openai-dual-provider-gateway-failover-sla/","summary":"\u003cp\u003eIf your production stack calls both Claude and OpenAI, the hard part is not API integration. The hard part is keeping user experience stable when one provider starts throwing 429/5xx spikes, regional latency, or timeout storms.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical dual-provider gateway playbook: health probes, circuit breaking, SLA-aware fallback, and observability loops. The goal is not “never fail.” The goal is \u003cstrong\u003econtrolled failure with controlled cost and controlled latency\u003c/strong\u003e.\u003c/p\u003e","title":"Claude + OpenAI Dual-Provider Gateway Failover: Health Probes, Circuit Breaking, and SLA Fallback"},{"content":"Most streaming failures are not about “can it stream”, but “does it stay stable under load”: broken chunks, stuck clients, timeout cascades, and retry storms.\nDefine stability goals first Prioritize steady state over one-shot peak performance:\nSustainable throughput under traffic spikes. Recoverability when a stream is interrupted. Observability to isolate network/model/app bottlenecks quickly. A practical SLO baseline:\np95 time-to-first-token \u0026lt; 2.5s p99 recovery time after interruption \u0026lt; 8s streaming request error rate \u0026lt; 1% Baseline architecture: 3 control layers Split control into three layers:\nIngress layer: concurrency cap + global rate limit Application layer: connection pool + backpressure queue + timeout budget Model API layer: retry policy for 429/5xx with exponential backoff This prevents one failure from propagating through the entire path.\nBackpressure: protect the system from slow consumers Classic incident pattern: upstream keeps streaming, client consumes slowly, memory buffer grows without bound.\nRecommended policy:\nSet per-stream buffer caps (for example 256KB–1MB) On threshold breach: pause upstream reads when possible drop low-value increments when acceptable otherwise terminate the stream with retryable error Go snippet:\nconst maxBuf = 512 * 1024 if conn.BufferedBytes() \u0026gt; maxBuf { metrics.Inc(\u0026#34;stream_backpressure_trigger\u0026#34;) return ErrClientTooSlow } Chunk reassembly: from token deltas to valid output Naive concatenation causes:\nbroken UTF-8 characters unclosed markdown/code fences partial JSON objects Production-safe approach:\nKeep raw []byte buffer and validate UTF-8 before decoding. For structured outputs, run incremental parse checks. Create checkpoints every N chunks (offset + hash) for resumability. if !utf8.Valid(buf) { // wait for more bytes before decoding continue } Timeout budget: use layered timeouts, not one global timeout Suggested split:\ndial_timeout: 1s tls_handshake_timeout: 1s first_token_timeout: 3s stream_idle_timeout: 8s total_timeout: 45s stream_idle_timeout is critical. Many real failures are idle stalls, not total-duration overruns.\nRetry policy: retry only recoverable segments Avoid blind full-stream retries.\nRetry only transient network failures, 429, and short-lived 5xx. Include idempotency key (request-id) plus checkpoint metadata. Fail fast after retry budget is exhausted. Recommended defaults:\nmax_retries: 2–3 base_backoff: 300ms jitter: 20%–30% Observability: 8 metrics you should have stream_first_token_latency_ms stream_chunk_gap_ms stream_bytes_total stream_abort_client_slow_total stream_retry_total stream_resume_success_total stream_timeout_idle_total stream_error_rate You can’t optimize what you can’t observe.\nMinimal rollout checklist Per-stream buffer cap and slow-consumer protection UTF-8 safe chunk reassembly Checkpointing (offset/hash) for resume Layered timeout budget including idle timeout Bounded retry policy for retryable errors only Streaming metrics + alert thresholds Summary For OpenAI Responses streaming in production, stability comes from three things: flow control, recoverability, and observability. Build that loop first, then optimize throughput and cost.\n","permalink":"https://www.mfun.ink/en/2026/03/27/openai-responses-streaming-backpressure-chunk-reassembly-timeout-budget/","summary":"\u003cp\u003eMost streaming failures are not about “can it stream”, but “does it stay stable under load”: broken chunks, stuck clients, timeout cascades, and retry storms.\u003c/p\u003e","title":"OpenAI Responses Streaming in Production: Backpressure, Chunk Reassembly, and Timeout Budget"},{"content":"Connecting both Claude and OpenAI in production is the easy part. The hard part is keeping the system stable across the quality-latency-cost triangle.\nWithout a routing gateway, you usually get latency spikes, runaway bills, and ugly cascading failures.\n1) Define routing objectives before writing any policy Treat routing as an SLO problem, not a vibe-based decision:\nLatency target: keep P95 under your business threshold (for example, \u0026lt; 3s) Cost target: cap per-request token cost (for example, \u0026lt;$0.02) Quality target: keep task success rate above a hard floor (for example, \u0026gt;= 97%) Then classify traffic by business value:\nL0 (high value): checkout, risk controls, escalated support L1 (medium value): normal Q\u0026amp;A, report generation L2 (low value): rewrite, draft, tagging No tiers, no control.\n2) Latency tiers: fast first, stable second A pragmatic policy stack:\nPrimary model per tier L0: quality-first primary L1/L2: value-first primary Soft-timeout failover If no first token in ~1200ms, trigger a hedged request to the fallback provider Return the first successful response, cancel the slower one Circuit breaker + half-open probes Trip breaker on consecutive failures (for example, 5 failures in 30s) Probe recovery with controlled half-open traffic type RoutePolicy struct { SoftTimeoutMs int MaxCostUSD float64 Tier string // L0/L1/L2 } func Route(req Request, p RoutePolicy) Provider { if p.Tier == \u0026#34;L0\u0026#34; { return providerWithBestQuality() } if req.EstimatedCostUSD() \u0026gt; p.MaxCostUSD { return cheaperProvider() } return providerWithLowestP95() } 3) Cost caps: estimate first, call later Most teams fail here by watching price per token but ignoring total token volume.\nAt the gateway layer, implement:\nPre-call cost estimation (input + expected output) Hard cap policy (degrade or block before the request executes) Use 3 thresholds:\nwarn_threshold: alert only degrade_threshold: switch model or reduce max tokens block_threshold: reject sync path and enqueue async job 4) Quality guardrails: A/B is not enough A/B testing alone misses regressions in edge cases. Use a fixed regression gate:\nGold dataset for high-risk scenarios (refusal, safety, structured outputs) Weekly regression for every policy/model change Progressive rollout (1% → 10% → 50% → 100%) Automatic rollback on metric breach Minimum metrics to track:\nsuccess rate and P50/P95 latency by provider/model cost per 1k requests and token consumption task-level quality score (hybrid auto + human) 5) Production checklist L0/L1/L2 traffic tiers finalized Soft-timeout + hedged requests configured Circuit breakers and half-open probes configured Warn/degrade/block cost caps configured Gold set + weekly regression pipeline ready Progressive rollout + auto rollback enabled 6) Common failure modes Failure mode 1: price-only routing hurts critical quality Fix: pin L0 traffic to quality-priority pools.\nFailure mode 2: serial retry inflates tail latency Fix: use soft-timeout hedging with bounded parallelism.\nFailure mode 3: cost alerts come too late Fix: estimate and gate budget at request entry.\nSummary A dual-provider Claude + OpenAI stack only works at scale when latency tiers, cost caps, and quality guardrails are first-class routing primitives.\nHard-code policy intent first, then automate execution. That is how you stay stable under real traffic.\n","permalink":"https://www.mfun.ink/en/2026/03/25/claude-openai-model-routing-gateway-latency-cost-quality/","summary":"\u003cp\u003eConnecting both Claude and OpenAI in production is the easy part. The hard part is keeping the system stable across the quality-latency-cost triangle.\u003cbr\u003e\nWithout a routing gateway, you usually get latency spikes, runaway bills, and ugly cascading failures.\u003c/p\u003e","title":"Claude + OpenAI Model Routing Gateway: Latency Tiers, Cost Caps, and Quality Guardrails"},{"content":"In production, the painful part is not “streaming is slow.” It’s “streaming breaks and then duplicates output after reconnect.”\nThis guide gives you a practical recovery loop: delta persistence + resume token + idempotent dedup, so reconnection does not replay garbage.\nFailure model first: what are you actually fixing? For OpenAI Responses streaming pipelines, the common failures are:\nTransient network break: client socket dies, server job continues. Client timeout cancellation: app timeout is shorter than generation budget. Duplicate consumption: reconnect resumes from wrong offset and repeats text. Multi-worker race: same request_id processed by multiple workers. If you don’t separate these modes, your “fixes” will be random.\nMinimal stable architecture (conservative mode) Use a three-layer state model:\nL1 in-memory buffer: low-latency append for active connections. L2 Redis Stream: append-only delta events by response_id. L3 Postgres snapshot: final deduplicated text + metadata. Required fields per delta event:\n{ \u0026#34;response_id\u0026#34;: \u0026#34;resp_xxx\u0026#34;, \u0026#34;segment_seq\u0026#34;: 128, \u0026#34;chunk_id\u0026#34;: \u0026#34;ck_8f2...\u0026#34;, \u0026#34;delta_text\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;delta_hash\u0026#34;: \u0026#34;sha1(...)\u0026#34;, \u0026#34;created_at\u0026#34;: \u0026#34;2026-03-23T01:13:09Z\u0026#34; } segment_seq + delta_hash is your anti-dup backbone.\nGo template: consume, persist, checkpoint This template does three things:\npersists every delta to Redis (replayable), deduplicates in-memory (cheap), supports checkpoint-based recovery. package stream import ( \u0026#34;context\u0026#34; \u0026#34;crypto/sha1\u0026#34; \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/redis/go-redis/v9\u0026#34; ) type DeltaEvent struct { ResponseID string SegmentSeq int64 ChunkID string Text string At time.Time } type CheckpointStore interface { Save(ctx context.Context, responseID string, seq int64, resumeToken string) error Load(ctx context.Context, responseID string) (seq int64, resumeToken string, err error) } type Deduper struct { seen map[string]struct{} } func NewDeduper() *Deduper { return \u0026amp;Deduper{seen: make(map[string]struct{}, 4096)} } func (d *Deduper) SeenOrSet(seq int64, text string) bool { h := sha1.Sum([]byte(fmt.Sprintf(\u0026#34;%d|%s\u0026#34;, seq, text))) k := hex.EncodeToString(h[:]) if _, ok := d.seen[k]; ok { return true } d.seen[k] = struct{}{} return false } func PersistDelta(ctx context.Context, rdb *redis.Client, ev DeltaEvent) error { key := fmt.Sprintf(\u0026#34;resp:%s:deltas\u0026#34;, ev.ResponseID) return rdb.XAdd(ctx, \u0026amp;redis.XAddArgs{ Stream: key, Values: map[string]any{ \u0026#34;response_id\u0026#34;: ev.ResponseID, \u0026#34;segment_seq\u0026#34;: ev.SegmentSeq, \u0026#34;chunk_id\u0026#34;: ev.ChunkID, \u0026#34;delta_text\u0026#34;: ev.Text, \u0026#34;ts\u0026#34;: ev.At.UnixMilli(), }, }).Err() } Resume token usage without foot-guns Recommended policy:\ncheckpoint every N events (N=20) or every 1 second. checkpoint fields: last_seq, resume_token, updated_at. on reconnect, request only events after last_seq + 1. Pseudo logic:\non reconnect: cp = loadCheckpoint(response_id) stream = openStream(resume_token=cp.resume_token) for delta in stream: if delta.seq \u0026lt;= cp.last_seq: skip if dedupe(delta.seq, delta.text): skip append(delta) if should_checkpoint(): save(delta.seq, delta.resume_token) Do not recover by wall-clock time; clock skew will burn you.\nDedup strategy: text equality is not enough If you dedup only by delta_text, you will misfire on tiny repeated fragments (\u0026quot;.\u0026quot;, \u0026quot;\\n\u0026quot;, etc.).\nSafer keys:\nprimary: response_id + segment_seq fallback: response_id + delta_hash(text) Suggested SQL constraints:\ncreate table if not exists response_deltas ( response_id text not null, segment_seq bigint not null, delta_hash text not null, delta_text text not null, created_at timestamptz not null default now(), primary key (response_id, segment_seq) ); create unique index if not exists uq_response_hash on response_deltas(response_id, delta_hash); Timeout budget alignment Many “stream failures” are timeout mismatches:\nAPI timeout: 45s upstream gateway timeout: 30s client read timeout: 20s Result: client dies first, server still runs, reconnect duplicates output.\nConservative baseline:\nclient read timeout \u0026gt;= 90s keepalive on gateway idle timeout \u0026gt;= client timeout + 15s generation budget controlled separately from socket timeout Failure drills before production 3s network break: verify resume without duplicate output. forced 502 from proxy: verify reconnect + checkpoint correctness. two workers recover same response_id: verify uniqueness constraints block duplicates. temporary Redis outage: verify memory fallback + later backfill. Useful macOS network chaos drill:\n# 25% packet loss + 200ms delay (remember to reset) sudo dnctl pipe 1 config delay 200ms plr 0.25 sudo pfctl -E printf \u0026#34;dummynet out quick proto tcp from any to any pipe 1\\n\u0026#34; | sudo pfctl -f - # reset sudo pfctl -d sudo dnctl -q flush Metrics that actually matter Track at least these six:\nstream_resume_success_rate stream_duplicate_drop_count checkpoint_lag_ms delta_persist_p99_ms resume_from_stale_checkpoint_count same_response_multi_worker_conflict Threshold examples:\nresume success \u0026lt; 99% =\u0026gt; page the team duplicate drop rate \u0026gt; 3% =\u0026gt; investigate replay path checkpoint lag p99 \u0026gt; 1500ms =\u0026gt; scale persistence path Summary This design optimizes for explainable stability:\ncheckpoint makes reconnection deterministic, uniqueness constraints block duplicate replay, metrics make failures diagnosable. Get recovery correctness first. Then chase lower latency.\n","permalink":"https://www.mfun.ink/en/2026/03/23/openai-responses-go-stream-resume-delta-dedup/","summary":"\u003cp\u003eIn production, the painful part is not “streaming is slow.” It’s “streaming breaks and then duplicates output after reconnect.”\u003cbr\u003e\nThis guide gives you a practical recovery loop: \u003cstrong\u003edelta persistence + resume token + idempotent dedup\u003c/strong\u003e, so reconnection does not replay garbage.\u003c/p\u003e","title":"OpenAI Responses + Go Stream Recovery: Delta Persistence, Resume Tokens, and Duplicate Chunk Dedup"},{"content":"Most multi-tenant AI platforms fail for two boring reasons: one tenant saturates shared capacity, and finance discovers the burn too late.\nThis guide gives you a practical Go blueprint: token-bucket throttling, budget circuit breakers, and request-level cost attribution.\nThree hard goals (conservative but production-safe) Isolation: one noisy tenant must not degrade others. Control: over-budget traffic is degraded or blocked automatically. Attribution: every request is traceable to tenant, model, and cost. If you do not have all three, do not optimize routing yet. First, stop the bleeding.\nArchitecture: three gates in order Tenant token bucket (QPS/TPM) Budget gate (hour/day/month windows) Provider call + usage writeback Client -\u0026gt; API Gateway -\u0026gt; Tenant Middleware -\u0026gt; Token Bucket (tenant) -\u0026gt; Budget Guard (tenant/model/time-window) -\u0026gt; OpenAI Responses API -\u0026gt; Usage Collector -\u0026gt; Cost Ledger 1) Token buckets: tenant-level + model-level A global limiter is not enough.\nTenant bucket: caps total tenant throughput Model bucket: caps expensive model traffic separately package quota import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; \u0026#34;golang.org/x/time/rate\u0026#34; ) type BucketSet struct { TenantLimiter *rate.Limiter ModelLimiter map[string]*rate.Limiter } func (b *BucketSet) Allow(ctx context.Context, model string) error { if !b.TenantLimiter.Allow() { return fmt.Errorf(\u0026#34;tenant_rate_limited\u0026#34;) } lim, ok := b.ModelLimiter[model] if ok \u0026amp;\u0026amp; !lim.Allow() { return fmt.Errorf(\u0026#34;model_rate_limited\u0026#34;) } return nil } func NewDefaultBucketSet() *BucketSet { return \u0026amp;BucketSet{ TenantLimiter: rate.NewLimiter(rate.Every(50*time.Millisecond), 20), ModelLimiter: map[string]*rate.Limiter{ \u0026#34;gpt-5.3\u0026#34;: rate.NewLimiter(rate.Every(100*time.Millisecond), 8), \u0026#34;gpt-5.3-mini\u0026#34;: rate.NewLimiter(rate.Every(40*time.Millisecond), 30), }, } } 2) Budget circuit breaker: close earlier, not later Use three levels:\nhourly_soft_limit: degrade daily_hard_limit: block non-critical traffic monthly_hard_limit: strict block except allowlist package quota import \u0026#34;fmt\u0026#34; type BudgetSnapshot struct { TenantID string HourlyUsedUSD float64 DailyUsedUSD float64 MonthlyUsedUSD float64 HourlySoftUSD float64 DailyHardUSD float64 MonthlyHardUSD float64 } type Decision struct { Action string // allow | degrade | block Reason string } func EvaluateBudget(b BudgetSnapshot) Decision { if b.MonthlyUsedUSD \u0026gt;= b.MonthlyHardUSD { return Decision{Action: \u0026#34;block\u0026#34;, Reason: \u0026#34;monthly_budget_exceeded\u0026#34;} } if b.DailyUsedUSD \u0026gt;= b.DailyHardUSD { return Decision{Action: \u0026#34;block\u0026#34;, Reason: \u0026#34;daily_budget_exceeded\u0026#34;} } if b.HourlyUsedUSD \u0026gt;= b.HourlySoftUSD { return Decision{Action: \u0026#34;degrade\u0026#34;, Reason: \u0026#34;hourly_soft_limit_reached\u0026#34;} } return Decision{Action: \u0026#34;allow\u0026#34;, Reason: \u0026#34;within_budget\u0026#34;} } func MustAllow(d Decision) error { if d.Action == \u0026#34;block\u0026#34; { return fmt.Errorf(d.Reason) } return nil } Keep degradation deterministic:\nDowngrade model tier Shrink context window Disable non-critical tool calls 3) Cost attribution: request-level ledger, not month-end guesswork Log this per request:\nrequest_id, tenant_id, user_id model, input_tokens, output_tokens latency_ms, status, cost_usd route (normal vs degraded) CREATE TABLE ai_cost_ledger ( id BIGSERIAL PRIMARY KEY, request_id TEXT NOT NULL, tenant_id TEXT NOT NULL, model TEXT NOT NULL, input_tokens INT NOT NULL, output_tokens INT NOT NULL, cost_usd NUMERIC(12,6) NOT NULL, route TEXT NOT NULL, status TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_ai_cost_ledger_tenant_time ON ai_cost_ledger(tenant_id, created_at DESC); 4) Middleware order for OpenAI Responses in Go func HandleResponsesRequest(ctx context.Context, req Req) (Resp, error) { bucket := limiterStore.Get(req.TenantID) if err := bucket.Allow(ctx, req.Model); err != nil { return Resp{}, wrap429(err) } snap := budgetStore.Snapshot(req.TenantID) decision := EvaluateBudget(snap) if decision.Action == \u0026#34;degrade\u0026#34; { req.Model = \u0026#34;gpt-5.3-mini\u0026#34; req.Route = \u0026#34;degraded_hourly_budget\u0026#34; } if err := MustAllow(decision); err != nil { return Resp{}, wrap402(err) } resp, usage, err := openaiClient.Responses(req) cost := pricing.Calc(req.Model, usage.InputTokens, usage.OutputTokens) ledger.Write(req, usage, cost, decision) budgetStore.Accumulate(req.TenantID, cost) return resp, err } Common failure modes 1) Request-only limiter, no token limiter Long prompts bypass cost assumptions and blow budgets. Add TPM limits for expensive models.\n2) Budget checks and cost writeback are inconsistent Traffic keeps passing after threshold due to race conditions. Use a consistent transactional boundary.\n3) Everything returns 500 Downstream teams cannot react correctly. Return explicit classes:\n429: tenant_rate_limited 402/429: daily_budget_exceeded 503: provider_unavailable Minimum dashboard Tenant QPS/TPM (P95) Throttle hit ratio by tenant Hour/day/month budget consumption Degradation trigger ratio Request cost distribution (P50/P95) Attribution missing ratio (target ~0) Safe default parameters (starter profile) Tenant limiter: 20 rps burst 20 Expensive model limiter: 8 rps burst 8 Hourly soft budget: 0.8% of monthly budget Daily hard budget: 8% of monthly budget Monthly hard budget: 100% contract budget Degrade order: model downshift -\u0026gt; shorter context -\u0026gt; fewer tools Run this profile for two weeks, then tune with real usage data.\nWrap-up Reliable multi-tenant AI governance is not magic. It is discipline:\nisolate with token buckets, contain with budget breakers, explain with request-level ledgers. Once these are stable, scale is finally a product problem, not a fire drill.\n","permalink":"https://www.mfun.ink/en/2026/03/20/openai-responses-go-multitenant-quota-governance/","summary":"\u003cp\u003eMost multi-tenant AI platforms fail for two boring reasons: one tenant saturates shared capacity, and finance discovers the burn too late.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical Go blueprint: token-bucket throttling, budget circuit breakers, and request-level cost attribution.\u003c/p\u003e","title":"OpenAI Responses in Go Multi-Tenant Quota Governance: Token Buckets, Budget Circuit Breakers, and Cost Attribution"},{"content":"In production Go agents, the first thing that breaks is usually not model quality. It is memory management: context grows, bills spike, and answers drift.\nUse a 3-layer memory design:\nL1: short-term conversational window (seconds) L2: rolling summary (minutes) L3: long-term retrieval memory (days) 1) Define hard rules: what stays in context vs index Do not push full chat history into every Responses call.\nUse strict rules instead:\nKeep only the last N turns in L1 (usually 8-12 turns). Summarize old turns into L2 once a threshold is reached. Store durable facts in L3 (preferences, constraints, decisions). Enforce a per-request token budget and a per-session dollar cap. 2) Go data model for layered memory type Message struct { Role string `json:\u0026#34;role\u0026#34;` Content string `json:\u0026#34;content\u0026#34;` Timestamp time.Time `json:\u0026#34;ts\u0026#34;` } type SessionMemory struct { SessionID string ShortWindow []Message // L1 RollingSummary string // L2 Budget TokenBudget } type TokenBudget struct { MaxInputTokens int ReserveOutput int HardCapUSD float64 } Rule: budget first, prompt assembly second.\n3) Prompt assembly order (critical) For each Responses request, build input in this order:\nSystem instructions L2 rolling summary L3 retrieved memory (Top-K with sources) L1 recent turns Current user message This gives better stability and lower cost.\n4) Copy-paste context explosion control (Go) func BuildInput(mem SessionMemory, retrieved []string, userInput string) []Message { var input []Message input = append(input, Message{Role: \u0026#34;system\u0026#34;, Content: \u0026#34;You are a precise coding assistant.\u0026#34;}) if mem.RollingSummary != \u0026#34;\u0026#34; { input = append(input, Message{Role: \u0026#34;system\u0026#34;, Content: \u0026#34;Session summary:\\n\u0026#34; + mem.RollingSummary}) } topK := min(4, len(retrieved)) if topK \u0026gt; 0 { input = append(input, Message{Role: \u0026#34;system\u0026#34;, Content: \u0026#34;Retrieved memory:\\n\u0026#34; + strings.Join(retrieved[:topK], \u0026#34;\\n---\\n\u0026#34;)}) } window := tail(mem.ShortWindow, 10) input = append(input, window...) input = append(input, Message{Role: \u0026#34;user\u0026#34;, Content: userInput}) return input } 5) Cost caps with two gates Gate A: pre-request token estimation If estimated_input + reserve_output \u0026gt; MaxInputTokens, reduce in this order:\nL1 window size L2 summary length L3 topK Gate B: session-level dollar cap func GuardCost(sessionCostUSD, hardCapUSD float64) error { if sessionCostUSD \u0026gt;= hardCapUSD { return fmt.Errorf(\u0026#34;cost cap reached: %.2f/%.2f\u0026#34;, sessionCostUSD, hardCapUSD) } return nil } When capped, return an explicit message. Never fail silently.\n6) L3 memory: keep signal, drop noise Store only:\nstable preferences, project-critical facts, historical decisions with rationale. Do not store:\ncasual chat, transient emotions, expired context. Practical hygiene:\ndown-rank items with no hits in 7 days, archive after 30 days, flag conflicting facts for review. 7) Production metrics you need Track at least:\nagent_input_tokens_p95 agent_output_tokens_p95 memory_retrieval_hit_rate summary_compression_ratio response_latency_p95 cost_usd_per_session If hit rate drops while input tokens rise, retrieval quality is likely degrading.\n8) Fast troubleshooting checklist Off-topic answers -\u0026gt; inspect L3 for stale facts. Cost spike -\u0026gt; verify L1 window and L2 length controls. Latency spike -\u0026gt; check accidental large topK. Repeated actions -\u0026gt; ensure summary captures completed steps. Summary Stable Go + OpenAI Responses agents are not about “remembering everything”. They are about layering, limits, and observability.\nA solid default baseline:\nL1: last 10 turns L2: 300-500 token summary L3: topK=4 Session hard cap: $0.5-$2 depending on business value Start here, then tune with real traffic and metrics.\n","permalink":"https://www.mfun.ink/en/2026/03/18/go-openai-responses-agent-memory-layering/","summary":"\u003cp\u003eIn production Go agents, the first thing that breaks is usually not model quality. It is memory management: context grows, bills spike, and answers drift.\u003c/p\u003e\n\u003cp\u003eUse a 3-layer memory design:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eL1: short-term conversational window (seconds)\u003c/li\u003e\n\u003cli\u003eL2: rolling summary (minutes)\u003c/li\u003e\n\u003cli\u003eL3: long-term retrieval memory (days)\u003c/li\u003e\n\u003c/ul\u003e","title":"Go + OpenAI Responses Agent Memory Layering: Short-Term Context, Long-Term Index, and Cost Caps"},{"content":"Most Go teams are not killed by a single API error. They are killed by a retry storm they created themselves.\nTL;DR When OpenAI starts returning 429 and 5xx under pressure, the stable pattern is:\nRate-limit at ingress with a token bucket. Retry only recoverable failures with exponential backoff + jitter. Trip a circuit breaker on sustained failure windows. Use request budgets + idempotency keys to prevent runaway retries and duplicate side effects. Track the right metrics (429 ratio, retry depth, breaker state). Why simple retries fail A common production loop looks like this:\ntraffic spike raises concurrency upstream rate limit returns 429 app retries immediately (without jitter) retries multiply load 5xx increases even more retries That is a positive feedback loop, not resilience.\n1) Token bucket first: control concurrency before anything else import \u0026#34;golang.org/x/time/rate\u0026#34; var limiter = rate.NewLimiter(rate.Limit(8), 16) // 8 rps, burst 16 func allow(ctx context.Context) error { return limiter.Wait(ctx) } Practical baseline:\nonline traffic: per-tenant + global limit batch jobs: isolated queue and quota (never steal online quota) 2) Backoff with jitter: retry only what is retryable func backoff(attempt int) time.Duration { base := 200 * time.Millisecond max := 5 * time.Second d := base * time.Duration(1\u0026lt;\u0026lt;attempt) if d \u0026gt; max { d = max } jitter := time.Duration(rand.Int63n(int64(d / 2))) return d/2 + jitter } func retryable(status int) bool { if status == 429 { return true } if status \u0026gt;= 500 \u0026amp;\u0026amp; status \u0026lt;= 599 { return true } return false } Rules that save real systems:\ndon’t retry most 4xx respect Retry-After when present cap retries to 2–3 attempts per request 3) Circuit breaker: fail fast when failure ratio is sustained Using sony/gobreaker:\ncb := gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: \u0026#34;openai-responses\u0026#34;, Interval: 30 * time.Second, Timeout: 20 * time.Second, ReadyToTrip: func(c gobreaker.Counts) bool { if c.Requests \u0026lt; 20 { return false } return float64(c.TotalFailures)/float64(c.Requests) \u0026gt;= 0.5 }, }) Open state protects both upstream and your own workers from meltdown.\n4) Request budget: retries must fit your SLA, not exceed it For an 8-second end-to-end budget:\nfirst attempt: 3.5s two retries: 1.5s each remaining headroom: network tail and serialization Budget is a hard boundary, not a suggestion.\n5) Idempotency keys: prevent duplicate cost and duplicate writes Build key from business identity + payload fingerprint:\nidempotency_key = sha256(user_id + task_id + payload_hash) short TTL result cache return cached result on replay Production incident checklist check 15-minute metrics: 429_rate, 5xx_rate, retry_attempt_avg if 429_rate \u0026gt; 5%: reduce token bucket rate by 20% if 5xx_rate \u0026gt; 10%: open breaker and pause non-critical traffic verify jitter is enabled verify Retry-After handling verify batch traffic cannot consume online capacity Minimal middleware skeleton func CallOpenAI(ctx context.Context, req *http.Request) (*http.Response, error) { if err := allow(ctx); err != nil { return nil, err } var lastErr error for attempt := 0; attempt \u0026lt;= 2; attempt++ { resp, err := cb.Execute(func() (interface{}, error) { cctx, cancel := context.WithTimeout(ctx, 3500*time.Millisecond) defer cancel() return client.Do(req.WithContext(cctx)) }) if err == nil { r := resp.(*http.Response) if !retryable(r.StatusCode) { return r, nil } lastErr = fmt.Errorf(\u0026#34;retryable status=%d\u0026#34;, r.StatusCode) } else { lastErr = err } if attempt == 2 { break } time.Sleep(backoff(attempt)) } return nil, lastErr } Final takeaway If you only “retry harder,” you amplify failures. If you control flow, budget retries, and break circuits on bad windows, your Go + OpenAI stack behaves like a production system instead of a lucky demo.\n","permalink":"https://www.mfun.ink/en/2026/03/18/go-openai-429-5xx-storm-defense-token-bucket-backoff-circuit-breaker/","summary":"\u003cp\u003eMost Go teams are not killed by a single API error. They are killed by a retry storm they created themselves.\u003c/p\u003e","title":"Handling OpenAI 429/5xx Storms in Go: Token Bucket, Exponential Backoff, and Circuit Breakers"},{"content":"You don\u0026rsquo;t need an AI reviewer that “sounds smart.” You need a gate that stops risky PRs before they hit main.\nThis post shows a production-ready minimum setup: OpenAI Responses generates structured risk output, GitHub Actions enforces tiered policies, and critical failures can trigger a one-click rollback.\nGoal and Use Cases This setup is for teams that:\nHandle many PRs and miss high-risk changes in manual review Already run CI but lack semantic quality checks (API compatibility, data leakage, privilege issues) Need AI output in auditable JSON, not free-form prose Conservative objective: block obvious high-risk changes first, then expand coverage.\nArchitecture (Conservative and Reversible) Use three layers:\nHard-rule layer: lint/test/secret scan AI semantic eval layer: Responses returns structured risk report Policy decision layer: pass/warn/block based on risk level Recommended levels:\nlow: pass medium: pass with mandatory human confirmation high: block and request fixes critical: block + trigger rollback playbook Step 1: Define an Executable Risk Schema Turn subjective review into machine-enforceable fields.\n{ \u0026#34;risk_level\u0026#34;: \u0026#34;low|medium|high|critical\u0026#34;, \u0026#34;summary\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;findings\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;security|compatibility|data-loss|performance|compliance\u0026#34;, \u0026#34;severity\u0026#34;: \u0026#34;low|medium|high|critical\u0026#34;, \u0026#34;file\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;evidence\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;fix\u0026#34;: \u0026#34;string\u0026#34; } ], \u0026#34;confidence\u0026#34;: 0.0, \u0026#34;block\u0026#34;: true, \u0026#34;rollback_recommended\u0026#34;: false } Key rules:\nNever trust block blindly; policy layer re-checks it If confidence \u0026lt; 0.6, downgrade to medium to avoid false positives Step 2: Call the Evaluator in GitHub Actions Minimal workflow fragment:\nname: pr-risk-gate on: pull_request: types: [opened, synchronize, reopened] jobs: gate: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v4 - name: Collect PR diff run: | git fetch origin ${{ github.base_ref }} --depth=1 git diff --unified=0 origin/${{ github.base_ref }}...HEAD \u0026gt; pr.diff - name: AI risk evaluation env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | python3 scripts/eval_pr_risk.py \\ --diff pr.diff \\ --schema scripts/risk_schema.json \\ --out risk.json - name: Decide gate policy run: python3 scripts/gate_decision.py --in risk.json Step 3: Enforce Tiered Blocking Recommended gate_decision.py behavior:\ncritical: exit 2 (hard stop) high: exit 1 (block) medium: exit 0 + PR comment + required reviewer low: exit 0 Example:\npython3 scripts/gate_decision.py --in risk.json code=$? if [ \u0026#34;$code\u0026#34; -eq 2 ]; then echo \u0026#34;critical risk, trigger rollback plan\u0026#34; bash scripts/rollback_to_last_green.sh exit 1 elif [ \u0026#34;$code\u0026#34; -eq 1 ]; then echo \u0026#34;high risk, block merge\u0026#34; exit 1 fi Step 4: One-Click Rollback (Only for Mainline Incidents) Rollback should require both:\nCurrent deployment is tied to the risky change Last green commit is traceable #!/usr/bin/env bash set -euo pipefail LAST_GREEN_SHA=$(cat .ci/last-green.sha) git fetch origin --depth=20 git checkout main git reset --hard \u0026#34;$LAST_GREEN_SHA\u0026#34; git push origin main --force-with-lease Use force-with-lease only inside a controlled incident process with audit logs.\nCommon Failure Modes and Fixes 1) Too many false positives Cause: broad prompt + loose schema.\nFix: narrow to three classes first: security/compatibility/data-loss.\n2) Slow pipeline Cause: sending full repository context.\nFix: evaluate incremental diff plus short file summaries with token budgets.\n3) Blocking without actionable fixes Cause: evaluator reports issues but no repair steps.\nFix: require executable fix guidance; if missing, auto-downgrade to medium.\nMVP Rollout Checklist PR diff extraction Structured risk schema Gate decision script with exit-code policy PR comment template for fast human triage Rollback script + last-green tracking Ship these five first. Then iterate.\nSummary The right pattern is not “AI decides.” It is “AI provides structured evidence, policy layer makes the final call.” That is what scales safely.\n","permalink":"https://www.mfun.ink/en/2026/03/16/openai-responses-github-actions-pr-risk-gate/","summary":"\u003cp\u003eYou don\u0026rsquo;t need an AI reviewer that “sounds smart.” You need a gate that \u003cstrong\u003estops risky PRs before they hit main\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThis post shows a production-ready minimum setup: OpenAI Responses generates structured risk output, GitHub Actions enforces tiered policies, and critical failures can trigger a one-click rollback.\u003c/p\u003e","title":"OpenAI Responses + GitHub Actions PR Risk Gate: Automated Evals, Tiered Blocking, and One-Click Rollback"},{"content":"Short answer: if your workload is delay-tolerant, batchable, and replay-safe, move it from online calls to Batch API. The savings are real, but only if you design splitting, failure routing, and replay first.\nMany teams treat Batch API as a cheaper sync endpoint. That usually creates a replay mess instead of stable savings. A conservative rollout starts with cost boundaries and SLOs, then implements offline batching and controlled replay.\nPractical rollout plan (conservative mode) Use this order:\nSplit tasks into “offline-safe” vs “must-be-realtime”. Define batch windows and max batch size. Attach custom_id to every task for traceability and idempotency. Route outcomes into success, retryable failure, non-retryable failure. Canary first, full switch later. 1) Define cost boundaries before coding Answer three questions first:\nLatency tolerance: how late can results arrive (1h / 6h / 24h)? Failure tolerance: what replay ratio is acceptable? Budget ceiling: what is the hard daily spending cap? Minimal budget dashboard with local logs:\njq -r \u0026#39;.date + \u0026#34;\\t\u0026#34; + (.input|tostring) + \u0026#34;\\t\u0026#34; + (.ok|tostring) + \u0026#34;\\t\u0026#34; + (.fail|tostring)\u0026#39; /Users/wow/dev/book/mengboy/tmp/batch-metrics/*.json python3 - \u0026lt;\u0026lt;\u0026#39;PY\u0026#39; import json,glob for f in glob.glob(\u0026#39;/Users/wow/dev/book/mengboy/tmp/batch-metrics/*.json\u0026#39;): d=json.load(open(f)) rate=d[\u0026#39;fail\u0026#39;]/max(d[\u0026#39;input\u0026#39;],1) if rate\u0026gt;0.03: print(\u0026#39;ALERT\u0026#39;, f, f\u0026#39;{rate:.2%}\u0026#39;) PY 2) Offline splitting: optimize for stable throughput, not max peak Conservative defaults:\nStart with small batches (for example 200~500 records) Use fixed windows (for example every 15 minutes) Submit on either threshold: window timeout or item cap In Go, normalize tasks and enforce unique custom_id:\ntype BatchTask struct { CustomID string `json:\u0026#34;custom_id\u0026#34;` Method string `json:\u0026#34;method\u0026#34;` URL string `json:\u0026#34;url\u0026#34;` Body json.RawMessage `json:\u0026#34;body\u0026#34;` } func buildCustomID(bizKey string, ts int64) string { return fmt.Sprintf(\u0026#34;%s-%d\u0026#34;, bizKey, ts) } Persist JSONL input for audit and replay:\nmkdir -p /Users/wow/dev/book/mengboy/tmp/batch-input ls -lh /Users/wow/dev/book/mengboy/tmp/batch-input 3) Failure replay: replay only retryable failures Do not replay everything blindly. Use three buckets:\nSUCCESS: store result and close task RETRYABLE_FAIL: replay with capped attempts (for example 3) FINAL_FAIL: move to manual queue Keep replay rules centralized:\nfunc retryable(code string) bool { switch code { case \u0026#34;rate_limit\u0026#34;, \u0026#34;timeout\u0026#34;, \u0026#34;server_error\u0026#34;: return true default: return false } } Replay generation should be executable as a standalone command:\npython3 /Users/wow/dev/book/mengboy/scripts/rebuild_batch_replay.py \\ --failed /Users/wow/dev/book/mengboy/tmp/batch-failed/failed-2026-03-13.jsonl \\ --out /Users/wow/dev/book/mengboy/tmp/batch-replay/replay-2026-03-13.jsonl 4) Idempotency first, automation second The classic replay incident is duplicated inserts.\nMinimum requirements:\nunique key on business table (custom_id) upsert writes track replay_count ALTER TABLE ai_batch_result ADD CONSTRAINT uk_custom_id UNIQUE (custom_id); 5) Deployment acceptance metrics Do not use “request success” as your only KPI. Track at least:\ndaily cost within target range controllable failure ratio stable post-replay success ratio manageable manual fallback volume For conservative execution, keep part of the critical path on realtime mode until offline replay remains stable for multiple cycles.\nMVP checklist If time is tight, do these five first:\nmigrate only non-realtime tasks to Batch API enforce custom_id on every task split failures into retryable/non-retryable cap replay attempts to 3, then manual fallback publish daily cost + failure report This is usually enough to reduce cost significantly without compromising stability.\n","permalink":"https://www.mfun.ink/en/2026/03/13/openai-batch-api-go-cost-control-offline-batching-failure-replay/","summary":"\u003cp\u003eShort answer: if your workload is \u003cstrong\u003edelay-tolerant, batchable, and replay-safe\u003c/strong\u003e, move it from online calls to Batch API. The savings are real, but only if you design splitting, failure routing, and replay first.\u003c/p\u003e\n\u003cp\u003eMany teams treat Batch API as a cheaper sync endpoint. That usually creates a replay mess instead of stable savings. A conservative rollout starts with cost boundaries and SLOs, then implements offline batching and controlled replay.\u003c/p\u003e","title":"OpenAI Batch API with Go: Offline Batching, Failure Replay, and Cost Boundaries"},{"content":"The hardest part of Structured Outputs is not getting JSON once. It is surviving schema changes without turning production into a small fire with excellent logs and terrible business results.\nOnce a Go service starts evolving prompts and response contracts, the usual failure modes show up fast: a new required field breaks older consumers, an enum expands and strict validation kills valid requests, or one bad sample drags the whole chain into retries and rollback panic.\nTL;DR: conservative rollout needs four guardrails Version every schema instead of editing in place. Decode on two tracks so strict parsing can fall back safely. Ramp traffic gradually instead of flipping 100% at once. Prepare rollback commands first instead of inventing them during the outage. If you are using OpenAI Responses Structured Outputs from Go, the winning mindset is not “maximum strictness”. It is evolvable, observable, and reversible.\nHow these incidents usually start The pattern is painfully common:\nyou add two new required fields you expand an enum that downstream code does not recognize you change steps from a string to an object array you assume model output always matches the latest schema you return HTTP 500 on strict validation failure Then the boring disaster begins:\nthe new schema hits production and old traffic starts failing retries amplify bad samples into a storm logs only say invalid json schema output the real problem is not the model — it is overconfident rollout 1) Never do in-place schema surgery Use explicit versions for structural changes.\nA practical payload shape looks like this:\n{ \u0026#34;schema_version\u0026#34;: \u0026#34;v2\u0026#34;, \u0026#34;ticket_type\u0026#34;: \u0026#34;billing\u0026#34;, \u0026#34;priority\u0026#34;: \u0026#34;high\u0026#34;, \u0026#34;summary\u0026#34;: \u0026#34;duplicate charge after retry\u0026#34;, \u0026#34;actions\u0026#34;: [ \u0026#34;check idempotency key\u0026#34;, \u0026#34;review payment callback logs\u0026#34; ] } In Go, keep versioned structs instead of one “forever” type:\ntype TicketV1 struct { SchemaVersion string `json:\u0026#34;schema_version\u0026#34;` Type string `json:\u0026#34;ticket_type\u0026#34;` Summary string `json:\u0026#34;summary\u0026#34;` } type TicketV2 struct { SchemaVersion string `json:\u0026#34;schema_version\u0026#34;` Type string `json:\u0026#34;ticket_type\u0026#34;` Priority string `json:\u0026#34;priority\u0026#34;` Summary string `json:\u0026#34;summary\u0026#34;` Actions []string `json:\u0026#34;actions\u0026#34;` } Normalize and diff schemas before rollout instead of eyeballing them:\njq -S . schema/ticket_v1.json \u0026gt; /tmp/ticket_v1.norm.json jq -S . schema/ticket_v2.json \u0026gt; /tmp/ticket_v2.norm.json diff -u /tmp/ticket_v1.norm.json /tmp/ticket_v2.norm.json The risky questions are not “did the schema change?” but:\ndid required expand? did an enum become narrower? did a field type change? did an old field disappear? The first two break production the fastest.\n2) Bad-case fallback: strict first, compatible second A conservative decode chain is simple:\nlayer 1: decode strictly against the current version layer 2: on failure, try a compatible or tolerant path layer 3: if that still fails, degrade to a readable summary instead of wasting the whole request Example:\nfunc decodeTicket(raw []byte) (any, error) { var peek struct { SchemaVersion string `json:\u0026#34;schema_version\u0026#34;` } if err := json.Unmarshal(raw, \u0026amp;peek); err != nil { return nil, fmt.Errorf(\u0026#34;peek version: %w\u0026#34;, err) } switch peek.SchemaVersion { case \u0026#34;v2\u0026#34;: var out TicketV2 if err := json.Unmarshal(raw, \u0026amp;out); err == nil { return out, nil } return decodeTicketCompat(raw) case \u0026#34;v1\u0026#34;: var out TicketV1 if err := json.Unmarshal(raw, \u0026amp;out); err != nil { return nil, err } return out, nil default: return decodeTicketCompat(raw) } } Do not silently swallow fallback. At minimum, record:\nschema_version_detected decode_mode=strict|compat|summary fallback_reason model prompt_version Keep a replay folder for bad cases. It pays for itself quickly:\nmkdir -p /Users/wow/dev/book/mengboy/tmp/structured-output-samples/bad cp bad_case.json /Users/wow/dev/book/mengboy/tmp/structured-output-samples/bad/ go test ./... -run TestDecodeTicketCompat -v 3) Canary rollout: do not full-send on day one Structured Outputs problems often appear only on real long-tail inputs.\nSo “staging looked fine” is not evidence for a full production flip. It is evidence that staging is staging.\nA practical rollout ladder:\n5%: watch parse success and bad-case distribution 20%: inspect business field correctness 50%: observe cost, latency, and fallback ratio 100%: switch the main path only after stable windows The dumbest and most useful rollout control is still an environment variable:\nexport STRUCTURED_OUTPUT_SCHEMA_VERSION=v2 export STRUCTURED_OUTPUT_CANARY_PERCENT=20 export STRUCTURED_OUTPUT_COMPAT_FALLBACK=1 ./bin/api Check the effective runtime config:\ncurl -s http://127.0.0.1:8080/debug/config | jq \u0026#39;.structured_output\u0026#39; If compat traffic keeps growing, do not call it “good enough”. It usually means:\nprompt and schema have drifted apart certain samples hit model output boundaries your supposedly required field is not reliably produced 4) Gradual rollback: write the commands before the incident A useful rollback is not “Git has history”. A useful rollback is “production can recover in five minutes”.\nI recommend keeping two rollback modes ready:\nconfiguration rollback: switch v2 back to v1 logic rollback: keep v2 generation but force consumers onto compat Minimum rollback playbook:\nexport STRUCTURED_OUTPUT_SCHEMA_VERSION=v1 export STRUCTURED_OUTPUT_CANARY_PERCENT=0 export STRUCTURED_OUTPUT_COMPAT_FALLBACK=1 ./bin/api If you are on Kubernetes or another orchestrator, make it explicit rather than memorable:\nkubectl set env deploy/agent-api \\ STRUCTURED_OUTPUT_SCHEMA_VERSION=v1 \\ STRUCTURED_OUTPUT_CANARY_PERCENT=0 \\ STRUCTURED_OUTPUT_COMPAT_FALLBACK=1 5) Success rate is not enough Structured Outputs is sneaky because you can get HTTP 200 while business structure is already broken.\nShip at least these metrics:\nstructured_decode_total{mode,version} structured_decode_fail_total{reason} structured_fallback_total{from,to} structured_summary_degrade_total structured_field_missing_total{field} structured_enum_unknown_total{field,value} Three quick checks during an incident:\ngrep -R \u0026#34;decode_mode=compat\u0026#34; /var/log/agent-api | tail -n 50 grep -R \u0026#34;fallback_reason\u0026#34; /var/log/agent-api | sort | uniq -c grep -R \u0026#34;schema_version=v2\u0026#34; /var/log/agent-api | tail -n 100 If summary degradation is rising, the system is not “more strict”. It is just more fragile.\n6) A conservative Go rollout order that actually works If you need stable Structured Outputs within a week, my order is:\nadd schema_version implement strict + compatible decoding add bad-case replay tests only then move to full traffic Do not reverse this. Full rollout before fallback is how teams create personal character-building exercises at 2 a.m.\nCommon failures and troubleshooting 1) Success rate drops after a new required field Check first:\ndoes the prompt clearly request the field? do historical samples often omit it already? can the compat path still accept the older shape? 2) The model returns an unknown enum value Safer handling:\nmap it to unknown first log the raw value for audit do not kill the entire request over one fresh enum 3) Canary shows HTTP 200 but downstream crashes on nil That usually means decode succeeded but semantics failed.\nAdd:\nfield-level business validation default value policy nil assertions before downstream consumption Summary The real challenge of Structured Outputs is not generating JSON. It is keeping the system alive while schemas keep moving.\nFor a conservative target, the advice is blunt:\nversion the schema use dual-path decoding ramp traffic by percentage prepare rollback commands in advance Minimum viable plan If you can only fix one round today:\nadd schema_version implement strict -\u0026gt; compat -\u0026gt; summary decoding in Go keep v2 at 5% canary monitor structured_fallback_total First make the system hard to break. Pretty structure can wait. Production is not an art gallery.\n","permalink":"https://www.mfun.ink/en/2026/03/11/openai-responses-structured-outputs-go-schema-evolution-fallback-rollback/","summary":"\u003cp\u003eThe hardest part of Structured Outputs is not getting JSON once. It is surviving schema changes without turning production into a small fire with excellent logs and terrible business results.\u003c/p\u003e\n\u003cp\u003eOnce a Go service starts evolving prompts and response contracts, the usual failure modes show up fast: a new required field breaks older consumers, an enum expands and strict validation kills valid requests, or one bad sample drags the whole chain into retries and rollback panic.\u003c/p\u003e","title":"OpenAI Responses Structured Outputs with Go: Schema Evolution, Bad-Case Fallbacks, and Gradual Rollback"},{"content":"If you plan to put OpenAI Realtime into production, do not let a passing demo fool you.\nWhat usually breaks the system is not the model itself. It is non-rotating short-lived auth, missing interruption state, and zero end-to-end latency budgeting. Miss those three and your voice UX starts sounding like an angry walkie-talkie.\nTL;DR: protect these four things first Use WebRTC for browser/mobile voice. Use WebSocket only for server-side bridging. Issue short-lived session tokens and rotate them before expiry. Treat user interruption as a real state machine event, not a UI hack. Split latency into capture, uplink, inference, downlink, and playback. When to choose WebRTC and when to choose WebSocket The practical rule is simple:\nDirect user voice/audio: prefer WebRTC Server-side transcription bridges, recording, orchestration: WebSocket can work Need low-latency voice plus server audit/routing: keep WebRTC at the edge and send event summaries to the server instead of piping full audio around Why:\nWebRTC is built for real-time media transport It handles NAT, jitter, and packet loss better than a hand-rolled WebSocket voice path in most real networks WebSocket is fine for events, but a poor choice for carrying the full burden of last-mile voice UX Auth rotation: never ship long-lived keys to clients Wrong pattern:\nFrontend holds a long-lived API key No TTL on tokens Reconnect keeps using an old token until a 401 blows up the session Right pattern:\nYour Go service owns the real credential. The service issues a short-lived session token to the client. TTL stays around 1 to 5 minutes. Rotate 30 seconds before expiry; if rotation fails, reconnect gracefully. Example Go handler for issuing short-lived sessions:\ntype RealtimeSessionRequest struct { UserID string `json:\u0026#34;user_id\u0026#34;` DeviceID string `json:\u0026#34;device_id\u0026#34;` Voice string `json:\u0026#34;voice\u0026#34;` ExpiresIn int `json:\u0026#34;expires_in\u0026#34;` } func issueRealtimeSession(w http.ResponseWriter, r *http.Request) { req := RealtimeSessionRequest{ UserID: mustUserID(r.Context()), DeviceID: r.Header.Get(\u0026#34;X-Device-ID\u0026#34;), Voice: \u0026#34;alloy\u0026#34;, ExpiresIn: 300, } body, _ := json.Marshal(map[string]any{ \u0026#34;model\u0026#34;: \u0026#34;gpt-4o-realtime-preview\u0026#34;, \u0026#34;voice\u0026#34;: req.Voice, \u0026#34;expires_in\u0026#34;: req.ExpiresIn, }) upstreamReq, _ := http.NewRequest(\u0026#34;POST\u0026#34;, \u0026#34;https://api.openai.com/v1/realtime/sessions\u0026#34;, bytes.NewReader(body)) upstreamReq.Header.Set(\u0026#34;Authorization\u0026#34;, \u0026#34;Bearer \u0026#34;+os.Getenv(\u0026#34;OPENAI_API_KEY\u0026#34;)) upstreamReq.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) resp, err := http.DefaultClient.Do(upstreamReq) if err != nil { http.Error(w, \u0026#34;issue session failed\u0026#34;, http.StatusBadGateway) return } defer resp.Body.Close() io.Copy(w, resp.Body) } Rotation rule:\nfunc shouldRotate(expireAt time.Time, now time.Time) bool { return expireAt.Sub(now) \u0026lt;= 30*time.Second } Interruption recovery: stop the response transaction, not just the speaker The most common fake-stable setup in realtime voice is this: the frontend stops playback, but the backend/model side is still generating.\nThat leads to:\nthe user starts a second utterance while the previous response is still alive transcript stitching goes out of order the UI thinks it recovered while the model thinks the old turn is still active Use an explicit state machine instead:\nIDLE -\u0026gt; LISTENING -\u0026gt; THINKING -\u0026gt; SPEAKING SPEAKING --interrupt--\u0026gt; CANCELLING -\u0026gt; LISTENING THINKING --timeout/error--\u0026gt; RECOVERING -\u0026gt; LISTENING At minimum, persist:\nsession_id conversation_id response_id last_user_audio_seq last_ack_event_id When the user interrupts, do three things:\nstop local playback send cancel / truncate upstream clear the current response_id and move back to LISTENING Example:\ntype SessionState struct { SessionID string ConversationID string ResponseID string Phase string } func interrupt(state *SessionState, send func(any) error) error { if state.ResponseID == \u0026#34;\u0026#34; { state.Phase = \u0026#34;LISTENING\u0026#34; return nil } evt := map[string]any{ \u0026#34;type\u0026#34;: \u0026#34;response.cancel\u0026#34;, \u0026#34;response_id\u0026#34;: state.ResponseID, } if err := send(evt); err != nil { state.Phase = \u0026#34;RECOVERING\u0026#34; return err } state.ResponseID = \u0026#34;\u0026#34; state.Phase = \u0026#34;LISTENING\u0026#34; return nil } End-to-end latency budgets: do not stare only at model time In conservative mode, I would rather get you to a stable 800ms to 1500ms perceived first response than chase fantasy 300ms lab numbers.\nSplit the bill into five segments:\nCapture: local capture and VAD chunking Uplink: client-to-upstream transport Inference: model reasoning and event generation Downlink: event delivery back to the client Playback: player buffering and speech output A reasonable conservative budget:\nCapture: 80ms - 150ms Uplink: 80ms - 150ms Inference: 250ms - 700ms Downlink: 50ms - 120ms Playback: 80ms - 250ms At minimum, instrument these histograms in Go:\nvar ( captureMs = prometheus.NewHistogram(...) uplinkMs = prometheus.NewHistogram(...) inferMs = prometheus.NewHistogram(...) downlinkMs = prometheus.NewHistogram(...) playbackMs = prometheus.NewHistogram(...) e2eMs = prometheus.NewHistogram(...) ) Suggested reporting flow:\nstart := time.Now() // capture done captureMs.Observe(float64(time.Since(start).Milliseconds())) uplinkStart := time.Now() // send audio chunk uplinkMs.Observe(float64(time.Since(uplinkStart).Milliseconds())) inferStart := time.Now() // first model event arrived inferMs.Observe(float64(time.Since(inferStart).Milliseconds())) WebRTC recovery: split reconnects into two classes Do not treat every disconnect as the same problem. At least separate these:\n1) transport-layer blips Symptoms:\nICE failure network switch (Wi‑Fi -\u0026gt; 4G) temporary packet loss causing media interruption Action:\nrebuild the PeerConnection first try to reuse the session context check whether the token is near expiry before renegotiation 2) session-layer failures Symptoms:\nexpired token upstream 401/403 broken response/event ordering Action:\nissue a new session token create a new session restore only a short necessary summary instead of replaying all historical audio Recovery pseudo code:\nfunc recoverSession(reason string) RecoveryPlan { switch reason { case \u0026#34;ice_failed\u0026#34;, \u0026#34;network_switch\u0026#34;: return RecoveryPlan{RebuildPeer: true, ReissueToken: false, ReplaySummary: true} case \u0026#34;token_expired\u0026#34;, \u0026#34;auth_failed\u0026#34;: return RecoveryPlan{RebuildPeer: true, ReissueToken: true, ReplaySummary: true} default: return RecoveryPlan{RebuildPeer: true, ReissueToken: true, ReplaySummary: false} } } A production baseline that is actually enough Before launch, add at least these six guardrails:\nshort-lived token + rotation explicit interruption state machine first-byte and full-turn latency metrics classified alerts for 401 and ICE failures auto-recovery tests after client network switching server-side summary replay instead of unbounded session growth Recommended alerts:\nrealtime_first_audio_p95 \u0026gt; 1500ms session_rotate_fail_total \u0026gt; 0 interrupt_recovery_fail_rate \u0026gt; 5% sharp increase in ice_restart_total within 15 minutes Troubleshooting order: do not blame the model first When something breaks, check in this order:\nAuth: is the client holding an expired token? Connectivity: did ICE / NAT / network switching break media flow? State machine: is a stale response_id left behind after interruption? Budget: is the bottleneck capture, transport, inference, or playback? Replay policy: are you shoving too much old context back during recovery? Minimum viable production setup If your goal is stability first, not showmanship, start here:\nUse WebRTC for the edge voice path Let the Go service only issue short-lived tokens and store event summaries Set token TTL to 300 seconds and rotate 30 seconds early Force response.cancel on interruption Get first response under 1.5 seconds before chasing fancier optimizations Summary The hard part of OpenAI Realtime is not “connecting voice.” It is maintaining a stable experience under flaky networks, interruptible interaction, and short-lived credentials.\nIn production, the best order of work is simple: lock down auth first, fix interruption state second, then budget latency. Do those three and the system starts acting like a product instead of a demo.\n","permalink":"https://www.mfun.ink/en/2026/03/09/openai-realtime-go-webrtc-auth-recovery-latency-budget/","summary":"\u003cp\u003eIf you plan to put OpenAI Realtime into production, do not let a passing demo fool you.\u003c/p\u003e\n\u003cp\u003eWhat usually breaks the system is not the model itself. It is \u003cstrong\u003enon-rotating short-lived auth, missing interruption state, and zero end-to-end latency budgeting\u003c/strong\u003e. Miss those three and your voice UX starts sounding like an angry walkie-talkie.\u003c/p\u003e","title":"OpenAI Realtime + Go in Production: WebRTC Token Rotation, Interruption Recovery, and End-to-End Latency Budgets"},{"content":"When Go services call the OpenAI Responses API in production, the real failures are rarely about model quality. Most incidents come from transport instability: weak connection pooling, conflicting timeout layers, and retry storms.\nThis guide gives you a practical baseline: HTTP/2 reuse, layered timeout budgets, bounded retries, and error-budget driven operations.\nTL;DR baseline for production If you need a safe default today, start here:\nEnforce HTTP/2 reuse Layer timeouts (business timeout \u0026gt; per-call timeout \u0026gt; retry remaining budget) Retry only retryable failures (429/5xx/transient network errors) Cap retries at 2-3 attempts with exponential backoff + jitter Add per-instance in-flight limits 1) Transport first: fix reuse before tuning everything else A lot of “random latency spikes” are actually transport-layer issues.\ntr := \u0026amp;http.Transport{ MaxIdleConns: 512, MaxIdleConnsPerHost: 128, MaxConnsPerHost: 256, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 5 * time.Second, ExpectContinueTimeout: 1 * time.Second, ForceAttemptHTTP2: true, } client := \u0026amp;http.Client{ Transport: tr, Timeout: 25 * time.Second, // hard cap per API call } A practical starting point: MaxConnsPerHost = CPU cores * 16, then tune by p95 latency and QPS.\n2) Timeout budgets: do not collapse all timeouts into one knob Use three budget layers:\nBusiness request budget (for example, 30s) Single OpenAI call budget (for example, 25s) Per-retry remaining budget (derived from context) ctx, cancel := context.WithTimeout(parentCtx, 30*time.Second) defer cancel() resp, err := callOpenAIWithRetry(ctx, client, payload) If both business timeout and client timeout are set to 30s, retries will push requests over budget and wreck tail latency.\n3) Retries: only for failures that can recover Retryable:\nHTTP 429 HTTP 500/502/503/504 transient network errors (reset/timeout) deadline exceeded only if global budget still has room Non-retryable:\n400/401/403/404 payload or parameter validation failures hard quota failures that cannot recover in short windows func backoff(attempt int) time.Duration { base := 200 * time.Millisecond max := 2 * time.Second d := base * time.Duration(1\u0026lt;\u0026lt;attempt) if d \u0026gt; max { d = max } jitter := time.Duration(rand.Int63n(int64(120 * time.Millisecond))) return d + jitter } 4) Concurrency guard + circuit logic to stop meltdowns Add a local in-flight guard per instance:\nsem := make(chan struct{}, 64) // max 64 in-flight requests per instance func withLimit(fn func() error) error { sem \u0026lt;- struct{}{} defer func(){ \u0026lt;-sem }() return fn() } Then add a lightweight circuit condition:\n1-minute error rate \u0026gt; 20% sample size \u0026gt; 100 When triggered, temporarily degrade (lower model tier, lower concurrency, or fallback response).\n5) Minimum observability: track these 6 metrics openai_requests_total (grouped by status_code) openai_request_latency_ms (p50/p95/p99) openai_retries_total openai_timeout_total openai_inflight openai_error_budget_burn_rate Fast triage tip: check timeout_total + inflight before chasing individual error strings.\nCommon failures and fast triage 1) p95 latency suddenly doubles Check current connection pressure:\nnetstat -an | grep ESTABLISHED | wc -l lsof -iTCP -sTCP:ESTABLISHED -n -P | grep \u0026lt;your-service-name\u0026gt; | wc -l If established connections jump abnormally, inspect MaxConnsPerHost, retry storms, and upstream throttling.\n2) 429 spikes Triage order:\nMultiple services sharing one API key? Batch/offline jobs competing with online traffic? Retry policy missing jitter, causing synchronized retries? 3) High deadline exceeded ratio Check for budget conflicts across layers (gateway 20s, service 25s, client 30s often creates fake timeouts).\nMVP you can ship today At minimum, do these four:\nStabilize Transport + HTTP/2 parameters Apply layered timeout budgets (30s / 25s / retry remainder) Retry only 429/5xx/transient errors, max 2 retries Expose the 6 core Prometheus metrics That usually moves you from “random incidents” to “observable and recoverable operations.”\nSummary Reliable Go + OpenAI Responses integration is mostly a systems problem: connection reuse + timeout budgets + error-budget governance.\nGet these three right first, then optimize multi-provider routing and cost controls.\n","permalink":"https://www.mfun.ink/en/2026/03/06/go-openai-responses-connection-pool-timeout-budget/","summary":"\u003cp\u003eWhen Go services call the OpenAI Responses API in production, the real failures are rarely about model quality. Most incidents come from transport instability: weak connection pooling, conflicting timeout layers, and retry storms.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical baseline: HTTP/2 reuse, layered timeout budgets, bounded retries, and error-budget driven operations.\u003c/p\u003e","title":"Go + OpenAI Responses: Connection Pooling and Timeout Budgets from HTTP/2 Reuse to Error-Budget Control"},{"content":"The most expensive outage is not a single failure — it is a failure amplified by retries.\nIn an OpenAI Responses + Go tool-calling stack, missing idempotency, jittered backoff, and breaker thresholds can turn 10 failing requests into 1000 downstream calls in minutes.\nTL;DR: You need all three guardrails Idempotency key: one business action should apply once. Backoff + jitter: retries must spread out, not synchronize. Circuit breaker threshold: fail fast when error budget is blown. How retry storms usually start Common bad setup:\nHTTP timeout too short (for example, 3 seconds) Gateway retries 3 times + service retries 3 times No idempotency control in tool execution Fixed retry interval on all instances (no jitter) What happens next:\nA tiny upstream hiccup is amplified 9x to 27x P95 latency spikes and queues pile up Alerts fan out across API errors, DB lock contention, and cache misses Go implementation: idempotency keys Recommended key format:\nidem:{tenant}:{workflow}:{biz_id}:{step} Rules:\nBuild from business-unique fields (not random UUIDs) TTL must cover your max retry window (for example, 15 minutes) Store status, response hash, first-seen and last-updated timestamps Redis example (SETNX + TTL):\nok, err := rdb.SetNX(ctx, idemKey, \u0026#34;PENDING\u0026#34;, 15*time.Minute).Result() if err != nil { return err } if !ok { // Existing execution found: return cached outcome return ErrDuplicateSuppressed } Write a result summary after success:\n_ = rdb.Set(ctx, idemKey, \u0026#34;DONE:tool_result_hash\u0026#34;, 15*time.Minute).Err() Go implementation: exponential backoff with full jitter Wrong: fixed sleep(500ms).\nRight: exponential backoff + full jitter:\nfunc backoff(attempt int, base, cap time.Duration) time.Duration { max := base \u0026lt;\u0026lt; attempt if max \u0026gt; cap { max = cap } return time.Duration(rand.Int63n(int64(max))) } Conservative defaults:\nbase = 200ms cap = 5s maxAttempts = 4 Retry only retryable classes (429/5xx/transient network errors) Go implementation: breaker thresholds with error budget Use a 30-second sliding window:\nrequests \u0026gt;= 50 error rate \u0026gt;= 25% trigger in 2 consecutive windows → open for 20 seconds Pseudo code:\nif window.Req \u0026gt;= 50 \u0026amp;\u0026amp; window.ErrRate() \u0026gt;= 0.25 { breaker.Trip(20 * time.Second) } if breaker.Open() { return ErrFastFail } Fallback policy when open:\nReturn cached summary or last known good result Skip non-critical tools Tell users output may be partial Metrics you must ship At minimum:\ntool_call_total{tool,status} retry_total{reason} idempotency_suppressed_total breaker_open_total llm_latency_ms_p95 cost_usd_total Alert ideas:\nretry_total \u0026gt; 3x baseline in 5 minutes sudden jump in idempotency_suppressed_total sustained breaker_open_total \u0026gt; 0 Troubleshooting checklist Check 429/5xx ratio in the last 15 minutes. Confirm you do not have double retry layers. Sample failing requests and verify key stability. Verify retries are jittered, not fixed sleep. Check breaker open/half-open recovery behavior. Reconcile duplicate writes or duplicate charges. Summary Retries are not free.\nIn production Responses + Go pipelines, idempotency first, jittered retries second, circuit breaker third is the practical order that turns a potential avalanche into controlled degradation.\nIf you can do only one thing today: add idempotency keys first. It usually delivers the highest ROI immediately.\n","permalink":"https://www.mfun.ink/en/2026/03/04/openai-responses-go-retry-storm-idempotency-backoff-circuit-breaker/","summary":"\u003cp\u003eThe most expensive outage is not a single failure — it is a failure amplified by retries.\u003c/p\u003e\n\u003cp\u003eIn an OpenAI Responses + Go tool-calling stack, missing idempotency, jittered backoff, and breaker thresholds can turn 10 failing requests into 1000 downstream calls in minutes.\u003c/p\u003e","title":"OpenAI Responses + Go: Taming Retry Storms with Idempotency Keys, Jittered Backoff, and Circuit Breakers"},{"content":"Long-running agent sessions usually fail the same way: context keeps growing, latency spikes, costs blow up, and answer quality gets worse.\nThat is rarely a model-quality issue. It is almost always missing context governance.\nTypical symptoms p95 latency increases with every conversation turn token spend grows non-linearly per session answer consistency drops on repeated intents more 429/timeouts at peak traffic A practical 3-layer strategy 1) Hard truncation: budgeted window + allowlist Use a strict max_context_tokens budget (for example 24k).\nAlways preserve system constraints and critical user facts Keep regular history with a recency-first sliding window Never let request builders exceed a hard ceiling type Msg struct { Role string Content string Important bool Tokens int } func TrimByBudget(msgs []Msg, budget int) []Msg { keep := make([]Msg, 0, len(msgs)) used := 0 for _, m := range msgs { if m.Important { keep = append(keep, m) used += m.Tokens } } for i := len(msgs)-1; i \u0026gt;= 0; i-- { m := msgs[i] if m.Important { continue } if used+m.Tokens \u0026gt; budget { continue } keep = append([]Msg{m}, keep...) used += m.Tokens } return keep } 2) Summary backfill: compress old history into structured memory Avoid free-form prose summaries. Use a stable schema:\nFacts Open Issues Preferences Decisions + Why This lets you do incremental updates with less drift.\n3) Cost caps: per-session budget + graceful degradation Set a budget (for example $0.5/session):\nat 70%: lower max_output_tokens at 90%: disable expensive tools at 100%: keep only summary + latest turn Go implementation notes for Responses API isolate a dedicated “history builder” module emit metrics per request: tokens, latency, estimated cost version your summary schema (summary_schema=v2) compress tool outputs before reinjection Troubleshooting checklist duplicate history append in multiple layers raw tool output (logs/HTML) injected unbounded summary drift (assumptions recorded as facts) token-only budget without price-aware control Recommended baseline settings max_context_tokens: 24k summary_trigger_tokens: 16k max_output_tokens: 1k session_cost_cap_usd: 0.5 tool_output_hard_cap_chars: 4000 Wrap-up Context explosion is an engineering control problem.\nIf you implement truncation, structured summaries, and strict budget gates, latency and cost become predictable—and model quality work finally becomes meaningful.\n","permalink":"https://www.mfun.ink/en/2026/03/02/openai-assistants-responses-go/","summary":"\u003cp\u003eLong-running agent sessions usually fail the same way: context keeps growing, latency spikes, costs blow up, and answer quality gets worse.\u003c/p\u003e\n\u003cp\u003eThat is rarely a model-quality issue. It is almost always missing context governance.\u003c/p\u003e","title":"Taming Context Explosion in OpenAI Assistants/Responses with Go: Truncation, Summary Backfill, and Cost Caps"},{"content":"When OpenAI API calls start timing out in production, the real problem is usually not “OpenAI is down.”\nThe real problem is you don’t know which hop is failing: DNS, TLS handshake, proxy path, or your own connection pool.\n1) Split timeout into 4 hops A Go request to OpenAI usually goes through:\nDNS resolution TCP connect + TLS handshake Proxy forwarding (if any) Request/response I/O (including streaming) If you only look at context deadline exceeded, you are blind.\n2) Add minimal observability with httptrace Use a reusable http.Client with explicit transport timeouts and httptrace hooks.\n// key knobs only tr := \u0026amp;http.Transport{ Proxy: http.ProxyFromEnvironment, TLSHandshakeTimeout: 5 * time.Second, ResponseHeaderTimeout: 30 * time.Second, IdleConnTimeout: 90 * time.Second, MaxIdleConns: 200, MaxIdleConnsPerHost: 50, MaxConnsPerHost: 100, } client := \u0026amp;http.Client{Transport: tr, Timeout: 45 * time.Second} Log DNS/TLS/connect events so you can identify the failing stage in minutes.\n3) Troubleshooting order (practical) DNS timeout Symptoms: lookup api.openai.com: i/o timeout\ndig api.openai.com nslookup api.openai.com Fix: stable resolver, explicit DNS config in containers, reuse client.\nTLS handshake timeout Symptoms: net/http: TLS handshake timeout\nopenssl s_client -connect api.openai.com:443 -servername api.openai.com -brief curl -v --connect-timeout 5 https://api.openai.com/v1/models Fix: verify proxy TLS behavior and cert chain; don’t just increase timeout blindly.\nProxy jitter Fix: compare direct vs proxy success rate, add health checks, retry idempotent requests only.\nConnection pool starvation Fix: one long-lived client per process, tune pool sizes, always close response body.\n4) Conservative baseline config Dial timeout: 3s TLS handshake: 5s Response header timeout: 30s Client timeout: 45s MaxConnsPerHost: 100 MaxIdleConnsPerHost: 50 5) Final takeaway Timeouts are rarely random. Break the path, instrument each hop, and fix the bottleneck instead of retrying blindly.\n","permalink":"https://www.mfun.ink/en/2026/03/02/go-openai-api-timeout-troubleshooting-dns-tls-proxy-connection-pool/","summary":"\u003cp\u003eWhen OpenAI API calls start timing out in production, the real problem is usually not “OpenAI is down.”\u003c/p\u003e\n\u003cp\u003eThe real problem is you don’t know which hop is failing: DNS, TLS handshake, proxy path, or your own connection pool.\u003c/p\u003e","title":"Go + OpenAI API Timeout Troubleshooting: DNS, TLS, Proxy, and Connection Pool"},{"content":"When CI keeps failing, the real risk is not “slow fixes” — it is “fast bad fixes.” This guide gives you a practical GitHub Actions + AI Agent auto-fix pipeline with failure tiering, strict edit boundaries, and merge-time gates.\nBottom line: auto-fix only low-risk, verifiable failures Use three severity levels:\nP0 (high risk): security, data consistency, migrations → no auto-fix P1 (medium risk): dependency conflicts, type errors, lint failures → AI patch allowed, human approval required P2 (low risk): formatting/docs/simple test fixes → can auto-merge after gates Rule: speed is good, unauthorized changes are not.\n1) Tier failures before invoking the agent name: ci-auto-fix on: workflow_run: workflows: [\u0026#34;CI\u0026#34;] types: [completed] jobs: classify: if: ${{ github.event.workflow_run.conclusion == \u0026#39;failure\u0026#39; }} runs-on: ubuntu-latest outputs: severity: ${{ steps.cls.outputs.severity }} steps: - uses: actions/checkout@v4 - name: classify failure id: cls run: | python3 scripts/classify_failure.py \u0026gt; result.txt cat result.txt echo \u0026#34;severity=$(cat result.txt)\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; A minimal classify_failure.py can map:\nsecurity, migration, destructive → P0 test failed, type error, dependency → P1 markdownlint, prettier, ruff format → P2 2) Put hard limits on what the agent can edit Never hand over the full repository. Enforce scope:\nALLOWED_PATHS=\u0026#34;src/ tests/ .github/\u0026#34; MAX_CHANGED_LINES=200 git diff --name-only \u0026gt; changed_files.txt python3 scripts/check_scope.py changed_files.txt \u0026#34;$ALLOWED_PATHS\u0026#34; \u0026#34;$MAX_CHANGED_LINES\u0026#34; If scope check fails, stop and route to humans.\n3) Open a fix PR (never push directly to main) propose-fix: needs: classify if: ${{ needs.classify.outputs.severity != \u0026#39;P0\u0026#39; }} runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v4 - name: run agent patch run: | ./scripts/run_agent_fix.sh \u0026#34;${{ needs.classify.outputs.severity }}\u0026#34; - name: open PR run: | gh pr create \\ --title \u0026#34;ci: agent fix for failed run ${{ github.run_id }}\u0026#34; \\ --body \u0026#34;Auto-generated patch. Requires gated checks.\u0026#34; 4) Required merge gates (all must pass) Regression tests (unit + critical smoke e2e) Security scans (CodeQL/SAST + secret scanning) Policy checks (scope and sensitive-file protection) - name: gated regression run: make test-critical - name: security scan run: make security-scan - name: policy check run: python3 scripts/policy_guard.py 5) Common failure modes and fixes A) Retry storm: the agent keeps patching the same error Limit to 1 auto-fix per failed run Cap retries per error fingerprint (e.g., 3 in 24h) python3 scripts/retry_budget.py --fingerprint \u0026#34;$ERR_FP\u0026#34; --max 3 --window 24h B) Functional fix but hidden performance regression Add benchmark smoke tests to gates Enforce P95 thresholds on key endpoints C) Agent modifies forbidden infrastructure files Hard denylist in policy_guard.py: terraform/, migrations/, secrets/ 6) MVP rollout plan If you need to ship today:\nStart with P2 only Force all patches through PRs Enable regression + security gates Track metrics: auto-fix success rate, rollback rate, MTTR Expand to P1 only after rollback rate stays below 3% for two weeks.\nSummary The winning pattern is not “smarter model first.” It is failure tiering + permission boundaries + hard validation gates. Start conservative, then widen safely.\n","permalink":"https://www.mfun.ink/en/2026/02/27/github-actions-ai-agent-auto-fix-pipeline/","summary":"\u003cp\u003eWhen CI keeps failing, the real risk is not “slow fixes” — it is “fast bad fixes.”\nThis guide gives you a practical \u003cstrong\u003eGitHub Actions + AI Agent auto-fix pipeline\u003c/strong\u003e with failure tiering, strict edit boundaries, and merge-time gates.\u003c/p\u003e","title":"GitHub Actions + AI Agent Auto-Fix Pipeline: Failure Tiers, Regression Gates, and Security Guardrails"},{"content":"Most teams can connect an LLM in a demo. The real pain starts in production: multi-step tasks, flaky tool calls, unclear retries, and rising cost.\nThis guide gives you a pragmatic Go-first blueprint for shipping an Agent workflow that can survive real incidents.\n1) Lock the architecture first: 3 layers, strict boundaries Use a three-layer model:\nOrchestrator: accepts requests and advances a task state machine Agent Runtime: talks to OpenAI Agents SDK and handles reasoning + tool call decisions Tool Adapter: executes business actions and returns structured results Rules that matter:\nAgents only see tool contracts, not direct DB/service credentials Every tool result must be serializable JSON Every step carries trace_id and idempotency_key 2) Tool calling quality is mostly a schema problem Poor schemas create most failures.\nBest practices:\nExplicit field types and enum constraints ISO8601 for time Minor currency units for money (cents) Unified response shape: ok/data/error_code/error_message/retryable Example tool response:\n{ \u0026#34;ok\u0026#34;: false, \u0026#34;error_code\u0026#34;: \u0026#34;ORDER_LOCKED\u0026#34;, \u0026#34;error_message\u0026#34;: \u0026#34;order is locked by settlement process\u0026#34;, \u0026#34;retryable\u0026#34;: true, \u0026#34;data\u0026#34;: null } Now the model can decide whether to retry, re-plan, or hand off.\n3) Session memory: short-window context + long-form summary In production, split memory into two layers:\nShort window (last N turns) for local continuity Long summary (task-level facts) for stable recall Operational strategy:\nUpdate summary every turn (cap at 300–500 tokens) Redact sensitive fields before storing Re-inject only memory relevant to the current goal Otherwise you get the classic trio: slower responses, higher cost, worse focus.\n4) Error recovery needs a state machine, not nested if-else Define recoverable states per request:\nRECEIVED PLANNED TOOL_RUNNING TOOL_FAILED_RETRYABLE TOOL_FAILED_FATAL COMPLETED Recovery policy:\nretryable=true: exponential backoff with retry cap Repeated failures: route to human queue Timeout in any step: rollback to last replayable checkpoint Go pseudo-code:\nif step.Retryable \u0026amp;\u0026amp; step.RetryCount \u0026lt; 3 { backoff := time.Second * time.Duration(1\u0026lt;\u0026lt;step.RetryCount) scheduleRetry(step, backoff) } else if step.Retryable { moveToManualQueue(step) } else { markFatal(step) } 5) Metrics and cost controls you need on day one Track at least:\ntool_call_success_rate tool_call_p95_latency agent_step_retry_rate token_input/output_per_request cost_per_successful_task manual_handoff_ratio Add alerts for:\nsudden retry-rate spikes token burn per task over threshold 6) Minimal production checklist JSON schema and error-code dictionary for every tool Persistent step snapshots (input/output) End-to-end idempotency keys Tested retry, circuit-break, and human fallback paths Prometheus/Grafana dashboards online Canary rollout (10%) + rollback switch Summary The hard part of OpenAI Agents SDK with Go is not making it work once. It is making it recover gracefully under failure.\nIf you implement layered boundaries, deterministic state transitions, and observability first, you get a system that scales without waking you up at 3 a.m.\n","permalink":"https://www.mfun.ink/en/2026/02/25/openai-agents-sdk-go-tool-calling/","summary":"\u003cp\u003eMost teams can connect an LLM in a demo. The real pain starts in production: multi-step tasks, flaky tool calls, unclear retries, and rising cost.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a pragmatic Go-first blueprint for shipping an Agent workflow that can survive real incidents.\u003c/p\u003e","title":"OpenAI Agents SDK with Go: Tool Calling, Session Memory, and Error Recovery"},{"content":"Production streaming fails in two predictable ways: users wait while the stream silently drops, and your logs say \u0026ldquo;timeout\u0026rdquo; without telling you where it actually broke.\nThis guide gives you a practical Go pattern for OpenAI Responses API streaming with strict timeout boundaries, safe retries, and useful telemetry.\n1) Define retry boundaries first Do not retry every error.\nRetryable: 429, 5xx, transient network resets, upstream gateway timeout Non-retryable: 401/403, invalid request payload, explicit context cancellation Conditional: mid-stream disconnects (depends on whether continuation is acceptable) Retrying 4xx blindly is just automated self-harm.\n2) Recommended timeout model in Go Use three layers:\nRequest context timeout (hard deadline, e.g. 45s) HTTP client timeout (connection guard, e.g. 50s) Stream idle timeout (abort when no token arrives for too long) ctx, cancel := context.WithTimeout(r.Context(), 45*time.Second) defer cancel() httpClient := \u0026amp;http.Client{Timeout: 50 * time.Second} Keep HTTP timeout slightly larger than context timeout.\n3) Exponential backoff with jitter Practical defaults:\nInitial backoff: 300-500ms Exponential growth with cap (e.g. 5s) Add jitter to avoid synchronized retry spikes Max attempts: 3-5 func backoff(attempt int) time.Duration { base := 400 * time.Millisecond max := 5 * time.Second d := base * time.Duration(1\u0026lt;\u0026lt;attempt) if d \u0026gt; max { d = max } jitter := time.Duration(rand.Int63n(int64(d / 4))) return d + jitter } 4) Minimum observability signals At least emit these metrics:\nllm_stream_first_token_ms llm_stream_total_duration_ms llm_stream_tokens_in llm_stream_tokens_out llm_stream_retry_count llm_stream_error_total{code,type} Attach trace_id per request so you can correlate request entry, model latency, and stream completion.\n5) Retry-capable stream skeleton func StreamWithRetry(ctx context.Context, req *Request) error { var lastErr error for attempt := 0; attempt \u0026lt; 4; attempt++ { start := time.Now() err := streamOnce(ctx, req) recordMetrics(time.Since(start), attempt, err) if err == nil { return nil } if !isRetryable(err) { return err } lastErr = err select { case \u0026lt;-ctx.Done(): return ctx.Err() case \u0026lt;-time.After(backoff(attempt)): } } return fmt.Errorf(\u0026#34;stream failed after retries: %w\u0026#34;, lastErr) } 6) Common failure patterns Not canceling context when client disconnects (goroutine leaks) Buffering the whole answer before returning (not really streaming) Tracking failures only, not first-token latency Retrying without error-type filtering 7) Pre-launch validation checklist Inject 429 and verify backoff + retries Inject 5xx and verify final error observability Simulate short network drop and verify recovery Run concurrency test (50/100) and watch p95 first-token latency Summary Reliable Responses API streaming in Go is mostly engineering hygiene:\nError boundaries Time boundaries Observability boundaries Build these three first, then optimize prompt and model quality.\n","permalink":"https://www.mfun.ink/en/2026/02/23/openai-responses-api-streaming-go-timeout-retry-observability/","summary":"\u003cp\u003eProduction streaming fails in two predictable ways: users wait while the stream silently drops, and your logs say \u0026ldquo;timeout\u0026rdquo; without telling you where it actually broke.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical Go pattern for OpenAI Responses API streaming with strict timeout boundaries, safe retries, and useful telemetry.\u003c/p\u003e","title":"OpenAI Responses API Streaming in Go: Timeouts, Retries, and Observability"},{"content":"Getting FreshRSS + RSSHub running is easy. Keeping it stable is the hard part: lost state after restarts, repeated 403 errors, broken proxy headers, and risky upgrades.\nThis guide gives a production-ready Linux baseline focused on long-term stability, observability, and safe upgrades.\nArchitecture and Stability Goals FreshRSS: feed aggregation, reading, filtering rules RSSHub: dynamic feed generation Redis: cache layer for RSSHub to reduce upstream pressure Nginx/Caddy: reverse proxy + HTTPS Target outcomes:\nServices auto-recover after crashes Persistent data volumes Key settings managed through env vars Health checks and clear log paths 1) Directory Layout and Prerequisites sudo mkdir -p /opt/rss-stack/{freshrss-data,rsshub-cache,logs} cd /opt/rss-stack docker --version docker compose version 2) docker-compose.yml (Production Baseline) services: freshrss: image: freshrss/freshrss:latest container_name: freshrss restart: unless-stopped environment: TZ: Asia/Shanghai CRON_MIN: \u0026#39;3,33\u0026#39; TRUSTED_PROXY: 172.16.0.0/12 192.168.0.0/16 volumes: - ./freshrss-data:/var/www/FreshRSS/data - ./freshrss-data/extensions:/var/www/FreshRSS/extensions healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;wget\u0026#34;, \u0026#34;-qO-\u0026#34;, \u0026#34;http://127.0.0.1:80/\u0026#34;] interval: 30s timeout: 5s retries: 5 networks: - rss_net redis: image: redis:7-alpine container_name: rsshub-redis restart: unless-stopped command: [\u0026#34;redis-server\u0026#34;, \u0026#34;--appendonly\u0026#34;, \u0026#34;yes\u0026#34;] volumes: - ./rsshub-cache:/data networks: - rss_net rsshub: image: diygod/rsshub:chromium-bundled container_name: rsshub restart: unless-stopped depends_on: - redis environment: NODE_ENV: production CACHE_TYPE: redis REDIS_URL: redis://redis:6379/ PUPPETEER_WS_ENDPOINT: \u0026#39;\u0026#39; REQUEST_RETRY: 2 REQUEST_TIMEOUT: 10000 healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;wget\u0026#34;, \u0026#34;-qO-\u0026#34;, \u0026#34;http://127.0.0.1:1200/healthz\u0026#34;] interval: 30s timeout: 5s retries: 5 networks: - rss_net networks: rss_net: driver: bridge Start services:\ndocker compose up -d docker compose ps 3) Reverse Proxy + HTTPS (Nginx Example) server { listen 443 ssl http2; server_name rss.example.com; location / { proxy_pass http://127.0.0.1:1200; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } } server { listen 443 ssl http2; server_name reader.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } } If FreshRSS is not exposed on host port 8080, use the actual mapped port or proxy through an internal Docker network.\n4) Common Failure Cases and Fixes RSSHub keeps returning 403 / timeout Lower crawl frequency and prioritize caching (Redis) Use proxy/IP rotation for heavily protected sources Increase REQUEST_TIMEOUT (10s → 15s) and inspect logs docker logs --tail=200 rsshub FreshRSS scheduled updates do not run Verify CRON_MIN Verify container timezone Manually trigger refresh from admin panel White screen or extension issues after upgrades Always back up before pull:\ntar czf backup-freshrss-$(date +%F).tgz /opt/rss-stack/freshrss-data docker compose pull docker compose up -d 5) Conservative Growth Operations Validate new feed routes in small batches for 24 hours Weekly maintenance: docker compose pull \u0026amp;\u0026amp; docker compose up -d Tier RSS routes by value: keep high-value feeds stable, reduce frequency for low-value ones Summary The key is not just “it works”, but “it is recoverable under failure”.\nWith persistent data, Redis caching, health checks, and correct proxy headers, FreshRSS + RSSHub can run reliably on Linux and scale smoothly later (monitoring, migration to k3s, etc.).\n","permalink":"https://www.mfun.ink/en/2026/02/20/freshrss-rsshub-linux-deployment/","summary":"\u003cp\u003eGetting FreshRSS + RSSHub running is easy. Keeping it stable is the hard part: lost state after restarts, repeated 403 errors, broken proxy headers, and risky upgrades.\u003c/p\u003e\n\u003cp\u003eThis guide gives a production-ready Linux baseline focused on \u003cstrong\u003elong-term stability, observability, and safe upgrades\u003c/strong\u003e.\u003c/p\u003e","title":"A Stable FreshRSS + RSSHub Deployment on Linux (Docker Compose)"},{"content":"In high-concurrency scenarios, distributed locks are essential for ensuring data consistency. However, many developers\u0026rsquo; understanding of Redis distributed locks stops at \u0026ldquo;SETNX\u0026rdquo;, leading to frequent production incidents.\nThis article comprehensively covers the correct usage of Redis distributed locks from principles, implementation, common misuse cases to production-grade solutions.\nWhy Do We Need Distributed Locks? In single-node applications, we use synchronized or ReentrantLock to solve concurrency issues. But in distributed systems:\nMultiple service instances access shared resources simultaneously Database transactions cannot cover cross-service business logic Cache-database consistency requires coordination At this point, distributed locks are needed to ensure that only one thread/process can execute critical code sections at the same time.\nBasic Implementation: SETNX + EXPIRE The simplest implementation:\n# Acquire lock SETNX lock_key 1 EXPIRE lock_key 30 # Release lock DEL lock_key Problems:\nAtomicity issue: SETNX and EXPIRE are not atomic operations Deadlock risk: If the service crashes after acquiring the lock, the lock will never be released Misdeletion risk: Releasing the lock might delete another client\u0026rsquo;s lock Improved Solution: SET NX PX + Lua Script # Acquire lock (atomic operation) SET lock_key client_id NX PX 30000 # Release lock (Lua script ensures atomicity) if redis.call(\u0026#34;GET\u0026#34;, KEYS[1]) == ARGV[1] then return redis.call(\u0026#34;DEL\u0026#34;, KEYS[1]) else return 0 end Advantages:\nSET command\u0026rsquo;s NX and PX parameters ensure atomicity Lua script guarantees atomic release of lock client_id prevents misdeletion of other clients\u0026rsquo; locks Production-Grade Solutions Solution 1: Redisson (Recommended) Redisson provides mature distributed lock implementation:\nRLock lock = redisson.getLock(\u0026#34;myLock\u0026#34;); lock.lock(); // Automatic renewal try { // Business logic } finally { lock.unlock(); } Features:\nAutomatic renewal (Watchdog mechanism) Reentrant locks Fair/non-fair locks Timeout auto-release Solution 2: Redisson + Spring Boot # application.yml redisson: config: | singleServerConfig: address: \u0026#34;redis://127.0.0.1:6379\u0026#34; connectionMinimumIdleSize: 10 connectionPoolSize: 64 database: 0 idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 Common Misuse Cases Case 1: Unreasonable Lock Expiration Time Problem: Setting too short expiration time Consequence: Business logic not completed before lock expires Solution: Set based on maximum business execution time + safety margin Case 2: Ignoring Lock Renewal Problem: Long-running tasks where lock expires Consequence: Other clients acquire lock and modify data Solution: Use automatic renewal mechanism or manual renewal Case 3: Ignoring Network Partition Issues Problem: Network jitter causes client to think lock acquisition succeeded Consequence: Split-brain problem, multiple clients think they hold the lock Solution: Use Redlock algorithm or strong consistency storage Best Practice Checklist Must set timeout: Prevent deadlocks Use unique identifier: Avoid misdeleting other clients\u0026rsquo; locks Atomic operations: Both lock acquisition and release must be atomic Consider auto-renewal: For long-running tasks Monitoring \u0026amp; alerts: Lock acquisition failure rate, waiting time, etc. Fallback strategy: Backup strategy when lock service is unavailable Performance Comparison Test Solution Lock Acquisition Time Lock Release Time Reliability Use Case Native SETNX 0.5ms 0.3ms Low Test environment SET NX PX 0.6ms 0.4ms Medium Simple business Redisson 1.2ms 0.8ms High Production environment Redlock 2.5ms 1.5ms Very High Financial-grade systems Conclusion Redis distributed locks are not just simple \u0026ldquo;lock-acquire-lock-release\u0026rdquo; operations, but a complete concurrency control solution. Choosing the appropriate implementation method and combining it with business scenario characteristics is key to truly leveraging the value of distributed locks.\nRemember: Locks are just means, consistency is the goal.\nThis article is compiled from actual production experience. Feel free to share your distributed lock practical experience in the comments.\n","permalink":"https://www.mfun.ink/en/2026/02/19/redis-distributed-lock-best-practices/","summary":"\u003cp\u003eIn high-concurrency scenarios, distributed locks are essential for ensuring data consistency. However, many developers\u0026rsquo; understanding of Redis distributed locks stops at \u0026ldquo;SETNX\u0026rdquo;, leading to frequent production incidents.\u003c/p\u003e\n\u003cp\u003eThis article comprehensively covers the correct usage of Redis distributed locks from principles, implementation, common misuse cases to production-grade solutions.\u003c/p\u003e","title":"Redis Distributed Lock Best Practices (with Common Misuse Cases)"},{"content":"If your WebSocket keeps reconnecting every few seconds, it\u0026rsquo;s usually not your app code. In most production incidents, the root cause is an incomplete reverse-proxy chain in Nginx (or one proxy layer before it).\nThis guide gives you a copy-paste checklist to stabilize WebSocket connections fast.\n1) Missing Upgrade/Connection headers (no 101 handshake) WebSocket requires an HTTP upgrade. Without proper header forwarding, upstream sees plain HTTP.\nmap $http_upgrade $connection_upgrade { default upgrade; \u0026#39;\u0026#39; close; } location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } Verify:\ncurl -i -N \\ -H \u0026#34;Connection: Upgrade\u0026#34; \\ -H \u0026#34;Upgrade: websocket\u0026#34; \\ -H \u0026#34;Sec-WebSocket-Version: 13\u0026#34; \\ -H \u0026#34;Sec-WebSocket-Key: SGVsbG9Xb3JsZDEyMw==\u0026#34; \\ https://example.com/ws/ You should get HTTP/1.1 101 Switching Protocols.\n2) Idle timeout is too short A very common killer: default timeout values close idle but healthy sockets.\nlocation /ws/ { proxy_connect_timeout 15s; proxy_send_timeout 60s; proxy_read_timeout 3600s; } Also send ping/pong heartbeats every 20-30 seconds from server or client.\n3) Load-balanced upstream without session stickiness Handshake lands on node A, next frame hits node B, in-memory session is gone, socket drops.\nupstream ws_backend { ip_hash; server 10.0.0.11:8080; server 10.0.0.12:8080; } If possible, move session state to Redis and avoid sticky dependencies.\n4) Another proxy layer closes first (CDN/LB/Ingress) Even if Nginx is set to 1 hour, your outer ALB/CDN might cut at 60 seconds.\nCheck all layers and align idle timeout to a sane baseline (300s+ for many real-time apps).\n5) Protocol mismatch (http2, ws, wss) Public edge can run HTTP/2, but WebSocket upgrade semantics still rely on HTTP/1.1 proxy behavior.\nKeep this consistent:\nClient uses wss://example.com/ws/ Nginx upstream uses proxy_http_version 1.1 TLS cert chain is complete openssl s_client -connect example.com:443 -servername example.com \u0026lt;/dev/null 6) Buffering/compression side effects For WebSocket paths, avoid normal response buffering.\nlocation /ws/ { proxy_buffering off; gzip off; } If messages arrive in bursts instead of real-time, buffering is often the culprit.\n7) No observability = blind debugging Add a focused log format and track handshake success and disconnect rates.\nlog_format ws \u0026#39;$remote_addr - $host [$time_local] \u0026#39; \u0026#39;\u0026#34;$request\u0026#34; $status $body_bytes_sent \u0026#39; \u0026#39;rt=$request_time urt=$upstream_response_time \u0026#39; \u0026#39;ua=\u0026#34;$http_user_agent\u0026#34; up=\u0026#34;$upstream_addr\u0026#34;\u0026#39;; access_log /var/log/nginx/ws_access.log ws; error_log /var/log/nginx/ws_error.log warn; Track at least:\nactive websocket connections disconnects per minute HTTP 101 success ratio Production-ready baseline snippet map $http_upgrade $connection_upgrade { default upgrade; \u0026#39;\u0026#39; close; } upstream ws_backend { least_conn; server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; } server { listen 443 ssl http2; server_name example.com; location /ws/ { proxy_pass http://ws_backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_connect_timeout 15s; proxy_send_timeout 60s; proxy_read_timeout 3600s; proxy_buffering off; gzip off; } } Summary Most WebSocket disconnect incidents are boring infrastructure issues, not mysterious app bugs.\nFix these first:\nCorrect upgrade headers + HTTP/1.1 proxying Longer read timeout + heartbeat Aligned idle timeouts across all proxy layers Do this and your reconnect storm usually disappears.\n","permalink":"https://www.mfun.ink/en/2026/02/18/nginx-websocket-disconnect-traps/","summary":"\u003cp\u003eIf your WebSocket keeps reconnecting every few seconds, it\u0026rsquo;s usually not your app code. In most production incidents, the root cause is an incomplete reverse-proxy chain in Nginx (or one proxy layer before it).\u003c/p\u003e\n\u003cp\u003eThis guide gives you a copy-paste checklist to stabilize WebSocket connections fast.\u003c/p\u003e","title":"Nginx Reverse Proxy WebSocket Disconnects: 7 Common Traps and Reliable Fixes"},{"content":"If your RAG system feels unreliable, switching to a more expensive LLM is usually the wrong first move. In most cases, the bottleneck is retrieval quality: weak recall, poor ranking, and no measurement loop.\nThis guide gives a practical path: make recall broader, make ranking sharper, then close the loop with offline + online evaluation.\n1) Define what “accurate” means first Build a small eval set (50–200 real queries), each with:\nuser query reference answer or key facts gold evidence chunk IDs (can be multiple) Track at least these metrics:\nRecall@k (most important first) MRR nDCG@k Faithfulness (is the answer grounded in retrieved evidence?) If Recall@k doesn’t improve, prompt tuning won’t save you.\n2) Make recall broad with hybrid retrieval Use multiple retrieval channels:\ndense vector retrieval (semantic) BM25 keyword retrieval (lexical) optional rule-based retrieval (title/tag/time filters) Why this works: product names, error codes, and versions are often better handled by BM25 than embeddings.\nSuggested defaults chunk size: 300–600 Chinese chars or 200–400 English words overlap: 10%–20% retrieve top 30–100 candidates before reranking deduplicate by doc_id + span hash 3) Make ranking precise with a cross-encoder reranker Adding reranking is often the highest-ROI quality upgrade.\nPipeline:\nretrieve top 50 rerank and keep top 5–10 force citations in the final answer Example:\ncandidates = hybrid_retrieve(query, topk=50) ranked = rerank_cross_encoder(query, candidates) context = ranked[:8] answer = llm_generate(query, context, require_citations=True) 4) Keep query rewriting conservative Over-aggressive rewrite can damage intent.\nA safer default:\nkeep the original query only expand abbreviations/synonyms never invent constraints A strong pattern is parallel retrieval: original query + rewritten query, then merge and deduplicate.\n5) Close the evaluation loop weekly Run offline evaluation weekly, and feed back online signals:\nexplicit: thumbs up/down, “solved?” implicit: follow-up rate, copy rate, dwell time If follow-up rate rises while offline metrics look fine, check:\nstale eval set delayed indexing for fresh docs missing retrieval strategy for specific query types (e.g., version-diff questions) 6) Fast troubleshooting checklist Hallucinations: increase evidence coverage; try context top 8 instead of top 3. Off-topic answers: inspect reranker false positives. Fresh docs not found: audit incremental indexing and retries. Old-version hits: add version and updated_at filters. Long complex queries fail: split into sub-questions before retrieval. 7) Minimal production-ready stack vector store: Milvus / pgvector / Weaviate lexical search: OpenSearch/Elasticsearch or local BM25 reranker: bge-reranker or cross-encoder evaluation: custom scripts + weekly Recall@k/MRR/Faithfulness report Get this pipeline stable before adding agent complexity.\nSummary The most reliable RAG improvement order is:\nbroader recall (hybrid retrieval) better ranking (cross-encoder reranker) continuous evaluation loop (offline + online) Don’t start with a pricier model. Start with better retrieval engineering.\n","permalink":"https://www.mfun.ink/en/2026/02/17/rag-retrieval-rerank-eval-loop/","summary":"\u003cp\u003eIf your RAG system feels unreliable, switching to a more expensive LLM is usually the wrong first move. In most cases, the bottleneck is retrieval quality: weak recall, poor ranking, and no measurement loop.\u003c/p\u003e\n\u003cp\u003eThis guide gives a practical path: make recall broader, make ranking sharper, then close the loop with offline + online evaluation.\u003c/p\u003e","title":"RAG Accuracy Playbook: Retrieval Recall, Re-Ranking, and Evaluation Loop"},{"content":"Your local hugo build works, but GitHub Actions fails randomly. Classic. The root cause is usually not the workflow syntax. It is environment drift, missing permissions, and unstable dependencies.\nThis guide gives you a stable Hugo deployment workflow for GitHub Pages: pinned versions, minimum required permissions, and actionable failure signals.\n1) Deployment rules first A reliable pipeline should guarantee:\nAuto build and publish on every push Fixed toolchain versions Fast failure with readable logs 2) Recommended workflow (copy and run) Create .github/workflows/deploy-hugo.yml:\nname: Deploy Hugo on: push: branches: [\u0026#34;main\u0026#34;] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: true jobs: build: runs-on: ubuntu-latest env: HUGO_VERSION: \u0026#34;0.145.0\u0026#34; TZ: \u0026#34;Asia/Shanghai\u0026#34; steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: ${{ env.HUGO_VERSION }} extended: true - name: Setup Pages id: pages uses: actions/configure-pages@v5 - name: Build run: hugo --minify --gc - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./public deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 3) Why this setup is robust Pin Hugo version Never rely on runner defaults. Theme and template behavior can break across versions.\nExplicit Pages permissions pages: write and id-token: write are both required for deployment.\nConcurrency guard If multiple pushes happen quickly, old runs are canceled to prevent stale deploys.\nRecursive submodule checkout If your theme is a submodule, missing recursive checkout will break builds.\n4) Top 5 common failures Error: Failed to create deployment Check:\nRepository Settings → Pages → Source is GitHub Actions Workflow includes pages: write and id-token: write hugo: command not found Check:\nactions-hugo step actually ran hugo-version is valid module not found or theme render errors Check:\nsubmodules: recursive is enabled Local and CI Hugo versions match No files were found with the provided path: ./public Check:\nHugo build really outputs to public/ Config errors did not stop build early Deploy succeeds but styles are broken Check:\nbaseURL matches your Pages domain No hardcoded absolute asset paths 5) Add a quick sanity check Append this after build:\n- name: Sanity check run: | test -f public/index.html echo \u0026#34;Build output looks good\u0026#34; Simple, but effective against \u0026ldquo;green build, empty output\u0026rdquo; mistakes.\n6) Summary For stable Hugo deployment, focus on four things:\nPin versions Set correct permissions Pull complete dependencies Keep logs readable for troubleshooting Get these right and your CI will be more reliable than most blog pipelines.\n","permalink":"https://www.mfun.ink/en/2026/02/16/github-actions-hugo-auto-deploy-safe-config/","summary":"\u003cp\u003eYour local \u003ccode\u003ehugo\u003c/code\u003e build works, but GitHub Actions fails randomly. Classic.\nThe root cause is usually not the workflow syntax. It is environment drift, missing permissions, and unstable dependencies.\u003c/p\u003e","title":"Hugo Auto Deploy with GitHub Actions: Safe Config and Troubleshooting"},{"content":"If you still use one model for everything, you usually pay in one of three ways: higher cost, slower delivery, or more rework.\nA better setup is role-based collaboration: Claude Code for planning and quality gates, Codex for fast implementation and batch edits.\nBottom line first: the most practical split The most reliable split in real projects:\nClaude Code: requirement breakdown, architecture choices, risk checks, critical refactors Codex: scaffolding, batch updates, test generation, docs sync Short version: Claude owns direction, Codex owns throughput.\nHow to measure cost, speed, and quality Don’t rely on vibes. Track at least 4 metrics:\nCost: token/call cost per task Speed: total minutes from task start to PR-ready Quality: first-pass success rate, rollback rate, review rounds Stability: output consistency with longer context windows A tiny log format is enough:\necho \u0026#34;$(date +%F_%T),task=api-refactor,model=codex,cost=0.42,time_min=26,review_round=2\u0026#34; \u0026gt;\u0026gt; .ai-benchmark.csv A copy-paste workflow you can run today 1) Let Claude Code define boundaries first Prompt template for Claude Code:\nYou are the tech lead. Goal: move auth logic in UserService from controller to middleware. Output: 1) file-level change list 2) risk points 3) acceptance criteria 4) rollback plan Required output quality:\nfile-level scope (no vague “refactor everything”) explicit out-of-scope list testable acceptance criteria 2) Let Codex do high-throughput execution Codex prompt template:\nImplement only the listed file changes. Do not modify files outside the list. Before finishing: - run tests - provide change summary - highlight risky diffs This is where speed gains are obvious, especially for:\nmechanical renames cross-file interface alignment test expansion README/comments synchronization 3) Return to Claude Code for quality gate Ask Claude Code to focus on:\narchitecture consistency edge cases (nulls, retries, timeouts, concurrency) reviewer-grade risk callouts 5 rules that save the most time and money One model, one role per stage. One iteration, one sub-goal. Keep all AI outputs replayable (prompt + diff + test output). No passing tests, no next stage. If rework happens twice, change role assignment. Common failure modes and fixes Issue 1: faster output, but more rollbacks Usually caused by Codex crossing boundaries.\nFix:\nenforce file allowlist enforce one theme per change always inspect summary diff git diff --stat git diff --name-only Issue 2: Claude plan is great, but execution is slow Usually caused by oversized task scope.\nFix:\nask for minimum mergeable plan (MMP) split work into 30–60 minute chunks Issue 3: model recommendations conflict Resolution priority:\ntests and production signals first system constraints (SLA, compatibility) lowest-risk change path Optional lightweight automation TASK_ID=\u0026#34;auth-mw-$(date +%s)\u0026#34; echo \u0026#34;$TASK_ID,start,$(date +%s)\u0026#34; \u0026gt;\u0026gt; .ai-run.log npm test echo \u0026#34;$TASK_ID,end,$(date +%s)\u0026#34; \u0026gt;\u0026gt; .ai-run.log Then calculate average lead time and rework rate. If numbers don’t improve, the workflow is wrong.\nWhen not to use dual-model workflow tiny changes (1–2 files) extreme urgency (need result in 10 minutes) no review discipline in team In these cases, one model is often faster and safer.\nSummary Multi-model collaboration is not “more advanced”; it’s just better role matching.\nIf your current pain is “fast but fragile,” let Claude define boundaries and acceptance criteria, then let Codex execute. If your pain is “good plans, slow delivery,” split tasks smaller and use Codex for mechanical work.\nRun it for 2 weeks, track ~20 tasks, and decide with data—not intuition.\n","permalink":"https://www.mfun.ink/en/2026/02/15/claude-code-codex-multi-model-collaboration/","summary":"\u003cp\u003eIf you still use one model for everything, you usually pay in one of three ways: higher cost, slower delivery, or more rework.\u003c/p\u003e\n\u003cp\u003eA better setup is role-based collaboration: Claude Code for planning and quality gates, Codex for fast implementation and batch edits.\u003c/p\u003e","title":"Claude Code + Codex for Multi-Model Development: Cost, Speed, and Quality (Practical Workflow)"},{"content":"If your Go service RSS keeps climbing, drops after restart, then climbs again, you likely have a memory retention problem (or an actual leak pattern).\nDo not start with random code edits. Run a clean evidence chain: metrics trend check → pprof snapshots → FlameGraph comparison → object growth path → regression validation.\nTL;DR Trend beats point-in-time values. Use heap + allocs + GC metrics together, or you will misdiagnose normal cache growth. Start with go tool pprof; use FlameGraph to pinpoint the business call path. 1) Confirm it is likely a real leak pattern Watch these three metrics first:\nprocess_resident_memory_bytes (RSS) go_memstats_heap_inuse_bytes go_memstats_heap_objects Escalate to leak triage when:\nQPS is stable (or lower) RSS and heap objects still rise over time Full GC does not bring memory down meaningfully 2) Expose pprof safely (internal only) Standard setup import _ \u0026#34;net/http/pprof\u0026#34; func init() { go func() { // internal or localhost only _ = http.ListenAndServe(\u0026#34;127.0.0.1:6060\u0026#34;, nil) }() } Use a dedicated debug port if your service framework is Gin/Echo/Fiber.\nCollect snapshots curl -s http://127.0.0.1:6060/debug/pprof/heap \u0026gt; heap_1.pb.gz sleep 300 curl -s http://127.0.0.1:6060/debug/pprof/heap \u0026gt; heap_2.pb.gz curl -s http://127.0.0.1:6060/debug/pprof/allocs \u0026gt; allocs.pb.gz Always collect at least two time-separated heap snapshots.\n3) Use pprof to find growth owners go tool pprof -top heap_1.pb.gz go tool pprof -top heap_2.pb.gz go tool pprof -base heap_1.pb.gz heap_2.pb.gz Focus on:\nBiggest growth in inuse_space Call paths that keep expanding Object-heavy types (map, slice, string, buffers) Common leak patterns:\nGlobal map that only grows Goroutine leak keeping references alive Cache without TTL/LRU limits Channel backlog retaining payloads 4) FlameGraph for call-path attribution go tool pprof -http=:8081 heap_2.pb.gz In FlameGraph:\nWider block = larger memory share Drill down to business-level functions Compare early vs later snapshots to find widening chains A widening path like BuildResponse -\u0026gt; append -\u0026gt; json.Marshal often indicates oversized object assembly plus retained references.\n5) Practical fix example Problematic pattern var userCache = map[string]*UserProfile{} func GetUserProfile(uid string) *UserProfile { if v, ok := userCache[uid]; ok { return v } p := loadProfile(uid) userCache[uid] = p return p } With high-cardinality user IDs, this grows forever.\nBetter approach Add size limit (LRU) Add expiration (TTL) Add observability (size, hit rate, evictions) Pseudo example:\ncache := lru.NewWithTTL(20000, 10*time.Minute) 6) Regression validation (no gut feeling) After the fix:\nRun the same load profile for 30–60 minutes Compare heap_objects slope before/after Re-capture heap and run -base comparison Healthy result:\nObject count plateaus heap_inuse drops after GC cycles RSS fluctuates but no longer trends one-way up 7) On-call checklist pprof endpoint is internal-only at least two heap snapshots (\u0026gt;=5 min apart) -base delta analysis completed goroutine count checked cache has limit + TTL post-fix load regression completed Summary In most Go incidents, “memory leak” is not GC failure; it is retention caused by uncontrolled reference paths.\nIf you run the full chain (metrics → pprof → FlameGraph → fix → regression), you move from guessing to proof-driven incident handling—and that is what actually fixes production issues fast.\n","permalink":"https://www.mfun.ink/en/2026/02/14/go-memory-leak-pprof-flamegraph/","summary":"\u003cp\u003eIf your Go service RSS keeps climbing, drops after restart, then climbs again, you likely have a memory retention problem (or an actual leak pattern).\u003c/p\u003e\n\u003cp\u003eDo not start with random code edits. Run a clean evidence chain: \u003cstrong\u003emetrics trend check → pprof snapshots → FlameGraph comparison → object growth path → regression validation\u003c/strong\u003e.\u003c/p\u003e","title":"Go Memory Leak Triage in Production: pprof + FlameGraph Step by Step"},{"content":"If you\u0026rsquo;ve already used function calling but keep writing glue code for every non-trivial task, you\u0026rsquo;re likely at the point where Responses API + MCP makes more sense.\nThis guide is practical: how to move from single tool calls to a scalable agent workflow where retrieval, execution, validation, and write-back follow a consistent structure.\nOne-line conclusion Function Calling is good for isolated single-step tool use. Responses API + MCP is better for multi-step, multi-tool, stateful workflows. What you need is not just better prompting, but a better tool protocol and workflow architecture. 1) Clarify the boundary: Function Calling vs MCP Typical function-calling pain points At scale, teams usually hit these issues:\nTool definitions are scattered across business code. Cross-tool orchestration needs custom state machines. Permissions and observability are inconsistent. What MCP actually fixes MCP (Model Context Protocol) is a standard layer for tool integration:\nTool discovery Unified invocation schema Better isolation and permission boundaries Traceable tool execution 2) MVP architecture that works Start with a simple three-layer setup:\nOrchestrator: business entry point Model Layer: Responses API for reasoning and routing Tool Layer: MCP servers exposing retrieval/read/write/ops tools Flow:\nUser task enters orchestrator Model decomposes task and selects tools MCP tools return structured results Model continues or finalizes output 3) Reusable calling skeleton Simplified pseudo-code (focus on shape, not SDK details):\nfrom openai import OpenAI client = OpenAI() tools = [ { \u0026#34;type\u0026#34;: \u0026#34;mcp\u0026#34;, \u0026#34;server_label\u0026#34;: \u0026#34;docs\u0026#34;, \u0026#34;server_url\u0026#34;: \u0026#34;http://127.0.0.1:8080/mcp\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;mcp\u0026#34;, \u0026#34;server_label\u0026#34;: \u0026#34;ops\u0026#34;, \u0026#34;server_url\u0026#34;: \u0026#34;http://127.0.0.1:8081/mcp\u0026#34; } ] resp = client.responses.create( model=\u0026#34;gpt-5\u0026#34;, input=\u0026#34;Find root cause of last night\u0026#39;s failed deployment and propose a fix plan\u0026#34;, tools=tools ) print(resp.output_text) Add a hard safety policy early:\nPOLICY = { \u0026#34;dangerous_actions_require_approval\u0026#34;: True, \u0026#34;readonly_tools_default\u0026#34;: True, \u0026#34;max_tool_hops\u0026#34;: 8 } 4) Reliability and debugging (the part most teams skip) 1) Enforce structured JSON output from tools Do not return free-form text only. Keep at least:\n{ \u0026#34;status\u0026#34;: \u0026#34;ok\u0026#34;, \u0026#34;data\u0026#34;: {}, \u0026#34;error\u0026#34;: null, \u0026#34;trace_id\u0026#34;: \u0026#34;...\u0026#34; } This dramatically improves model decision stability in subsequent steps.\n2) Add timeout and retry controls per tool # pseudo config TOOL_TIMEOUT_MS=8000 TOOL_MAX_RETRY=2 TOOL_RETRY_BACKOFF_MS=300 3) Keep replayable logs At minimum, log:\nOriginal task Model decision summary Every MCP request/response Final output Without replay logs, production debugging becomes guesswork.\n5) Common mistakes Mistake 1: Treating MCP as a magic plugin layer MCP is a protocol, not a business model. Bad tool design stays bad.\nMistake 2: Adding too many tools too early More tools can reduce routing stability. Start with 3–5 high-value tools.\nMistake 3: Measuring demo quality, not long-run reliability Track these metrics:\nMulti-step task completion rate Tool failure rate Average tool hops Human takeover rate 6) Minimum practical rollout checklist Pick one narrow scenario (e.g., deploy incident triage). Integrate only three MCP tools (logs, config read, fix suggestion). Use Responses API for decomposition and routing. Gate dangerous actions with human approval. Run for one week and iterate on failure paths. This is where an agent moves from “works in demo” to “works in production”.\nSummary Responses API is the brain, MCP is the hands, workflow is the operating discipline.\nYou need all three aligned for production-grade agent systems.\nIf you\u0026rsquo;re migrating from function calling, use this path: single-scenario MVP → metrics → gradual tool expansion.\n","permalink":"https://www.mfun.ink/en/2026/02/11/openai-responses-api-mcp-agent-workflow/","summary":"\u003cp\u003eIf you\u0026rsquo;ve already used function calling but keep writing glue code for every non-trivial task, you\u0026rsquo;re likely at the point where \u003cstrong\u003eResponses API + MCP\u003c/strong\u003e makes more sense.\u003c/p\u003e\n\u003cp\u003eThis guide is practical: how to move from single tool calls to a scalable agent workflow where retrieval, execution, validation, and write-back follow a consistent structure.\u003c/p\u003e","title":"OpenAI Responses API + MCP in Practice: From Function Calling to Agent Workflows"},{"content":"If your WSL2 + Docker setup suddenly fails with docker pull timeouts, Temporary failure in name resolution, or containers that start but cannot access the internet, don\u0026rsquo;t nuke your environment yet. Most cases are recoverable in 15 minutes.\nThis guide gives you a practical sequence: identify whether the fault is DNS, proxy/VPN, virtual NIC, or Docker daemon config—then apply the smallest fix first.\n1) Identify the failure layer first Run these checks inside WSL:\n# 1) Basic network reachability from WSL ping -c 2 1.1.1.1 curl -I https://registry-1.docker.io # 2) DNS health cat /etc/resolv.conf nslookup registry-1.docker.io # 3) Docker daemon/container network health docker info | sed -n \u0026#39;1,80p\u0026#39; docker run --rm alpine sh -c \u0026#34;ping -c 2 1.1.1.1 \u0026amp;\u0026amp; nslookup google.com\u0026#34; Quick mapping:\nIP works but domain fails: DNS issue. WSL can access network but containers cannot: Docker network/daemon issue. Both fail: check Windows proxy/VPN/firewall first. 2) Fix WSL2 DNS (most common root cause) WSL2 may auto-generate an unusable /etc/resolv.conf. Pin DNS manually to verify:\nsudo tee /etc/wsl.conf \u0026gt;/dev/null \u0026lt;\u0026lt;\u0026#39;CONF\u0026#39; [network] generateResolvConf = false CONF sudo rm -f /etc/resolv.conf printf \u0026#34;nameserver 1.1.1.1\\nnameserver 8.8.8.8\\n\u0026#34; | sudo tee /etc/resolv.conf \u0026gt;/dev/null Then restart WSL from Windows PowerShell:\nwsl --shutdown Retest after relaunch:\nnslookup registry-1.docker.io docker pull alpine:latest 3) Add Docker daemon DNS + registry mirror fallback If DNS is fixed but pulling images is still unstable, add daemon-level fallback:\nsudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json \u0026gt;/dev/null \u0026lt;\u0026lt;\u0026#39;JSON\u0026#39; { \u0026#34;dns\u0026#34;: [\u0026#34;1.1.1.1\u0026#34;, \u0026#34;8.8.8.8\u0026#34;], \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://docker.m.daocloud.io\u0026#34; ] } JSON sudo service docker restart || sudo systemctl restart docker If you\u0026rsquo;re using Docker Desktop with WSL integration, prefer setting DNS/mirrors in Docker Desktop to avoid conflicting configs.\n4) Common errors and targeted fixes 1) Temporary failure in name resolution Meaning: DNS resolution is broken. Fix: apply step 2 and restart WSL. 2) net/http: request canceled while waiting for connection Meaning: outbound traffic blocked by proxy/VPN/firewall/routing. Fix: disable proxy/VPN temporarily and retest; then check enterprise network policy. 3) WSL can reach internet, containers cannot Meaning: Docker bridge/NAT path is broken. Fix: restart Docker service and rebuild networks if needed: docker network prune -f docker network ls docker run --rm alpine ping -c 2 8.8.8.8 5) MVP recovery path (fastest stable route) If you only need things working again quickly:\nPin WSL DNS (wsl.conf + resolv.conf). Run wsl --shutdown and retest docker pull. If still failing, add Docker daemon dns + registry-mirrors. This sequence usually restores WSL2 Docker networking with minimal side effects.\nSuggested follow-up reads WSL2 disk cleanup and performance tuning Docker build cache strategy for CI/CD Unified proxy setup for Docker/apt/git in enterprise networks ","permalink":"https://www.mfun.ink/en/2026/02/11/wsl2-docker-network-troubleshooting/","summary":"\u003cp\u003eIf your \u003cstrong\u003eWSL2 + Docker\u003c/strong\u003e setup suddenly fails with \u003ccode\u003edocker pull\u003c/code\u003e timeouts, \u003ccode\u003eTemporary failure in name resolution\u003c/code\u003e, or containers that start but cannot access the internet, don\u0026rsquo;t nuke your environment yet. Most cases are recoverable in 15 minutes.\u003c/p\u003e\n\u003cp\u003eThis guide gives you a practical sequence: identify whether the fault is DNS, proxy/VPN, virtual NIC, or Docker daemon config—then apply the smallest fix first.\u003c/p\u003e","title":"WSL2 + Docker Network Troubleshooting: Fix DNS Timeouts and Image Pull Failures"},{"content":"MCP sounds great in theory, but real-world setup often fails at browser debugging: AI cannot reach Chrome, cannot inspect network requests, and cannot collect performance traces. This guide gives you a copy-paste setup that works on Windows + WSL.\nEnvironment Windows 11 + WSL2 Google Chrome on Windows node / npx available inside WSL Any MCP-capable client (for example, Codex CLI) 1) Enable port forwarding and firewall rule on Windows Open PowerShell as Administrator and run:\nnetsh interface portproxy add v4tov4 ` listenaddress=0.0.0.0 listenport=9222 ` connectaddress=127.0.0.1 connectport=9222 New-NetFirewallRule ` -DisplayName \u0026#34;Chrome DevTools MCP 9222\u0026#34; ` -Direction Inbound ` -Protocol TCP ` -LocalPort 9222 ` -Action Allow This allows WSL to reach Chrome\u0026rsquo;s debugging endpoint.\n2) Launch Chrome with remote debugging \u0026amp; \u0026#34;C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\u0026#34; ` --user-data-dir=\u0026#34;$env:USERPROFILE\\ChromeProfiles\\mcp\u0026#34; ` --remote-debugging-port=9222 ` --remote-debugging-address=127.0.0.1 ` --no-first-run ` --no-default-browser-check Use a dedicated user data directory to avoid affecting your daily profile.\n3) Configure DevTools MCP in WSL Example configuration for Codex:\n[mcp_servers.chrome-devtools] command = \u0026#34;npx\u0026#34; args = [\u0026#34;chrome-devtools-mcp@latest\u0026#34;, \u0026#34;--browserUrl=http://172.31.240.1:9222/json\u0026#34;] The gateway IP (172.31.240.1) is environment-specific. Find it via ipconfig on Windows (vEthernet (WSL...)).\n4) Verify connectivity before asking AI to debug curl http://172.31.240.1:9222/json If you get JSON with webSocketDebuggerUrl, the pipeline is ready.\nNow AI can automate:\nConsole error collection Network bottleneck analysis Performance tracing for LCP/INP diagnostics Repeatable regression checks Troubleshooting curl times out Recheck portproxy and firewall rule Confirm Chrome is running with --remote-debugging-port=9222 MCP connects but no pages are visible Use the /json endpoint, not just :9222 Ensure Chrome is running in the expected profile/session Random disconnects Sleep/resume can break old sessions Restart Chrome first, then restart the MCP server Why this matters for content/SEO sites If you operate a blog, page quality affects indexing and engagement. With DevTools MCP connected, browser diagnostics become automation instead of manual QA, which makes technical SEO maintenance much more reliable.\nSummary Three essentials: expose 9222 correctly, launch Chrome with remote debugging, and connect from WSL using the gateway IP and /json. Once this is stable, AI-driven browser debugging finally becomes practical.\n","permalink":"https://www.mfun.ink/en/2026/02/10/mcp-devtools-mcp-auto-browser-debugging/","summary":"\u003cp\u003eMCP sounds great in theory, but real-world setup often fails at browser debugging: AI cannot reach Chrome, cannot inspect network requests, and cannot collect performance traces. This guide gives you a copy-paste setup that works on Windows + WSL.\u003c/p\u003e","title":"MCP in Practice: Automated Browser Debugging with DevTools MCP"},{"content":"If you use AI as a chatbot only, these tools feel similar. In real engineering workflows, they behave very differently.\nMy conclusion first: use Codex for repo-native coding changes, Claude for deep reasoning and long-form planning, and OpenAI CLI for standardized automation pipelines.\nEvaluation Criteria (Output, Not Hype) I compare them with five practical metrics:\nTime to first useful output Code applicability in real repos Stability across long multi-turn tasks Integration with scripts/MCP/CI Cost per useful outcome Codex: Best for In-Repo Execution Best for:\nEditing existing codebases Config/script fixes with fast iteration \u0026ldquo;inspect -\u0026gt; edit -\u0026gt; commit\u0026rdquo; loops Strengths:\nStrong project-context execution flow Fast command-line feedback cycle Good for iterative engineering tasks Tradeoff:\nLess ideal than Claude for long narrative strategy documents Claude: Strong for Reasoning and Structured Thinking Best for:\nArchitecture analysis Long-form documentation synthesis Constraint-heavy decision analysis Strengths:\nStable in long-context reasoning Clear explanation quality Tradeoff:\nLess direct than CLI-first tooling for file-level repo execution OpenAI CLI: Great for Automation and Orchestration Best for:\nBatch tasks Standardized content pipelines Shell-driven publishing flows Strengths:\nEasy to integrate into DevOps routines Good for repeatable operational jobs Tradeoff:\nAutomation amplifies whatever process you already have (good or bad) Recommended Stack (Fastest in Practice) Daily coding: Codex Planning/review: Claude Automation/publishing: OpenAI CLI One line summary: assign tools by role, not by fandom.\nA Practical Workflow You Can Use Today Draft architecture and risk plan with Claude Implement concrete repo changes with Codex Run standard release/documentation automation with OpenAI CLI Example (pseudo-commands):\n# 1) Strategy review claude \u0026#34;Review this migration plan and list risks\u0026#34; # 2) Execute inside repository codex \u0026#34;Apply config changes in ./infra and update docs\u0026#34; # 3) Automate publishing pipeline openai workflows run publish-blog --topic \u0026#34;ai-cli-comparison\u0026#34; Common Mistakes Trying one tool for everything -\u0026gt; unstable quality and cost No evaluation criteria -\u0026gt; lots of opinions, little output Automating a messy process -\u0026gt; faster failures MVP Setup If you want immediate gains:\nDefine 3 repeatable task templates (code edit, doc synthesis, publish) Assign one primary tool to each template Track execution time and rework rate for two weeks You will see quickly: productivity comes from workflow design, not model worship.\n","permalink":"https://www.mfun.ink/en/2026/02/09/claude-codex-openai-cli-workflow-comparison/","summary":"\u003cp\u003eIf you use AI as a chatbot only, these tools feel similar. In real engineering workflows, they behave very differently.\u003c/p\u003e\n\u003cp\u003eMy conclusion first: \u003cstrong\u003euse Codex for repo-native coding changes, Claude for deep reasoning and long-form planning, and OpenAI CLI for standardized automation pipelines.\u003c/strong\u003e\u003c/p\u003e","title":"Claude vs Codex vs OpenAI CLI: Which Workflow Actually Improves Dev Productivity"},{"content":"Large language models are changing how we clarify requirements, generate code, and design tests, and many teams feel that traditional workflows are being rewritten. To understand what is truly changing, it helps to place today inside the longer history of software engineering.\nThis article walks through the major stages of software engineering and ends with the AI-era variables and a simple checklist so you can map your current problems to the right time scale.\nBackground: From “Writing Code” to “Engineering Delivery” Software engineering is not just about writing code. It is about delivering reliable software under controlled cost and risk. From the beginning, that required teams, coordination, and method, not solo heroics.\nStages and Turning Points Stage Time Primary Traits Keywords Early Exploration 1940s-1960s Hardware-led, manual control of complexity Assembly, batch processing, early OS Software Crisis and Discipline 1968-1970s Scale out of control, delays, cost overruns Software crisis, structured methods, specs Standardization 1980s Process and quality management CMM, QA, structured design Object Orientation and Reuse 1990s Better modeling for complex systems OOP, UML, design patterns, components Internet and Agile 2000s Faster iteration and delivery cadence Scrum, XP, continuous integration Cloud Native and DevOps 2010s Infra automation and observability Microservices, containers, SRE, IaC AI Co-Creation 2020s- Shifts in collaboration across the lifecycle LLM, Copilot, evaluation, governance Most turning points fall into three categories:\nScale: moving from small teams to large coordination, driving process. Complexity: broader system boundaries, driving abstraction and reuse. Efficiency: faster delivery expectations, driving automation and platforms. Typical Evolution of Engineering Methods A short view of the main flow models:\nWaterfall: clear phase boundaries and documentation, good for stable requirements. Iterative/Incremental: staged delivery to reduce big-bang risk. Agile: feedback-focused, shorter cycles. DevOps/SRE: delivery and operations together, automation for stability. Platform Engineering: shared capabilities as a platform to raise org efficiency. New Variables in the AI Era AI does not replace engineering methods; it changes knowledge-heavy steps into collaborative, amplifiable, and governance-heavy work:\nRequirements and design: higher dependency on context quality; prompts become a new type of spec. Coding: faster generation, but consistency and constraints matter more. Testing: easier to generate cases, but coverage and quality still need human control. Operations: faster diagnosis and runbook generation, still requires strong observability. Governance: data security, compliance, and evaluation become new engineering overhead. How AI Participates in Real Workflows Treat AI as a structured artifact generator, not a free-form replacement.\nRequirements and planning: clarify needs, fill exception flows, draft acceptance criteria lists. Solutions and design: produce option comparisons, risk checklists, and API contract drafts. Development: generate module-level code under explicit inputs, outputs, and boundaries. Testing: generate edge and exception case drafts, humans curate critical paths. Release and operations: impact analysis, log summaries, and incident investigation drafts. After adoption, keep three controls: traceability (prompts and model version), evaluation (correctness/consistency/usability), and constraints (boundaries and compliance).\nVerify: Which Stage Is Your Team In? Use this checklist for a quick self-assessment:\nDo you have a stable mechanism for requirements and change control? Do you have CI/CD with rollback strategy? Do you enforce automated tests and quality gates on critical paths? Do you have unified monitoring, logging, and alerting? Do you evaluate AI outputs with explicit quality baselines (accuracy, explainability, compliance)? If the first four are weak, AI will not produce stable gains. If the fifth is missing, AI can introduce new risks.\nSummary Software engineering history is a line of “scaled collaboration + complexity control + delivery efficiency.” AI pushes that line into a new stage: higher productivity, denser collaboration, and stronger governance needs. Align your team capability to the right stage to capture AI’s real value.\n","permalink":"https://www.mfun.ink/en/2025/12/31/software-engineering-history-ai/","summary":"\u003cp\u003eLarge language models are changing how we clarify requirements, generate code, and design tests, and many teams feel that traditional workflows are being rewritten. To understand what is truly changing, it helps to place today inside the longer history of software engineering.\u003c/p\u003e\n\u003cp\u003eThis article walks through the major stages of software engineering and ends with the AI-era variables and a simple checklist so you can map your current problems to the right time scale.\u003c/p\u003e","title":"Software Engineering History: From Software Crisis to AI Co-Creation"},{"content":"Chrome DevTools MCP lets MCP clients connect to Chrome\u0026rsquo;s remote debugging endpoint. Because WSL2 and Windows are isolated at the network layer, you need port forwarding and a firewall rule. The commands below are split into clear steps.\nPrerequisites Chrome installed on Windows. WSL2 set up and npx available inside WSL. PowerShell running with admin privileges. 1. Configure port forwarding and firewall on Windows From an elevated PowerShell prompt, run:\nnetsh interface portproxy add v4tov4 ` listenaddress=0.0.0.0 listenport=9222 ` connectaddress=127.0.0.1 connectport=9222 New-NetFirewallRule ` -DisplayName \u0026#34;Chrome DevTools MCP 9222\u0026#34; ` -Direction Inbound ` -Protocol TCP ` -LocalPort 9222 ` -Action Allow The first command forwards 0.0.0.0:9222 to 127.0.0.1:9222. The second command allows inbound TCP 9222.\n2. Launch Chrome with remote debugging Still in PowerShell, run:\n\u0026amp; \u0026#34;C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\u0026#34; ` --user-data-dir=\u0026#34;$env:USERPROFILE\\ChromeProfiles\\mcp\u0026#34; ` --remote-debugging-port=9222 ` --remote-debugging-address=127.0.0.1 ` --no-first-run ` --no-default-browser-check This uses a dedicated user data directory to avoid affecting your daily browsing profile.\n3. Configure the MCP client in WSL Example for Codex in ~/.codex/config.toml:\n[mcp_servers.chrome-devtools] command = \u0026#34;npx\u0026#34; args = [\u0026#34;chrome-devtools-mcp@latest\u0026#34;, \u0026#34;--browserUrl=http://172.31.240.1:9222/json\u0026#34;] 172.31.240.1 should be taken from Windows. In PowerShell, run ipconfig and find the IPv4 address for vEthernet (WSL (Hyper-V firewall)):\nEthernet adapter vEthernet (WSL (Hyper-V firewall)): Connection-specific DNS suffix . : Link-local IPv6 Address . . . . . : fe80::2ec3:951c:91a0:afbd%61 IPv4 Address. . . . . . . . . . . : 172.31.240.1 Subnet Mask . . . . . . . . . . . : 255.255.240.0 Default Gateway . . . . . . . . . : Use that IPv4 address in browserUrl.\n4. Verify the connection From WSL, check the endpoint directly:\ncurl http://172.31.240.1:9222/json If it returns JSON entries with webSocketDebuggerUrl, the connection is working.\nTroubleshooting Port in use: Make sure nothing else is bound to 9222. Cannot reach 9222: Confirm the port proxy and firewall rule were created. Chrome didn\u0026rsquo;t launch: The command assumes C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe; update it if your install path differs. Summary The core flow is: forward and open port 9222 on Windows, launch Chrome with remote debugging, then use the WSL gateway IP to reach /json. After that, WSL-based MCP clients can connect to Chrome reliably.\n","permalink":"https://www.mfun.ink/en/2025/12/28/chrome-devtools-mcp-wsl/","summary":"\u003cp\u003eChrome DevTools MCP lets MCP clients connect to Chrome\u0026rsquo;s remote debugging endpoint. Because WSL2 and Windows are isolated at the network layer, you need port forwarding and a firewall rule. The commands below are split into clear steps.\u003c/p\u003e","title":"Configure Chrome DevTools MCP in WSL"},{"content":"For developers who love the power of Linux tools but work on Windows, the Windows Subsystem for Linux (WSL) is a game-changer. Cursor, the AI-first code editor, can further enhance this setup by integrating with Model Control Program (MCP) servers. Running these MCP servers directly within your WSL environment keeps your development workflow clean and consolidated. This guide will walk you through configuring Cursor to use MCP servers running in WSL.\nWhat is MCP? MCP (Model Control Protocol) is a protocol that allows AI models, like those in Cursor, to securely interact with various tools and data sources. These tools, or \u0026ldquo;MCP servers,\u0026rdquo; can perform actions like web searches, file system operations, or even browser interactions, providing the AI with a richer context and more capabilities.\nWhy Run MCP Servers in WSL? Consistency: Keep your entire development toolchain within the Linux environment you\u0026rsquo;re comfortable with. Simplified Paths: Avoid complexities with Windows vs. Linux path translations for tools that access the file system. Leverage Linux-specific Tools: Some MCP servers or their dependencies might work better or exclusively in a Linux environment. Containment: Keep Node.js versions and other dependencies required by MCP servers managed within WSL, separate from your Windows system. Prerequisites Before you begin, ensure you have the following:\nWSL Installed: A working WSL installation with a Linux distribution (e.g., Ubuntu). Node.js and npx in WSL: Node.js and npx (which comes with npm) must be installed inside your WSL distribution. It\u0026rsquo;s highly recommended to use a version manager like nvm (Node Version Manager) within WSL to manage Node.js versions. This helps avoid permission issues and allows easy switching between Node.js versions. Cursor IDE: Installed on your Windows machine. Configuration Steps The key to running MCP servers in WSL is to tell Cursor how to launch them using the WSL command-line interface. This is done by editing the mcp.json file.\n1. Locate or Create mcp.json Cursor stores its MCP server configurations in a file named mcp.json. On Windows, you can typically find this file in:\nC:\\\\Users\\\\\u0026lt;YourUserName\u0026gt;\\\\.cursor\\\\mcp.json\nReplace \u0026lt;YourUserName\u0026gt; with your actual Windows username. If the file or .cursor directory doesn\u0026rsquo;t exist, Cursor might create it when you first try to add an MCP server through its UI, or you might need to create it manually.\n2. Understanding mcp.json Structure The mcp.json file contains a JSON object, where each key under mcpServers represents a configured MCP server. Here\u0026rsquo;s a basic structure:\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;your-mcp-server-name\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;executable_or_wsl\u0026#34;, \u0026#34;args\u0026#34;: [\u0026#34;argument1\u0026#34;, \u0026#34;argument2\u0026#34;, \u0026#34;command_to_run_in_wsl\u0026#34;], \u0026#34;enabled\u0026#34;: true // Other options like \u0026#34;env\u0026#34;, \u0026#34;workingDirectory\u0026#34; can also be specified } } } 3. Core Configuration for WSL To run an MCP server (which is typically an npx command) within WSL, you\u0026rsquo;ll configure a server entry as follows:\n\u0026quot;command\u0026quot;: Set this to \u0026quot;wsl\u0026quot;.\n\u0026quot;args\u0026quot;: This will be an array. The first elements tell wsl which shell to use and to execute a command string. The final element is the command string itself.\nExample for bash: [\u0026quot;bash\u0026quot;, \u0026quot;-c\u0026quot;, \u0026quot;npx @some-mcp/package --port {{port}}\u0026quot;] Example for zsh: [\u0026quot;zsh\u0026quot;, \u0026quot;-c\u0026quot;, \u0026quot;npx @some-mcp/package --port {{port}}\u0026quot;] (Note: {{port}} is a placeholder that Cursor often uses to inject a port number for the MCP server to listen on. Check the specific MCP server\u0026rsquo;s documentation if it requires a port.)\n4. Handling Shell Environment (Crucial for nvm users) A common issue is that when Cursor invokes wsl ... npx ..., the npx command might not be found, or it might not use the Node.js version you expect (especially if you use nvm). This is because the non-interactive shell session started by wsl might not load your full shell profile (e.g., .bashrc, .zshrc), where nvm and other PATH modifications are usually configured.\nHere are two reliable solutions:\nSolution A: Using a Shell Script (Recommended for Clarity)\nThis method involves creating a small shell script within your WSL environment that sets up the environment and then runs the npx command.\nCreate the Script in WSL: Open your WSL terminal and create a script, for example, ~/run_browsermcp.sh:\n#!/bin/zsh # Or use #!/bin/bash if you use bash # Source your shell\u0026#39;s rc file to load nvm and other environment settings # Adjust the path to your rc file if necessary if [ -f \u0026#34;$HOME/.zshrc\u0026#34; ]; then source \u0026#34;$HOME/.zshrc\u0026#34; elif [ -f \u0026#34;$HOME/.bashrc\u0026#34; ]; then source \u0026#34;$HOME/.bashrc\u0026#34; fi # For debugging: check which npx is being used and Node version # which npx # node -v # The actual npx command for your MCP server # Replace with the specific MCP server command you need npx @browsermcp/mcp@latest --port {{port}} a example:\n#!/bin/sh zsh -c \u0026#34;source /home/vector/.zshrc \u0026amp;\u0026amp; npx @browsermcp/mcp@latest\u0026#34; (Note: The script directly invokes zsh -c. If using this approach, ensure the script itself is executable and the path to .zshrc is correct.)\nMake the Script Executable: In your WSL terminal:\nchmod +x ~/run_browsermcp.sh (Adjust script name as needed).\nConfigure mcp.json: Modify your mcp.json to execute this script. If your WSL username is vector and you use zsh, and the script is at /home/vector/run_browsermcp.sh:\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;browsermcp-wsl\u0026#34;: { // Give it a descriptive name \u0026#34;command\u0026#34;: \u0026#34;wsl\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;zsh\u0026#34;, // Or \u0026#34;bash\u0026#34; \u0026#34;-c\u0026#34;, \u0026#34;/home/vector/run_browsermcp.sh\u0026#34; // Full path to your script within WSL ], \u0026#34;enabled\u0026#34;: true } } } This matches your provided mcp.json structure for browsermcp:\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;browsermcp\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;wsl\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;zsh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;/home/vector/browsermcp.sh\u0026#34; // Path from your example ] } } } Solution B: Directly Sourcing in mcp.json\nYou can try to source your rc file directly in the args. This can sometimes be less reliable depending on the complexity of your rc file, but it\u0026rsquo;s more concise if it works.\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;my-mcp-server-bash\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;wsl\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;bash\u0026#34;, \u0026#34;-ic\u0026#34;, // Note: -i for interactive to potentially help source rc files \u0026#34;source ~/.bashrc \u0026amp;\u0026amp; npx @some-mcp/package --port {{port}}\u0026#34; ], \u0026#34;enabled\u0026#34;: true }, \u0026#34;my-mcp-server-zsh\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;wsl\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;zsh\u0026#34;, \u0026#34;-ic\u0026#34;, // Note: -i for interactive \u0026#34;source ~/.zshrc \u0026amp;\u0026amp; npx @some-mcp/package --port {{port}}\u0026#34; ], \u0026#34;enabled\u0026#34;: true } } } If nvm is used and the above still fails, you might need to provide the absolute path to nvm.sh and the npx executable installed by nvm:\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;my-mcp-nvm-direct\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;wsl\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;bash\u0026#34;, // Or \u0026#34;zsh\u0026#34; \u0026#34;-c\u0026#34;, // Replace /home/your_user and YOUR_NODE_VERSION appropriately \u0026#34;source /home/your_user/.nvm/nvm.sh \u0026amp;\u0026amp; /home/your_user/.nvm/versions/node/vXX.YY.Z/bin/npx @some-mcp/package --port {{port}}\u0026#34; ], \u0026#34;enabled\u0026#34;: true } } } To find the full path to npx after sourcing nvm.sh and selecting a Node version with nvm use, run which npx in your WSL terminal.\n5. Example: Configuring a Browser Interaction MCP Let\u0026rsquo;s use @browsermcp/mcp as an example, as it\u0026rsquo;s common.\nUsing Solution A (shell script ~/run_browsermcp.sh as defined before):\n{ \u0026#34;mcpServers\u0026#34;: { \u0026#34;browsermcp-wsl\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;wsl\u0026#34;, \u0026#34;args\u0026#34;: [ \u0026#34;zsh\u0026#34;, // Or \u0026#34;bash\u0026#34; if your script and environment use bash \u0026#34;-c\u0026#34;, \u0026#34;/home/YOUR_WSL_USERNAME/run_browsermcp.sh\u0026#34; // Adjust path ], \u0026#34;enabled\u0026#34;: true, \u0026#34;env\u0026#34;: { // Optional: if the MCP server needs specific environment variables // \u0026#34;DEBUG\u0026#34;: \u0026#34;true\u0026#34; } } } } Remember to replace YOUR_WSL_USERNAME with your actual WSL username.\nVerification After saving mcp.json:\nRestart Cursor or go to Cursor Settings -\u0026gt; Features -\u0026gt; MCP and click the refresh icon next to one of your servers or for all MCP servers. A green dot next to the server name in the MCP settings indicates Cursor has successfully connected to it. You might see a new terminal window pop up (launched by wsl). This window must remain open for the MCP server to function. This is expected behavior when running MCP servers this way. Test the MCP server by asking Cursor to perform an action that uses it (e.g., for @browsermcp/mcp, ask it to \u0026ldquo;take a screenshot of the current browser tab\u0026rdquo; after setting up the companion browser extension). Troubleshooting npx: command not found or wrong Node version: This almost always means the shell environment within WSL isn\u0026rsquo;t being set up correctly. Double-check paths in your shell script or direct command. Ensure nvm is sourced correctly. Try adding export NVM_DIR=\u0026quot;$HOME/.nvm\u0026quot; and [ -s \u0026quot;$NVM_DIR/nvm.sh\u0026quot; ] \u0026amp;\u0026amp; \\. \u0026quot;$NVM_DIR/nvm.sh\u0026quot; explicitly at the start of your script or command string if using nvm. Verify that node and npx are indeed installed in your WSL distribution and accessible after sourcing your rc file. MCP Server Fails to Connect (No Green Dot): Check the output in the WSL terminal window that pops up. It might contain error messages. Ensure the npx command for the MCP server is correct and includes any necessary arguments like --port {{port}}. Some MCPs might auto-assign ports if not specified. Firewall issues: Less common with WSL2\u0026rsquo;s networking model, but ensure Windows Firewall isn\u0026rsquo;t blocking connections to WSL. \u0026ldquo;Blank\u0026rdquo; Terminal Window: As mentioned, a terminal window will open for each MCP server launched via wsl. This is normal. Closing it will stop the MCP server. Conclusion Configuring Cursor to run MCP servers within your WSL environment provides a streamlined and powerful development setup on Windows. By carefully setting up your mcp.json and ensuring your WSL shell environment is correctly sourced, you can seamlessly integrate AI capabilities into your Linux-based workflows. The shell script method (Solution A) is generally the most robust way to handle environments managed by tools like nvm.\nHappy coding with Cursor and WSL! ","permalink":"https://www.mfun.ink/en/2025/05/13/installing-cursor-mcp-in-wsl/","summary":"\u003cp\u003eFor developers who love the power of Linux tools but work on Windows, the Windows Subsystem for Linux (WSL) is a game-changer. Cursor, the AI-first code editor, can further enhance this setup by integrating with Model Control Program (MCP) servers. Running these MCP servers directly within your WSL environment keeps your development workflow clean and consolidated. This guide will walk you through configuring Cursor to use MCP servers running in WSL.\u003c/p\u003e","title":"Installing Cursor MCP Servers in WSL for a Seamless Dev Experience"},{"content":"Introduction Hexo and Hugo are both excellent static site generators, but due to performance considerations or personal preference, you might want to migrate from Hexo to Hugo. This article will document the complete migration process in detail, including configuration, article conversion, multilingual support, and more.\nPreparation Before starting the migration, you need to install Hugo and understand the basic site creation process. Here are the official documentation links, which are recommended to follow:\n1. Install Hugo Hugo provides multiple installation methods, including pre-compiled binaries and package managers. Based on your operating system, please refer to the official installation documentation:\nHugo Official Installation Guide 2. Create a Hugo Site After installing Hugo, you can create a new Hugo site using the following command:\nhugo new site your-site-name cd your-site-name For more detailed information about creating a site, please refer to:\nHugo Quick Start Guide 3. Install a Theme Hugo has a rich selection of themes to choose from. You can browse and select a suitable theme on the Hugo Themes page.\nThe basic steps to install a theme are as follows:\ngit init git submodule add https://github.com/theme-author/theme-name.git themes/theme-name Then set the theme in your configuration file:\necho \u0026#34;theme = \u0026#39;theme-name\u0026#39;\u0026#34; \u0026gt;\u0026gt; hugo.toml Site Configuration Create a hugo.toml configuration file:\nbaseURL = \u0026#39;https://www.mfun.ink/\u0026#39; defaultContentLanguage = \u0026#39;zh-cn\u0026#39; title = \u0026#34;Mengboy\u0026#39;s Blog\u0026#34; theme = \u0026#39;PaperMod\u0026#39; hasCJKLanguage = true # Google Analytics configuration GoogleAnalytics = \u0026#34;G-xxxxxxx\u0026#34; # Please replace with your Google Analytics ID # Add permalink configuration to make link format compatible with Hexo [permalinks] post = \u0026#39;/:year/:month/:day/:contentbasename/\u0026#39; # PaperMod theme parameters [params] # Set environment to production, which is important for Google Analytics and AdSense loading env = \u0026#34;production\u0026#34; # Author information author = \u0026#34;mengboy\u0026#34; # Main sections mainSections = [\u0026#39;post\u0026#39;] defaultTheme = \u0026#34;auto\u0026#34; ShowReadingTime = true ShowShareButtons = true ShowPostNavLinks = true ShowBreadCrumbs = true ShowCodeCopyButtons = true ShowRssButtonInSectionTermList = true disableSpecial1stPost = false disableScrollToTop = false hideMeta = false hideFooter = false # Website favicon settings [params.assets] favicon = \u0026#34;/images/site/favicon.png\u0026#34; favicon16x16 = \u0026#34;/images/site/favicon.png\u0026#34; favicon32x32 = \u0026#34;/images/site/favicon.png\u0026#34; apple_touch_icon = \u0026#34;/images/site/favicon.png\u0026#34; # Google AdSense configuration, requires custom extension or theme support googleAdsense = \u0026#34;ca-pub-xxxxxxxxxx\u0026#34; # Please replace with your Google AdSense publisher ID googleAdsenseSlot = \u0026#34;xxxxxxxx\u0026#34; # Please replace with your Google AdSense ad unit ID # Sidebar configuration [params.profileMode] enabled = false title = \u0026#34;mengboy\u0026#34; subtitle = \u0026#34;Recording Learning and Life\u0026#34; imageUrl = \u0026#34;images/site/favicon.png\u0026#34; imageWidth = 120 imageHeight = 120 # Social icons [[params.socialIcons]] name = \u0026#34;github\u0026#34; url = \u0026#34;https://github.com/mengboy\u0026#34; # Search settings [params.fuseOpts] isCaseSensitive = false shouldSort = true location = 0 distance = 1000 threshold = 0.4 minMatchCharLength = 0 keys = [\u0026#34;title\u0026#34;, \u0026#34;permalink\u0026#34;, \u0026#34;summary\u0026#34;, \u0026#34;content\u0026#34;] # Allow HTML [markup.goldmark.renderer] unsafe = true # Ignore HTML warnings ignoreWarnings = [\u0026#39;raw-html\u0026#39;] ignoreLogs = [\u0026#39;warning-goldmark-raw-html\u0026#39;] [languages] [languages.zh-cn] contentDir = \u0026#39;content\u0026#39; languageName = \u0026#39;简体中文\u0026#39; weight = 10 title = \u0026#34;Mengboy\u0026#39;s Blog\u0026#34; # Chinese homepage information [languages.zh-cn.params] [languages.zh-cn.params.homeInfoParams] Title = \u0026#34;记录学习与生活点滴\u0026#34; # Chinese menu [languages.zh-cn.menu] [[languages.zh-cn.menu.main]] identifier = \u0026#34;home\u0026#34; name = \u0026#34;首页\u0026#34; url = \u0026#34;/\u0026#34; weight = 10 [[languages.zh-cn.menu.main]] identifier = \u0026#34;categories\u0026#34; name = \u0026#34;分类\u0026#34; url = \u0026#34;/categories/\u0026#34; weight = 20 [[languages.zh-cn.menu.main]] identifier = \u0026#34;tags\u0026#34; name = \u0026#34;标签\u0026#34; url = \u0026#34;/tags/\u0026#34; weight = 30 [[languages.zh-cn.menu.main]] identifier = \u0026#34;archives\u0026#34; name = \u0026#34;归档\u0026#34; url = \u0026#34;/archives/\u0026#34; weight = 40 [[languages.zh-cn.menu.main]] identifier = \u0026#34;about\u0026#34; name = \u0026#34;关于我\u0026#34; url = \u0026#34;/about/\u0026#34; weight = 50 [languages.en] contentDir = \u0026#39;content/english\u0026#39; languageName = \u0026#39;English\u0026#39; weight = 20 title = \u0026#34;Mengboy\u0026#39;s Blog\u0026#34; # English homepage information [languages.en.params] [languages.en.params.homeInfoParams] Title = \u0026#34;Recording Learning and Life\u0026#34; # English menu [languages.en.menu] [[languages.en.menu.main]] identifier = \u0026#34;home\u0026#34; name = \u0026#34;Home\u0026#34; url = \u0026#34;/\u0026#34; weight = 10 [[languages.en.menu.main]] identifier = \u0026#34;categories\u0026#34; name = \u0026#34;Categories\u0026#34; url = \u0026#34;/en/categories/\u0026#34; weight = 20 [[languages.en.menu.main]] identifier = \u0026#34;tags\u0026#34; name = \u0026#34;Tags\u0026#34; url = \u0026#34;/en/tags/\u0026#34; weight = 30 [[languages.en.menu.main]] identifier = \u0026#34;archives\u0026#34; name = \u0026#34;Archives\u0026#34; url = \u0026#34;/en/archives/\u0026#34; weight = 40 [[languages.en.menu.main]] identifier = \u0026#34;about\u0026#34; name = \u0026#34;About Me\u0026#34; url = \u0026#34;/en/about/\u0026#34; weight = 50 Create Content Directories mkdir -p content/post mkdir -p content/english/post # Directory for English articles Create Basic Pages About Page mkdir -p content/about Create Chinese about page content/about/index.md:\n--- title: \u0026#34;关于我\u0026#34; date: 2023-06-01T12:00:00+08:00 --- 这是关于我的页面内容... Create English about page content/english/about/index.md:\n--- title: \u0026#34;About Me\u0026#34; date: 2023-06-01T12:00:00+08:00 --- This is about me page... You can create other basic pages using the about page as a reference.\nWrite the Conversion Script Create a Python script to convert Hexo articles to Hugo format:\n#!/usr/bin/env python3 # Convert Hexo Markdown files to Hugo format import os import re import datetime import shutil from pathlib import Path # Setup paths hexo_posts_dir = \u0026#34;hexo/source/_posts\u0026#34; hugo_posts_dir = \u0026#34;hugo/content/post\u0026#34; hexo_img_dir = \u0026#34;hexo/source/images\u0026#34; hugo_static_dir = \u0026#34;hugo/static/images\u0026#34; # Ensure target directories exist os.makedirs(hugo_posts_dir, exist_ok=True) os.makedirs(hugo_static_dir, exist_ok=True) # Copy image folder def copy_images(): if os.path.exists(hexo_img_dir): print(f\u0026#34;Copying image files from {hexo_img_dir} to {hugo_static_dir}\u0026#34;) # Copy folder contents for item in os.listdir(hexo_img_dir): s = os.path.join(hexo_img_dir, item) d = os.path.join(hugo_static_dir, item) if os.path.isdir(s): shutil.copytree(s, d, dirs_exist_ok=True) else: shutil.copy2(s, d) # Process YAML front-matter def convert_front_matter(content): # Extract front-matter match = re.match(r\u0026#39;^---\\s+(.*?)\\s+---\\s*\u0026#39;, content, re.DOTALL) if not match: return content front_matter = match.group(1) rest_content = content[match.end():] # Convert tags and categories new_front_matter = [] categories = [] tags = [] date = None for line in front_matter.strip().split(\u0026#39;\\n\u0026#39;): line = line.strip() # Process date if line.startswith(\u0026#39;date:\u0026#39;): date_str = line[5:].strip() try: date = datetime.datetime.strptime(date_str, \u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) new_front_matter.append(f\u0026#39;date: {date.strftime(\u0026#34;%Y-%m-%dT%H:%M:%S+08:00\u0026#34;)}\u0026#39;) # Add lastmod new_front_matter.append(f\u0026#39;lastmod: {date.strftime(\u0026#34;%Y-%m-%dT%H:%M:%S+08:00\u0026#34;)}\u0026#39;) except ValueError: new_front_matter.append(line) # Process tags elif line.startswith(\u0026#39;tags:\u0026#39;): tags_str = line[5:].strip() if tags_str.startswith(\u0026#39;[\u0026#39;) and tags_str.endswith(\u0026#39;]\u0026#39;): # Format is tags: [tag1, tag2] tags = [tag.strip(\u0026#39; \u0026#34;\\\u0026#39;\u0026#39;) for tag in tags_str[1:-1].split(\u0026#39;,\u0026#39;)] else: # Might be multi-line format continue # Process categories elif line.startswith(\u0026#39;categories:\u0026#39;): cats_str = line[11:].strip() if cats_str.startswith(\u0026#39;[\u0026#39;) and cats_str.endswith(\u0026#39;]\u0026#39;): # Format is categories: [cat1, cat2] categories = [cat.strip(\u0026#39; \u0026#34;\\\u0026#39;\u0026#39;) for cat in cats_str[1:-1].split(\u0026#39;,\u0026#39;)] else: # Might be multi-line format continue # Copy other lines elif not (line.startswith(\u0026#39;- \u0026#39;) and (\u0026#39;tags:\u0026#39; in front_matter or \u0026#39;categories:\u0026#39; in front_matter)): new_front_matter.append(line) # Add categories and tags if categories: new_front_matter.append(f\u0026#39;categories: {categories}\u0026#39;) if tags: new_front_matter.append(f\u0026#39;tags: {tags}\u0026#39;) # Add slug if \u0026#39;slug:\u0026#39; not in front_matter: filename = os.path.splitext(os.path.basename(content))[0] new_front_matter.append(f\u0026#39;slug: \u0026#34;{filename}\u0026#34;\u0026#39;) # Combine new front-matter new_content = \u0026#39;---\\n\u0026#39; + \u0026#39;\\n\u0026#39;.join(new_front_matter) + \u0026#39;\\n---\\n\\n\u0026#39; + rest_content return new_content # Convert Markdown content def convert_markdown_content(content): # Replace image links content = re.sub(r\u0026#39;!\\[(.*?)\\]\\((.*?)/images/(.*?)\\)\u0026#39;, r\u0026#39;![\\1](/images/\\3)\u0026#39;, content) # Handle the \u0026#34;more\u0026#34; marker content = re.sub(r\u0026#39;\u0026lt;!--\\s*more\\s*--\u0026gt;\u0026#39;, \u0026#39; \u0026#39;, content) return content # Main conversion function def convert_posts(): print(f\u0026#34;Starting to convert posts from {hexo_posts_dir} to {hugo_posts_dir}\u0026#34;) files = os.listdir(hexo_posts_dir) for file in files: if not file.endswith(\u0026#39;.md\u0026#39;): continue source_file = os.path.join(hexo_posts_dir, file) target_file = os.path.join(hugo_posts_dir, file) print(f\u0026#34;Converting: {source_file} -\u0026gt; {target_file}\u0026#34;) # Read source file with open(source_file, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # Convert content content = convert_front_matter(content) content = convert_markdown_content(content) # Write to target file with open(target_file, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(content) # Detect language and create multilingual files def handle_multilingual(): print(\u0026#34;Processing multilingual articles...\u0026#34;) for file in os.listdir(hugo_posts_dir): if not file.endswith(\u0026#39;.md\u0026#39;): continue filepath = os.path.join(hugo_posts_dir, file) # Read file content with open(filepath, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() # Check if there\u0026#39;s a language marker lang_match = re.search(r\u0026#39;language:\\s*[\\\u0026#39;\u0026#34;]?([a-z\\-]+)[\\\u0026#39;\u0026#34;]?\u0026#39;, content) if lang_match: lang = lang_match.group(1) if lang.startswith(\u0026#39;en\u0026#39;): # Create English version en_file = os.path.splitext(file)[0] + \u0026#39;.en.md\u0026#39; en_filepath = os.path.join(hugo_posts_dir, en_file) # Remove language marker and write to new file content = re.sub(r\u0026#39;language:\\s*[\\\u0026#39;\u0026#34;]?[a-z\\-]+[\\\u0026#39;\u0026#34;]?\\n\u0026#39;, \u0026#39;\u0026#39;, content) with open(en_filepath, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(content) # Delete original file os.remove(filepath) print(f\u0026#34; Created English article: {en_filepath}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: copy_images() convert_posts() handle_multilingual() print(\u0026#34;Conversion complete!\u0026#34;) Run the Conversion Script cd ~/dev/book python3 convert_hexo_to_hugo.py Create a Start Script Create start.sh:\n#!/bin/bash # Start Hugo server hugo server -D Add execution permission:\nchmod +x start.sh Create a Deployment Script Create deploy.sh:\n#!/bin/bash echo -e \u0026#34;\\033[0;32mDeploying blog to GitHub Pages...\\033[0m\u0026#34; # Generate static files hugo # Go to public directory cd public # Initialize git repository if [ ! -d \u0026#34;.git\u0026#34; ]; then git init git remote add origin YOUR_GITHUB_REPOSITORY_URL # Replace with your GitHub repository URL fi # Add files git add . # Commit changes msg=\u0026#34;rebuilding site $(date)\u0026#34; if [ $# -eq 1 ]; then msg=\u0026#34;$1\u0026#34; fi git commit -m \u0026#34;$msg\u0026#34; # Push to GitHub git push -u origin master # Return to parent directory cd .. Add execution permission:\nchmod +x deploy.sh Test Run ./start.sh Create Multilingual Article Examples Chinese Article hugo new post/my-first-post.md Edit content/post/my-first-post.md:\n--- title: \u0026#34;我的第一篇博客\u0026#34; date: 2023-06-10T15:30:00+08:00 lastmod: 2023-06-10T15:30:00+08:00 categories: [\u0026#34;博客\u0026#34;] tags: [\u0026#34;Hugo\u0026#34;, \u0026#34;入门\u0026#34;] slug: \u0026#34;my-first-post\u0026#34; description: \u0026#34;这是我使用Hugo创建的第一篇博客文章\u0026#34; --- ## 介绍 这是我的第一篇Hugo博客文章。 \u0026lt;!--more--\u0026gt; 这是摘要后的内容。 English Article hugo new post/english/my-first-post.md Edit content/english/post/my-first-post.md:\n--- title: \u0026#34;My First Blog Post\u0026#34; date: 2023-06-10T15:30:00+08:00 lastmod: 2023-06-10T15:30:00+08:00 categories: [\u0026#34;Blog\u0026#34;] tags: [\u0026#34;Hugo\u0026#34;, \u0026#34;Getting Started\u0026#34;] slug: \u0026#34;my-first-post\u0026#34; description: \u0026#34;This is my first blog post created with Hugo\u0026#34; --- ## Introduction This is my first Hugo blog post. \u0026lt;!--more--\u0026gt; This is the content after the summary. Conclusion At this point, we have completed the entire migration process from Hexo to Hugo, including article conversion, multilingual settings, and deployment preparation. Hugo\u0026rsquo;s powerful performance and flexible features will bring a new experience to your blog.\n","permalink":"https://www.mfun.ink/en/2025/05/04/hexo-to-hugo-migration/","summary":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eHexo and Hugo are both excellent static site generators, but due to performance considerations or personal preference, you might want to migrate from Hexo to Hugo. This article will document the complete migration process in detail, including configuration, article conversion, multilingual support, and more.\u003c/p\u003e\n\u003ch2 id=\"preparation\"\u003ePreparation\u003c/h2\u003e\n\u003cp\u003eBefore starting the migration, you need to install Hugo and understand the basic site creation process. Here are the official documentation links, which are recommended to follow:\u003c/p\u003e\n\u003ch3 id=\"1-install-hugo\"\u003e1. Install Hugo\u003c/h3\u003e\n\u003cp\u003eHugo provides multiple installation methods, including pre-compiled binaries and package managers. Based on your operating system, please refer to the official installation documentation:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://gohugo.io/installation/\"\u003eHugo Official Installation Guide\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"2-create-a-hugo-site\"\u003e2. Create a Hugo Site\u003c/h3\u003e\n\u003cp\u003eAfter installing Hugo, you can create a new Hugo site using the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo new site your-site-name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd your-site-name\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFor more detailed information about creating a site, please refer to:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://gohugo.io/getting-started/quick-start/\"\u003eHugo Quick Start Guide\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"3-install-a-theme\"\u003e3. Install a Theme\u003c/h3\u003e\n\u003cp\u003eHugo has a rich selection of themes to choose from. You can browse and select a suitable theme on the \u003ca href=\"https://themes.gohugo.io/\"\u003eHugo Themes page\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe basic steps to install a theme are as follows:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit init\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit submodule add https://github.com/theme-author/theme-name.git themes/theme-name\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen set the theme in your configuration file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;theme = \u0026#39;theme-name\u0026#39;\u0026#34;\u003c/span\u003e \u0026gt;\u0026gt; hugo.toml\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"site-configuration\"\u003eSite Configuration\u003c/h2\u003e\n\u003cp\u003eCreate a \u003ccode\u003ehugo.toml\u003c/code\u003e configuration file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-toml\" data-lang=\"toml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ebaseURL\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;https://www.mfun.ink/\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edefaultContentLanguage\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;zh-cn\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Mengboy\u0026#39;s Blog\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003etheme\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;PaperMod\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ehasCJKLanguage\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Google Analytics configuration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eGoogleAnalytics\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;G-xxxxxxx\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# Please replace with your Google Analytics ID\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Add permalink configuration to make link format compatible with Hexo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[\u003cspan style=\"color:#a6e22e\"\u003epermalinks\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003epost\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/:year/:month/:day/:contentbasename/\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# PaperMod theme parameters\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Set environment to production, which is important for Google Analytics and AdSense loading\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eenv\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;production\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Author information\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eauthor\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mengboy\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Main sections\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003emainSections\u003c/span\u003e = [\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;post\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003edefaultTheme\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;auto\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eShowReadingTime\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eShowShareButtons\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eShowPostNavLinks\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eShowBreadCrumbs\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eShowCodeCopyButtons\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eShowRssButtonInSectionTermList\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003edisableSpecial1stPost\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003edisableScrollToTop\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ehideMeta\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ehideFooter\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Website favicon settings\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eassets\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efavicon\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/images/site/favicon.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efavicon16x16\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/images/site/favicon.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efavicon32x32\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/images/site/favicon.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eapple_touch_icon\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/images/site/favicon.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Google AdSense configuration, requires custom extension or theme support\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003egoogleAdsense\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ca-pub-xxxxxxxxxx\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# Please replace with your Google AdSense publisher ID\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003egoogleAdsenseSlot\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;xxxxxxxx\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# Please replace with your Google AdSense ad unit ID\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Sidebar configuration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eprofileMode\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eenabled\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mengboy\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003esubtitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Recording Learning and Life\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eimageUrl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;images/site/favicon.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eimageWidth\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e120\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eimageHeight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e120\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Social icons\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [[\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003esocialIcons\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://github.com/mengboy\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e# Search settings\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003efuseOpts\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eisCaseSensitive\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eshouldSort\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003elocation\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003edistance\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e1000\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ethreshold\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e0.4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eminMatchCharLength\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ekeys\u003c/span\u003e = [\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;title\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;permalink\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;summary\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;content\u0026#34;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Allow HTML\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[\u003cspan style=\"color:#a6e22e\"\u003emarkup\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003egoldmark\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003erenderer\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eunsafe\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Ignore HTML warnings\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eignoreWarnings\u003c/span\u003e = [\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;raw-html\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eignoreLogs\u003c/span\u003e = [\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;warning-goldmark-raw-html\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003econtentDir\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003elanguageName\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;简体中文\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003etitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Mengboy\u0026#39;s Blog\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Chinese homepage information\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ehomeInfoParams\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eTitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;记录学习与生活点滴\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Chinese menu\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;home\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;首页\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;categories\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;分类\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/categories/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;tags\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;标签\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/tags/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e30\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;archives\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;归档\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/archives/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e40\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ezh-cn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;about\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;关于我\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/about/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e50\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e]    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003econtentDir\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content/english\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003elanguageName\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;English\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003etitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Mengboy\u0026#39;s Blog\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# English homepage information\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ehomeInfoParams\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eTitle\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Recording Learning and Life\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# English menu\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        [\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;home\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Home\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;categories\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Categories\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/en/categories/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;tags\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Tags\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/en/tags/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e30\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;archives\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Archives\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/en/archives/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e40\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          [[\u003cspan style=\"color:#a6e22e\"\u003elanguages\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003een\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emenu\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eidentifier\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;about\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;About Me\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/en/about/\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eweight\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e50\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"create-content-directories\"\u003eCreate Content Directories\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir -p content/post\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir -p content/english/post  \u003cspan style=\"color:#75715e\"\u003e# Directory for English articles\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"create-basic-pages\"\u003eCreate Basic Pages\u003c/h2\u003e\n\u003ch3 id=\"about-page\"\u003eAbout Page\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir -p content/about\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eCreate Chinese about page \u003ccode\u003econtent/about/index.md\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etitle: \u0026#34;关于我\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edate: 2023-06-01T12:00:00+08:00\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e这是关于我的页面内容...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eCreate English about page \u003ccode\u003econtent/english/about/index.md\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etitle: \u0026#34;About Me\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edate: 2023-06-01T12:00:00+08:00\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eThis is about me page...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can create other basic pages using the about page as a reference.\u003c/p\u003e\n\u003ch2 id=\"write-the-conversion-script\"\u003eWrite the Conversion Script\u003c/h2\u003e\n\u003cp\u003eCreate a Python script to convert Hexo articles to Hugo format:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#!/usr/bin/env python3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Convert Hexo Markdown files to Hugo format\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e os\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e re\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e datetime\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e shutil\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e pathlib \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e Path\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Setup paths\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehexo_posts_dir \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hexo/source/_posts\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo_posts_dir \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hugo/content/post\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehexo_img_dir \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hexo/source/images\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo_static_dir \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hugo/static/images\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Ensure target directories exist\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eos\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emakedirs(hugo_posts_dir, exist_ok\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTrue\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eos\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emakedirs(hugo_static_dir, exist_ok\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTrue\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Copy image folder\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecopy_images\u003c/span\u003e():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eexists(hexo_img_dir):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        print(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Copying image files from \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003ehexo_img_dir\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e to \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003ehugo_static_dir\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Copy folder contents\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e item \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elistdir(hexo_img_dir):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            s \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(hexo_img_dir, item)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            d \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(hugo_static_dir, item)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eisdir(s):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                shutil\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecopytree(s, d, dirs_exist_ok\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eTrue\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                shutil\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecopy2(s, d)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Process YAML front-matter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econvert_front_matter\u003c/span\u003e(content):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Extract front-matter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;^---\\s+(.*?)\\s+---\\s*\u0026#39;\u003c/span\u003e, content, re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDOTALL)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e content\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    front_matter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egroup(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    rest_content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e content[\u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eend():]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Convert tags and categories\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    new_front_matter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    categories \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    tags \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    date \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e line \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        line \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e line\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Process date\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e line\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;date:\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            date_str \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e line[\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e:]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                date \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e datetime\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edatetime\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrptime(date_str, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;%Y-%m-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e %H:%M:%S\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;date: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003edate\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrftime(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eT%H:%M:%S+08:00\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Add lastmod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;lastmod: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003edate\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrftime(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eT%H:%M:%S+08:00\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eexcept\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eValueError\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(line)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Process tags\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e line\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;tags:\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            tags_str \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e line[\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e:]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e tags_str\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;[\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e tags_str\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eendswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;]\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Format is tags: [tag1, tag2]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                tags \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [tag\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e tag \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e tags_str[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;,\u0026#39;\u003c/span\u003e)]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Might be multi-line format\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Process categories\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e line\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;categories:\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            cats_str \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e line[\u003cspan style=\"color:#ae81ff\"\u003e11\u003c/span\u003e:]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e cats_str\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;[\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e cats_str\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eendswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;]\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Format is categories: [cat1, cat2]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                categories \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [cat\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e cat \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e cats_str[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;,\u0026#39;\u003c/span\u003e)]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Might be multi-line format\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Copy other lines\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e (line\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;- \u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e (\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;tags:\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e front_matter \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;categories:\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e front_matter)):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(line)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Add categories and tags\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e categories:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;categories: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003ecategories\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e tags:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;tags: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003etags\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Add slug\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;slug:\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e front_matter:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        filename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplitext(os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebasename(content))[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        new_front_matter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;slug: \u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003efilename\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Combine new front-matter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    new_content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;---\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(new_front_matter) \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e---\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e rest_content\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new_content\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Convert Markdown content\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econvert_markdown_content\u003c/span\u003e(content):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Replace image links\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esub(\u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;!\\[(.*?)\\]\\((.*?)/images/(.*?)\\)\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;![\\1](/images/\\3)\u0026#39;\u003c/span\u003e, content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# Handle the \u0026#34;more\u0026#34; marker\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esub(\u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;!--\\s*more\\s*--\u0026gt;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;, content)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e content\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Main conversion function\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econvert_posts\u003c/span\u003e():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    print(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Starting to convert posts from \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003ehexo_posts_dir\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e to \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003ehugo_posts_dir\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    files \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elistdir(hexo_posts_dir)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e file \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e files:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e file\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eendswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.md\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        source_file \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(hexo_posts_dir, file)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        target_file \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(hugo_posts_dir, file)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        print(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Converting: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003esource_file\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e -\u0026gt; \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003etarget_file\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Read source file\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e open(source_file, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;r\u0026#39;\u003c/span\u003e, encoding\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e f:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eread()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Convert content\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e convert_front_matter(content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e convert_markdown_content(content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Write to target file\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e open(target_file, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;w\u0026#39;\u003c/span\u003e, encoding\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e f:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewrite(content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Detect language and create multilingual files\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehandle_multilingual\u003c/span\u003e():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Processing multilingual articles...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e file \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elistdir(hugo_posts_dir):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e file\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eendswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.md\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        filepath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(hugo_posts_dir, file)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Read file content\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e open(filepath, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;r\u0026#39;\u003c/span\u003e, encoding\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e f:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eread()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# Check if there\u0026#39;s a language marker\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        lang_match \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esearch(\u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;language:\\s*[\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;]?([a-z\\-]+)[\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;]?\u0026#39;\u003c/span\u003e, content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e lang_match:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            lang \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lang_match\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egroup(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e lang\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;en\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Create English version\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                en_file \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplitext(file)[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.en.md\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                en_filepath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(hugo_posts_dir, en_file)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Remove language marker and write to new file\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                content \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esub(\u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;language:\\s*[\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;]?[a-z\\-]+[\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;]?\\n\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e, content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e open(en_filepath, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;w\u0026#39;\u003c/span\u003e, encoding\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e f:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewrite(content)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# Delete original file\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eremove(filepath)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                print(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;  Created English article: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003een_filepath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e __name__ \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;__main__\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    copy_images()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    convert_posts()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    handle_multilingual()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Conversion complete!\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"run-the-conversion-script\"\u003eRun the Conversion Script\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd ~/dev/book\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epython3 convert_hexo_to_hugo.py\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"create-a-start-script\"\u003eCreate a Start Script\u003c/h2\u003e\n\u003cp\u003eCreate \u003ccode\u003estart.sh\u003c/code\u003e:\u003c/p\u003e","title":"Complete Guide to Migrating from Hexo to Hugo"},{"content":"About Me I\u0026rsquo;m Mengboy. I work a lot on backend systems, cloud-native tooling, and automation, and I turn hard-earned debugging experience into reusable notes.\nThis blog is not about generic theory. It\u0026rsquo;s about real problems + executable solutions: commands you can run, configs you can apply, and workflows you can reuse.\nWhat I Write Here Engineering Practice: troubleshooting and performance tuning for Go, Redis, Nginx, Docker, Linux/WSL AI in Production Workflows: MCP, automation pipelines, and integrating AI into day-to-day development Deployment \u0026amp; Ops: stable release strategies, rollback safety, and production-minded setup Reusable Methods: decision frameworks, checklists, and templates that save time Tech Preferences Backend: Go, Python Infrastructure: Docker, Kubernetes, Linux Data Layer: MySQL, Redis, MongoDB Engineering Habits: incremental delivery, scripting-first, docs-first Writing Principles Conclusion first, then explanation. Focus on why failures happen and how to locate them fast. Include copy-paste-ready commands and configuration whenever possible. If a post helps you solve a real issue faster, it has done its job.\n","permalink":"https://www.mfun.ink/en/about/","summary":"\u003ch2 id=\"about-me\"\u003eAbout Me\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;m Mengboy. I work a lot on backend systems, cloud-native tooling, and automation, and I turn hard-earned debugging experience into reusable notes.\u003c/p\u003e\n\u003cp\u003eThis blog is not about generic theory. It\u0026rsquo;s about \u003cstrong\u003ereal problems + executable solutions\u003c/strong\u003e: commands you can run, configs you can apply, and workflows you can reuse.\u003c/p\u003e\n\u003ch2 id=\"what-i-write-here\"\u003eWhat I Write Here\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eEngineering Practice\u003c/strong\u003e: troubleshooting and performance tuning for Go, Redis, Nginx, Docker, Linux/WSL\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAI in Production Workflows\u003c/strong\u003e: MCP, automation pipelines, and integrating AI into day-to-day development\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDeployment \u0026amp; Ops\u003c/strong\u003e: stable release strategies, rollback safety, and production-minded setup\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eReusable Methods\u003c/strong\u003e: decision frameworks, checklists, and templates that save time\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"tech-preferences\"\u003eTech Preferences\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eBackend\u003c/strong\u003e: Go, Python\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInfrastructure\u003c/strong\u003e: Docker, Kubernetes, Linux\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eData Layer\u003c/strong\u003e: MySQL, Redis, MongoDB\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEngineering Habits\u003c/strong\u003e: incremental delivery, scripting-first, docs-first\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"writing-principles\"\u003eWriting Principles\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eConclusion first, then explanation.\u003c/li\u003e\n\u003cli\u003eFocus on why failures happen and how to locate them fast.\u003c/li\u003e\n\u003cli\u003eInclude copy-paste-ready commands and configuration whenever possible.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIf a post helps you solve a real issue faster, it has done its job.\u003c/p\u003e","title":"About Me"}]