搜索引擎里搜"AI Skill 怎么写",出来的教程大部分只教你怎么写 CLAUDE.md。好一点的教你怎么写 SKILL.md 的 description 字段和 ## 规则 部分。但真正用好 Skill 的人都知道——Skill 不是一篇 prompt,是一套工程系统。

这篇文章把这套系统拆开讲。每个模块解决一个真实的问题,每个问题都来自实际的踩坑经验。看完之后,你的 Skill 不会只是"有时候好用有时候不行",而是"每次任务稳定触发、行为可预期、越用越好"。


一、Skill 是什么,不是什么

先把概念说清楚。

你让 AI 写代码,AI 看到的全部信息就是它的上下文窗口。上下文窗口里的内容分两类:一类是始终在线的,一类是触发了才加载的。这两类各有各的入口,各有各的用途。

机制入口加载方式用途
项目指令AGENTS.md / CLAUDE.md始终在线代码规范、构建命令、项目约定
技能SKILL.md按需触发专项工作流(部署、审查、测试生成)
外部工具MCP按需调用数据库、API、第三方服务
子智能体subagent按需派遣独立子任务隔离执行
分发包插件按需安装跨项目复用技能包

问题在于:很多人在"项目指令"里塞了全部东西。代码规范、部署流程、审查清单、测试模版——全扔进一个 CLAUDE.md。后果是 AI 不管做什么任务都背着一堆跟当前工作无关的信息,上下文窗口被挤满。等它真正需要记住类型约束或构建命令时,已经想不起来了。

Skill 的机制不同。它用渐进式加载,启动时只暴露 name 和 description。匹配到任务之后才读完整内容。不会白白占用上下文。这套机制在 agentskills.io 上有完整的开放标准定义,所有主流平台都遵循。

五层机制区分——不是所有东西都该塞进 AGENTS.md

下面从零开始构建一套 Skill 工程系统。


二、三根柱子:Prompt × Context × Harness

Skill 能不能工作,不只看你写的规则好不好。三个维度同时决定了效果:

  • Prompt(指令):定义 AI 做什么。写什么规则,给什么约束,输出什么格式。
  • Context(上下文):决定 AI 能看到什么。你写的规则再好,如果在 AI 执行的那一刻看不到,就等于没写。
  • Harness(验证层):度量改了之后变好了还是变坏了。没有这一层,你永远在猜"是不是 description 写错了"。

大多数人 90% 的时间花在 Prompt 上:反复调规则措辞、加约束、改步骤。发现不对又回去调 Prompt。

问题往往不在 Prompt。可能规则写了但 Context 太挤被挤掉了。可能 Agent 跨多轮任务时走了错误的路由分支。可能 /compact 压缩后关键指令消失了。可能换了一个工具入口(Claude Code → Cursor)根本没有读到同一份 Skill。

Harness 就是用来把这些变成"有据可查"的东西。


三、description:只有一次机会

SKILL.md 的 frontmatter:

---
name: code-review
description: 代码审查工具
---

这是最容易被写废的一行。

AI 启动时扫描所有 Skill,记住它们的 name 和 description。用户说话之后,用说的话去和每条 description 做匹配。匹配上了,去读完整的 SKILL.md。没匹配上,Skill 永远不会被加载。

“代码审查工具"是分类标题,不是触发条件。用户说"帮我看看这段代码”,AI 不会把"看看"理解成"审查"。跨度太大。

agentskills.io 的原话:“The description carries the entire burden of triggering. If the description doesn’t convey when the skill is useful, the agent won’t know to reach for it.”

改为:

description: >
  代码审查。当用户要求"review 代码"、"审查"、"检查代码"、
  "看看有没有问题"、"过一遍代码"、"code review"时激活。

多列具体说法,覆盖用户怎么说,而不是你想它怎么说。

还有一个细节:Skill 列表只能占模型上下文的大约 2%。如果列表太长,后面的 description 可能被截断。所以最关键的触发词必须在最前面。

反面案例:过度宽泛导致误触发

触发词不是越多越好。如果你写的 description 是"当用户提到代码、质量、检查、review、审查、查看、分析、改 bug、优化、重构时激活",AI 在大量无关场景下也会触发这个 Skill。用户说"帮我检查一下网络配置"命中"检查",说"审查一下账户权限"命中了"审查"。这些都不是代码审查,但 Skill 被激活了。

