PostHog Signals 异常检测 Scout 的持久化记忆设计:Watchlist 台账、Explore-vs-Exploit 与 Scratchpad 内存约定
2026/9/19 10:28:38 网站建设 项目流程

PostHog Signals 异常检测 Scout 的持久化记忆设计:Watchlist 台账、Explore-vs-Exploit 与 Scratchpad 内存约定

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

导读

signals-scout-anomaly-detection是 PostHog Signals 体系中的一只异常检测 Scout,它持续监控团队最常查看的 Dashboard 与 Insight,并针对最新完整数据桶相对其季节性基线的偏离发出告警。它无法在一次运行中扫描整个项目,其核心杠杆在于一份持久化的 watchlist(观察清单):每次运行按预算拆分为exploit(利用:复查到期项目)explore(探索:补充新项目),让覆盖率随运行轮次不断复利累积,而不是每次冷启动。本文档即这份账本(ledger)与其背后内存约定的完整设计——读完你将掌握 scratchpad 键位词汇表、条目模式(schema)、轮询调度、退役与四态分类器等全部约定,并能直接复刻到自己的 Scout 技能中。

关联文档位于 watchlist-and-memory.md,是这只 Scout 的"脊梁",建议与技能主文件 SKILL.md、评分方法 anomaly-methods.md、报告契约 report-contract.md 一起阅读。

为什么需要一份持久化账本

一个繁忙项目拥有的 Dashboard 与 Insight 数量,远超单次短运行能够完成评分的范围。如果不做任何记忆,每次运行都会从零开始:既不知道上一次查到了什么,也无法区分"新出现的异常"与"早已上报过、仍在持续的异常"。

Watchlist-and-memory 设计给出的答案是一条核心原则:"scratchpad(便签板)是唯一的持久化层",Scout 把一整套账本维护在 scratchpad 上,并将每次运行拆分为两半:

  • Exploit(占运行主体):复查 watchlist 中到期的项目——每日项目约 24 小时复查一次,小时项目约 1–3 小时复查一次;按"超期最久优先"排序,逐个对照基线打分,直到时间预算耗尽。
  • Explore(占运行的一个切片):通过趋势榜单与最近访问的 Dashboard 发现少量新的高价值项目,让覆盖率跟上团队当前真正关心的内容。

二者缺一不可:只 exploit 会让清单逐渐过时,只 explore 则永远不做跟进。这正是 scout-patterns.md 中"Watchlist explore/exploit"模式的规范实现,其 canonical 例子就是本 Scout。

Scratchpad:唯一的持久化层

Scratchpad 是按团队隔离、以字符串为键的持久化散文存储(per-team prose keyed by string)。没有标签(tags)、没有 TTL(默认为永久有效):

  • 类别(category)就是键前缀(key prefix):后续运行只需一次text=搜索即可找到某类条目。
  • 复用键名会原地重写该条目(幂等刷新):可以用它更新基线或last_checked时间戳,而不会产生重复条目。

从仓库后端实现可以印证这套语义。scratchpad.py 中的remember()基于(team, key)的幂等 upsert:已存在则只更新content/expires_at/updated_at,并保留最初的created_by_run溯源;forget()按精确键删除(返回是否真的删除了东西);search_scratchpad()keycontent做 ILIKE 子串匹配。存储模型 SignalScratchpad 的字段约束与此一一对应:key为 300 字符、按团队唯一;content为无界 TextField(写端钳制在 50,000 字符);expires_at为可选的 TTL(不传即为永久)。搜索默认上限 20、最大 1000,这正是 SKILL.md 中"默认 limit 是 20,必须传高 limit,否则老旧到期项会被挤出视野"这一告诫的根源。

补充说明:expires_at在 PR2 简化评审时曾被移除,后来因"写了临时记忆的 Scout 几乎不会回来 forget"而恢复——过期条目先对search_scratchpad隐藏,超过宽限期后由每日 janitor 硬删除,而expires_at为空的条目永远不会被清扫。这只异常检测 Scout 的所有账本键位默认都是持久条目,恰好落在"durable by default"的语义上。

键位词汇表(Key vocabulary)

这是整个账本的核心契约。类别编码在键前缀中,一次text=搜索即可命中。完整词汇表如下(必须完整保留,勿缩水):

