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.
This commit is contained in:
AAA authored and GitHub committed 2026-08-27 11:54:54 +08:00
1 parent 3d960dc5bd
commit 6163cf389f
3 files changed
+131 -21

No files matched your search

+4 -2
View File
@@ -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 只做快速、确定、可复现的检查;工程判断留给评审和测试。
+22 -4
View File
@@ -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
+105 -15
View File
@@ -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;