Dagger engine-lab:让 Agent 用源码构建的引擎跑通调试循环——从 SKILL.md 到模块实现全解析

发布时间:2026/9/14 11:52:13
Dagger engine-lab:让 Agent 用源码构建的引擎跑通调试循环——从 SKILL.md 到模块实现全解析 Dagger engine-lab让 Agent 用源码构建的引擎跑通调试循环——从 SKILL.md 到模块实现全解析【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/daggerengine-lab 是 Dagger 仓库内置的一个沙箱化调试工具集它把人类开发者用./hack/dev./hack/with-dev完成的从源码构建引擎 → 对活引擎发命令 → 戳 debug 端点 → 改码重启复现循环改写为一组agent工具供无 Bash 的编码 Agent 在 Dagger 会话中直接使用。本文基于 SKILL.md 的工作流骨架结合 main.dang 的完整实现、debug.go 的路由注册和 engine-dev/test.go 的测试链路讲清每个工具的用法、参数与底层设计决策。一、engine-lab 的定位与完整工作流SKILL.md 的 frontmatter 一句话定义了使用前提Build and debug the Dagger engine from the workspace source with the EngineLab tools — the sandbox equivalent of the./hack/dev./hack/with-devloop. Read before using the engine-lab tools to run commands against a live from-source engine, poke its debug/pprof endpoints, run engine tests, or re-run repros after editing engine code.main.dang 文件头的注释块进一步阐明了动机一个没有 bash 的 Agent 在排查引擎 bug 时需要人类由./hack/dev./hack/with-dev得到的那个循环——从 workspace 源码构建引擎和 CLI、对活引擎执行命令、戳 debug 端点、改码后重跑。这个模块就是把这个循环按 tui-qa/main.dang 的会话状态模式重写成工具SKILL.md 中的步骤对应工具等价的人类命令构建引擎CLI 并启动常驻服务start./hack/dev对活引擎执行 dagger 命令dagger(args: [...])./hack/with-dev ./bin/dagger ...裸 GraphQL 直连引擎querydagger api query戳 :6060 debug 端点debugGet/debugJqcurl http://localhost:6060/debug/...改码后重建替换引擎restart再次./hack/dev跑引擎测试engineTest(pkg, run)dagger api call engine-dev test读引擎日志ListServicesReadLogsdocker logs dagger-engine.dev \| grep释放引擎stopdocker rm -f dagger-engine.dev对照 hack/dev 可以验证等价性它先在仓库根构建调用hack/build再委托hack/with-dev执行命令而 hack/with-dev 设置了DAGGER_ENGINEcontainer://dagger-engine.dev、_EXPERIMENTAL_DAGGER_CLI_BIN等环境变量后exec目标命令。engine-lab 的start则用容器Service 原语复刻了同样效果并额外启用标准 Playground 服务没有的--debugaddr。模块配置见 dagger-module.toml运行时为dang声明了两个本地依赖——cli-dev构建源码版 CLI和engine-dev构建源码版引擎并提供测试工具链依赖路径分别为../cli-dev与../engine-dev。二、会话状态与工具全景engine-lab 的全部持久化状态只有三个字段且刻意声明为私有不暴露为工具let client: Container! container # 绑定引擎的客户端容器源码版 dagger CLI 在 PATH装了 curl/jq/git let engine: Service null # 已启动的源码版引擎服务start 之前为 null let engineEndpoint: String! # tcp://host:1234会话内可解析start 之前为 外加一个技能目录let skills: Directory! currentModule.source.directory(skills)它通过agent(base: LLM!)函数把工具挂到任意 LLM 上base.withTools(currentNode).withSkills(skills)。技能采用按需发现策略——模型用ListSkills发现、用ReadSkill读取 SKILL.md只在真正需要时占用上下文避免调试工作流提前污染会话。对外工具共 9 个SKILL.md 按调用顺序给出工作流这里逐个给出签名均出自 main.dang工具签名说明startstart(source: Workspace!): EngineLab!构建引擎CLI启动常驻服务并打印 endpointendpointendpoint: String!返回tcp://host:1234未 start 时返回提示文案daggerdagger(source: Workspace!, args: [String!]!): Void对活引擎执行dagger argsargs不带前导 dagger如[call, test]queryquery(source: Workspace!, graphql: String!): Void裸 GraphQL 直连引擎不加载任何模块debugGetdebugGet(path: String! /debug/pprof/): String!GET 引擎 debug HTTP 服务的任意路径返回尾部截断的原始响应debugJqdebugJq(path: String!, filter: String! .): String!GET JSON 端点后过 jq 过滤防止大 payload 撑爆上下文restartrestart(source: Workspace!): EngineLab!重建并替换运行中的引擎内部就是再调一次startengineTestengineTest(source: Workspace!, pkg: String! ./..., run: String! , testVerbose: Boolean! false): Void经 engine-dev 工具链跑引擎测试自带临时引擎stopstop: Dagger.EngineLab!停掉引擎服务并释放资源三、start从源码构建引擎的完整链路start的实现main.dang 第 74–131 行分四步值得逐步拆解1. 清理旧引擎。若engine ! null先engine.stop并打印 Stopped previous engine service——所以restart与start本质相同restart(source: Workspace!): EngineLab! cache(policy: FunctionCachePolicy.Never) { start(source: source) }2. 构建并启动引擎服务。调用engineDev(ws: source)从 workspace 源码构建引擎incrementSubnet得到用于嵌套的递增子网然后let engineCtr svcDev .container .withExposedPort(1234) .withMountedCache( /var/lib/dagger, cacheVolume(engine-lab-state- Random.string), # 全新状态卷 ) let engineArgs [ --addr, tcp://0.0.0.0:1234, --network-name, dagger-lab, --network-cidr, svcDev.networkCidr, --debugaddr, 0.0.0.0: toString(debugPort), # debugPort 6060 ] let svc engineCtr.asService(args: engineArgs, useEntrypoint: true, insecureRootCapabilities: true).start关键参数与 engine-dev/test.go 第 278–288 行里测试引擎的服务参数几乎一致同样--addr tcp://0.0.0.0:1234--debugaddr 0.0.0.0:6060useEntrypointinsecureRootCapabilities印证了 SKILL.md 说的与./hack/dev等价。而--debugaddr这个 flag 本身定义在 cmd/engine/main.godebugging address (eg. 0.0.0.0:6060)只有显式设置时才会启动 debug HTTP 服务——这正是start注释所说stock Playground service doesnt enable的部分。每次 start 都挂一个新的随机命名缓存卷作状态目录保证调试基线干净。3. 准备客户端容器。从alpine:3.20起装curl jq git用dev.installClient(client: base, service: svc)把引擎服务绑定到dagger-engine主机名并将_EXPERIMENTAL_DAGGER_RUNNER_HOST指向它再withFile(/usr/local/bin/dagger, cli)用源码版 CLI 覆盖。注意注释强调workspace没有在这里挂载见下一节。4. 打印 endpoint。self.engineEndpoint tcp:// host :1234并打印提示把这个地址传给 tui-qa 的start工具engine参数即可让 TUI 会话打到同一个引擎上。四、新鲜度模型源码树每次重挂二进制只在 restart 时变这是 engine-lab 最值得记住的设计SKILL.md 原文The from-source CLI is on PATH and your CURRENT workspace tree is mounted at /src, re-mounted fresh on every call so your edits are always visible — only the enginebinaryis pinned untilrestart.实现是withSource辅助函数main.dang 第 305–320 行let withSource(source: Workspace!): Container! { client .withEnvVariable(ENGINE_LAB_NONCE, Random.string) # 打破 exec 缓存 .withMountedDirectory(defaultWorkdir, source.directory(/)) # /src .withWorkdir(defaultWorkdir) }源码注释解释了它修掉的 bug 类别如果客户端容器在start时一次性构建并挂上当时的 workspace 快照之后每次exec都会静默地跑在陈旧的源码树上——三次连续修改 fixture 却拿到字节级完全相同的报错甚至在容器里cp -r /src/...也传播了陈旧性。把source: Workspace!作为每个工具的自动注入的入参让当前树包括 start 之后做的所有编辑在每次调用时结构性地重新注入。而引擎二进制的重建昂贵且刻意只在restart时变化——新鲜度是结构性的不是程序性的。五、dagger与query两种对活引擎发命令的方式dagger是 SKILL.md 的主力工具dagger(args: [...])runsdagger argsagainst the live engine — pass the subcommand WITHOUT a leading dagger (e.g.[call, test]).实现上它就是withSource(source).withExec([dagger] args).sync等价于人类侧./hack/with-dev ./bin/dagger ...。注意 hack/with-dev 的人类侧还有一个对应的坑——在 shell 包装命令里可能误用 PATH 上的非 dev 版 dagger所以文档建议显式写./bin/daggerengine-lab 因 CLI 直接以源码构建覆盖到/usr/local/bin/dagger天然规避了该问题。未 start 时调用会打印 No engine session. Call the engine-lab start tool first. 并返回空。query用于绕过 CLI 管道直接驱动核心 API 复现实现是dagger api query --no-load-modulegraphql 文本走 stdinexpect: ReturnType.ANY意味着查询报错也不会让整个工具调用失败。典型场景查version字段或手工驱动container { ... }查询链。六、debugGet/debugJq:6060 端点有哪些SKILL.md 指出debugGet/debugJqhit the engines :6060 debug endpoints (routes incmd/engine/debug.go), e.g./debug/pprof/goroutine?debug2for hangs; usedebugJqto filter big JSON like/debug/dagql/cache.对照 cmd/engine/debug.go 的setupDebugHandlers实际注册的路由比文档例子更丰富路径方法用途/debug/pprof/默认 pathGETpprof 索引页/debug/pprof/goroutineGET挂死排查?debug2输出全部 goroutine 栈/debug/pprof/heap、/profile、/trace、/mutex、/block、/cmdline、/symbolGET标准 pprof 全家桶/debug/dagql/cacheGETdagql 缓存快照流式 JSON——debugJq的主战场/debug/dagql/egraphGETe-graph 调试快照DagqlDebugSnapshot/debug/gcGET手动触发 GC debug.FreeOSMemory()/debug/varsGETexpvar 变量/debug/requests、/debug/eventsGETgolang.org/x/net/trace事件/debug/wcprof/enabledGET/POST引擎级 workspace-profiling 开关POST body 为on/off或布尔值/debug/wcprof/dumpGET导出 wcprof 事件ndjson?flush0保持缓冲两个工程细节值得注意权限模型。cmd/engine/debug.go 注释setting debugaddr is opt-in. permission is defined by listener address一旦设置--debugaddrtrace.AuthRequest直接放行访问控制完全靠监听地址。engine-lab 把 debug 服务绑在0.0.0.0:6060但只在会话的隔离网络内可达debugURL拼出的地址是http://dagger-engine:6060pathdagger-engine是installClient绑定的主机名。防缓存双保险。debugGet/debugJq/dagger/query全部带cache(policy: FunctionCachePolicy.Never)并且每次 exec 前注入随机ENGINE_LAB_NONCE环境变量。源码注释解释了原因dagql 会按 (对象 id 字段 参数) 在会话内记忆化函数结果重复调用会回放第一次调用的输出而不是重跑而客户端容器的 exec 缓存还需要 nonce 来打穿。debugJq里 URL 和 jq 过滤器走环境变量传入sh -c脚本避免了 shell 引号注入问题。七、engineTest独立于start的临时引擎测试链路SKILL.md 的用法engineTest(pkg, run)runs engine tests with their own ephemeral engine (nostartneeded), e.g. pkg./core/integrationwith runTestSuite/TestSub.底层是engineDev(ws: source).test(pkg: pkg, run: run, testVerbose: testVerbose)即 engine-dev 模块的Test函数.dagger/modules/engine-dev/test.go。从源码可以确认几个关键参数语义默认值pkg缺省./...run-run正则缺省空count缺省 1timeout 缺省 30mNo test suite should take more than 30 minutes to run还支持skip、failfast、parallel、race会置CGO_ENABLED1、update更新 golden 文件。临时引擎testContainer每次都从源码重建 dev 引擎并.start()参数与 engine-lab 的start同源--debugaddr 0.0.0.0:6060等另带registry:5000/privateregistry:5000绑定供镜像测试使用。测试包装器实际执行的是otelgotest而非裸go test便于产出 OpenTelemetry 追踪——这也是失败时完整输出在 telemetry 里用 ReadLogs 读失败 span的原因。会话缓存语义Test带// cachesession同一会话内完全相同的重跑可能命中会话缓存但任何 workspace 编辑都会改变引擎源码哈希强制真实重跑。这与 internal-docs/dynamicinputs.md 讲的隐式缓存范围一致。SKILL.md 同时给出纪律性建议与engine-debugging技能一致保持运行聚焦tight例如pkg ./core/integrationrun TestSuite/TestSub不要在整个调试循环里裸跑./...。八、引擎日志ListServices ReadLogsSKILL.md 明确引擎日志没有专用工具Engine logs: ListServices shows the engine services span ids; ReadLogs(span, grep, limit) reads them — the equivalent ofdocker logs dagger-engine.dev | grep.也就是说start返回的引擎 Service 会出现在 workspace 的ListServices输出里拿到其 span id 后用 workspace 原生的ReadLogs(span, grep, limit)三参数读日志——这正是把docker logs dagger-engine.dev | grep翻译成了 workspace 原语也是 main.dang 文件头注释第 26–28 行刻意声明的设计取舍。九、与 tui-qa 的联动调试同一个引擎这是 SKILL.md 中最有实战价值的一段the engine-lab start tool prints the engines tcp:// :1234 endpoint (endpointre-prints it). If tui-qa tools are available, pass it to their start toolsenginearg to run a TUI session against THIS engine — thendebugGet/debugJq/ReadLogs introspect the very engine the TUI is driving (e.g. reproduce a hang in the TUI, then pprof it live).restartand the engine-lab stop tool break attached TUI sessions; restart them after.对照 tui-qa/main.dang 第 79–86 行的engine参数文档可以确认这条链路传入已运行引擎的地址后客户端容器通过_EXPERIMENTAL_DAGGER_RUNNER_HOSTendpoint指向它engine-lab 的 Service 启动在会话 DNS 域下任何会话内容器都能按名解析。注意 tui-qa 注释里的重要限制attach 模式的 runner 不会继承嵌套的 LLM 认证所以args: [agent]这类需要 LLM 鉴权的会话建议走 tui-qa 默认自建嵌套引擎attach 模式适合驱动普通命令类 TUI 复现。典型组合拳在 TUI 里复现一个挂死 → 立刻用debugGet(/debug/pprof/goroutine?debug2)对正在挂死的那个引擎做 pprof 快照 →ReadLogs关联 span 日志。restart/stop会杀死已 attach 的 TUI 会话之后需要用新打印的 endpoint 重新start。十、完整调试循环实战清单把以上拼成 SKILL.md 描述的端到端循环Agent 视角start— 首次构建耗时较长引擎CLI 都要编译之后各工具复用这个运行中的引擎输出tcp://host:1234。dagger(args: [call, test])— 复现问题workspace 树每次调用都从当前状态重挂到/src所以改测试 fixture、改模块源码不需要restart。query(graphql: query { version { schema } })— 需要绕过 CLI 直查核心 API 时使用。挂死/性能问题debugGet(path: /debug/pprof/goroutine?debug2)抓栈大 JSON 用debugJq(path: /debug/dagql/cache, filter: ...)过滤后输出必要时ListServicesReadLogs(span, grep, limit)查引擎日志。改了引擎代码core/、dagql/、cmd/engine/等后restart— 重建二进制并替换服务然后dagger重跑复现。回归验证engineTest(pkg: ./core/integration, run: TestMySuite/TestSub, testVerbose: true)独立临时引擎不依赖当前start会话。stop— 结束会话、释放服务。适用前提与限制需要说清楚engine-lab 依赖 Dagger workspace v1 原语Workspace、Service、agent、cache模块声明的engineVersion v1.0.0-0dagger-module.toml它调试的对象是当前 workspace 检出源码构建的引擎与安装在本机的任何发布版 dagger 二进制无关——这与 engine-debugging/SKILL.md 中v1.0 前版本需用dagger call替代dagger api call的兼容性说明是同一类适用前提。debug 端点仅在显式--debugaddr时开启且无鉴权权限由监听地址界定engine-lab 只在会话隔离网络内暴露它不要将其理解为对外网开放的服务。十一、延伸阅读仓库内相关实现路径模块本体main.dang、dagger-module.toml技能文档engine-lab SKILL.md、人类侧调试指南 engine-debugging/SKILL.md脚本等价物hack/dev、hack/with-devdebug 路由注册cmd/engine/debug.go--debugaddrflag 定义见 cmd/engine/main.go测试工具链.dagger/modules/engine-dev/test.go、.dagger/modules/engine-dev/main.goTUI 联动.dagger/modules/tui-qa/main.dang缓存/动态输入语义解释cachesession为何编辑源码即失效internal-docs/dynamicinputs.md、internal-docs/typedefs.mddagql 缓存排查背景internal-docs/cachebasics.md、internal-docs/egraph.md【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考