免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Comp AI CRM 遥测体系全解析:install_daily、设置漏斗与隐私边界设计

Comp AI CRM 遥测体系全解析:install_daily、设置漏斗与隐私边界设计 后端前端CRM人工智能AI Agent【免费下载链接】crmComp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.项目地址https://gitcode.com/gh_mirrors/crm48/crm点击查看免费下载本指南以 Comp AI CRM 的遥测文档 docs/telemetry.md 为骨架完整拆解这个开源 CRM 在自身安装中上报的全部数据每天一次的install_daily聚合事件、八个一次性设置漏斗事件、四类错误事件以及围绕绝不泄露客户数据设计的一系列机制——白名单属性、无 IP、行锁防重、确定性 UUID、落地页域名门控。读完你将掌握该项目的遥测数据流、每个属性的确切含义、如何关闭/审计它以及如何为自己的安装新增一个遥测属性。遥测是什么以及为什么这样设计Comp AI CRM 是一个 Agent-first 的开源 CRM见 README.md它内置了一套匿名使用遥测安装会向 PostHog 上报少量聚合数据用于作者了解这个软件被怎样部署和使用。关键前提是这是一个存储联系人、邮箱、公司、交易金额的 CRM遥测的设计目标从第一行起就是在收集任何数据之前先划定永不触碰的边界。docs/telemetry.md是这份上报内容的完整清单——文档明确说明没有设置页面可以查看或控制它这就是该读的东西。设计决策本身记录在adrs/的决策文档中本文只聚焦这份清单及其在源码中的落地。三个最核心的事实开启状态默认开启。关闭方式是设置环境变量后重启见下一节。目标不可配置PostHog 的项目 key 与上报 host 是 packages/telemetry/src/project.ts 中的三个常量POSTHOG_KEY、POSTHOG_HOST、POSTHOG_UI_HOST。phc_前缀的 key 是只写write-only的——只发送事件、不读回任何数据——因此作者刻意不把它做成配置项一个可配置变量只会让一个公开标识符看起来像机密。要 fork 到别处就编辑该文件。测试环境绝不发送NODE_ENVtest时遥测直接禁用见 packages/telemetry/src/disabled.ts 第 8 行apps/api与apps/agent的test脚本还额外设置CRM_TELEMETRY_DISABLED1——因为规范测试允许重新赋值process.env.NODE_ENV曾经就有这样一个测试让真实事件漏发出去。关闭遥测环境变量与重启在原文档的基础上这里给出完整的关闭方式与底层判定逻辑# 在仓库根目录 .env 中任选其一或两者都设 CRM_TELEMETRY_DISABLED1 DO_NOT_TRACK1然后重启 API 与 Agent 进程即可。从 packages/telemetry/src/disabled.ts 的源码可以确认判定规则两个变量名被硬编码在DISABLE_VARIABLES [CRM_TELEMETRY_DISABLED, DO_NOT_TRACK]中值为1、true、yes、on大小写不敏感、先 trim之一即视为关闭其他值不算NODE_ENV test无条件返回关闭优先级最高该判定被 packages/telemetry/src/client.ts 的send()与 apps/api/src/telemetry/rollup.service.ts 的run()在发消息前双重检查。关闭后的行为在 apps/api/src/telemetry/telemetry.service.ts 中可见TelemetryService.onModuleInit()若检测到禁用仅打印一行日志便返回不会启动任何定时器。而 rollup 在禁用时先于一切返回——不记录里程碑、不清空计数器、不认领 rollup 日。这意味着关闭期间没有任何数据被静默消费重新打开后漏斗依然完整见下文关闭不留痕迹。上报形态六个硬性约束docs/telemetry.md的 The shape of it 一节定义了遥测的总体形态逐条对应源码实现1. 纯服务端、无自动捕获autocapture。posthog-node只存在于apps/api与apps/agent两个服务。没有 autocapture、没有 session replay、没有任何能触达记录数据的客户端代码。原因直白autocapture 会把联系人姓名、邮箱、公司域名、交易金额从你的数据库里捞出来——最简单的不持有方式就是不采集。唯一的例外是营销站点trycrm.ai它是网页而非安装详见落地页遥测一节。2. 单一标识符一个由创建数据库的 migration 生成的 UUIDinstall表不绑定任何其他东西。identify()从不被调用没有任何 person 属性任何用户、邮箱或ALLOWED_SIGN_IN值都不会发送。在 packages/telemetry/src/client.ts 的payload()中可以看到distinctId就是install.uuid且每个事件都携带$process_person_profile: false——安装事件永远不会成为 person、也不会加入任何 person。3. 频率极低每天一个install_daily事件加上八个一次性设置事件再加上每个失败一个错误事件。4. 无 IP 地址。每个事件都设置$ip: null并禁用 geoip同时接收项目开启了anonymize_ips地址在 ingestion 时被丢弃。这两者缺一不可PostHog 无论 SDK 发送什么都会从传入连接中盖上$ip并且把 null 读作未提供而非不要保留。文档强调如果你把这个指向别的项目必须在那边也开启anonymize_ips。client.ts 中new PostHog(...)的配置可见disableGeoip: true与enableExceptionAutocapture: false。5. 从不阻塞、从不抛异常。每次发送都是 fire-and-forget包在吞掉错误的 handler 里遥测失败只以 debug 级别记日志其余地方不可见。client.ts的send()全链路 try/catchcapture()甚至不返回 Promise。6. 白名单在代码里。packages/telemetry/src/allowlist.ts 的ALLOWED_PROPERTIES数组列出每一个允许发送的属性名其余一律在事件构建前被丢弃——靠的是机制而非审查约定。permitted()的实现很干脆遍历属性对象凡不在ALLOWEDSet 中或值为undefined的一律跳过。每日事件 install_dailyinstall_daily是遥测的主体每个安装每天一次由 API 自身发出内容全部来自分组聚合查询counts and distributions永远不会是一行数据里的某个值。运行机制进程内定时无需 crondocs/telemetry.md说明了这个事件如何做到无需外部调度TelephoneService即 telemetry.service.ts在启动时onModuleInit执行一次rollup.run()之后每60 * 60 * 1000ms一小时再跑一次认领机制见下让除第一次外的所有执行都变成空操作——同一 UTC 日只发一次。这覆盖了两种部署形态serverless在冷启动时聚合常驻容器靠定时器聚合。POST /internal/telemetry/rollup路由依然存在供想要自己驱动它的平台 cron 使用——它仍然是匿名路由因此必须携带CRON_SECRET见 apps/api/src/telemetry/telemetry.controller.ts用常量时间的timingSafeEquals校验Authorization: Bearer CRON_SECRET未设置 secret 直接返回 503。若不设CRON_SECRET安装完全不受影响报告内容一模一样。防重发三重保险第一层行锁认领。packages/telemetry/src/install.ts 的claimRollup()在一个事务里对install行执行SELECT ... FOR UPDATE再比较sameUtcDay(previous, at)若今天已发过则拒绝认领。两个 cron 同时触发也只会产生一个事件——而且第二个不会发现计数器已被抽干。第二层确定性 UUID。事件携带stableUuid(install.uuid, install_daily, day)——一个 SHA-256 摘要改造成 v8 UUID。PostHog 对已见过的重复事件只摄入一次。这里的技术细节很有讲究RFC 4122 的 v5 是 SHA-1CodeQL 会拒绝安装身份哈希 弱算法出现在同一表达式里项目不需要 v5 的语义所以用了 RFC 9562 为自定义派生预留的 v8 槽位。install.ts 的stableUuid()实现可见SHA-256 取前 16 字节按位设置版本0x80与变体0x3f | 0x80标志后格式化为 UUID 字符串。锁阻止两个进程竞争确定性 id 阻止同一个进程发送两次release 路径可能引发。计数安装量在重复下本来就准PostHog 的 unique 计算按安装 × 天但tool_calls_total这类求和属性会被重复计算——现在不会了。第三层失败即归还。posthog-node在发送失败时不会 reject——它的sendImmediate会捕获错误并转而在 client 上抛出事件——所以client.ts先capture再await flush()flush会真正抛错并额外检查 client 的错误计数器飞行中的任何 capture 都可能拨动它。任一信号都视为失败。这倾向于重发一个其实已送达的 rollup确定性 id 让重发免费而不是消耗掉一个事件其实从未到达的日子那不可恢复。失败时rollup.service.ts调用giveBack()把lastRollupAt回退到认领前的值并把telemetryCounter计数器写回——明天的运行不会被一次没发出去的消息压制。安装信息install_daily携带的通用属性均可在ALLOWED_PROPERTIES中验证Property含义crm_version根package.json的version每次启动重新读取packages/telemetry/src/version.ts 的crmVersion()定位 workspace 根后解析 JSON安装行里的版本由 install.ts 的syncVersion()在启动时更新git_commit_sha有VERCEL_GIT_COMMIT_SHA时为其值否则省略commitSha()还接受GIT_COMMIT_SHA、GITHUB_SHA并校验 7–40 位十六进制days_since_install自首个 migration 运行以来的整天数is_vercelVERCEL是否被设置node_version仅主版本号如22postgres_version仅主版本号如17members_bucket团队规模分档agent_model_idSettings → General 中选择的模型如zai/glm-5.2-fastagent_model_context_window该模型的上下文窗口token 数seed_only所有联系人是否都来自bun run db:seed能力标记布尔值永不带值以下属性只表示对应 key 是否已设置绝不携带 key 本身、值或末四位cap_perplexity、cap_context_dev、cap_blob、cap_github、cap_redis、cap_agent_bridge、cap_cron_secret、cap_ai_gateway、cap_google_oauth、cap_sso_provider、cap_tracking、is_marketing。实现上的差异点cap_context_dev看的是AppSetting行里是否存有 context dev keycap_sso_provider看ssoProvider行是否存在cap_tracking看是否铸造过 tracking site id。文档特别注明cap_context_dev一个布尔覆盖了 Agent 的 Context 两项能力按域名查公司品牌数据、通过 LinkedIn URL 识别人因为它们共用同一个 key。Agent 运行数据Property含义tool_calls最近 24h 的工具调用按工具名分组tool_calls_total上述求和tool_errors其中返回错误的调用按工具名分组sandbox_used是否有任何沙箱工具bash、grep、glob、文件读写运行过sessions_started/sessions_completed/sessions_failed窗口内的不同会话数tools_per_session_mean工具调用数 ÷ 发起过调用的会话数tasks_claimed/tasks_completed/tasks_retiredAgentTask计数按kind分组task_attempts_mean/task_attempts_max每种 kind 的尝试次数budget_exhausted因研究预算耗尽而停止的会话数。每个会话只计一次不是每次被拦截的工具调用都计recheck_scheduled窗口内排队的recheck任务数recheck_interval_days它们的间隔按天分档agent_conversationsAgentConversation行数工具名会与apps/agent/agent/tools/下作者编写的工具以及 eve 自带的内建工具比对AGENT_TOOLS与EVE_TOOLS两份常量清单见 allowlist.ts其他一律归入other。任务 kind 与TASK_KINDS来自crm/db/agent-tasks比对。值得注意工具计数读的是 audit hook 已经写入的agentEvent行只取事件type和工具名——AgentEvent.data本身永不发送。测试 packages/telemetry/test/allowlist.spec.ts 甚至断言AGENT_TOOLS与apps/agent/agent/tools/目录下的.ts文件名逐一对应防止清单与实现脱节。证据台账Evidence LedgerProperty含义facts_by_statusContactFact按APPLIED/PROPOSED/DISMISSED/SUPERSEDED计数facts_by_band按VERIFIED/PROBABLE/POSSIBLE计数facts_by_method按工具记录的method标签计数facts_by_evidence_kind按证据种类计数来自 apps/agent/agent/lib/evidence.ts 的WEIGHTS映射fact_dismissal_rate已决策事实中被人否决的比例fact_decision_median_hours从observedAt到decidedAt的中位小时数facts_superseded_within_7_daysAgent 在 7 天内改判的事实数证据种类必须匹配lib/evidence.ts中的十一种EVIDENCE_KINDS常量测试断言其与WEIGHTS的键逐一相等method是工具写的标签因此只有当它匹配严格的小写 dotted-slug 形态且短于 40 字符时才发送——名字、地址、句子一律过不了正则/^[a-z][a-z0-9]*(?:[.-][a-z0-9])*$/计为other。ContactFact的value、field、evidence、sourceUrl永不发送。CRM 数据Property含义contacts_bucket、companies_bucket、deals_bucket、activities_bucket规模分档contacts_by_source、companies_by_source按MANUAL/IMPORT/EMAIL/CALENDAR计数deals_by_stage按DealStage计数。阶段永远不是金额activities_by_type按ActivityType计数mailbox_sync_configured是否有任何MailboxSync行mailbox_sync_status按GoogleSyncStatus计数threads_ingested、messages_ingested窗口内到达的数量。仅计数enrichment_by_statusCompany按EnrichmentStatus计数suppressed_domains、suppressed_contacts计数。从不发送域名或地址本身workspace_profile_written是否存在WorkspaceProfile行网站跟踪Website TrackingProperty含义tracking_domains白名单上有多少域名分档。永不含主机名tracking_page_views窗口内记录的页面浏览数。计数不含路径tracking_forms窗口内存储的表单提交数。计数不含字段tracking_contacts_created窗口内由跟踪功能创建的联系人数——即Contact.source TRACKING不包括挂到已存在联系人名下的提交tracking_capped被每小时联系人上限拒绝的提交数。与CONTACT_CAP_REASONcrm/db/tracking精确匹配——文案是两个文件共享的常量而不是某个文件可能改写的措辞tracking_paused采集是否暂停设置漏斗八个一次性事件以下八个事件每个安装一生各发送一次且各自携带事件发生时刻的时间戳而非被发现时刻——所以即使发送它们的 nightly job 是后来才跑的漏斗读起来依然真实migrations_applied → first_sign_in → google_oauth_configured → first_mailbox_sync → first_non_seed_contact → first_agent_task_claimed → first_agent_task_completed → first_fact_applied除通用属性版本、安装天数、是否 Vercel外不携带任何自有属性。几个值得展开的实现细节google_oauth_configured是唯一没有行可读时间的步骤两个环境变量GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET不会留下时间戳。它的时间戳取第一个 Googleaccount行配置必然先于账号存在否则取 sweep 第一次看到变量被设置的时刻。该 sweep 在每次 API 启动时都跑而不仅限于 cron——所以一个带着配置抵达的安装会在首次启动时就被盖章。first_fact_applied取人类接受的任一事实的最早decidedAt或 Agent 自己应用的最早observedAt取先者。被应用后又 supersede 的事实依然算数——后来被替换不改变第一次应用发生过。先认领、后发送发送失败即交还。认领就是插入本身telemetryMilestone.step是主键两个进程同时 sweep 时只有插入成功createMany返回count 1的那个会发送发送失败则删除该行forgetMilestone下次 sweep 重试而不是永久丢失该步骤。文档还记录了一个真实的演进教训这个顺序与最初版本相反——最初是先发送后记录理由是重复只是舍入误差而丢失步骤无法恢复。但那个顺序让重复成为常态而非罕见启动 sweep 和 rollup 都会调用sweep()曾有安装把first_fact_applied发了六次。改为先认领代价是认领后、发送前崩溃会丢失该步骤——但每个事件还带stableUuid(install.uuid, step)的确定性 UUID真正被发送两次也会被 PostHog 只摄入一次。遥测关闭时什么都不记录。错误事件只报类不报内容错误遥测的规则是错误类别 来源。绝不发送消息、堆栈、payload——我们的错误里会带联系人字段。事件属性agent_errorerror_class、tool、task_kind、error_sourcetool/turn/sessionsync_errorerror_class、sync_sourcegmail/calendar/outlook、error_sourcegoogle_sync/microsoft_sync/mailbox_syncapi_errorerror_class、route、status_codemodel_errorerror_class、model_id从 packages/telemetry/src/events.ts 与 allowlist.ts 可以看到这些事件的构建函数其约束值得细读sync_error的error_source由sync_source推导而非直接传入——因为同一条 mailbox 管道会分派给两个 provider直接传值会让 Outlook 失败被报告成 Google 失败。非三种 source 的发送为other其error_source固定为mailbox_syncpermittedSyncErrorSource。route是路由模式——/internal/sync/google、/trpc/contacts.list——绝不带参数的 URL。它必须匹配严格路径形态/^\/[A-Za-z0-9/_:.*-]*$/最长 120 字符否则为other。error_class取Error实例的name/constructor.name、字符串本身或对象上的code必须匹配/[A-Za-z][A-Za-z0-9_.-]*/且短于 64 字符否则为other。测试 allowlist.spec.ts 验证了new TypeError(adaexample.test is not a fn)只会送出TypeError。apps/agent/agent/hooks/telemetry.ts是 tool、turn、session 三类 Agent 失败的入口。沙箱内部apps/agent/agent/sandbox完全没有插桩——它按设计运行deny-all出站策略里面的 capture 会挂起然后失败。落地页遥测trycrm.ai 与域名门控trycrm.ai是仓库里唯一运行posthog-js的地方陌生人阅读营销页面的 web 分析和 session replay。它是网站而非安装——页面背后没有数据库记录也在它触达不到的地方。对 CRM 本身而言以上所有承诺依然成立。它无法在你的主机名上运行。apps/app/lib/analytics.ts 的analyticsAllowed()把window.location.hostname与两个字面量trycrm.ai、www.trycrm.ai比对posthog-js藏在这道检查之后的动态import()后面apps/app/components/landing/analytics.tsx。即使你设置IS_MARKETINGtrue并在自己的域名上托管同一页面SDK 也永远不会被下载——不是发送后被丢弃而是从未加载。门控用 hostname 而非IS_MARKETING是因为后者恰恰是自托管者最可能去设置的那个旗标。PostHog 那一侧也拒绝接收项目的recording_domains声明了同样的两个 origin任何带着该 key 出现的其他页面都会被拒收 replay 和 heatmap。它与安装共享同一个项目、key 和 host但两者绝不混合落地页发送$pageview、$pageleave、$autocapture、$web_vitals以及下面两个 CTA 事件是关于浏览器的安装发送install_daily和漏斗是关于数据库的。安装事件带$process_person_profile: false永远不会成为 person 或加入任何 person。两个 CTA 事件事件触发时机setup_prompt_copied设置提示真正写入剪贴板之后——被拒绝的剪贴板不算复制成功github_star_clicked点击了Star on GitHub按钮。只是前往 GitHub 的点击不是已加星——是否真的加星发生在看不到的页面上两者都带一个属性cta_location——hero或closing——因为两个按钮在页面上各出现两次访客到达的是哪一个是唯一值得知道的点。它们经由captureLanding发送与init一样在 hostname 门控与动态import()之后在其他主机名上 SDK 根本不存在点击也不会被发送。两个发送都是 fire-and-forget不延迟复制或导航。anonymize_ips保持开启是有代价的PostHog 在 geoip 之前就丢弃地址所以落地页没有国家/地区/城市数据。这正是让上面无 IP承诺对每个安装成立的那个设置——安装的匿名性优先于营销地图。想要两者兼得唯一的办法是拆成两个独立项目。ALLOWED_PROPERTIES管不到这些事件——它守卫的是packages/telemetry构建的内容而这里是浏览器 SDK 自己的。发送的是普通 web 分析URL、referrer、UTM 参数、浏览器、设备、屏幕尺寸、会话 ID。Replay 默认掩蔽所有输入而这个页面没有可输入的字段。永不发送清单以下内容在任何情况下都不会被发送任何Contact或User的姓名、邮箱、头衔或照片任何Company的名称、域名、行业或地点ContactFact的.value、.sourceUrl、.evidence、.fieldAgentTask.reason和AgentTask.outcome——两者都是 Agent 针对具名人物写的自由文本ContactBrief内容、WorkspaceProfile的.website、.narrative、.sectionsEmailThread与EmailMessage的主题或正文、CalendarEvent标题、CalendarAttendee行Deal名称和金额阶段分布可以金额不可以AgentEvent.data、AgentConversation内容、prompt、completion、推理轨迹ALLOWED_SIGN_IN、AppSetting.contextDevApiKey、任何 key、secret、token 或连接串SuppressedDomain与SuppressedContact的值——只发计数IP 地址设$ip: null并禁用 geoip最后一项要特别说明仅仅两个属性并不能达成无 IP因为 PostHog 会在 ingestion 时从连接里填上$ip——真正丢弃它的是接收项目上开启的anonymize_ips。代码位置速查位置职责packages/telemetry全仓库唯一posthog-node导入处。白名单、安装访问器、事件构建器install表UUID、版本、上次 rollup 时间。一行由 migration 创建telemetryMilestone表哪些漏斗步骤已触发telemetryCounter表唯一一个没有其他行记录的运行时数字——budget_exhausted——每次 rollup 抽干发送失败则写回packages/telemetry/src/project.tskey、ingest host、UI host。三个常量、零导入所以浏览器也能读它们apps/api/src/telemetry每日 rollup、漏斗 sweep、启动每小时定时器、可选 cron 路由apps/agent/agent/hooks/telemetry.tstool、turn、session 失败apps/app/lib/analytics.ts落地页允许上报的两个主机名apps/app/components/landing/analytics.tsx仓库里唯一的posthog-js导入位于门控与动态import()之后。init在挂载时captureLanding用于两个 CTAapps/agent/agent/sandbox内无任何插桩——它按设计deny-all出站里面的 capture 只会挂起后失败。新增一个遥测属性三步走文档给出了清晰的流程结合源码可以展开为完整的可操作步骤把属性名加入 packages/telemetry/src/allowlist.ts 的ALLOWED_PROPERTIES。在那之前它会被permitted()静默丢弃——这是硬闸门。在本清单docs/telemetry.md的表格中补一行说明它是什么。如果它是开放键集合——一个工具名、一个 method、一条 route——为它写一个permitted*函数如permittedTool、permittedMethod、permittedRoute、permittedSyncSource、permittedTaskKind、permittedEvidenceKind、permittedModelId、permittedErrorClass让键本身也受约束而不只是属性名受约束。配套的测试文件 packages/telemetry/test/allowlist.spec.ts 展示了这套机制如何被验证白名单外的属性如contact_email: adaexample.test被丢弃、工具清单与apps/agent/agent/tools/目录逐一对应、证据种类镜像lib/evidence.ts的WEIGHTS、错误类只取类名不取消息、bucket()永不报告精确规模1和9都归入1-91_000_000归入25000、dayBucket()把间隔分档7天归入2-7365归入180。新增属性时为它补充同样的测试是保持这份隐私承诺可审计的最佳实践。这份遥测体系的设计哲学可以概括为一句话聚合到的都是分布发送出去的都不是记录。分档bucket替代精确值、白名单替代约定、行锁与确定性 UUID 替代去重服务、hostname 门控替代环境变量信任——每一层都在回答同一个问题如果一个恶意或粗心的读取者拿到了这份事件流他能从中复原出任何一条客户记录吗答案被 docs/telemetry.md 的永不发送清单和源码里的每个permitted*函数钉死在了代码里。赞分享后端前端CRM人工智能AI Agent【免费下载链接】crmComp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.项目地址https://gitcode.com/gh_mirrors/crm48/crm点击查看免费下载相关推荐Agent Orchestrator 产品遥测Telemetry机制全解数据边界、隐私设计与关闭指南Agent Orchestrator 产品遥测Telemetry机制全解数据边界、隐私设计与关闭指南 产品遥测是 Agent OrchestratorAgRPC-Go 如何给 status 错误附加自定义 details 并在客户端读取gRPC Go 如何给 status 错误附加自定义 details 并在客户端读取 当你的 gRPC Go 服务需要向客户端返回错误时光靠一个错误码和描述后端前端CRM人工智能AI AgentiFixAi 安全策略与隐私设计全解析漏洞报告、密钥脱敏与遥测机制iFixAi 安全策略与隐私设计全解析漏洞报告、密钥脱敏与遥测机制 iFixAi 是一个面向 AI Agent 的独立审计框架可在 120 秒内回答Age上一篇3个真实场景告诉你为什么需要WeChatExporter告别微信数据丢失的终极方案下一篇虚幻引擎Pak文件的黑盒困境与UnrealPakViewer的透明化解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表