键前缀存放内容
watchlist:anomaly_detection:insight:<short_id>一条精选的待观察 Insight(账本行,schema 见下文)。
watchlist:anomaly_detection:dashboard:<id>一个精选的整页 Dashboard(当其所有磁贴被集体视为关键时进行整页清扫)。
watchlist:anomaly_detection:importance-refresh备忘:watchlist 的重要性排序上次在何时被重新校准,以及改变了什么。
baseline:anomaly_detection:insight:<short_id>已学习的"正常值":每个季节性桶的中位数 + MAD,使评分保持廉价。
report:anomaly_detection:insight:<short_id>指向你为此异常撰写的收件箱报告的指针:report_id+ 重新升级(re-escalation)条件。当一条 Insight 同时携带真正不同的并发异常(多序列 / breakdown,或反向移动)时,追加:<series-or-direction>后缀,避免它们坍缩到同一份报告。
retired:anomaly_detection:<suffix of the retired row>对某个已停止评分的 watchlist 项目的墓碑(tombstone):为什么停止,以及什么条件下会把它加回来。一行即可。
reviewer:anomaly_detection:<area>缓存的负责人:某个 Dashboard / 指标区域的裸小写 GitHub 登录名。
noise:anomaly_detection:<topic>一个需要忽略的模式(长期剧烈波动的 Insight、季节性怪癖)。
addressed:anomaly_detection:<topic>团队已确认的预期事件(发布/回填)或已修复——跳过。
allowlist:anomaly_detection:insight:<short_id>一个永远不向外呈现的 Insight(已弃用、沙箱、测试)。
not-in-use:anomaly_detection:team{team_id}收尾备忘:团队当前并未积极使用已保存的分析。

这套键位与 SKILL.md 的"Save memory as you go"一节完全一致:类别编码在前缀中,未来运行用一次text=搜索即可找到。在"快速收尾"场景中,not-in-use:anomaly_detection:team{team_id}支持以相同键幂等刷新时间戳,避免团队长期无人查看时反复做无用功。

Watchlist 条目 schema

每条watchlist:条目应保持为紧凑、可解析的一行,使下一次运行可以廉价地读取与更新:

key: watchlist:anomaly_detection:insight:ym0K91uz content: "Revenue over time | dashboards: go/revenue(198672) | metric: daily revenue sum | cadence: daily | priority: high | last_checked: 2026-06-07T12:00Z | next_due: 2026-06-08T12:00Z | last_status: normal (z=0.8) | added: 2026-06-05"

字段语义:

  • 人类可读名称(human name):如 "Revenue over time";
  • 所在 Dashboard:如go/revenue(198672),便于跳转与关联;
  • 实际打分的指标(metric):如daily revenue sum
  • 节奏(cadence)hourly/daily,由 Insight 定义与数据量推断而来(详见 anomaly-methods 的节奏选择表);
  • 优先级(priority)high/med/low,依据视图数 + 业务重要性得出;
  • last_checked:上次检查时间;
  • next_due:下次到期时间;
  • last_statusnormal/watch/reported(并附最近一次 z 值);对还无法建立基线的项目,low-data也是合法取值;
  • added:加入日期。

last_checkednext_due这对时间戳正是 round-robin 调度的载体:SKILL.md 中明确"只评分最新完整数据桶"、"复查到期项、最超期者优先",而账本让不同轮次覆盖不同项目,避免每小时的运行都重复检查同一批头部 Insight。

Baseline 条目 schema

基线条目让下一次运行无需重算整个基线即可对最新数据桶评分:

key: baseline:anomaly_detection:insight:ym0K91uz content: "daily revenue sum, same-weekday baseline over 8 weeks (computed 2026-06-07): Mon median ~$X MAD ~$Y; Tue median ~$X MAD ~$Y; ... weekend lower. Refresh weekly."

存储的信息量要足够下一次运行直接打分,但约每周从新鲜数据重新推导一次,使基线跟随真实漂移而非日渐陈旧。这对应 anomaly-methods 中"基线窗口"表(每日 6–8 周、同日星期匹配;每小时 2–4 周、同时段匹配)的落地形态:基线不是一次性的快照,而是周期性再推导的活数据。

Explore vs Exploit:每次运行的预算分配

Exploit:复查到期项(占运行主体)

  • 复查 watchlist 中到期的项目:每日项目next_due已过(约 24 小时节奏),小时项目已过约 1–3 小时节奏。
  • 最超期优先排序,自上而下工作,直到时间预算接近耗尽。
  • 边查边更新每个项目的last_checked/next_due/last_status
  • 异常正是在这里被真正捕获的

Explore:补充新项目(运行的一个切片)

