跳转到内容

提示词叠加层与系统提示组装:AGENTS.md 运行时注入

提示词叠加层与系统提示组装:AGENTS.md 运行时注入

学习目标

  • 理解 OMX 如何通过 HTML 注释标记实现 AGENTS.md 的运行时叠加层
  • 掌握叠加层注入内容的优先级排序和溢出保护
  • 了解代码地图生成与缓存策略
  • 理解会话作用域模型指令文件的三层合并

前置知识

本章涉及系统提示组装的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 Oh-My-Codex 的具体实现。


项目实践

AGENTS.md 作为”操作系统”

OMX 的 templates/AGENTS.md 是 Agent 的顶层操作合同,定义了:

  • <invocation_conventions>$name 调用工作流技能,/skills 浏览可用技能
  • 委托规则、子代理协议、模型路由、专家路由
  • 关键词回退行为

每次 Codex 会话启动时,OMX 在这个静态文件上注入运行时上下文。

叠加层注入流程

伪代码(src/hooks/agents-overlay.ts — generateOverlay):
function generateOverlay(options):
sections = []
totalChars = 0
MAX_CHARS = 3500
// Required 部分(总是注入)
sections.push(generateSessionMetadata(options.sessionId))
sections.push(generateNativeSubagentRouting())
sections.push(generateCompactionProtocol())
totalChars = sumLengths(sections)
// Optional 部分(按优先级注入,直到溢出)
optionalSections = [
{ gen: generateCodebaseMap, maxLen: 1000, label: "codebase" },
{ gen: generateActiveModes, maxLen: 500, label: "modes" },
{ gen: generatePriorityNotepad, maxLen: 300, label: "notepad" },
{ gen: generateProjectMemory, maxLen: 500, label: "project-memory" },
{ gen: generateTeamOrchestratorOverlay, maxLen: 400, label: "team" },
{ gen: generateExploreRouting, maxLen: 200, label: "explore" },
{ gen: generateRalphPlanningGate, maxLen: 200, label: "ralph-gate" },
]
for section in optionalSections:
content = section.gen()
if totalChars + content.length <= MAX_CHARS:
sections.push(content)
totalChars += content.length
else:
log(`Overlay overflow: skipping ${section.label}`)
break
return wrapWithMarkers(sections.join("\n\n"))

HTML 注释标记

AGENTS.md 叠加层示例:
<!-- OMX:RUNTIME:START -->
## Session Metadata
Session ID: omx-1717567890-abc123
Timestamp: 2026-06-05T02:10:00Z
## Native Subagent Routing
Use `agent_type: "architect"` to invoke the architect agent.
Use `agent_type: "code-reviewer"` to invoke the code reviewer.
...
## Compaction Protocol
Before context compaction, checkpoint all active states.
<!-- OMX:RUNTIME:END -->

幂等应用

伪代码:
function applyOverlayToFile(filePath, overlay):
content = read(filePath)
// 先剥离
content = content.replace(
/<!-- OMX:RUNTIME:START -->.*?<!-- OMX:RUNTIME:END -->/gs,
""
)
content = content.replace(
/<!-- OMX:TEAM:WORKER:START -->.*?<!-- OMX:TEAM:WORKER:END -->/gs,
""
)
// 再注入
content = content.trim() + "\n\n" + overlay
write(filePath, content)

代码地图生成

伪代码(src/hooks/codebase-map.ts — generateCodebaseMap):
function generateCodebaseMap():
mtime = getGitIndexMtime()
if cache.mtime == mtime:
return cache.map
files = run("git ls-files --cached")
dirs = groupByTopLevelDirectory(files)
map = ""
dirCount = 0
for dir in dirs:
if dirCount >= 14:
break
if dir in EXCLUDED_DIRS: // node_modules, .git, dist, etc.
continue
map += `## ${dir}/\n`
fileCount = 0
for file in dirs[dir]:
if fileCount >= 10:
map += ` ... and ${dirs[dir].length - 10} more\n`
break
map += ` ${file}\n`
fileCount++
dirCount++
cache.mtime = mtime
cache.map = truncate(map, 1000)
return cache.map

