From 6163cf389f25dc6ed252e9f0fe38766450584f0d Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:54:54 +0800 Subject: [PATCH] docs: calibrate agent context gates (#296) Use official Codex, Claude, and Hermes context guidance as advisory working budgets with bounded hard ceilings and actionable remediation. Local cross-platform repository hygiene checks pass; GitHub-hosted steps were not started because of the account billing state. --- docs/maintenance.md | 6 +- docs/research/agent-context-governance.md | 26 ++++- scripts/check-docs.mjs | 120 +++++++++++++++++++--- 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/docs/maintenance.md b/docs/maintenance.md index e9edd1e..16e8ebf 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -25,7 +25,9 @@ ## 顶级指令卫生 -[官方调研](./research/agent-context-governance.md)的共同结论是:顶层指令应是短而稳定的地图,流程和专业细节按需加载,并定期删除冲突或陈旧规则。因此本仓库的 `AGENTS.md` 保持在 100 行内,而不是把公开上限当作填充目标。模块特有规则只有在对应代码存在且每次读取确有必要时,才放到最近目录的 `AGENTS.md`;流程说明进入 `CONTRIBUTING.md`,当前工作进入 `docs/todo.md`,证据与理由进入其 owner。 +[官方调研](./research/agent-context-governance.md)的共同结论是:顶层指令应是短而稳定的地图,流程和专业细节按需加载,并定期删除冲突或陈旧规则。检查器把官方边界分成推荐线和硬顶:Codex 项目指令链在默认 32 KiB 上限的 80% 提示、到上限失败;Claude 的有效启动指令在官方建议的 200 行提示,并容许到 250 行的项目缓冲;Hermes 在动态下限 20,000 字符的 80% 提示、到下限失败。`CLAUDE.md` 导入的 `AGENTS.md` 计入有效行数;每次任务必读的 repo STS skill 使用同一保守包络。 + +推荐线只触发园艺提醒,不是压缩目标。硬顶失败时应把目录专属规则迁到嵌套或 path-scoped context,把流程和参考资料迁到按需 skill/文档,简化重复措辞,并删除已完成历史或陈旧结论;不要为了过闸损失仍有消费者的约束。模块特有规则只有在对应代码存在且每次读取确有必要时,才放到最近目录的 `AGENTS.md`;流程说明进入 `CONTRIBUTING.md`,当前工作进入 `docs/todo.md`,证据与理由进入其 owner。 ## 真相更新顺序 @@ -61,6 +63,6 @@ TURN 的减负证据由 [ADR-0005](./adr/0005-automatic-hybrid-media-routing.md) - 迭代中运行覆盖当前改动的最小定向检查;完整套件、真实浏览器矩阵、网络整形、跨设备、长时稳定性和部署检查在相关候选的验收边界批量运行。 - 文档改动默认运行本地 Markdown 链接、whitespace、diff 和 repository-hygiene 检查;不因无关文档变化重跑媒体基准。 -- 断链、缺失必需 owner 和格式损坏是 hard failure。每次注入的 `AGENTS.md`、repo STS skill 与只负责导入的 `CLAUDE.md` 也使用 hard context budget,避免规则稀释。Product、ADR、research、operations 等按需文档的行数/字节阈值只是 warning:它提示检查是否应拆分二级 owner、删除历史或移出过程信息,不要求为通过门禁压缩合理正文,也不是交付质量指标。 +- 断链、缺失必需 owner 和格式损坏是 hard failure。每次注入的有效 `AGENTS.md`/`CLAUDE.md` 上下文与每次必读的 repo STS skill 使用“推荐线 warning、硬顶 failure”,避免把建议值误当死线,也避免超过工具加载边界。Product、ADR、research、operations 等按需文档的行数/字节阈值始终只是 warning:它提示检查是否应拆分二级 owner、删除历史或移出过程信息,不要求为通过门禁压缩合理正文,也不是交付质量指标。 - 昂贵证据记录 commit、环境、结果和适用边界。只有相关路径、协议、配置、依赖、环境或门槛改变,或旧证据失败/含糊时才重跑。 - `.githooks/pre-commit`、`scripts/check-project-state.sh` 和 PowerShell 等价入口共享 `scripts/required-project-paths.txt`。Hook 只做快速、确定、可复现的检查;工程判断留给评审和测试。 diff --git a/docs/research/agent-context-governance.md b/docs/research/agent-context-governance.md index 28243ee..302f769 100644 --- a/docs/research/agent-context-governance.md +++ b/docs/research/agent-context-governance.md @@ -14,24 +14,42 @@ versioned knowledge base. Its reported failure mode for one large instruction file is the same one observed in Screener: scarce context is crowded out, every rule appears equally important, stale guidance accumulates, and ownership is hard to verify. The recommended corrective structure is progressive disclosure, -cross-links, and mechanical freshness/structure checks. +cross-links, and mechanical freshness/structure checks. Codex concatenates the +applicable project instruction chain until `project_doc_max_bytes`, whose +default is 32 KiB. Anthropic likewise recommends concise, specific project instructions and moves multi-step procedures or path-specific rules to on-demand skills or scoped rule -files. A size ceiling is not a target. +files. Claude Code's stated target is under 200 lines per `CLAUDE.md`; imported +files still enter the startup context, so an import is organization rather than +context reduction. Hermes distinguishes project conventions (`AGENTS.md`) from user preference, personality, and learned memory. Its persistent memory is deliberately bounded and curated; full session history remains searchable on demand rather than being injected into every prompt. Memory is a frozen session-start snapshot, so it -cannot replace current repository truth. Nested context is useful only when a -subtree has genuinely distinct rules. +cannot replace current repository truth. Its context-file limit is configurable +or dynamically derived with a 20,000-character floor, and it recommends staying +below that value and using nested context for genuinely distinct subtrees. Its +bounded-memory guidance starts consolidation at 80% rather than treating the +hard limit as the normal operating target. + +Screener therefore warns at the published recommendation or 80% of a loader +limit, then fails only at a real loading boundary or a small explicit buffer: +32 KiB for the Codex chain, 20,000 characters for Hermes, and 250 effective +Claude lines above the 200-line recommendation. The same envelope applies to the +repo STS skill because `AGENTS.md` requires it on every task. A failure tells the +maintainer to use nested/path-scoped context, on-demand skills or linked docs, +remove duplication and completed history, and delete stale conclusions; raising +the ceiling is not the default remedy. Sources: - [OpenAI Harness engineering](https://openai.com/index/harness-engineering/) +- [OpenAI Codex `AGENTS.md`](https://learn.chatgpt.com/docs/agent-configuration/agents-md) - [Claude Code memory](https://code.claude.com/docs/en/memory) - [Hermes file ownership](https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/which-file-does-what.md) +- [Hermes context files](https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/context-files.md) - [Hermes persistent memory](https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/memory.md) ### Documentation Shape diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index 6a5848e..1aba5f4 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -19,11 +19,64 @@ const failures = []; const warnings = []; const anchorCache = new Map(); -const exactHardBudgets = new Map([ - ["AGENTS.md", [100, 8_000]], - ["CLAUDE.md", [10, 1_000]], - [".agents/skills/stop-that-shit/SKILL.md", [140, 8_000]], -]); +const contextBudgetRemediation = + "Move directory-specific rules to nested or path-scoped context, move " + + "procedures and reference material to on-demand skills or linked docs, " + + "simplify repeated wording, and remove completed history or stale conclusions."; +const contextWarningRatio = 0.8; +const codexProjectDocMaxBytes = 32 * 1_024; +const claudeRecommendedLines = 200; +const hermesMinimumContextChars = 20_000; + +const agentInstructions = readFileSync(resolve(root, "AGENTS.md"), "utf8"); +const claudeWrapper = readFileSync(resolve(root, "CLAUDE.md"), "utf8").replace( + /^@AGENTS\.md\s*(?:\r?\n)?/u, + "", +); +const stsInstructions = readFileSync( + resolve(root, ".agents/skills/stop-that-shit/SKILL.md"), + "utf8", +); +const topLevelContextBudgets = [ + { + name: "Codex project instruction chain", + text: agentInstructions, + recommended: { + bytes: Math.floor(codexProjectDocMaxBytes * contextWarningRatio), + }, + hard: { bytes: codexProjectDocMaxBytes }, + }, + { + name: "Claude effective project instructions", + text: `${agentInstructions}\n${claudeWrapper}`, + recommended: { lines: claudeRecommendedLines }, + hard: { + lines: Math.ceil(claudeRecommendedLines / contextWarningRatio), + }, + }, + { + name: "Hermes root project context", + text: agentInstructions, + recommended: { + chars: Math.floor(hermesMinimumContextChars * contextWarningRatio), + }, + hard: { chars: hermesMinimumContextChars }, + }, + { + name: "Required stop-that-shit skill", + text: stsInstructions, + recommended: { + lines: claudeRecommendedLines, + chars: Math.floor(hermesMinimumContextChars * contextWarningRatio), + bytes: Math.floor(codexProjectDocMaxBytes * contextWarningRatio), + }, + hard: { + lines: Math.ceil(claudeRecommendedLines / contextWarningRatio), + chars: hermesMinimumContextChars, + bytes: codexProjectDocMaxBytes, + }, + }, +]; const exactWarningBudgets = new Map([ ["docs/README.md", [180, 16_000]], ["docs/deployment.md", [250, 20_000]], @@ -54,6 +107,8 @@ for (const file of markdownFiles) { for (const raw of targets) checkLink(file, absolute, raw); } +checkTopLevelContextBudgets(); + if (warnings.length > 0) { process.stderr.write(`${warnings.map((warning) => `warning: ${warning}`).join("\n")}\n`); } @@ -74,7 +129,6 @@ function checkFormat(file, text) { } function checkBudget(file, text) { - const hardBudget = exactHardBudgets.get(file); const warningBudget = exactWarningBudgets.get(file) ?? prefixWarningBudgets @@ -82,15 +136,6 @@ function checkBudget(file, text) { .map(([, maxLines, maxBytes]) => [maxLines, maxBytes])[0]; const lines = logicalLines(text); const bytes = Buffer.byteLength(text); - if (hardBudget) { - const [maxLines, maxBytes] = hardBudget; - if (lines > maxLines || bytes > maxBytes) { - failures.push( - `${file}: exceeds required ${maxLines}-line/${maxBytes}-byte ` + - `top-level context budget (${lines} lines, ${bytes} bytes)`, - ); - } - } if (warningBudget) { const [maxLines, maxBytes] = warningBudget; if (lines <= maxLines && bytes <= maxBytes) return; @@ -101,6 +146,51 @@ function checkBudget(file, text) { } } +function checkTopLevelContextBudgets() { + for (const budget of topLevelContextBudgets) { + const measures = { + lines: logicalLines(budget.text), + chars: [...budget.text].length, + bytes: Buffer.byteLength(budget.text), + }; + if (exceeds(measures, budget.hard)) { + failures.push( + `${budget.name}: exceeds hard context ceiling ${formatLimits(budget.hard)} ` + + `(${formatUsage(measures, budget.hard)}). ${contextBudgetRemediation}`, + ); + continue; + } + if (exceeds(measures, budget.recommended)) { + warnings.push( + `${budget.name}: exceeds recommended working budget ` + + `${formatLimits(budget.recommended)} ` + + `(${formatUsage(measures, budget.recommended)}). ` + + `${contextBudgetRemediation}`, + ); + } + } +} + +function exceeds(measures, limits) { + return Object.entries(limits).some(([metric, limit]) => measures[metric] > limit); +} + +function formatLimits(limits) { + return Object.entries(limits) + .map(([metric, limit]) => `${limit} ${metricLabel(metric)}`) + .join(" / "); +} + +function formatUsage(measures, limits) { + return Object.keys(limits) + .map((metric) => `${measures[metric]} ${metricLabel(metric)}`) + .join(", "); +} + +function metricLabel(metric) { + return metric === "bytes" ? "UTF-8 bytes" : metric; +} + function checkLink(file, sourcePath, raw) { const target = linkTarget(raw); if (!target || isExternal(target)) return;