agentskills.io 把这类叫"near-misses"——共享了关键词但不需要这个 Skill 的场景。测试时不仅要测"该触发时触发了吗",还要测"不该触发时有没有误触发"。

description 不是分类标题,是触发条件

显式调用模式

有些 Skill 只应该在用户明确说的时候才激活,比如部署。可以在 SKILL.md 旁边加 agents/openai.yaml

policy:
  allow_implicit_invocation: false

设成 false 后,只有 $skill-name(Codex CLI)或 /skill-name(Claude Code)的显式调用才会激活。


四、解剖一个 SKILL.md

SKILL.md 不是百科全书。它是一张路由表,告诉 AI “什么情况读什么文件”。

一个完整的 SKILL.md 有四个模块:

---
name: code-review
description: >
  代码审查。当用户要求审查代码、检查代码质量、
  检查变更、review PR 时激活。
primary: true
---

## Always Read

每次任务必须读以下文件:
1. references/checklist-base.md
2. rules/coding-standards.md

## Session Discipline

每个新任务——即使是同一会话的第 N 轮——必须重读本文件,
重新匹配 Task Routing,重读该路由列出的所有必读文件。
"I already read it" 不是有效的跳过理由。

## Task Routing

根据任务类型读对应文件:
- 前端代码 → references/frontend-checklist.md
- 后端代码 → references/backend-checklist.md
- 部署脚本 → references/deploy-rules.md
- 其他任务 → 只读 Always Read

## Known Gotchas

- Filter 必须在 app init 之前注册,否则首次渲染空白
  → see references/gotchas.md#filter-registration
- 弹窗 Tabs + service 只打首层接口
  → see references/gotchas.md#nested-service-tabs

逐个板块说。

Always Read:放任何任务都必须遵守的基础约束,控制在 2-3 个文件。领域特定规则不放在这里,交给 Task Routing 按需加载。

Session Discipline:这是很多人忽略的一个要点。同一会话里 AI 处理了多轮任务,第一轮读了 SKILL.md,第二轮就凭记忆干了。但第二轮的任务类型可能完全不同,需要的路由文件也不一样。AI 的记忆——尤其是在上下文压缩之后——靠不住。Session Discipline 强制每个新任务走一遍完整路由。“I already read it” 被显式声明为无效理由。

Task Routing:5-10 条规则,每条必须带精确的文件路径。最后必须有兜底规则(“其他任务 → 只读 Always Read”),否则 AI 在路由表里找不到匹配时会随机行动。

Known Gotchas:价值密度最高的板块。只放坑点的一句话摘要 + 锚点,详细说明放 references。为什么不能全量放?——SKILL.md 是路由表,不是坑点百科,太长会稀释注意力。为什么不能只放 references?——只放 references 的话,AI 在任务路径上永远不会"碰巧"看到这些坑,除非触发了对应路由。


五、三层渐进式加载

全部内容堆在一个文件里,AI 读到后面的能力越来越差。这是 Transformer 注意力机制决定的,不是幻觉。

渐进式加载分三层:

第一层:name + description。 AI 启动时只加载这个。用于判断"要不要激活这个 Skill"。

第二层:SKILL.md 正文。 被激活后才加载。Always Read + Session Discipline + Task Routing + Known Gotchas。路由表和关键摘要。

第三层:references/ 和 rules/。 在 Task Routing 里被明确引用了才加载。领域检查清单、部署规则、详细 gotcha 说明。

渐进式加载:不是全塞进去,是分层加载

典型目录结构:

code-review/
├── SKILL.md
├── references/
│   ├── frontend-checklist.md
│   ├── backend-checklist.md
│   └── gotchas.md
├── rules/
│   └── coding-standards.md
└── scripts/
    └── check-secrets.sh

scripts/ 的使用条件: 只在需要确定性行为或外部依赖时才用。一个 shell 脚本扫描代码里的 API key 泄露,每次输出一致,适合 scripts。格式校验和检查清单这种需要模型判断的东西,留给指令。

agentskills.io 有个重要提醒:Skill 引用了一个工具但当前平台没有这个工具,它会静默失败——不报错,用散文替代。如果 Skill 要跨平台(Claude Code + Cursor + Codex CLI),scripts/ 里依赖的工具必须在每个平台上都可用。


