ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

当 WorkBuddy 只能定时,DeepSeek Harness 用 TaoToken 做事件驱动

当 WorkBuddy 只能定时,DeepSeek Harness 用 TaoToken 做事件驱动 1. 从 WorkBuddy 定时更新到 DeepSeek Harness 事件驱动改造起点如果你也在用 WorkBuddy 给 Obsidian 做定时更新大概率遇到过这种尴尬cron 到点了笔记还没写完或者文件刚保存下一轮同步要等半小时。更麻烦的是高风险操作没有地方插入人工确认Agent 一跑就是全量更新。我最近把这条链路换成了 DeepSeek Harness事件驱动接入 TaoToken官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_drivenBase URL 用https://taotoken.net/apiKey 用YOUR_API_KEY占位。改造目标很明确文件一改就触发、保存多次只跑一次、内容没变不重复调用、必填项不全不放行、高风险操作先审批、每次调用都记录 Token。和只贴 TypeScript 源码的做法不同这篇会把接入 TaoToken 的 Key 获取、Claude Code / Codex / CC Switch 配置、Obsidian 目录监听、防抖指纹、条件放行、Token 消耗记录和排障步骤串起来。消耗 Token 的主角不是 WorkBuddy而是 DeepSeek Harness 触发的知识更新 Agent所以重点不是“定时任务能不能跑”而是“事件来了以后Agent 该不该跑、跑多少、花了多少 Token、结果能不能审计”。过去 WorkBuddy 的任务模型是 cron 驱动Obsidian 侧只能等定时窗口。它适合日报、周报、夜间批处理但不适合知识库这种随时改、随时想更新的场景。DeepSeek Harness 的思路反过来把 Obsidian 的 Markdown 文件变更当成事件源事件经过防抖、指纹比对、frontmatter 校验之后才决定是否调用知识更新 Agent。这样一来定时更新变成了按需触发Token 消耗也从“每轮全量”变成“按真实变更增量”。下面先把定时和触发的差异列清楚再进入 TaoToken 配置和监听脚本。2. 定时与触发对照WorkBuddy 和 DeepSeek Harness 在 Obsidian 场景的差异先给一张对照表方便判断你的知识库适不适合从定时切到事件驱动维度WorkBuddy 定时更新DeepSeek Harness 事件驱动触发时机cron 到点才执行Markdown 文件变更即触发延迟分钟级到小时级通常秒级到十秒级更新范围容易变成全量扫描可只处理变更文件人工审批难插入确认步骤可在调用 Agent 前阻断Token 消耗固定周期消耗空跑多按真实变更消耗失败重试等下一周期指数退避或进入待处理队列可观测性偏任务日志可按文件、指纹、Token 记录适用场景日报、周报、夜间批处理知识库实时更新、项目文档联动WorkBuddy 不是不能用。如果你的笔记每天只更新一次或者更新必须集中在凌晨低峰期定时任务反而更简单。问题在于 Obsidian 知识库往往是碎片化更新项目背景补一段、目标改一句、会议记录新增一条、决策记录追加一段。定时任务很难判断“这次到底值不值得跑”而事件驱动可以精确到文件。真正要解决的是三个问题第一文件保存一次可能触发多次事件。编辑器保存、同步盘回写、格式化插件改写都会让监听器连续收到change。如果不防抖一个文件能触发五六次 Agent 调用Token 消耗直接翻倍。第二文件名变了不代表内容变了。有些操作只改 mtime内容指纹一致这时不应该调用 Agent。内容指纹比 mtime 可靠。第三不是所有变更都允许自动更新。比如项目背景、目标、风险等级这些字段没填全或者标记了risk: high就应该进入审批队列而不是直接调用模型。所以事件驱动方案不是“监听文件然后调模型”这么简单它是一条带过滤器的流水线Obsidian Markdown 变更 - 目录监听 - 防抖 - 内容指纹比对 - frontmatter 必填项校验 - 风险分级 - 调用 DeepSeek Harness 知识更新 Agent - 记录 Token 消耗 - 输出更新建议或进入审批队列这条流水线里TaoToken 提供模型调用入口Base URL 是https://taotoken.net/apiKey 从官网控制台获取。接下来先解决接入配置。3. 在 TaoToken 拿 Key 并配置 Claude Code / Codex / CC Switch先到 TaoToken 官网注册或登录拿到 API KeyTaoToken 官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_driven登录后进入控制台创建 API Key。复制出来的值不要写进代码仓库建议放进环境变量或本地密钥文件。Key 占位符统一写成YOUR_API_KEY。Base URL 固定为https://taotoken.net/api注意Base URL 是工具配置用的不要在后面拼 UTM 参数。UTM 只用于官网和 deep link 追踪。3.1 Claude Codesettings.json 用 ANTHROPIC_*Claude Code 的配置文件通常放在~/.claude/settings.json。如果你的环境使用项目级配置也可以放在项目.claude/settings.json。核心是把ANTHROPIC_BASE_URL指向 TaoToken把ANTHROPIC_API_KEY换成你的 Key{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: YOUR_API_KEY, ANTHROPIC_MODEL: claude-sonnet-4-5 } }如果你的 Claude Code 版本支持模型切换ANTHROPIC_MODEL可以按 TaoToken 控制台里显示的模型名填写。不要凭空写不存在的模型 ID以控制台可用列表为准。配置完成后在终端验证claude进入交互界面后发一句“只回复 ok”能正常返回就说明 Key 和 Base URL 通了。如果报 401优先检查ANTHROPIC_API_KEY是否带了多余空格如果报 404检查ANTHROPIC_BASE_URL是否误写成了带/v1或带 UTM 的地址。3.2 Codexconfig.toml 不要套 ANTHROPIC_*Codex 用 TOML 配置常见路径是~/.codex/config.toml。这里千万不要把ANTHROPIC_*抄过来Codex 不认这套变量。正确做法是配置一个自定义 model providermodel gpt-5-codex model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api responses然后在 shell 里设置export TAOTOKEN_API_KEYYOUR_API_KEY如果你用的是 Windows PowerShell$env:TAOTOKEN_API_KEYYOUR_API_KEYCodex 的wire_api可以按你使用的版本选择responses或chat以 Codex 文档和 TaoToken 控制台说明为准。核心记住一点Codex 用config.toml 自定义 provider不要把 Claude Code 的ANTHROPIC_*环境变量套到 Codex 上。3.3 CC Switch 三件套Base URL、API Key、ModelCC Switch 用来在多个供应商、多个模型之间切换。配置时只需要盯住三件套Base URL: https://taotoken.net/api API Key: YOUR_API_KEY Model: claude-sonnet-4-5如果 CC Switch 支持自定义供应商名称可以写成TaoToken。切换完成后用一次最小请求验证。能返回内容说明三件套生效如果返回鉴权失败先确认 API Key 是否属于当前 Base URL 对应的账号。3.4 DeepSeek Harness 的 OpenAI 兼容接入DeepSeek Harness 触发知识更新 Agent 时如果走 OpenAI 兼容协议可以这样设置环境变量export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYYOUR_API_KEY export KNOWLEDGE_AGENT_MODELYOUR_MODEL_IDTypeScript 侧初始化客户端时把baseURL指到https://taotoken.net/apiapiKey读取TAOTOKEN_API_KEY。不要硬编码 Key也不要把 Key 提交到 Git。4. Obsidian 事件监听文件变更、防抖、指纹与条件放行这一节是方案核心。目标监听 Obsidian 笔记目录只对真正变化的 Markdown 文件触发知识更新 Agent。4.1 监听目录监听范围限定在 Vault 下的**/*.md忽略.obsidian、.trash、临时文件和隐藏目录。推荐使用chokidar因为它对编辑器的“先写临时文件再重命名”行为处理得比较好。同时开启awaitWriteFinish避免文件还没写完就被读取。4.2 防抖一次保存可能触发多个事件。防抖窗口可以设 800ms 到 1500ms。按文件路径做 key同一文件在窗口内只处理最后一次事件。4.3 内容指纹不要只看修改时间。读取文件内容后计算 SHA-256 指纹和上一次记录的指纹比对。指纹相同就直接返回不调用 Agent。指纹不同才进入下一步。4.4 frontmatter 条件放行不是有改动就更新。读取 YAML frontmatter检查knowledge_update: trueproject_background非空goals非空risk不是high示例笔记头--- knowledge_update: true project_background: 该项目用于验证事件驱动知识库更新链路 goals: 降低无效 Agent 调用记录每次 Token 消耗 risk: low ---如果risk: high不直接调用 Agent而是写入待审批队列由人工确认后再继续。4.5 可跟做的 TypeScript 监听脚本下面是一个最小实现依赖chokidar、gray-matter、openai和 Node.js 内置模块// listener.ts import chokidar from chokidar; import matter from gray-matter; import crypto from node:crypto; import fs from node:fs/promises; import path from node:path; import OpenAI from openai; const VAULT process.env.OBSIDIAN_VAULT ?? /path/to/your/vault; const BASE_URL https://taotoken.net/api; const API_KEY process.env.TAOTOKEN_API_KEY ?? YOUR_API_KEY; const MODEL process.env.KNOWLEDGE_AGENT_MODEL ?? YOUR_MODEL_ID; const client new OpenAI({ baseURL: BASE_URL, apiKey: API_KEY }); const fingerprints new Mapstring, string(); const pending new Setstring(); function sha256(text: string): string { return crypto.createHash(sha256).update(text).digest(hex); } function hasRequiredFields(data: Recordstring, unknown): boolean { return [project_background, goals].every((key) { const value data[key]; return typeof value string value.trim().length 0; }); } async function queueForApproval(file: string, fingerprint: string, content: string) { const approvalDir path.join(VAULT, .harness, approvals); await fs.mkdir(approvalDir, { recursive: true }); await fs.writeFile( path.join(approvalDir, ${path.basename(file)}.${fingerprint.slice(0, 8)}.json), JSON.stringify({ file, fingerprint, content, createdAt: new Date().toISOString() }, null, 2), utf8 ); } async function runKnowledgeAgent(file: string, fingerprint: string, content: string) { const started Date.now(); const completion await client.chat.completions.create({ model: MODEL, messages: [ { role: system, content: 你是知识库更新 Agent。只输出结构化更新建议不要执行删除不要生成 SQL 直接操作生产库。 }, { role: user, content: 文件${path.basename(file)}\n指纹${fingerprint}\n正文\n${content.slice(0, 12000)} } ], temperature: 0.2, max_tokens: 1200 }); const usage completion.usage; const logLine JSON.stringify({ ts: new Date().toISOString(), file: path.basename(file), fingerprint, model: MODEL, prompt_tokens: usage?.prompt_tokens ?? 0, completion_tokens: usage?.completion_tokens ?? 0, total_tokens: usage?.total_tokens ?? 0, latency_ms: Date.now() - started }); const logDir path.join(VAULT, .harness); await fs.mkdir(logDir, { recursive: true }); await fs.appendFile(path.join(logDir, token-usage.jsonl), logLine \n, utf8); } async function handleFile(file: string) { if (pending.has(file)) return; pending.add(file); try { const raw await fs.readFile(file, utf8); const fingerprint sha256(raw); if (fingerprints.get(file) fingerprint) { return; } fingerprints.set(file, fingerprint); const parsed matter(raw); if (parsed.data.knowledge_update ! true) return; if (!hasRequiredFields(parsed.data)) return; if (parsed.data.risk high) { await queueForApproval(file, fingerprint, parsed.content); return; } await runKnowledgeAgent(file, fingerprint, parsed.content); } catch (error) { console.error([harness] handle file failed: ${file}, error); } finally { pending.delete(file); } } function debounceT extends (...args: any[]) void(fn: T, waitMs 1000) { const timers new Mapstring, NodeJS.Timeout(); return (...args: any[]) { const key String(args[0]); const old timers.get(key); if (old) clearTimeout(old); timers.set( key, setTimeout(() { timers.delete(key); fn(...args); }, waitMs) ); }; } const watcher chokidar.watch(${VAULT}/**/*.md, { ignored: [/(^|[/\\])\../, **/.obsidian/**, **/.trash/**], awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 } }); const onFileChange debounce(handleFile, 1000); watcher.on(add, onFileChange); watcher.on(change, onFileChange); console.log([harness] watching ${VAULT});运行方式export OBSIDIAN_VAULT/path/to/your/vault export TAOTOKEN_API_KEYYOUR_API_KEY export KNOWLEDGE_AGENT_MODELYOUR_MODEL_ID npx tsx listener.ts这段代码做了四件事监听 Markdown、防抖、指纹去重、按 frontmatter 放行。高风险文件不会直接调用 Agent而是落到.harness/approvals。Token 消耗会写到.harness/token-usage.jsonl。5. 知识更新 Agent 的请求编排与 Token 消耗记录事件驱动省 Token 的关键在于“少调用”和“调用后能算账”。少调用靠防抖、指纹、frontmatter 校验算账靠 usage 记录。每次 Agent 调用后TaoToken 返回的 usage 里通常包含prompt_tokens、completion_tokens、total_tokens。不要只打印到控制台要落到 JSONL。示例{ts:2026-01-15T10:22:31.245Z,file:project-a.md,fingerprint:9f2c...,model:YOUR_MODEL_ID,prompt_tokens:1840,completion_tokens:320,total_tokens:2160,latency_ms:4120}如果你想按天汇总可以把 JSONL 导入本地 SQLite然后执行-- 读者在本机 sqlite3 中执行 CREATE TABLE usage ( ts TEXT, file TEXT, fingerprint TEXT, model TEXT, prompt_tokens INTEGER, completion_tokens INTEGER, total_tokens INTEGER, latency_ms INTEGER ); -- 导入后按天统计 SELECT date(ts) AS day, sum(prompt_tokens) AS prompt_tokens, sum(completion_tokens) AS completion_tokens, sum(total_tokens) AS total_tokens, count(*) AS runs FROM usage GROUP BY date(ts) ORDER BY day DESC;这段 SQL 由你在本地执行不要让知识更新 Agent 直连生产库。Agent 只读 Markdown、只输出建议涉及数据库变更、删除文件、覆盖配置一律由人工在本地确认后执行。进一步控制 Token 的方法只把变更文件的相关片段发给模型不要全量 Vault。限制max_tokens避免模型长篇扩写。对相同指纹直接跳过。对低价值变更比如只改错别字可以在 frontmatter 里加knowledge_update: false。高风险操作进入审批队列审批不通过就不消耗 Token。在我的记录里改造前 WorkBuddy 定时全量跑即使当天没有实质更新也会产生调用改造后只有指纹变化且必填项齐全的文件才会触发 DeepSeek Harness 的知识更新 Agent。高频编辑时防抖和指纹去重减少的重复调用最明显低频编辑时按需触发减少的空跑最明显。6. 排障实录401、429、重复触发与 YAML 解析失败事件驱动链路比定时任务多几个故障点但每个都有明确排查顺序。6.1 401 Unauthorized常见原因TAOTOKEN_API_KEY没设置或者值前后有空格。用了旧 Key控制台已删除。Claude Code 配了ANTHROPIC_API_KEY但 Codex 侧还在读TAOTOKEN_API_KEY两边混淆。Base URL 写错比如写成了https://taotoken.net/api/且工具又拼了路径导致重复斜杠。排查echo $TAOTOKEN_API_KEY | wc -c确认长度正常再用最小请求验证。Claude Code 看ANTHROPIC_*Codex 看config.toml里的env_key。6.2 429 Too Many Requests文件批量变更时防抖窗口内可能同时触发多个文件。如果你没有做并发限制几十个文件同时调用就会撞限流。解决加队列限制同时只跑 2 到 3 个 Agent。失败后指数退避比如 1s、2s、4s、8s。把非紧急文件放进下一批。对低风险文件合并成一个摘要请求。6.3 重复触发如果你发现同一个文件连续调用多次按顺序检查防抖是否按文件路径做 key而不是全局一个 timer。awaitWriteFinish是否开启。指纹是否在读取后立即写入内存如果等 Agent 跑完才写期间第二次变更会再次进入。编辑器同步盘是否在保存后回写元数据。6.4 YAML 解析失败gray-matter解析失败时直接跳过该文件并记录日志不要让它中断整个监听进程。常见原因是 frontmatter 里混用了 Tab、冒号后没空格、字符串里包含未转义引号。6.5 审批队列堆积高风险文件会进入.harness/approvals。如果队列一直堆积检查risk字段是否被误写成high。审批通过后再手动触发对应文件的处理或者把审批结果写回 frontmatter 后保存让事件驱动重新走一遍。7. 落地路径从模型对话到 Coding Plan、创建 Key 与 Claude Code 文档如果你准备把 WorkBuddy 的定时更新替换成 DeepSeek Harness 事件驱动建议按下面路径落地。先验证模型对话再决定套餐然后创建 Key最后对照 Claude Code 文档把工具配置跑通。模型对话先确认你的账号能正常调用模型。https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_drivenCoding Plan如果你要把知识更新 Agent 接进日常开发流可以查看 Coding Plan 是否匹配你的调用量。https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_driven创建 Key在控制台创建 API Key复制后填入YOUR_API_KEY位置。https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_drivenClaude Code 文档如果你同时用 Claude Code对照文档配置settings.json和ANTHROPIC_*。https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_driven官网入口也放在这里方便注册和查看控制台https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian_event_driven回到方案本身WorkBuddy 定时更新并不是错误答案它只是不适合“文件随时改、知识随时更新、Token 要按需消耗”的场景。DeepSeek Harness 配 Obsidian 事件驱动核心不是插件多高级而是把触发条件收紧文件变更才触发防抖后只跑一次指纹相同不跑必填项不全不跑高风险先审批跑完记录 Token。Base URL 统一用https://taotoken.net/apiKey 从 TaoToken 控制台创建Claude Code 走settings.json和ANTHROPIC_*Codex 走config.tomlCC Switch 盯住 Base URL、API Key、Model 三件套。这样从定时到事件驱动的改造才算真正落到可跟做、可排障、可算账的工程链路上。
返回列表