项目上下文
你的Harness是通用的。它涉及的每个项目都不是。
有一个项目使用bun test。下一个用的是vitest。第三种用npm run check来做typechecklint组合,完全不符合你在提示词中设定的任何惯例。Agent不应该需要猜测,你也不应该需要一个项目一个项目地教它。
大多数生产Harness使用的技巧和团队对人类贡献者用的一样:在仓库里放一个 markdown 文件,解释项目的工作原理。Agent会读取并调整。
学习成果
如果工作目录中存在AGENTS.md文件,Harness会读取该文件并将其内容注入系统提示词中,作为“项目指令”部分。没有文件时,提示词只回退到基础指令。
快速路径
- 检查
AGENTS.md里有没有工作目录 - 如果有,读成字符串
- 把它当作
buildSystemPrompt传给projectContext
动手练习 3.4
发现并注入工作目录AGENTS.md。
要求:
index.ts时,检查cwd/AGENTS.md是否存在- 如果有,就读作UTF-8
- 将内容(或
undefined)作为buildSystemPrompt传递给projectContext - 确认提示词构建器现有的
projectContext处理在有该部分时添加,缺席时省略
实现提示:
node:fs的existsSync在这里没问题。检查只在启动时进行一次,而不是在热循环中- 使用
path.join(cwd, "AGENTS.md"),而非模板文字,以保持路径处理的一致性 - 暂时不要添加单仓库目录巡回。下面的挑战涵盖了这一点,模块4的沙箱抽象无论如何都会改变文件发现的工作方式
发现经过
整个故事只有几句话:
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
const agentsPath = join(cwd, "AGENTS.md");
const projectContext = existsSync(agentsPath)
? readFileSync(agentsPath, "utf-8")
: undefined;把它传给构建器:
const instructions = buildSystemPrompt({
workingDirectory: cwd,
sandboxType: "local",
toolNames: Object.keys(tools),
projectContext,
});就这些。约定胜于配置。没有插件系统,没有注册,没有事件总线。一个已知位置的文件。
AGENTS.md里会有什么
这个文件是用于 harness 无法从代码中推断出的项目特定事实:
# Project Instructions
## Commands
- `bun test` runs the test suite
- `bun run build` builds for production
- `bun run lint` checks code style
## Architecture
- Monorepo, packages live in `packages/`
- Each package has its own `tsconfig.json`
- Shared types in `packages/shared/`
## Style
- Functional components, no classes
- Named exports, not default
- Error messages must be user-facing
## Lessons learned
- Auth middleware must run before rate limiting
- Don't modify migration files directly, generate new onesAgent读到这些后做出调整。它知道指令。它熟悉架构。它了解项目中反复出现的错误。现在,这种Harness在React项目中表现得像React项目Agent,在CLI项目中像CLI项目Agent,因为项目本身告诉Agent自己是哪种项目。
**注意:一个熟悉的模式,文件名不同**
这和Cursor用在.cursorrules、Codex用AGENTS.md、Claude用代码CLAUDE.md、用π用自己约定的技巧一样。文件名会有所不同。模式没有。发现一个 markdown 文件,注入为指令。
为什么现在一个文件就足够了
真正的Harness会在目录中父 Agent单存储库中寻找AGENTS.md文件。它会把它们合并。它也可能支持.cursorrules、.github/copilot-instructions.md或~/.agents/default.md。
我们还没有构建这些。工作目录里有一个文件涵盖了教学点:项目上下文来自文件,Harness在启动时发现,提示词吸收。行走和合体叠加在上面,形状不变。
动手试试
在工作目录中输入一个AGENTS.md,Agent无法从代码中推断出具体指令:
# Project Instructions
- All commits must use the format `feat(scope): message`
- The verification step is `bun test`, not `npm test`运行Agent:
bun run index.ts . "What command do I use to run the tests in this project?"Agent应该回答,因为档案里已经说明了bun test。移除AGENTS.md,继续运行同样的提示词。Agent会猜(大概率npm test)。
npx tsc --noEmit提交
git add index.ts
git commit -m "feat(prompt): inject AGENTS.md as project context"完成标准
- [ ]
index.ts检查AGENTS.md的工作目录 - [ ] 如果存在,其内容会作为
buildSystemPrompt传递给projectContext - [ ] 在有
AGENTS.md的情况下,Agent会从文件中回答项目相关的具体问题 - [ ] 没有
AGENTS.md,Harness仅使用基础指令运行,没有错误 - [ ]
npx tsc --noEmit
**注意:单存储库的目录行进**
真正的单仓库在根节点有一个AGENTS.md,每个包内部也有另一个。从cwd开始,走到仓库根点(找.git),沿途收集所有AGENTS.md。合并它们,最深的文件覆盖或扩展根节点。现在想想冲突:当根节点显示use npm而包显示use pnpm时会发生什么?不同的Harness解决这个问题的方式不同。 Pi会合并所有找到的东西。Cursor只用最深的。Codex将根和 cwd 连接起来。试试一种策略,注意它哪里出了问题。
参考实现
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { buildSystemPrompt } from "./src/system";
const cwd = resolve(process.argv[2] || process.cwd());
const agentsPath = join(cwd, "AGENTS.md");
const projectContext = existsSync(agentsPath)
? readFileSync(agentsPath, "utf-8")
: undefined;
const tools = { read, grep, bash };
const agent = new ToolLoopAgent({
model: "anthropic/claude-haiku-4-5",
instructions: buildSystemPrompt({
workingDirectory: cwd,
sandboxType: "local",
toolNames: Object.keys(tools),
projectContext,
}),
tools,
stopWhen: stepCountIs(10),
});