六、Session 纪律:为什么读到的东西会消失

先看一个真实场景:

第 1 轮:用户说"帮我修一下 UserService 里的空指针 bug"
  → AI 读 SKILL.md
  → 匹配 Task Routing 的 "修 bug" → 读 rules/fix-bug.md
  → 按 workflow 修好

第 4 轮:用户说"顺便加个导出 Excel 的接口"
  → AI 觉得"我知道这个项目的规则了"
  → 跳过 SKILL.md
  → 直接开写 Controller

但是:
- "加导出接口"实际匹配的是 Task Routing 里的 "加 Controller"
- 对应的 rules/backend-rules.md 里有一条:"导出必须走 async 队列,同步响应会超时"
- AI 没读到这条 → 写了同步导出
- 小数据量测试通过,生产数据量一大直接超时

问题根源不是规则没写。规则一直在那里。问题是 AI 跨任务时没重走路由。

三个原因叠加导致失效:

  1. 跨任务记忆污染:第 1 轮的路由结果被当成"所有任务都该这么走"。
  2. 上下文压缩:/compact 触发后,SKILL.md 被压缩成摘要,文件路径信息丢失。
  3. 平台差异:某些工具在长会话里会丢弃早期系统指令。

解法:Section Discipline 必须在 SKILL.md 里显式声明,不能只靠"记得这件事"。同时还需要在下一步的薄壳里再加一层冗余——因为 /compact 甚至可能把 SKILL.md 本身的 Session Discipline 也压掉。

同一会话第 N 轮,你写的规则还在吗?


七、薄壳:跨工具兼容的最后防线

一个 Skill 要在 Claude Code、Cursor、Codex CLI、Gemini CLI 里都能用。最笨的办法是把 SKILL.md 复制四份,各自放在对应工具的目录下。但这样改了任何一条规则都要同步四个地方,早晚会漂移。

解法是在每个工具的入口文件里放一层薄壳。薄壳不包含完整规则,只包含三样东西:路由表、自动触发器列表、Red Flags 拦截信号。

以 Claude Code 为例,项目根目录的 CLAUDE.md(薄壳):

# CLAUDE.md

Formal docs live under skills/. Read skills/*/SKILL.md — default to
primary: true skill; only switch when task clearly matches another.

## Quick Routing (survives context truncation)

| Task | Required reads | Workflow |
|------|---------------|----------|
| Fix bug | rules/project-rules.md + rules/coding-standards.md | workflows/fix-bug.md |
| Add API endpoint | rules/backend-rules.md | workflows/add-controller.md |
| Multi-subtask (≥3 independent) | rules/project-rules.md | workflows/subagent-driven.md |
| Other | rules/project-rules.md + rules/coding-standards.md | Check workflows/ for closest match |

## Auto-Triggers

- New task in same session → re-read skills/*/SKILL.md, re-match Task Routing,
  re-read all required files. "I already read it" is not valid.
- Before declaring any non-trivial task complete → run Task Closure Protocol.
- Skip only for: formatting-only, comment-only, dependency-version-only,
  behavior-preserving refactors.

## Red Flags — STOP

- "Just this once I'll skip the AAR" → stop
- Task declared "complete" without running 30-second post-task scan → stop
- Same class of bug fixed twice but rules not updated → stop

薄壳设计的核心逻辑:不写"去读 SKILL.md",因为 /compact 之后自然语言指令会被当成普通描述丢弃。但结构化表格(路由表)、清单(Auto-Triggers)、前置拦截(Red Flags)在压缩后留存的概率更高。Agent 拿到新任务时可以当场查表。

反例——常见的错误写法:

# CLAUDE.md
Please read skills/my-skill/SKILL.md before starting any task.
It has all the rules and workflows you need.

短会话里能用。长会话 /compact 后,这行字被摘要掉了。Agent 看到新任务,没有路由表可查,凭感觉动手。输出看起来合理,只是少了几条关键约束。用户察觉不到,直到出 bug。

各工具的薄壳入口:

工具薄壳文件
Claude Code项目根 CLAUDE.md
Cursor.cursor/rules/workflow.mdc + .cursor/skills/{name}/SKILL.md
Codex CLI项目根 AGENTS.md + .codex/instructions.md
Gemini CLI项目根 GEMINI.md

