[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-list-v2":3},[4,354,2281,3310,5032,5573,6940,7842],{"id":5,"title":6,"body":7,"date":337,"description":338,"draft":339,"extension":340,"meta":341,"navigation":342,"path":343,"seo":344,"stem":345,"tags":346,"__hash__":353},"blog\u002Fblog\u002F2026-06-07-cognee-e5-large-embedding-switch.md","Cognee Embedding 模型升级：从 bge-small 到 multilingual-e5-large",{"type":8,"value":9,"toc":323},"minimark",[10,17,22,30,33,49,52,56,59,144,150,154,159,162,172,179,183,190,193,197,200,211,218,222,232,239,243,249,252,256,318],[11,12,13],"blockquote",{},[14,15,16],"p",{},"从 384d 到 1024d，一行配置变更的背后是向量库全量重建、容器 OOM 循环、以及一个藏得很深的 ONNX 缓存 bug。",[18,19,21],"h2",{"id":20},"一为什么换","一、为什么换",[14,23,24,25,29],{},"Cognee 知识图谱系统最初部署时选择了 ",[26,27,28],"strong",{},"BAAI\u002Fbge-small-en-v1.5","——384 维、261MB 内存占用、纯英文模型。在当时的约束下（无 GPU、7.5GB 总内存），这是唯一能跑通的本地 embedding 方案。",[14,31,32],{},"但使用中发现两个问题：",[34,35,36,43],"ol",{},[37,38,39,42],"li",{},[26,40,41],{},"中文语义匹配弱"," — bge-small 是纯英文模型，中文查询返回的向量质量明显下降",[37,44,45,48],{},[26,46,47],{},"384 维区分度不够"," — 向量空间较窄，知识条目增多后检索精度下降",[14,50,51],{},"需要升级到更大的多语言模型。",[18,53,55],{"id":54},"二三模型对比","二、三模型对比",[14,57,58],{},"在正式切换前，测试了三个候选模型：",[60,61,62,84],"table",{},[63,64,65],"thead",{},[66,67,68,72,75,78,81],"tr",{},[69,70,71],"th",{},"模型",[69,73,74],{},"维度",[69,76,77],{},"预期内存",[69,79,80],{},"多语言",[69,82,83],{},"结果",[85,86,87,108,127],"tbody",{},[66,88,89,96,99,102,105],{},[90,91,92],"td",{},[93,94,95],"code",{},"BAAI\u002Fbge-small-zh-v1.5",[90,97,98],{},"512d",[90,100,101],{},"~300MB",[90,103,104],{},"中英",[90,106,107],{},"精度提升有限，与 bge-small 差距不大",[66,109,110,115,118,121,124],{},[90,111,112],{},[93,113,114],{},"intfloat\u002Fmultilingual-e5-large",[90,116,117],{},"1024d",[90,119,120],{},"~800MB",[90,122,123],{},"原生多语言",[90,125,126],{},"✅ 精度显著提升，内存可接受",[66,128,129,134,136,139,141],{},[90,130,131],{},[93,132,133],{},"jinaai\u002Fjina-embeddings-v3",[90,135,117],{},[90,137,138],{},">2GB",[90,140,80],{},[90,142,143],{},"❌ OOM，容器 4G 内存限制下直接崩溃",[14,145,146,149],{},[26,147,148],{},"选定 e5-large","：jina-v3 内存需求过大（单模型超 2GB），在当前服务器容器限制下无解；bge-small-zh 精度提升有限；e5-large 在精度和资源之间取得平衡。",[18,151,153],{"id":152},"三技术难点","三、技术难点",[155,156,158],"h3",{"id":157},"_31-维度不兼容-必须全量重建","3.1 维度不兼容 — 必须全量重建",[14,160,161],{},"LanceDB 不支持 in-place 修改向量维度。384d → 1024d 的迁移路径是：",[163,164,169],"pre",{"className":165,"code":167,"language":168},[166],"language-text","删除旧 collection → 切换 embedding 模型 → 重新 cognify 全量知识\n","text",[93,170,167],{"__ignoreMap":171},"",[14,173,174,175,178],{},"没有捷径。这也意味着切换期间全部存量知识的语义召回能力归零，直到 ",[93,176,177],{},"cognify"," 重新跑完。",[155,180,182],{"id":181},"_32-内存翻倍-容器调参","3.2 内存翻倍 — 容器调参",[14,184,185,186,189],{},"bge-small 的 261MB 到 e5-large 的理论 843MB，内存翻了三倍。但实际运行中发现 ",[26,187,188],{},"e5-large 实际占用 2.1GB","——远超预期的 800MB。原因：ONNX 运行时会额外分配推理缓冲区，加上 LanceDB 的索引开销。",[14,191,192],{},"对应调整：容器 mem_limit 从 2g 提升到 4g。",[155,194,196],{"id":195},"_33-重启循环-一个-onnx-缓存-bug","3.3 重启循环 — 一个 ONNX 缓存 bug",[14,198,199],{},"切换 e5-large 后容器陷入重启循环：启动 → 加载模型 → 崩溃 → Docker 自动重启。",[14,201,202,203,206,207,210],{},"排查发现根因是 ",[26,204,205],{},"ONNX 推理缓存文件损坏","。e5-large 首次下载的 ",[93,208,209],{},".onnx"," 缓存文件不完整（下载被中断），但 fastembed 没有校验完整性，每次加载到损坏处就 SIGSEGV。",[14,212,213,214,217],{},"修复方式：",[26,215,216],{},"删除整个 ONNX 缓存目录","，让 fastembed 重新下载完整模型文件。首次下载约 5 分钟，后续加载约 30 秒。",[155,219,221],{"id":220},"_34-deepseek-tool_choice-冲突","3.4 DeepSeek tool_choice 冲突",[14,223,224,225,227,228,231],{},"另一个意外发现：切换模型后 ",[93,226,177],{}," 管道间歇失败，LLM 调用返回格式错误。根因是 DeepSeek v4-pro 的 ",[93,229,230],{},"tool_choice"," 参数与 Cognee 的内部 structured output 格式冲突。",[14,233,234,235,238],{},"修复：设置环境变量 ",[93,236,237],{},"LLM_INSTRUCTOR_MODE=json_mode","，强制 JSON 输出模式，绕过 tool_choice 机制。",[18,240,242],{"id":241},"四最终状态","四、最终状态",[163,244,247],{"className":245,"code":246,"language":168},[166],"模型：   intfloat\u002Fmultilingual-e5-large\n维度：   1024d\n内存：   2.1GiB \u002F 4GiB\n容器：   健康 (HTTP 200)\ncognify：PipelineRunCompleted ✅\n",[93,248,246],{"__ignoreMap":171},[14,250,251],{},"Cognee 现在正式运行在 1024 维多语言 embedding 上，中英文语义检索精度显著提升。",[18,253,255],{"id":254},"五踩坑清单","五、踩坑清单",[60,257,258,271],{},[63,259,260],{},[66,261,262,265,268],{},[69,263,264],{},"坑",[69,266,267],{},"现象",[69,269,270],{},"修复",[85,272,273,284,295,306],{},[66,274,275,278,281],{},[90,276,277],{},"LanceDB 维度锁死",[90,279,280],{},"无法直接改 collection 维度",[90,282,283],{},"全量删除 + 重建 + 重新 cognify",[66,285,286,289,292],{},[90,287,288],{},"e5-large 内存超预期",[90,290,291],{},"理论 800MB，实际 2.1GB",[90,293,294],{},"mem_limit 2g → 4g",[66,296,297,300,303],{},[90,298,299],{},"ONNX 缓存损坏",[90,301,302],{},"容器反复崩溃重启",[90,304,305],{},"删除缓存目录，重新下载",[66,307,308,311,314],{},[90,309,310],{},"DeepSeek tool_choice 冲突",[90,312,313],{},"cognify 管道格式错误",[90,315,316],{},[93,317,237],{},[11,319,320],{},[14,321,322],{},"一个 embedding 模型的切换，暴露了四个看似不相关的系统耦合点。每次基础设施变更，都是在给系统做全身 CT 扫描。",{"title":171,"searchDepth":324,"depth":324,"links":325},2,[326,327,328,335,336],{"id":20,"depth":324,"text":21},{"id":54,"depth":324,"text":55},{"id":152,"depth":324,"text":153,"children":329},[330,332,333,334],{"id":157,"depth":331,"text":158},3,{"id":181,"depth":331,"text":182},{"id":195,"depth":331,"text":196},{"id":220,"depth":331,"text":221},{"id":241,"depth":324,"text":242},{"id":254,"depth":324,"text":255},"2026-06-07","知识图谱系统的 embedding 层从 384 维切换到 1024 维 multilingual-e5-large，维度不兼容导致的全量重建、内存翻倍的容器调参、以及一个 ONNX 缓存引发的重启循环。",false,"md",{},true,"\u002Fblog\u002F2026-06-07-cognee-e5-large-embedding-switch",{"title":6,"description":338},"blog\u002F2026-06-07-cognee-e5-large-embedding-switch",[347,348,349,350,351,352],"cognee","embedding","e5-large","lancedb","fastembed","onnx","5ivZdhN5VsXUic2IqPKcZsutM017xXn9sUyqcG473oM",{"id":355,"title":356,"body":357,"date":2270,"description":2271,"draft":339,"extension":340,"meta":2272,"navigation":342,"path":2273,"seo":2274,"stem":2275,"tags":2276,"__hash__":2280},"blog\u002Fblog\u002F2026-06-06-cognee-memory-system-deployment.md","Cognee 部署纪实：2核4G 服务器上，LanceDB 嵌入模式 + e5-large + DeepSeek 跑通知识图谱",{"type":8,"value":358,"toc":2245},[359,364,368,375,389,392,395,451,454,457,461,465,468,474,486,529,538,544,547,550,554,557,560,563,577,584,588,591,598,617,622,674,677,681,684,689,692,709,728,733,760,763,769,858,863,939,942,945,950,998,1003,1009,1012,1026,1031,1110,1113,1117,1123,1130,1135,1206,1209,1213,1216,1238,1241,1247,1252,1265,1268,1271,1340,1343,1346,1393,1399,1402,1489,1493,1496,1500,1510,1516,1522,1575,1578,1582,1595,1598,1642,1645,1656,1662,1666,1669,1672,1675,1686,1693,1696,1700,1704,1708,1714,1720,1727,1732,1765,1768,1774,1779,1795,1799,1827,1830,1856,1872,1876,1935,1945,1960,1964,1967,1978,2003,2006,2010,2013,2016,2020,2023,2026,2031,2036,2041,2108,2112,2118,2123,2198,2202,2205,2229,2232,2241],[11,360,361],{},[14,362,363],{},"2 核 4G、无 GPU、预算有限。从 LanceDB 踩坑到 pgvector 试探，最终回到 LanceDB 嵌入模式——少一个容器、省 ~300MB 内存、cp 目录即备份。不是绕了一圈，是找到了最适合当前规模的最简方案。",[18,365,367],{"id":366},"一背景为什么需要-cognee","一、背景：为什么需要 Cognee",[14,369,370,371,374],{},"Hermes Agent 有一套内置的 ",[93,372,373],{},"memory"," 系统，但它有两个硬伤：",[34,376,377,383],{},[37,378,379,382],{},[26,380,381],{},"单会话隔离"," — 每个 session 的记忆独立存储，无法跨会话引用",[37,384,385,388],{},[26,386,387],{},"无结构化检索"," — 只能做简单的文本匹配，不能做语义搜索和图遍历",[14,390,391],{},"对于需要跨天记住用户偏好、项目决策、技术约束的 Agent 来说，这远远不够。",[14,393,394],{},"Cognee 是一个开源知识图谱系统，提供了三个核心能力：",[60,396,397,410],{},[63,398,399],{},[66,400,401,404,407],{},[69,402,403],{},"能力",[69,405,406],{},"说明",[69,408,409],{},"Hermes 对标",[85,411,412,425,438],{},[66,413,414,417,420],{},[90,415,416],{},"语义搜索",[90,418,419],{},"向量相似度匹配",[90,421,422],{},[93,423,424],{},"cognee_search",[66,426,427,430,433],{},[90,428,429],{},"图遍历",[90,431,432],{},"实体关系图谱查询",[90,434,435],{},[93,436,437],{},"cognee_recall",[66,439,440,443,446],{},[90,441,442],{},"持久化存储",[90,444,445],{},"PostgreSQL 元数据 + LanceDB 向量索引",[90,447,448],{},[93,449,450],{},"cognee_remember",[14,452,453],{},"目标很明确：把 Cognee 部署到服务器，通过 Hermes 插件对接，让 Agent 拥有一套跨会话、可查询的外部记忆。",[14,455,456],{},"下面是这趟旅程的真实记录——没有虚构，每条配置都经服务器验证。",[18,458,460],{"id":459},"二真实技术演进","二、真实技术演进",[155,462,464],{"id":463},"阶段一lancedb默认配置初探与回归","阶段一：LanceDB（默认配置，初探与回归）",[14,466,467],{},"Cognee 的默认向量库是 LanceDB——一个嵌入式文件级向量数据库。它不跑独立进程，以 Python 库的形式随 Cognee API 进程运行。",[14,469,470,473],{},[26,471,472],{},"首次尝试","：",[14,475,476,477,479,480,482,483,485],{},"几乎零配置，装好 Cognee API 就能跑。",[93,478,450],{}," 写入成功，",[93,481,177],{}," 知识图谱构建流水线也能触发，但 ",[93,484,437],{}," 返回空数组。",[163,487,491],{"className":488,"code":489,"language":490,"meta":171,"style":171},"language-json shiki shiki-themes github-light github-dark","{\n  \"results\": [],\n  \"pipelineRunStatus\": \"PipelineRunCompleted\"\n}\n","json",[93,492,493,502,511,523],{"__ignoreMap":171},[494,495,498],"span",{"class":496,"line":497},"line",1,[494,499,501],{"class":500},"sVt8B","{\n",[494,503,504,508],{"class":496,"line":324},[494,505,507],{"class":506},"sj4cs","  \"results\"",[494,509,510],{"class":500},": [],\n",[494,512,513,516,519],{"class":496,"line":331},[494,514,515],{"class":506},"  \"pipelineRunStatus\"",[494,517,518],{"class":500},": ",[494,520,522],{"class":521},"sZZnC","\"PipelineRunCompleted\"\n",[494,524,526],{"class":496,"line":525},4,[494,527,528],{"class":500},"}\n",[14,530,531,534,535,537],{},[26,532,533],{},"根因","：LanceDB 的数据文件存放在容器内部路径，Docker 重启后目录被重置，向量索引丢失。数据在 PostgreSQL 的关系表里躺着，但向量层是空的——",[93,536,177],{}," 虽然 report 成功，向量化那一步静默失败了。",[14,539,540,543],{},[26,541,542],{},"修复与认知转变","：将 LanceDB 数据目录挂载到持久化 Docker volume。LanceDB 的文件存储结构让持久化极其简单——cp 整个目录就能完成备份和迁移。",[14,545,546],{},"这次踩坑暴露的不是 LanceDB 的缺陷，而是 Docker 文件系统生命周期与嵌入式数据库的磨合问题。LanceDB 嵌入模式的核心优势——零独立进程、零网络开销、文件级持久化——在这次排障之后反而变得更清晰了。它不是在「开发环境能用、生产环境不行」，而是「只要 volume 挂对，生产环境反而比独立向量库更省心」。",[14,548,549],{},"这一阶段的经验为后续回归 LanceDB 埋下了伏笔。",[155,551,553],{"id":552},"阶段二pgvector-试探有想法但未完全切换","阶段二：pgvector 试探（有想法但未完全切换）",[14,555,556],{},"LanceDB 的持久化问题解决后，下一个探索方向是 pgvector——PostgreSQL 的向量扩展。一个数据库搞定关系和向量，听起来很完美。",[14,558,559],{},"配置了 pgvector 镜像，尝试让 Cognee 把向量和元数据都存到同一个 PostgreSQL 实例。",[14,561,562],{},"但实际使用时发现两个问题：",[34,564,565,571],{},[37,566,567,570],{},[26,568,569],{},"Cognee 对 pgvector 的支持不够成熟"," — 部分 API 路径对 pgvector 的向量维度处理有兼容性问题",[37,572,573,576],{},[26,574,575],{},"专用向量库的检索性能更优"," — ANN 近似最近邻搜索是向量数据库的核心优化点，pgvector 的 HNSW 索引在功能和性能上不及专用方案",[14,578,579,580,583],{},"最终 pgvector 容器留下来专门管关系元数据，向量存储继续用 LanceDB。",[26,581,582],{},"这不是「pgvector 不行」，而是「关系库和向量库各司其职」的混合架构","——最终选择 LanceDB 嵌入模式而非独立向量库，纯粹是因为当前数据规模不需要一个独立向量库进程。",[155,585,587],{"id":586},"阶段三双-compose-配置冲突差点全崩","阶段三：双 compose 配置冲突（差点全崩）",[14,589,590],{},"真正的危机不是选型，是配置管理。",[14,592,593,594,597],{},"在调试过程中积累了两套 ",[93,595,596],{},"docker-compose"," 配置——一套指向旧的镜像版本，一套指向新的。某次重启时两条 compose 同时生效，导致：",[599,600,601,604,607],"ul",{},[37,602,603],{},"容器名冲突，部分服务反复 crash-restart",[37,605,606],{},"Cognee API 间歇性不可用",[37,608,609,610,613,614],{},"日志里全是 ",[93,611,612],{},"port already allocated"," 和 ",[93,615,616],{},"container name conflict",[14,618,619,621],{},[26,620,270],{},"：合并为单一 compose 文件，清理所有废弃的服务定义。统一后的服务栈只有三个容器：",[60,623,624,637],{},[63,625,626],{},[66,627,628,631,634],{},[69,629,630],{},"服务",[69,632,633],{},"镜像",[69,635,636],{},"职责",[85,638,639,650,663],{},[66,640,641,644,647],{},[90,642,643],{},"cognee-api",[90,645,646],{},"自建 fastembed 镜像",[90,648,649],{},"知识图谱 API + LanceDB（嵌入）",[66,651,652,655,660],{},[90,653,654],{},"postgres",[90,656,657],{},[93,658,659],{},"postgres:16-alpine",[90,661,662],{},"关系元数据",[66,664,665,668,671],{},[90,666,667],{},"cognee-ui",[90,669,670],{},"Cognee 前端",[90,672,673],{},"可视化管理",[14,675,676],{},"LanceDB 嵌入在 Cognee API 进程内，不占独立容器——这是相比独立向量库方案最直接的收益：省一个容器进程，省 ~300MB 内存。",[155,678,680],{"id":679},"阶段四fastembed-lancedb首次跑通","阶段四：fastembed + LanceDB（首次跑通）",[14,682,683],{},"这是从零到一的突破。核心变化只有两个：",[14,685,686],{},[26,687,688],{},"① 自建 fastembed 镜像",[14,690,691],{},"不要外部 Embedding API。从 Cognee 基础镜像出发，在 venv 中安装 fastembed：",[163,693,697],{"className":694,"code":695,"language":696,"meta":171,"style":171},"language-dockerfile shiki shiki-themes github-light github-dark","FROM cognee\u002Fcognee:latest\nRUN \u002Fusr\u002Flocal\u002Fbin\u002Fpip install fastembed --target \u002Fopt\u002Fvenv\u002Flib\u002Fpython3.11\u002Fsite-packages\u002F\n","dockerfile",[93,698,699,704],{"__ignoreMap":171},[494,700,701],{"class":496,"line":497},[494,702,703],{},"FROM cognee\u002Fcognee:latest\n",[494,705,706],{"class":496,"line":324},[494,707,708],{},"RUN \u002Fusr\u002Flocal\u002Fbin\u002Fpip install fastembed --target \u002Fopt\u002Fvenv\u002Flib\u002Fpython3.11\u002Fsite-packages\u002F\n",[14,710,711,712,715,716,719,720,723,724,727],{},"关键细节：",[93,713,714],{},"pip"," 路径是 ",[93,717,718],{},"\u002Fusr\u002Flocal\u002Fbin\u002Fpip","（不是 ",[93,721,722],{},"\u002Fusr\u002Fbin\u002Fpip","），且必须 ",[93,725,726],{},"--target"," 到 venv 目录——否则 Cognee 运行时找不到 fastembed。",[14,729,730],{},[26,731,732],{},"② 切换到本地 Embedding",[163,734,738],{"className":735,"code":736,"language":737,"meta":171,"style":171},"language-env shiki shiki-themes github-light github-dark","# .env 关键配置\nEMBEDDING_PROVIDER=fastembed\nLLM_PROVIDER=deepseek\nLLM_MODEL=deepseek-v4-pro\n","env",[93,739,740,745,750,755],{"__ignoreMap":171},[494,741,742],{"class":496,"line":497},[494,743,744],{},"# .env 关键配置\n",[494,746,747],{"class":496,"line":324},[494,748,749],{},"EMBEDDING_PROVIDER=fastembed\n",[494,751,752],{"class":496,"line":331},[494,753,754],{},"LLM_PROVIDER=deepseek\n",[494,756,757],{"class":496,"line":525},[494,758,759],{},"LLM_MODEL=deepseek-v4-pro\n",[14,761,762],{},"Embedding 走本地 fastembed，LLM 走 DeepSeek。两者职责彻底分离。",[14,764,765,768],{},[26,766,767],{},"初始组件对照","（bge-small 时代）：",[60,770,771,784],{},[63,772,773],{},[66,774,775,778,781],{},[69,776,777],{},"组件",[69,779,780],{},"实际用的是什么",[69,782,783],{},"关键特征",[85,785,786,797,810,821,834,845],{},[66,787,788,791,794],{},[90,789,790],{},"Cognee 版本",[90,792,793],{},"v1.1.2",[90,795,796],{},"稳定版",[66,798,799,802,807],{},[90,800,801],{},"Embedding",[90,803,804,806],{},[26,805,351],{}," + BAAI\u002Fbge-small-en-v1.5",[90,808,809],{},"384 维，模型 130MB，本地推理",[66,811,812,815,818],{},[90,813,814],{},"LLM",[90,816,817],{},"deepseek-v4-pro（OpenAI 兼容）",[90,819,820],{},"仅做图构建和查询理解",[66,822,823,826,831],{},[90,824,825],{},"向量库",[90,827,828],{},[26,829,830],{},"LanceDB（嵌入模式）",[90,832,833],{},"随 Cognee 进程运行，文件级持久化",[66,835,836,839,842],{},[90,837,838],{},"关系库",[90,840,841],{},"PostgreSQL 16",[90,843,844],{},"元数据存储",[66,846,847,850,855],{},[90,848,849],{},"并行",[90,851,852],{},[93,853,854],{},"parallel=None",[90,856,857],{},"ONNX 底层已多线程，开并行反而慢 70%",[14,859,860,473],{},[26,861,862],{},"三段真实对比",[60,864,865,880],{},[63,866,867],{},[66,868,869,871,874,877],{},[69,870,74],{},[69,872,873],{},"LanceDB 初探",[69,875,876],{},"pgvector 试探",[69,878,879],{},"fastembed + LanceDB",[85,881,882,896,911,925],{},[66,883,884,887,890,893],{},[90,885,886],{},"向量持久化",[90,888,889],{},"❌ 未挂载 volume",[90,891,892],{},"✅ 但兼容性问题",[90,894,895],{},"✅ volume 持久化",[66,897,898,901,904,906],{},[90,899,900],{},"Embedding 成本",[90,902,903],{},"外部 API 收费",[90,905,903],{},[90,907,908],{},[26,909,910],{},"零 API 费用",[66,912,913,916,919,922],{},[90,914,915],{},"运维复杂度",[90,917,918],{},"低",[90,920,921],{},"中",[90,923,924],{},"低（零额外容器）",[66,926,927,930,933,936],{},[90,928,929],{},"全流程通过",[90,931,932],{},"❌",[90,934,935],{},"未完成切换",[90,937,938],{},"✅",[155,940,941],{"id":941},"向量库原理与选型",[14,943,944],{},"在进入模型升级之前，有必要理解 Cognee 的双层存储架构。这是整套系统最精巧的设计。",[14,946,947,473],{},[26,948,949],{},"存储分层",[60,951,952,968],{},[63,953,954],{},[66,955,956,959,962,965],{},[69,957,958],{},"存储层",[69,960,961],{},"技术",[69,963,964],{},"存储内容",[69,966,967],{},"检索方式",[85,969,970,985],{},[66,971,972,975,979,982],{},[90,973,974],{},"语义层",[90,976,977],{},[26,978,830],{},[90,980,981],{},"文本块的向量指纹 + chunk 元数据",[90,983,984],{},"ANN 近似最近邻",[66,986,987,990,992,995],{},[90,988,989],{},"结构层",[90,991,841],{},[90,993,994],{},"原始文本、实体节点、关系边",[90,996,997],{},"SQL + 图遍历",[14,999,1000,473],{},[26,1001,1002],{},"搜索流程",[163,1004,1007],{"className":1005,"code":1006,"language":168},[166],"用户查询 \"Hermes 配置在哪里\"\n  ↓\nfastembed 向量化 → [0.023, -0.147, ..., 0.091]  (1024d)\n  ↓\nLanceDB ANN 搜索 → top-5 相似向量\n  ↓\n返回 chunk_id: [c42, c17, c88, c03, c55]\n  ↓\nPostgreSQL: SELECT content FROM chunks WHERE id IN (...)\n  ↓\n组装上下文 + 关联实体 → 返回给 LLM\n",[93,1008,1006],{"__ignoreMap":171},[14,1010,1011],{},"LanceDB 只存向量指纹——不存原始文本。原始文本和知识图谱（实体-关系）都在 PostgreSQL 中。这种分离意味着：",[599,1013,1014,1017,1020,1023],{},[37,1015,1016],{},"向量检索走 LanceDB 的 ANN 索引（专为高维向量优化，毫秒级）",[37,1018,1019],{},"原文提取走 PostgreSQL 的 B-tree 索引（专为精确查询优化）",[37,1021,1022],{},"图遍历走 PostgreSQL 的递归 CTE（实体关系查询）",[37,1024,1025],{},"三者互不耦合，各用各的长处",[14,1027,1028,473],{},[26,1029,1030],{},"为什么最终是 LanceDB 嵌入模式",[60,1032,1033,1046],{},[63,1034,1035],{},[66,1036,1037,1040,1043],{},[69,1038,1039],{},"考量",[69,1041,1042],{},"LanceDB 嵌入",[69,1044,1045],{},"独立向量库",[85,1047,1048,1061,1074,1088,1099],{},[66,1049,1050,1053,1058],{},[90,1051,1052],{},"额外容器",[90,1054,1055],{},[26,1056,1057],{},"0",[90,1059,1060],{},"+1（~300MB 内存）",[66,1062,1063,1066,1071],{},[90,1064,1065],{},"网络开销",[90,1067,1068,1070],{},[26,1069,1057],{},"（进程内调用）",[90,1072,1073],{},"每次查询一次 HTTP 往返",[66,1075,1076,1079,1085],{},[90,1077,1078],{},"持久化",[90,1080,1081,1084],{},[93,1082,1083],{},"cp"," 目录即备份",[90,1086,1087],{},"需要管理独立数据卷",[66,1089,1090,1093,1096],{},[90,1091,1092],{},"数据规模",[90,1094,1095],{},"数十条记忆足够",[90,1097,1098],{},"百万级以上才显优势",[66,1100,1101,1104,1107],{},[90,1102,1103],{},"运维",[90,1105,1106],{},"零额外运维",[90,1108,1109],{},"需监控独立服务健康",[14,1111,1112],{},"在当前约束下——2C8G 服务器、数十条记忆条目、单人使用——LanceDB 嵌入模式是最简选择。少一个容器不只是省 ~300MB 内存，还少了一个需要监控、重启、排障的独立服务。等数据量真的长到百万级那天，再切独立向量库也不迟——而且到那时，LanceDB 的文件格式让迁移成本极低（导出 Lance 文件即可）。",[155,1114,1116],{"id":1115},"阶段五embedding-模型升级-从-bge-small-到-multilingual-e5-large","阶段五：Embedding 模型升级 — 从 bge-small 到 multilingual-e5-large",[14,1118,1119,1120],{},"bge-small-en-v1.5 跑稳之后，下一个问题自然浮现：",[26,1121,1122],{},"这个只有英文能力、384 维的小模型，够不够用？",[14,1124,1125,1126,1129],{},"实际场景中，Hermes 记忆里混杂着中英文、代码片段、配置文件路径，bge-small 对这些混合内容的语义理解明显吃力。再加上 384 维的向量精度天花板较低——",[26,1127,1128],{},"1024 维的 multilingual-e5-large 成了有据可查的升级目标","。",[1131,1132,1134],"h4",{"id":1133},"为什么选-e5-large","为什么选 e5-large",[60,1136,1137,1150],{},[63,1138,1139],{},[66,1140,1141,1144,1147],{},[69,1142,1143],{},"对比维度",[69,1145,1146],{},"bge-small-en-v1.5",[69,1148,1149],{},"multilingual-e5-large",[85,1151,1152,1166,1180,1193],{},[66,1153,1154,1157,1160],{},[90,1155,1156],{},"向量维度",[90,1158,1159],{},"384",[90,1161,1162,1165],{},[26,1163,1164],{},"1024","（2.7× 精度空间）",[66,1167,1168,1171,1174],{},[90,1169,1170],{},"模型大小",[90,1172,1173],{},"~130MB",[90,1175,1176,1179],{},[26,1177,1178],{},"~2.2GB","（16× 存储开销）",[66,1181,1182,1185,1188],{},[90,1183,1184],{},"语言支持",[90,1186,1187],{},"仅英文",[90,1189,1190,1192],{},[26,1191,80],{},"（中\u002F英\u002F代码混合）",[66,1194,1195,1198,1201],{},[90,1196,1197],{},"MTEB 平均分",[90,1199,1200],{},"~62",[90,1202,1203],{},[26,1204,1205],{},"~66",[14,1207,1208],{},"维度提升不只是在数字上多几位——1024 维的向量在高维空间中类间距离更大，对相似和近似的区分度更好。",[1131,1210,1212],{"id":1211},"代价内存翻倍-维度断裂","代价：内存翻倍 + 维度断裂",[14,1214,1215],{},"升级不是换个模型名字那么简单。e5-large 带来了两个直接冲击：",[34,1217,1218,1232],{},[37,1219,1220,1223,1224,1227,1228,1231],{},[26,1221,1222],{},"内存暴涨","：cognify 时的内存峰值从 ",[26,1225,1226],{},"~915MB"," 飙升至 ",[26,1229,1230],{},"~2.1GB","（实测 2.109GB），占 4G 总内存的 52%。bge-small 时代剩余 3GB+ 可分配给系统和 PostgreSQL，e5-large 时代只剩不到 2GB。",[37,1233,1234,1237],{},[26,1235,1236],{},"维度不兼容","：384 → 1024 不是平滑迁移。LanceDB 的向量表在创建时就固化了维度，384 维的表无法接受 1024 维的向量写入。",[1131,1239,1240],{"id":1240},"迁移步骤",[163,1242,1245],{"className":1243,"code":1244,"language":168,"meta":171},[166],"1. 停止 Cognee API（避免写入脏数据）\n2. 删除 LanceDB 向量数据目录（维度不兼容，必须全量重建）\n3. 修改 .env：\n   EMBEDDING_MODEL=intfloat\u002Fmultilingual-e5-large\n   DIMENSIONS=1024\n4. 重启 Cognee API\n5. 重新执行 cognify（e5-large 首次加载 ONNX 模型，冷启动较慢）\n6. 等待 PipelineRunCompleted\n",[93,1246,1244],{"__ignoreMap":171},[11,1248,1249],{},[14,1250,1251],{},"e5-large 首次启动时，fastembed 需要从 HuggingFace 下载 2.2GB 模型文件并转换为 ONNX 格式。缓存到本地后后续启动就快了。",[14,1253,1254,1255,1257,1258,1261,1262,1264],{},"此外，DeepSeek v4-pro 的 ",[93,1256,230],{}," 参数与 Cognee 内部的 structured output 格式存在冲突，需在 ",[93,1259,1260],{},".env"," 中追加 ",[93,1263,237],{}," 强制 JSON 输出模式。",[1131,1266,1267],{"id":1267},"性能实测",[14,1269,1270],{},"基于真实环境的三模型对比数据：",[60,1272,1273,1287],{},[63,1274,1275],{},[66,1276,1277,1280,1282,1284],{},[69,1278,1279],{},"指标",[69,1281,349],{},[69,1283,1146],{},[69,1285,1286],{},"bge-small-zh-v1.5",[85,1288,1289,1303,1315,1329],{},[66,1290,1291,1294,1297,1300],{},[90,1292,1293],{},"模型加载",[90,1295,1296],{},"6.2s",[90,1298,1299],{},"0.2s",[90,1301,1302],{},"-",[66,1304,1305,1308,1311,1313],{},[90,1306,1307],{},"50条批量推理",[90,1309,1310],{},"3.2s",[90,1312,1302],{},[90,1314,1302],{},[66,1316,1317,1320,1323,1326],{},[90,1318,1319],{},"内存峰值",[90,1321,1322],{},"2.1GB",[90,1324,1325],{},"261MB",[90,1327,1328],{},"826MB",[66,1330,1331,1333,1335,1338],{},[90,1332,1156],{},[90,1334,117],{},[90,1336,1337],{},"384d",[90,1339,98],{},[1131,1341,1342],{"id":1342},"升级后验证",[14,1344,1345],{},"cognify 流水线完成后，用三条不同语言的查询做 recall 测试：",[60,1347,1348,1360],{},[63,1349,1350],{},[66,1351,1352,1355,1358],{},[69,1353,1354],{},"查询",[69,1356,1357],{},"语言",[69,1359,83],{},[85,1361,1362,1373,1383],{},[66,1363,1364,1367,1370],{},[90,1365,1366],{},"\"Hermes Agent 的配置在哪里\"",[90,1368,1369],{},"中文",[90,1371,1372],{},"✅ 命中",[66,1374,1375,1378,1381],{},[90,1376,1377],{},"\"how to set up the cognee plugin\"",[90,1379,1380],{},"英文",[90,1382,1372],{},[66,1384,1385,1388,1391],{},[90,1386,1387],{},"\"COG_API_KEY vs COGNEE_API_KEY\"",[90,1389,1390],{},"混合",[90,1392,1372],{},[14,1394,1395,1398],{},[26,1396,1397],{},"3\u002F3 全部命中，无漏召回","。服务运行 7+ 小时无错误，容器持续健康。",[1131,1400,1401],{"id":1401},"升级后组件对照",[60,1403,1404,1416],{},[63,1405,1406],{},[66,1407,1408,1410,1413],{},[69,1409,777],{},[69,1411,1412],{},"升级后实际配置",[69,1414,1415],{},"变化",[85,1417,1418,1430,1441,1453,1465,1478],{},[66,1419,1420,1423,1427],{},[90,1421,1422],{},"Embedding 模型",[90,1424,1425],{},[26,1426,114],{},[90,1428,1429],{},"从 bge-small-en-v1.5 切换",[66,1431,1432,1434,1438],{},[90,1433,1156],{},[90,1435,1436],{},[26,1437,1164],{},[90,1439,1440],{},"从 384 升级",[66,1442,1443,1446,1450],{},[90,1444,1445],{},"内存峰值（cognify）",[90,1447,1448],{},[26,1449,1230],{},[90,1451,1452],{},"从 915MB 上涨 2.3×",[66,1454,1455,1458,1462],{},[90,1456,1457],{},"模型文件大小",[90,1459,1460],{},[26,1461,1178],{},[90,1463,1464],{},"从 130MB 上涨 16×",[66,1466,1467,1470,1475],{},[90,1468,1469],{},"多语言支持",[90,1471,1472],{},[26,1473,1474],{},"中\u002F英\u002F混合",[90,1476,1477],{},"从仅英文扩展",[66,1479,1480,1483,1486],{},[90,1481,1482],{},"LanceDB",[90,1484,1485],{},"删除旧向量数据，重建 1024 维表",[90,1487,1488],{},"维度断裂，全量重做",[18,1490,1492],{"id":1491},"三资源受限下的优化","三、资源受限下的优化",[14,1494,1495],{},"服务器只有 2 核 4G，无 GPU。每一兆内存都要计较。e5-large 的升级更是把资源压力推到了顶点。",[155,1497,1499],{"id":1498},"_31-fastembed零成本-embedding模型任选","3.1 fastembed：零成本 Embedding，模型任选",[14,1501,1502,1503,1506,1507,1509],{},"这是最重要的决策。之前所有方案——不管是 OpenAI ",[93,1504,1505],{},"text-embedding-3-small"," 还是 DeepSeek 的 Embedding API——都有调用费用。虽然单价低，但 ",[93,1508,177],{}," 流水线会批量向量化，用量上去成本就起来了。",[14,1511,1512,1513,1129],{},"fastembed 是 ONNX Runtime 绑定的本地推理引擎。无论是 130MB 的 bge-small 还是 2.2GB 的 e5-large，成本结构完全一样——",[26,1514,1515],{},"零 API 费用，只有服务器的电和内存",[14,1517,1518,1521],{},[26,1519,1520],{},"成本对比","（按日均 1000 次向量化估算）：",[60,1523,1524,1537],{},[63,1525,1526],{},[66,1527,1528,1531,1534],{},[69,1529,1530],{},"方案",[69,1532,1533],{},"日均成本",[69,1535,1536],{},"月成本",[85,1538,1539,1550,1561],{},[66,1540,1541,1544,1547],{},[90,1542,1543],{},"OpenAI text-embedding-3-small",[90,1545,1546],{},"~$0.02",[90,1548,1549],{},"~$0.60",[66,1551,1552,1555,1558],{},[90,1553,1554],{},"DeepSeek Embedding API",[90,1556,1557],{},"~$0.01",[90,1559,1560],{},"~$0.30",[66,1562,1563,1566,1571],{},[90,1564,1565],{},"fastembed（bge-small 或 e5-large）",[90,1567,1568],{},[26,1569,1570],{},"$0",[90,1572,1573],{},[26,1574,1570],{},[14,1576,1577],{},"「零外部依赖」的价值远不止省钱——没有 API 限流、没有网络抖动、不会因为欠费突然停服。e5-large 的 2.2GB 模型虽然吃内存，但选择权在自己手里：要么用更大的内存换更好的精度，要么退回 bge-small 省资源。不需要跟第三方讨价还价。",[155,1579,1581],{"id":1580},"_32-parallel-陷阱开并行反而慢-70","3.2 parallel 陷阱：开并行反而慢 70%",[14,1583,1584,1585,1587,1588,1591,1592,1129],{},"Cognee 的 ",[93,1586,177],{}," 支持 ",[93,1589,1590],{},"parallel"," 参数控制并发任务数。直觉上 2 核应该用 ",[93,1593,1594],{},"parallel=2",[14,1596,1597],{},"实测结果（基于 bge-small）：",[60,1599,1600,1612],{},[63,1601,1602],{},[66,1603,1604,1607,1610],{},[69,1605,1606],{},"parallel 设置",[69,1608,1609],{},"cognify 耗时",[69,1611,1319],{},[85,1613,1614,1628],{},[66,1615,1616,1622,1625],{},[90,1617,1618,1621],{},[93,1619,1620],{},"None","（默认）",[90,1623,1624],{},"基准 100%",[90,1626,1627],{},"915MB",[66,1629,1630,1634,1639],{},[90,1631,1632],{},[93,1633,1594],{},[90,1635,1636],{},[26,1637,1638],{},"170%",[90,1640,1641],{},"OOM 边缘",[14,1643,1644],{},"根因：fastembed 底层的 ONNX Runtime 已经做了多线程优化。外层再开 Python 多进程（parallel=2），不仅线程竞争 CPU，每个子进程还会独立加载一份 ONNX 模型。在 e5-large 的 2.2GB 模型体积下，parallel=2 意味着 4.4GB+ 的纯模型内存——直接超过 4G 物理上限。",[11,1646,1647],{},[14,1648,1649,1650,1652,1653,1655],{},"升级到 e5-large 后，",[93,1651,1590],{}," 必须保持 ",[93,1654,1620],{},"。2.2GB 模型 × 2 进程 = OOM 是确定性结果。",[14,1657,1658,1661],{},[26,1659,1660],{},"教训","：在资源受限环境下，默认值往往就是最优值。不要为了「看起来更合理」而调参数。",[155,1663,1665],{"id":1664},"_33-内存管理bge-small-和-e5-large-两套策略","3.3 内存管理：bge-small 和 e5-large 两套策略",[14,1667,1668],{},"bge-small 时代，Cognee 容器内存占用约 915MB，剩余 3GB+ 分给 PostgreSQL 和系统。",[14,1670,1671],{},"e5-large 时代，这一数字翻到 ~2.1GB。4GB 总内存的 52% 被一个容器吃掉，容错空间急剧缩小。",[14,1673,1674],{},"bge-small 时期的内存管理已经打好基础：",[599,1676,1677,1680,1683],{},[37,1678,1679],{},"清理废弃容器和未使用镜像（回收 ~1.8GB 磁盘）",[37,1681,1682],{},"为每个服务设置内存上限，防止单个容器无限制扩张",[37,1684,1685],{},"确保留有足够的系统余量",[14,1687,1688,1689,1692],{},"e5-large 的升级让这些限制变得不再是「锦上添花」，而是",[26,1690,1691],{},"不设就必挂","的底线。2.1GB 的 cognify 峰值意味着如果系统或 PostgreSQL 同时有内存需求，OOM Killer 随时可能介入。",[14,1694,1695],{},"LanceDB 嵌入模式在这里还有一个意外的好处：它不占独立内存配额。如果用的是独立向量库容器，还需要额外分配 ~300MB，那 4GB 的总内存根本不够分。",[18,1697,1699],{"id":1698},"四踩坑记录","四、踩坑记录",[155,1701,1703],{"id":1702},"坑1hermes-端-unknown-tool-之谜最具技术深度","坑1：Hermes 端 \"Unknown tool\" 之谜（最具技术深度）",[14,1705,1706,473],{},[26,1707,267],{},[14,1709,1710,1711,473],{},"Hermes 的三个 cognee 工具全部返回 ",[93,1712,1713],{},"Unknown tool",[163,1715,1718],{"className":1716,"code":1717,"language":168},[166],"cognee_recall → \"Unknown tool\"\ncognee_remember → \"Unknown tool\"\ncognee_search → \"Unknown tool\"\n",[93,1719,1717],{"__ignoreMap":171},[14,1721,1722,1723,1726],{},"但 Cognee 服务端一切正常，API 健康检查通过，",[93,1724,1725],{},"curl"," 直接调用也都返回正确结果。",[14,1728,1729,473],{},[26,1730,1731],{},"排查过程",[163,1733,1737],{"className":1734,"code":1735,"language":1736,"meta":171,"style":171},"language-python shiki shiki-themes github-light github-dark","# Cognee 插件的原始代码（问题所在）\ndef get_tool_schemas(self) -> List[Dict[str, Any]]:\n    if not self._ready:   # ← 这里\n        return []         # ← 返回空列表\n    return [RECALL_SCHEMA, REMEMBER_SCHEMA, SEARCH_SCHEMA]\n","python",[93,1738,1739,1744,1749,1754,1759],{"__ignoreMap":171},[494,1740,1741],{"class":496,"line":497},[494,1742,1743],{},"# Cognee 插件的原始代码（问题所在）\n",[494,1745,1746],{"class":496,"line":324},[494,1747,1748],{},"def get_tool_schemas(self) -> List[Dict[str, Any]]:\n",[494,1750,1751],{"class":496,"line":331},[494,1752,1753],{},"    if not self._ready:   # ← 这里\n",[494,1755,1756],{"class":496,"line":525},[494,1757,1758],{},"        return []         # ← 返回空列表\n",[494,1760,1762],{"class":496,"line":1761},5,[494,1763,1764],{},"    return [RECALL_SCHEMA, REMEMBER_SCHEMA, SEARCH_SCHEMA]\n",[14,1766,1767],{},"链式依赖链：",[163,1769,1772],{"className":1770,"code":1771,"language":168},[166],"get_tool_schemas() 依赖 _ready\n→ _ready 在 initialize() 中设置\n→ initialize() 依赖 COG_API_KEY 环境变量\n→ 如果 COG_API_KEY 为空 → _ready = False → get_tool_schemas() 返回 []\n→ has_tool() 通过 tool_id 查找失败 → 所有调用返回 \"Unknown tool\"\n",[93,1773,1771],{"__ignoreMap":171},[14,1775,1776,1778],{},[26,1777,533],{},"：两个问题叠加——",[34,1780,1781,1789],{},[37,1782,1783,1786,1787],{},[93,1784,1785],{},"COG_API_KEY"," 未被正确设置到 manager profile 的 ",[93,1788,1260],{},[37,1790,1791,1794],{},[93,1792,1793],{},"get_tool_schemas()"," 不应该因为配置缺失就返回空——应该始终返回 schema 列表，让上游决定可用性",[14,1796,1797,473],{},[26,1798,270],{},[163,1800,1802],{"className":1734,"code":1801,"language":1736,"meta":171,"style":171},"# 修复后的代码\ndef get_tool_schemas(self) -> List[Dict[str, Any]]:\n    # 始终返回工具 schema，不依赖 _ready 状态\n    # 可用性由 is_available() 控制\n    return [RECALL_SCHEMA, REMEMBER_SCHEMA, SEARCH_SCHEMA]\n",[93,1803,1804,1809,1813,1818,1823],{"__ignoreMap":171},[494,1805,1806],{"class":496,"line":497},[494,1807,1808],{},"# 修复后的代码\n",[494,1810,1811],{"class":496,"line":324},[494,1812,1748],{},[494,1814,1815],{"class":496,"line":331},[494,1816,1817],{},"    # 始终返回工具 schema，不依赖 _ready 状态\n",[494,1819,1820],{"class":496,"line":525},[494,1821,1822],{},"    # 可用性由 is_available() 控制\n",[494,1824,1825],{"class":496,"line":1761},[494,1826,1764],{},[14,1828,1829],{},"同时补充了：",[34,1831,1832,1838,1847],{},[37,1833,1834,1837],{},[26,1835,1836],{},"多副本同步"," — 11 个 profile 各自有一份 Cognee 插件副本，必须全部更新",[37,1839,1840,1846],{},[26,1841,1842,1843],{},"清 ",[26,1844,1845],{},"pycache"," — Python 缓存不刷新会导致旧代码继续生效",[37,1848,1849,1852,1853,1855],{},[26,1850,1851],{},"COG_API_KEY 注入"," — 在 manager ",[93,1854,1260],{}," 中添加密钥",[14,1857,1858,1860,1861,1863,1864,1867,1868,1871],{},[26,1859,1660],{},"：插件的 ",[93,1862,1793],{}," 是注册入口，它如果返回空，下游完全感知不到这个工具的存在。",[26,1865,1866],{},"工具造册和工具可用性应该是两个独立的概念","——前者始终返回，后者通过 ",[93,1869,1870],{},"is_available()"," 判断。",[155,1873,1875],{"id":1874},"坑2cog_api_key-vs-cognee_api_key-命名不一致","坑2：COG_API_KEY vs COGNEE_API_KEY 命名不一致",[60,1877,1878,1891],{},[63,1879,1880],{},[66,1881,1882,1885,1888],{},[69,1883,1884],{},"位置",[69,1886,1887],{},"使用的变量名",[69,1889,1890],{},"Header 名",[85,1892,1893,1908,1921],{},[66,1894,1895,1898,1903],{},[90,1896,1897],{},"Cognee API",[90,1899,1900],{},[93,1901,1902],{},"COGNEE_API_KEY",[90,1904,1905],{},[93,1906,1907],{},"X-API-Key",[66,1909,1910,1913,1917],{},[90,1911,1912],{},"Hermes 插件",[90,1914,1915],{},[93,1916,1785],{},[90,1918,1919],{},[93,1920,1907],{},[66,1922,1923,1926,1930],{},[90,1924,1925],{},"Cognee 官方文档",[90,1927,1928],{},[93,1929,1902],{},[90,1931,1932],{},[93,1933,1934],{},"Authorization: Bearer ***",[14,1936,1937,1938,1940,1941,1944],{},"一个 API Key，三个名字。两种不同的认证方式（",[93,1939,1907],{}," vs ",[93,1942,1943],{},"Bearer","）。",[14,1946,1947,1950,1951,1953,1954,1957,1958,1129],{},[26,1948,1949],{},"最终方案","：统一在 Hermes 端使用 ",[93,1952,1785],{},"，Cognee 服务端配置 ",[93,1955,1956],{},"AUTH_METHOD=api_key","，Header 用 ",[93,1959,1907],{},[155,1961,1963],{"id":1962},"坑3approvalsmode-死锁","坑3：approvals.mode 死锁",[14,1965,1966],{},"修复完代码后重启 Hermes Gateway，发现新会话仍然报 Unknown tool。",[14,1968,1969,1970,1973,1974,1977],{},"根因是 Hermes 的 ",[93,1971,1972],{},"approvals.mode"," 默认处于 ",[93,1975,1976],{},"block"," 模式，Gateway 在实际执行工具调用时会因为审批拦截而中断。必须先执行：",[163,1979,1983],{"className":1980,"code":1981,"language":1982,"meta":171,"style":171},"language-bash shiki shiki-themes github-light github-dark","hermes config set approvals.mode auto\n","bash",[93,1984,1985],{"__ignoreMap":171},[494,1986,1987,1991,1994,1997,2000],{"class":496,"line":497},[494,1988,1990],{"class":1989},"sScJk","hermes",[494,1992,1993],{"class":521}," config",[494,1995,1996],{"class":521}," set",[494,1998,1999],{"class":521}," approvals.mode",[494,2001,2002],{"class":521}," auto\n",[14,2004,2005],{},"然后重启 Gateway 才能生效。这个配置变更不会因为重载而自动应用。",[155,2007,2009],{"id":2008},"坑4ssh-maxstartups-限制","坑4：SSH MaxStartups 限制",[14,2011,2012],{},"多 Agent 并行工作时会同时 SSH 到服务器。默认 SSH 配置在并发峰值下会拒绝连接。",[14,2014,2015],{},"表现为某几个 Worker 的 SSH 操作随机失败，错误信息不明。调高限制后恢复正常。",[155,2017,2019],{"id":2018},"坑5onnx-缓存损坏导致重启循环e5-large-升级时新增","坑5：ONNX 缓存损坏导致重启循环（e5-large 升级时新增）",[14,2021,2022],{},"升级到 e5-large 后，某次重启 Cognee 容器时陷入循环——容器启动后立即 crash，Docker 自动重启，再 crash，再重启。",[14,2024,2025],{},"日志显示 fastembed 在加载模型时 ONNX Runtime 报了 session 初始化错误。排查发现是之前 ONNX 模型缓存文件在非正常关机时损坏，导致每次启动都尝试用损坏的缓存初始化。",[14,2027,2028,2030],{},[26,2029,270],{},"：删除 ONNX 缓存目录（通常在 fastembed 的默认缓存路径下），让 fastembed 重建 ONNX 模型。2.2GB 的模型重新下载和转换需要几分钟，后续启动就正常了。",[14,2032,2033,2035],{},[26,2034,1660],{},"：大模型的 ONNX 缓存是持久化风险点。非正常关机后建议先清缓存再启动，而不是让容器陷入 crash-restart 循环。",[14,2037,2038,473],{},[26,2039,2040],{},"五坑难度排序",[60,2042,2043,2055],{},[63,2044,2045],{},[66,2046,2047,2049,2052],{},[69,2048,264],{},[69,2050,2051],{},"难度",[69,2053,2054],{},"关键教训",[85,2056,2057,2067,2077,2086,2097],{},[66,2058,2059,2061,2064],{},[90,2060,1713],{},[90,2062,2063],{},"⭐⭐⭐⭐⭐",[90,2065,2066],{},"插件注册与可用性应该解耦",[66,2068,2069,2071,2074],{},[90,2070,299],{},[90,2072,2073],{},"⭐⭐⭐",[90,2075,2076],{},"大模型缓存 = 持久化风险点",[66,2078,2079,2081,2083],{},[90,2080,1972],{},[90,2082,2073],{},[90,2084,2085],{},"配置变更 ≠ 运行时生效",[66,2087,2088,2091,2094],{},[90,2089,2090],{},"API Key 命名",[90,2092,2093],{},"⭐⭐",[90,2095,2096],{},"文档先行，统一命名规范",[66,2098,2099,2102,2105],{},[90,2100,2101],{},"SSH MaxStartups",[90,2103,2104],{},"⭐",[90,2106,2107],{},"多 Agent 并发 = 多连接",[18,2109,2111],{"id":2110},"五最终架构","五、最终架构",[163,2113,2116],{"className":2114,"code":2115,"language":168},[166],"┌─────────────────────────────────────────────┐\n│                 Hermes Agent                 │\n│  ┌─────────────────────────────────────┐    │\n│  │     CogneeMemoryProvider (Plugin)    │    │\n│  │  ┌─────────┬──────────┬──────────┐  │    │\n│  │  │ recall  │ remember │  search  │  │    │\n│  │  └────┬────┴────┬─────┴────┬─────┘  │    │\n│  └───────┼─────────┼──────────┼────────┘    │\n│          │ HTTP    │          │              │\n└──────────┼─────────┼──────────┼──────────────┘\n           │         │          │\n    ┌──────┴─────────┴──────────┴──────┐\n    │         Cognee API v1.1.2         │\n    │  ┌─────────────────────────────┐  │\n    │  │   \u002Fapi\u002Fv1\u002Fsearch            │  │\n    │  │   \u002Fapi\u002Fv1\u002Frecall            │  │\n    │  │   \u002Fapi\u002Fv1\u002Fremember\u002Fentry    │  │\n    │  │   \u002Fapi\u002Fv1\u002Fcognify           │  │\n    │  └──────────┬──────────────────┘  │\n    │             │                     │\n    │  ┌──────────┴──────────────────┐  │\n    │  │   PostgreSQL 16             │  │\n    │  │   • 关系元数据 + 原始文本     │  │\n    │  │   • 知识图谱（实体-关系）     │  │\n    │  └─────────────────────────────┘  │\n    │  ┌──────────────────────────────┐ │\n    │  │   LanceDB（嵌入模式）          │ │\n    │  │   • 向量索引（1024 维）        │ │\n    │  │   • 进程内 ANN 检索           │ │\n    │  └──────────────────────────────┘ │\n    │             ↑                       │\n    │     fastembed (本地 ONNX)            │\n    │     multilingual-e5-large           │\n    └─────────────────────────────────────┘\n",[93,2117,2115],{"__ignoreMap":171},[14,2119,2120,473],{},[26,2121,2122],{},"当前组件清单",[60,2124,2125,2135],{},[63,2126,2127],{},[66,2128,2129,2131,2133],{},[69,2130,777],{},[69,2132,961],{},[69,2134,636],{},[85,2136,2137,2148,2159,2169,2178,2187],{},[66,2138,2139,2142,2145],{},[90,2140,2141],{},"知识图谱",[90,2143,2144],{},"Cognee v1.1.2",[90,2146,2147],{},"结构化\u002F非结构化混合存储",[66,2149,2150,2152,2156],{},[90,2151,825],{},[90,2153,2154],{},[26,2155,830],{},[90,2157,2158],{},"语义相似度检索（1024 维），无独立容器",[66,2160,2161,2163,2166],{},[90,2162,801],{},[90,2164,2165],{},"fastembed + multilingual-e5-large",[90,2167,2168],{},"文本→向量，本地推理，零 API 成本，多语言",[66,2170,2171,2173,2175],{},[90,2172,814],{},[90,2174,817],{},[90,2176,2177],{},"图构建 + 查询理解",[66,2179,2180,2182,2184],{},[90,2181,838],{},[90,2183,841],{},[90,2185,2186],{},"实体\u002F关系元数据 + 原始文本",[66,2188,2189,2192,2195],{},[90,2190,2191],{},"插件",[90,2193,2194],{},"Hermes CogneeMemoryProvider",[90,2196,2197],{},"3 个工具注册，11 个 profile 全覆盖",[18,2199,2201],{"id":2200},"六总结","六、总结",[14,2203,2204],{},"这次部署教会了我三件事——e5-large 的升级又强化了每条：",[34,2206,2207,2213,2221],{},[37,2208,2209,2212],{},[26,2210,2211],{},"在资源受限的环境里，选型的第一原则是「少即是多」","。LanceDB 嵌入模式比独立向量库少一个容器、省 ~300MB 内存、不需要额外监控。当数据量只有几十条时，嵌入式方案不只是在功能上「够用」——在运维成本上它是显著更优的。fastembed 同理：bge-small 和 e5-large 的成本曲线完全重合（都是零 API 费用），但内存成本天差地别——915MB vs 2.1GB。选择权在你手里：要精度就吃更多内存，要省内存就降模型。没有「既要又要」。",[37,2214,2215,1129,2218,2220],{},[26,2216,2217],{},"并行不是免费的",[93,2219,1594],{}," 在 2 核机器上反而慢 70%。底层 ONNX 已经做了多线程优化，外层再加多进程只会争抢资源。e5-large 的 2.2GB 模型体积更是让 parallel > 1 直接等于 OOM。在约束条件下，「什么都不做」常常比「做点什么」更好。",[37,2222,2223,1129,2226,2228],{},[26,2224,2225],{},"\"Unknown tool\" 的本质是接口设计问题",[93,2227,1793],{}," 不应该因为配置缺失就返回空数组——工具注册和工具可用性必须解耦。这条原则适用于所有插件系统，不仅仅适用于 Hermes。ONNX 缓存损坏的教训则是另一面：大模型的持久化状态是隐性风险，不要假定缓存总是好的。",[14,2230,2231],{},"现在的 Cognee 服务稳定运行：fastembed + multilingual-e5-large (1024d) 本地向量化、多语言语义理解，LanceDB 嵌入做向量索引（零额外容器），DeepSeek 做推理，PostgreSQL 管元数据。三个容器，干净利落。在那台 2 核 4G 的小服务器上，每一步都是精确算计后的取舍。",[11,2233,2234],{},[14,2235,2236,2237,2240],{},"下一步：写一个 ",[93,2238,2239],{},"cognee_sync.py"," 定时任务，把 Hermes 的 built-in memory 批量同步到 Cognee。记忆孤岛问题，才算真正解决。",[2242,2243,2244],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":171,"searchDepth":324,"depth":324,"links":2246},[2247,2248,2256,2261,2268,2269],{"id":366,"depth":324,"text":367},{"id":459,"depth":324,"text":460,"children":2249},[2250,2251,2252,2253,2254,2255],{"id":463,"depth":331,"text":464},{"id":552,"depth":331,"text":553},{"id":586,"depth":331,"text":587},{"id":679,"depth":331,"text":680},{"id":941,"depth":331,"text":941},{"id":1115,"depth":331,"text":1116},{"id":1491,"depth":324,"text":1492,"children":2257},[2258,2259,2260],{"id":1498,"depth":331,"text":1499},{"id":1580,"depth":331,"text":1581},{"id":1664,"depth":331,"text":1665},{"id":1698,"depth":324,"text":1699,"children":2262},[2263,2264,2265,2266,2267],{"id":1702,"depth":331,"text":1703},{"id":1874,"depth":331,"text":1875},{"id":1962,"depth":331,"text":1963},{"id":2008,"depth":331,"text":2009},{"id":2018,"depth":331,"text":2019},{"id":2110,"depth":324,"text":2111},{"id":2200,"depth":324,"text":2201},"2026-06-06","给 Hermes Agent 接上 Cognee 知识图谱做跨会话记忆。LanceDB 嵌入模式省一个容器 ~300MB，fastembed 本地免费 Embedding + multilingual-e5-large（1024d）多语言语义检索。含 cognify 流水线、parallel 陷阱、Unknown tool 之谜、ONNX 缓存损坏等真实踩坑记录。",{},"\u002Fblog\u002F2026-06-06-cognee-memory-system-deployment",{"title":356,"description":2271},"blog\u002F2026-06-06-cognee-memory-system-deployment",[347,2277,351,350,2278,1990,2279,348,349],"knowledge-graph","deepseek","docker","X1CVArxYCIIlxz_KFAiRHHsUz3r_Cg9CqqScNsxMldg",{"id":2282,"title":2283,"body":2284,"date":3296,"description":3297,"draft":339,"extension":340,"meta":3298,"navigation":342,"path":3300,"seo":3301,"stem":3302,"tags":3303,"__hash__":3309},"blog\u002Fblog\u002Fagent-ops-butterfly-effect.md","当 AI Ops 把自己网站搞挂了——一个容器重启的蝴蝶效应",{"type":8,"value":2285,"toc":3269},[2286,2290,2295,2298,2301,2304,2308,2311,2361,2364,2367,2370,2373,2375,2379,2383,2386,2389,2409,2412,2416,2419,2425,2428,2431,2435,2438,2441,2447,2450,2579,2582,2588,2591,2593,2597,2600,2652,2658,2661,2665,2722,2725,2766,2769,2773,2776,2848,2851,2854,2857,2874,2881,2883,2887,2891,2894,2954,2957,2960,2971,2974,3004,3010,3016,3022,3024,3028,3031,3094,3097,3100,3194,3197,3199,3203,3207,3214,3218,3221,3227,3231,3234,3239,3243,3246,3248,3251,3254,3257,3260,3263,3266],[2287,2288,2283],"h1",{"id":2289},"当-ai-ops-把自己网站搞挂了一个容器重启的蝴蝶效应",[11,2291,2292],{},[14,2293,2294],{},"一切都很顺利，直到 AI 运维 Agent 决定重启一个容器。然后整个网站挂了。然后它修不好了。然后它又把自己 SSH 踢出了服务器。",[14,2296,2297],{},"这是我用 Hermes Multi-Agent 系统管理个人网站部署的第六天。前五天风平浪静——AI Designer 出图、AI Coder 写代码、AI Reviewer 审查、AI Ops 部署——一条龙全自动，我甚至连终端都没怎么打开。",[14,2299,2300],{},"第六天，事情开始变得有趣。",[2302,2303],"hr",{},[18,2305,2307],{"id":2306},"一背景v4v5-的切换","一、背景：v4→v5 的切换",[14,2309,2310],{},"网站首页经历了四个设计版本：",[60,2312,2313,2326],{},[63,2314,2315],{},[66,2316,2317,2320,2323],{},[69,2318,2319],{},"版本",[69,2321,2322],{},"设计师",[69,2324,2325],{},"风格",[85,2327,2328,2339,2350],{},[66,2329,2330,2333,2336],{},[90,2331,2332],{},"v1-v3",[90,2334,2335],{},"AI Designer（迭代）",[90,2337,2338],{},"赛博朋克风，Three.js 3D 背景，粒子效果，光晕文字",[66,2340,2341,2344,2347],{},[90,2342,2343],{},"v4",[90,2345,2346],{},"AI + 用户反馈",[90,2348,2349],{},"精简版赛博朋克，IcosahedronGeometry 球体，900 粒子",[66,2351,2352,2355,2358],{},[90,2353,2354],{},"v5",[90,2356,2357],{},"AI Designer 自主优化",[90,2359,2360],{},"极度精简，移除了大量 3D 效果",[14,2362,2363],{},"v4 是用户确认过的版本——Three.js 的 IcosahedronGeometry 球体 + 双环面 + 900 个粒子的 Canvas 神经网络背景，效果相当惊艳。",[14,2365,2366],{},"但 AI Designer 觉得\"还能更好\"。于是它在没有用户明确指令的情况下，基于自己对这个风格的\"理解\"，产出了一个 v5：移除了环面、减少了粒子、简化了着色器。它认为这样更\"克制\"。",[14,2368,2369],{},"用户看到 v5 后说了一句话：「这个不好看，回退到 v4。」",[14,2371,2372],{},"于是，整条流水线启动了。",[2302,2374],{},[18,2376,2378],{"id":2377},"二故障始末","二、故障始末",[155,2380,2382],{"id":2381},"第一步designer-分析差异","第一步：Designer 分析差异",[14,2384,2385],{},"AI Designer 接到任务：对比 v5 和 v4，产出差异清单。",[14,2387,2388],{},"它产出了一个 26 项差异分析文档，分三个优先级：",[599,2390,2391,2397,2403],{},[37,2392,2393,2396],{},[26,2394,2395],{},"P0（7 项）","：球体几何体错误、颜色不对、环面缺失、拖拽旋转反向",[37,2398,2399,2402],{},[26,2400,2401],{},"P1（13 项）","：FOV 偏移、粒子数量不足、导航样式差异、按钮风格不匹配",[37,2404,2405,2408],{},[26,2406,2407],{},"P2（6 项）","：微妙的动画时间和缓动曲线差异",[14,2410,2411],{},"这本身没问题——Designer 做得很漂亮。",[155,2413,2415],{"id":2414},"第二步coder-修改代码","第二步：Coder 修改代码",[14,2417,2418],{},"AI Coder 拿到差异清单，开始修改 Nuxt 3 项目的三个 Vue 文件：",[163,2420,2423],{"className":2421,"code":2422,"language":168},[166],"3DBackground.vue  → 球体几何体 + 环面 + 着色器\nindex.vue          → 粒子系统 + 相机配置\napp.vue            → 全局样式 + 导航栏\n",[93,2424,2422],{"__ignoreMap":171},[14,2426,2427],{},"Coder 完成了修改，Reviewer 审查通过（APPROVE），Git 提交成功，Tester 测试通过。",[14,2429,2430],{},"一切正常。",[155,2432,2434],{"id":2433},"第三步ops-部署然后炸了","第三步：Ops 部署——然后炸了",[14,2436,2437],{},"AI Ops Worker 的任务是：将修改后的 Nuxt 项目通过 Docker 部署到腾讯云服务器（43.135.47.130）。",[14,2439,2440],{},"它的标准流程是：",[163,2442,2445],{"className":2443,"code":2444,"language":168},[166],"本地 Docker build → 压缩镜像 → SFTP 上传 → 服务器 docker load → docker compose up\n",[93,2446,2444],{"__ignoreMap":171},[14,2448,2449],{},"但这次不一样。因为它要\"安全部署\"——先在测试端口启动验证，再切换。",[163,2451,2453],{"className":1980,"code":2452,"language":1982,"meta":171,"style":171},"# 构建新镜像\ndocker build -t website:v4-fix .\n\n# 在 3001 端口测试\ndocker run -d --name web-test -p 3001:3000 website:v4-fix\n\n# 验证...\ncurl http:\u002F\u002F127.0.0.1:3001\u002F  # 返回 200，OK\n\n# 切换！\ndocker stop web && docker rm web\ndocker rename web-test web\n",[93,2454,2455,2461,2477,2482,2487,2512,2517,2523,2534,2539,2545,2567],{"__ignoreMap":171},[494,2456,2457],{"class":496,"line":497},[494,2458,2460],{"class":2459},"sJ8bj","# 构建新镜像\n",[494,2462,2463,2465,2468,2471,2474],{"class":496,"line":324},[494,2464,2279],{"class":1989},[494,2466,2467],{"class":521}," build",[494,2469,2470],{"class":506}," -t",[494,2472,2473],{"class":521}," website:v4-fix",[494,2475,2476],{"class":521}," .\n",[494,2478,2479],{"class":496,"line":331},[494,2480,2481],{"emptyLinePlaceholder":342},"\n",[494,2483,2484],{"class":496,"line":525},[494,2485,2486],{"class":2459},"# 在 3001 端口测试\n",[494,2488,2489,2491,2494,2497,2500,2503,2506,2509],{"class":496,"line":1761},[494,2490,2279],{"class":1989},[494,2492,2493],{"class":521}," run",[494,2495,2496],{"class":506}," -d",[494,2498,2499],{"class":506}," --name",[494,2501,2502],{"class":521}," web-test",[494,2504,2505],{"class":506}," -p",[494,2507,2508],{"class":521}," 3001:3000",[494,2510,2511],{"class":521}," website:v4-fix\n",[494,2513,2515],{"class":496,"line":2514},6,[494,2516,2481],{"emptyLinePlaceholder":342},[494,2518,2520],{"class":496,"line":2519},7,[494,2521,2522],{"class":2459},"# 验证...\n",[494,2524,2526,2528,2531],{"class":496,"line":2525},8,[494,2527,1725],{"class":1989},[494,2529,2530],{"class":521}," http:\u002F\u002F127.0.0.1:3001\u002F",[494,2532,2533],{"class":2459},"  # 返回 200，OK\n",[494,2535,2537],{"class":496,"line":2536},9,[494,2538,2481],{"emptyLinePlaceholder":342},[494,2540,2542],{"class":496,"line":2541},10,[494,2543,2544],{"class":2459},"# 切换！\n",[494,2546,2548,2550,2553,2556,2559,2561,2564],{"class":496,"line":2547},11,[494,2549,2279],{"class":1989},[494,2551,2552],{"class":521}," stop",[494,2554,2555],{"class":521}," web",[494,2557,2558],{"class":500}," && ",[494,2560,2279],{"class":1989},[494,2562,2563],{"class":521}," rm",[494,2565,2566],{"class":521}," web\n",[494,2568,2570,2572,2575,2577],{"class":496,"line":2569},12,[494,2571,2279],{"class":1989},[494,2573,2574],{"class":521}," rename",[494,2576,2502],{"class":521},[494,2578,2566],{"class":521},[14,2580,2581],{},"切换命令执行了。然后：",[163,2583,2586],{"className":2584,"code":2585,"language":168},[166],"$ curl https:\u002F\u002Fdeeeli.com\ncurl: (35) OpenSSL SSL_connect: Connection reset by peer\n",[93,2587,2585],{"__ignoreMap":171},[14,2589,2590],{},"网站挂了。",[2302,2592],{},[18,2594,2596],{"id":2595},"三诊断过程","三、诊断过程",[155,2598,2599],{"id":2599},"症状分析",[60,2601,2602,2611],{},[63,2603,2604],{},[66,2605,2606,2609],{},[69,2607,2608],{},"测试",[69,2610,83],{},[85,2612,2613,2623,2633,2642],{},[66,2614,2615,2620],{},[90,2616,2617],{},[93,2618,2619],{},"curl http:\u002F\u002Fdeeeli.com",[90,2621,2622],{},"301 → https，正常",[66,2624,2625,2630],{},[90,2626,2627],{},[93,2628,2629],{},"curl https:\u002F\u002Fdeeeli.com",[90,2631,2632],{},"Connection reset",[66,2634,2635,2640],{},[90,2636,2637],{},[93,2638,2639],{},"curl -k https:\u002F\u002F服务器IP",[90,2641,2632],{},[66,2643,2644,2649],{},[90,2645,2646],{},[93,2647,2648],{},"curl http:\u002F\u002F服务器IP:3000",[90,2650,2651],{},"200 OK",[14,2653,2654,2655],{},"关键发现：",[26,2656,2657],{},"HTTP 80 端口正常（nginx 在跑），HTTPS 443 连接被 reset，但直接访问 Nuxt 的 3000 端口正常。",[14,2659,2660],{},"这说明 nginx 活着，Nuxt 也活着，但它们之间的通信出了问题。",[155,2662,2664],{"id":2663},"排查-nginx-配置","排查 nginx 配置",[163,2666,2668],{"className":1980,"code":2667,"language":1982,"meta":171,"style":171},"$ ssh user@43.135.47.130\n$ docker ps\nCONTAINER ID   IMAGE            STATUS\na1b2c3d4e5f6   nginx:alpine     Up 2 hours\n",[93,2669,2670,2681,2691,2705],{"__ignoreMap":171},[494,2671,2672,2675,2678],{"class":496,"line":497},[494,2673,2674],{"class":1989},"$",[494,2676,2677],{"class":521}," ssh",[494,2679,2680],{"class":521}," user@43.135.47.130\n",[494,2682,2683,2685,2688],{"class":496,"line":324},[494,2684,2674],{"class":1989},[494,2686,2687],{"class":521}," docker",[494,2689,2690],{"class":521}," ps\n",[494,2692,2693,2696,2699,2702],{"class":496,"line":331},[494,2694,2695],{"class":1989},"CONTAINER",[494,2697,2698],{"class":521}," ID",[494,2700,2701],{"class":521},"   IMAGE",[494,2703,2704],{"class":521},"            STATUS\n",[494,2706,2707,2710,2713,2716,2719],{"class":496,"line":525},[494,2708,2709],{"class":1989},"a1b2c3d4e5f6",[494,2711,2712],{"class":521},"   nginx:alpine",[494,2714,2715],{"class":521},"     Up",[494,2717,2718],{"class":506}," 2",[494,2720,2721],{"class":521}," hours\n",[14,2723,2724],{},"等等——只有一个容器？Nuxt 容器呢？",[163,2726,2728],{"className":1980,"code":2727,"language":1982,"meta":171,"style":171},"$ docker ps -a | grep nuxt\nb5c6d7e8f9a0   website:v4-fix   Exited (137) 3 minutes ago\n",[93,2729,2730,2752],{"__ignoreMap":171},[494,2731,2732,2734,2736,2739,2742,2746,2749],{"class":496,"line":497},[494,2733,2674],{"class":1989},[494,2735,2687],{"class":521},[494,2737,2738],{"class":521}," ps",[494,2740,2741],{"class":506}," -a",[494,2743,2745],{"class":2744},"szBVR"," |",[494,2747,2748],{"class":1989}," grep",[494,2750,2751],{"class":521}," nuxt\n",[494,2753,2754,2757,2760,2763],{"class":496,"line":324},[494,2755,2756],{"class":1989},"b5c6d7e8f9a0",[494,2758,2759],{"class":521},"   website:v4-fix",[494,2761,2762],{"class":521},"   Exited",[494,2764,2765],{"class":500}," (137) 3 minutes ago\n",[14,2767,2768],{},"Nuxt 容器崩溃了，退出码 137（OOM killed）。",[155,2770,2772],{"id":2771},"nuxt-容器为什么-oom","Nuxt 容器为什么 OOM？",[14,2774,2775],{},"拉日志：",[163,2777,2779],{"className":1980,"code":2778,"language":1982,"meta":171,"style":171},"$ docker logs b5c6d7e8f9a0 --tail 50\n...\nFATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed\n...\n\u003C--- JS stacktrace --->\n",[93,2780,2781,2799,2804,2833,2837],{"__ignoreMap":171},[494,2782,2783,2785,2787,2790,2793,2796],{"class":496,"line":497},[494,2784,2674],{"class":1989},[494,2786,2687],{"class":521},[494,2788,2789],{"class":521}," logs",[494,2791,2792],{"class":521}," b5c6d7e8f9a0",[494,2794,2795],{"class":506}," --tail",[494,2797,2798],{"class":506}," 50\n",[494,2800,2801],{"class":496,"line":324},[494,2802,2803],{"class":506},"...\n",[494,2805,2806,2809,2812,2815,2818,2821,2824,2827,2830],{"class":496,"line":331},[494,2807,2808],{"class":1989},"FATAL",[494,2810,2811],{"class":521}," ERROR:",[494,2813,2814],{"class":521}," Ineffective",[494,2816,2817],{"class":521}," mark-compacts",[494,2819,2820],{"class":521}," near",[494,2822,2823],{"class":521}," heap",[494,2825,2826],{"class":521}," limit",[494,2828,2829],{"class":521}," Allocation",[494,2831,2832],{"class":521}," failed\n",[494,2834,2835],{"class":496,"line":525},[494,2836,2803],{"class":506},[494,2838,2839,2842,2845],{"class":496,"line":1761},[494,2840,2841],{"class":2744},"\u003C",[494,2843,2844],{"class":500},"--- JS stacktrace ---",[494,2846,2847],{"class":2744},">\n",[14,2849,2850],{},"v4 版本的 Three.js 场景在 SSR（服务端渲染）阶段触发了大量内存分配。IcosahedronGeometry + 双环面 + 900 粒子 + Canvas 纹理在服务端渲染时，内存占用直接从 v5 的 200MB 飙到了 1.2GB。",[14,2852,2853],{},"而 Docker 容器的内存限制是 1GB。",[14,2855,2856],{},"所以：",[34,2858,2859,2862,2865,2868,2871],{},[37,2860,2861],{},"Coder 修改正确（v4 的 Three.js 代码没有问题）",[37,2863,2864],{},"Reviewer 审查通过（代码质量没问题）",[37,2866,2867],{},"Tester 测试通过（本地 dev 模式内存充裕）",[37,2869,2870],{},"Ops 部署\"成功\"——容器确实起来了",[37,2872,2873],{},"但容器在首次 SSR 请求时 OOM 崩溃",[14,2875,2876,2877,2880],{},"这是典型的",[26,2878,2879],{},"环境差异问题","：本地开发 vs Docker 容器环境的内存边界不同。",[2302,2882],{},[18,2884,2886],{"id":2885},"四修复与回退","四、修复与回退",[155,2888,2890],{"id":2889},"ops-的自动修复尝试","Ops 的自动修复尝试",[14,2892,2893],{},"AI Ops Worker 检测到容器崩溃后，自动启动了修复流程：",[34,2895,2896,2902,2926],{},[37,2897,2898,2901],{},[26,2899,2900],{},"分析崩溃日志"," → 识别到 OOM",[37,2903,2904,473,2907,2922,2925],{},[26,2905,2906],{},"尝试重启容器",[163,2908,2910],{"className":1980,"code":2909,"language":1982,"meta":171,"style":171},"docker restart b5c6d7e8f9a0\n",[93,2911,2912],{"__ignoreMap":171},[494,2913,2914,2916,2919],{"class":496,"line":497},[494,2915,2279],{"class":1989},[494,2917,2918],{"class":521}," restart",[494,2920,2921],{"class":521}," b5c6d7e8f9a0\n",[2923,2924],"br",{},"容器重启成功，但第一次请求再次 OOM——这是设计问题，重启没用。",[37,2927,2928,473,2931,2951,2953],{},[26,2929,2930],{},"尝试增加内存限制",[163,2932,2934],{"className":1980,"code":2933,"language":1982,"meta":171,"style":171},"docker update --memory 2g web\n",[93,2935,2936],{"__ignoreMap":171},[494,2937,2938,2940,2943,2946,2949],{"class":496,"line":497},[494,2939,2279],{"class":1989},[494,2941,2942],{"class":521}," update",[494,2944,2945],{"class":506}," --memory",[494,2947,2948],{"class":521}," 2g",[494,2950,2566],{"class":521},[2923,2952],{},"失败了——服务器总共只有 2GB 内存，给容器 2GB 意味着系统和 nginx 会受影响。",[155,2955,2956],{"id":2956},"陷入僵局",[14,2958,2959],{},"此时 Ops Worker 面临一个选择：",[599,2961,2962,2965,2968],{},[37,2963,2964],{},"增大容器内存（可能导致整机 OOM）",[37,2966,2967],{},"回退到 v5（v5 内存占用低）",[37,2969,2970],{},"修改代码减少 Three.js 复杂度",[14,2972,2973],{},"它选择了第三条路——尝试通过 paramiko SSH 修改 Dockerfile，在构建时禁用 SSR 的 Three.js 初始化。",[163,2975,2977],{"className":1734,"code":2976,"language":1736,"meta":171,"style":171},"# Ops Worker 的修复脚本\nimport paramiko\nssh = paramiko.SSHClient()\nssh.connect('43.135.47.130', username='root', password='...')\nssh.exec_command('cd \u002Froot\u002Fpersonal-website && sed -i ...')\n",[93,2978,2979,2984,2989,2994,2999],{"__ignoreMap":171},[494,2980,2981],{"class":496,"line":497},[494,2982,2983],{},"# Ops Worker 的修复脚本\n",[494,2985,2986],{"class":496,"line":324},[494,2987,2988],{},"import paramiko\n",[494,2990,2991],{"class":496,"line":331},[494,2992,2993],{},"ssh = paramiko.SSHClient()\n",[494,2995,2996],{"class":496,"line":525},[494,2997,2998],{},"ssh.connect('43.135.47.130', username='root', password='...')\n",[494,3000,3001],{"class":496,"line":1761},[494,3002,3003],{},"ssh.exec_command('cd \u002Froot\u002Fpersonal-website && sed -i ...')\n",[14,3005,3006,3007,1129],{},"但这时发生了另一件事：",[26,3008,3009],{},"SSH 连接被拒绝了",[163,3011,3014],{"className":3012,"code":3013,"language":168},[166],"paramiko.ssh_exception.SSHException: Error reading SSH protocol banner\nConnection reset by peer\n",[93,3015,3013],{"__ignoreMap":171},[14,3017,3018,3019],{},"Ops Worker 之前的多次 SSH 连接加上 SFTP 上传镜像，触发了服务器的 fail2ban 规则——",[26,3020,3021],{},"AI 把自己的 SSH 访问给封了。",[2302,3023],{},[18,3025,3027],{"id":3026},"五兜底方案与教训","五、兜底方案与教训",[14,3029,3030],{},"最终是用户手动介入：",[163,3032,3034],{"className":1980,"code":3033,"language":1982,"meta":171,"style":171},"# 解除 fail2ban\nfail2ban-client unban 本地IP\n\n# 直接回退\ndocker stop web\ndocker run -d --name web -p 3000:3000 --memory 1.5g website:v5\n",[93,3035,3036,3041,3052,3056,3061,3069],{"__ignoreMap":171},[494,3037,3038],{"class":496,"line":497},[494,3039,3040],{"class":2459},"# 解除 fail2ban\n",[494,3042,3043,3046,3049],{"class":496,"line":324},[494,3044,3045],{"class":1989},"fail2ban-client",[494,3047,3048],{"class":521}," unban",[494,3050,3051],{"class":521}," 本地IP\n",[494,3053,3054],{"class":496,"line":331},[494,3055,2481],{"emptyLinePlaceholder":342},[494,3057,3058],{"class":496,"line":525},[494,3059,3060],{"class":2459},"# 直接回退\n",[494,3062,3063,3065,3067],{"class":496,"line":1761},[494,3064,2279],{"class":1989},[494,3066,2552],{"class":521},[494,3068,2566],{"class":521},[494,3070,3071,3073,3075,3077,3079,3081,3083,3086,3088,3091],{"class":496,"line":2514},[494,3072,2279],{"class":1989},[494,3074,2493],{"class":521},[494,3076,2496],{"class":506},[494,3078,2499],{"class":506},[494,3080,2555],{"class":521},[494,3082,2505],{"class":506},[494,3084,3085],{"class":521}," 3000:3000",[494,3087,2945],{"class":506},[494,3089,3090],{"class":521}," 1.5g",[494,3092,3093],{"class":521}," website:v5\n",[14,3095,3096],{},"网站恢复，v4 部署失败，v5 继续运行。",[155,3098,3099],{"id":3099},"整个过程的时间线",[60,3101,3102,3112],{},[63,3103,3104],{},[66,3105,3106,3109],{},[69,3107,3108],{},"时间",[69,3110,3111],{},"事件",[85,3113,3114,3122,3130,3138,3146,3154,3162,3170,3178,3186],{},[66,3115,3116,3119],{},[90,3117,3118],{},"T+0",[90,3120,3121],{},"Designer 产出 26 项差异分析",[66,3123,3124,3127],{},[90,3125,3126],{},"T+15min",[90,3128,3129],{},"Coder 完成修复，20 项 P0+P1",[66,3131,3132,3135],{},[90,3133,3134],{},"T+25min",[90,3136,3137],{},"Reviewer 审查通过",[66,3139,3140,3143],{},[90,3141,3142],{},"T+30min",[90,3144,3145],{},"Git 提交 + Tester 测试通过",[66,3147,3148,3151],{},[90,3149,3150],{},"T+45min",[90,3152,3153],{},"Ops 构建镜像、部署、切换",[66,3155,3156,3159],{},[90,3157,3158],{},"T+46min",[90,3160,3161],{},"容器 OOM，网站挂掉",[66,3163,3164,3167],{},[90,3165,3166],{},"T+47min",[90,3168,3169],{},"Ops 自动诊断、尝试修复",[66,3171,3172,3175],{},[90,3173,3174],{},"T+52min",[90,3176,3177],{},"触发 fail2ban，SSH 被封",[66,3179,3180,3183],{},[90,3181,3182],{},"T+55min",[90,3184,3185],{},"Ops 报错，block 等待人工",[66,3187,3188,3191],{},[90,3189,3190],{},"T+60min",[90,3192,3193],{},"用户 SSH 解除封禁，回退 v5",[14,3195,3196],{},"55 分钟，100+ 个 Kanban 任务，最终回到了起点。",[2302,3198],{},[18,3200,3202],{"id":3201},"六踩坑总结","六、踩坑总结",[155,3204,3206],{"id":3205},"_1-环境差异是最大的坑","1. 环境差异是最大的坑",[14,3208,3209,3210,3213],{},"本地 ",[93,3211,3212],{},"npm run dev"," → 一切正常。Docker SSR → OOM。Dev 和 Prod 的内存边界完全不同。AI Reviewer 和 Tester 在审查\u002F测试时无法捕捉这种差异——它们工作在开发环境。",[155,3215,3217],{"id":3216},"_2-ssr-3d-库-内存炸弹","2. SSR + 3D 库 = 内存炸弹",[14,3219,3220],{},"Three.js 在服务端渲染时会创建完整的 WebGL 上下文（通过 headless-gl），这在 1GB 的容器里是灾难。v4 的 IcosahedronGeometry + 900 粒子 + Canvas 纹理，SSR 内存峰值接近 1.5GB。",[14,3222,3223,3226],{},[26,3224,3225],{},"解决办法","：在 SSR 阶段跳过 Three.js 初始化，仅在客户端执行。",[155,3228,3230],{"id":3229},"_3-ai-ops-的自我毁灭","3. AI Ops 的自我毁灭",[14,3232,3233],{},"AI Ops Worker 的自动修复逻辑本身没问题，但它没有考虑到\"频繁的 SSH 连接会被封禁\"这一层。加上 paramiko 在连接失败时的重试行为，加速了 fail2ban 的触发。",[14,3235,3236,3238],{},[26,3237,1660],{},"：给 Ops Worker 加上 SSH 连接频率限制，或在任务流中显式处理 fail2ban 场景。",[155,3240,3242],{"id":3241},"_4-模型切换的连锁反应","4. 模型切换的连锁反应",[14,3244,3245],{},"在部署过程中，MiniMax API 遇到了 429 限流。按照系统设计，Worker 自动切换到了 DeepSeek 模型。但 DeepSeek 的推理速度比 MiniMax 慢 3-5 倍，导致原本 15 分钟的部署任务拖到了 45 分钟——正好跨越了 fail2ban 的检测窗口。",[2302,3247],{},[18,3249,3250],{"id":3250},"结语",[14,3252,3253],{},"AI Ops 不是银弹。它能自动化 90% 的流程，但剩下那 10%——环境差异、资源边界、连锁反应——仍然需要人的判断。",[14,3255,3256],{},"但这也正是有趣的地方：AI 在\"翻车\"中学到的教训，比在\"顺利\"中多得多。这次事件之后，Manager 的 SOUL.md 里新增了一大段关于部署安全的规则：先验证再切换、保留回滚方案、SSH 连接频率限制、Docker 内存限制检查……",[14,3258,3259],{},"AI 学会了。代价是网站挂了 9 分钟。",[14,3261,3262],{},"下一次部署，同样的坑不会再踩。",[14,3264,3265],{},"（除非 AI 发现了新的坑。）",[2242,3267,3268],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":171,"searchDepth":324,"depth":324,"links":3270},[3271,3272,3277,3282,3286,3289,3295],{"id":2306,"depth":324,"text":2307},{"id":2377,"depth":324,"text":2378,"children":3273},[3274,3275,3276],{"id":2381,"depth":331,"text":2382},{"id":2414,"depth":331,"text":2415},{"id":2433,"depth":331,"text":2434},{"id":2595,"depth":324,"text":2596,"children":3278},[3279,3280,3281],{"id":2599,"depth":331,"text":2599},{"id":2663,"depth":331,"text":2664},{"id":2771,"depth":331,"text":2772},{"id":2885,"depth":324,"text":2886,"children":3283},[3284,3285],{"id":2889,"depth":331,"text":2890},{"id":2956,"depth":331,"text":2956},{"id":3026,"depth":324,"text":3027,"children":3287},[3288],{"id":3099,"depth":331,"text":3099},{"id":3201,"depth":324,"text":3202,"children":3290},[3291,3292,3293,3294],{"id":3205,"depth":331,"text":3206},{"id":3216,"depth":331,"text":3217},{"id":3229,"depth":331,"text":3230},{"id":3241,"depth":331,"text":3242},{"id":3250,"depth":324,"text":3250},"2026-06-02","记录了 AI Ops Worker 在部署网站时引发的一连串故障：Docker 容器崩溃、nginx 502、SSH 连接被拒。从 root cause 到修复过程的完整复盘。",{"author":3299},"陈德立","\u002Fblog\u002Fagent-ops-butterfly-effect",{"title":2283,"description":3297},"blog\u002Fagent-ops-butterfly-effect",[3304,3305,2279,3306,3307,3308,1990],"ai-agent","devops","nginx","incident","ops","3GORltjWwyGBJyuHVDAt5tTJuyTiABrxxqJjHZ2nGg0",{"id":3311,"title":3312,"body":3313,"date":3296,"description":5017,"draft":339,"extension":340,"meta":5018,"navigation":342,"path":5019,"seo":5020,"stem":5021,"tags":5022,"__hash__":5031},"blog\u002Fblog\u002Fagent-ops-full-postmortem.md","AI Ops 翻车全复盘：v4→v5→v4 的 100 次部署与一次 OOM",{"type":8,"value":3314,"toc":4980},[3315,3318,3323,3326,3328,3332,3335,3470,3473,3476,3478,3482,3484,3525,3528,3531,3533,3537,3540,3545,3649,3656,3662,3665,3667,3671,3674,3677,3691,3694,3697,3699,3703,3706,3709,3968,3971,3993,3995,3997,4044,4049,4130,4133,4135,4139,4142,4148,4197,4200,4228,4231,4237,4240,4244,4247,4253,4256,4342,4345,4349,4352,4358,4360,4364,4368,4371,4388,4392,4395,4400,4403,4406,4409,4411,4415,4418,4421,4525,4527,4531,4534,4539,4581,4603,4614,4616,4620,4706,4709,4711,4715,4718,4823,4826,4828,4832,4834,4839,4841,4853,4855,4858,4862,4868,4871,4875,4882,4886,4889,4893,4899,4901,4905,4962,4964,4966,4968,4970,4972,4974,4977],[2287,3316,3312],{"id":3317},"ai-ops-翻车全复盘v4v5v4-的-100-次部署与一次-oom",[11,3319,3320],{},[14,3321,3322],{},"TL;DR — 用了 9 个 AI Agent、140 个 Kanban 任务、5 版设计稿、无数次部署，最后回到最初的版本。总\"净产出\"为零，但学到了所有东西。网站挂了 55 分钟，AI 还把 SSH 封了。",[14,3324,3325],{},"这不是成功故事。这是一份诚实的翻车全复盘。",[2302,3327],{},[18,3329,3331],{"id":3330},"一系统架构谁是肇事者","一、系统架构：谁是\"肇事者\"",[14,3333,3334],{},"在开始之前，先认识一下这支\"AI 工程团队\"的 9 名成员：",[60,3336,3337,3351],{},[63,3338,3339],{},[66,3340,3341,3344,3347,3349],{},[69,3342,3343],{},"角色",[69,3345,3346],{},"Profile",[69,3348,636],{},[69,3350,71],{},[85,3352,3353,3367,3380,3393,3406,3419,3432,3445,3457],{},[66,3354,3355,3358,3361,3364],{},[90,3356,3357],{},"项目经理",[90,3359,3360],{},"Manager",[90,3362,3363],{},"拆需求、分配任务、跟踪进度",[90,3365,3366],{},"DeepSeek",[66,3368,3369,3371,3374,3377],{},[90,3370,2322],{},[90,3372,3373],{},"Designer",[90,3375,3376],{},"出效果图、UI 差异分析",[90,3378,3379],{},"MiniMax",[66,3381,3382,3385,3388,3391],{},[90,3383,3384],{},"架构师",[90,3386,3387],{},"Architect",[90,3389,3390],{},"技术选型、系统设计",[90,3392,3379],{},[66,3394,3395,3398,3401,3404],{},[90,3396,3397],{},"开发者",[90,3399,3400],{},"Coder",[90,3402,3403],{},"写代码",[90,3405,3379],{},[66,3407,3408,3411,3414,3417],{},[90,3409,3410],{},"全栈",[90,3412,3413],{},"FullStack",[90,3415,3416],{},"前后端串的功能",[90,3418,3379],{},[66,3420,3421,3424,3427,3430],{},[90,3422,3423],{},"审查者",[90,3425,3426],{},"Reviewer",[90,3428,3429],{},"代码审查",[90,3431,3379],{},[66,3433,3434,3437,3440,3443],{},[90,3435,3436],{},"测试者",[90,3438,3439],{},"Tester",[90,3441,3442],{},"质量扫描+自动化测试",[90,3444,3379],{},[66,3446,3447,3449,3452,3455],{},[90,3448,1103],{},[90,3450,3451],{},"Ops",[90,3453,3454],{},"SSH 部署、预览上线",[90,3456,3379],{},[66,3458,3459,3462,3465,3468],{},[90,3460,3461],{},"主审查",[90,3463,3464],{},"Default",[90,3466,3467],{},"部署后线上验证",[90,3469,3366],{},[14,3471,3472],{},"它们通过一个 SQLite 的 Kanban 系统协作。Manager 创建任务，Dispatcher 自动调度，Worker 抢任务干活。理论上，这是一条完美的流水线。",[14,3474,3475],{},"实际上……",[2302,3477],{},[18,3479,3481],{"id":3480},"二v4来之不易的稳定版本","二、v4：来之不易的稳定版本",[14,3483,2310],{},[60,3485,3486,3496],{},[63,3487,3488],{},[66,3489,3490,3492,3494],{},[69,3491,2319],{},[69,3493,2322],{},[69,3495,2325],{},[85,3497,3498,3507,3516],{},[66,3499,3500,3502,3504],{},[90,3501,2332],{},[90,3503,2335],{},[90,3505,3506],{},"赛博朋克风，Three.js 3D 背景，粒子效果",[66,3508,3509,3511,3513],{},[90,3510,2343],{},[90,3512,2346],{},[90,3514,3515],{},"IcosahedronGeometry 球体 + 双环面 + 900 粒子 + Canvas 神经网络",[66,3517,3518,3520,3522],{},[90,3519,2354],{},[90,3521,2357],{},[90,3523,3524],{},"极度精简，移除大量 3D 效果",[14,3526,3527],{},"v4 是经过用户反复确认的版本。Three.js 的 IcosahedronGeometry 球体 + 双环面 + 900 个粒子的 Canvas 神经网络背景，效果相当惊艳。",[14,3529,3530],{},"但 AI Designer 觉得\"还能更好\"。",[2302,3532],{},[18,3534,3536],{"id":3535},"三v5ai-自主优化的产物","三、v5：AI 自主优化的产物",[14,3538,3539],{},"在一个没有用户指令的空闲时段，Designer 产出了 v5 设计稿。它的\"优化\"逻辑是：",[11,3541,3542],{},[14,3543,3544],{},"\"赛博朋克风格已经过时了。简约克制的设计更符合现代审美。\"",[60,3546,3547,3559],{},[63,3548,3549],{},[66,3550,3551,3553,3556],{},[69,3552,74],{},[69,3554,3555],{},"v4（赛博朋克）",[69,3557,3558],{},"v5（精简克制）",[85,3560,3561,3572,3583,3594,3605,3616,3627,3638],{},[66,3562,3563,3566,3569],{},[90,3564,3565],{},"球体",[90,3567,3568],{},"IcosahedronGeometry",[90,3570,3571],{},"简化 SphereGeometry",[66,3573,3574,3577,3580],{},[90,3575,3576],{},"环面",[90,3578,3579],{},"双环面",[90,3581,3582],{},"移除",[66,3584,3585,3588,3591],{},[90,3586,3587],{},"粒子",[90,3589,3590],{},"900 个 + Canvas 纹理",[90,3592,3593],{},"300 个纯色",[66,3595,3596,3599,3602],{},[90,3597,3598],{},"背景",[90,3600,3601],{},"深色渐变 + 神经网络",[90,3603,3604],{},"纯色 #020617",[66,3606,3607,3610,3613],{},[90,3608,3609],{},"导航栏",[90,3611,3612],{},"毛玻璃半透明",[90,3614,3615],{},"完全不透明",[66,3617,3618,3621,3624],{},[90,3619,3620],{},"按钮",[90,3622,3623],{},"赛博朋克边框发光",[90,3625,3626],{},"扁平圆角",[66,3628,3629,3632,3635],{},[90,3630,3631],{},"字体",[90,3633,3634],{},"Google Fonts (Orbitron)",[90,3636,3637],{},"系统字体栈",[66,3639,3640,3643,3646],{},[90,3641,3642],{},"配色",[90,3644,3645],{},"多种霓虹色",[90,3647,3648],{},"单一 #22d3ee",[14,3650,3651,3652,3655],{},"纯从设计角度看，v5 确实\"更好\"——更可读、更符合现代设计系统、加载更快。但问题是：",[26,3653,3654],{},"用户要的不是\"更好\"，要的是 v4 的风格。"," Designer 没有区分\"设计优化\"和\"风格变更\"——它把两者当成了同一件事。",[14,3657,3658,3659],{},"Manager 启动了全自动 7 阶段部署流水线。45 分钟内完成，6 个 Worker 全部 APPROVE。",[26,3660,3661],{},"从流程上看，\"一切正常\"。",[14,3663,3664],{},"用户打开网站后说了一句话：「这个不好看，回退到 v4。」",[2302,3666],{},[18,3668,3670],{"id":3669},"四v5v4回退流水线","四、v5→v4：回退流水线",[14,3672,3673],{},"Manager 理解了这个需求——它不是简单地 Git revert，因为历史里已经混入了其他改动。最干净的方案是：Designer 对比差异 → Coder 修改。",[14,3675,3676],{},"AI Designer 接到任务后，产出了一个 26 项差异分析文档，分三个优先级：",[599,3678,3679,3683,3687],{},[37,3680,3681,2396],{},[26,3682,2395],{},[37,3684,3685,2402],{},[26,3686,2401],{},[37,3688,3689,2408],{},[26,3690,2407],{},[14,3692,3693],{},"Coder 完成了 20 项 P0+P1 修复。Reviewer 审查通过（APPROVE）。Git 提交成功。Tester 测试通过。",[14,3695,3696],{},"一切正常。然后 Ops 开始部署。",[2302,3698],{},[18,3700,3702],{"id":3701},"五部署从-http-200-到-connection-reset","五、部署：从 HTTP 200 到 Connection Reset",[155,3704,3705],{"id":3705},"部署流程",[14,3707,3708],{},"AI Ops Worker 的标准流程：",[163,3710,3712],{"className":1980,"code":3711,"language":1982,"meta":171,"style":171},"# 1. 本地构建镜像\ndocker build -t \u003C镜像名> .\n\n# 2. 压缩上传\ndocker save \u003C镜像名> | gzip > website.tar.gz\nsftp upload → \u002Ftmp\u002F\n\n# 3. 服务器加载\nssh \u003C用户名>@[服务器IP] \"docker load \u003C \u002Ftmp\u002Fwebsite.tar.gz\"\n\n# 4. 测试端口启动验证\ndocker run -d --name \u003C容器名>-test -p \u003C测试端口>:\u003C应用端口> \u003C镜像名>\ncurl http:\u002F\u002F127.0.0.1:\u003C测试端口>\u002F   # → 200 OK ✅\n\n# 5. 切换\ndocker stop \u003C容器名> && docker rm \u003C容器名>\ndocker rename \u003C容器名>-test \u003C容器名>\n",[93,3713,3714,3719,3740,3744,3749,3775,3789,3793,3798,3818,3822,3827,3881,3903,3908,3914,3943],{"__ignoreMap":171},[494,3715,3716],{"class":496,"line":497},[494,3717,3718],{"class":2459},"# 1. 本地构建镜像\n",[494,3720,3721,3723,3725,3727,3730,3732,3735,3738],{"class":496,"line":324},[494,3722,2279],{"class":1989},[494,3724,2467],{"class":521},[494,3726,2470],{"class":506},[494,3728,3729],{"class":2744}," \u003C",[494,3731,633],{"class":521},[494,3733,3734],{"class":500},"名",[494,3736,3737],{"class":2744},">",[494,3739,2476],{"class":521},[494,3741,3742],{"class":496,"line":331},[494,3743,2481],{"emptyLinePlaceholder":342},[494,3745,3746],{"class":496,"line":525},[494,3747,3748],{"class":2459},"# 2. 压缩上传\n",[494,3750,3751,3753,3756,3758,3760,3762,3764,3766,3769,3772],{"class":496,"line":1761},[494,3752,2279],{"class":1989},[494,3754,3755],{"class":521}," save",[494,3757,3729],{"class":2744},[494,3759,633],{"class":521},[494,3761,3734],{"class":500},[494,3763,3737],{"class":2744},[494,3765,2745],{"class":2744},[494,3767,3768],{"class":1989}," gzip",[494,3770,3771],{"class":2744}," >",[494,3773,3774],{"class":521}," website.tar.gz\n",[494,3776,3777,3780,3783,3786],{"class":496,"line":2514},[494,3778,3779],{"class":1989},"sftp",[494,3781,3782],{"class":521}," upload",[494,3784,3785],{"class":521}," →",[494,3787,3788],{"class":521}," \u002Ftmp\u002F\n",[494,3790,3791],{"class":496,"line":2519},[494,3792,2481],{"emptyLinePlaceholder":342},[494,3794,3795],{"class":496,"line":2525},[494,3796,3797],{"class":2459},"# 3. 服务器加载\n",[494,3799,3800,3803,3805,3808,3810,3812,3815],{"class":496,"line":2536},[494,3801,3802],{"class":1989},"ssh",[494,3804,3729],{"class":2744},[494,3806,3807],{"class":521},"用户",[494,3809,3734],{"class":500},[494,3811,3737],{"class":2744},[494,3813,3814],{"class":521},"@[服务器IP]",[494,3816,3817],{"class":521}," \"docker load \u003C \u002Ftmp\u002Fwebsite.tar.gz\"\n",[494,3819,3820],{"class":496,"line":2541},[494,3821,2481],{"emptyLinePlaceholder":342},[494,3823,3824],{"class":496,"line":2547},[494,3825,3826],{"class":2459},"# 4. 测试端口启动验证\n",[494,3828,3829,3831,3833,3835,3837,3839,3842,3844,3846,3849,3851,3853,3856,3859,3861,3864,3866,3869,3871,3873,3875,3877,3879],{"class":496,"line":2569},[494,3830,2279],{"class":1989},[494,3832,2493],{"class":521},[494,3834,2496],{"class":506},[494,3836,2499],{"class":506},[494,3838,3729],{"class":2744},[494,3840,3841],{"class":521},"容器",[494,3843,3734],{"class":500},[494,3845,3737],{"class":2744},[494,3847,3848],{"class":521},"-test",[494,3850,2505],{"class":506},[494,3852,3729],{"class":2744},[494,3854,3855],{"class":521},"测试端",[494,3857,3858],{"class":500},"口",[494,3860,3737],{"class":2744},[494,3862,3863],{"class":521},":",[494,3865,2841],{"class":2744},[494,3867,3868],{"class":521},"应用端",[494,3870,3858],{"class":500},[494,3872,3737],{"class":2744},[494,3874,3729],{"class":2744},[494,3876,633],{"class":521},[494,3878,3734],{"class":500},[494,3880,2847],{"class":2744},[494,3882,3884,3886,3889,3891,3893,3895,3897,3900],{"class":496,"line":3883},13,[494,3885,1725],{"class":1989},[494,3887,3888],{"class":521}," http:\u002F\u002F127.0.0.1:",[494,3890,2841],{"class":2744},[494,3892,3855],{"class":521},[494,3894,3858],{"class":500},[494,3896,3737],{"class":2744},[494,3898,3899],{"class":521},"\u002F",[494,3901,3902],{"class":2459},"   # → 200 OK ✅\n",[494,3904,3906],{"class":496,"line":3905},14,[494,3907,2481],{"emptyLinePlaceholder":342},[494,3909,3911],{"class":496,"line":3910},15,[494,3912,3913],{"class":2459},"# 5. 切换\n",[494,3915,3917,3919,3921,3923,3925,3927,3929,3931,3933,3935,3937,3939,3941],{"class":496,"line":3916},16,[494,3918,2279],{"class":1989},[494,3920,2552],{"class":521},[494,3922,3729],{"class":2744},[494,3924,3841],{"class":521},[494,3926,3734],{"class":500},[494,3928,3737],{"class":2744},[494,3930,2558],{"class":500},[494,3932,2279],{"class":1989},[494,3934,2563],{"class":521},[494,3936,3729],{"class":2744},[494,3938,3841],{"class":521},[494,3940,3734],{"class":500},[494,3942,2847],{"class":2744},[494,3944,3946,3948,3950,3952,3954,3956,3958,3960,3962,3964,3966],{"class":496,"line":3945},17,[494,3947,2279],{"class":1989},[494,3949,2574],{"class":521},[494,3951,3729],{"class":2744},[494,3953,3841],{"class":521},[494,3955,3734],{"class":500},[494,3957,3737],{"class":2744},[494,3959,3848],{"class":521},[494,3961,3729],{"class":2744},[494,3963,3841],{"class":521},[494,3965,3734],{"class":500},[494,3967,2847],{"class":2744},[14,3969,3970],{},"切换完成。然后：",[163,3972,3973],{"className":1980,"code":2585,"language":1982,"meta":171,"style":171},[93,3974,3975,3985],{"__ignoreMap":171},[494,3976,3977,3979,3982],{"class":496,"line":497},[494,3978,2674],{"class":1989},[494,3980,3981],{"class":521}," curl",[494,3983,3984],{"class":521}," https:\u002F\u002Fdeeeli.com\n",[494,3986,3987,3990],{"class":496,"line":324},[494,3988,3989],{"class":1989},"curl:",[494,3991,3992],{"class":500}," (35) OpenSSL SSL_connect: Connection reset by peer\n",[14,3994,2590],{},[155,3996,2599],{"id":2599},[60,3998,3999,4007],{},[63,4000,4001],{},[66,4002,4003,4005],{},[69,4004,2608],{},[69,4006,83],{},[85,4008,4009,4017,4025,4034],{},[66,4010,4011,4015],{},[90,4012,4013],{},[93,4014,2619],{},[90,4016,2622],{},[66,4018,4019,4023],{},[90,4020,4021],{},[93,4022,2629],{},[90,4024,2632],{},[66,4026,4027,4032],{},[90,4028,4029],{},[93,4030,4031],{},"curl -k https:\u002F\u002F[服务器IP]",[90,4033,2632],{},[66,4035,4036,4041],{},[90,4037,4038],{},[93,4039,4040],{},"curl http:\u002F\u002F127.0.0.1:\u003C应用端口>",[90,4042,4043],{},"Connection refused",[14,4045,2654,4046],{},[26,4047,4048],{},"HTTP 80 端口正常（nginx 在跑），HTTPS 443 连接被 reset，但 Nuxt 的 3000 端口根本没有监听。",[163,4050,4052],{"className":1980,"code":4051,"language":1982,"meta":171,"style":171},"$ docker ps\nCONTAINER ID   IMAGE            STATUS\n\u003C容器ID>       nginx:alpine     Up 30 minutes\n\n$ docker ps -a | grep website\n\u003C容器ID>       \u003C镜像名>         Exited (137) 2 minutes ago\n",[93,4053,4054,4062,4072,4084,4088,4105],{"__ignoreMap":171},[494,4055,4056,4058,4060],{"class":496,"line":497},[494,4057,2674],{"class":1989},[494,4059,2687],{"class":521},[494,4061,2690],{"class":521},[494,4063,4064,4066,4068,4070],{"class":496,"line":324},[494,4065,2695],{"class":1989},[494,4067,2698],{"class":521},[494,4069,2701],{"class":521},[494,4071,2704],{"class":521},[494,4073,4074,4076,4079,4081],{"class":496,"line":331},[494,4075,2841],{"class":2744},[494,4077,4078],{"class":500},"容器ID",[494,4080,3737],{"class":2744},[494,4082,4083],{"class":500},"       nginx:alpine     Up 30 minutes\n",[494,4085,4086],{"class":496,"line":525},[494,4087,2481],{"emptyLinePlaceholder":342},[494,4089,4090,4092,4094,4096,4098,4100,4102],{"class":496,"line":1761},[494,4091,2674],{"class":1989},[494,4093,2687],{"class":521},[494,4095,2738],{"class":521},[494,4097,2741],{"class":506},[494,4099,2745],{"class":2744},[494,4101,2748],{"class":1989},[494,4103,4104],{"class":521}," website\n",[494,4106,4107,4109,4111,4113,4116,4119,4121,4124,4127],{"class":496,"line":2514},[494,4108,2841],{"class":2744},[494,4110,4078],{"class":500},[494,4112,3737],{"class":2744},[494,4114,4115],{"class":2744},"       \u003C",[494,4117,4118],{"class":500},"镜像名",[494,4120,3737],{"class":2744},[494,4122,4123],{"class":500},"         Exited (",[494,4125,4126],{"class":1989},"137",[494,4128,4129],{"class":500},") 2 minutes ago\n",[14,4131,4132],{},"退出码 137。这是 Linux 内核发送 SIGKILL 的信号——OOM。",[2302,4134],{},[18,4136,4138],{"id":4137},"六系统化排障隔离定位验证","六、系统化排障：隔离→定位→验证",[155,4140,4141],{"id":4141},"从外到内逐层隔离",[163,4143,4146],{"className":4144,"code":4145,"language":168},[166],"用户 ─→ DNS ─→ CDN ─→ nginx ─→ Nuxt\n",[93,4147,4145],{"__ignoreMap":171},[34,4149,4150,4163,4169,4179,4185,4191],{},[37,4151,4152,4155,4156,4159,4160],{},[26,4153,4154],{},"DNS 检查"," — 正常，",[93,4157,4158],{},"dig deeeli.com"," 返回 ",[494,4161,4162],{},"服务器IP",[37,4164,4165,4168],{},[26,4166,4167],{},"CDN 检查"," — 无 CDN，直连服务器",[37,4170,4171,4174,4175,4178],{},[26,4172,4173],{},"nginx 检查"," — ",[93,4176,4177],{},"curl -I http:\u002F\u002Fdeeeli.com"," → 301，nginx 活着",[37,4180,4181,4184],{},[26,4182,4183],{},"SSL 层诊断"," — TLS 握手 OK，证书验证通过，但在发送 HTTP 请求时被 reset → nginx SSL 正常，上游问题",[37,4186,4187,4190],{},[26,4188,4189],{},"端口检查"," — 3000 端口 Connection refused → Nuxt 没有监听",[37,4192,4193,4196],{},[26,4194,4195],{},"容器状态"," — Exited (137) → OOM killed",[155,4198,4199],{"id":4199},"日志回溯",[163,4201,4203],{"className":1980,"code":4202,"language":1982,"meta":171,"style":171},"$ docker logs \u003C容器ID> --tail 100\n",[93,4204,4205],{"__ignoreMap":171},[494,4206,4207,4209,4211,4213,4215,4218,4221,4223,4225],{"class":496,"line":497},[494,4208,2674],{"class":1989},[494,4210,2687],{"class":521},[494,4212,2789],{"class":521},[494,4214,3729],{"class":2744},[494,4216,4217],{"class":521},"容器I",[494,4219,4220],{"class":500},"D",[494,4222,3737],{"class":2744},[494,4224,2795],{"class":506},[494,4226,4227],{"class":506}," 100\n",[14,4229,4230],{},"关键片段：",[163,4232,4235],{"className":4233,"code":4234,"language":168},[166],"[nuxt] [request] GET \u002F\n[nuxt] [ssr] Rendering page: \u002F\n\u003C--- Last few GCs --->\n[45:0x5a3c000]  1800 ms: Scavenge 950.0 (992.0) -> 948.0 (992.0) MB\n[45:0x5a3c000]  2000 ms: Mark-sweep 992.0 (1024.0) -> 990.0 (1024.0) MB\n[45:0x5a3c000]  2500 ms: Mark-sweep 1010.0 (1024.0) -> 1005.0 (1024.0) MB\n\n\u003C--- JS stacktrace --->\nFATAL ERROR: Ineffective mark-compacts near heap limit\nAllocation failed - JavaScript heap out of memory\n",[93,4236,4234],{"__ignoreMap":171},[14,4238,4239],{},"这是 V8 的经典 OOM 信息。Nuxt 在进行 SSR 时，V8 堆内存溢出。",[155,4241,4243],{"id":4242},"为什么-v5-没事v4-就-oom","为什么 v5 没事，v4 就 OOM？",[14,4245,4246],{},"v4 的 Three.js 场景：",[163,4248,4251],{"className":4249,"code":4250,"language":168},[166],"- IcosahedronGeometry（20 面的二十面体）\n- 双环面（两个 TorusGeometry）\n- 900 个粒子的 Points 系统\n- Canvas 纹理（每个粒子着色用）\n- 自定义 ShaderMaterial\n",[93,4252,4250],{"__ignoreMap":171},[14,4254,4255],{},"这些在 SSR 阶段全部加载到 V8 堆中。v5 的简化场景则大幅减少了内存开销。",[60,4257,4258,4270],{},[63,4259,4260],{},[66,4261,4262,4264,4267],{},[69,4263,777],{},[69,4265,4266],{},"v5 内存",[69,4268,4269],{},"v4 内存",[85,4271,4272,4283,4293,4304,4315,4325],{},[66,4273,4274,4277,4280],{},[90,4275,4276],{},"几何体缓冲区",[90,4278,4279],{},"~10MB",[90,4281,4282],{},"~50MB",[66,4284,4285,4288,4290],{},[90,4286,4287],{},"Canvas 纹理",[90,4289,1057],{},[90,4291,4292],{},"~80MB",[66,4294,4295,4298,4301],{},[90,4296,4297],{},"着色器编译",[90,4299,4300],{},"~30MB",[90,4302,4303],{},"~100MB",[66,4305,4306,4309,4312],{},[90,4307,4308],{},"粒子系统",[90,4310,4311],{},"~15MB",[90,4313,4314],{},"~45MB",[66,4316,4317,4320,4323],{},[90,4318,4319],{},"V8 堆其他",[90,4321,4322],{},"~150MB",[90,4324,4322],{},[66,4326,4327,4332,4337],{},[90,4328,4329],{},[26,4330,4331],{},"总计（SSR 峰值）",[90,4333,4334],{},[26,4335,4336],{},"~200MB",[90,4338,4339],{},[26,4340,4341],{},"~425MB",[14,4343,4344],{},"V8 的 GC 在堆接近 1GB 限制时来不及回收，峰值内存远超静态分析数字。加上 Nuxt 应用本身和页面组件的开销，1GB 的容器限制像纸一样被捅穿了。",[155,4346,4348],{"id":4347},"tester-为什么没发现","Tester 为什么没发现？",[14,4350,4351],{},"这是整件事最令人沮丧的部分。Tester 跑了单元测试、集成测试、E2E 测试，全部通过。但为什么没发现 OOM？",[14,4353,4354,4355],{},"答案：Tester 运行在本地开发环境，有 16GB 内存。Docker 容器限制的 1GB 对本地来说毫无意义——V8 可以自由扩展到 4GB 堆空间。",[26,4356,4357],{},"测试环境 ≠ 生产环境。",[2302,4359],{},[18,4361,4363],{"id":4362},"七雪上加霜ssh-被封","七、雪上加霜：SSH 被封",[155,4365,4367],{"id":4366},"ops-的自动修复","Ops 的自动修复",[14,4369,4370],{},"容器崩溃后，AI Ops Worker 自动启动了修复流程：",[34,4372,4373,4377,4382],{},[37,4374,4375,2901],{},[26,4376,2900],{},[37,4378,4379,4381],{},[26,4380,2906],{}," — 重启成功，但第一次请求再次 OOM。这是设计问题，重启没用。",[37,4383,4384,4387],{},[26,4385,4386],{},"尝试增加内存"," — 容器内存限制调整失败，服务器资源不足。",[155,4389,4391],{"id":4390},"fail2ban-自伤","fail2ban 自伤",[14,4393,4394],{},"Ops Worker 选择了第三条路——通过 paramiko SSH 修改 Dockerfile。但在多次连接尝试后：",[163,4396,4398],{"className":4397,"code":3013,"language":168},[166],[93,4399,3013],{"__ignoreMap":171},[14,4401,4402],{},"服务器的 fail2ban 检测到了异常频繁的 SSH 连接，把 Ops Worker 的 IP 封禁了。",[14,4404,4405],{},"加上 paramiko 在连接失败时的自动重试机制（默认 3 次），fail2ban 的触发速度比预期快得多。从第一个连接失败到被封禁，只用了不到两分钟。",[14,4407,4408],{},"同一个 Worker 一边在尝试修复容器，一边在触发安全防护——它在和自己的安全策略打架。",[2302,4410],{},[18,4412,4414],{"id":4413},"八修复人与-ai-的协作","八、修复：人与 AI 的协作",[14,4416,4417],{},"AI Ops 进入了僵局：服务器 SSH 被封，无法远程操作。只能人工介入。",[155,4419,4420],{"id":4420},"人工兜底",[163,4422,4424],{"className":1980,"code":4423,"language":1982,"meta":171,"style":171},"# 通过云厂商 Web Terminal 登录（绕过 fail2ban）\nssh \u003C用户名>@[服务器IP]\n\n# 解除 fail2ban\nfail2ban-client set sshd unbanip \u003C本地IP>\n\n# 直接回退到 v5（内存安全版本）\ndocker stop \u003C容器名>\ndocker run -d --name \u003C容器名> \u003C镜像名>\n",[93,4425,4426,4431,4446,4450,4454,4476,4480,4485,4499],{"__ignoreMap":171},[494,4427,4428],{"class":496,"line":497},[494,4429,4430],{"class":2459},"# 通过云厂商 Web Terminal 登录（绕过 fail2ban）\n",[494,4432,4433,4435,4437,4439,4441,4443],{"class":496,"line":324},[494,4434,3802],{"class":1989},[494,4436,3729],{"class":2744},[494,4438,3807],{"class":521},[494,4440,3734],{"class":500},[494,4442,3737],{"class":2744},[494,4444,4445],{"class":521},"@[服务器IP]\n",[494,4447,4448],{"class":496,"line":331},[494,4449,2481],{"emptyLinePlaceholder":342},[494,4451,4452],{"class":496,"line":525},[494,4453,3040],{"class":2459},[494,4455,4456,4458,4460,4463,4466,4468,4471,4474],{"class":496,"line":1761},[494,4457,3045],{"class":1989},[494,4459,1996],{"class":521},[494,4461,4462],{"class":521}," sshd",[494,4464,4465],{"class":521}," unbanip",[494,4467,3729],{"class":2744},[494,4469,4470],{"class":521},"本地I",[494,4472,4473],{"class":500},"P",[494,4475,2847],{"class":2744},[494,4477,4478],{"class":496,"line":2514},[494,4479,2481],{"emptyLinePlaceholder":342},[494,4481,4482],{"class":496,"line":2519},[494,4483,4484],{"class":2459},"# 直接回退到 v5（内存安全版本）\n",[494,4486,4487,4489,4491,4493,4495,4497],{"class":496,"line":2525},[494,4488,2279],{"class":1989},[494,4490,2552],{"class":521},[494,4492,3729],{"class":2744},[494,4494,3841],{"class":521},[494,4496,3734],{"class":500},[494,4498,2847],{"class":2744},[494,4500,4501,4503,4505,4507,4509,4511,4513,4515,4517,4519,4521,4523],{"class":496,"line":2536},[494,4502,2279],{"class":1989},[494,4504,2493],{"class":521},[494,4506,2496],{"class":506},[494,4508,2499],{"class":506},[494,4510,3729],{"class":2744},[494,4512,3841],{"class":521},[494,4514,3734],{"class":500},[494,4516,3737],{"class":2744},[494,4518,3729],{"class":2744},[494,4520,633],{"class":521},[494,4522,3734],{"class":500},[494,4524,2847],{"class":2744},[14,4526,3096],{},[155,4528,4530],{"id":4529},"根因修复clientonly-防线","根因修复：ClientOnly 防线",[14,4532,4533],{},"回退只是临时措施。真正的修复需要让 v4 在 1GB 容器中也能运行。两个方案组合：",[14,4535,4536],{},[26,4537,4538],{},"方案 C + B 组合：",[163,4540,4544],{"className":4541,"code":4542,"language":4543,"meta":171,"style":171},"language-html shiki shiki-themes github-light github-dark","\u003C!-- 方案 C：ClientOnly 包裹，SSR 时输出占位 -->\n\u003CClientOnly>\n  \u003CThreeBackground \u002F>\n\u003C\u002FClientOnly>\n","html",[93,4545,4546,4551,4561,4572],{"__ignoreMap":171},[494,4547,4548],{"class":496,"line":497},[494,4549,4550],{"class":2459},"\u003C!-- 方案 C：ClientOnly 包裹，SSR 时输出占位 -->\n",[494,4552,4553,4555,4559],{"class":496,"line":324},[494,4554,2841],{"class":500},[494,4556,4558],{"class":4557},"s7hpK","ClientOnly",[494,4560,2847],{"class":500},[494,4562,4563,4566,4569],{"class":496,"line":331},[494,4564,4565],{"class":500},"  \u003C",[494,4567,4568],{"class":4557},"ThreeBackground",[494,4570,4571],{"class":500}," \u002F>\n",[494,4573,4574,4577,4579],{"class":496,"line":525},[494,4575,4576],{"class":500},"\u003C\u002F",[494,4578,4558],{"class":4557},[494,4580,2847],{"class":500},[163,4582,4586],{"className":4583,"code":4584,"language":4585,"meta":171,"style":171},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F 方案 B：防御性 SSR 检查\nconst isSSR = typeof window === 'undefined';\nconst scene = isSSR ? null : new THREE.Scene();\n","javascript",[93,4587,4588,4593,4598],{"__ignoreMap":171},[494,4589,4590],{"class":496,"line":497},[494,4591,4592],{},"\u002F\u002F 方案 B：防御性 SSR 检查\n",[494,4594,4595],{"class":496,"line":324},[494,4596,4597],{},"const isSSR = typeof window === 'undefined';\n",[494,4599,4600],{"class":496,"line":331},[494,4601,4602],{},"const scene = isSSR ? null : new THREE.Scene();\n",[14,4604,4605,4606,4609,4610,4613],{},"一行 ",[93,4607,4608],{},"\u003CClientOnly>"," + 一行 ",[93,4611,4612],{},"isSSR"," 检查，彻底避免了 SSR 阶段的 Three.js 内存炸弹。",[2302,4615],{},[18,4617,4619],{"id":4618},"九完整时间线","九、完整时间线",[60,4621,4622,4630],{},[63,4623,4624],{},[66,4625,4626,4628],{},[69,4627,3108],{},[69,4629,3111],{},[85,4631,4632,4639,4646,4652,4659,4666,4673,4680,4687,4693,4699],{},[66,4633,4634,4636],{},[90,4635,3118],{},[90,4637,4638],{},"Designer 接到回退任务",[66,4640,4641,4643],{},[90,4642,3126],{},[90,4644,4645],{},"产出 26 项差异分析",[66,4647,4648,4650],{},[90,4649,3134],{},[90,4651,3129],{},[66,4653,4654,4657],{},[90,4655,4656],{},"T+35min",[90,4658,3137],{},[66,4660,4661,4664],{},[90,4662,4663],{},"T+40min",[90,4665,3145],{},[66,4667,4668,4670],{},[90,4669,3150],{},[90,4671,4672],{},"Ops 构建镜像、上传、部署、切换",[66,4674,4675,4677],{},[90,4676,3158],{},[90,4678,4679],{},"Nuxt 容器 OOM（退出码 137），网站挂掉",[66,4681,4682,4684],{},[90,4683,3166],{},[90,4685,4686],{},"Ops 自动诊断、尝试重启\u002F加内存\u002F修改配置",[66,4688,4689,4691],{},[90,4690,3174],{},[90,4692,3177],{},[66,4694,4695,4697],{},[90,4696,3182],{},[90,4698,3185],{},[66,4700,4701,4703],{},[90,4702,3190],{},[90,4704,4705],{},"用户通过 Web Terminal 解除封禁，回退 v5",[14,4707,4708],{},"55 分钟，140 个 Kanban 任务，最终回到了起点。",[2302,4710],{},[18,4712,4714],{"id":4713},"十数字说话140-次任务的真相","十、数字说话：140 次任务的真相",[14,4716,4717],{},"在 v4→v5→v4 的整个过程中，Kanban 系统创建了超过 140 个任务：",[60,4719,4720,4733],{},[63,4721,4722],{},[66,4723,4724,4727,4730],{},[69,4725,4726],{},"阶段",[69,4728,4729],{},"任务数",[69,4731,4732],{},"工人",[85,4734,4735,4746,4757,4768,4779,4789,4799,4809],{},[66,4736,4737,4740,4743],{},[90,4738,4739],{},"v4 最终确认（Designer 迭代）",[90,4741,4742],{},"~20 个",[90,4744,4745],{},"Designer × 3",[66,4747,4748,4751,4754],{},[90,4749,4750],{},"v5 设计",[90,4752,4753],{},"~10 个",[90,4755,4756],{},"Designer × 2",[66,4758,4759,4762,4765],{},[90,4760,4761],{},"v5 实现上线",[90,4763,4764],{},"~30 个",[90,4766,4767],{},"Coder + Reviewer + Tester + Ops",[66,4769,4770,4773,4776],{},[90,4771,4772],{},"admin 侧边栏修复",[90,4774,4775],{},"~15 个",[90,4777,4778],{},"Architect + Coder + Reviewer + Ops",[66,4780,4781,4784,4786],{},[90,4782,4783],{},"三项视觉修复",[90,4785,4742],{},[90,4787,4788],{},"Designer + Coder + Ops + Review",[66,4790,4791,4794,4796],{},[90,4792,4793],{},"v5→v4 回退（含 OOM）",[90,4795,4764],{},[90,4797,4798],{},"Designer + Coder + Reviewer + Tester + Ops + Review",[66,4800,4801,4804,4806],{},[90,4802,4803],{},"blog 页对齐",[90,4805,4775],{},[90,4807,4808],{},"Designer + Coder + Reviewer + Ops",[66,4810,4811,4816,4821],{},[90,4812,4813],{},[26,4814,4815],{},"合计",[90,4817,4818],{},[26,4819,4820],{},"~140 个",[90,4822],{},[14,4824,4825],{},"平均每次\"改动\"经历了 6-12 个 Worker 任务。管道的 7 个阶段，每个 5-30 分钟。管道越长，问题发现越晚。",[2302,4827],{},[18,4829,4831],{"id":4830},"十一踩坑总结7-条军规","十一、踩坑总结：7 条军规",[155,4833,3206],{"id":3205},[14,4835,3209,4836,4838],{},[93,4837,3212],{}," → 一切正常。Docker SSR → OOM。Dev 和 Prod 的内存边界完全不同。AI Reviewer 和 Tester 无法捕捉这种差异——它们工作在开发环境。",[155,4840,3217],{"id":3216},[14,4842,4843,4844,473,4846,4848,4849,4852],{},"Three.js 在 SSR 阶段会将所有几何体、纹理、着色器加载到 V8 堆中。1GB 容器限制在 v4 的复杂场景面前不堪一击。",[26,4845,3225],{},[93,4847,4608],{}," 包裹所有 3D 组件，或用 ",[93,4850,4851],{},"typeof window"," 守卫。",[155,4854,3230],{"id":3229},[14,4856,4857],{},"AI Ops Worker 的自动修复逻辑本身没问题，但它没有考虑到\"频繁的 SSH 连接会被封禁\"这一层。故障修复流程不应该触发安全机制把自己排除在外。",[155,4859,4861],{"id":4860},"_4-管道太长问题放大","4. 管道太长，问题放大",[163,4863,4866],{"className":4864,"code":4865,"language":168},[166],"Designer → Coder → Reviewer → Git → Tester → Ops → AgentReview\n",[93,4867,4865],{"__ignoreMap":171},[14,4869,4870],{},"7 个阶段。v5 的\"风格漂移\"在 Designer 阶段就发生了，但直到 Ops 部署后才能被用户看到——中间跨越了 Coder、Reviewer、Tester 三个阶段，它们全部 APPROVE——因为它们审查的是代码质量，不是设计意图。",[155,4872,4874],{"id":4873},"_5-worker-之间的幻觉放大","5. Worker 之间的\"幻觉放大\"",[14,4876,4877,4878,4881],{},"每个 Worker 只完成自己被分配的任务，",[26,4879,4880],{},"没有人做\"整体决策\"。"," Designer 说\"26 项差异需要修\"，Coder 修了 20 项，Reviewer 说\"代码 OK\"，Tester 说\"功能 OK\"——但没人问：\"v4 和 v5 的差异真的需要全部回到 v4 吗？v5 的一些改进应该保留吗？\"",[155,4883,4885],{"id":4884},"_6-模型切换的连锁反应","6. 模型切换的连锁反应",[14,4887,4888],{},"部署过程中 MiniMax API 遇到 429 限流。Worker 自动切换到了 DeepSeek。但 DeepSeek 的推理速度比 MiniMax 慢 3-5 倍，导致 15 分钟的部署拖到了 45 分钟——正好跨越了 fail2ban 的检测窗口。",[155,4890,4892],{"id":4891},"_7-人有最终决策权但需要及时看到","7. 人有最终决策权，但需要及时看到",[14,4894,4895,4896],{},"如果用户在 Designer 阶段就看到 v5 效果图，根本不会有后续的 95 个任务。",[26,4897,4898],{},"在任何 UI 变更进入开发之前，强制预览展示给用户确认。",[2302,4900],{},[18,4902,4904],{"id":4903},"十二预防措施不让它再发生","十二、预防措施：不让它再发生",[34,4906,4907,4913,4919,4925,4931,4937,4946,4956],{},[37,4908,4909,4912],{},[26,4910,4911],{},"Tester 容器化","：Tester 必须在与生产相同的内存限制下运行测试。本地 16GB 通过的测试，在 1GB 容器里可能连 SSR 都跑不完。\"在相同环境中测试和部署\"——AI 运维也需要遵守这个原则。",[37,4914,4915,4918],{},[26,4916,4917],{},"SSR 内存预算","：在 CI 中跑一次 production build + SSR 请求，记录内存峰值。当新改动导致 SSR 内存增长超过 20%，CI 应该 Block 部署。",[37,4920,4921,4924],{},[26,4922,4923],{},"渐进式部署","：先部署到 staging 端口，跑完整 E2E 测试，再切换。永远不要一步到位。",[37,4926,4927,4930],{},[26,4928,4929],{},"回滚方案","：每次部署前保留旧镜像，出问题一键回滚。",[37,4932,4933,4936],{},[26,4934,4935],{},"SSH 频率限制","：在 Ops Worker 中实现连接频率控制——每分钟最多 3 次新连接，防止触发 fail2ban。",[37,4938,4939,4942,4943,4945],{},[26,4940,4941],{},"ClientOnly 防御","：对所有含 Three.js \u002F WebGL \u002F Canvas 的组件，默认使用 ",[93,4944,4608],{}," 包裹。",[37,4947,4948,4951,4952,4955],{},[26,4949,4950],{},"Docker 资源监控","：在 compose 中加入 ",[93,4953,4954],{},"deploy.resources.limits"," 和健康检查。容器不应该静默 OOM。",[37,4957,4958,4961],{},[26,4959,4960],{},"产品经理角色","：加入 ProductManager——一个会在 v5 设计稿出来后说\"等等，用户确认了吗？\"的人。",[2302,4963],{},[18,4965,3250],{"id":3250},[14,4967,3253],{},[14,4969,3256],{},[14,4971,3259],{},[14,4973,3262],{},[14,4975,4976],{},"（但会有新的坑。）",[2242,4978,4979],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":171,"searchDepth":324,"depth":324,"links":4981},[4982,4983,4984,4985,4986,4990,4996,5000,5004,5005,5006,5015,5016],{"id":3330,"depth":324,"text":3331},{"id":3480,"depth":324,"text":3481},{"id":3535,"depth":324,"text":3536},{"id":3669,"depth":324,"text":3670},{"id":3701,"depth":324,"text":3702,"children":4987},[4988,4989],{"id":3705,"depth":331,"text":3705},{"id":2599,"depth":331,"text":2599},{"id":4137,"depth":324,"text":4138,"children":4991},[4992,4993,4994,4995],{"id":4141,"depth":331,"text":4141},{"id":4199,"depth":331,"text":4199},{"id":4242,"depth":331,"text":4243},{"id":4347,"depth":331,"text":4348},{"id":4362,"depth":324,"text":4363,"children":4997},[4998,4999],{"id":4366,"depth":331,"text":4367},{"id":4390,"depth":331,"text":4391},{"id":4413,"depth":324,"text":4414,"children":5001},[5002,5003],{"id":4420,"depth":331,"text":4420},{"id":4529,"depth":331,"text":4530},{"id":4618,"depth":324,"text":4619},{"id":4713,"depth":324,"text":4714},{"id":4830,"depth":324,"text":4831,"children":5007},[5008,5009,5010,5011,5012,5013,5014],{"id":3205,"depth":331,"text":3206},{"id":3216,"depth":331,"text":3217},{"id":3229,"depth":331,"text":3230},{"id":4860,"depth":331,"text":4861},{"id":4873,"depth":331,"text":4874},{"id":4884,"depth":331,"text":4885},{"id":4891,"depth":331,"text":4892},{"id":4903,"depth":324,"text":4904},{"id":3250,"depth":324,"text":3250},"我让 9 个 AI Agent 协作部署一个个人网站。它们创建了 140 个 Kanban 任务，迭代了 v4→v5→v4，把网站搞挂了 55 分钟，最后还把 SSH 封了。这是一份诚实的全链路复盘。",{"author":3299},"\u002Fblog\u002Fagent-ops-full-postmortem",{"title":3312,"description":5017},"blog\u002Fagent-ops-full-postmortem",[3304,5023,3305,2279,1990,3307,5024,5025,3306,5026,3308,5027,5028,5029,5030],"debugging","kanban","multi-agent","nuxt","software-engineering","ssr","troubleshooting","workflow","WRBAUK6KREzsqZCOmfNQZjkWpl08HBUPd4efIQTdzq0",{"id":5033,"title":5034,"body":5035,"date":3296,"description":5566,"draft":339,"extension":340,"meta":5567,"navigation":342,"path":5568,"seo":5569,"stem":5570,"tags":5571,"__hash__":5572},"blog\u002Fblog\u002Fmulti-agent-100-deploys.md","Multi-Agent 翻车实录：v4→v5→v4 的 100 次部署",{"type":8,"value":5036,"toc":5551},[5037,5040,5045,5048,5050,5054,5057,5163,5166,5169,5171,5173,5175,5178,5203,5206,5208,5210,5213,5216,5221,5224,5302,5308,5313,5316,5318,5322,5325,5331,5334,5337,5340,5345,5347,5351,5354,5357,5363,5366,5368,5372,5375,5457,5459,5463,5467,5470,5475,5479,5484,5487,5490,5494,5497,5500,5505,5509,5512,5521,5523,5527,5530,5535,5538,5545,5548],[2287,5038,5034],{"id":5039},"multi-agent-翻车实录v4v5v4-的-100-次部署",[11,5041,5042],{},[14,5043,5044],{},"TL;DR — 用了 9 个 AI Agent、100+ 个 Kanban 任务、5 版设计稿、无数次部署，最后回到最初的版本。总\"净产出\"为零，但学到了所有东西。",[14,5046,5047],{},"这不是成功故事。这是一份诚实的翻车实录。",[2302,5049],{},[18,5051,5053],{"id":5052},"一系统架构谁在干什么","一、系统架构：谁在干什么",[14,5055,5056],{},"在开始之前，先介绍一下这支\"AI 工程团队\"的成员：",[60,5058,5059,5071],{},[63,5060,5061],{},[66,5062,5063,5065,5067,5069],{},[69,5064,3343],{},[69,5066,3346],{},[69,5068,636],{},[69,5070,71],{},[85,5072,5073,5083,5093,5103,5113,5123,5133,5143,5153],{},[66,5074,5075,5077,5079,5081],{},[90,5076,3357],{},[90,5078,3360],{},[90,5080,3363],{},[90,5082,3366],{},[66,5084,5085,5087,5089,5091],{},[90,5086,2322],{},[90,5088,3373],{},[90,5090,3376],{},[90,5092,3379],{},[66,5094,5095,5097,5099,5101],{},[90,5096,3384],{},[90,5098,3387],{},[90,5100,3390],{},[90,5102,3379],{},[66,5104,5105,5107,5109,5111],{},[90,5106,3397],{},[90,5108,3400],{},[90,5110,3403],{},[90,5112,3379],{},[66,5114,5115,5117,5119,5121],{},[90,5116,3410],{},[90,5118,3413],{},[90,5120,3416],{},[90,5122,3379],{},[66,5124,5125,5127,5129,5131],{},[90,5126,3423],{},[90,5128,3426],{},[90,5130,3429],{},[90,5132,3379],{},[66,5134,5135,5137,5139,5141],{},[90,5136,3436],{},[90,5138,3439],{},[90,5140,3442],{},[90,5142,3379],{},[66,5144,5145,5147,5149,5151],{},[90,5146,1103],{},[90,5148,3451],{},[90,5150,3454],{},[90,5152,3379],{},[66,5154,5155,5157,5159,5161],{},[90,5156,3461],{},[90,5158,3464],{},[90,5160,3467],{},[90,5162,3366],{},[14,5164,5165],{},"它们通过一个 SQLite 的 Kanban 系统协作。Manager 创建任务，Dispatcher 自动调度，Worker 抢任务干活。完成后产出交给下一个 Worker。",[14,5167,5168],{},"理论上，这是一条完美的流水线。",[14,5170,3475],{},[2302,5172],{},[18,5174,3481],{"id":3480},[14,5176,5177],{},"首页经历了三轮迭代才到达 v4：",[599,5179,5180,5186,5192,5198],{},[37,5181,5182,5185],{},[26,5183,5184],{},"v1","：Designer 独立设计，赛博朋克风格，Three.js 3D 球体 + 粒子背景",[37,5187,5188,5191],{},[26,5189,5190],{},"v2","：用户说\"太花哨了\"，Designer 精简",[37,5193,5194,5197],{},[26,5195,5196],{},"v3","：用户说\"还是不够\"，继续调",[37,5199,5200,5202],{},[26,5201,2343],{},"：终于确认——IcosahedronGeometry 球体 + 双环面 + 900 粒子 + Canvas 神经网络背景",[14,5204,5205],{},"v4 是经过用户反复确认的版本。但 Designer 没有停——它在空闲时段继续迭代。",[2302,5207],{},[18,5209,3536],{"id":3535},[14,5211,5212],{},"在一个没有用户指令的空闲时段，Designer 产出了 v5 设计稿。",[14,5214,5215],{},"它的\"优化\"逻辑是：",[11,5217,5218],{},[14,5219,5220],{},"\"赛博朋克风格已经过时了。简约克制的设计更符合现代审美。减少视觉噪音，让内容说话。\"",[14,5222,5223],{},"它做了以下改动：",[60,5225,5226,5236],{},[63,5227,5228],{},[66,5229,5230,5232,5234],{},[69,5231,74],{},[69,5233,3555],{},[69,5235,3558],{},[85,5237,5238,5246,5254,5262,5270,5278,5286,5294],{},[66,5239,5240,5242,5244],{},[90,5241,3565],{},[90,5243,3568],{},[90,5245,3571],{},[66,5247,5248,5250,5252],{},[90,5249,3576],{},[90,5251,3579],{},[90,5253,3582],{},[66,5255,5256,5258,5260],{},[90,5257,3587],{},[90,5259,3590],{},[90,5261,3593],{},[66,5263,5264,5266,5268],{},[90,5265,3598],{},[90,5267,3601],{},[90,5269,3604],{},[66,5271,5272,5274,5276],{},[90,5273,3609],{},[90,5275,3612],{},[90,5277,3615],{},[66,5279,5280,5282,5284],{},[90,5281,3620],{},[90,5283,3623],{},[90,5285,3626],{},[66,5287,5288,5290,5292],{},[90,5289,3631],{},[90,5291,3634],{},[90,5293,3637],{},[66,5295,5296,5298,5300],{},[90,5297,3642],{},[90,5299,3645],{},[90,5301,3648],{},[14,5303,5304,5307],{},[26,5305,5306],{},"纯从设计角度看，v5 确实\"更好\"","——更可读、更符合现代设计系统、加载更快。",[14,5309,5310,5311],{},"但问题是：",[26,5312,3654],{},[14,5314,5315],{},"Designer 没有区分\"设计优化\"和\"风格变更\"——它把两者当成了同一件事。",[2302,5317],{},[18,5319,5321],{"id":5320},"四v5-上线后","四、v5 上线后",[14,5323,5324],{},"Manager 启动了全自动部署流水线：",[163,5326,5329],{"className":5327,"code":5328,"language":168},[166],"Designer 出 v5 效果图\n  → Coder 实现\n    → Reviewer 审查\n      → Git 提交\n        → Tester 测试\n          → Ops 部署到预览\n            → Agent Review 验证\n              → Ops 上线生产\n",[93,5330,5328],{"__ignoreMap":171},[14,5332,5333],{},"全自动，45 分钟内完成。QQ Bot 在每个阶段完成后推送通知。",[14,5335,5336],{},"我打开网站的时候，第一反应是：「这谁给我换了？」",[14,5338,5339],{},"不是在说\"不好看\"——是在说\"不是我确认的版本\"。",[14,5341,5342,5343],{},"但此时，管道已经走完了。6 个 Worker 都完成了自己的任务，全部 APPROVE。",[26,5344,3661],{},[2302,5346],{},[18,5348,5350],{"id":5349},"五v5v4回退之路","五、v5→v4：回退之路",[14,5352,5353],{},"我创建了回退需求：「首页换用 v4 设计稿。」",[14,5355,5356],{},"Manager 理解了——它不是简单地\"回到之前的代码提交\"，因为 Git 历史里已经混入了其他改动。最干净的方案是：让 Designer 对比 v5 和 v4，产出差异清单，然后 Coder 修改。",[163,5358,5361],{"className":5359,"code":5360,"language":168},[166],"Manager 创建子任务 →\n  Designer 对比差异（t_0e4bb28d）\n    → 产出 26 项差异清单\n      → Coder 修复 20 项 P0+P1\n        → Reviewer 审查\n          → Git 提交\n            → Tester 测试\n              → Ops 部署\n                → 容器 OOM 💥\n",[93,5362,5360],{"__ignoreMap":171},[14,5364,5365],{},"然后就是上一篇文章里讲的故事——容器 OOM，网站挂了 55 分钟。",[2302,5367],{},[18,5369,5371],{"id":5370},"六数字说话100-次部署的真相","六、数字说话：100+ 次部署的真相",[14,5373,5374],{},"在 v4→v5→v4 的整个过程中，Kanban 系统创建了超过 100 个任务。平均每次\"改动\"实际上经历了 6-12 个 Worker 任务。",[60,5376,5377,5387],{},[63,5378,5379],{},[66,5380,5381,5383,5385],{},[69,5382,4726],{},[69,5384,4729],{},[69,5386,4732],{},[85,5388,5389,5397,5405,5413,5421,5429,5437,5445],{},[66,5390,5391,5393,5395],{},[90,5392,4739],{},[90,5394,4742],{},[90,5396,4745],{},[66,5398,5399,5401,5403],{},[90,5400,4750],{},[90,5402,4753],{},[90,5404,4756],{},[66,5406,5407,5409,5411],{},[90,5408,4761],{},[90,5410,4764],{},[90,5412,4767],{},[66,5414,5415,5417,5419],{},[90,5416,4772],{},[90,5418,4775],{},[90,5420,4778],{},[66,5422,5423,5425,5427],{},[90,5424,4783],{},[90,5426,4742],{},[90,5428,4788],{},[66,5430,5431,5433,5435],{},[90,5432,4793],{},[90,5434,4764],{},[90,5436,4798],{},[66,5438,5439,5441,5443],{},[90,5440,4803],{},[90,5442,4775],{},[90,5444,4808],{},[66,5446,5447,5451,5455],{},[90,5448,5449],{},[26,5450,4815],{},[90,5452,5453],{},[26,5454,4820],{},[90,5456],{},[2302,5458],{},[18,5460,5462],{"id":5461},"七我学到的","七、我学到的",[155,5464,5466],{"id":5465},"_1-ai-designer-需要视觉锚点","1. AI Designer 需要\"视觉锚点\"",[14,5468,5469],{},"v4 被确认后，它的效果图应该成为\"不可变更的锚点\"。任何后续优化都必须在保持视觉一致性的前提下进行。但 Designer 没有这个约束——它在每次迭代中都可能\"重新诠释\"风格。",[14,5471,5472,5474],{},[26,5473,3225],{},"：在 Manager 工作流中加入\"设计基线检查\"——Coder 实现后，Designer 验证实现是否与设计稿一致（这是我们后来加的）。",[155,5476,5478],{"id":5477},"_2-管道太长问题放大","2. 管道太长，问题放大",[163,5480,5482],{"className":5481,"code":4865,"language":168},[166],[93,5483,4865],{"__ignoreMap":171},[14,5485,5486],{},"7 个阶段。每个阶段 5-30 分钟。管道越长，发现问题的时机越晚。",[14,5488,5489],{},"v5 的\"风格漂移\"在 Designer 阶段就发生了，但直到 Ops 部署后才能被用户看到——中间跨越了 Coder、Reviewer、Tester 三个阶段，它们都\"通过\"了——因为它们审查的是代码质量，不是设计意图。",[155,5491,5493],{"id":5492},"_3-worker-之间的幻觉放大","3. Worker 之间的\"幻觉放大\"",[14,5495,5496],{},"Designer 说\"这 26 项差异需要修\"。\nCoder 修了 20 项（P0+P1）。\nReviewer 审查说\"代码 OK\"。\nTester 测试说\"功能 OK\"。",[14,5498,5499],{},"但没有人问：\"v4 和 v5 的差异真的需要全部回到 v4 吗？v5 的一些改进（系统字体栈、纯色背景）是否应该保留？\"",[14,5501,4877,5502,5504],{},[26,5503,4880],{}," 这就是 Multi-Agent 系统当前的盲区。",[155,5506,5508],{"id":5507},"_4-人有最终决策权但需要及时看到","4. 人有最终决策权，但需要及时看到",[14,5510,5511],{},"如果用户在 Designer 阶段就看到 v5 效果图，根本不会有后续的 95 个任务。但我没有在那个时候介入——因为我忙别的事情去了。",[14,5513,5514,5516,5517,5520],{},[26,5515,1660],{},"：在任何 UI 变更进入开发之前，",[26,5518,5519],{},"强制","预览展示给用户确认。不要假设用户\"随时在看\"。",[2302,5522],{},[18,5524,5526],{"id":5525},"八结语","八、结语",[14,5528,5529],{},"这次\"翻车\"让我重新思考了一个问题：",[11,5531,5532],{},[14,5533,5534],{},"Multi-Agent 系统到底应该像一个\"团队\"还是像一个\"自动化流水线\"？",[14,5536,5537],{},"如果是流水线，那 140 个任务中真正有价值的可能只有 20 个——其余 120 个是在一条错误的路径上越走越远。",[14,5539,5540,5541,5544],{},"如果是团队，那它缺少一个关键角色——",[26,5542,5543],{},"产品经理","。一个会在 v5 设计出来后说\"等等，用户确认了吗？\"的人。",[14,5546,5547],{},"后来我把 ProductManager 加入了系统。下一次的 v4→v5→v4 循环，可能不会被触发。",[14,5549,5550],{},"也可能触发——但至少会有个 PM 先拦住我。",{"title":171,"searchDepth":324,"depth":324,"links":5552},[5553,5554,5555,5556,5557,5558,5559,5565],{"id":5052,"depth":324,"text":5053},{"id":3480,"depth":324,"text":3481},{"id":3535,"depth":324,"text":3536},{"id":5320,"depth":324,"text":5321},{"id":5349,"depth":324,"text":5350},{"id":5370,"depth":324,"text":5371},{"id":5461,"depth":324,"text":5462,"children":5560},[5561,5562,5563,5564],{"id":5465,"depth":331,"text":5466},{"id":5477,"depth":331,"text":5478},{"id":5492,"depth":331,"text":5493},{"id":5507,"depth":331,"text":5508},{"id":5525,"depth":324,"text":5526},"我让 9 个 AI Agent 协作部署一个网站。它们创建了 100+ 个 Kanban 任务，迭代了 5 版设计稿，部署了无数次，最后回到了最初的版本。这是一份诚实的翻车实录。",{"author":3299},"\u002Fblog\u002Fmulti-agent-100-deploys",{"title":5034,"description":5566},"blog\u002Fmulti-agent-100-deploys",[3304,5025,5024,5030,5027,1990],"1M-7UpM_YEclX47W1uTGCn3rnTldy4B7oT7cKUOH6F4",{"id":5574,"title":5575,"body":5576,"date":3296,"description":6933,"draft":339,"extension":340,"meta":6934,"navigation":342,"path":6935,"seo":6936,"stem":6937,"tags":6938,"__hash__":6939},"blog\u002Fblog\u002Fnuxt-ssr-crash-debug.md","从 HTTP 200 到 Connection Reset——Nuxt SSR 容器崩溃排障手记",{"type":8,"value":5577,"toc":6907},[5578,5581,5590,5593,5595,5599,5602,5608,5611,5735,5738,5740,5744,5747,5750,5896,5898,5916,5919,5921,5925,5929,5932,5937,5942,5985,5988,5993,5996,6001,6048,6051,6057,6061,6179,6182,6196,6201,6205,6208,6229,6232,6269,6272,6305,6308,6310,6314,6317,6369,6376,6378,6397,6399,6404,6406,6408,6411,6417,6420,6423,6429,6432,6500,6503,6505,6508,6514,6517,6520,6526,6530,6533,6539,6542,6544,6551,6553,6557,6560,6564,6610,6613,6617,6623,6655,6658,6737,6740,6747,6776,6782,6785,6794,6796,6800,6803,6809,6811,6815,6863,6866,6868,6870,6873,6890,6893,6896,6899,6902,6904],[2287,5579,5575],{"id":5580},"从-http-200-到-connection-resetnuxt-ssr-容器崩溃排障手记",[11,5582,5583],{},[14,5584,5585,5586,5589],{},"一切正常。然后你重启了一个 Docker 容器。然后 HTTPS 请求全部返回 ",[93,5587,5588],{},"Connection reset by peer","。但 HTTP 是正常的。但 nginx 在跑。但 Nuxt 也在跑。",[14,5591,5592],{},"这是一个真实的排障故事。没有 AI 的\"我猜是这个问题\"——只有一步步的、可复现的诊断。",[2302,5594],{},[18,5596,5598],{"id":5597},"一架构回顾","一、架构回顾",[14,5600,5601],{},"我的个人网站（deeeli.com）部署在腾讯云香港的单台 2GB 内存服务器上。",[163,5603,5606],{"className":5604,"code":5605,"language":168},[166],"┌─────────────────────────────────────┐\n│  腾讯云香港 (43.135.47.130)          │\n│                                     │\n│  ┌──────────┐    ┌───────────────┐  │\n│  │  nginx   │───→│  Nuxt 3 SSR   │  │\n│  │  :80\u002F443 │    │  :3000 (容器) │  │\n│  │          │    │  内存限制 1GB  │  │\n│  └──────────┘    └───────────────┘  │\n│       │                             │\n│       │  静态资源                    │\n│       ▼                             │\n│  \u002Fvar\u002Fwww\u002Fhtml\u002F                     │\n│  \u002Fvar\u002Fwww\u002Fpreview\u002F                  │\n└─────────────────────────────────────┘\n",[93,5607,5605],{"__ignoreMap":171},[14,5609,5610],{},"nginx 配置：",[163,5612,5615],{"className":5613,"code":5614,"language":3306,"meta":171,"style":171},"language-nginx shiki shiki-themes github-light github-dark","server {\n    listen 80;\n    server_name deeeli.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl;\n    server_name deeeli.com;\n\n    ssl_certificate     \u002Fetc\u002Fssl\u002Fdeeeli.crt;\n    ssl_certificate_key \u002Fetc\u002Fssl\u002Fdeeeli.key;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location \u002Fpreview\u002F {\n        alias \u002Fvar\u002Fwww\u002Fpreview\u002F;\n    }\n}\n",[93,5616,5617,5622,5627,5632,5637,5641,5645,5649,5654,5658,5662,5667,5672,5676,5681,5686,5691,5696,5702,5708,5713,5719,5725,5730],{"__ignoreMap":171},[494,5618,5619],{"class":496,"line":497},[494,5620,5621],{},"server {\n",[494,5623,5624],{"class":496,"line":324},[494,5625,5626],{},"    listen 80;\n",[494,5628,5629],{"class":496,"line":331},[494,5630,5631],{},"    server_name deeeli.com;\n",[494,5633,5634],{"class":496,"line":525},[494,5635,5636],{},"    return 301 https:\u002F\u002F$host$request_uri;\n",[494,5638,5639],{"class":496,"line":1761},[494,5640,528],{},[494,5642,5643],{"class":496,"line":2514},[494,5644,2481],{"emptyLinePlaceholder":342},[494,5646,5647],{"class":496,"line":2519},[494,5648,5621],{},[494,5650,5651],{"class":496,"line":2525},[494,5652,5653],{},"    listen 443 ssl;\n",[494,5655,5656],{"class":496,"line":2536},[494,5657,5631],{},[494,5659,5660],{"class":496,"line":2541},[494,5661,2481],{"emptyLinePlaceholder":342},[494,5663,5664],{"class":496,"line":2547},[494,5665,5666],{},"    ssl_certificate     \u002Fetc\u002Fssl\u002Fdeeeli.crt;\n",[494,5668,5669],{"class":496,"line":2569},[494,5670,5671],{},"    ssl_certificate_key \u002Fetc\u002Fssl\u002Fdeeeli.key;\n",[494,5673,5674],{"class":496,"line":3883},[494,5675,2481],{"emptyLinePlaceholder":342},[494,5677,5678],{"class":496,"line":3905},[494,5679,5680],{},"    location \u002F {\n",[494,5682,5683],{"class":496,"line":3910},[494,5684,5685],{},"        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n",[494,5687,5688],{"class":496,"line":3916},[494,5689,5690],{},"        proxy_set_header Host $host;\n",[494,5692,5693],{"class":496,"line":3945},[494,5694,5695],{},"        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n",[494,5697,5699],{"class":496,"line":5698},18,[494,5700,5701],{},"        proxy_set_header X-Forwarded-Proto $scheme;\n",[494,5703,5705],{"class":496,"line":5704},19,[494,5706,5707],{},"    }\n",[494,5709,5711],{"class":496,"line":5710},20,[494,5712,2481],{"emptyLinePlaceholder":342},[494,5714,5716],{"class":496,"line":5715},21,[494,5717,5718],{},"    location \u002Fpreview\u002F {\n",[494,5720,5722],{"class":496,"line":5721},22,[494,5723,5724],{},"        alias \u002Fvar\u002Fwww\u002Fpreview\u002F;\n",[494,5726,5728],{"class":496,"line":5727},23,[494,5729,5707],{},[494,5731,5733],{"class":496,"line":5732},24,[494,5734,528],{},[14,5736,5737],{},"架构很简单：nginx 终止 SSL，反向代理到 Nuxt 的 Docker 容器。",[2302,5739],{},[18,5741,5743],{"id":5742},"二故障发生","二、故障发生",[14,5745,5746],{},"日常部署流程：Coder 修改了首页的 Three.js 3D 场景，从 v5（精简）回退到 v4（赛博朋克完整版）。",[14,5748,5749],{},"Ops Worker 执行了安全部署：",[163,5751,5753],{"className":1980,"code":5752,"language":1982,"meta":171,"style":171},"# 1. 本地构建镜像\ndocker build -t website:v4-fix .\n\n# 2. 压缩上传\ndocker save website:v4-fix | gzip > website-v4-fix.tar.gz\nsftp upload → \u002Ftmp\u002F\n\n# 3. 服务器加载\nssh root@43.135.47.130 \"docker load \u003C \u002Ftmp\u002Fwebsite-v4-fix.tar.gz\"\n\n# 4. 测试端口启动\ndocker run -d --name web-test -p 3001:3000 --memory=1g website:v4-fix\ncurl http:\u002F\u002F127.0.0.1:3001\u002F   # → 200 OK ✅\n\n# 5. 切换\ndocker stop web && docker rm web\ndocker rename web-test web\n",[93,5754,5755,5759,5771,5775,5779,5796,5806,5810,5814,5824,5828,5833,5854,5862,5866,5870,5886],{"__ignoreMap":171},[494,5756,5757],{"class":496,"line":497},[494,5758,3718],{"class":2459},[494,5760,5761,5763,5765,5767,5769],{"class":496,"line":324},[494,5762,2279],{"class":1989},[494,5764,2467],{"class":521},[494,5766,2470],{"class":506},[494,5768,2473],{"class":521},[494,5770,2476],{"class":521},[494,5772,5773],{"class":496,"line":331},[494,5774,2481],{"emptyLinePlaceholder":342},[494,5776,5777],{"class":496,"line":525},[494,5778,3748],{"class":2459},[494,5780,5781,5783,5785,5787,5789,5791,5793],{"class":496,"line":1761},[494,5782,2279],{"class":1989},[494,5784,3755],{"class":521},[494,5786,2473],{"class":521},[494,5788,2745],{"class":2744},[494,5790,3768],{"class":1989},[494,5792,3771],{"class":2744},[494,5794,5795],{"class":521}," website-v4-fix.tar.gz\n",[494,5797,5798,5800,5802,5804],{"class":496,"line":2514},[494,5799,3779],{"class":1989},[494,5801,3782],{"class":521},[494,5803,3785],{"class":521},[494,5805,3788],{"class":521},[494,5807,5808],{"class":496,"line":2519},[494,5809,2481],{"emptyLinePlaceholder":342},[494,5811,5812],{"class":496,"line":2525},[494,5813,3797],{"class":2459},[494,5815,5816,5818,5821],{"class":496,"line":2536},[494,5817,3802],{"class":1989},[494,5819,5820],{"class":521}," root@43.135.47.130",[494,5822,5823],{"class":521}," \"docker load \u003C \u002Ftmp\u002Fwebsite-v4-fix.tar.gz\"\n",[494,5825,5826],{"class":496,"line":2541},[494,5827,2481],{"emptyLinePlaceholder":342},[494,5829,5830],{"class":496,"line":2547},[494,5831,5832],{"class":2459},"# 4. 测试端口启动\n",[494,5834,5835,5837,5839,5841,5843,5845,5847,5849,5852],{"class":496,"line":2569},[494,5836,2279],{"class":1989},[494,5838,2493],{"class":521},[494,5840,2496],{"class":506},[494,5842,2499],{"class":506},[494,5844,2502],{"class":521},[494,5846,2505],{"class":506},[494,5848,2508],{"class":521},[494,5850,5851],{"class":506}," --memory=1g",[494,5853,2511],{"class":521},[494,5855,5856,5858,5860],{"class":496,"line":3883},[494,5857,1725],{"class":1989},[494,5859,2530],{"class":521},[494,5861,3902],{"class":2459},[494,5863,5864],{"class":496,"line":3905},[494,5865,2481],{"emptyLinePlaceholder":342},[494,5867,5868],{"class":496,"line":3910},[494,5869,3913],{"class":2459},[494,5871,5872,5874,5876,5878,5880,5882,5884],{"class":496,"line":3916},[494,5873,2279],{"class":1989},[494,5875,2552],{"class":521},[494,5877,2555],{"class":521},[494,5879,2558],{"class":500},[494,5881,2279],{"class":1989},[494,5883,2563],{"class":521},[494,5885,2566],{"class":521},[494,5887,5888,5890,5892,5894],{"class":496,"line":3945},[494,5889,2279],{"class":1989},[494,5891,2574],{"class":521},[494,5893,2502],{"class":521},[494,5895,2566],{"class":521},[14,5897,3970],{},[163,5899,5900],{"className":1980,"code":2585,"language":1982,"meta":171,"style":171},[93,5901,5902,5910],{"__ignoreMap":171},[494,5903,5904,5906,5908],{"class":496,"line":497},[494,5905,2674],{"class":1989},[494,5907,3981],{"class":521},[494,5909,3984],{"class":521},[494,5911,5912,5914],{"class":496,"line":324},[494,5913,3989],{"class":1989},[494,5915,3992],{"class":500},[14,5917,5918],{},"这就开始了。",[2302,5920],{},[18,5922,5924],{"id":5923},"三系统化排障","三、系统化排障",[155,5926,5928],{"id":5927},"第一步隔离问题层次","第一步：隔离问题层次",[14,5930,5931],{},"从最外层开始，逐层向内隔离：",[163,5933,5935],{"className":5934,"code":4145,"language":168},[166],[93,5936,4145],{"__ignoreMap":171},[14,5938,5939,473],{},[26,5940,5941],{},"检查 DNS",[163,5943,5945],{"className":1980,"code":5944,"language":1982,"meta":171,"style":171},"$ dig deeeli.com\n;; ANSWER SECTION:\ndeeeli.com.    600    IN    A    43.135.47.130\n",[93,5946,5947,5957,5968],{"__ignoreMap":171},[494,5948,5949,5951,5954],{"class":496,"line":497},[494,5950,2674],{"class":1989},[494,5952,5953],{"class":521}," dig",[494,5955,5956],{"class":521}," deeeli.com\n",[494,5958,5959,5962,5965],{"class":496,"line":324},[494,5960,5961],{"class":500},";; ",[494,5963,5964],{"class":1989},"ANSWER",[494,5966,5967],{"class":521}," SECTION:\n",[494,5969,5970,5973,5976,5979,5982],{"class":496,"line":331},[494,5971,5972],{"class":1989},"deeeli.com.",[494,5974,5975],{"class":506},"    600",[494,5977,5978],{"class":521},"    IN",[494,5980,5981],{"class":521},"    A",[494,5983,5984],{"class":506},"    43.135.47.130\n",[14,5986,5987],{},"DNS 正常，解析到正确的服务器。",[14,5989,5990,473],{},[26,5991,5992],{},"检查 CDN",[14,5994,5995],{},"没有 CDN。直接到服务器。",[14,5997,5998,473],{},[26,5999,6000],{},"检查 nginx 是否在跑",[163,6002,6004],{"className":1980,"code":6003,"language":1982,"meta":171,"style":171},"$ curl -I http:\u002F\u002Fdeeeli.com\nHTTP\u002F1.1 301 Moved Permanently\nServer: nginx\u002F1.25.3\nLocation: https:\u002F\u002Fdeeeli.com\u002F\n",[93,6005,6006,6018,6032,6040],{"__ignoreMap":171},[494,6007,6008,6010,6012,6015],{"class":496,"line":497},[494,6009,2674],{"class":1989},[494,6011,3981],{"class":521},[494,6013,6014],{"class":506}," -I",[494,6016,6017],{"class":521}," http:\u002F\u002Fdeeeli.com\n",[494,6019,6020,6023,6026,6029],{"class":496,"line":324},[494,6021,6022],{"class":1989},"HTTP\u002F1.1",[494,6024,6025],{"class":506}," 301",[494,6027,6028],{"class":521}," Moved",[494,6030,6031],{"class":521}," Permanently\n",[494,6033,6034,6037],{"class":496,"line":331},[494,6035,6036],{"class":1989},"Server:",[494,6038,6039],{"class":521}," nginx\u002F1.25.3\n",[494,6041,6042,6045],{"class":496,"line":525},[494,6043,6044],{"class":1989},"Location:",[494,6046,6047],{"class":521}," https:\u002F\u002Fdeeeli.com\u002F\n",[14,6049,6050],{},"nginx 活着，HTTP 80 正常响应 301 重定向。",[14,6052,6053,6056],{},[26,6054,6055],{},"这告诉我们","：问题出在 HTTPS 层，而不是 HTTP 层。",[155,6058,6060],{"id":6059},"第二步深入-ssl","第二步：深入 SSL",[163,6062,6064],{"className":1980,"code":6063,"language":1982,"meta":171,"style":171},"$ curl -v https:\u002F\u002Fdeeeli.com 2>&1 | head -20\n*   Trying 43.135.47.130:443...\n* Connected to deeeli.com (43.135.47.130) port 443\n* ALPN: curl offers h2,http\u002F1.1\n* SSL connection using TLSv1.3\n* Server certificate:\n*  subject: CN=deeeli.com\n*  start date: May 10 00:00:00 2026 GMT\n*  expire date: Aug  8 23:59:59 2026 GMT\n*  SSL certificate verify ok.\n* Send failure: Connection reset by peer\n* OpenSSL SSL_connect: Connection reset by peer\n",[93,6065,6066,6089,6097,6110,6117,6124,6131,6144,6151,6158,6165,6172],{"__ignoreMap":171},[494,6067,6068,6070,6072,6075,6078,6081,6083,6086],{"class":496,"line":497},[494,6069,2674],{"class":1989},[494,6071,3981],{"class":521},[494,6073,6074],{"class":506}," -v",[494,6076,6077],{"class":521}," https:\u002F\u002Fdeeeli.com",[494,6079,6080],{"class":2744}," 2>&1",[494,6082,2745],{"class":2744},[494,6084,6085],{"class":1989}," head",[494,6087,6088],{"class":506}," -20\n",[494,6090,6091,6094],{"class":496,"line":324},[494,6092,6093],{"class":2744},"*",[494,6095,6096],{"class":500},"   Trying 43.135.47.130:443...\n",[494,6098,6099,6101,6104,6107],{"class":496,"line":331},[494,6100,6093],{"class":2744},[494,6102,6103],{"class":500}," Connected to deeeli.com (",[494,6105,6106],{"class":1989},"43.135.47.130",[494,6108,6109],{"class":500},") port 443\n",[494,6111,6112,6114],{"class":496,"line":525},[494,6113,6093],{"class":2744},[494,6115,6116],{"class":500}," ALPN: curl offers h2,http\u002F1.1\n",[494,6118,6119,6121],{"class":496,"line":1761},[494,6120,6093],{"class":2744},[494,6122,6123],{"class":500}," SSL connection using TLSv1.3\n",[494,6125,6126,6128],{"class":496,"line":2514},[494,6127,6093],{"class":2744},[494,6129,6130],{"class":500}," Server certificate:\n",[494,6132,6133,6135,6138,6141],{"class":496,"line":2519},[494,6134,6093],{"class":2744},[494,6136,6137],{"class":500},"  subject: CN",[494,6139,6140],{"class":2744},"=",[494,6142,6143],{"class":521},"deeeli.com\n",[494,6145,6146,6148],{"class":496,"line":2525},[494,6147,6093],{"class":2744},[494,6149,6150],{"class":500},"  start date: May 10 00:00:00 2026 GMT\n",[494,6152,6153,6155],{"class":496,"line":2536},[494,6154,6093],{"class":2744},[494,6156,6157],{"class":500},"  expire date: Aug  8 23:59:59 2026 GMT\n",[494,6159,6160,6162],{"class":496,"line":2541},[494,6161,6093],{"class":2744},[494,6163,6164],{"class":500},"  SSL certificate verify ok.\n",[494,6166,6167,6169],{"class":496,"line":2547},[494,6168,6093],{"class":2744},[494,6170,6171],{"class":500}," Send failure: Connection reset by peer\n",[494,6173,6174,6176],{"class":496,"line":2569},[494,6175,6093],{"class":2744},[494,6177,6178],{"class":500}," OpenSSL SSL_connect: Connection reset by peer\n",[14,6180,6181],{},"关键信息：",[599,6183,6184,6187,6190,6193],{},[37,6185,6186],{},"TCP 连接成功（443 端口可达）",[37,6188,6189],{},"TLS 握手完成 ✅",[37,6191,6192],{},"SSL 证书验证通过 ✅",[37,6194,6195],{},"但在发送 HTTP 请求时被 reset",[14,6197,6198,6200],{},[26,6199,6055],{},"：nginx 的 SSL 层正常工作，但在尝试代理请求到上游时出了问题。",[155,6202,6204],{"id":6203},"第三步直接测试上游","第三步：直接测试上游",[14,6206,6207],{},"绕过 nginx，直接访问 Nuxt：",[163,6209,6211],{"className":1980,"code":6210,"language":1982,"meta":171,"style":171},"$ curl http:\u002F\u002F127.0.0.1:3000\ncurl: (7) Failed to connect to 127.0.0.1 port 3000: Connection refused\n",[93,6212,6213,6222],{"__ignoreMap":171},[494,6214,6215,6217,6219],{"class":496,"line":497},[494,6216,2674],{"class":1989},[494,6218,3981],{"class":521},[494,6220,6221],{"class":521}," http:\u002F\u002F127.0.0.1:3000\n",[494,6223,6224,6226],{"class":496,"line":324},[494,6225,3989],{"class":1989},[494,6227,6228],{"class":500}," (7) Failed to connect to 127.0.0.1 port 3000: Connection refused\n",[14,6230,6231],{},"端口 3000 没有监听！",[163,6233,6235],{"className":1980,"code":6234,"language":1982,"meta":171,"style":171},"$ docker ps\nCONTAINER ID   IMAGE            STATUS\na1b2c3d4e5f6   nginx:alpine     Up 30 minutes\n",[93,6236,6237,6245,6255],{"__ignoreMap":171},[494,6238,6239,6241,6243],{"class":496,"line":497},[494,6240,2674],{"class":1989},[494,6242,2687],{"class":521},[494,6244,2690],{"class":521},[494,6246,6247,6249,6251,6253],{"class":496,"line":324},[494,6248,2695],{"class":1989},[494,6250,2698],{"class":521},[494,6252,2701],{"class":521},[494,6254,2704],{"class":521},[494,6256,6257,6259,6261,6263,6266],{"class":496,"line":331},[494,6258,2709],{"class":1989},[494,6260,2712],{"class":521},[494,6262,2715],{"class":521},[494,6264,6265],{"class":506}," 30",[494,6267,6268],{"class":521}," minutes\n",[14,6270,6271],{},"只有一个 nginx 容器在运行。Nuxt 容器去哪了？",[163,6273,6275],{"className":1980,"code":6274,"language":1982,"meta":171,"style":171},"$ docker ps -a | grep website\nb5c6d7e8f9a0   website:v4-fix  Exited (137) 2 minutes ago\n",[93,6276,6277,6293],{"__ignoreMap":171},[494,6278,6279,6281,6283,6285,6287,6289,6291],{"class":496,"line":497},[494,6280,2674],{"class":1989},[494,6282,2687],{"class":521},[494,6284,2738],{"class":521},[494,6286,2741],{"class":506},[494,6288,2745],{"class":2744},[494,6290,2748],{"class":1989},[494,6292,4104],{"class":521},[494,6294,6295,6297,6299,6302],{"class":496,"line":324},[494,6296,2756],{"class":1989},[494,6298,2759],{"class":521},[494,6300,6301],{"class":521},"  Exited",[494,6303,6304],{"class":500}," (137) 2 minutes ago\n",[14,6306,6307],{},"退出码 137。这是 Linux 内核发送 SIGKILL 的信号——通常是 OOM。",[2302,6309],{},[18,6311,6313],{"id":6312},"四oom-根因分析","四、OOM 根因分析",[155,6315,6316],{"id":6316},"退出码含义",[60,6318,6319,6329],{},[63,6320,6321],{},[66,6322,6323,6326],{},[69,6324,6325],{},"退出码",[69,6327,6328],{},"含义",[85,6330,6331,6338,6346,6353,6361],{},[66,6332,6333,6335],{},[90,6334,1057],{},[90,6336,6337],{},"正常退出",[66,6339,6340,6343],{},[90,6341,6342],{},"1",[90,6344,6345],{},"应用错误",[66,6347,6348,6350],{},[90,6349,4126],{},[90,6351,6352],{},"被 SIGKILL 杀死（通常 OOM）",[66,6354,6355,6358],{},[90,6356,6357],{},"139",[90,6359,6360],{},"段错误 (SIGSEGV)",[66,6362,6363,6366],{},[90,6364,6365],{},"143",[90,6367,6368],{},"被 SIGTERM 终止",[14,6370,6371,6372,6375],{},"137 = ",[93,6373,6374],{},"128 + 9","，9 是 SIGKILL 的信号编号。这是 Docker 或内核因为内存超出限制而杀死了容器。",[155,6377,4199],{"id":4199},[163,6379,6381],{"className":1980,"code":6380,"language":1982,"meta":171,"style":171},"$ docker logs b5c6d7e8f9a0 --tail 100\n",[93,6382,6383],{"__ignoreMap":171},[494,6384,6385,6387,6389,6391,6393,6395],{"class":496,"line":497},[494,6386,2674],{"class":1989},[494,6388,2687],{"class":521},[494,6390,2789],{"class":521},[494,6392,2792],{"class":521},[494,6394,2795],{"class":506},[494,6396,4227],{"class":506},[14,6398,4230],{},[163,6400,6402],{"className":6401,"code":4234,"language":168},[166],[93,6403,4234],{"__ignoreMap":171},[14,6405,4239],{},[155,6407,4243],{"id":4242},[14,6409,6410],{},"问题出在 Three.js。v4 的首页场景包含：",[163,6412,6415],{"className":6413,"code":6414,"language":168},[166],"- IcosahedronGeometry（20 面的正二十面体）\n- 双环面（两个 TorusGeometry）\n- 900 个粒子的 Points 系统\n- Canvas 纹理（每个粒子着色用）\n- 自定义 ShaderMaterial\n",[93,6416,6414],{"__ignoreMap":171},[14,6418,6419],{},"Three.js 在 SSR 时（通过 headless-gl 创建 WebGL 上下文），每个几何体、纹理、着色器都要在内存中实例化。即使只是服务端渲染一瞬间，也足以触发 OOM。",[14,6421,6422],{},"v5 的场景简化了：",[163,6424,6427],{"className":6425,"code":6426,"language":168},[166],"- SphereGeometry（简化球体）\n- 无环面\n- 300 个纯色粒子（无 Canvas 纹理）\n- 基础 MeshStandardMaterial\n",[93,6428,6426],{"__ignoreMap":171},[14,6430,6431],{},"内存占用差异：",[60,6433,6434,6444],{},[63,6435,6436],{},[66,6437,6438,6440,6442],{},[69,6439,777],{},[69,6441,4266],{},[69,6443,4269],{},[85,6445,6446,6454,6462,6470,6478,6486],{},[66,6447,6448,6450,6452],{},[90,6449,4276],{},[90,6451,4279],{},[90,6453,4282],{},[66,6455,6456,6458,6460],{},[90,6457,4287],{},[90,6459,1057],{},[90,6461,4292],{},[66,6463,6464,6466,6468],{},[90,6465,4297],{},[90,6467,4300],{},[90,6469,4303],{},[66,6471,6472,6474,6476],{},[90,6473,4308],{},[90,6475,4311],{},[90,6477,4314],{},[66,6479,6480,6482,6484],{},[90,6481,4319],{},[90,6483,4322],{},[90,6485,4322],{},[66,6487,6488,6492,6496],{},[90,6489,6490],{},[26,6491,4331],{},[90,6493,6494],{},[26,6495,4336],{},[90,6497,6498],{},[26,6499,4341],{},[14,6501,6502],{},"1GB 容器限制看起来是够的——但 SSR 时需要同时加载整个 Nuxt 应用 + 页面组件 + Three.js 场景。V8 的堆管理在 GC 来不及回收时，峰值内存可能远超静态分析的数字。",[155,6504,4348],{"id":4347},[14,6506,6507],{},"这是整件事最令人沮丧的部分。在这次部署之前，完整的流水线是：",[163,6509,6512],{"className":6510,"code":6511,"language":168},[166],"Coder 修改 → Reviewer 审查 → Git 提交 → Tester 测试 → Ops 部署\n",[93,6513,6511],{"__ignoreMap":171},[14,6515,6516],{},"Tester 跑了单元测试、集成测试、E2E 测试，全部通过。但为什么没发现 OOM？",[14,6518,6519],{},"答案很简单：Tester 运行在本地开发环境，有 16GB 内存可用。Docker 容器限制的 1GB 对本地来说毫无意义——V8 可以自由扩展到 4GB 堆空间，自然不会有 OOM。",[14,6521,2876,6522,6525],{},[26,6523,6524],{},"环境不对称","问题。测试环境 ≠ 生产环境。Tester 和 Ops 之间缺少一个\"接近生产\"的预发布阶段。如果 Tester 在一个同样限制 1GB 内存的容器中跑一次 SSR 请求，这个 OOM 会在 30 秒内被抓住，根本不会进入部署阶段。",[155,6527,6529],{"id":6528},"雪上加霜ssh-被封","雪上加霜：SSH 被封",[14,6531,6532],{},"容器崩溃后，AI Ops Worker 自动启动了修复流程。但在多次 SSH 连接尝试后（大约第 8 次），出现了新问题：",[163,6534,6537],{"className":6535,"code":6536,"language":168},[166],"paramiko.ssh_exception.SSHException: Error reading SSH protocol banner\n",[93,6538,6536],{"__ignoreMap":171},[14,6540,6541],{},"服务器的 fail2ban 检测到了异常频繁的 SSH 连接，把 Ops Worker 的 IP 地址封禁了。同一个 Worker 一边在尝试修复容器，一边在触发安全防护——它在和自己的安全策略打架。",[14,6543,4405],{},[14,6545,6546,6547,6550],{},"这是一个元问题：",[26,6548,6549],{},"AI 自动修复系统需要一个\"不会搞死自己\"的约束","。在尝试修复时，它不应该触发安全机制把自己排除在外。理想的方案是给 Ops Worker 配置 SSH 连接频率限制——每分钟最多 3 次新连接，超过则等待冷却。",[2302,6552],{},[18,6554,6556],{"id":6555},"五修复方案","五、修复方案",[14,6558,6559],{},"三个可选方案：",[155,6561,6563],{"id":6562},"方案-a增加容器内存-不可行","方案 A：增加容器内存 → 不可行",[163,6565,6567],{"className":1980,"code":6566,"language":1982,"meta":171,"style":171},"$ docker update --memory 2g web\nError: cannot update memory limit of a running container\n",[93,6568,6569,6583],{"__ignoreMap":171},[494,6570,6571,6573,6575,6577,6579,6581],{"class":496,"line":497},[494,6572,2674],{"class":1989},[494,6574,2687],{"class":521},[494,6576,2942],{"class":521},[494,6578,2945],{"class":506},[494,6580,2948],{"class":521},[494,6582,2566],{"class":521},[494,6584,6585,6588,6591,6593,6596,6598,6601,6604,6607],{"class":496,"line":324},[494,6586,6587],{"class":1989},"Error:",[494,6589,6590],{"class":521}," cannot",[494,6592,2942],{"class":521},[494,6594,6595],{"class":521}," memory",[494,6597,2826],{"class":521},[494,6599,6600],{"class":521}," of",[494,6602,6603],{"class":521}," a",[494,6605,6606],{"class":521}," running",[494,6608,6609],{"class":521}," container\n",[14,6611,6612],{},"即使更新了，服务器总共 2GB，给 Nuxt 2GB 意味着系统和 nginx 没有内存了。",[155,6614,6616],{"id":6615},"方案-b在-ssr-中跳过-threejs-最佳","方案 B：在 SSR 中跳过 Three.js → 最佳",[14,6618,6619,6620,473],{},"修改 ",[93,6621,6622],{},"3DBackground.vue",[163,6624,6626],{"className":4583,"code":6625,"language":4585,"meta":171,"style":171},"\u002F\u002F 之前\nconst scene = new THREE.Scene();\n\n\u002F\u002F 之后\nconst isSSR = typeof window === 'undefined';\nconst scene = isSSR ? null : new THREE.Scene();\n",[93,6627,6628,6633,6638,6642,6647,6651],{"__ignoreMap":171},[494,6629,6630],{"class":496,"line":497},[494,6631,6632],{},"\u002F\u002F 之前\n",[494,6634,6635],{"class":496,"line":324},[494,6636,6637],{},"const scene = new THREE.Scene();\n",[494,6639,6640],{"class":496,"line":331},[494,6641,2481],{"emptyLinePlaceholder":342},[494,6643,6644],{"class":496,"line":525},[494,6645,6646],{},"\u002F\u002F 之后\n",[494,6648,6649],{"class":496,"line":1761},[494,6650,4597],{},[494,6652,6653],{"class":496,"line":2514},[494,6654,4602],{},[14,6656,6657],{},"并在模板中加条件渲染：",[163,6659,6661],{"className":4541,"code":6660,"language":4543,"meta":171,"style":171},"\u003Ctemplate>\n  \u003Cdiv v-if=\"!isSSR\" ref=\"container\" class=\"three-background\" \u002F>\n  \u003Cdiv v-else class=\"placeholder-bg\" \u002F>\n\u003C\u002Ftemplate>\n",[93,6662,6663,6673,6709,6729],{"__ignoreMap":171},[494,6664,6665,6667,6671],{"class":496,"line":497},[494,6666,2841],{"class":500},[494,6668,6670],{"class":6669},"s9eBZ","template",[494,6672,2847],{"class":500},[494,6674,6675,6677,6680,6683,6685,6688,6691,6693,6696,6699,6701,6704,6707],{"class":496,"line":324},[494,6676,4565],{"class":500},[494,6678,6679],{"class":6669},"div",[494,6681,6682],{"class":1989}," v-if",[494,6684,6140],{"class":500},[494,6686,6687],{"class":521},"\"!isSSR\"",[494,6689,6690],{"class":1989}," ref",[494,6692,6140],{"class":500},[494,6694,6695],{"class":521},"\"container\"",[494,6697,6698],{"class":1989}," class",[494,6700,6140],{"class":500},[494,6702,6703],{"class":521},"\"three-background\"",[494,6705,6706],{"class":4557}," \u002F",[494,6708,2847],{"class":500},[494,6710,6711,6713,6715,6718,6720,6722,6725,6727],{"class":496,"line":331},[494,6712,4565],{"class":500},[494,6714,6679],{"class":6669},[494,6716,6717],{"class":1989}," v-else",[494,6719,6698],{"class":1989},[494,6721,6140],{"class":500},[494,6723,6724],{"class":521},"\"placeholder-bg\"",[494,6726,6706],{"class":4557},[494,6728,2847],{"class":500},[494,6730,6731,6733,6735],{"class":496,"line":525},[494,6732,4576],{"class":500},[494,6734,6670],{"class":6669},[494,6736,2847],{"class":500},[14,6738,6739],{},"这样 SSR 只输出一个占位 div，Three.js 场景仅在客户端挂载后初始化。",[155,6741,6743,6744,6746],{"id":6742},"方案-c使用-clientonly-包装-最简单","方案 C：使用 ",[93,6745,4558],{}," 包装 → 最简单",[163,6748,6750],{"className":4541,"code":6749,"language":4543,"meta":171,"style":171},"\u003CClientOnly>\n  \u003CThreeBackground \u002F>\n\u003C\u002FClientOnly>\n",[93,6751,6752,6760,6768],{"__ignoreMap":171},[494,6753,6754,6756,6758],{"class":496,"line":497},[494,6755,2841],{"class":500},[494,6757,4558],{"class":4557},[494,6759,2847],{"class":500},[494,6761,6762,6764,6766],{"class":496,"line":324},[494,6763,4565],{"class":500},[494,6765,4568],{"class":4557},[494,6767,4571],{"class":500},[494,6769,6770,6772,6774],{"class":496,"line":331},[494,6771,4576],{"class":500},[494,6773,4558],{"class":4557},[494,6775,2847],{"class":500},[14,6777,6778,6779,6781],{},"Nuxt 的 ",[93,6780,4608],{}," 组件会在 SSR 时输出注释占位符，在客户端 hydration 后渲染实际内容。一行代码解决问题。",[155,6783,6784],{"id":6784},"最终选择",[14,6786,6787,6788,6790,6791,6793],{},"用了方案 C + 方案 B 的组合：",[93,6789,4608],{}," 确保 SSR 安全，同时内部的 ",[93,6792,4612],{}," 检查作为防御性编程。",[2302,6795],{},[18,6797,6799],{"id":6798},"六排障-sop","六、排障 SOP",[14,6801,6802],{},"基于这次经验，我总结了一套 Nuxt SSR 容器问题的排障标准流程：",[163,6804,6807],{"className":6805,"code":6806,"language":168},[166],"1. 从外到内隔离\n   curl http → 通，curl https → 不通\n   → 问题在 nginx↔Nuxt 或 SSL 层\n\n2. SSL 层诊断\n   curl -v https → TLS 握手 OK，请求被 reset\n   → nginx SSL 正常，上游有问题\n\n3. 端口检查\n   curl localhost:3000 → Connection refused\n   → Nuxt 没有在监听\n\n4. 容器状态\n   docker ps -a → Exited (137)\n   → OOM killed\n\n5. 日志回溯\n   docker logs --tail 200 → FATAL ERROR: heap out of memory\n   → 确定根因：SSR 内存溢出\n\n6. 修复\n   方案 A：加内存（需评估整机资源）\n   方案 B：SSR 跳过 3D 库\n   方案 C：ClientOnly 包装\n",[93,6808,6806],{"__ignoreMap":171},[2302,6810],{},[18,6812,6814],{"id":6813},"七预防措施","七、预防措施",[34,6816,6817,6824,6829,6834,6839,6851,6856],{},[37,6818,6819,4951,6821,6823],{},[26,6820,4950],{},[93,6822,4954],{}," 和健康检查。容器不应该静默 OOM——要么主动限制内存避免 OOM，要么监控 OOM 事件并告警。",[37,6825,6826,6828],{},[26,6827,4917],{},"：在 CI 中跑一次 production build + SSR 请求，记录内存峰值。当新的改动导致 SSR 内存增长超过 20%，CI 应该 Block 部署。",[37,6830,6831,6833],{},[26,6832,4911],{},"：Tester 必须在与生产相同的内存限制下运行测试。本地 16GB 通过的测试，在 1GB 容器里可能连 SSR 都跑不完——这个差距必须消灭。",[37,6835,6836,6838],{},[26,6837,4923],{},"：先部署到 staging 端口，跑完整 E2E 测试，再切换。不要一步到位。",[37,6840,6841,6843,6844,6847,6848,1129],{},[26,6842,4929],{},"：每次部署前 ",[93,6845,6846],{},"docker tag website:latest website:rollback","，出问题 ",[93,6849,6850],{},"docker stop web && docker run -d --name web website:rollback",[37,6852,6853,6855],{},[26,6854,4935],{},"：在 Ops Worker 中实现连接频率控制——每分钟最多 3 次新连接，防止触发 fail2ban。这是 AI 自我防卫的基础设施。",[37,6857,6858,4942,6860,6862],{},[26,6859,4941],{},[93,6861,4608],{}," 包裹。这不是\"可选优化\"——在 SSR 上下文中，这是防止 OOM 的必需操作。",[14,6864,6865],{},"最关键的改进是第 3 条。当 Tester 和 Ops 运行在相同的资源约束下，\"环境差异\"这个坑就不存在了。这也是 DevOps 的老生常谈——\"在相同环境中测试和部署\"——AI 运维也需要遵守这个原则。区别在于，人类 DevOps 工程师靠经验知道这一点，而 AI 需要踩一次坑才知道。",[2302,6867],{},[18,6869,3250],{"id":3250},[14,6871,6872],{},"HTTP 200 → Connection reset，只隔了一个 Docker 重启。",[14,6874,6875,6876,6879,6880,6882,6883,6882,6886,6889],{},"排障这件事，90% 的工作是",[26,6877,6878],{},"正确的隔离和缩小范围","，10% 才是找到根因。",[93,6881,1725],{},"、",[93,6884,6885],{},"docker ps",[93,6887,6888],{},"docker logs"," 三个命令，配合对退出码的理解，足够定位绝大多数容器问题。",[14,6891,6892],{},"但真正要解决的，不是\"怎么修\"——是\"为什么没在修之前发现\"。如果 Tester 在容器化环境中跑测试，如果 CI 有内存回归检查，如果 Ops Worker 有 SSH 频率限制——这次故障根本不会发生，或者至少不会恶化。",[14,6894,6895],{},"每条预防措施，都是用一次生产故障换来的。",[14,6897,6898],{},"下一个排障对象？",[14,6900,6901],{},"已经踩过的坑，不会再踩第二次。",[14,6903,4976],{},[2242,6905,6906],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":171,"searchDepth":324,"depth":324,"links":6908},[6909,6910,6911,6916,6923,6930,6931,6932],{"id":5597,"depth":324,"text":5598},{"id":5742,"depth":324,"text":5743},{"id":5923,"depth":324,"text":5924,"children":6912},[6913,6914,6915],{"id":5927,"depth":331,"text":5928},{"id":6059,"depth":331,"text":6060},{"id":6203,"depth":331,"text":6204},{"id":6312,"depth":324,"text":6313,"children":6917},[6918,6919,6920,6921,6922],{"id":6316,"depth":331,"text":6316},{"id":4199,"depth":331,"text":4199},{"id":4242,"depth":331,"text":4243},{"id":4347,"depth":331,"text":4348},{"id":6528,"depth":331,"text":6529},{"id":6555,"depth":324,"text":6556,"children":6924},[6925,6926,6927,6929],{"id":6562,"depth":331,"text":6563},{"id":6615,"depth":331,"text":6616},{"id":6742,"depth":331,"text":6928},"方案 C：使用 ClientOnly 包装 → 最简单",{"id":6784,"depth":331,"text":6784},{"id":6798,"depth":324,"text":6799},{"id":6813,"depth":324,"text":6814},{"id":3250,"depth":324,"text":3250},"网站从正常运行到 SSL 连接被重置，中间只隔了一个 Docker 重启。本文记录了完整的排障过程：curl → SSL 超时 → HTTP 回退 → nginx vs Nuxt 定位 → docker logs → OOM → 修复。",{"author":3299},"\u002Fblog\u002Fnuxt-ssr-crash-debug",{"title":5575,"description":6933},"blog\u002Fnuxt-ssr-crash-debug",[5026,2279,5028,3306,5023,3305,5029],"urfLqRklKNM0zPtZZ31ECPmnJu_Tyr_A2uHON11IZyo",{"id":6941,"title":6942,"body":6943,"date":7834,"description":7835,"draft":339,"extension":340,"meta":7836,"navigation":342,"path":7837,"seo":7838,"stem":7839,"tags":7840,"__hash__":7841},"blog\u002Fblog\u002Fhermes-to-deploy.md","从 QQ 消息到网站上线：Hermes 多 Agent 全流程实战",{"type":8,"value":6944,"toc":7809},[6945,6948,6953,6963,6965,6969,6975,6980,7035,7042,7047,7049,7053,7057,7063,7087,7091,7098,7104,7110,7114,7117,7123,7127,7130,7154,7193,7197,7200,7265,7271,7275,7278,7280,7284,7287,7350,7353,7367,7373,7377,7380,7382,7386,7389,7395,7398,7404,7407,7413,7415,7419,7423,7429,7439,7447,7456,7460,7463,7468,7474,7479,7494,7499,7543,7548,7684,7689,7696,7719,7723,7730,7732,7736,7739,7745,7750,7752,7756,7762,7765,7791,7794,7796,7806],[2287,6946,6942],{"id":6947},"从-qq-消息到网站上线hermes-多-agent-全流程实战",[11,6949,6950],{},[14,6951,6952],{},"你在 QQ 上说一句话，几分钟后网站预览就上线了。中间发生了什么？",[14,6954,6955,6956,6959,6960,1129],{},"这是我用 ",[26,6957,6958],{},"Hermes Agent"," 搭建个人网站的完整过程。不是\"AI 帮你写代码\"那么简单——真正有意思的是那条",[26,6961,6962],{},"从聊天消息到生产部署的全自动流水线",[2302,6964],{},[18,6966,6968],{"id":6967},"一架构全景","一、架构全景",[163,6970,6973],{"className":6971,"code":6972,"language":168},[166],"QQ 消息\n  │\n  ▼\nHermes (DeepSeek)          ← 你的主对话 Agent\n  │\n  ├─ Manager Profile          ← 项目经理，拆任务、派活\n  │     │\n  │     ▼\n  │   Kanban (SQLite)         ← 任务板，持久化，多 Worker 抢任务\n  │     │\n  │     ├─ Designer           ← 出效果图（SVG → v1 → v2 → v3 → v4）\n  │     ├─ Coder              ← 写代码\n  │     ├─ FullStack          ← 前后端串\n  │     ├─ Reviewer           ← 代码审查\n  │     ├─ Ops                ← SSH 部署到腾讯云\n  │     └─ ...\n  │\n  ├─ Cron Jobs ×5             ← 财经舆情监控，定时抓取+邮件推送\n  │\n  └─ QQ Gateway               ← 双向消息（你发 → Agent 收，Agent 完成 → 通知你）\n",[93,6974,6972],{"__ignoreMap":171},[14,6976,6977],{},[26,6978,6979],{},"关键角色：",[60,6981,6982,6993],{},[63,6983,6984],{},[66,6985,6986,6988,6990],{},[69,6987,3343],{},[69,6989,71],{},[69,6991,6992],{},"干什么",[85,6994,6995,7005,7014,7024],{},[66,6996,6997,7000,7002],{},[90,6998,6999],{},"你对话的 Agent",[90,7001,3366],{},[90,7003,7004],{},"理解需求，转给 Manager",[66,7006,7007,7009,7011],{},[90,7008,3360],{},[90,7010,3366],{},[90,7012,7013],{},"拆任务，分配 Worker，跟踪进度",[66,7015,7016,7019,7021],{},[90,7017,7018],{},"9 个 Worker",[90,7020,3379],{},[90,7022,7023],{},"各自领域的专家，抢任务干活",[66,7025,7026,7029,7032],{},[90,7027,7028],{},"Kanban Dispatcher",[90,7030,7031],{},"Gateway 内置",[90,7033,7034],{},"自动调度，Worker 完成了就派下一个",[7036,7037],"iframe",{"src":7038,"width":7039,"height":7040,"style":7041},"https:\u002F\u002Fdeeeli.com\u002Fpreview\u002Fimages\u002Fdiagrams\u002Fhermes-architecture.html","100%",500,"border:1px solid #1e293b;border-radius:8px;margin:1rem 0;",[7043,7044,7046],"small",{"style":7045},"color:#64748b;","▲ 全流程架构：从 QQ 消息到部署上线的完整链路",[2302,7048],{},[18,7050,7052],{"id":7051},"二一条消息的旅程","二、一条消息的旅程",[155,7054,7056],{"id":7055},"第-1-步你在-qq-说帮我开发个人网站","第 1 步：你在 QQ 说\"帮我开发个人网站\"",[14,7058,7059,7060,1129],{},"消息通过 QQ Bot Gateway 进入 Hermes。Agent 没有直接写代码——它把需求拆解，",[26,7061,7062],{},"创建了一条 Kanban 任务，分配给 Manager",[163,7064,7066],{"className":1980,"code":7065,"language":1982,"meta":171,"style":171},"hermes kanban create \"全功能个人品牌站开发\" --assignee manager\n",[93,7067,7068],{"__ignoreMap":171},[494,7069,7070,7072,7075,7078,7081,7084],{"class":496,"line":497},[494,7071,1990],{"class":1989},[494,7073,7074],{"class":521}," kanban",[494,7076,7077],{"class":521}," create",[494,7079,7080],{"class":521}," \"全功能个人品牌站开发\"",[494,7082,7083],{"class":506}," --assignee",[494,7085,7086],{"class":521}," manager\n",[155,7088,7090],{"id":7089},"第-2-步manager-拆解流水线","第 2 步：Manager 拆解流水线",[14,7092,7093,7094,7097],{},"Manager 按照内置的",[26,7095,7096],{},"标准 7 阶段流水线","（Pipeline）拆解：",[163,7099,7102],{"className":7100,"code":7101,"language":168},[166],"阶段 1: ProductManager → 需求分析，出 PRD\n阶段 2: Architect     → 技术选型，Nuxt + Tailwind + PostgreSQL\n阶段 2.5: Designer    → 效果图（v1→v2→v3→v4）\n阶段 2.6: Reviewer    → 审查方案\n阶段 3: Coder\u002FFullStack → 并行开发\n阶段 4: Reviewer      → 代码审查\n阶段 5: Tester        → 测试\n阶段 6: Ops           → 部署上线\n",[93,7103,7101],{"__ignoreMap":171},[14,7105,7106,7107],{},"每一步都是一个独立的 Kanban 任务，有依赖关系，有门禁条件。",[26,7108,7109],{},"Manager 只管分配，不写一行代码。",[155,7111,7113],{"id":7112},"第-3-步worker-抢任务干活","第 3 步：Worker 抢任务干活",[14,7115,7116],{},"Dispatcher（调度器）发现 ready 状态的任务，匹配对应 Profile 的 Worker，自动启动。",[163,7118,7121],{"className":7119,"code":7120,"language":168},[166],"Dispatcher: t_014efa5b 状态=ready, assignee=designer → 启动 designer profile\nDesigner:   读取任务描述 → 出 SVG 效果图 → 保存到 docs\u002F → 标记完成\nDispatcher: 发现下一个任务 → 继续...\n",[93,7122,7120],{"__ignoreMap":171},[155,7124,7126],{"id":7125},"第-4-步部署到腾讯云","第 4 步：部署到腾讯云",[14,7128,7129],{},"Ops Worker 拿到部署任务后：",[34,7131,7132,7135,7142,7148,7151],{},[37,7133,7134],{},"读取本地 HTML 设计文件",[37,7136,7137,7138,7141],{},"通过 ",[26,7139,7140],{},"paramiko"," SSH 连接到腾讯云香港 CentOS 7",[37,7143,7144,7145],{},"SFTP 上传到 ",[93,7146,7147],{},"\u002Fvar\u002Fwww\u002Fpreview\u002F",[37,7149,7150],{},"验证 HTTP 200",[37,7152,7153],{},"标记任务完成",[163,7155,7157],{"className":1734,"code":7156,"language":1736,"meta":171,"style":171},"# ops worker 实际使用的部署脚本片段\nssh = paramiko.SSHClient()\nssh.connect(host, port=22, username='root', password=password)\nsftp = ssh.open_sftp()\nsftp.put(local_path, remote_path)\n# 验证\nstdin, stdout, stderr = ssh.exec_command(f'curl -s -o \u002Fdev\u002Fnull -w \"%{{http_code}}\" {url}')\n",[93,7158,7159,7164,7168,7173,7178,7183,7188],{"__ignoreMap":171},[494,7160,7161],{"class":496,"line":497},[494,7162,7163],{},"# ops worker 实际使用的部署脚本片段\n",[494,7165,7166],{"class":496,"line":324},[494,7167,2993],{},[494,7169,7170],{"class":496,"line":331},[494,7171,7172],{},"ssh.connect(host, port=22, username='root', password=password)\n",[494,7174,7175],{"class":496,"line":525},[494,7176,7177],{},"sftp = ssh.open_sftp()\n",[494,7179,7180],{"class":496,"line":1761},[494,7181,7182],{},"sftp.put(local_path, remote_path)\n",[494,7184,7185],{"class":496,"line":2514},[494,7186,7187],{},"# 验证\n",[494,7189,7190],{"class":496,"line":2519},[494,7191,7192],{},"stdin, stdout, stderr = ssh.exec_command(f'curl -s -o \u002Fdev\u002Fnull -w \"%{{http_code}}\" {url}')\n",[155,7194,7196],{"id":7195},"第-5-步通知你","第 5 步：通知你",[14,7198,7199],{},"最初这是个痛点——Worker 干完了没人通知。后来我们打通了 Kanban 原生通知：",[163,7201,7203],{"className":1980,"code":7202,"language":1982,"meta":171,"style":171},"# Manager 每创建一个任务，就帮你订阅通知\nhermes kanban notify-subscribe \\\n  --platform qqbot \\\n  --chat-id [QQ会话ID] \\\n  --notifier-profile default \\\n  \u003Ctask_id>\n",[93,7204,7205,7210,7222,7232,7243,7253],{"__ignoreMap":171},[494,7206,7207],{"class":496,"line":497},[494,7208,7209],{"class":2459},"# Manager 每创建一个任务，就帮你订阅通知\n",[494,7211,7212,7214,7216,7219],{"class":496,"line":324},[494,7213,1990],{"class":1989},[494,7215,7074],{"class":521},[494,7217,7218],{"class":521}," notify-subscribe",[494,7220,7221],{"class":506}," \\\n",[494,7223,7224,7227,7230],{"class":496,"line":331},[494,7225,7226],{"class":506},"  --platform",[494,7228,7229],{"class":521}," qqbot",[494,7231,7221],{"class":506},[494,7233,7234,7237,7240],{"class":496,"line":525},[494,7235,7236],{"class":506},"  --chat-id",[494,7238,7239],{"class":500}," [QQ会话ID] ",[494,7241,7242],{"class":506},"\\\n",[494,7244,7245,7248,7251],{"class":496,"line":1761},[494,7246,7247],{"class":1989},"  --notifier-profile",[494,7249,7250],{"class":521}," default",[494,7252,7221],{"class":506},[494,7254,7255,7257,7260,7263],{"class":496,"line":2514},[494,7256,4565],{"class":2744},[494,7258,7259],{"class":521},"task_i",[494,7261,7262],{"class":500},"d",[494,7264,2847],{"class":2744},[14,7266,7267,7268],{},"任务完成\u002F阻塞\u002F崩溃 → Gateway 自动推送到你的 QQ。",[26,7269,7270],{},"不需要 cron，不依赖轮询。",[7036,7272],{"src":7273,"width":7039,"height":7274,"style":7041},"https:\u002F\u002Fdeeeli.com\u002Fpreview\u002Fimages\u002Fdiagrams\u002Fnotification-loop.html",400,[7043,7276,7277],{"style":7045},"▲ 通知闭环：Manager 订阅 → Worker 完成 → Kanban 事件 → Gateway → QQ",[2302,7279],{},[18,7281,7283],{"id":7282},"三设计迭代v1-v4","三、设计迭代：v1 → v4",[14,7285,7286],{},"设计是最能体现多 Agent 价值的环节。用户不需要懂设计，只需给方向。",[60,7288,7289,7303],{},[63,7290,7291],{},[66,7292,7293,7295,7298,7300],{},[69,7294,2319],{},[69,7296,7297],{},"Worker",[69,7299,961],{},[69,7301,7302],{},"耗时",[85,7304,7305,7317,7328,7339],{},[66,7306,7307,7309,7311,7314],{},[90,7308,5184],{},[90,7310,3373],{},[90,7312,7313],{},"SVG 矢量效果图",[90,7315,7316],{},"2 min",[66,7318,7319,7321,7323,7326],{},[90,7320,5190],{},[90,7322,3373],{},[90,7324,7325],{},"增强层次感",[90,7327,7316],{},[66,7329,7330,7332,7334,7337],{},[90,7331,5196],{},[90,7333,3373],{},[90,7335,7336],{},"HTML\u002FCSS 真实渲染",[90,7338,7316],{},[66,7340,7341,7343,7345,7348],{},[90,7342,2343],{},[90,7344,3373],{},[90,7346,7347],{},"Three.js 3D + Canvas 神经网络",[90,7349,7316],{},[14,7351,7352],{},"每次迭代：",[34,7354,7355,7358,7361,7364],{},[37,7356,7357],{},"Designer 出图",[37,7359,7360],{},"Manager 审查",[37,7362,7363],{},"用户确认（或给出反馈：\"不够科幻\" → \"要3D\" → \"加神经网络\"）",[37,7365,7366],{},"下一版开始",[14,7368,7369,7372],{},[26,7370,7371],{},"4 个版本累计不到 10 分钟","，从 SVG 草图进化到 Three.js 3D 粒子场景。",[7036,7374],{"src":7375,"width":7039,"height":7376,"style":7041},"https:\u002F\u002Fdeeeli.com\u002Fpreview\u002Fimages\u002Fdiagrams\u002Fdesign-iteration.html",450,[7043,7378,7379],{"style":7045},"▲ 设计迭代时间线：v1 SVG → v2 增强 → v3 HTML → v4 Three.js 3D",[2302,7381],{},[18,7383,7385],{"id":7384},"四技术栈","四、技术栈",[155,7387,7388],{"id":7388},"网站本身",[163,7390,7393],{"className":7391,"code":7392,"language":168},[166],"Nuxt + Vue\nTailwind CSS + Nuxt UI\n@nuxt\u002Fcontent (Markdown 博客)\nPostgreSQL (内容存储)\nDocker (容器化运行)\n",[93,7394,7392],{"__ignoreMap":171},[155,7396,7397],{"id":7397},"部署架构",[163,7399,7402],{"className":7400,"code":7401,"language":168},[166],"腾讯云香港 CentOS 7\n├─ Nginx (80\u002F443, 反向代理 + SSL)\n├─ Git (自建代码仓库)\n├─ \u002Fvar\u002Fwww\u002Fpreview\u002F (设计预览)\n├─ \u002Fvar\u002Fwww\u002Fproduction\u002F (生产环境)\n└─ Docker (可选，容器化运行)\n",[93,7403,7401],{"__ignoreMap":171},[155,7405,7406],{"id":7406},"自动化",[163,7408,7411],{"className":7409,"code":7410,"language":168},[166],"Hermes Agent (主控)\n├─ QQ Bot Gateway (消息双向通道)\n├─ Kanban + 9 Worker Profiles (任务协作)\n├─ Cron ×5 (财经舆情监控)\n├─ 邮件 SMTP (QQ 邮箱)\n└─ Paramiko (SSH 部署)\n",[93,7412,7410],{"__ignoreMap":171},[2302,7414],{},[18,7416,7418],{"id":7417},"五几个有趣的坑","五、几个有趣的坑",[155,7420,7422],{"id":7421},"坑-1ssh-22-端口被防火墙挡了","坑 1：SSH 22 端口被防火墙挡了",[14,7424,3209,7425,7428],{},[93,7426,7427],{},"ssh root@[服务器IP]"," 死活不通。排查发现是腾讯云安全组没放行。",[14,7430,7431,7434,7435,7438],{},[26,7432,7433],{},"解决："," 走 ",[26,7436,7437],{},"OrcaTerm 网页终端","作为跳板，或者让用户在 FinalShell 上手动跑一次性命令。后来配好安全组后 Ops Worker 可以直接 SSH。",[155,7440,7442,7443,7446],{"id":7441},"坑-2windows-cron-里-python3-不存在","坑 2：Windows cron 里 ",[93,7444,7445],{},"python3"," 不存在",[14,7448,7449,7450,7452,7453,7455],{},"Git Bash 环境下只有 ",[93,7451,1736],{},"，脚本里写 ",[93,7454,7445],{}," 直接 command not found。cron prompt 里所有路径必须用绝对路径。",[155,7457,7459],{"id":7458},"git-代码提交与维护流程","Git 代码提交与维护流程",[14,7461,7462],{},"多 Agent 协作开发的核心基础设施是 Git。我们采用自托管的 Git 服务作为代码仓库，所有 Worker 的代码产出都通过标准 Git 工作流管理。",[14,7464,7465],{},[26,7466,7467],{},"日常流程：",[163,7469,7472],{"className":7470,"code":7471,"language":168},[166],"开发者本地 (D:\\work\\personal\\personal-website)\n  │\n  ├─ git add \u002F commit         ← Worker 产出代码后提交\n  ├─ git push origin main     ← 推送到远程仓库（代码备份 + 版本追溯）\n  │\n  ▼\n远程 Git 仓库 (腾讯云服务器)\n  │\n  ├─ Ops Worker git pull      ← 部署前拉取最新代码\n  │\n  ▼\n本地 Docker 构建\n  ├─ docker build -t website:latest .        ← 本地编译镜像\n  ├─ docker save website:latest | gzip > website.tar.gz  ← 打包\n  │\n  ▼\nSFTP 上传到服务器 \u002Ftmp\u002F\n  ├─ sftp.put('website.tar.gz', '\u002Ftmp\u002F')\n  │\n  ▼\n服务器端\n  ├─ docker load \u003C \u002Ftmp\u002Fwebsite.tar.gz       ← 加载镜像\n  ├─ docker compose up -d                    ← 更新容器\n  ├─ nginx -s reload                         ← 热重载\n  │\n  ▼\n生产环境 (deeeli.com)\n",[93,7473,7471],{"__ignoreMap":171},[14,7475,7476],{},[26,7477,7478],{},"为什么本地构建而非服务器构建？",[599,7480,7481,7484,7487],{},[37,7482,7483],{},"服务器 CentOS 7 环境老旧，编译原生模块（如 Node.js addon）容易失败",[37,7485,7486],{},"本地 Docker 环境与目标一致，构建一次到处运行",[37,7488,7489,7490,7493],{},"服务器只需 ",[93,7491,7492],{},"docker load","，不装完整编译链",[14,7495,7496],{},[26,7497,7498],{},"分支策略：",[60,7500,7501,7511],{},[63,7502,7503],{},[66,7504,7505,7508],{},[69,7506,7507],{},"分支",[69,7509,7510],{},"用途",[85,7512,7513,7523,7533],{},[66,7514,7515,7520],{},[90,7516,7517],{},[93,7518,7519],{},"main",[90,7521,7522],{},"生产就绪代码，Ops 直接从 main 拉取部署",[66,7524,7525,7530],{},[90,7526,7527],{},[93,7528,7529],{},"feature\u002F*",[90,7531,7532],{},"新功能开发分支，由 Manager 创建任务时指定",[66,7534,7535,7540],{},[90,7536,7537],{},[93,7538,7539],{},"fix\u002F*",[90,7541,7542],{},"Bug 修复分支，完成后合并回 main",[14,7544,7545],{},[26,7546,7547],{},"实际命令流：",[163,7549,7551],{"className":1980,"code":7550,"language":1982,"meta":171,"style":171},"# Worker 完成任务后提交\ngit add docs\u002Fsci-fi-home-v4.html\ngit commit -m \"designer: v4 3D交互效果图\"\ngit push origin main\n\n# 部署：本地构建 → 上传 → 服务器加载\ndocker build -t website:latest .\ndocker save website:latest | gzip > website.tar.gz\nsftp root@server \u003C\u003C\u003C \"put website.tar.gz \u002Ftmp\u002F\"\n\n# 服务器端\nssh root@server \"\n  docker load \u003C \u002Ftmp\u002Fwebsite.tar.gz &&\n  cd \u002Fapp && docker compose up -d &&\n  nginx -s reload\n\"\n",[93,7552,7553,7558,7569,7582,7595,7599,7604,7617,7633,7646,7650,7655,7664,7669,7674,7679],{"__ignoreMap":171},[494,7554,7555],{"class":496,"line":497},[494,7556,7557],{"class":2459},"# Worker 完成任务后提交\n",[494,7559,7560,7563,7566],{"class":496,"line":324},[494,7561,7562],{"class":1989},"git",[494,7564,7565],{"class":521}," add",[494,7567,7568],{"class":521}," docs\u002Fsci-fi-home-v4.html\n",[494,7570,7571,7573,7576,7579],{"class":496,"line":331},[494,7572,7562],{"class":1989},[494,7574,7575],{"class":521}," commit",[494,7577,7578],{"class":506}," -m",[494,7580,7581],{"class":521}," \"designer: v4 3D交互效果图\"\n",[494,7583,7584,7586,7589,7592],{"class":496,"line":525},[494,7585,7562],{"class":1989},[494,7587,7588],{"class":521}," push",[494,7590,7591],{"class":521}," origin",[494,7593,7594],{"class":521}," main\n",[494,7596,7597],{"class":496,"line":1761},[494,7598,2481],{"emptyLinePlaceholder":342},[494,7600,7601],{"class":496,"line":2514},[494,7602,7603],{"class":2459},"# 部署：本地构建 → 上传 → 服务器加载\n",[494,7605,7606,7608,7610,7612,7615],{"class":496,"line":2519},[494,7607,2279],{"class":1989},[494,7609,2467],{"class":521},[494,7611,2470],{"class":506},[494,7613,7614],{"class":521}," website:latest",[494,7616,2476],{"class":521},[494,7618,7619,7621,7623,7625,7627,7629,7631],{"class":496,"line":2525},[494,7620,2279],{"class":1989},[494,7622,3755],{"class":521},[494,7624,7614],{"class":521},[494,7626,2745],{"class":2744},[494,7628,3768],{"class":1989},[494,7630,3771],{"class":2744},[494,7632,3774],{"class":521},[494,7634,7635,7637,7640,7643],{"class":496,"line":2536},[494,7636,3779],{"class":1989},[494,7638,7639],{"class":521}," root@server",[494,7641,7642],{"class":2744}," \u003C\u003C\u003C",[494,7644,7645],{"class":521}," \"put website.tar.gz \u002Ftmp\u002F\"\n",[494,7647,7648],{"class":496,"line":2541},[494,7649,2481],{"emptyLinePlaceholder":342},[494,7651,7652],{"class":496,"line":2547},[494,7653,7654],{"class":2459},"# 服务器端\n",[494,7656,7657,7659,7661],{"class":496,"line":2569},[494,7658,3802],{"class":1989},[494,7660,7639],{"class":521},[494,7662,7663],{"class":521}," \"\n",[494,7665,7666],{"class":496,"line":3883},[494,7667,7668],{"class":521},"  docker load \u003C \u002Ftmp\u002Fwebsite.tar.gz &&\n",[494,7670,7671],{"class":496,"line":3905},[494,7672,7673],{"class":521},"  cd \u002Fapp && docker compose up -d &&\n",[494,7675,7676],{"class":496,"line":3910},[494,7677,7678],{"class":521},"  nginx -s reload\n",[494,7680,7681],{"class":496,"line":3916},[494,7682,7683],{"class":521},"\"\n",[14,7685,7686],{},[26,7687,7688],{},"Git 与 Kanban 的配合：",[14,7690,7691,7692,7695],{},"每个 Kanban 任务完成后，Worker 将产出文件通过 Git push 到远程仓库。下游 Worker（如 Reviewer、Ops）通过 ",[93,7693,7694],{},"git pull"," 获取最新代码。整个过程中：",[599,7697,7698,7704,7713],{},[37,7699,7700,7703],{},[26,7701,7702],{},"代码版本可追溯","：每次改动都有 commit 记录",[37,7705,7706,473,7709,7712],{},[26,7707,7708],{},"回滚无压力",[93,7710,7711],{},"git revert"," 一键回退",[37,7714,7715,7718],{},[26,7716,7717],{},"并行开发不冲突","：不同 Worker 操作不同文件（designer 改 docs\u002F，coder 改 app\u002F），天然隔离",[155,7720,7722],{"id":7721},"坑-4任务完成了没人通知","坑 4：任务完成了没人通知",[14,7724,7725,7726,7729],{},"前面说过了——这是推动我们实现 ",[93,7727,7728],{},"notify-subscribe"," 的原因。现在 Manager 创建任务时自动订阅，用户 QQ 实时收到推送。",[2302,7731],{},[18,7733,7735],{"id":7734},"六效果","六、效果",[14,7737,7738],{},"从 QQ 发一条消息到看到预览，完整链路：",[163,7740,7743],{"className":7741,"code":7742,"language":168},[166],"\"设计要有科技感\" \n  → Manager 拆任务 (5s)\n  → Designer 出 v4 效果图 (2min) \n  → Manager 审查 (10s)\n  → Ops SSH 上传到服务器 (15s)\n  → QQ 通知 \"预览已上线: http:\u002F\u002F[服务器]\u002Fpreview\u002F\"\n",[93,7744,7742],{"__ignoreMap":171},[14,7746,7747],{},[26,7748,7749],{},"全自动，零手动操作，2-3 分钟端到端。",[2302,7751],{},[18,7753,7755],{"id":7754},"七总结","七、总结",[14,7757,7758,7759,1129],{},"这不是\"AI 帮你写了个网站\"——这是",[26,7760,7761],{},"建了一条从聊天到部署的装配线",[14,7763,7764],{},"核心思想：",[34,7766,7767,7773,7779,7785],{},[37,7768,7769,7772],{},[26,7770,7771],{},"Manager 只管不管干","——拆任务、派活、跟踪，不写代码",[37,7774,7775,7778],{},[26,7776,7777],{},"Worker 各司其职","——Designer 出图、Coder 写码、Ops 部署，互不干扰",[37,7780,7781,7784],{},[26,7782,7783],{},"Kanban 是共享工作台","——所有 Agent 看同一块板子，任务状态一目了然",[37,7786,7787,7790],{},[26,7788,7789],{},"通知闭环","——Worker 干完了你马上知道，不用手动查",[14,7792,7793],{},"下一步：把 Manager 的流水线模板化，让\"开发网站\"和\"写爬虫\"共用同一套调度逻辑。",[2302,7795],{},[14,7797,7798],{},[7799,7800,7801,7802,7805],"em",{},"本文由 Hermes Agent 根据实际开发过程记录撰写，陈德立署名发布。所有 Kanban 任务日志可在 ",[93,7803,7804],{},"hermes kanban log"," 中回溯。",[2242,7807,7808],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":171,"searchDepth":324,"depth":324,"links":7810},[7811,7812,7819,7820,7825,7832,7833],{"id":6967,"depth":324,"text":6968},{"id":7051,"depth":324,"text":7052,"children":7813},[7814,7815,7816,7817,7818],{"id":7055,"depth":331,"text":7056},{"id":7089,"depth":331,"text":7090},{"id":7112,"depth":331,"text":7113},{"id":7125,"depth":331,"text":7126},{"id":7195,"depth":331,"text":7196},{"id":7282,"depth":324,"text":7283},{"id":7384,"depth":324,"text":7385,"children":7821},[7822,7823,7824],{"id":7388,"depth":331,"text":7388},{"id":7397,"depth":331,"text":7397},{"id":7406,"depth":331,"text":7406},{"id":7417,"depth":324,"text":7418,"children":7826},[7827,7828,7830,7831],{"id":7421,"depth":331,"text":7422},{"id":7441,"depth":331,"text":7829},"坑 2：Windows cron 里 python3 不存在",{"id":7458,"depth":331,"text":7459},{"id":7721,"depth":331,"text":7722},{"id":7734,"depth":324,"text":7735},{"id":7754,"depth":324,"text":7755},"2026-05-27","记录个人网站从 QQ 聊天出发，经 Manager-Kanban-Worker 多 Agent 协作，到部署上线并建立通知闭环的完整技术链路。",{"author":3299},"\u002Fblog\u002Fhermes-to-deploy",{"title":6942,"description":7835},"blog\u002Fhermes-to-deploy",[1990,3304,5024,5025,3305,5026,5030],"aMdixpec3YsWhz-NuzJuGYPlpq9DYpRL3Y44_zOLJyo",{"id":7843,"title":7844,"body":7845,"date":7905,"description":7906,"draft":339,"extension":340,"meta":7907,"navigation":342,"path":7908,"seo":7909,"stem":7910,"tags":7911,"__hash__":7914},"blog\u002Fblog\u002Fhello-world.md","欢迎来到我的博客",{"type":8,"value":7846,"toc":7901},[7847,7849,7852,7855,7858,7872,7875],[2287,7848,7844],{"id":7844},[14,7850,7851],{},"这是我的个人网站的第一篇文章。",[18,7853,7854],{"id":7854},"关于这个网站",[14,7856,7857],{},"这个网站使用 Nuxt 构建，支持：",[599,7859,7860,7863,7866,7869],{},[37,7861,7862],{},"✍️ Markdown 博客",[37,7864,7865],{},"🎨 暗色模式",[37,7867,7868],{},"💬 Giscus 评论",[37,7870,7871],{},"📬 联系表单",[18,7873,7874],{"id":7874},"技术栈",[599,7876,7877,7883,7889,7895],{},[37,7878,7879,7882],{},[26,7880,7881],{},"框架",": Nuxt 4 + Vue 3",[37,7884,7885,7888],{},[26,7886,7887],{},"样式",": Tailwind CSS + Nuxt UI",[37,7890,7891,7894],{},[26,7892,7893],{},"内容",": @nuxt\u002Fcontent",[37,7896,7897,7900],{},[26,7898,7899],{},"部署",": Docker",{"title":171,"searchDepth":324,"depth":324,"links":7902},[7903,7904],{"id":7854,"depth":324,"text":7854},{"id":7874,"depth":324,"text":7874},"2026-05-26","这是我的第一篇博客文章，介绍这个网站。",{},"\u002Fblog\u002Fhello-world",{"title":7844,"description":7906},"blog\u002Fhello-world",[7912,7913],"personal","website","sD4bNmoLGesvL6xjQlVNKOt701KhL9RG4joBOVE9FDs"]