新增少量高价值项目,使覆盖率跟随团队当前关注点:

  • insights-trending-retrieve——days=7用于稳定常青项目,days=1用于当下热门。高view_count是"团队关心"的首要信号。
  • recent_dashboards(来自 profile)+dashboard-get磁贴 —— 最近被访问的 Dashboard 上的 Insight 具有关联高价值。
  • 与已有 watchlist 交叉比对,只添加真正的新项目,每次运行至多约 2–3 个,每个都附带首条基线与节奏。

每几天刷新一次重要性——清单变大不等于"完成"

发现不只是新增项目;清单的成员与优先级会随团队焦点转移而过时。约每 3 天,把重要性排序本身当作要复查的对象:

  • 重新拉取insights-trending-retrievedays=7)与recent_dashboards,与已有 watchlist 对账——不只是"添加",更要重新排序与修剪:把视图数上升的项目priority调高,把 Dashboard 不再被访问或视图数崩塌的项目退役。上周创建、如今打开次数最多的 Dashboard 应当上榜;一个月无人打开的不该继续消耗预算。
  • "冷"需要正面证据,而非"缺席"。这两次读取都是 top-N——trending 默认为 10,recent_dashboards只装最近访问的——所以一个项目缺席可能只是它排在第 11 名。退役前要调高 limit 并检查项目本身。在有界列表中的缺席意味着"未知",不是"冷"。
  • 无论看起来多冷,以下条目永不退役:带有活跃report:指针的项目(你仍欠它下文四态所需的复发检查,且它的基线正是衡量已上报移动是否持续的尺子);以及importance-refresh备忘本身(节奏要运作,它必须存活)。
  • 通过处理真正变冷的项目来收尾刷新。在备忘里写下"活跃可评分集合"不是修剪——每条冷行都存活,账本继续增长而备忘却说覆盖率良好。要真正退役你判定为冷的行(见下文"退役条目"),使账本与备忘一致。在稳定清单上"本轮回合未退役任何条目"是完全正当的结局,在备忘里写明即可。绝不为了显得刷新有成果而退役活跃条目。
  • 清单很大不是跳过的理由。"清单已经成熟"是陷阱:它把覆盖率冻结在 bootstrap 时重要的内容上。刷新很廉价——两次读取加一次 diff——正是它让"重要"保持"当下"的含义。
  • 让它真正发生:维护一条watchlist:anomaly_detection:importance-refresh备忘,带last_refreshed时间戳与一行"改了什么"的说明。若它缺失或超过约 3 天,本轮在 exploit 之前先做刷新,然后复用同一键名原地更新。与每周基线再推导一样,这防止清单过时——但运行得更频繁,因为团队的注意力变化快于指标自身的分布。
  • 一条备忘,一个键名——绝不加日期后缀(如…:importance-refresh:2026-06-18)或用第二种写法。带日期的键无法原地重写,每次刷新都会留下一行残留,你想找的备忘越来越难找。这与remember()的幂等 upsert 语义直接呼应:只有固定键名才能"覆盖即更新"。

Round-robin:不要每次全量重扫

watchlist +next_due时间戳正是让连续多轮覆盖不同项目的机制,而不是每小时的运行都重复同一批头部 Insight。信任账本:如果某项目 20 分钟前被前一轮检查过,它现在就不到期。

给自己留指针

预算耗尽于扫描中途时,快速写一条笔记(复用运行摘要,或将下一条目的next_due设为"now"),让下一轮知道从哪继续。运行摘要(scout-runs-list)是天然的落点:写下"已检查 A–F;G–K 下一轮仍到期"。这与 SKILL.md 的"Close out"一节一致:摘要由 harness 保存,未来运行通过scout-runs-list读取。

退役条目(终结态)

不再评分的项目必须离开账本。priority: low不是终结态——它让该行继续出现在每次watchlist:搜索、round-robin 与真正被评分项目的路径中。退役按以下顺序执行:

  1. 先写紧凑的retired:墓碑(形状见下)。三步调用彼此独立,因此中途崩溃的运行留下的是一条记录,而不是覆盖率上无声的洞。
  2. scout-scratchpad-forgetwatchlist:anomaly_detection:…行。
  3. scout-scratchpad-forget掉该行拥有的baseline:anomaly_detection:…条目。基线比使用它的行活得更久,因此基线是第一个撑爆账本的东西——只退役 watchlist 行只是把臃肿挪了个位置。只删除"仅被退役行使用"的基线;Dashboard 行的磁贴可能与仍在评分的 Insight 行共享基线。
