# 定时任务执行位置

## 一、调度器启动位置

### CLI 模式（main.py）

```python
# main.py:33-34
from cron import start_cron, stop_cron
start_cron(root, core_config)            # web_mode=False
```

### Web 模式（web/server.py）

```python
# web/server.py:85-86
from cron import start_cron, stop_cron
start_cron(_root, _core_config, web_mode=True)  # web_mode=True
```

两种模式都启动同一个后台 daemon 线程，区别在于任务执行方式。

## 二、调度循环架构

```
主进程
  │
  ├─ 主线程: 用户交互 (CLI) / Flask (Web)
  │
  └─ daemon 线程: cron-scheduler
       │
       │  心跳 5s，首次启动等 3s
       │
       ▼
     _scheduler_loop()
       │
       ├─ 遍历所有用户目录
       │    │
       │    ├─ 扫描 users/<name>/tasks/*.json → 匹配时间 → 执行任务
       │    │
       │    ├─ run_forget() → 清理过期临时记忆 (每 12 心跳 = 60s)
       │    │
       │    └─ run_auto_improve_trigger() → 定期被动记忆提取 (每 720 心跳 = 3600s)
       │
       └─ 等待 5s → 下一轮
```

## 三、定时任务执行（两种模式）

### CLI 模式（`_run_task`）— 子进程

```python
def _run_task(root: str, task: dict):
    """通过 subprocess 启动新进程执行"""
    subprocess.run(
        [sys.executable, start_py, "--user", user_name, "--prompt", command, "--once"],
        timeout=300, cwd=root, capture_output=True, text=True,
    )
```

```
cron 线程 → subprocess.run(start.py --user xxx --prompt "任务内容" --once)
  │
  ▼
新 Python 进程 → 加载配置 → 创建 provider → 执行一轮对话 → stdout 输出 → 退出
  │
  ▼
cron 线程捕获 stdout → 返回结果文本
```

### Web 模式（`_run_task_web`）— 进程内

```python
def _run_task_web(root: str, core_config: dict, task: dict):
    """进程内执行，不创建子进程"""
    provider = create_provider(user_config, core_config)
    chat = ChatManager(user_dir, ...)
    chat.load_history()
    # ... 构建 system_prompt, tools
    chat.add_user_message(command)
    for event in run_chat_turn(chat, tool_runner, provider, tools):
        # 收集结果
    # 保存归档
```

```
cron 线程（与 Flask 同进程）→ 进程内直接执行
  → create_provider → ChatManager → run_chat_turn → 归档
```

## 四、执行位置关系图

```
┌──────────────────────────────────────────────────────┐
│                    主进程 (Python)                     │
│                                                      │
│  ┌─────────────────┐   ┌──────────────────────────┐  │
│  │  CLI 模式        │   │      Web 模式             │  │
│  │                 │   │                          │  │
│  │  主线程: input()│   │  主线程: Flask (:5000)   │  │
│  │   └─ _run_turn()│   │   ├─ /api/chat          │  │
│  │                 │   │   └─ /api/messages       │  │
│  │  daemon: cron   │   │                          │  │
│  │   └─ _run_task()│   │  daemon: cron (web_mode) │  │
│  │      └─ 子进程  │   │   └─ _run_task_web()    │  │
│  └─────────────────┘   └──────────────────────────┘  │
│                                                      │
│  共享: run_forget, run_auto_improve_trigger           │
└──────────────────────────────────────────────────────┘
```

## 五、三种后台活动汇总

| 活动 | 触发者 | 执行位置 | 执行方式 |
|------|--------|----------|----------|
| 定时任务 | cron 心跳匹配时间 | CLI: 新进程 / Web: 进程内 | CLI: subprocess / Web: run_chat_turn |
| 临时记忆清理 | cron 每 60s | 进程内 cron 线程 | os.remove() |
| 被动 auto_improve | cron 每 3600s | 进程内 cron 线程 | provider.respond() → 写临时文件 |

## 六、并发安全

```python
# run/user_locks.py
def get_user_lock(user_name: str) -> threading.RLock:
    """每个用户一把可重入锁"""
```

| 操作 | 是否加锁 |
|------|----------|
| 定时任务执行 | ✅ get_user_lock |
| Web 对话请求 | ✅ 通过 session |
| run_forget() | ❌ 无锁 |
| run_auto_improve_trigger() | ❌ 无锁 |

## 七、任务结果推送

当定时任务来自外部消息路由时，执行完成后自动推送结果：

```
cron 执行完成 → task 有 source 字段?
  │  否 → 结束
  │  是 → enqueue_message(platform, chat_id, result)
  ▼
推送到 QQ/Telegram
```

## 八、任务存储格式

```
users/<name>/tasks/<id>.json
{
  "id": "abc123",
  "type": "daily",           // once | daily | recurring
  "time": "09:00",           // HH:MM 北京时间
  "command": "每日新闻摘要",
  "user": "username",
  "created_at": "2026-05-12T09:00:00",
  "last_run": "2026-05-17T09:00:00",
  "source": {                // 可选：来自外部消息路由
    "platform": "onebot",
    "chat_type": "private",
    "chat_id": "12345"
  }
}
```
