Skill: huyaohuahk/task-memory (version 1.2.0)

--- name: task-memory description: | 任务遗忘防护系统 — 解决 AI Agent 任务发出但未执行的记忆漏洞问题。 当需要创建、追踪、管理长期任务时使用，特别是：提出或承诺了某项计划后、设置 cron/自动化任务时、任务状态变更后、晨间/心跳检查时。 核心功能：通过 todo.json 持久化任务状态，todo_manager.py 管理增/查/改/完成，系统自动追踪不过期。 --- # Task Memory — 任务遗忘防护系统 ## 核心原则 **做 A 的同时必须完成 B，不能分开两步。** AI Agent 的记忆是每次重新初始化，但任务必须是持续的。"发消息给用户说会做 X" ≠ "任务被追踪"。必须写入 todo.json 才能被系统自动管理。 ## 触发场景 当以下场景发生时，必须使用本技能： 1. **提出或承诺了某项计划** → 立刻调用 `todo_manager.py --add` 2. **用户交代了任务** → 立即登记，设置 deadline 3. **任务状态变更**（完成/废弃/遇到障碍）→ 调用 `--done` 或 `--remove` 4. **会话开场（晨间）** → 运行 `--check`，报告过期/即将到期的任务 5. **心跳检查** → 运行 `--check`，有过期项推送到 QQ/IM 6. **设置 cron/自动化任务** → 自动化执行的同时必须登记 todo ## 快速命令 ```bash # 登记任务（每次承诺后必须执行） python3 scripts/todo_manager.py --add "任务描述" --tag <标签> --system --deadline "YYYY-MM-DD" # 注意：deadline 格式必须为 YYYY-MM-DD，不合法会报错拒绝 # 会话开场 / 心跳检查（自动运行） python3 scripts/todo_manager.py --check # 列出所有任务 python3 scripts/todo_manager.py --list # 任务完成（自动归档，保留30天） python3 scripts/todo_manager.py --done <id> # 删除任务 python3 scripts/todo_manager.py --remove <id> # 查看归档（30天内） python3 scripts/todo_manager.py --archive # 清理30天前归档 python3 scripts/todo_manager.py --purge # 更新任务 python3 scripts/todo_manager.py --update <id> --title "新描述" ``` ## 工作流 ### 工作流1：主动提出计划 ``` 用户：我们来做 BTC 监控自动化吧 → 思考：这是一个持续性任务，需要登记 → 调用 --add "BTC MACD实盘监控自动化" --tag btc_monitor --system --deadline 今天 → 写监控脚本 → 设置 cron → --done <id> ``` ### 工作流2：用户交代任务 ``` 用户：帮我验证 ETH 回测策略 → 调用 --add "验证ETH回测策略" --tag research --deadline "YYYY-MM-DD" → 执行 → --done <id> → 告知用户完成 ``` ### 工作流3：晨间检查 ``` 会话开场 → python3 scripts/todo_manager.py --check → 输出： ⚠️ 已过期: [id] A股模拟账户持仓5只 (截止: 昨天) 📅 即将到期: [id] 策略验证 (截止: 今天) → 主动报告给用户 ``` ### 工作流4：任务完成 ``` 任务完成后 → --done <id> → 同时更新相关状态文件 → 告知用户"已完成" ``` ## 文件结构 ``` task-memory/ ├── SKILL.md ← 本文件 ├── scripts/ │ └── todo_manager.py ← 任务管理工具（Python） └── references/ └── todo.json ← 任务数据库（运行时会创建） ``` ## todo.json 结构 ```json { "version": "1.0", "updated": "2026-04-01T00:00:00+08:00", "items": [ { "id": "c3436d4d", "title": "A股模拟账户 - 持仓5只", "created": "2026-04-01T00:00:00+08:00", "deadline": "2026-04-01", "status": "pending", "tag": "paper_trading", "system": true, "notes": "每只10万，09:25买入", "completed_at": null } ] } ``` 字段说明： - `id`: 唯一标识（8位 UUID） - `title`: 任务描述 - `deadline`: ISO 格式截止日期（YYYY-MM-DD） - `status`: pending / in_progress / completed / cancelled - `tag`: 分类标签（见下表） - `system`: true = agent 主动提出，false = 用户交代 - `notes`: 额外备注 - `completed_at`: 完成时间（自动填充） ## 标签使用 | 标签 | 用途 | |------|------| | paper_trading | 模拟交易相关 | | btc_monitor | 加密货币监控相关 | | research | 研究调研任务 | | other | 其他 | ## 知识库 详细版本历史、设计决策、已知局限、未来计划： → `references/CHANGELOG.md` ## 重要规则 1. **deadline 格式**：必须是 ISO 格式（YYYY-MM-DD），否则 `--check` 无法识别过期 2. **过期不过期只看 deadline**：`status` 字段不影响过期判断 3. **todo.json 路径**：默认保存在当前目录 `references/todo.json`，部署时可调整 4. **system 标记**：区分"agent 主动提出的"和"用户交代的" 5. **承诺即登记**：任何计划在告诉用户的同时，必须完成 --add 登记 ## AGENTS.md 集成 在会话开场规则中加入 Todo 扫描： ```markdown 6. Todo 扫描: 运行 `python3 scripts/todo_manager.py --check` 有过期/即将到期的系统任务 → 在晨间汇报里报告 ``` 在 `HEARTBEAT.md` 中加入心跳检查： ```markdown ## Todo 过期检查 每次心跳运行 `python3 scripts/todo_manager.py --check` 有过期项 → 推送提醒到 QQ/IM ```

