Skill: wubin010/preference-guide (version 1.0.5)

--- name: ask-to-remember version: 1.0.5 description: Proactively capture reusable user preferences, habits, default ways of working, stable constraints, and recurring expectations likely worth remembering for future conversations. Trigger whenever the interaction exposes a plausible, non-sensitive reusable gap adjacent to the task that could help with future requests or interactions. Do not skip just because the task is already clear or completed, or because the signal already appears inside the task. Do not require certainty that the signal is permanent across all future situations; a plausible reusable default is enough. Do not treat a broad existing preference as a substitute for a newly surfaced, more specific signal. Ask at most one brief, low-friction, non-sensitive follow-up, preferably with concrete options, and append it naturally to the normal response. This skill is for non-blocking memory capture, not required clarification. Only trigger this skill in the user's direct main session, not in group chats or other shared contexts. --- # Ask-to-Remember（ATR）—— 执行手册 如需具体的真实对话、多轮状态流转、以及 self-validation 样例，读取 `references/test-scenarios.md`。 路径约定：将 `<workspace>` 视为当前 Agent 的 workspace 根目录。不要假设所有宿主都固定使用 `~/.openclaw/workspace`。本文中的宿主文件统一指：`<workspace>/MEMORY.md`、`<workspace>/atr-state.json`、`<workspace>/atr-log.jsonl`。 ## 0. 注册 ATR Prompt 首次触发时，检查 `<workspace>/AGENTS.md` 中是否已包含 `## Ask-to-Remember` 章节。如果已包含（无论是手动写入还是通过 Hook 自动注入），跳过本步骤。如果没有，将以下内容追加到 AGENTS.md 的 `## Memory` 章节末尾（如果没有 Memory 章节，追加到文件末尾）： ```markdown ## Ask-to-Remember `ask-to-remember` skill proactively captures reusable user preferences. When the current interaction exposes a plausible, non-sensitive reusable preference gap, follow the execution steps in its `SKILL.md`. Only trigger in the human's direct main session. ### Pending Question Resolution Check `atr-state.json` in your workspace root (if the file does not exist, skip — there is nothing to resolve). If `pendingQuestion` is not `null`, resolve it **before** doing anything else: - **Answered** (user gave a clear preference) → extract the reusable fact, derive a short topic label from the question and reason (e.g. `周末行程风格`), append `## [ATR] <topic>` to MEMORY.md, clear `pendingQuestion`, append `{ "question": "...", "askedAt": "...", "outcome": "answered" }` to `askedQuestions` - **Refused** (e.g. `别问了`, `别问这种`) → clear `pendingQuestion`, add a short normalized topic label to `refusedTopics`, append `{ "question": "...", "askedAt": "...", "outcome": "refused" }` to `askedQuestions`, do not write memory - **Vague** (e.g. `都行`, `看情况`) → clear `pendingQuestion`; do not write memory; do not add to `askedQuestions` - **Ignored** (completely unrelated to the question) → clear `pendingQuestion`; do not write memory; do not add to `askedQuestions`; increment `ignoredCount` for the topic (see Ignored cooldown below) **Disambiguation priority:** If the user's wording can naturally be read as confirming one of the options, prefer **Answered**. If they are clearly just wrapping up the current task and bypassing the question, prefer **Ignored**. When uncertain, default to **Vague** (safest — no memory written, topic not locked). **Ignored cooldown:** Track consecutive ignored count per topic. When the same topic has been ignored 2 times, automatically upgrade to **Refused** — add the topic to `refusedTopics` and log with `"event":"refused","note":"upgraded_from_ignored"`. Every resolution writes one line to `atr-log.jsonl`: `{"time":"ISO timestamp","phase":"B","event":"answered|vague|refused|ignored","question":"the original question","answer":"user's reply (if any)"}` **Same-turn concurrency:** If Phase B resolves a pending question and Phase A is also triggered in the same turn, Phase A's precondition checks should treat `pendingQuestion` as already cleared by Phase B. **Phase B confirmation style:** When confirming an Answered result in the same turn as a new task, keep the confirmation minimal (one short sentence) and weave it naturally into the response — do not front-load a lengthy acknowledgment before delivering the task result. ``` 写入后，本次及后续会话 agent 启动时都会自动读到这段规则。 ## 1. 读取状态 & 检查前置条件 读取 `<workspace>/atr-state.json`。若文件不存在，用以下默认值创建： ```json { "pendingQuestion": null, "refusedTopics": [], "askedQuestions": [] } ``` **状态字段约定：** | 字段 | 类型 | 约定 | |------|------|------| | `pendingQuestion` | `object \| null` | 非空时固定包含 `question`、`reason`、`askedAt` | | `refusedTopics` | `string[]` | 存短的、归一化的话题标签，例如 `商务婉拒邮件风格`、`小段代码重构回复形式` | | `askedQuestions` | `object[]` | 只记录已定稿的问题；条目结构固定为 `{ "question": string, "askedAt": string, "outcome": "answered" \| "refused" }` | 这里的 `topic` 是运行时概念，不要求单独存进 `pendingQuestion`。需要写 `MEMORY.md` 或 `refusedTopics` 时，从问题和 reason 中提炼一个稳定、可复用的短标签即可。 **前置条件检查表**——全部通过才继续，否则跳过并写 skipped 日志（见步骤 4）： | 条件 | 不通过时 reason | |------|----------------| | `pendingQuestion` 为 `null`（Phase B 会在本轮优先收口，此处是同一个 gate 的 Phase A 侧） | `pending` | | 目标话题不在 `refusedTopics` 中 | `refused` | | `<workspace>/MEMORY.md` 中尚未记录该信息 | `already_known` | | `askedQuestions` 中不存在语义重复的问题 | `duplicate` | 额外 skip reason：若 skill 被触发但评估后认为当前交互不存在值得长期复用的偏好 gap，直接跳过并以 `not_reusable` 写 skipped 日志。 注意：当前请求里已经露出了一个可复用偏好信号，不是跳过理由。只要仍存在一个值得长期复用的隐式 gap，就继续问。 ## 2. 构造问题 唯一原则：**选择题 > 主观题**——尽量给 2-3 个具体选项，别让用户开放作答。 示例： > “对了，你平时更习惯 X、Y、还是 Z？知道的话我以后直接按这个来。” ## 3. 嵌入回复 & 提问 把问题自然接在正常回复末尾。 禁止事项： - 单独发一条消息来提问 - 提及 ATR、skill、状态机或任何内部机制 - 用分隔线、标题等特殊格式把问题和正常回复分开 - 同一轮提出多个 ATR 问题——如有多个候选话题，只选最有复用价值的一个 ## 4. 更新状态 & 写日志 ### 更新 atr-state.json 提问后仅更新相关字段，保留其他已有字段： ```json { "pendingQuestion": { "question": "你问的问题", "reason": "为什么这个信息值得长期复用", "askedAt": "ISO 时间戳" } } ``` ### 写日志 atr-log.jsonl 向 `<workspace>/atr-log.jsonl` 追加一行。只在**提问**或**明确跳过**时写日志。 **成功提问：** ```json {"time":"ISO时间戳","phase":"A","event":"asked","question":"你问的问题","reason":"复用价值说明"} ``` **跳过：** ```json {"time":"ISO时间戳","phase":"A","event":"skipped","reason":"pending|refused|duplicate|already_known|not_reusable"} ``` --- ## 可选：通过 Hook 自动注入（进阶） 如果你的宿主支持 OpenClaw Hooks，可以用 `agent:bootstrap` hook 替代上面的步骤 0，让 ATR prompt 在每次 agent 启动时自动注入，无需修改 AGENTS.md。 ### 安装方式 将 `hooks/openclaw/` 目录下的文件复制到 managed hooks 目录： ```bash mkdir -p ~/.openclaw/hooks/ask-to-remember cp hooks/openclaw/HOOK.md hooks/openclaw/handler.js ~/.openclaw/hooks/ask-to-remember/ ``` 重启 Gateway 后验证： ```bash openclaw hooks list # 应看到 ask-to-remember 状态为 ✓ ready ``` 安装成功后，ATR prompt 会通过 bootstrap 虚拟文件 `ASK_TO_REMEMBER.md` 自动注入，可以从 AGENTS.md 中移除手动写入的 `## Ask-to-Remember` 章节。