缓存依据:git 索引的 mtime 在 git add/git commit 时更新,代码未变更时缓存有效。

Ralph 规划门控

伪代码(generateRalphPlanningGate):
function generateRalphPlanningGate():
prdExists = findFiles(".omx/plans/", /prd-.*\.md/).length > 0
testSpecExists = findFiles(".omx/plans/", /test-spec-.*\.md/).length > 0
if prdExists and testSpecExists:
return "## Ralph Planning Gate\nStatus: UNLOCKED — PRD and test-spec artifacts exist. Ralph can proceed with implementation."
else:
return "## Ralph Planning Gate\nStatus: BLOCKED — No PRD found. Run $ralplan first before implementation. Do not implement code without an approved plan."

会话作用域模型指令文件

伪代码(writeSessionModelInstructionsFile):
function writeSessionModelInstructions(sessionId, options):
output = ".omx/state/sessions/${sessionId}/AGENTS.md"
// 合并三个来源
userAgents = readIfExists("~/.codex/AGENTS.md")
projectAgents = read("AGENTS.md") // 项目根目录
runtimeOverlay = generateOverlay(options)
// 阴影技能过滤
userSkills = extractSkillRefs(userAgents)
projectAgents = filterShadowedSkills(projectAgents, userSkills)
// 合并
content = ""
if userAgents:
content += userAgents + "\n\n"
content += projectAgents + "\n\n"
content += runtimeOverlay
write(output, content)

Codex 启动时被指向这个会话作用域的文件,而非源 AGENTS.md。这样源文件不被修改。

压缩协议

叠加层中的压缩协议指令:
"## Compaction Protocol
Before context compaction occurs, checkpoint all active states:
1. Write current mode state via `omx state write`
2. Record skill active state to .omx/state/skill-active-state.json
3. Save progress ledger entries
4. Update ralph-progress.json if active
5. Then proceed with compaction"

文件锁

伪代码:
function withOverlayLock(operation):
lockDir = ".omx/locks/overlay"
try:
mkdir(lockDir) // 原子操作,如果已存在则失败
catch EEXIST:
// 检查锁持有者是否还活着
pid = readPidFromLock(lockDir)
if not isProcessAlive(pid):
rmdir(lockDir) // 死 PID 回收
mkdir(lockDir)
else:
waitForLock(lockDir, timeout: 10s)
try:
return operation()
finally:
rmdir(lockDir)

问题与规避

问题后果OMX 的规避策略
叠加层标记不匹配源文件被污染严格使用 HTML 注释标记,正则剥离时 s 模式
代码地图超过上限叠加层被截断代码地图单独限制 1000 字符
并发写入竞争AGENTS.md 损坏目录级文件锁 + 死 PID 回收
压缩丢失状态Agent 忘记当前进度压缩协议强制先持久化状态
阴影技能冲突使用错误的技能定义合并时检测并删除项目级的阴影引用

设计取舍

为什么不直接修改 AGENTS.md 源文件?

OMX 选择在会话作用域的文件中注入运行时上下文,而非直接修改源 AGENTS.md

优势

  • 源文件清洁:不被运行时数据污染
  • Git 友好:源文件可以提交,不会被会话 ID 等动态内容干扰
  • 多会话隔离:每个会话有独立的叠加层,互不干扰
  • 幂等安全:重新应用不会产生重复内容

代价

  • 文件冗余:每次会话创建一个会话作用域的合并文件
  • 启动延迟:需要读取 → 合并 → 写入三个步骤

替代方案

  • 直接修改源文件 + 会话结束时回滚(风险高,容易污染)
  • 环境变量注入(Agent 可能不感知环境变量中的提示词)

参考来源

  • Oh-My-Codex v0.18.9 源码 — src/hooks/agents-overlay.ts(叠加层生成)
  • Oh-My-Codex v0.18.9 源码 — src/hooks/codebase-map.ts(代码地图生成)
  • Oh-My-Codex v0.18.9 源码 — templates/AGENTS.md(顶层操作合同)
  • Oh-My-Codex v0.18.9 源码 — docs/prompt-guidance-contract.md(引导合约文档)