提示词叠加层与系统提示组装: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 MetadataSession ID: omx-1717567890-abc123Timestamp: 2026-06-05T02:10:00Z
## Native Subagent RoutingUse `agent_type: "architect"` to invoke the architect agent.Use `agent_type: "code-reviewer"` to invoke the code reviewer....
## Compaction ProtocolBefore 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 ProtocolBefore 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.json3. Save progress ledger entries4. Update ralph-progress.json if active5. 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(引导合约文档)