path: hooks/openclaw/HOOK.md
content: ---
name: ask-to-remember
description: "Injects ATR trigger reminder and Phase B resolution rules during agent bootstrap"
metadata: {"openclaw":{"emoji":"🎯","events":["agent:bootstrap"]}}
---

# Ask-to-Remember Bootstrap Hook

Injects ATR prompt into every main-session agent run as a virtual bootstrap file.

## What It Does

- Fires on `agent:bootstrap`
- Pushes `ASK_TO_REMEMBER.md` virtual file containing:
  - A brief skill entry point that references SKILL.md for full Phase A execution logic
  - Phase B pending-question resolution rules (complete, including disambiguation priority, ignored cooldown, and same-turn concurrency)
- ATR is main-session only (enforced by prompt, not code)

## Configuration

No configuration needed. Installed automatically with the ask-to-remember skill.


path: hooks/openclaw/handler.js
content: /**
 * Ask-to-Remember Bootstrap Hook (Optional)
 *
 * Injects ATR trigger reminder and Phase B (pending-question resolution) rules
 * during agent bootstrap as a virtual file. This is an optional alternative to
 * the default approach of writing the ATR prompt into AGENTS.md (see SKILL.md §0).
 */

const ATR_CONTENT = `## Ask-to-Remember

\`ask-to-remember\` skill proactively captures reusable user preferences. When the current interaction exposes a plausible, non-sensitive reusable preference gap, follow the execution steps in its \`SKILL.md\`. Only trigger in the human's direct main session.

### Pending Question Resolution

Check \`atr-state.json\` in your workspace root (if the file does not exist, skip — there is nothing to resolve). If \`pendingQuestion\` is not \`null\`, resolve it **before** doing anything else:

- **Answered** (user gave a clear preference) → extract the reusable fact, derive a short topic label from the question and reason (e.g. \`周末行程风格\`), append \`## [ATR] <topic>\` to MEMORY.md, clear \`pendingQuestion\`, append \`{ "question": "...", "askedAt": "...", "outcome": "answered" }\` to \`askedQuestions\`
- **Refused** (e.g. \`别问了\`, \`别问这种\`) → clear \`pendingQuestion\`, add a short normalized topic label to \`refusedTopics\`, append \`{ "question": "...", "askedAt": "...", "outcome": "refused" }\` to \`askedQuestions\`, do not write memory
- **Vague** (e.g. \`都行\`, \`看情况\`) → clear \`pendingQuestion\`; do not write memory; do not add to \`askedQuestions\`
- **Ignored** (completely unrelated to the question) → clear \`pendingQuestion\`; do not write memory; do not add to \`askedQuestions\`; increment \`ignoredCount\` for the topic (see Ignored cooldown below)

**Disambiguation priority:** If the user's wording can naturally be read as confirming one of the options, prefer **Answered**. If they are clearly just wrapping up the current task and bypassing the question, prefer **Ignored**. When uncertain, default to **Vague** (safest — no memory written, topic not locked).

**Ignored cooldown:** Track consecutive ignored count per topic. When the same topic has been ignored 2 times, automatically upgrade to **Refused** — add the topic to \`refusedTopics\` and log with \`"event":"refused","note":"upgraded_from_ignored"\`.

Every resolution writes one line to \`atr-log.jsonl\`:
\`{"time":"ISO timestamp","phase":"B","event":"answered|vague|refused|ignored","question":"the original question","answer":"user's reply (if any)"}\`

**Same-turn concurrency:** If Phase B resolves a pending question and Phase A is also triggered in the same turn, Phase A's precondition checks should treat \`pendingQuestion\` as already cleared by Phase B.

**Phase B confirmation style:** When confirming an Answered result in the same turn as a new task, keep the confirmation minimal (one short sentence) and weave it naturally into the response — do not front-load a lengthy acknowledgment before delivering the task result.`.trim();

