作者展示编码Agent从一句需求出发,经规划、自我纠错后生成包含OpenSearch混合检索、Ollama、FastMCP及Helm部署的RAG项目。案例也记录了Agent误删模型分片并自主修复的过程。
我一行代码都没写。没有调试 Helm chart,也没有盯着 traceback,琢磨为什么 connector 无法完成身份验证。
我只输入了一句话,然后看着它工作。
20 分钟后,一个 draft pull request 出现在了我的私有仓库里。这个 PR 背后的一切——OpenSearch cluster、embedding models、五个步调一致的 init jobs、MCP server、RAG pipeline,以及预置的 30 篇文章——全都由一个自主 coding agent 使用名为 loop 的工作流构建完成。
这套技术栈本身已经足够令人印象深刻:OpenSearch 3.7 通过 hybrid search 融合语义搜索与全文搜索结果;retrieval-augmented generation pipeline 由本地 Ollama LLM 驱动;FastMCP server 通过 SSE 暴露四个工具;Skaffold + Helm 部署则只需运行 skaffold run,就能从零启动整个 cluster。但这套技术栈并不是故事的重点。
真正的故事,是从 prompt 到 PR 之间发生的一切:规划、自我纠错,以及 Agent 误以为 14 个 model chunks 是重复数据、把它们删除、导致整个 embedding 系统崩溃,最后又在没有任何帮助的情况下自行修复的那个时刻。
loop 就是这样运作的。
loop 是 opencode 的一个 skill,它能把自然语言 prompt 转化为可用的 feature branch 和 draft PR。听起来和其他 AI coding tools 没什么不同,直到你发现它有一件绝不会做的事:在你批准计划之前,它不会开始写代码。
每个 Agent 都应该这样工作,但大多数 Agent 并不会。通常,你一按下回车,它们就立刻开始写代码,接下来整个 session 里,你都在不停地说:“不,不是这样。”
loop 使用 .loop-state.json 保存状态,其中包括当前 phase、branch、PR number、plan,以及每一次 phase transition 的日志。如果 session 崩溃,或者你离开了一个小时,loop 都能准确地从中断的位置继续运行。单凭这一点,它就足以胜任持续数小时的自主运行任务。
此外还有 loop-police:一个在旁监督的配套 skill,专门监控无限循环,包括 thinking loops、tool-call loops、file-read spirals 和停滞状态。当 loop-police 检测到 Agent 正在原地打转时,它会出面打断:“你卡住了,换个方向。”
这种干预,决定了一个 Agent 究竟会浪费十个回合反复尝试同一次失败的部署,还是能够识别失败并改变策略。
如果没有 loop-police,后面将要讲到的 chunk 删除事故,很可能根本无法恢复。
Agent 给出的方案是:
构建 opensearch-hybrid-mcp,参考 os-hybrid-search:使用 OpenSearch 3.7 和 Dashboards、MCP server(FastMCP + UV)、五个 init jobs(template → embedding → connector → pipeline → seed)、包含六个 images 的 skaffold.yaml、justfile 和 README。使用私有仓库,并创建 draft PR。
然后是一张图——Agent 画出了系统架构:
macOS host Kubernetes (Colima)
┌───────────────┐ ┌────────────────────────────────────────────┐
│ Ollama │ │ ┌────────────────┐ ┌────────────────┐ │
│ (Desktop) │◄────:11434─────►│ │ OpenSearch 3.7 │ │ OS Dashboards │ │
│ qwen3.5:9b-mlx│ host.docker. │ │ os-hybrid:9200 │ │ :5601 │ │
└───────────────┘ internal │ └───────┬────────┘ └────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ MCP Server │ FastMCP + UV │
│ │ mcp-server:8000│ hybrid_search │
│ └────────────────┘ rag_query │
│ index_document │
│ │
│ Init Jobs (run in sequence): │
│ 1. template-job index template │
│ 2. embedding-job all-MiniLM-L12-v2 │
│ 3. connector-job Ollama → Qwen 3.5 │
│ 4. pipeline-job chunking/hybrid/RAG │
│ 5. seed-job 30 articles │
└────────────────────────────────────────────┘
Ollama 运行在 host 上,而不是 cluster 内部——pods 通过 host.docker.internal:11434 访问它。这一点是 Agent 自己弄明白的。单节点 OpenSearch、10Gi PVC、不启用 security(因为这是 dev cluster),MCP server 运行在 SSE 端口 8000。五个 init jobs 按顺序执行,每一个都通过 retry loops 等待上游依赖就绪。
我同意了。Agent 开始构建。
七次 commits,四十多个文件。Agent 在几分钟内搭建出了整个项目的骨架。
六个 Helm charts(OpenSearch parent、MCP server 和五个 init jobs)。一个包含六个并行构建 Docker images 的 skaffold.yaml。一个提供 skaffold run、kubectl port-forward 和 job logs 快捷命令的 justfile。每个 init job 都是带有 until loops 的 shell script——它们会依次等待 OpenSearch、index template、embedding model 完成部署,以及 pipeline 准备就绪。如果依赖不存在,job 就暂停;如果依赖已经就绪,job 就开始执行。
这让整套技术栈可以重复运行:删除一个 job,再重新创建,它就会从上次停下的位置继续。
这里存在一种令人愉悦的递归。loop 是一个由 gates 和 retries 组成的五阶段工作流,而它编写的 jobs 也是带有依赖检查的 retry loops。Agent 构建出的代码,映照着它自身的架构。
接下来完成的是 MCP server:使用 FastMCP,提供四个工具:
hybrid_search(query, index?, k?)——neural + full-text,并通过 z-score 融合结果
rag_query(question, index?, k?)——检索上下文并生成答案
index_document(id, title, body, index?)——分块、embedding 并建立索引
cluster_health()——获取 cluster 状态
Agent 执行了自己的 draft gate:对七个 charts 运行 helm lint 和 helm template,运行 skaffold render,并对每个 script 执行 sh -n。全部通过。它推送 branch,创建了 draft PR #1,然后回来汇报结果。
从技术上讲,loop 的工作已经完成:proposal → agreement → implementation → draft_gate → draft_pr。五个 phases,零次 handoff。
但这套技术栈还没有真正部署。接下来,一切先是偏离方向——然后再次偏离——最后却不知怎么又回到了正轨。
第一次运行 skaffold run,立刻就撞上了一堵墙。Agent 没有等我发现问题。它读取 logs、诊断问题、打补丁,然后重新部署。这样的过程重复了五次。
OpenSearch 3.7 要求提供 credential object,即使面对不使用身份验证的本地 Ollama endpoints 也不例外。Agent 添加了一个 dummy key(openAI_key: "ollama-local"),更关键的是,它把 connector 从 Ollama 原生的 /api/chat 切换到了兼容 OpenAI 的 /v1/chat/completions,因为后者才是 RAG processor 所期望的接口。
一次完成两个修复。重新部署。Connector 创建成功。
RAG queries 遇到了 400 错误:"Connector URL is not matching the trusted connector private endpoint regex"。
Agent 原本写的是 ^http://host.docker.internal:11434:.*$——端口后面是冒号。但实际 URL 是 http://host.docker.internal:11434/v1/chat/completions——这里应该是斜杠。
: 和 / 的差异让整个 pipeline 彻底失效。Agent 找到了问题,改掉这个字符,然后重新部署。这种 Bug,人类可能要花 20 分钟才能找到,而 Agent 一次就抓住了。
Connector 把在 OpenSearch 中注册的 model name 传给了 Ollama,但 Ollama 需要的是原始 model name(qwen3.5:9b-mlx)。
Agent 追踪了整个调用链——OpenSearch RAG processor → connector → Ollama API——然后更新 MCP server,在 ext.generative_qa_parameters 中传入正确的 model name。问题解决。
Seed-job 在 bulk indexing 时触发了 429 rate limits。JVM heap 在 2g 分配额度下已经达到 93%。
Agent 将其提高到 4g(-Xmx4g -Xms4g,container limits 为 4g/8Gi),重新部署,随后 jobs 成功完成。无需任何提示,Agent 就掌握了足够的信息,能够识别出 JVM memory pressure 才是根本原因。
这是 Agent 差点把整个项目毁掉的一次。清理孤立的 model registrations 时,它从 ML system index 中删除了 14 个 documents,以为这些是重复数据。
但它们不是。它们是 embedding model 的 chunks(_0 到 _13)。Model 进入 DEPLOY_FAILED 状态,embedding pipeline 彻底瘫痪。
大多数 Agent 要么根本不会发现,要么发现以后仍然不断重试同一个已经损坏的状态,直到你手动终止 session。
loop 采取了不同的做法:它意识到了自己的错误。它发现 model 进入 DEPLOY_FAILED,明白刚刚删除的 chunks 至关重要,于是删除了损坏的 base model document,让 embedding-job 使用新的 model ID 重新注册,然后再次运行 pipeline 和 seed jobs。
这套技术栈自行恢复了。
这种模式——部署、遇到错误、读取 logs、诊断、修复、重新部署、验证——重复了五次。Agent 运维这套技术栈的能力,和构建它的能力一样出色。
最后一次修复完成后,cluster 稳定下来。30 篇文章完成预置,两个 models 均成功部署,cluster health 为 green。
Hybrid search 返回了经过排序的结果,其中融合了 neural 和 full-text scores。针对 pipeline 的 RAG query,会先检索上下文,再通过 connector 调用 Ollama,最终返回基于检索文档生成的答案:
{
"query": { "match": { "body": "inverted index TF-IDF BM25" } },
"ext": {
"generative_qa_parameters": {
"llm_model": "qwen3.5:9b-mlx",
"llm_question": "Based on the context, explain what an inverted index is and how TF-IDF or BM25 ranking works."
}
}
}
倒排索引是一种将每个唯一单词映射到包含该单词的文档的数据结构,从而能够根据查询词快速检索。至于排序,TF-IDF 衡量词频与逆文档频率之间的关系,而 BM25 则在此基础上通过限制词频的影响改进了这种方法。
有依据、准确,来源就是 Agent 自己预置的 30 篇文章。四个 MCP tools 全部通过 SSE 进行了调用和验证。Agent 更新了 draft PR,补充了验证结果。
loop-police 不是可选项。没有它,Agent 可能会不断重试失败的部署,直到宇宙热寂。有了它,Agent 标记了 DEPLOY_FAILED 状态转换,意识到失败是由自己造成的,然后转向恢复方案。
如果你在运行自主 Agent,就必须配备 stall detector。没有商量余地。
agreement gate 才是整套流程的关键。没有 plan-agreement phase 的自主 coding agent,不过是拥有 commit 权限、精力过剩的实习生。在你说“同意”之前,loop 不会从 proposal 进入 implementation。
仅这一条规则,就消除了 AI coding 最常见的失败模式:Agent 构建出了错误的东西,而你只能无助地看着。
State files 让 long-running sessions 成为可能。.loop-state.json 会追踪 phase、branch、PR number 和 plan history。如果 session 中断——当工具连续自主运行 40 多分钟时,这种事一定会发生——loop 可以在不丢失上下文的情况下继续运行。
这是基础设施,而不是什么锦上添花的功能。
带 retry-loop 的 init jobs,就是具备自愈能力的基础设施。每个 job 都通过 until loops 等待依赖项。你可以销毁一个 job 再重新创建——它会等待、检测前置条件是否满足,然后继续执行。
loop 编写的基础设施,与 loop 自身的工作方式如出一辙。这要么很有诗意,要么是一种递归。很可能两者都是。
draft gate 是那种你永远不会亲自执行的 QA 步骤。loop 在创建 PR 之前运行了 helm lint、helm template、skaffold render 和 sh -n。我的团队里没有人会对每一次 commit 都执行这些检查。
Agent 会执行,因为工作流要求它这么做。gate 失败,loop 就不会进入 PR 阶段。这能在损坏的 manifests 进入仓库之前——甚至在你看到它们之前——把问题拦截下来。
这才是真正值得带走的结论。自主 coding agents 失败,并不是因为它们不够聪明,而是因为它们缺少结构——没有 plan phase、没有 review gate、没有 self-check,也没有 stall detection。
loop 补上了这种结构。它把 Agent 从 code generator 变成了 collaborator:你先拿到计划并批准它,Agent 随后进行构建、自我纠错,最后交付一个 draft PR。你负责 review、merge,然后继续做下一件事。
我一行代码都没写。但 loop 并不是误打误撞构建出了我的 RAG 技术栈。它之所以能完成任务,是因为这套流程迫使它规划、验证和恢复——就像一名优秀的工程师那样。
loop 就是这样运作的。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。