模型负责「想」,harness 负责把想法变成现实。这门课一行行真代码把三件套 + 13 块积木搭出来——全部出自能跑的 index.js / src/,再拿它对照工业级 Claude Code。看得懂代码,才学得会怎么写。
一匹马力气很大,但不套上缰绳和马具,拉不了犁。大模型也一样——这套马具(工程上叫 harness)就是把它的智力引导成实际行动的那套装备。
export async function runAgent(userInput, messages, depth = 0) {
messages.push({ role: "user", content: userInput });
while (true) {
await compactIfNeeded(messages); // ⑥ 太长先压缩
const res = await withRetry(async () => { // ⑩ 失败自动重试
const stream = client.messages.stream({
model: MODEL, system: buildSystemPrompt(), // ④人设+⑦记忆
messages, tools: toolSchemas,
});
stream.on("text", (c) => process.stdout.write(c)); // ⑤流式
return await stream.finalMessage();
});
messages.push({ role: "assistant", content: res.content });
if (res.stop_reason !== "tool_use") return finalText; // 没工具→完成
// …执行工具(②③⑧⑨⑫),把结果喂回 messages,进下一轮…
}
}
{
name: "write_file",
description: "把内容写入文件(会覆盖原文件)",
input_schema: {
type: "object",
properties: { path: { type: "string" },
content: { type: "string" } },
required: ["path", "content"],
},
needsApproval: true, // 写文件有风险,交给护栏问人
run: ({ path, content }) => {
writeFileSync(path, content);
return `已写入 ${path}(${content.length} 字符)`;
},
}
export async function checkPermission(tool, input) {
const action = matchRule(tool.name, input); // ⑨ 先查规则表
if (action === "allow") return true; // 自动放行
if (action === "deny") return false; // 直接拒
if (action === null && !tool.needsApproval) return true;
if (sessionAllowlist.has(tool.name)) return true; // 记住过
const ans = await ask(`允许吗? (y / n / a=以后都允许) `);
const c = ans.trim().toLowerCase();
if (c === "a") { sessionAllowlist.add(tool.name); return true; }
return c === "y";
}
🔌 [MCP] 从 "local-env-info" 发现并注册了 2 个外部工具
✅ [规则] 自动放行:git status → action=allow
⛔ [规则] 直接拒绝:sudo … → action=deny
✅ [演示] 回答 a(以后都允许)→ 记住 write_file 本会话放行
🛡️ [Hook 拦截] 危险命令被硬拦:命令 "rm -rf ." 匹配毁灭性模式
🔀 [编排] 本轮 3 个只读工具并发、0 个写/危险工具串行
✅ node_version → v24.1.0 📋 [审计] 调用了 git_current_branch
✅ 存取闭环验证通过:存进去 23 条、读回来 23 条,内容一致
一条命令,把三件套 + 护栏硬拦 + 规则放行 + 并发编排 + MCP 发现 + 会话存取全转一遍——真读目录、真拦 rm、真写盘。
// prompt.js —— 你是谁
export const SYSTEM_PROMPT = `你是一个谨慎、靠谱的命令行助手。
行为准则:
1. 动手前先说清楚你打算做什么,再调工具。
2. 涉及写文件 / 删除 / 跑命令,明确告诉用户风险点。`;
// memory.js —— 每轮把「人设 + 长期记忆」拼起来喂模型
export function buildSystemPrompt() {
const memory = loadMemory(); // ⑦ 读 memory.md
if (!memory) return SYSTEM_PROMPT;
return `${SYSTEM_PROMPT}\n\n## 你的长期记忆\n${memory}`;
}
// ⑤ 流式:每蹦一块就立刻打印
stream.on("text", (c) => process.stdout.write(c));
// 子 Agent:派活给一个全新的分身
} else if (block.name === "spawn_agent") {
if (depth >= 2) {
resultText = "已达最大深度,不能再往下派";
} else {
const subMessages = []; // 全新独立上下文
resultText = await runAgent(block.input.task,
subMessages, depth + 1); // 递归
}
}
export async function compactIfNeeded(messages) {
if (messages.length <= MAX_HISTORY) return; // 没超就不动
let keepStart = Math.max(0, messages.length - KEEP_RECENT);
const oldMessages = messages.slice(0, keepStart);
const recentMessages = messages.slice(keepStart);
// …把 oldMessages 转成文本,调模型总结成 summary…
const summaryMsg = { role: "user",
content: `【以上对话摘要】\n${summary}` };
messages.splice(0, messages.length, // 原地替换
summaryMsg, ...recentMessages);
console.log(`📦 已把前面 ${oldMessages.length} 条压成摘要`);
}
// memory.js —— 读出长期记忆(读不动也不崩)
export function loadMemory() {
try {
if (!existsSync(MEMORY_FILE)) return "";
return readFileSync(MEMORY_FILE, "utf8").trim();
} catch { return ""; }
}
// tools.js —— remember 工具:往 memory.md 追加一行
run: ({ note }) => {
appendFileSync(MEMORY_FILE,
`- [${new Date().toLocaleString("zh-CN")}] ${note}\n`);
return `已记住:${note}`;
},
const dangerousCmdHook = (toolName, input) => {
if (toolName !== "run_bash") return null;
const cmd = String(input.command || "");
const dangerous = [
/rm\s+-rf?\s+\/[^a-z]/i, // rm -rf /
/rm\s+-rf?\s+\./i, // rm -rf .
/mkfs\s*\./i, /dd\s+if=/i, // 格式化 / 覆写磁盘
];
for (const re of dangerous)
if (re.test(cmd)) return { block: true, reason: "毁灭性命令" };
return null; // 放行(再交给护栏问人)
};
export const preToolHooks = [dangerousCmdHook, backupBeforeWriteHook];
export const permissionRules = [
{ tool: "run_bash",
match: (i) => /^\s*git\s+(status|diff|log)\b/.test(i.command),
action: "allow" }, // 只读 git 自动放行
{ tool: "run_bash",
match: (i) => /\bsudo\b/.test(i.command),
action: "deny" }, // sudo 直接拒,连问都不问
];
export function matchRule(toolName, input) {
for (const rule of permissionRules) { // 从头扫,第一条命中即返回
if (rule.tool !== toolName) continue;
if (rule.match && !rule.match(input)) continue;
return rule.action;
}
return null;
}
export async function withRetry(fn, opts = {}) {
const { retries = 3, baseDelay = 500,
isRetriable = isRetriableError } = opts;
for (let attempt = 0; attempt <= retries; attempt++) {
try { return await fn(); }
catch (err) {
if (attempt >= retries || !isRetriable(err)) throw err;
const delay = baseDelay * Math.pow(2, attempt)
+ Math.floor(Math.random() * 100); // 指数退避+抖动
await new Promise((r) => setTimeout(r, delay));
}
}
}
export function saveSession(messages) {
const jsonl = messages.map((m) => JSON.stringify(m)).join("\n");
writeFileSync(SESSION_FILE, jsonl, "utf8"); // 每条一行
}
export function loadSession() {
if (!existsSync(SESSION_FILE)) return [];
const raw = readFileSync(SESSION_FILE, "utf8").trim();
return raw.split("\n").reduce((acc, line) => {
try { acc.push(JSON.parse(line)); } // 坏行跳过,不崩
catch {}
return acc;
}, []);
}
const isReadOnly = (b) =>
toolMap[b.name]?.needsApproval === false &&
b.name !== "spawn_agent"; // 会问人,不并发
const readOnly = toolUseBlocks.filter(isReadOnly);
const serial = toolUseBlocks.filter((b) => !isReadOnly(b));
// 只读组:并发;写组:串行
const reads = await Promise.all(readOnly.map((b) => runOneTool(b, depth)));
for (const b of serial) results.push(await runOneTool(b, depth));
// 按原始顺序重组(否则喂回的顺序错乱→API 报错)
const toolResults = toolUseBlocks.map((b) => resultMap.get(b.id));
export const localMcpServer = {
name: "local-env-info",
tools: [
{ name: "node_version", cmd: "node --version", /*…*/ },
{ name: "git_current_branch", cmd: "git branch --show-current" },
],
};
export function loadMcpTools(server, tools) {
for (const t of server.tools)
tools.push({ name: t.name, description: t.description,
needsApproval: false,
run: () => execSync(t.cmd).toString().trim() }); // 包成内部工具
console.log(`🔌 [MCP] 发现并注册了 ${server.tools.length} 个外部工具`);
}
每块积木 = 一个独立 commit,git checkout 到任意节点,就能看「讲到这一块时代码长什么样」——学员照着一块块学。
同一套骨架,工业级只是每块换成更强实现 + 一层「把 React 渲染进终端」的界面。看懂这 ~1180 行,再翻 51 万行,你认得出每一块。