const handler = async (event) => {
  if (!event || typeof event !== 'object') return;
  if (event.type !== 'agent' || event.action !== 'bootstrap') return;
  if (!event.context || typeof event.context !== 'object') return;

  if (Array.isArray(event.context.bootstrapFiles)) {
    event.context.bootstrapFiles.push({
      path: 'ASK_TO_REMEMBER.md',
      content: ATR_CONTENT,
      virtual: true,
    });
  }
};

module.exports = handler;
module.exports.default = handler;


path: references/test-scenarios.md
content: # ATR 测试场景（基于真实聊天样例）

用于替换旧的合成样例，重点验证 Ask-to-Remember 在**真实任务回复尾部自然提问**、**下一轮正确收口**、以及**用户已经露出偏好信号时也别错过继续挖掘**这三件事。

---

## 场景 1：小段 React 重构后，顺手捕获“默认回复粒度”偏好

**目标：** 验证技能会在任务已经完成后，顺手追问一个可长期复用、低打扰的问题；并在下一轮把回答沉淀成长期偏好。

### 第 1 轮 —— 用户请求小段代码重构

**用户：**
> 请帮我重构下面这段 React 代码，让它更易读一点，顺便把重复逻辑收一下，但不要过度抽象。直接给我改好的代码和简短说明。

