Lab 5 mini-letta v0.5:对话结束,后台反思
目标:反思水位线、两种触发器、反思子 agent(在 worktree 里运行)、收尾状态机、每个 agent 一把反思锁、仓库级 LF 配置。演示:三句话之后反思自动启动,从中挑出两条持久偏好写进
human.md并合并;主对话下一回合就知道了。前置:第 14–17 章。完成后:git checkout v0.5。
L5.1 改动一览
text
$ git diff v0.4 v0.5 --stat
package.json | 2 +-
src/agent/loop.ts | 4 +-
src/cli.ts | 69 ++++++++++---
src/llm/mock-helpers.ts | 71 +++++++++++++
src/llm/mock-reflection.ts | 76 ++++++++++++++
src/llm/mock.ts | 83 +++-------------
src/memory/memfs.ts | 4 +
src/reflection/launcher.ts | 95 ++++++++++++++++++
src/reflection/post-turn.ts | 28 ++++++
src/reflection/prompt.ts | 36 +++++++
src/reflection/transcript.ts | 78 +++++++++++++++
src/reflection/worktree.ts | 111 +++++++++++++++++++++
src/store/agent-store.ts | 14 +++
src/utils/file-lock.ts | 46 +++++++++
test/v02-memfs.test.ts | 8 ++
test/v05-reflection.test.ts | 230 +++++++++++++++++++++++++++++++++++++++++++
16 files changed, 870 insertions(+), 85 deletions(-)L5.2 步骤
- 设置:
AgentRecord.reflection?: { trigger, stepCount },默认compaction-event/ 25。 - 水位线(
src/reflection/transcript.ts):reflection-state.json;unreflectedMessages、stepsSinceLastReflection、buildReflectionPayload(去掉摘要和纯提醒消息、工具结果截 1 000 字、写 JSON 数组)、markReflected。 - worktree 与状态机(
src/reflection/worktree.ts):createMemoryWorktree、finalizeMemoryWorktree(在 memory-operation 锁内)、consumesTranscript。 - 锁(
src/utils/file-lock.ts)。 - 提示词(
src/reflection/prompt.ts)。 - 启动器(
src/reflection/launcher.ts):锁 → 父仓库干净 → payload → worktree → 以<parent>~reflection身份在 worktree 里runTurn→ 收尾 → 推进水位线。给runTurn加extraEnv参数传TRANSCRIPT_PATH、PARENT_AGENT_ID。 - 回合后触发(
src/reflection/post-turn.ts)。 - mock:把规则辅助函数拆到
mock-helpers.ts(否则mock.ts⇄mock-reflection.ts会循环依赖——letta-code 的 CI 用 madge 检查循环依赖,我们也在package.json里加了check:cycles);mock-reflection.ts实现确定性的反思者。 - CLI:
reflect [--instruction]、sleeptime --trigger … --steps …;每次chat之后调用maybeReflectAfterTurn。交互模式下反思在后台跑、不阻塞下一次输入——注意chatOnce返回{ reflection: Promise }而不是直接返回 promise:async 函数返回的 promise 会被展开,调用者await时会连反思一起等。 - LF:
initMemoryRepo钉死core.autocrlf=false、core.eol=lf(第 16 章踩坑记录)。
L5.3 运行
bash
bun test test/v05-reflection.test.ts
export MINI_LETTA_HOME=$(mktemp -d)
bun src/cli.ts sleeptime --trigger step-count --steps 3
bun src/cli.ts chat -p "帮我看下这个报错"
bun src/cli.ts chat -p "我更喜欢用 tabs 缩进"
bun src/cli.ts chat -p "今天我们决定以后一律用 pnpm"
bun src/cli.ts memory log
cat "$(bun src/cli.ts memory path)/human.md"
bun src/cli.ts chat -p "你知道我什么?"输出见第 15 章 15.6 节。之后看看留下了什么:
bash
A=$MINI_LETTA_HOME/agents
ls $A # agent-default agent-default~reflection
ls $A/agent-default/memory-worktrees # 空:worktree 已清理
git -C $A/agent-default/memory branch # 只有 main:反思分支已删除
ls $A/agent-default/reflections/payloads # 反思读过的 payload
cat $A/agent-default/conversations/default/reflection-state.json
cat $A/agent-default~reflection/conversations/*/messages.jsonl | jq -r '.role + ": " + (.content|.[0:80])'一次真实运行的输出(节选):
text
agent-default
agent-default~reflection
--wt: ← memory-worktrees 为空
--branches:
* main ← 反思分支已删除
payload-1791478331028.json
{
"lastReflectionStartedAt": "2026-10-08T16:52:11.028Z",
"reflectedThroughMessageId": "msg-muzrxz0t295ln4i",
"lastReflectionSucceededAt": "2026-10-08T16:52:11.517Z"
}
user: <system-reminder> Agent info: - AGENT_ID: agent-default~refl
assistant:
tool: 829 /tmp/tmp.xOnZ9i3toC/agents/agent-default/reflections/pay
assistant:
tool: 1cf1eaf chore(reflection): remember 2 user preference(s) 🔮
user: <system-reminder> <memory_update> The memory filesystem has
assistant: Summary: updated human.md. Commit: 1cf1eaf chore(reflection)反思子 agent 自己的对话:任务提示、读 transcript(829 字节)、提交、报告。注意倒数第二行:反思者提交之后,它自己也收到了一条 <memory_update>——它的 memoryDir 是 worktree,它的提交让 worktree 的 HEAD 变了,第 9 章的机制对它一视同仁。它的系统提示里有父 agent 的记忆——用 jq -r .content 看第一条之前的系统提示需要另想办法(练习 1)。
L5.4 用真模型做反思
bash
for t in "帮我看下这个报错" "我更喜欢用 tabs 缩进" "今天我们决定以后一律用 pnpm"; do bun src/cli.ts chat -p "$t" >/dev/null; done
MINI_LETTA_PROVIDER=openai MINI_LETTA_MODEL=qwen2.5:3b bun src/cli.ts reflect
bun src/cli.ts reflect # 再用 mock 跑一次qwen2.5:3b 两次分别给出了空回复和“只说不做”,加了证据检查之后都被判为 failed,第二条命令用 mock 重试同一段对话并成功合并(第 15 章踩坑记录 2)。如果你有更强的本地模型(比如 7B 以上、支持工具调用),换上它,观察它是否遵守“有界读取”和提交格式。
L5.5 练习
- 巩固:把
ResolvedSystemPrompt也写进反思子 agent 的对话目录(就像主 agent 的system-prompt.json),然后确认里面确实包含了父 agent 的<human>、<persona>。 - 扩展:给
reflect加--instruction(已经有参数了)的 mock 支持:比如--instruction "只关注工具偏好"时,mock 只提取包含pnpm|npm|yarn|tabs的偏好。 - 读源码:letta-code 的反思是独立进程(
spawnBackgroundSubagentTask),mini-letta 是同一进程里的runTurn。列出这个差别在三方面的影响:崩溃隔离、内核沙箱(第 19 章)、CLI 一次性模式(-p)下的生命周期。