Ask-to-Remember for OpenClaw
Read references/test-scenarios.md when you need concrete multi-turn examples, follow-up resolution cases, or self-validation samples.
Path convention: treat <workspace> as the current Agent's workspace root. Do not assume every host uses ~/.openclaw/workspace. The host files used by this skill are:
<workspace>/AGENTS.md<workspace>/MEMORY.md<workspace>/atr-state.json<workspace>/atr-log.jsonl
This skill is intentionally OpenClaw-oriented. It relies on a host-side rule block inside AGENTS.md so that the next user turn can resolve a pending ATR question in a stable way.
Session scope
Use this skill only in the human's direct main session.
Do not use this skill in:
- group chats or shared contexts
- subagents or internal-only sessions
- cron / heartbeat / webhook / background workflows
- any session where the agent is not directly replying to the end user
0. Host setup check (first run only)
Read <workspace>/AGENTS.md and search for Ask-to-Remember.
- If the section already exists, skip setup and continue to Step 1.
- If it does not exist, inject the template below.
Setup actions:
- Find the
## Memorysection and insert the template after the last###subsection inside it. If## Memorycannot be found, append the template to the end of the file. - Append one line to
<workspace>/atr-log.jsonl:{"time":"ISO timestamp","phase":"setup","event":"host_configured","target":"AGENTS.md"} - If this same turn also asks an ATR follow-up, briefly tell the user in natural language that memory support was added.
Inject this template exactly as written:
### 🎯 Ask-to-Remember
`ask-to-remember-openclaw` skill handles proactive preference capture — see its `SKILL.md` for the execution steps.
Only trigger Ask-to-Remember in the human's direct main session, not in group chats or other shared contexts.
If `pendingQuestion` in `<workspace>/atr-state.json` is not `null`, treat follow-up handling as part of the host memory rules here in `AGENTS.md` — not as part of the ATR skill itself.
On the **next** user message after a `pendingQuestion` is set, compare it against the question and **always** resolve it:
- **Answered** → extract the reusable fact, append `## [ATR] Topic` to `MEMORY.md`, clear `pendingQuestion`, append `{ "question": "...", "askedAt": "...", "outcome": "answered" }` to `askedQuestions`
- **Refused** (for example `stop asking this`) → clear `pendingQuestion`, add a short normalized topic label to `refusedTopics`, append `{ "question": "...", "askedAt": "...", "outcome": "refused" }` to `askedQuestions`, do not write memory
- **Vague** (for example `depends`, `either works`) → clear `pendingQuestion`; do not write memory; do not add to `askedQuestions`
- **Ignored** (completely unrelated reply) → clear `pendingQuestion`; do not write memory; do not add to `askedQuestions`
Every resolution writes one line to `atr-log.jsonl` in this format:
```json
{"time":"ISO timestamp","phase":"B","event":"answered|vague|refused|ignored","question":"the original question","answer":"user 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.
1. Read state and check preconditions
Read <workspace>/atr-state.json. If the file does not exist, create it with:
{
"lastAskTime": null,
"pendingQuestion": null,
"refusedTopics": [],
"askedQuestions": []
}
State conventions:
| Field | Type | Meaning |
|---|---|---|
lastAskTime |
string | null |
Timestamp of the latest Phase A question |
pendingQuestion |
object | null |
When non-null, always contains question, reason, and askedAt |
refusedTopics |
string[] |
Short normalized topic labels |
askedQuestions |
object[] |
Finalized question records: { "question": string, "askedAt": string, "outcome": "answered" | "refused" } |
topic is a runtime concept. You do not need to store it in pendingQuestion. When writing MEMORY.md or refusedTopics, derive a short stable reusable label from the question and reason.
All of these must pass before asking a new ATR question. Otherwise skip and write a skipped log in Step 4.
| Condition | Skip reason |
|---|---|
pendingQuestion is null |
pending |
target topic is not already in refusedTopics |
refused |
<workspace>/MEMORY.md does not already contain the same reusable fact |
already_known |
askedQuestions does not already contain a semantically duplicate finalized question |
duplicate |
Important: a reusable signal already appearing inside the current request is not a reason to skip. If there is still a worthwhile future-facing gap, continue.
2. Build the follow-up question
Rule of thumb: multiple choice beats open-ended.
Prefer a short question with 2-3 concrete options.
Example:
By the way, for this kind of task, do you usually want X, Y, or Z? If you know already, I can default to that next time.
3. Embed the question into the normal reply
Append the question naturally to the end of the normal response.
Do not:
- send the ATR question as a separate message
- mention ATR, skills, state machines, or internal mechanics
- separate the question with headers, dividers, or special formatting that makes it feel detached from the main reply
4. Update state and write logs
Update atr-state.json
After asking, update only the relevant fields and preserve everything else:
{
"lastAskTime": "ISO timestamp",
"pendingQuestion": {
"question": "the question you asked",
"reason": "why this is worth reusing later",
"askedAt": "ISO timestamp"
}
}
Append to atr-log.jsonl
Append one line to <workspace>/atr-log.jsonl. Only write logs when you ask or when you explicitly skip.
Asked:
{"time":"ISO timestamp","phase":"A","event":"asked","question":"the question you asked","reason":"reuse rationale"}
Skipped:
{"time":"ISO timestamp","phase":"A","event":"skipped","reason":"pending|refused|duplicate|already_known|not_reusable"}