**理想行为：**
1. 正常完成代码重构任务。
2. 识别到一个可长期复用的偏好缺口：
   - 以后这种**小段代码重构 / 润色**类请求，用户更希望只给一版干净答案，还是顺手附一版更工程化写法。
3. 检查前置条件：
   - `pendingQuestion == null`
   - 该话题未被拒绝
   - MEMORY.md 里还没有更具体的“小段代码重构回复形式”偏好
   - `askedQuestions` 中没有语义重复问题
4. 在正常回复结尾自然补一句短问题，不单独发新消息。
5. 写入 `atr-state.json` 的 `pendingQuestion`。

**用户看到的理想回复：**
> 改了一版，主要是把判断改成早返回、把 user 字段解构出来、顺手去掉了几处啰嗦写法。没乱抽 helper，够干净就收手。  
> [这里是改好的代码]  
>  
> 简短说明：  
> - 用早返回代替三元表达式，读起来更顺。  
> - 去掉 `u.status === 'active' ? true : false` 这种多余写法。  
> - 用解构让字段来源更清楚。  
> - `email / role / fullName` 都统一用更直接的兜底写法。  
> - `key` 优先用 `id`，没有再退回 `index`。  
>  
> 如果你愿意，我还能顺手再给你一版“更像团队里会提交 PR 的写法”。对了，以后这种小段代码重构 / 润色，你更想我默认只给一版改好代码 + 3~5 行简短说明，还是顺手再附一版更工程化的写法？前者 / 后者 / 看情况。

**期望的 `atr-state.json` 变化：**
```json
{
  "pendingQuestion": {
    "question": "以后这种小段代码重构 / 润色，你更想我默认只给一版改好代码 + 3~5 行简短说明，还是顺手再附一版更工程化的写法？前者 / 后者 / 看情况。",
    "reason": "小段代码任务的默认输出粒度可在未来重复复用",
    "askedAt": "<ISO timestamp>"
  }
}
```

### 第 2 轮 —— 用户先结束当前任务，再回答偏好

**用户：**
> 不用了就这版。

**理想行为：**
1. 这是对当前任务的收口，不是对 ATR 问题的回答。
2. 如果本轮要追问，仍应保持自然，不重复、不过度。
3. 若宿主规则是“下一轮必须判定是否 answered / vague / refused / ignored”，则这里应视为 **Ignored**：
   - 清除 `pendingQuestion`
   - 不写 MEMORY.md
   - 写一条 `ignored` 日志
4. 如果后续又遇到相近场景，仍可以换方式再问，因为这次不是明确拒绝。

**关键校验点：**
- 不要把“就这版”误判成“前者”。
- 不要在用户明确收口后再单独追一条 ATR 消息。

### 第 3 轮 —— 未来相似任务中再次自然触发，用户明确回答

**用户：**
> 再帮我顺一段小组件代码，还是直接给我改好的版本。

