Scheduled prompts, model self-wakeups, external triggers — v5 柱 H (shipped v0.6.1). Zero resident daemon.
定时 prompt、模型自我唤醒、外部触发——v5 柱 H(v0.6.1 已 ship)。零常驻 daemon。
seek doesn't run a background process. Instead, it teaches your OS scheduler (launchd / systemd / Task Scheduler) to invoke seek cron tick once a minute. seek's tick reads ~/.seek/cron/jobs.jsonl + triggers/, fires whatever's due, exits.
seek 自身不跑后台进程。改成教你的 OS 调度器(launchd / systemd / Task Scheduler)每分钟调用一次 seek cron tick。tick 读 ~/.seek/cron/jobs.jsonl + triggers/,触发所有到期任务,退出。
$ seek cron create --name ci-watch --at @daily \ --cwd ~/code/myproj \ 'check main branch CI status; summarise failures' $ seek cron list # see what's registered $ seek cron run ci-watch # fire NOW, bypass schedule
Saved to ~/.seek/cron/jobs.jsonl. But nothing fires yet — the OS scheduler hasn't been wired up. Continue below.
保存到 ~/.seek/cron/jobs.jsonl。但还不会自动跑——OS 调度器还没启动 tick,继续下面。
$ mkdir -p ~/Library/LaunchAgents $ cat > ~/Library/LaunchAgents/com.seek.cron.plist <<'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key><string>com.seek.cron</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/seek</string> <string>cron</string> <string>tick</string> </array> <key>StartInterval</key><integer>60</integer> <key>RunAtLoad</key><true/> </dict> </plist> EOF $ launchctl load ~/Library/LaunchAgents/com.seek.cron.plist
Replace /usr/local/bin/seek with your actual which seek. Uninstall: launchctl unload + rm.
把 /usr/local/bin/seek 改成你的 which seek 路径。卸载:launchctl unload + rm。
$ mkdir -p ~/.config/systemd/user $ cat > ~/.config/systemd/user/seek-cron.service <<'EOF' [Unit] Description=seek cron tick [Service] Type=oneshot ExecStart=%h/.local/bin/seek cron tick EnvironmentFile=-%h/.seek/cron/env EOF $ cat > ~/.config/systemd/user/seek-cron.timer <<'EOF' [Unit] Description=Fire seek cron tick every minute [Timer] OnBootSec=1min OnUnitActiveSec=1min [Install] WantedBy=timers.target EOF $ systemctl --user daemon-reload $ systemctl --user enable --now seek-cron.timer
The EnvironmentFile=- prefix means "no error if missing" — pairs with the env overlay in §3 below. Check: systemctl --user list-timers, journalctl --user -u seek-cron.service.
EnvironmentFile=- 前缀的 - 意味"文件不存在也不报错"——配合下面 §3 的 env overlay 一鱼两吃。检查:systemctl --user list-timers、journalctl --user -u seek-cron.service。
PS> schtasks /create /tn "seek cron tick" /tr "seek cron tick" /sc minute /mo 1 /ru "$env:USERNAME"
Or GUI: Task Scheduler → Create Basic Task; trigger Daily + Repeat every 1 minute; action Start a program (seek) with args cron tick. Uninstall: schtasks /delete /tn "seek cron tick" /f.
或 GUI:任务计划程序 → 创建基本任务;触发器选每天 + 重复任务每 1 分钟;操作启动程序 seek 参数 cron tick。卸载:schtasks /delete /tn "seek cron tick" /f。
~/.seek/cron/env~/.seek/cron/envCore problem: the OS scheduler hands your seek cron tick process a near-empty environment. launchd typically gives PATH=/usr/bin:/bin and nothing else; systemd user units inherit only what EnvironmentFile= names; Windows Task Scheduler is similar. Your .zshrc's DEEPSEEK_API_KEY will not cross that boundary automatically.
核心问题:OS 调度器拉起 seek cron tick 时 env 极简。launchd 通常只给 PATH=/usr/bin:/bin;systemd 用户单元只继承 EnvironmentFile= 列出的;Windows Task Scheduler 也类似。你 .zshrc 里的 DEEPSEEK_API_KEY 完全不会自动跨过这条边界。
Fix: write a dotenv-style file at ~/.seek/cron/env — seek auto-overlays it onto every spawned cron child:
解法:写一个 dotenv-style 文件 ~/.seek/cron/env——seek 自动叠加到每个 cron 子进程:
# ~/.seek/cron/env (macOS / Linux / WSL) # %USERPROFILE%\.seek\cron\env (Windows) DEEPSEEK_API_KEY=sk-... PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin # Linux desktop notifications additionally need: # DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
Format: one KEY=VALUE per line; # comments; balanced quotes stripped; no shell expansion ($HOME is three literal characters). Duplicate keys: last wins. Parse errors (missing =, empty key) fail spawn loudly — far safer than silently running without the API key you thought you set.
格式:一行一个 KEY=VALUE;# 注释;平衡引号被剥离;不做 shell 展开($HOME 是字面三个字符)。重复 KEY 后写赢。解析错误(缺 =、空 KEY)→ 明确报错,spawn 失败——比"静默掉 API key"安全多了。
EnvironmentFile=%h/.seek/cron/env at this same file makes it drive both systemd's env AND seek's overlay — one file, two layers.
💡 systemd 用户:EnvironmentFile=%h/.seek/cron/env 让同一个文件同时驱动 systemd 的 env 和 seek 的覆盖——一文件双用。
| Platform | 平台 | Implementation | 实现 | Status | 状态 |
|---|---|---|---|---|---|
| macOS | osascript -e 'display notification ...' | ✅ Notification Center banner | |||
| Linux | notify-send (libnotify) | ✅ Desktop (auto no-op when $DISPLAY + $WAYLAND_DISPLAY empty) | ✅ 桌面($DISPLAY+$WAYLAND_DISPLAY 都空时自动 no-op) | ||
| Windows | (none) | (无) | ⚠️ v0.6.1 no-op — BurntToast adapter planned for v0.6.x dot | ⚠️ v0.6.1 暂为 no-op——BurntToast 适配规划在 v0.6.x dot |
seek cron list for last_status and tail -F %USERPROFILE%\.seek\cron\runs\*.jsonl for run output.
⚠️ Windows 临时方案:toast 适配落地前,依赖 seek cron list 看 last_status + tail -F %USERPROFILE%\.seek\cron\runs\*.jsonl 查最近运行。
CI, IDE plugins, shell scripts can write JSON to ~/.seek/cron/triggers/ — the next tick consumes + deletes:
CI、IDE 插件、shell 脚本可写 JSON 到 ~/.seek/cron/triggers/——下一次 tick 消费 + 删除:
$ cat > ~/.seek/cron/triggers/ci-$(date +%s).json <<EOF
{
"trigger_id": "ci-build-1234",
"prompt": "CI build 1234 finished on branch foo-feature; summarise test failures",
"cwd": "/Users/me/code/myproj",
"ttl_seconds": 3600
}
EOF
Rules: file mtime must be older than "now − 1s" (prevents tick reading partial writes); parse failure → file renamed to triggers/.malformed/<id>.json + WARN, doesn't block; ttl_seconds expired → silently dropped; consumed → deleted (the directory is an inbox, not history). Run history lives in ~/.seek/cron/runs/<id>.jsonl with two-axis GC (keep 100 recent + 30d max-age).
规则:文件 mtime 必须早于"现在 − 1s"(防 tick 读半截);解析失败 → 文件 rename 到 triggers/.malformed/<id>.json + WARN,不阻塞;ttl_seconds 过期 → silently 丢弃;跑完即删(目录是 inbox,不是历史)。历史在 ~/.seek/cron/runs/<id>.jsonl,双轴 GC(保留 100 个最近 + 30 天最老)。
schedule_wakeup LLM toolschedule_wakeup LLM 工具The model can schedule a future check-in on itself — e.g., "30 minutes from now, look at CI again." Internally this is just a one-shot cron job (max_runs=1) registered through the same jobs.jsonl pipeline.
模型可以给自己安排未来回访——比如"30 分钟后再来检查 CI"。本质是通过同一 jobs.jsonl 管道注册一次性 cron 任务(max_runs=1)。
# Model calls (not user): schedule_wakeup({ "seconds": 1800, "prompt": "Check CI for branch foo-feature; report any new failures" }) # → registered as cron job; fires once; auto-deletes
| Symptom | 症状 | Likely cause / check | 大概率原因 / 检查 |
|---|---|---|---|
| Registered a job, never fires | 注册了 job,从来不跑 | OS scheduler not running. macOS: launchctl list | grep seek · Linux: systemctl --user list-timers · Windows: schtasks /query /tn "seek cron tick" | OS 调度器没启。macOS: launchctl list | grep seek · Linux: systemctl --user list-timers · Win: schtasks /query /tn "seek cron tick" |
| Fires but every run auth-fails | 跑了但每次 auth fail | Subprocess env missing API key. Check ~/.seek/cron/env exists; seek cron run <name> to reproduce | 子进程 env 没 API key。检查 ~/.seek/cron/env 是否存在;seek cron run <name> 重现 |
| Linux: "notify-send: command not found" | Linux 报 "notify-send: command not found" | libnotify not installed. apt install libnotify-bin / dnf install libnotify | libnotify 没装。apt install libnotify-bin / dnf install libnotify |
| Windows: notifications enabled but no popup | Windows 启用通知但没弹窗 | Known v0.6.1 limitation (see §4). Fallback to seek cron list. | v0.6.1 已知限制(见 §4)。暂时 fallback 到 seek cron list |
| Same job triggered twice | 同名 job 重复触发 | Prior run still active — per-job lock skips new trigger (design behavior; check runs/<id>.jsonl for WARN: prior run still active) | 上次 run 还没结束,per-job 锁跳过新触发(设计行为,看 runs/<id>.jsonl 的 WARN: prior run still active) |
| Env file parse error fails spawn | env 文件解析错就 spawn fail | By design — far safer than silent. Fix the syntax; re-run. | 设计如此——比静默更安全。修文件语法;再跑 |