path: references/CHANGELOG.md
content: # task-memory Skill 知识库

> 记录 Skill 的版本历史、设计决策、已知问题、未来计划
> 发布地址: https://clawhub.ai/skills/task-memory

---

## 版本历史

### v1.2.0（2026-04-01）修复版
**Bug 修复：**
- `--add` 时强制校验 deadline 格式，不合法直接报错退出（不再静默失效）
- `--done` 时自动归档完成的任务到 `archive` 列表（保留30天）
- 新增 `--archive` 命令：查看30天内归档
- 新增 `--purge` 命令：清理30天前归档

### v1.1.0（2026-04-01）清理版
**变更：**
- SKILL.md 去除所有个人信息（路径、身份标识等）
- 改为通用路径描述，适合所有用户部署
- CHANGELOG.md 去除个人身份信息

### v1.0.1（2026-04-01）
**变更：**
- 增加 CHANGELOG.md 知识库
- SKILL.md 加入知识库引用

### v1.0.0（2026-04-01）首发
**功能：**
- `todo_manager.py` — 任务管理工具（add/done/check/list/remove/update）
- `todo.json` — 任务数据库模板
- `SKILL.md` — 完整使用规范

**解决的问题：**
- AI Agent "任务发出但未执行"的遗忘问题
- 持续性任务（cron/模拟交易/监控）缺乏自动追踪机制
- 依赖"主动记忆"而非"系统追踪"

---

## 核心原则来源

> 做 A 的同时必须完成 B，不能分开两步。

**背景：** AI Agent 每次会话重新初始化，记忆不持续。当主动提出某项计划后，如果只发消息给用户而不写入持久化状态文件，系统重启后即遗忘。

**解决：** 所有承诺/计划 → 写入 todo.json → 系统自动追踪。

---

## 已知局限

- deadline 格式必须为 ISO（YYYY-MM-DD），格式错误会被 --check 跳过
- `status` 字段不影响过期判断（仅看 deadline）
- 无自动取消：过期项不会自动状态变更，需手动 --done 或 --remove
- 500+ 任务时考虑迁移 SQLite（当前为 JSON）

---

## 设计决策

| 问题 | 选择 | 原因 |
|------|------|------|
| 为什么用 JSON？ | 无外部依赖 | AI 可直接读写，无需数据库 |
| 为什么 deadline 不过期自动完成？ | 保留人工判断 | 有些过期任务仍有价值 |
| 为什么用 8 位 UUID？ | 够用 + 手动输入方便 | 太长不方便，太短易冲突 |
| system 标记的用途？ | 区分主动/被动任务 | 方便分类管理和优先跟进 |

---

## 未来计划

- [ ] v1.2：支持 `--repeat daily/weekly` 重复任务
- [ ] v1.3：支持任务依赖（Task A 完成后自动创建 Task B）
- [ ] v2.0：支持子任务（一个大任务拆成多个子任务）
- [ ] v2.1：支持迁移到 SQLite（任务量 > 500 时）

---

_此文档由 Skill 创建者维护 · 🔮_