每个工具的薄壳格式略有不同,但内容结构相同:Quick Routing + Auto-Triggers + Red Flags。

薄壳三层防线:不是一层,是三层才扛得住


八、对抗上下文压缩:SessionStart Hook

薄壳扛住了 /compact 后的场景,但 /clear 直接把上下文擦干净了,薄壳也得从磁盘重读。

可以在 Claude Code / Cursor 里配置 SessionStart hook:

#!/bin/bash
# session-start.sh — 在 startup / clear / compact 时自动重注 SKILL.md

skill_md=$(find skills/*/SKILL.md | head -1)
if [ -z "$skill_md" ]; then exit 0; fi
content=$(jq -Rs '.' "$skill_md")
echo "{\"prompt\": $content}"  # Claude Code 特定的注入格式

这个脚本在三个事件触发:startup(新会话)、clear(用户清上下文)、compact(自动压缩)。每次触发时自动读取 SKILL.md 并注回上下文。

Hook 不是万能的。它只负责把 SKILL.md 塞回去,不保证 Agent 会认真读。也不保证 Agent 按照 Task Routing 走了正确分支。那是 Session Discipline 和薄壳 Routing Table 的职责。

三者分工:

  • Session Discipline(SKILL.md 里):跨任务时的重读触发逻辑
  • 薄壳 Routing Table(CLAUDE.md 等入口里):压缩后仍可查的路由兜底
  • SessionStart hook:清空/压缩后自动把 SKILL.md 重新加载

三层叠加才能扛住长会话 + 多任务 + 多次 compact 的真实工作流。


九、任务闭环:做完不意味着做完了

AI 经常把"代码写完 + 测试跑通"当成任务结束。但真正的结束还差一步:扫一遍刚才的工作,有没有踩到新坑、发现新规则、暴露已有规则的漏洞。

完整闭环包含四个步骤:

步骤一:AAR 扫描(30 秒)

每完成一个任务,回答四个问题:

  • 用到了没有记录的 pattern 或约定?
  • 碰到了不提前知道就会浪费大量时间的陷阱?
  • 因为缺少某条规则导致了弯路?
  • 已有规则已经不准确了吗?

任何一个答案是"是",就往下走。全部"否",到此结束。

步骤二:录入标准(2/3 门槛)

不是所有发现都值得记。三条过滤标准:

  • 可重复吗?(换个时间、换个人,还会再踩吗)
  • 代价高吗?(踩一次导致的调试时间 ≥ 30 分钟 或 影响了生产)
  • 代码不可见吗?(从代码本身看不出,依赖时序、配置或暗知识)

至少 2/3 通过才录入。

一个通过的例子:“Filter 必须在 app init 之前注册,否则首次渲染空白”。可重复(每个新页面都可能犯),代价高(30 分钟以上的调试),代码不可见(时序依赖从静态代码看不出来)。3/3,录入。

一个不通过的例子:“Atom 命名约定用 xxxAtom 后缀”。可重复(是),代价不高(命名不一致不会造成 bug),代码可见(已有 atom 已经展示了模式)。1/3,不录入。

步骤三:录入后激活

光记下来不够。必须在 Agent 下次走正常任务路径时会自然"撞见"它:

  • 高代价陷阱 → 同时出现在 SKILL.md Known Gotchas 和对应 routing 的 workflow 里
  • 新增规则 → 更新对应 rules/ 文件,确保 Task Routing 能路由到
  • 宽泛教训 → 泛化成通用描述,加进 references/

判断标准:下次 Agent 为类似任务走 Task Routing 时,能读到这一条吗?不能 => 只是"存了",还没"生效"。

步骤四:Red Flags 前置拦截

以下情况出现时立刻停下:

  • 发现自己在想"这次 AAR 就算了"
  • 任务声明完成但没跑 30 秒扫描
  • 把坑写进了 reference 但没更新对应的 routing
  • 同一类 bug 修了两次但规则文件没变化

Red Flags 必须同时出现在薄壳里,因为 workflow 文件在压缩后会丢失,薄壳是最后一道防线。

AAR 闭环:做完不意味着做完了


十、不要让 Skill 变成日记本

Agent 在执行 AAR 时容易过度解读"记录"——把整个会话存档成一个 markdown 文件,扔进 references/。一个月下来,references/ 下出现了 2026-04-14-session-notes.md2026-04-15-debugging-log.md 等十多份同质文件。

这种文件会毁掉 Skill 的可维护性。它不是规则、不是工作流、不是可复用知识——它是项目叙事。

位置判断表:

内容类型目标位置
稳定约束 / 通用规则rules/
陷阱、架构坑、生命周期依赖references/
有序步骤 / 检查清单workflows/
会话历史 / 调试过程不写进 Skill

如果确实需要会话日志,放在 docs/ 而不是 references/。Skill 不是 git 的替代品。


十一、怎么测

AAR 回答了"这次任务学到了什么"。但 Skill 本身怎么测?

测触发率。 每次改 description 之后,开新对话,逐条输入准备的目标语句。记录激活次数。一半以上没激活,回去改。

脚本化更好——写一个 test-trigger.sh,从 Task Routing 的每个条目自动生成用户可能说的 prompt,批量跑验证。这样每次改完 description 之后跑一下就知道是不是变好了。

测结构中低级错误。 写一个 smoke-test.sh,自动检查:

  • Task Routing 里引用的每个文件是否真的存在
  • SKILL.md 的 description 和所有薄壳入口的 description 是否一致
  • {{NAME}} 之类的占位符是否还有残留
  • SKILL.md 行数是否超标
  • 每个薄壳入口文件是否都包含 Quick Routing 表

这些检查不需要 AI。一个 shell 脚本就能做完。80% 的翻车来自路径写错、文件没建、入口不一致这类遗忘型问题。

测真实任务。 用自己项目的实际代码跑一次完整的 Skill 执行。肉眼过输出,看 AI 遵守了每一条 checklist 没有,有没有编造规则,有没有绕过步骤。

脚本能抓遗忘,真实任务能抓理解偏差。两层都过,Skill 才算验收合格。


十二、一个 Skill 只做一件事

Skill 用久了会膨胀。description 列了 10 多个互不相关的触发词,common tasks 有 15 条以上,gotchas 文件按领域自然分成两半。

这时候该拆了。

拆之前确认三个信号:两个领域的 Task Routing 完全没有交集,description 要覆盖的场景已经跨领域了,坑点文件按领域已经自然分裂。三个信号任意一个出现,拆。

拆分后每个 Skill 的 SKILL.md 各自控制在 ≤100 行,description 各自精准。AI 激活的是"正好对应当前任务的那一个",而不是"什么都管的那个"。

多 Skill 共存时,primary: true 标记默认 Skill。SessionStart hook 自动读 primary Skill 的 SKILL.md。跨 Skill 共享的通用规则放在 skills/shared/ 下,各 Skill 的 Always Read 指向它。


十三、不要用 LLM 直接生成整个 Skill

用 ChatGPT 生成 Skill 是 agentskills.io 明确标记的常见反模式。原话:“A common pitfall is asking an LLM to generate a skill without providing domain-specific context — relying solely on the LLM’s general training knowledge. The result is vague, generic procedures.”

生成出来的结果通常是这样的:

## 代码审查要点
- 确保代码遵循行业最佳实践
- 检查常见安全漏洞
- 关注性能瓶颈

每一条都是正确的,每一条都没用。“行业最佳实践"是哪个行业?“常见安全漏洞"具体指什么?“关注性能瓶颈"怎么关注?

正确的做法:先用一个 Skill 只覆盖你项目中真实踩过的 3-5 个坑。比如上周三次都是 N+1 查询导致超时——“检查 N+1"写进 checklist。跑了几天输出稳定了,再加从 AAR 里淘出的新条目。Skill 的规则只能来自真实经验,不能来自"我猜 AI 应该检查这些”。


构筑一个能用的 Skill

从零开始做一个 code-review Skill。按上面的每个模块来。

目录结构:

.agents/skills/code-review/
├── SKILL.md
├── agents/
│   └── openai.yaml
├── references/
│   ├── checklist-base.md
│   ├── frontend-checklist.md
│   └── gotchas.md
├── rules/
│   └── coding-standards.md
└── scripts/
    └── smoke-test.sh

SKILL.md:

---
name: code-review
description: >
  代码审查。当用户要求审查代码、检查代码质量、
  检查变更、review PR 时激活。触发词包括"review"、
  "审查"、"code review"、"检查代码"、"过一遍"、"帮我看看"。
primary: true
---

## Always Read
1. references/checklist-base.md
2. rules/coding-standards.md

## Session Discipline
每个新任务必须重读本文件、重新匹配 Task Routing、
重读该路由列出的所有文件。"I already read it" 不是有效理由。

## Task Routing
- 前端代码 → references/frontend-checklist.md
- 后端代码 → references/backend-checklist.md(TODO:建立后解注释)
- 部署脚本 → references/deploy-rules.md(TODO:建立后启用)
- 其他任务 → 只读 Always Read

## Known Gotchas
- 跨层交互:弹窗 Tabs + service 只打首层接口
  → see references/gotchas.md#nested-service-tabs

## 执行流程

1. 读 references/checklist-base.md
2. 根据代码类型,通过 Task Routing 读对应领域检查文件
3. 逐条检查,输出每个问题:
   - 严重程度:高(必然出 bug)/ 中(特定条件下出 bug)/ 低(代码异味)
   - 涉及文件 + 行号
   - 修改建议(一两句话)
   - 对应 checklist 编号
4. 所有问题检查完毕,给出总体评价
5. 有"高"严重级别问题时标注"建议修复后合并"

## 约束
- 不编造本文件里不存在的检查项
- 某条不适用于当前代码时注明"不适用"并说明原因
- 输出中文,术语保留英文

references/checklist-base.md:

1. 类型安全:新代码有类型标注,不允许 implicit any
2. 重复代码:无连续三行以上重复代码(框架模板除外)
3. 错误处理:覆盖超时、空数据、鉴权失败、第三方服务异常
4. 日志:关键路径记录入参/出参/耗时,不记敏感信息
5. 输入校验:所有用户输入经过校验或转义
6. 权限:敏感操作检查用户权限
7. N+1:循环内无数据库查询
8. 分页:大列表查询带分页或游标,有总数上限
9. 配置:硬编码字符串用常量或配置文件代替
10. 测试:新增逻辑有对应单元测试

薄壳(CLAUDE.md):

# CLAUDE.md

Formal docs under skills/. Read skills/code-review/SKILL.md for
all code review tasks.

## Quick Routing (survives context truncation)

| Task | Required reads | Check against |
|------|---------------|---------------|
| Code review | references/checklist-base.md + coding-standards.md | SKILL.md checklist |
| Other tasks | — | Default project rules only |

## Auto-Triggers
- New task in same session → re-read SKILL.md, re-match Task Routing.
- Post-review → 30s AAR: new patterns? new traps? missing rules? stale rules?

## Red Flags — STOP
- "This review looks fine, I'll skip the detailed check" → stop
- Skipping AAR after review → stop

跑一遍验证:

  1. grep -rn "FILL:" skills/code-review/ 确认没有遗留占位符
  2. test-trigger.sh code-review 验证触发率
  3. smoke-test.sh code-review 验证结构完整性
  4. 用一段真实代码跑一次完整审查
  5. 看输出,把漏掉的检查加进 checklist

后续每发现一个新坑,走 30 秒 AAR → 通过 2/3 门槛 → 更新对应文件 → 确认在 Task Routing 路径上可见。Skill 不是一次写好的,是被失败喂大的。


想跟着学更多 AI 编程实战?关注公众号"全栈之巅-梦兽编程”,每周更新从工具配置到工程落地的完整内容。

也可了解 梦兽编程 AI 编程助手服务 ,帮你把 AI 编程工具用到生产环境。

FAQ

AI Skill 和普通提示词有什么区别?

提示词是一次性指令;Skill 是一套工程系统:SKILL.md 按需触发加载、description 决定触发时机、指令分层渐进加载、跨工具兼容(Claude Code/Codex CLI/Cursor),并可测试和持续演进。

SKILL.md 的 description 为什么重要?

description 是模型判断何时加载该 Skill 的唯一依据,只有一次机会。它应写明触发场景、具体能力和边界条件,避免描述过宽导致误触发、过窄导致不触发。

Skill、AGENTS.md 和 MCP 是什么关系?

三者是上下文的三种入口:AGENTS.md/CLAUDE.md 始终在线(项目规范与命令),SKILL.md 按需触发(专项工作流),MCP 按需调用(外部工具与数据)。Skill 解决工作流问题,MCP 解决连接问题。