数据截至 (上游 commit ee230f304a1a)
自我扩展:技能、子 agent 与改写 harness 的 mods
本章讲: 一个 agent 在运行期给自己加能力,有三条完全不同的路。三条路加的东西不同、执行者不同、活多久也不同——先把三者分清楚,剩下的代码就都好读了。
前面几章讲的是「跑一回合」的固定管线(一次回合是怎么跑的)、固定工具表(本地工具层)、固定权限规则(批准这件事)。本章讲的全是可变的那一层。
1. 先分清三条路
三条路都叫「扩展」,但它们连改的东西都不是同一个:
| 机制 | 加进去的是什么 | 谁来执行 | 活多久 | 模型侧入口 |
|---|---|---|---|---|
| 技能 Skill | 一段 Markdown 里的程序性知识(怎么做这件事) | 还是当前这个模型 | 一次工具调用注入,随上下文走 | Skill 工具 / /斜杠命令 |
| 子 agent Subagent | 一个独立进程里的另一个 agent | 新起的 letta 子进程 | 一次任务,返回一份报告就结束 | Task 工具 |
| mod | 一段受信任的本地 JS/TS 代码,直接接进 harness | harness 自己(进程内) | 常驻,直到 /reload 或退出 | mod 注册的新工具 / 新命令 |
一句话记法:
- 技能改的是「上下文」 —— 模型知道得更多了。
- 子 agent 改的是「进程」 —— 多了一个人干活,且他的上下文和你隔离。
- mod 改的是「harness」 —— 连工具表、权限判定、模型 provider 都能换。
┌──────────────── 一次回合 ────────────────┐
│ │
SKILL.md 文件 ─────┼─▶ ① 名字+描述常驻 system reminder │
(.agents/skills 等)│ ② Skill 工具调用 → 正文注入成 user 消息│
│ │
.letta/agents/*.md ┼─▶ Task 工具 ──▶ spawn("letta", headless) ─┼──▶ 子进程
(SubagentConfig) │ stream-json 回流 │ 独立上下文
│ │
~/.letta/mods/*.ts ┼─▶ import() → activate(letta) 注册 │
│ tools / commands / events / │
│ permissions / providers / ui │
└──────────────────────────────────────────┘
2. 技能:把「该怎么做」按需装进上下文
2.1 要解决的小问题
模型什么都懂一点,但不懂你们公司的发版流程。你可以每次都在 prompt 里贴一遍,也可以写成一份文件让它自己去读。
难点在于上下文是公共品——内置的 creating-skills 技能自己就是这么说的:技能要和 system prompt、历史消息、其它技能的元数据抢同一个窗口(src/skills/builtin/creating-skills/SKILL.md)。所以技能的设计核心不是「怎么写」,而是怎么让 99% 的时间里它只占一行。
2.2 一个技能长什么样
一个技能 = 一个目录 + 目录里的 SKILL.md(可选再带 scripts/、references/)。
---
name: scheduling-tasks
description: Schedules reminders and recurring tasks via the letta cron CLI. Use when...
---
# Scheduling Tasks
## When to Use This Skill
- User asks to be reminded of something ("remind me to X at Y")
...
frontmatter 里真正被解析的字段见 src/agent/skills.ts:416 的 parseSkillFile:
| 字段 | 作用 | 源码 |
|---|---|---|
id | 不写则由目录路径推导(web/scraper/SKILL.MD → web/scraper) | skills.ts:430-438 |
description | 不写则取正文第一段 | skills.ts:449-455 |
when_to_use | 拼到 description 后面,专门喂给模型做触发判断 | skills.ts:457-460 |
disable-model-invocation | 只许人用斜杠命令调,模型看不见 | skills.ts:471、isModelInvocableSkill:148 |
user-invocable | 反过来:只许模型调 | skills.ts:152 |
细节一则:扫描时匹配的是 entry.name.toUpperCase() === "SKILL.MD"(skills.ts:380),大小写不敏感;符号链接会 stat 后跟进去,并用 visitedRealPaths 防环(skills.ts:337-368)。