path: references/todo.json
content: {
  "version": "1.0",
  "updated": "2026-04-01T08:48:18.187564",
  "items": [
    {
      "id": "c3436d4d",
      "title": "A股模拟账户 - 持仓5只（美的/中联/泸州/胜宏/鹏鼎），每只10万，共50万",
      "created": "2026-04-01T08:48:11.766982",
      "deadline": "2026-04-01",
      "status": "pending",
      "tag": "paper_trading",
      "system": true,
      "notes": "昨日扫描候选股，09:25推送买入信号，15:05每日扫描持仓止损止盈"
    },
    {
      "id": "ecb77cf5",
      "title": "BTC MACD实盘监控 - 监控MACD金叉/死叉信号，diff<100每分钟检查，diff<200每5分钟检查",
      "created": "2026-04-01T08:48:18.187549",
      "deadline": null,
      "status": "pending",
      "tag": "btc_monitor",
      "system": true,
      "notes": "脚本: monitor_macd.py，pending队列推送至QQ，HEARTBEAT每30分钟推送一次"
    }
  ]
}

path: scripts/todo_manager.py
content: #!/usr/bin/env python3
"""
todo_manager.py — 结构化任务管理系统
用法:
  python3 todo_manager.py --add "任务描述" --tag paper_trading --system
  python3 todo_manager.py --done <id>
  python3 todo_manager.py --check        # 检查过期项
  python3 todo_manager.py --list         # 列出所有
  python3 todo_manager.py --list --tag paper_trading
  python3 todo_manager.py --remove <id>
  python3 todo_manager.py --update <id> --title "新描述"
"""
import json
import os
import sys
import uuid
from datetime import datetime, timedelta

TODO_FILE = "/home/openclaw/.openclaw/workspace/backtest/todo.json"

def load():
    if os.path.exists(TODO_FILE):
        with open(TODO_FILE) as f:
            return json.load(f)
    return {"version": "1.0", "updated": "", "items": []}

def save(data):
    data["updated"] = datetime.now().isoformat()
    with open(TODO_FILE, 'w') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

def _validate_deadline(deadline):
    """验证 deadline 格式，不合法则报错退出"""
    if not deadline:
        return None
    for fmt in ("%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"):
        try:
            datetime.strptime(deadline.replace("+08:00", "").replace("+00:00", ""), fmt)
            return deadline
        except ValueError:
            continue
    print(f"❌ deadline 格式错误: '{deadline}'")
    print(f"   正确格式: YYYY-MM-DD (如 2026-04-05)")
    sys.exit(1)

def add(title, tag=None, deadline=None, system=False, notes=None):
    _validate_deadline(deadline)
    data = load()
    item = {
        "id": str(uuid.uuid4())[:8],
        "title": title,
        "created": datetime.now().isoformat(),
        "deadline": deadline,
        "status": "pending",
        "tag": tag,
        "system": system,
        "notes": notes
    }
    data["items"].append(item)
    save(data)
    print(f"✅ 添加: [{item['id']}] {title} {'(系统任务)' if system else ''}")
    if deadline:
        print(f"   截止: {deadline}")
    return item["id"]

def done(id_):
    data = load()
    for item in data["items"]:
        if item["id"] == id_:
            item["status"] = "completed"
            item["completed_at"] = datetime.now().isoformat()
            # 自动归档：移动到 archive 列表
            if "archive" not in data:
                data["archive"] = []
            data["archive"].append(item)
            data["items"] = [x for x in data["items"] if x["id"] != id_]
            save(data)
            print(f"✅ 完成并归档: {item['title']}")
            return
    print(f"❌ 未找到: {id_}")

def remove(id_):
    data = load()
    before = len(data["items"])
    data["items"] = [x for x in data["items"] if x["id"] != id_]
    if len(data["items"]) < before:
        save(data)
        print(f"🗑️  删除: {id_}")
    else:
        print(f"❌ 未找到: {id_}")

def check():
    """检查过期未完成项，返回要推送的消息"""
    data = load()
    now = datetime.now()
    overdue = []
    upcoming = []
    
    for item in data["items"]:
        if item["status"] in ("completed", "cancelled"):
            continue
        if item.get("deadline"):
            try:
                dl = datetime.fromisoformat(item["deadline"])
                if dl < now:
                    overdue.append(item)
                elif dl < now + timedelta(hours=24):
                    upcoming.append(item)
            except:
                pass
    
    return overdue, upcoming