key: retired:anomaly_detection:insight:ym0K91uz content: "Revenue over time — retired 2026-06-14: the go/revenue dashboard hasn't been opened in ~6 weeks and its view count fell 412 → 3. Re-add if it returns to the trending ranking."

墓碑的键后缀与被替代行相同。它存在的意义是让下一次 explore 不把刚退役的项目再加回来——一行即可,不需要图表,不需要结论式散文。它与allowlist:不同:allowlist:表示"永远不要呈现这个"(已弃用 / 沙箱 / 测试);而retired:项目是正当的,只是今天不值得消耗预算。

在 explore 提出项目时搜索retired:,而不是在 orientation 阶段——否则墓碑会挤占你上限受限的读取,这正是整套约定要防止的失败。当退役项目确实赢得回归资格时,写入新的watchlist:行与基线的同时scout-scratchpad-forget掉墓碑,让账本绝不同时声称两种状态。

这条规则适用于每一条 watchlist 行,不只 Insight 键位的:一次完成的调查、一周某天的清扫、或已完成使命的 bootstrap 队列,都以同样方式退役——运行日志不是账本行。复盘文案应放在运行摘要(scout-runs-list)里,而不是watchlist:键下(round-robin 会不断把它捞起来)。importance-refresh备忘是唯一永不退役的watchlist:行。

保持工作集可扫视。orientation 搜索有上限,无界账本会把到期项目静默地藏在 limit 之后——这正是本约定要防止的确切失败。若某次watchlist:搜索返回的行数超过你几轮都评不完的量,多出来的部分是积压而非覆盖率:把最冷的退役,直到放得下。丢掉你自己的、超过约 90 天的retired:墓碑,以及窗口已过的带日期一次性条目。

只忘记自己拥有的键。scratchpad 是与所有其他 Scout 共享的团队级键空间,而scout-scratchpad-forget按精确键删除、不检查写入者是谁。把所有删除限制在本技能写入的anomaly_detection键内。另一个 Scout 的 cursor 或dedupe:行是它进行中的工作,不是你的积压——移除它会让那只 Scout 重复上报或丢失位置。这一点在 ARCHITECTURE.md 的SignalScratchpad一节有更广的表述:"一个键空间,许多写入者:团队内任何 agent 都能覆盖任何键,这正是为什么每个写提示都要求先搜索键并压缩、而不是盲写,也为什么每个读取者都把条目视为不可信输入。"

四个状态:上报前先分类

每次候选异常在进入上报通道前,必须对照先例、收件箱与 scratchpad 分类为以下四态之一(完整分类器见 watchlist-and-memory.md 与 SKILL.md 的 "Decide" 一节):

  1. Net new(全新)—— 没有先前的报告或 scratchpad 条目覆盖这次指标移动。若它越过门槛(稳健 z ≥ ~3.5、guard 通过、排除了季节性),则撰写报告(emit_report),并存放带新report_idreport:指针。
  2. Material update(实质性更新)—— 你已经上报过这条 Insight 的异常,但出现了新证据(仍在触发、升级了、扩散到相关 Insight、或与新的部署相关)。则编辑既有报告(edit_report):用append_evidence追加新观测(为新的时间窗口链接一份新 notebook)。不要为同一移动撰写第二份报告。
  3. Already covered(已被覆盖)—— 报告存在且移动未变,仍在窗口内。→ 跳过;可选地原地刷新report:指针的备注。
  4. Addressed or noise(已解决或噪音)—— 某条noise:/addressed:/allowlist:条目点名了它(长期剧烈波动的 Insight、已知的发布/回填、已弃用的 Insight)。→ 跳过;在摘要中记录。

这四态与 report-contract.md 的上报通道纪律咬合:上报前先搜索收件箱(inbox-reports-list,配合report:指针),通道不是幂等的,绝不撰写重复报告;复发时优先编辑既有报告而非新建;report:指针按稳定的short_id键控(无日期),再次确认会原地更新同一指针。

工作记忆示例(Worked memory examples)

好的条目对"未来运行"可执行——下一次运行读到它就会改变行为。以下三个是好条目,一个坏条目是反例:

key: report:anomaly_detection:insight:SRVNODib content: "report_id 0192f3a1-... — authored 2026-06-07 for the spike on 'LLM Costs By AI Product': daily sum 3.4x the 8-Saturday baseline (z=5.1), started 06-06. If still elevated next run, use `edit_report(append_evidence=...)` to escalate as sustained; if back within baseline, leave the report and stop."

