
PostHog 实验生命周期管理完全指南状态机、动作语义与决策框架【免费下载链接】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本指南以 PostHog 仓库中的managing-experiment-lifecycle技能文档为主体系统讲解实验从 launch、pause/resume、freeze/unfreeze exposure到 end、ship variant、archive、reset、duplicate 与 copy to project 的完整状态流转每个动作的前置条件、对「谁看到什么变体」与「谁进入分析」两个维度的具体影响、适用场景与常见 400 错误。读完后你将能够准确回答「什么时候该冻结曝光而不是结束」「ship variant 与 end 的本质区别」「复制到另一项目时 feature flag 的三种命运」等实操问题并掌握每个动作在 MCP 工具experiment-launch、experiment-freeze-exposure等与后端服务experiment_service.py中的底层实现逻辑。实验生命周期状态机总览PostHog 的实验Experiment围绕一个核心事实展开每个实验都绑定一个多元multivariate特性开关feature flag生命周期操作本质上是对该 flag 与实验自身日期字段的组合操作。完整状态流转如下draft ──launch──▶ running ──end──▶ stopped ──archive──▶ archived │ │ ▲ │ │ pause resume ship_variant │ │ │ (also ends if running) │ ▼ │ │ paused (flag inactive, still running status) │ ├─freeze_exposure──▶ exposure_frozen ──unfreeze_exposure──▶ running │ (enrollment closed, metrics keep flowing) Any non-draft state ──reset──▶ draft值得注意的细节与 实验模型 的源码实现一致paused与exposure_frozen都不是持久化的独立状态而是派生状态。从源码看is_paused被实现为「running 状态 关联 flag 已停用」self.is_running and not self.feature_flag.activeis_exposure_frozen被实现为「running 状态 flag 每个 release group 都带有冻结标记」groups_carry_restriction_marker(...)且 paused 优先于 frozen——flag 停用后先报告 paused恢复后重新落到 frozen。这就是状态图中 paused 仍标注为 running status 的原因。实验持久化的status只有draft/running/stopped三态见computed_status而对外暴露的status_label才是五态字符串draft/running/paused/exposure_frozen/stopped。任何非 draft 状态都可以通过reset回到 draft这也是唯一一条跨越所有状态的通路。理解每个动作的两把尺子对于每一个生命周期动作请始终带着两个问题去判断其影响谁看到什么变体用户视角flag 是否激活、分组与变体分配是否改变谁进入我的分析统计视角哪些曝光与指标数据进入结果计算下文每个动作都会用这两把尺子逐一拆解。Launch启动实验experiment-launch将实验从draft推进到running激活特性开关flagactivetrue并写入start_date当前服务器时间。前置条件必须处于 draftflag 需要 2–20 个多元变体不要求特定 key分析基线默认取 key 为control的变体若不存在则取第一个变体。上线前检查清单至少有一个指标变体配置正确flag 已在代码中实现变体影响用户开始按配置的分流比例被分桶bucketing到各变体。分析影响数据采集自start_date起算。请求体不需要No request body needed。源码印证在 experiment_service.py 的 launch_experiment 中可以看到完整链路先校验is_draft否则抛Experiment has already been launched.、校验 flag 配置然后在行锁内设置start_date、按新start_date重算每个指标的指纹fingerprint并持久化。特别地set_flag_active(...)会先走审批门approval gate——若团队启用了变更审批此处会以 409 中止且start_date保持为空实验不会启动。一个可选的建议当改动面向用户且影响较大时可在 launch 时一次性提及在用户完成被实验流程例如由表单 submit 事件触发后展示一个短问卷从第一天起在指标之外收集定性反馈一个评分 可选评论。仅作为设置建议提出一次被婉拒就不再坚持绝不让它拖延上线不要在 end 或 ship variant 时再提——那会被读作对放量的设卡。相关参考见 诊断实验结果技能中的定性反馈参考文档属于diagnosing-experiment-results技能。Pause暂停与 Resume恢复Pauseexperiment-pause停用特性开关flagactivefalse用户回落到默认体验通常是 control。前置条件必须处于 running 且尚未暂停。变体影响flag 不再由/decide返回——不再记录新的曝光事件。分析影响暂停期间没有新数据但已有数据保留实验状态保持 running不会被标记为 ended。请求体不需要。用experiment-resume重新激活。源码印证pause_experiment 依次校验 draftExperiment has not been launched yet.、stoppedExperiment has already ended.、无关联 flag、以及 flag 已停用Experiment is already paused.然后同样走set_flag_active(False)审批门并在实验自身的 activity log 写入paused事件flag 的翻转只记录在 FeatureFlag 作用域下没有这条补记实验的 History 页将看不到暂停。Resumeexperiment-resume在暂停后重新激活 flag用户被确定性地重新分桶回暂停前的同一变体。前置条件必须处于 paused。变体影响与暂停前分配一致——确定性分桶deterministic bucketing。分析影响曝光追踪恢复。请求体不需要。Freeze exposure冻结曝光与 Unfreeze解冻这是实验生命周期中最微妙、也是能力最强的动作值得单独深挖。Freeze exposure 做什么experiment-freeze-exposure停止新用户入组同时让其他一切照常已入组用户保留原变体、指标继续累积、end_date保持为 null。实现上会把已曝光用户快照成一个静态人群static cohort并把 flag 的每一个 release condition 都收窄到该人群。实验状态变为exposure_frozen。适用场景当样本量已足够、面向长期指标收入、LTV、留存、续费且你希望「停止新增用户但不停止计量」时。end和pause都做不到这件事end会在end_date停止计量pause则对所有用户停用 flag。前置条件必须处于 running不能是 draft、stopped、paused 或已冻结flag 已关联且未删除至少有一个 release condition。变体影响已入组用户保留变体确定性分桶新用户不再匹配该 flag。分析影响曝光量停止增长预期出现平坦的曝光曲线但已入组用户的指标数据持续累积。请求体不需要。用experiment-unfreeze-exposure重新开放入组。性能特征同步且可能很慢曝光扫描与人群快照在 API 调用内同步执行耗时随已曝光人数线性增长——数万已曝光用户的实验可能耗时数十秒量级。请提前向用户说明预期、等待响应不要把慢调用当成失败去重试。源码印证freeze_exposure 的实现注释直接印证了这一点并给出了三个关键的同步上限常量FREEZE_EXPOSURE_QUERY_TIMEOUT_SECONDS 20ClickHouse 扫描超时FREEZE_EXPOSURE_MAX_EXPOSED_USERS 100_000可快照的最大已曝光人数扫描取 cap1 行用于检测超限并干净拒绝FREEZE_EXPOSURE_MAX_UNRESOLVED_SHARE 0.05无法解析到 person 档案的匿名personless用户占比上限。实现分为两阶段阶段一无锁快速跑守卫、执行昂贵的 ClickHouse 曝光扫描与人群构建阶段二在select_for_update行锁下重新校验所有守卫、基于最新状态计算收窄后的 filters 并落库——这样并发到达的第二个 freeze 会得到already_frozen并删除自己刚建的空快照人群而不是双重冻结。快照人群命名带Exposure snapshot for experiment前缀任何失败路径都会清理已创建的快照绝不让 flag 指向一个未填充的人群。哪些实验会收到 400结构性限制不要重试Group 聚合实验——flag 面向 groups 而非 personsperson 人群无法冻结基于 group 的匹配处于 holdout 中的实验——holdout 分配在 release conditions 之前求值新用户仍会进入 holdout带 early access 条件的 flag——同样在 release conditions 之前求值冻结无法阻止新入组绝大多数为匿名曝光的实验如未登录页面的实验——匿名 personless 用户永远无法匹配 person 人群会静默丢失变体未解析占比超过小阈值即被拒绝超大规模的已曝光集合——曝光扫描受 person 上限与超时约束任一超限都以干净的 400 拒绝而不是冻结。当冻结被拒绝时向用户说明是哪一条结构性限制而不是重试——这些限制是结构性的不是暂时性的。与其他动作的相互作用易错点ship-variant与reset都会剥离冻结同时删除快照人群end不会触碰 flag——结束一个已冻结的实验flag 仍收窄在快照人群上如不希望如此先 unfreeze 或 ship variant使用**本地求值local evaluation**的 SDK 无法解析静态人群因此冻结后的 flag 会退回通过/decide端点求值标准静态人群行为冻结前最后一刻摄入的曝光可能错过快照摄入延迟。Unfreeze exposure 的偏倚警告experiment-unfreeze-exposure重新开放入组从每个 release group 移除快照人群条件与冻结标记、恢复 flag 原始定向并删除快照人群。状态回到running。前置条件曝光必须处于冻结状态且实验未结束。变体影响已入组用户保留变体新用户可在原始 release conditions 下重新入组。分析影响曝光量恢复增长。可能引入偏倚重新开放入组会把 flag 暴露给一个潜在的新人群。冻结前入组与解冻后入组发生在不同时间、可能在不同条件下把两批人混在一个分析里会污染结果。解冻前务必提醒用户——尤其是经历了长时间冻结、或期间受众/产品已发生变化时。如果用户只是想 sanity-check 冻结结果也许根本不需要解冻。源码印证unfreeze_exposure 通过_strip_frozen_exposure反操作冻结只移除快照人群条件与冻结印记保留冻结期间对 group 的编辑如新增条件快照人群在 flag 落库之后才软删除避免留下孤儿人群。End结束实验experiment-end设置end_date并将实验转为stopped。特性开关不会被修改。前置条件必须处于 running已 launch、未结束。变体影响用户继续看到被分配的变体flag 保持激活。分析影响结果冻结到end_date之前的数据。可选请求体conclusion取值won/lost/inconclusive/stopped_early/invalid与conclusion_comment。适用时机你想冻结结果但不改变用户看到的内容。若实验已冻结曝光end不会剥离冻结——flag 仍收窄在快照人群上不想要这样就先 unfreeze 或 ship variant。源码印证end_experiment 是纯实验侧写入只设置end_date、conclusion、conclusion_comment并递增版本号注释明确写着 Does NOT modify the feature flag。Ship variant发布获胜变体experiment-ship-variant重写特性开关使选定变体对100% 的用户生效。前置条件必须已 launchrunning 或 stopped 均可不能从 draft 直接 ship。变体影响所有用户看到被发布的变体——flag 被重写为 catch-all 全量发布组。分析影响若实验仍在 running则同时被结束设置end_date。必填variant_key例如test可选conclusion、conclusion_comment。若审批策略要求 flag 变更前审批返回409。务必先与用户确认再 ship——这会永久性地重写特性开关。它不只是发布动作还会触发实验结束因此属于「放量 收尾」的组合操作。源码印证ship_variant 展示了两个值得注意的实现细节默认不强制全量release_to_everyoneFalse时保留原有 release conditions变体只对原本就匹配条件的用户生效配合release_to_everyoneTrue才会前置追加 catch-all 组、覆盖原有条件与用户级覆盖。ship 会剥离冻结冻结实验的 release groups 带有机加的快照人群条件ship 会在同一次 flag 写入中将其剥离否则默认模式下发布结果会被锁死在过期快照上并随后清理快照人群。若 flag 写入因ApprovalRequired失败实验不会结束flag 仍保持冻结。Flag cleanup PR结束/发布时清理标志代码的可选动作experiment-end与experiment-ship-variant都接受open_cleanup_pr: true。此时一个后台 PostHog Code 任务会移除实验的 feature flag 代码并在团队已连接的 GitHub 仓库中打开一个draft PR。使用规则与限制仅在用户主动要求或确认时设置调用方 token 必须携带task:writescope否则整个请求以403被拒绝实验不会被结束或发布清理只在调用确实结束实验且设置了conclusion时执行——对已 stopped 实验 ship、或 end 时不带 conclusion 都会跳过还要求团队开启 flag cleanup 特性未开启时静默跳过repository格式organization/repository在连接了多个仓库时指定目标省略时依次回退到实验保存的仓库、团队默认、唯一连接的仓库存在多个候选且无默认时不提供则跳过清理用experiment-cleanup-task追踪进度——PR 地址出现在其中后即可获知一次清理通常需要几分钟。源码印证get_cleanup_repository_target 的仓库选择逻辑与文档完全对应且多了一条安全约束显式指定的仓库必须属于本团队安装GitHub installation 可被共享未校验的名字可能通过共享凭证触达其他项目的私有仓库否则以ambiguous跳过而不是静默改投。清理任务在事务提交后transaction.on_commit才创建确保回滚的 end 永远不会打开 PR。Archive归档experiment-archive将已结束的实验从默认列表视图中隐藏。前置条件必须已 stoppedend_date已设置。变体影响无变化——flag 不受影响。分析影响无变化——结果仍可访问。请求体不需要。可通过experiment-update设置archivedfalse恢复。源码补充archive_experiment 还有文档外的联动若关联 flag 仍处于激活例如正在放量获胜变体归档时默认不停用该 flag——需要显式传disable_feature_flag才会连停带归档已停用的 flag 则无条件随实验归档。联动还受调用方是否持有feature_flag:writescope 约束。Reset重置回草稿experiment-reset将实验恢复到 draft 状态清空start_date、end_date、conclusion、conclusion_comment与archived。前置条件当前不能已在 draft。变体影响flag 保持不变——用户继续看到被分配的变体。分析影响此前采集的数据仍然存在但重新 launch 后若不手动调整start_date这些数据不会进入结果。请求体不需要。源码补充reset_experiment 会顺带剥离冻结机加的快照人群收窄必须清除否则重 launch 的实验「生而冻结」、永远无法入组并清空flag_cleanup_task_id避免结束运行遗留的「Cleanup PR 已打开」提示在重新结束后复活。Duplicate复制为同一项目的新草稿experiment-duplicate创建一份新草稿副本新日期、无结果但继承原实验的指标、参数与配置。最重要的注意事项始终提供一个与原实验不同的、唯一的feature_flag_key。如果使用相同 key两个实验会共享同一个 flag——对其中一个的修改发布变体、暂停会同时影响另一个。可选自定义name默认Original Name (Copy)。Copy to project复制到另一项目experiment-copy-to-project将实验复制为同一组织内另一个项目中的新草稿。副本落在另一项目时用它留在同项目内用experiment-duplicate。前置条件源实验不得使用 legacy 指标Copying is not supported for experiments using legacy metrics.目标项目必须属于同一组织且你对其有写权限不能跨组织或跨区域复制。复制内容name、description、type、parameters、filters、primary/secondary 指标重新生成新 uuid、stats 与 scheduling 配置、exposure criteria。不复制内容saved-metric 引用project-scoped跨项目被丢弃、holdout、exposure cohort、日期、结果、conclusion。副本永远是全新 draft。请求体target_team_id必填feature_flag_key可选。feature flag 在目标项目中的三种命运解析出的 key省略feature_flag_key时默认取源实验的 flag key会在目标项目中查找查找结果而非你是否传了 key决定行为省略feature_flag_key默认取源实验的 flag key。该 key 通常在目标项目中不存在于是在目标项目创建新 flag。默认值仍可能冲突——见下一点——保险起见请显式传 key。解析出的 key 在目标项目已存在对应 flag副本共享该现有 flag 而不新建。两个实验指向同一 flag任一方的生命周期操作ship、pause都会影响双方。该 flag 必须是 2–20 个变体的多元 flag否则返回 400。解析出的 key 在目标项目不存在以该 key新建一个独立 flag。要保证独立性就传入一个目标项目中尚不存在的feature_flag_key。调用前必须按名称向用户确认源实验与目标项目——这会把数据写入用户当前并未查看的项目。返回的实验及其 id属于目标项目。决策框架什么场景该用哪个动作场景动作工具草稿就绪、flag 已实现、指标已配置启动experiment-launch结果明确、有显著胜者发布获胜变体experiment-ship-variant运行足够久仍无显著差异以 inconclusive 结束experiment-end出问题需要临时停止曝光暂停experiment-pause暂停后恢复恢复experiment-resume停止入组新用户、继续计量已入组用户冻结曝光experiment-freeze-exposure冻结后重新开放入组解冻曝光experiment-unfreeze-exposure实验已结束、准备清理列表归档experiment-archive用相同配置重新开始重置为草稿experiment-reset想要一个全新开始的相似实验复制experiment-duplicate想要同一实验出现在另一项目复制到其他项目experiment-copy-to-project解析实验 ID前置步骤所有生命周期动作都需要实验 ID。如果没有 ID先加载 finding-experiments 技能把用户对实验的引用名称、描述、latest等解析为具体的 ID 再继续。这一步对应 MCP 工具定义中反复出现的前置规则见 tools.yaml 中各工具的 description。错误处理速查表错误信息含义Experiment has already been launched.不能 launch 非 draft 实验Experiment has not been launched yet.不能 end/pause/ship 一个 draftExperiment has already ended.不能 end/pause 已 stopped 实验Experiment is already paused.应改用 resumeExperiment is not paused.它已经是激活状态Experiment is already in draft state.没有可重置的内容Experiment is already archived.已完成Experiment exposure is already frozen.没有可冻结的内容Experiment exposure is not frozen.没有可解冻的内容Cannot freeze a paused experiment. Resume it first.先 resume 再 freezeGroup-aggregated experiments cannot have their exposure frozen.结构性限制——不要重试收到 400 时向用户解释情况而不是盲目重试。这一点尤其适用于 freeze exposure 的各类结构性拒绝——_FREEZE_EXPOSURE_BLOCKER_MESSAGES见 experiment_service.py 常量定义中draft/stopped/paused/already_frozen/no_flag/flag_deleted/group_aggregation/holdout/super_groups/no_groups十个阻塞原因每个都有独立的用户可读文案且由模型侧单一的freeze_exposure_blocker属性见 实验模型统一产出序列化器门禁与服务端校验共用同一来源、不会漂移。相关技能与延伸阅读creating-experiments— 从零创建下一个实验创建实验技能diagnosing-experiment-results— 在 ship/end 决策前对结果做 sanity-check诊断实验结果技能其中包含 定性反馈参考、中途变更参考 等子文档configuring-experiment-rollout— 分流与放量调整属于配置编辑而非生命周期操作配置放量技能另见 launch 后变更分发的参考若需深入源码完整的业务逻辑单一来源是 experiment_service.pylaunch/archive/pause/resume/freeze/unfreeze/end/reset/ship/duplicate/copy 共 4968 行状态派生与冻结阻塞判定在 实验模型MCP 工具的 scope 要求与参数覆盖定义在 tools.yaml相关测试可参考 test_experiment_service.py 与 test_freeze_exposure_clickhouse.py。【免费下载链接】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),仅供参考