【免费下载链接】learn-harness-engineering
Harness engineering beginner tutorial, from 0 to 1
本篇是 learn-harness-engineering 教程中「Project 04: Runtime Feedback and Structural Control」的完整实战指南(对应日文版 docs/ja/projects/project-04-incremental-indexing/index.md,中文版 projects/project-04/README-CN.md)。它承接 Project 03 的知识库应用,教你为 Agent 补齐运行时可观测性(启动日志、导入/索引日志、错误状态)与层级边界约束(防越界架构检查),并在一个被刻意植入缺陷的索引服务上完成「诊断 → 修复 → 验证」的完整闭环。读完本文,你将掌握结构化日志、分层架构护栏脚本、增量索引机制,以及一套可复现的「同一任务跑两遍」的 Harness 实验方法。
为什么 Agent 需要运行时反馈:本项目的核心命题
在 Project 01~03 中,我们已经验证了能力再强的 Agent 也会失败:它看不见自己程序的运行时状态,只能靠猜测。Project 04 直接把这个痛点做成了一门调试课——给 Agent 配上"眼睛"(结构化日志)和"边界"(架构检查脚本),再让它去修复一个被植入的运行时缺陷。
相关讲义的论证是:Agent 的失败常常源于任务边界模糊(講義 07)与缺乏约束的工作范围(講義 08)。Project 04 把这两条教训落成具体产物:
- 运行时反馈(Runtime Feedback):服务启动、文档导入、增量索引、Q&A 问答每个环节都输出机器可解析的 JSON 日志,Agent 不再"盲修";
- 范围控制(Scope Control):用
check-architecture.sh强制 renderer / preload / main / services 四层各守其责,Agent 的改动无法越界; - 增量索引(Incremental Indexing):索引服务按文档粒度增量推进,配合
index-meta.json记录已索引状态,让长任务可以分段收敛。
这套组合正是本项目的Harness 机制:ランタイムフィードバック + スコープ制御 + 増分インデックス(运行时反馈 + 范围控制 + 增量索引)。
任务总览:同一份工作,跑两遍
原文档给出的任务非常明确——同一份工作执行两次:
- 第一遍(对照组):在没有任何日志与架构约束的环境下让 Agent 完成修复,观察它要花多久才能抵达根本原因;
- 第二遍(实验组):在配有结构化日志、架构边界文档与检查脚本的环境下让同一 Agent 完成修复,对比 logs 与 boundary checks 是否让修复更快、影响范围更小。
这种"A/B 对照"正是 Harness 工程的核心方法论:不靠口号判断工具链价值,而是用可重复的实验数据说话。合格的解法必须有诊断证据(日志输出、检查脚本通过记录),而不只是口头声称"修好了"。
动手前需要准备的环境(原文档"ツール"一节):
| 工具 | 用途 |
|---|---|
| Claude Code 或 Codex | 作为被测的编码 Agent |
| Git | 记录变更、回滚实验、对比两遍结果 |
| Node.js + Electron | 运行 Electron 知识库应用(依赖见 solution/package.json,含 react 18、electron 33、vite 6、vitest 2) |
starter 与 solution:一个"弱信号"起点 vs 一个"护栏完备"的参考实现
仓库中项目位于 projects/project-04/,包含两个可运行的完整切片:
| 目录 | 包含什么 | 实验要对比的量 |
|---|---|---|
| starter/ | 基于 Project 03 的代码,诊断信号弱;IndexingService中被植入了 indexing 缺陷,超过 1000 字符的大文件 chunking 会被破坏;没有架构检查脚本 | 在没有运行时信号的情况下,Agent 抵达根本原因所需的时间 |
| solution/ | 结构化 logger、架构边界文档与检查脚本、已修复的 chunking 逻辑、clean-state-checklist.md | logs 与 boundary checks 是否让修复更快、影响范围更小 |
两者的差异可以精确对应到具体文件(对照 README-CN.md 的任务对应表):
| 功能 / 产物 | starter 状态 | solution 证据 |
|---|---|---|
| 结构化日志 | 无共享 logger 服务,仅零散console.log | src/services/logger.ts 及 main、ipc-handlers、各 services 中的日志调用 |
| 导入/索引诊断 | 运行输出难以定位失败 | 导入、索引开始/完成、Q&A 失败路径的结构化日志 |
| 架构边界 | 无脚本检查 renderer/main/service 越界 | scripts/check-architecture.sh、docs/ARCHITECTURE.md、AGENTS 边界规则 |
| 植入的 chunking bug | 大文件可能产生空 chunk | 修复后的 src/services/indexing-service.ts |
| 干净交接 | 无最终检查清单 | clean-state-checklist.md |
提示:solution 刻意保持比后续项目更精简的 Harness。按 AGENTS.md 的说明,本项目不包含
feature_list.json、claude-progress.md、init.sh、session-handoff.md——那些产物在更后面的项目阶段才引入。做本项目时不要臆造这些文件存在。
运行时可观测性落地:解剖结构化 Logger
原文档点名要重点研读 projects/project-04/solution/src/services/logger.ts。它是本项目"运行时反馈"的基石,把原来不可解析的散乱输出统一为带时间戳、分级、可 JSON 解析的日志条目。
核心设计如下(对照源码逐段解读):
- 四个日志级别:
DEBUG、INFO、WARN、ERROR(LogLevel枚举,源码 L9-L14),并按LEVEL_ORDER定义优先级顺序(L26-L31); - 日志条目结构(
LogEntry,L16-L22):timestamp(ISO 8601)、level、service(服务名)、message、可选data(任意结构化字段Record<string, unknown>); - 级别过滤:
shouldLog()用级别序号比较,低于最小级别(默认DEBUG)的日志直接丢弃,避免噪音淹没信号(L37-L41); - 按级别分流输出:
emit()中ERROR走console.error、WARN走console.warn、其余走console.log,且全部输出 JSON 字符串(L43-L56),保证 stdout/stderr 都能被工具链(如grep、jq)继续消费; - 服务级子 logger:
forService(name)返回ServiceLogger,让每个服务只传消息与数据、自动带上服务名(L91-L121),例如IndexingService内部持有logger.forService('IndexingService'); - 可配置最小级别:单例
logger从环境变量LOG_LEVEL读取最小级别,缺省DEBUG(L123-L126),意味着可以用LOG_LEVEL=ERROR npm run dev一键压噪,或LOG_LEVEL=DEBUG全量观测。
// 实际调用形态(摘自 solution 各服务) this.log.info('Indexing document', { docId: doc.id, title: doc.title, contentLength: content.length }); this.log.error('Document content not found', { documentId });这种"每条日志都自带 service + 结构化 data"的做法,直接服务于 AGENTS.md 的调试指引:排查时检查服务初始化事件、IPC 调用及其参数、索引 chunk 数与内容长度、Q&A 置信度与 citation 数量。
架构约束:四层边界与 check-architecture.sh 源码拆解
ARCHITECTURE.md 定义的四层模型
docs/ARCHITECTURE.md 把 Electron 应用明确划分为四层(自顶向下):
Renderer (React UI) → Preload (contextBridge) → Main Process (IPC Handlers) → Services (Business Logic) → Persistence (Filesystem)每层职责与红线(不可越界):
| 层 | 职责 | 约束(MUST NOT) |
|---|---|---|
| Renderer(src/renderer/) | 渲染 React 组件、处理用户输入、通过window.knowledgeBaseAPI 与主进程通信 | 不得 importfs/path/os/child_process等 Node 核心模块;不得直接访问 Electron API;不得承载业务逻辑 |
| Preload(src/preload/) | 用contextBridge.exposeInMainWorld暴露类型化 API;映射 IPC 通道名 | 不得包含业务逻辑;不得直接 import services;只用ipcRenderer.invoke |
| Main Process(src/main/) | 创建管理 BrowserWindow、注册 IPC handler 并委托给 services | 不得承载路由之外的业务逻辑;不直接访问持久化层 |
| Services(src/services/) | 实现全部业务逻辑(文档管理、索引、Q&A) | 不得 import Electron API(ipcMain、BrowserWindow等);不得 import React;所有文件系统访问必须经PersistenceService |
所有 IPC 通信的通道名收敛在 src/shared/types.ts 的IPC_CHANNELS常量(单一事实来源),例如documents:list、documents:import、indexing:start、indexing:chunks、qa:ask等。数据流方向固定:Renderer 调用window.knowledgeBase.*→ Preload 转成ipcRenderer.invoke(channel, ...)→ Main 的 IPC handler 委托给 service → service 经PersistenceService落盘 → 结果沿 IPC 返回渲染层。
check-architecture.sh 的三种越界检查
scripts/check-architecture.sh 用纯 bash 实现了架构护栏(set -euo pipefail保证失败即退出,退出码 0 = 全部通过,1 = 存在违规)。它做三件事:
- 检查 renderer 是否 import Node 核心模块:遍历
src/renderer下所有.ts/.tsx,用grep -qE "import.*\b(fs|path|os|child_process)\b"命中即记为违规(L21-L33); - 检查 services 是否触碰 Electron:遍历
src/services下.ts,既查import ... from 'electron',也查ipcMain、ipcRenderer、BrowserWindow关键字(L36-L56); - 检查 services/main 是否 import React:遍历
src/services src/main下的.ts,命中import ... from 'react'即违规(L59-L74)。
脚本末尾汇总违规数并按结果exit 0/1(L78-L85),因此它可以无缝挂进 CI 或 Agent 的 pre-commit 流程。这正好把 ARCHITECTURE.md 文档化的规则变成了可执行、可证伪的检查——Agent 想偷偷越界,脚本会当场拦下。
bash scripts/check-architecture.sh # 期望输出:PASS: All architecture boundary checks passed(退出码 0)植入缺陷分析:1000 字符魔咒与增量索引的修复对比
starter 中被植入的 bug
对比 starter/src/services/indexing-service.ts 与 solution 版本,缺陷藏得很"自然":chunk 生成逻辑里悄悄加了一个条件——当content.length > 1000时,把 chunk 内容置为空字符串:
// starter 版本中的缺陷(L106-L119) const chunkContent = content.length > 1000 ? '' : buffer.trim();后果是:任何超过 1000 字符的文件,索引后产出的 chunk 全是空内容。空 chunk 会沿检索链向下传导——Q&A 的关键词匹配拿不到任何命中、citation excerpt 为空、答案置信度骤降。而 starter 只有零散的console.log,日志中"chunkDocument produced N chunks"看起来一切正常,Agent 若无运行时信号,极难定位到问题出在"大文件的分块内容被清空"。
solution 的修复与增量索引机制
solution/src/services/indexing-service.ts 的修复版删除了那个三元条件,chunk 内容始终取真实文本:
- 分块策略:
CHUNK_SIZE = 500(约 500 字符),先按/\n\s*\n/(空行)拆成段落、过滤空白段落,再贪心合并段落直至接近 500 字符边界(L110-L146); - 每个 chunk 携带元数据:
createChunk记录charCount与wordCount(L148-L159),日志里也输出totalChars总和用于校验分块完整性(L139-L143); - 增量索引:
startIndexing()支持按单个documentId增量索引;批量模式读取documents-meta.json与index-meta.json,只处理尚未记录在chunksMeta中的文档(if (chunksMeta[doc.id]) continue;,L53-L54),索引结果写入chunks/<docId>.json并把 chunk id 列表登记回index-meta.json——这样重复运行不会重复消费已索引文档,长任务天然可断点续跑; - 状态机:
getStatus()依据"已索引数 == 文档总数"推导idle | indexing | ready | error四种状态,供 UI 与日志同步展示(L75-L89)。
修复版还在每个关键节点埋了结构化日志:批量开始(记录 totalDocs / alreadyIndexed)、单文档索引(记录 contentLength / chunkCount)、内容缺失跳过(WARN 级),让 Agent 从日志就能判断"这个文档到底有没有被正确分块"。
Q&A 侧的可观测锚点
qa-service.ts 是缺陷的"受害方",也提供了诊断信号:检索时按查询词与 chunk 内容的包含关系打分、取 Top 2 作为 citation(L66-L98);每次回答都记录confidence、citationCount、answerLength到结构化日志(L111-L115)。当答案置信度低、citation 数量为 0 时,日志会直接指向"索引层产出异常"——这正是"运行时反馈驱动定位"的完整演示链路。
完整复现步骤:把两遍实验跑起来
按照 README-CN.md 与 AGENTS.md 的启动工作流:
# 第一遍:弱信号环境 cd starter npm install npm run dev # 观察:Agent 能否仅凭 console.log 定位 chunking bug # 导入一个大文件(>1000 字符),观察分块结果异常(空 chunk) # 第二遍:带护栏环境 cd ../solution npm install npm run dev # 对比:结构化日志如何加速诊断每个会话的标准验证命令(solution 内):
npm run check # tsc 双 tsconfig 类型检查 npm run build # tsc -p tsconfig.node.json && vite build bash scripts/check-architecture.sh # 架构边界护栏,必须 PASS npm run dev # 启动应用,确认结构化日志出现在控制台复现要求:修复前后都用长文档各复现一次,保留日志证据(导入事件、索引 chunk 数、Q&A 置信度),证明"诊断证据存在"而不只是声称修复通过。
干净的交接:clean-state-checklist 与 AGENTS 工作流
本项目强调"Session 结束时仓库必须可重启、可交接"。清单 clean-state-checklist.md 按五组核对:
- 构建:
npm run check无类型错误、npm run build成功; - 架构:
bash scripts/check-architecture.sh零违规、renderer 无 Node 核心模块 import、services 无 Electron IPC、services/main 无 React import; - 运行时:应用无错启动、启动时出现结构化日志、文档导入正常(日志含 IMPORT_DOCUMENT 事件)、各种大小的文档索引正常、Q&A 返回带 citation 的答案(日志含 ASK_QUESTION 事件);
- 数据完整性:已索引文档无空 chunk(用 GET_CHUNKS 验证)、Q&A 历史跨重启持久化、文档元数据与实际文件一致;
- 仓库:git status 无意外文件、无敏感数据入库、最终总结记录当前状态/验证记录/未决风险、AGENTS.md / ARCHITECTURE.md / 本清单与实际文件保持一致。
配套的 AGENTS.md 还定义了长期运行 Agent 的操作纪律:动手前先跑启动工作流(pwd→ 读 ARCHITECTURE.md →git log --oneline -5→npm install→npm run check→ 架构检查),一次只做一个功能,不静默改动验证规则,优先把结论沉淀为仓库内持久产物而非聊天摘要。其Definition of Done明确要求"目标行为已实现 + 验证确实运行过 + 证据已记录 + 仓库可从标准路径重启 + 架构检查通过",这正是避免 Agent 过早宣布胜利(对应 講義 09 的主题)的工程化落地。
关键文件速查
| 关注点 | 路径 |
|---|---|
| 项目说明与 A/B 任务 | projects/project-04/README-CN.md |
| 结构化日志实现 | projects/project-04/solution/src/services/logger.ts |
| 架构边界文档 | projects/project-04/solution/docs/ARCHITECTURE.md |
| 架构检查脚本 | projects/project-04/solution/scripts/check-architecture.sh |
| 已修复的增量索引 | projects/project-04/solution/src/services/indexing-service.ts |
| 植入缺陷的对照组 | projects/project-04/starter/src/services/indexing-service.ts |
| 检索与 citation 日志锚点 | projects/project-04/solution/src/services/qa-service.ts |
| IPC 通道与共享类型 | projects/project-04/solution/src/shared/types.ts |
| 会话交接清单 | projects/project-04/solution/clean-state-checklist.md |
| Agent 运行纪律 | projects/project-04/solution/AGENTS.md |
关联讲义:講義 07:エージェントのタスク境界を明確に引く · 講義 08:機能リストでエージェントの作業を制約する。
小结:Project 04 用一次可重复的 A/B 实验,证明了 Harness 的核心主张——Agent 的修复速度与准确度,取决于它能否看见运行时真相、能否被边界约束住。结构化日志提供"反馈",架构脚本提供"约束",增量索引提供"可控的推进粒度";三者合一,才是让长运行 Agent 从"猜测式编码"走向"证据驱动修复"的最小闭环。
【免费下载链接】learn-harness-engineering
Harness engineering beginner tutorial, from 0 to 1
相关推荐
learn-harness-engineering Project 04 实战:用运行时反馈与架构约束修正 Agent 行为
learn harness engineering Project 04 实战:用运行时反馈与架构约束修正 Agent 行为 本项目(Project 04 ·
learn-harness-engineering 项目 04:用运行时反馈修正 Agent 行为——结构化日志、架构约束与增量索引实战
learn harness engineering 项目 04:用运行时反馈修正 Agent 行为——结构化日志、架构约束与增量索引实战 本篇技术指南以仓库 d
Harness 实战:用运行时反馈与架构约束纠正 Agent 行为——learn-harness-engineering Project 04 增量索引调试指南
Harness 实战:用运行时反馈与架构约束纠正 Agent 行为——learn harness engineering Project 04 增量索引调试指南
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考