这条report:指针同时携带report_id重新升级条件——下一次运行据此决定是edit_report升级为持续异常,还是回到基线后"留报告、停止"。注意它落在 SKILL.md 强调的规则上:report:按稳定short_id键控(无日期),并仅在一条 Insight 承载真正不同的并发异常时才加:<series-or-direction>后缀。

key: noise:anomaly_detection:insight:tQnsSMoI content: "'Generation calls' is chronically spiky — big legit swings on model launches and backfills. Require z>=4.5 AND a same-day deploy/launch correlation before reporting; otherwise refresh baseline only."

noise:条目不只是一句"忽略它",而是编码了新的触发条件(z ≥ 4.5 且需当日部署/发布相关),把误报源转化为可执行规则。

key: addressed:anomaly_detection:revenue-backfill-2026-06 content: "Revenue insights show a one-off step on 2026-06-03 from a Stripe backfill, not a real change. Team aware. Don't report revenue-series steps dated 2026-06-03."

addressed:条目把"团队已知的预期一次性事件"(Stripe 回填导致的台阶)固化为跳过规则,防止未来运行把它当真实变化上报。

坏条目反例:键note-1,内容 "revenue looked weird today"——没有实体、没有条件、没有类别前缀,既不可查找也不可执行。这正是键位词汇表与紧凑 schema 要防止的东西。

冷启动一个没有历史的团队(头几次运行)

第一次运行没有 watchlist。按以下方式引导:

  1. insights-trending-retrievedays=7limit=15)→ 团队观看次数最多的 Insight。
  2. 从 profile 读recent_dashboards→ 人类实际打开的 Dashboard。
  3. 从中挑出约 5–10 个最高价值的,为每个设置基线与节奏,写入它们的watchlist:条目。
  4. 本轮把时间允许范围内的都打上分;其余的下一轮到期。

到第 3–5 轮左右,你将拥有一份稳定清单,且每轮的大部分时间都花在对存储基线的快速廉价复查上——这正是整套设计的全部意义。探索阶段的工具组合(insights-trending-retrieverecent_dashboards+dashboard-get、必要时dashboards-get-all/insights-list/execute-sqlsystem.dashboards/system.insights)在 SKILL.md 的 "Explore" 一节有完整清单。

从源码看这套约定的实现支撑

这套文档约定并非空中楼阁,而是与仓库中的实际实现一一对应:

  • Scratchpad 语义:scratchpad.py 的remember()/forget()/search_scratchpad()分别实现了幂等 upsert、精确键删除、以及content+key的 ILIKE 检索;DEFAULT_SCRATCHPAD_SEARCH_LIMIT = 20MAX_SCRATCHPAD_SEARCH_LIMIT = 1000正是 SKILL.md 告诫"默认 limit 只有 20"的源码出处。
  • 存储模型:SignalScratchpad 以(team, key)唯一约束、content无界(读入未来运行提示)、expires_at可选 TTL(默认永久)——与"类别是键前缀、durable by default、无 tags 无 TTL"的约定完全吻合。
  • 模式地位:scout-patterns.md 将 "Watchlist explore/exploit" 单列为一档模式,明确指出"这是唯一自带参考文档集的特化 Scout,完整处理见signals-scout-anomaly-detection/",并概括其要点:watchlist:<domain>:<id>条目携带 last-checked 时间戳与逐项基线;"只 exploit 会过时,只 explore 从不跟进"。
  • 评分与上报接口:exploit 阶段的主力评分器alert-simulate、SQL 回退评分、以及emit_report/edit_report通道的字段契约,分别在 anomaly-methods.md 与 report-contract.md 中有完整细节;四态分类器中的 "Material update → edit_report" 与报告契约中的"复发即编辑、绝不重复上报"互为表里。

小结

Watchlist 与内存约定是这只异常检测 Scout 得以"越跑越聪明"的根:scratchpad 以键前缀承载类别、以固定键名支持幂等原地更新;exploit/explore 的预算拆分让覆盖率随时间复利;retired:墓碑与"只忘记自己拥有的键"防止账本臃肿与跨 Scout 误删;四态分类器让每一次候选异常都先对先例与记忆归类再决定上报、编辑还是跳过。对任何想要构建"watchlist 式"监控 Agent 的读者,这套键位词汇表、条目 schema 与调度纪律都是可直接照搬的成熟范本。

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询