**理想行为：**
1. 正常完成任务。
2. 在合适时机再次问同类问题（可比上次更短、更贴脸）。
3. 用户若回答：
   > 前者。
4. 则应判定为 **Answered**，并写入长期记忆。

**期望写入 MEMORY.md 的内容：**
```markdown
## [ATR] 小段代码重构回复形式
默认偏好：小段代码重构/润色时，先给一版改好的代码 + 3-5 行简短说明；不必默认再附一版更工程化写法。
```

**关键校验点：**
- 只在用户明确表达偏好后落长期记忆。
- 记录要写成**未来可复用的默认工作方式**，不是复述当轮聊天。

---

## 场景 2：写英文婉拒邮件后，捕获“默认拒绝风格”偏好

**目标：** 验证技能能从一次具体写作任务里，抽出一个以后可反复复用的风格偏好；同时问题要足够轻，不打断主任务。

### 第 1 轮 —— 用户请求写礼貌婉拒合作邮件

**用户：**
> 帮我写一封英文邮件，礼貌婉拒下面这个合作邀请。要求语气专业、友好，不要太冷淡，也不要给出过多细节。请直接给我完整邮件正文和一个合适的 subject。

**理想行为：**
1. 先完整给出邮件正文和 subject。
2. 识别到可长期复用的偏好缺口：
   - 用户以后对这类**商务合作邀约婉拒**，更偏向“保留 future possibility 的委婉版”，还是“更直接、收得更紧的版本”。
3. 在正文之后自然补一句带选项的问题。

**用户看到的理想回复（示意）：**
> **Subject:** Re: Collaboration Opportunity  
>  
> Hi,  
>  
> Thank you for reaching out and for your kind words about my work. I really appreciate the interest and the opportunity to connect.  
>  
> At the moment, I’m not able to take on new collaborations, so I’ll have to pass on this opportunity. That said, I truly appreciate you thinking of me, and I wish you and your team the best with the initiative.  
>  
> Best regards,  
> [Your Name]  
>  
> 对了，以后这种合作邀约婉拒，你更想我默认写成哪种：A. 保留一点 future possibility 的委婉版；B. 更直接简短一点；C. 看具体对象？

### 第 2 轮 —— 用户明确回答风格偏好

**用户：**
> A

**理想行为：**
1. 判定为 **Answered**。
2. 提取可复用事实：
   - 商务合作邀约的婉拒邮件，默认偏好“保留 future possibility 的委婉版”。
3. 追加写入 MEMORY.md。
4. 清除 `pendingQuestion`，并把该问题加入 `askedQuestions`，避免重复问。

**期望写入 MEMORY.md 的内容：**
```markdown
## [ATR] 商务婉拒邮件风格
对合作邀约的婉拒邮件，默认偏好“保留 future possibility 的委婉版”，而不是过于简短直接的拒绝。
```

**关键校验点：**
- 提问必须附着在正常交付后面，不能喧宾夺主。
- 记忆条目应抽象成可复用默认值，而不是只描述这一次邮件。

---

## 场景 3：用户已经把偏好写进需求里 —— 仍可继续挖掘未来默认值

**目标：** 验证技能不会因为用户已经在当前需求里露出一个可复用偏好，就直接放弃 ATR；只要还有未来可复用的隐式 gap，仍应继续挖掘。

**用户：**
> 帮我写一封英文邮件，礼貌婉拒合作邀请。默认就写成保留 future possibility 的委婉版，别太冷，也别解释太多。

**理想行为：**
1. 正常完成邮件。
2. 识别到用户已经在当前请求里露出一个强信号：
   - 这类商务婉拒邮件，当前明显偏向“保留 future possibility 的委婉版”。
3. 不要因为这个信号已经出现在需求里就跳过 ATR；如果仍有长期复用价值，继续追问一个更面向未来默认值的轻量问题，例如：
   > 对了，以后这种合作邀约婉拒，我默认都按这种“保留一点 future possibility 的委婉版”来写，还是你更想我看对象调整？默认按这个 / 看对象 / 看情况。
4. 写入新的 `pendingQuestion`，把这次单轮要求往未来默认值上收口。

