Hand-Building a Skill Runtime: Scanning, Frontmatter Parsing, Injecting the System Prompt, Reading the Body on Demand
Actually build three-stage progressive disclosure: scan a directory to discover skills, parse frontmatter leniently, splice names and descriptions into a directory injected into the system prompt, and read the full body only on activation.
今日目标
- 能实现一个 skill 扫描器,处理作用域优先级与同名冲突
- 能宽松解析 SKILL.md 的 frontmatter,对畸形输入降级而不是崩溃
- 能把目录注入系统提示,并实现按需读取正文的激活入口
前四天你一直站在写 skill 的那一侧:怎么选题、描述怎么写、什么时候加脚本。今天换一次身份,去写那个读 skill 的程序。这不是为了让你造一个客户端,而是因为自己实现一遍之后,前四天每一条建议你都能指出它对应运行时的哪一行代码——为什么描述是唯一的触发面、为什么正文要控制在几百行、为什么名字必须等于目录名,全都会从「规范这么要求」变成「不这么做代码就跑不通」。读完正文、做完实验之后,回到页面顶部把三条目标勾掉。
小白版讲解
三阶段落到代码里就是三个函数
第一天讲档案柜时,你是那个来翻柜子的新人。今天你要去当管柜子的人:清点柜子里有哪些文件夹、把每个封面上那句话抄成一张索引卡贴在门口、有人问起某件事时才把对应那本抽出来递过去。
这三件事就是渐进式加载的三个阶段,落到代码里干干净净地对应三个函数。
发现:扫描约定的几个目录,找出所有含 SKILL.md 的文件夹,把每一份的元数据读出来。这一步在会话开始时跑一次。
披露:把所有 skill 的名字与描述拼成一份清单,塞进系统提示。正文一个字都不进来。 这份清单是模型判断「有没有该翻开的东西」的唯一依据。
激活:模型说要用某个 skill 了,才去读那一份完整的 SKILL.md,把正文放进上下文。
真正值钱的洞察藏在第二步和第三步之间:披露的成本是每一轮都要付的,激活的成本只付一次。 系统提示会跟着每一次请求重新发送,所以清单每多一个字,你都要乘以会话轮数。正文只在被激活的那一轮进上下文,之后作为历史消息留着,不会重复计费。这条不对称性解释了规范里几乎所有的硬约束——为什么描述有长度上限而正文没有,为什么描述必须写触发条件而不是使用说明。
扫描:找出所有含 SKILL.md 的文件夹
判定规则就一条:一个目录里有 SKILL.md,它就是一个 skill。没有注册表、没有清单文件、不用编译。
真正难的是遍历本身。三件事必须处理。
深度上限与跳过清单。 在一个真实项目里无脑递归,你会扫上几万个目录,其中绝大多数是 node_modules。设一个四五层的上限,再维护一个跳过集合,把 .git、node_modules、构建产物挡在外面。这不是优化,是能不能用的问题——扫描发生在会话启动路径上,慢两秒每次都要挨。
找到就停。 一个目录里有了 SKILL.md,就不要再往下钻了。skill 不嵌套,继续往下只会把它的 references/ 当成新的 skill 目录去试。
多作用域与同名冲突。 至少要扫两处:项目级和用户级。第二天讲过,跨客户端的通行约定是项目级压过用户级,但 Claude Code 的顺序是企业级、个人级、项目级由高到低——具体优先级要按你实现的那家来定,关键是固定一种并保持一致,不要每次看运气。
冲突的处理方式比冲突的方向更重要。最糟的做法是静默丢弃:用户改了项目里那份 skill,重启之后行为一点没变,因为生效的其实是用户级那份。他会怀疑文件没保存、怀疑客户端有缓存,就是不会想到有个同名的在别处。所以被遮蔽的时候必须留一条警告,而且要把两个路径都打出来——那条日志是排查这类问题的第一现场。
解析:宽松到什么程度
SKILL.md 的结构极简单:三横线夹一段 YAML,剩下全是正文。但你解析的是别人写的文件,畸形输入是常态。
这里要做一个关键决策:遇到不合规范的内容,是拒绝加载还是降级加载?答案是宽松,而且只有一个例外。
唯一的硬性淘汰是缺 description。 少了它,这个 skill 在发现阶段就没有触发面,永远不会被选中,留在清单里只是白占 token。这种直接跳过,并记一条错误级诊断。
其余一律只告警、仍然加载。 名字与目录名不一致、名字用了大写或下划线、描述超过上限——这些都影响质量,但不影响这个 skill 能不能用。你的运行时不是校验器,用户装这个 skill 是为了干活,不是为了通过检查。
最常见的畸形是 YAML 里的裸冒号。 一句 description: 当用户说:帮我提交时使用,中间那个全角或半角冒号会让正规解析器判定整行非法,进而拒绝整个文件。正确的兜底顺序是:先用完整的 YAML 解析器,失败了再退回一种「按行取值」的笨办法,只把认识的那几个标量字段抠出来。这样一个写错标点的 skill 仍然能用,而不是整个消失。
下面这段是运行时里最该看清楚的一小块:分离 frontmatter,然后按上面这套规则做宽松校验。注意每一处 continue 的意思都是「记下来,但放它过去」。
export type Diagnostic = { level: 'warn' | 'error'; path: string; message: string }
export type Skill = { name: string; description: string; location: string; body: string }
const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/
export function splitFrontmatter(raw: string): { front: string; body: string } | null {
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw)
return m ? { front: m[1], body: m[2].trim() } : null
}
// 按行取值的兜底:正规 YAML 解析失败时用它,专治值里没加引号的冒号
function readField(front: string, key: string): string | undefined {
const m = new RegExp(`^${key}:\\s*(.+)$`, 'm').exec(front)
return m?.[1].trim().replace(/^["']|["']$/g, '')
}
export function parseSkill(location: string, dir: string, raw: string) {
const diagnostics: Diagnostic[] = []
const parts = splitFrontmatter(raw)
if (!parts) return { skill: null, diagnostics: [{ level: 'error', path: location, message: '没有 frontmatter' }] }
const name = readField(parts.front, 'name') ?? dir
const description = readField(parts.front, 'description')
if (!description) {
// 唯一的硬性淘汰:没有描述就没有触发面,留着也永远不会被选中
diagnostics.push({ level: 'error', path: location, message: '缺少 description,跳过' })
return { skill: null, diagnostics }
}
// 下面三条只告警,仍然加载 —— 这就是宽松校验
if (name !== dir) diagnostics.push({ level: 'warn', path: location, message: `name 与目录名不一致:${name}` })
if (!NAME_RE.test(name)) diagnostics.push({ level: 'warn', path: location, message: `name 不符合命名规则` })
if (description.length > 1024) diagnostics.push({ level: 'warn', path: location, message: '描述超过上限' })
return { skill: { name, description, location, body: parts.body }, diagnostics }
}import re
from dataclasses import dataclass
NAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
FM_RE = re.compile(r"^---\r?\n(.*?)\r?\n---\r?\n?(.*)$", re.S)
@dataclass
class Skill:
name: str
description: str
location: str
body: str
def split_frontmatter(raw: str):
m = FM_RE.match(raw)
return (m.group(1), m.group(2).strip()) if m else None
def read_field(front: str, key: str):
# 按行取值的兜底:正规 YAML 解析失败时用它,专治值里没加引号的冒号
m = re.search(rf"^{key}:\s*(.+)$", front, re.M)
return m.group(1).strip().strip("\"'") if m else None
def parse_skill(location: str, dirname: str, raw: str):
diagnostics: list[dict] = []
parts = split_frontmatter(raw)
if parts is None:
return None, [{"level": "error", "path": location, "message": "没有 frontmatter"}]
front, body = parts
name = read_field(front, "name") or dirname
description = read_field(front, "description")
if not description:
# 唯一的硬性淘汰:没有描述就没有触发面,留着也永远不会被选中
diagnostics.append({"level": "error", "path": location, "message": "缺少 description,跳过"})
return None, diagnostics
# 下面三条只告警,仍然加载 —— 这就是宽松校验
if name != dirname:
diagnostics.append({"level": "warn", "path": location, "message": f"name 与目录名不一致:{name}"})
if not NAME_RE.match(name):
diagnostics.append({"level": "warn", "path": location, "message": "name 不符合命名规则"})
if len(description) > 1024:
diagnostics.append({"level": "warn", "path": location, "message": "描述超过上限"})
return Skill(name, description, location, body), diagnostics披露:目录是你唯一每一轮都要付的钱
发现阶段的产物是一份目录,注入系统提示。它长这样:
以下技能提供了特定任务的专门指令。
当任务命中某条 description 时,先读取对应 location 的文件再继续。
技能正文里的相对路径,按该技能目录解析。
<available_skills>
<skill>
<name>commit-message</name>
<description>当用户要提交代码、写提交信息时使用……</description>
<location>.agents/skills/commit-message/SKILL.md</location>
</skill>
</available_skills>三条设计要点。
每一项只有名字、描述、位置三样。 正文进来就等于取消了渐进式加载——你会为二十个 skill 的全部正文按每一轮重复付费,而其中十九个跟当前任务无关。位置这一项不能省:模型需要它才知道去读哪个文件,而且它的父目录就是正文里所有相对路径的解析基准。
一个 skill 都没有时整段省略。 给模型一份空清单,它只会困惑,还白花几十个 token 讲一段用不上的规则。
把这份清单的开销算出来打印在启动日志里。 这是一个非常便宜、又非常有用的习惯。你会立刻看见哪个 skill 的描述写得太长——一条描述占了整份目录的一半,说明它八成在描述里写使用说明,而不是写触发条件。粗算按两三个字符一个 token 估就够了,要精确再换各家的分词器。
激活:文件读取式与专用工具式
模型决定要用某个 skill 之后,正文怎么进上下文?两条路。
文件读取式:目录里给了 location,模型直接用它已有的读文件工具去读。好处是零新增机制,任何有文件读取能力的 Agent 都能立刻支持 skill;这也是为什么这个格式能在几十家客户端里铺开。代价是模型可能读错路径、可能只读了一半,你也没有一个明确的钩子去做去重和保护。
专用工具式:给模型一个叫「打开某个技能」的工具,参数是名字。好处是激活这件事变成了一次可观测、可拦截的调用——你能在这一步做去重、做权限检查、把技能目录和资源清单一起塞进返回值。代价是多一个工具定义,而且要求宿主愿意为 skill 单开一条通路。
没有绝对的对错,取舍点是你控不控得住宿主。 自己写 Agent 就用工具式,能拿到全部控制权;做一个要塞进别人客户端的通用实现,文件读取式的兼容性无可替代。
不管走哪条路,注入进去的那段内容都该用一个结构化标签包起来,并在里面补两样东西:技能目录的绝对路径(告诉模型相对路径按哪儿解析),以及 scripts/、references/、assets/ 三个目录下的文件名清单。
活得久一点:去重与压缩保护
前面几步跑通,一个演示就成立了。但会话一长,两个问题会冒出来。
重复激活。 模型可能在同一次会话里第二次选中同一个 skill——它忘了自己读过。同一段指令在上下文里出现两遍,既白花 token,又容易在两处措辞略有出入时互相干扰。修法很简单:维护一个已激活集合,命中就直接返回,不再注入。
被压缩掉。 长会话触发压缩时,早期消息会被摘要替换。skill 正文如果落在被压缩的区间里,不会报任何错——模型只是悄悄退回没有这个 skill 时的行为。用户看到的现象是「前面还好好的,聊到后面它又不按规范写了」,这是这套机制里最难查的一类问题。
所以激活出来的那条消息要带一个「受保护」的标记,压缩时整段保留或至少重新注入一次。这个标记本身很简单,难的是记得给它。压缩、记忆文件、子代理隔离这一整套长时程会话的手艺,姊妹课 5 天上下文工程的第 4 天 讲得系统得多,今天只需要知道:skill 正文是压缩时的高优先级保留项。
源码导读
今天的实验就是一份可以逐行读的运行时,六个文件各管一件事,加起来不到四百行。建议按这个顺序读。
parse.ts 是地基,也是最该抠细节的一份:分离 frontmatter、按行取值的兜底、以及那组只告警不淘汰的校验。看清楚唯一一处返回 null 的地方,它就是缺 description 那一条。
scan.ts 是遍历加冲突。findSkillDirs 里有两处容易漏:找到 SKILL.md 就 return(不再下钻),以及跳过集合与深度上限。scanSkills 按作用域顺序推进,先进来的赢,被遮蔽的记进 shadowed 并补一条 warn 级诊断。
catalog.ts 只有两个函数,buildCatalog 拼那份清单、catalogCost 逐个算开销。它短得几乎不像重点,但它是三阶段里唯一每轮重复的成本,值得你盯着输出看一会儿。
activate.ts 是激活入口:去重、包标签、列资源、打上受压缩保护的标记。listResources 只走三个约定目录并且只取文件名。
select.ts 是整个运行时唯一的网络出口。这里要注意一个设计纪律:选哪个 skill 是模型做的判断,不是运行时做的。运行时自己搞关键词匹配,等于把描述的语义判断退化成字符串命中,第三天讲的那套触发测试也就失去了意义。离线模式里的朴素匹配只是为了让流程能跑通,描述写得好不好,必须去掉离线开关用真模型验。
index.ts 把三阶段串起来跑一遍,末尾打印八项自检。先跑 solution 看八项全绿,再跑 starter 看它们怎么红的——五个练习点各对应哪几项自检,一目了然。
动手实验
fixtures/ 里那六个 skill 是故意写坏的:一个缺描述、一个名字与目录不符、一个描述里有裸冒号、一个只有 README、还有一对同名的用来演示遮蔽。跑之前先自己翻一遍,猜猜哪几个会被加载、哪几个只会留下告警,再跑起来对答案。
- 跑通 solution,看清扫描到几个 skill、目录长什么样、激活后正文进了哪一条消息。
- 在 starter 里补全扫描函数,处理项目级与用户级同名冲突并打印一条被遮蔽的警告。
- 补全 frontmatter 解析,让缺描述的被跳过、名字与目录不符的只告警。
- 补全目录注入与开销估算,让系统提示里出现结构化的可用 skill 清单。
- 补全激活入口的去重,验证只有被激活的那些正文进了上下文。
面试题
今天 3 道题在下方题库区,侧重运行时三阶段的实现要点、宽松解析与冲突处理、以及激活方式与上下文保护。展开后先看「分析过程」再看要点——照着推导练,比背要点管用。标注「国内高频 / 海外高频」方便按目标市场取舍。
检查清单与明日预告
- 能实现一个 skill 扫描器,处理作用域优先级与同名冲突
- 能宽松解析 SKILL.md 的 frontmatter,对畸形输入降级而不是崩溃
- 能把目录注入系统提示,并实现按需读取正文的激活入口
- 能说清「披露每轮付费、激活只付一次」这条不对称性,以及它解释了规范里的哪些约束
- 实验的 5 条验收标准全部通过
- 3 道面试题不看要点也能答出至少 2 道
明天(D6)从一个人的文件夹走到一支团队的能力包:一组 skill 怎么打成插件、市场怎么发布、版本怎么升,以及这门课欠了你五天的那张表——函数调用、MCP 与 Skills 三者到底各管什么、什么时候该用哪个。今天你已经从运行时的角度看清了 skill 是怎么被加载的,明天那张表会好懂很多。
Interview questions
If you implemented skill support in your own agent, what happens in the discovery stage versus the activation stage, and why split them?如果让你自己给一个 Agent 实现 skill 支持,发现阶段和激活阶段各要做什么?为什么要分成两步?
Common in ChinaCommon overseasIntermediate#agent-skills#runtime#progressive-disclosureHow to reason about it · think before answering
- This tests whether progressive disclosure is a mechanism you could build, not a slogan. Repeating the three stage names is not enough; say what each stage reads and where it writes.
- Discovery: scan the conventional directories, find every folder containing SKILL.md, parse out name and description, and assemble a catalog injected into the system prompt. No body text enters here; each entry carries only name, description and location.
- Activation: once the model judges that a task matches a description, read that full SKILL.md into context, along with the skill directory path and a list of bundled resource files.
- The reason for the split is an asymmetry in cost: disclosure is paid every turn, activation is paid once. The system prompt is resent with every request, so each extra character in the catalog is multiplied by the number of turns.
- That asymmetry also explains the spec's hard limits: descriptions are capped and bodies are not, and descriptions must state trigger conditions rather than usage instructions, because the description is the part that keeps costing money.
- Expected follow-up: can the location field be dropped? No. The model needs it to know which file to read, and its parent directory is the base for every relative path in the body.
分析过程 · 先想清楚再作答
- 这题在考你有没有把渐进式加载当成一个可实现的机制,而不是一句口号。只复述「发现、激活、执行」三个词是不够的,要落到每一步读了什么、写进了哪里。
- 发现:扫描约定目录,把所有含 SKILL.md 的文件夹找出来,解析出名字与描述,拼成一份清单注入系统提示。**这一步正文一个字都不进来**,清单里只有名字、描述、位置三样。
- 激活:模型判断当前任务命中了某条描述,才去读那一份完整的 SKILL.md,把正文放进上下文,同时告诉它技能目录在哪、附带哪些资源文件。
- 分两步的理由是成本结构不对称,这是本题的核心句:**披露的成本每一轮都要付,激活的成本只付一次。** 系统提示随每次请求重发,清单每多一个字都要乘会话轮数;正文只在被激活的那一轮进上下文,之后作为历史消息留着。
- 由这条不对称性可以顺手解释规范里的硬约束:为什么描述有长度上限而正文没有,为什么描述必须写触发条件而不是使用说明——描述是每轮都在花钱的那一段。
- 可预期的追问是「位置这一项能不能省」。不能:模型要靠它知道去读哪个文件,而且它的父目录是正文里所有相对路径的解析基准。
Key points
- Discovery scans directories, parses name and description, and injects a catalog into the system prompt with no body text.
- Activation reads the full SKILL.md and adds the skill directory plus a list of bundled resource filenames.
- The split exists because disclosure is paid every turn while activation is paid once.
- That asymmetry explains why descriptions are length-capped and must state triggers rather than usage.
- The location field is required: it is both the read target and the base for relative paths.
答题要点
- 发现阶段扫描目录、解析名字与描述、拼成清单注入系统提示,正文不进来。
- 激活阶段才读完整 SKILL.md,并附上技能目录与资源文件名清单。
- 分两步的根据是披露每轮付费、激活只付一次这条不对称性。
- 这条不对称性解释了描述为什么有长度上限、为什么要写触发条件而不是使用说明。
- 清单里位置字段不能省,它既是读取目标也是相对路径的解析基准。
When your runtime parses a SKILL.md that violates the spec, do you refuse to load it or degrade gracefully? And how do you handle a name collision across scopes?你的运行时解析到一份不合规范的 SKILL.md,是拒绝加载还是降级加载?另外,两个作用域里有同名 skill 时你怎么处理?
Common in ChinaCommon overseasIntermediate#agent-skills#runtime#error-handlingHow to reason about it · think before answering
- Both halves share one stance: a runtime exists to get work done, not to validate. State that first.
- For loose loading, give a decidable boundary. The only hard rejection is a missing description: without it the skill has no trigger surface, can never be selected, and only wastes catalog tokens.
- Everything else warns and still loads: a name that differs from the directory, a name using capitals or underscores, an over-long description. These hurt quality but not usability.
- Cite the most common malformation as evidence: an unquoted colon inside a YAML value makes a strict parser reject the whole file. The right fallback order is full YAML parsing first, then a line-wise field reader that extracts only the scalar fields you know.
- For collisions, the direction matters less than the handling. The cross-client convention is project over user, while Claude Code orders enterprise, personal, then project. Both are defensible; pick one and stay consistent.
- The worst handling is silent discard. The user edits the project copy, nothing changes, and they suspect caching or a failed save rather than a same-named skill elsewhere. Always log a warning that prints both paths.
- Expected follow-up: does loose loading let bad skills in? These are different layers. Looseness is format tolerance; safety comes from source trust and tool permissions, not from schema validation.
分析过程 · 先想清楚再作答
- 两个小问共用一个立场:**运行时是给人干活的,不是校验器。** 先把这句说出来,后面两半都好答。
- 宽松加载这一半要给出可判定的边界,不能只说「尽量宽松」。**唯一的硬性淘汰是缺 description**——少了它这个 skill 在发现阶段没有触发面,永远不会被选中,留在清单里只是白占 token。
- 其余一律只告警仍然加载:名字与目录名不一致、名字用了大写或下划线、描述超过上限。它们影响质量,不影响能不能用。
- 举一个最常见的畸形做证据:YAML 值里没加引号的冒号会让正规解析器判整行非法,进而拒绝整个文件。正确的兜底顺序是先用完整 YAML 解析,失败了再退回按行取值,只抠出认识的那几个标量字段。
- 同名冲突这一半,方向不是重点,**处理方式才是**。跨客户端通行约定是项目级压过用户级,但 Claude Code 的顺序是企业级、个人级、项目级由高到低,两种都合理,关键是固定一种并保持一致。
- 最糟的做法是静默丢弃:用户改了项目里那份,行为一点没变,他会去怀疑缓存和保存,就是不会想到别处有个同名的。**必须留一条警告并把两个路径都打出来**,那条日志是排查这类问题的第一现场。
- 可预期的追问是「宽松会不会把坏 skill 放进来」。答案是这两件事的层次不同:宽松说的是格式容错,安全靠的是来源信任与工具权限,不能拿格式校验当安全边界。
Key points
- A runtime is not a validator; degrade by default.
- The only hard rejection is a missing description, which leaves no trigger surface.
- Name mismatches, invalid names and over-long descriptions warn but still load.
- Parse with full YAML first, then fall back to line-wise field reading for unquoted colons.
- Fix one collision priority, keep it consistent, and never discard silently: log both paths.
答题要点
- 立场是运行时不是校验器,默认降级加载。
- 唯一硬性淘汰是缺 description,因为它没有触发面、永远不会被选中。
- 名字不一致、名字不合规、描述超长都只记诊断仍然加载。
- 解析顺序是先完整 YAML、失败再按行取值兜底,专治值里没加引号的冒号。
- 同名冲突要固定一种优先级并保持一致,绝不静默丢弃,警告里要带上两个路径。
Once a skill body is in context, how do you keep it effective across a long session? And would you activate skills by file read or by a dedicated tool?skill 的正文进了上下文之后,长会话里怎么保证它不失效?激活方式上文件读取和专用工具你会选哪个?
Common in ChinaCommon overseasDeep dive#agent-skills#runtime#long-sessionHow to reason about it · think before answering
- This is about the gap between a working demo and something you can ship. The first half is long-session failure modes, the second is the activation mechanism trade-off.
- Two long-session problems. Duplicate activation: the model forgets it already read the skill and selects it again, so the same instructions appear twice, wasting tokens and creating conflicts where the wording differs. Fix it with a set of already-activated names.
- The worse problem is compaction. Summarizing early messages can drop the skill body, and nothing errors: the model quietly reverts to its behavior without the skill. Users report that it stopped following the convention later in the conversation, and it is the hardest failure here to diagnose.
- The fix is to mark the activated message as protected so compaction preserves it, or to re-inject it afterward. The marker is trivial; remembering to set it is not.
- For the second half give criteria, not a preference. File-read activation adds no new mechanism, so any agent that can read files supports skills immediately, which is why the format spread across dozens of clients. The cost is no clean hook for dedup or protection, and the model can read the wrong path.
- A dedicated tool turns activation into an observable, interceptable call where you can dedupe, check permissions, and return the skill directory and resource list together. The cost is another tool definition and host cooperation. The criterion is whether you control the host.
- Expected follow-up: should resource files be read during activation? No, list filenames only. The value of three stages is that the third usually never happens.
分析过程 · 先想清楚再作答
- 这题考的是「演示能跑」和「上线能用」之间那段距离。前半是长会话的失效模式,后半是激活机制的取舍。
- 长会话有两个问题。第一个是重复激活:模型忘了自己读过,第二次又选中同一个 skill,同一段指令出现两遍既浪费又容易在措辞出入时互相干扰。修法是维护一个已激活集合,命中就直接返回。
- 第二个问题更要命——**被压缩掉**。压缩会把早期消息换成摘要,skill 正文落在那个区间里**不会报任何错**,模型只是悄悄退回没有这个 skill 的行为。用户看到的现象是「聊到后面它又不按规范写了」,这是这套机制里最难查的一类问题。
- 解法是给激活出来的那条消息打一个受保护标记,压缩时整段保留,或者在压缩后重新注入一次。标记本身很简单,难的是记得给它。
- 后半的取舍要给判据而不是偏好。文件读取式零新增机制,任何有读文件能力的 Agent 都能立刻支持,这正是这个格式能在几十家客户端铺开的原因;代价是没有明确钩子做去重和保护,模型还可能读错路径。
- 专用工具式把激活变成一次可观测可拦截的调用,能在这一步做去重、权限检查、连技能目录与资源清单一起返回;代价是多一个工具定义,且要求宿主愿意开这条通路。**判据是你控不控得住宿主**:自己写 Agent 用工具式,做通用实现用文件读取式。
- 可预期的追问是「资源文件要不要在激活时一起读进来」。不要,只列文件名。三阶段的全部价值就在于第三阶段大多数时候不会发生。
Key points
- Dedupe with a set of activated skills or the same instructions appear twice and conflict.
- Losing a skill body to compaction raises no error; the model silently reverts, which is the hardest failure to spot.
- Mark the activated message as compaction-protected, or re-inject after compaction.
- File-read activation adds no mechanism and has the best compatibility but offers no hook for dedup or protection.
- A dedicated tool is observable and interceptable; choose by whether you control the host, and in both cases list resource filenames without reading them.
答题要点
- 重复激活要靠已激活集合去重,否则同一段指令会出现两遍并互相干扰。
- 压缩掉 skill 正文不会报错,模型只会悄悄退回原行为,是最难查的失效。
- 激活出来的消息要打受保护标记,压缩时保留或事后重新注入。
- 文件读取式零新增机制、兼容性最好,但没有去重与保护的钩子。
- 专用工具式可观测可拦截,判据是你控不控得住宿主;两者都只列资源文件名,不预读内容。
Comments
Sign in to join the discussion
No comments yet — be the first.