def list_items(tag=None, status=None):
    data = load()
    items = data["items"]
    if tag:
        items = [x for x in items if x.get("tag") == tag]
    if status:
        items = [x for x in items if x.get("status") == status]
    
    if not items:
        print("（无）")
        return
    
    now = datetime.now()
    for item in items:
        deadline_str = ""
        if item.get("deadline"):
            try:
                dl = datetime.fromisoformat(item["deadline"])
                overdue = "⚠️已过期" if dl < now and item["status"] != "completed" else ""
                deadline_str = f" | 截止: {item['deadline']} {overdue}"
            except:
                deadline_str = f" | 截止: {item['deadline']}"
        
        system_tag = "🤖" if item.get("system") else "👤"
        status_icon = {"pending": "⏳", "in_progress": "🔄", "completed": "✅", "cancelled": "❌"}.get(item["status"], "•")
        print(f"{status_icon} [{item['id']}] {system_tag} {item['title']}{deadline_str}")
        if item.get("notes"):
            print(f"   备注: {item['notes']}")

def archive_list():
    """列出归档（30天内）"""
    data = load()
    arch = data.get("archive", [])
    now = datetime.now()
    cutoff = now - timedelta(days=30)
    recent = []
    for item in arch:
        try:
            completed_at = datetime.fromisoformat(item.get("completed_at", "2020-01-01"))
            if completed_at > cutoff:
                recent.append((item, completed_at))
        except:
            pass
    if not recent:
        print("（无30天内归档）")
        return
    print(f"📦 归档（{len(recent)}条，保留30天）：")
    for item, ct in sorted(recent, key=lambda x: x[1], reverse=True):
        age = (now - ct).days
        print(f"  ✅ [{item['id']}] {item['title']} | 完成于: {ct.date()} ({age}天前)")
    print(f"\n超过30天的归档可用 --purge 清理")

def purge_archive():
    """清理30天前归档"""
    data = load()
    arch = data.get("archive", [])
    now = datetime.now()
    cutoff = now - timedelta(days=30)
    kept, removed = [], 0
    for item in arch:
        try:
            ct = datetime.fromisoformat(item.get("completed_at", "2020-01-01"))
            if ct > cutoff:
                kept.append(item)
            else:
                removed += 1
        except:
            kept.append(item)
    data["archive"] = kept
    save(data)
    print(f"🗑️  已清理 {removed} 条归档，保留 {len(kept)} 条")

def main():
    if len(sys.argv) == 1:
        print(__doc__)
        sys.exit(0)

    args = sys.argv[1:]
    
    if "--add" in args:
        idx = args.index("--add")
        title = args[idx + 1]
        tag = None
        deadline = None
        system = False
        notes = None
        if "--tag" in args:
            tag = args[args.index("--tag") + 1]
        if "--deadline" in args:
            deadline = args[args.index("--deadline") + 1]
        if "--system" in args:
            system = True
        if "--notes" in args:
            notes = args[args.index("--notes") + 1]
        add(title, tag=tag, deadline=deadline, system=system, notes=notes)
    
    elif "--done" in args:
        id_ = args[args.index("--done") + 1]
        done(id_)
    
    elif "--remove" in args:
        id_ = args[args.index("--remove") + 1]
        remove(id_)
    
    elif "--check" in args:
        overdue, upcoming = check()
        if overdue:
            print("⚠️ 已过期:")
            for item in overdue:
                print(f"  [{item['id']}] {item['title']} (截止: {item['deadline']})")
        if upcoming:
            print("📅 即将到期 (24h内):")
            for item in upcoming:
                print(f"  [{item['id']}] {item['title']} (截止: {item['deadline']})")
        if not overdue and not upcoming:
            print("✅ 无过期项")
    
    elif "--archive" in args:
        archive_list()
    
    elif "--purge" in args:
        purge_archive()
    
    elif "--list" in args:
        tag = None
        status = None
        if "--tag" in args:
            tag = args[args.index("--tag") + 1]
        if "--status" in args:
            status = args[args.index("--status") + 1]
        list_items(tag=tag, status=status)
    
    elif "--update" in args:
        idx = args.index("--update")
        id_ = args[idx + 1]
        data = load()
        for item in data["items"]:
            if item["id"] == id_:
                if "--title" in args:
                    item["title"] = args[args.index("--title") + 1]
                if "--deadline" in args:
                    item["deadline"] = args[args.index("--deadline") + 1]
                if "--status" in args:
                    item["status"] = args[args.index("--status") + 1]
                save(data)
                print(f"✅ 更新: [{id_}] {item['title']}")
                break
        else:
            print(f"❌ 未找到: {id_}")
    else:
        print(__doc__)

if __name__ == "__main__":
    main()


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