**关键校验点：**
- 不要把“用户已经说过一点偏好”误当成 ATR 的阻断条件。
- 问题仍然要面向未来默认值，而不是机械复读用户刚才那句话。

---

## 场景 4：用户回答“看情况” —— 视为 Vague，不落长期记忆

**目标：** 验证技能能把模糊回答和明确偏好区分开。

**承接场景 1 或 2 的提问后，用户回复：**
> 看情况。

**理想行为：**
1. 判定为 **Vague**。
2. 清除 `pendingQuestion`。
3. 不写 MEMORY.md。
4. 不把该问题加入 `askedQuestions`，因为未来仍可能换种方式再问。

**关键校验点：**
- “看情况 / 都行” 不是长期默认值，不能硬记。

---

## 场景 5：用户明确拒绝该话题 —— 记 refusal，不写长期记忆

**目标：** 验证技能能正确处理 Refused 分支，并把该话题锁进 `refusedTopics`。

**承接场景 1 或 2 的提问后，用户回复：**
> 别问这种默认偏好了，具体任务具体看。

**理想行为：**
1. 判定为 **Refused**。
2. 清除 `pendingQuestion`。
3. 向 `refusedTopics` 写入一个短的归一化话题标签，例如 `商务婉拒邮件风格` 或 `小段代码重构回复形式`。
4. 向 `askedQuestions` 追加 `{ "question": "...", "askedAt": "...", "outcome": "refused" }`。
5. 写一条 `refused` 日志。
6. 不写 MEMORY.md。

**关键校验点：**
- Refused 不是 Vague，后续同话题应被 `refusedTopics` 挡住。
- `askedQuestions` 里记录的是已经定稿的问题对象，不是裸字符串。

---

## 场景 6：同轮并发 —— 先收口上一题，再决定是否问下一题

**目标：** 验证宿主的 Same-turn concurrency 规则真的能落地：本轮既处理上一轮 pending，也允许在同一轮里再次触发 ATR。

**前置状态：**
```json
{
  "pendingQuestion": {
    "question": "以后这种小段代码重构 / 润色，你更想我默认只给一版改好代码 + 3~5 行简短说明，还是顺手再附一版更工程化的写法？前者 / 后者 / 看情况。",
    "reason": "小段代码任务的默认输出粒度可在未来重复复用",
    "askedAt": "<ISO timestamp>"
  }
}
```

**用户：**
> 前者。另外帮我写一封英文邮件，礼貌婉拒这个合作邀请。

**理想行为：**
1. 先执行 Phase B，把“前者”判定为 **Answered**。
2. 写入 MEMORY.md，清除旧的 `pendingQuestion`，并向 `askedQuestions` 追加 `{ "question": "...", "askedAt": "...", "outcome": "answered" }`。
3. 再处理本轮新的邮件任务。
4. 如果邮件任务里又出现新的可复用偏好 gap，可以在同一轮回复结尾再次自然提问。
5. 这时 Phase A 的前置条件应把旧 `pendingQuestion` 视为已经清空，而不是因为“本轮一开始有 pending”就错误跳过。
6. 最终状态应是：旧问题已收口，新问题成为新的 `pendingQuestion`。

**关键校验点：**
- 同轮里可以同时发生一条 Phase B 日志和一条 Phase A `asked` 日志。
- 不要因为本轮开头存在 pending，就错失新的偏好挖掘机会。

---

## 自验证覆盖说明

这组样例至少覆盖了：

- **正常样例**：任务完成后自然提问，用户明确回答，成功写入长期记忆（场景 2）
- **边界样例**：用户先收口当前任务，导致上一轮问题被 ignored；后续再次自然触发（场景 1）
- **继续挖掘样例**：用户已在当前请求里露出偏好信号，但 ATR 仍继续把它收口成未来默认值（场景 3）
- **模糊回答样例**：用户回答模糊，不应硬写记忆（场景 4）
- **拒绝样例**：用户明确拒绝该话题，应锁进 `refusedTopics`（场景 5）
- **同轮并发样例**：本轮既要处理上一轮 pending，又要决定是否在本轮再次触发 ATR（场景 6）


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)