gstack Domain Skills 深度解析:Agent 自写的站点笔记如何持久化、晋升与防注入
gstack Domain Skills 深度解析Agent 自写的站点笔记如何持久化、晋升与防注入【免费下载链接】gstackUse Garry Tans exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstackDomain Skills 是 gstack 中浏览器 Agent 的“跨会话站点记忆”机制Agent 在某个网站上总结出非显而易见的操作技巧后会把笔记保存为该站点的 domain skill后续同一站点的新会话会自动把笔记注入 prompt 上下文。本文基于 docs/domain-skills.md 与 browse/src/domain-skills.ts、browse/src/domain-skill-commands.ts 等源码实现完整讲解其 CLI 用法、三态状态机、JSONL 存储纪律、五层安全模型与错误排查读完你可以理解这套“Agent 给 Agent 写笔记”机制的全貌与防 prompt injection 设计。一、Domain Skills 是什么Agent 给未来的自己留的便签gstack 的浏览器 Agentbrowse 模块在执行网页任务时会遇到大量站点特有的“隐性知识”例如某个按钮藏在 iframe 里、某页面需要先展开折叠面板。Domain Skills 就是让 Agent 把这些经验写下来、跨会话复用的机制官方描述为Per-site notes the agent writes for itself. Compounds across sessions: once an agent figures out something non-obvious about a website, it saves a skill, and future sessions on that host get the note injected into their prompt context.这个模式借鉴自 browser-use/browser-harness 项目的 per-site-notes 思路但 gstack 只拷贝了“按站点记笔记”的模式不拷贝自修改运行时self-modifying-runtime的模式。文档明确强调skill 是加载进 prompt 的 markdown 纯文本不是可执行代码——这决定了它的安全边界skill 的内容本质上是 agent 撰写的、会进入未来 prompt 上下文的不可信内容因此整套机制的重头戏在于防注入与晋升管控详见第五节。二、CLI 实战$B domain-skill子命令全集gstack 文档中$B指 browse 守护进程的 CLI 入口domain-skill 是其下的元命令在 browse/src/commands.ts 中注册于Meta分类。完整的子命令面如下源码实现在 browse/src/domain-skill-commands.ts# Agent 任务成功后写下学到的站点知识。 # 注意host 自动取自当前激活标签页不需要 agent 传参数 echo # LinkedIn Apply Button The Apply button on /jobs/view pages is inside an iframe with a class matching jobs-apply-button-iframe. Use \$B frame --url apply first, then snapshot. | $B domain-skill save # 查看已保存的 skill $B domain-skill list # 读取某个 host 的 skill 正文 $B domain-skill show linkedin.com # 用 $EDITOR 交互式编辑 $B domain-skill edit linkedin.com # 把当前项目的 skill 提升为全局跨项目生效 $B domain-skill promote-to-global linkedin.com # 回滚最近一次编辑 $B domain-skill rollback linkedin.com # 删除tombstone可通过 rollback 恢复 $B domain-skill rm linkedin.com各子命令的关键行为均可在 browse/src/domain-skill-commands.ts 中对应到具体 handler子命令行为源码要点save正文从stdin 或--from-file path读取绝不接受内联 argv多行 markdown 会被 shell 引号搞坏host 从激活标签页顶层 origin 派生readBodyFromArgsderiveHostFromActiveTab空正文直接报Save failed: empty bodylist分组展示当前项目可见的 project 与 global skill含状态、版本、字节数、使用次数与标记数listSkills只返回非 tombstone 行show host打印 skill 正文头部附 host、来源 scope、版本、使用次数与 flag 数只返回 active项目或 global 状态的 skillquarantined 的不显示edit host把当前正文写入临时文件拉起$EDITOR缺省vi编辑保存后以source: human重新落盘内容与原文相同则输出No changes不产生新版本promote-to-global host仅允许把active状态的项目级 skill 提升为 globalskill 在全局文件中从 v1 重新计数非 active 状态直接抛错rollback host [--global]恢复上一个版本版本号单调递增回滚内容本身成为新版本少于 2 个版本时拒绝rm host [--global]追加 tombstone 行不物理删除输出提示可用rollback恢复一个值得注意的设计save命令输出的Saved结果会明确告知用户“skill 目前是 quarantined未连续使用 3 次且不被分类器标记前不会在 prompt 中生效”即保存成功不等于立即生效。主机名归一化规则save不接受 agent 提供的 host 参数而是调用 browse/src/domain-skills.ts 中的deriveHostFromActiveTab从当前页面 URL 派生。派生前先经normalizeHost归一化规则为转小写、去首尾空白剥掉协议前缀https?://、路径、query、hash剥掉端口号剥掉www.前缀但保留完整子域名按子域精确匹配Punycode 域名按编码后原样存储。三、状态机quarantined → active → global这是整个机制的核心防止被污染的页面笔记直接“洗”进长期记忆。文档给出的状态机如下┌──────────────┐ 3 successful uses ┌────────┐ promote-to-global ┌────────┐ │ quarantined │ ─────────────────────▶ │ active │ ──────────────────▶ │ global │ │ (per-project)│ (no classifier flags) │(project)│ (manual command) │ │ └──────────────┘ └────────┘ └────────┘ ▲ │ │ classifier flag during use │ rollback (version log) └───────────────────────────────────────┘源码中对应SkillState quarantined | active | global晋升阈值常量PROMOTE_THRESHOLD 3见 browse/src/domain-skills.ts。三个关键语义新保存的 skill 一律是quarantined且绝不自动注入 prompt。readSkill的实现明确只返回state active项目层或state global全局层的行quarantined 行对读取路径完全不可见。自动晋升需要同时满足四个条件由recordSkillUse维护use_count累计达到 3flag_count 0使用期间从未被分类器标记当前状态仍是quarantinedclassifier_score 0正文确实被 L4 ML 扫过。关于最后一条门槛源码注释交代了一个重要现状save 路径写入的classifier_score固定为 0含义是“从未被 ML 扫描过”而加载期的 L4 扫描路径已随旧 chat 路径一起移除因此生产路径写入的 skill 目前无法通过自动晋升——这个classifier_score 0门槛正是补偿性防线一旦 L4 重新接线并写入非零分数自动晋升即自动恢复。换言之当前版本中 quarantined skill 想进入 prompt 需要等待 L4 重新接入或被手动处理这与文档描述的“设计意图”存在时差从源码注释看属于有意保留的开关。跨项目晋升global必须显式人工执行$B domain-skill promote-to-global host永不自动。文档给出的理由源自 Codex T4 outside-voice 评审是无差别的跨项目记忆复利会在不相关项目之间泄漏上下文。读取优先级上readSkill先查项目层、再 fallback 全局层项目级 active skill 会遮蔽shadow同 host 的 global skill。这让你可以在某个项目里为同一网站写一份定制版笔记而全局笔记继续服务其他项目。四、存储层双 JSONL 文件与追加写纪律Skill 存储在两个位置项目级~/.gstack/projects/slug/learnings.jsonl—— 与/learnskill 共用同一个 JSONL 文件domain skill 是其中type:domain的行同一文件里还有学习记录等其他行解析器按type domain过滤全局级~/.gstack/global-domain-skills.jsonl—— 只存放state:global的行。两个文件都是追加写append-onlyJSONL。每行是一个完整的DomainSkillRow字段结构在 browse/src/domain-skills.ts 中定义export interface DomainSkillRow { type: domain; host: string; // 归一化后的 host scope: project | global; state: quarantined | active | global; body: string; // markdown 正文 version: number; // 版本计数器单调递增 classifier_score: number; // 0 表示“从未被 L4 扫描” source: agent | human; sha256: string; // 正文摘要 use_count: number; flag_count: number; created_ts: string; updated_ts: string; tombstone?: boolean; // 删除标记 }docs/domain-skills.md 描述的三条存储纪律在源码中逐条可验证原子追加appendRow用O_WRONLY | O_CREAT | O_APPEND打开文件后单次writeSync加fsyncSync。源码注释说明依据POSIX 对小于PIPE_BUF通常 4KB的 O_APPEND 写保证原子性而单行 JSON 远小于该上限因此即使并发写入也不会交错。容忍式解析readRows逐行JSON.parse任何一行尤其是崩溃时刻写了一半的尾行解析失败即静默跳过保证“写一半崩溃”不会毒化后续所有读取TODOS.md 中亦记录了该 JSONL 存储的后续演进方向多写者场景下考虑 SQLite。墓碑 压缩rm不删行而是追加一行tombstone: true的新版本行“latest-wins”解析时墓碑胜出使被删的 skill 保持已删状态空闲 compactor 周期性重写文件做物理清理。版本解析逻辑resolveLatest按(scope, host)分组、取版本号最大的行版本号在每次 save/use/rollback/tombstone 时单调 1因此“回滚”实际是把上一个版本的内容以新版本号重新落盘——历史完整保留审计链不断。五、安全模型Agent 写的内容为何能进未来 prompt文档对此的定性非常直接“Skills are agent-authored content loaded into future prompt context. That makes them a classic agent-to-agent prompt-injection vector.”这是典型的 agent 间 prompt 注入向量。防御分五层原文表格如下Layer内容位置L1-L3数据水印、隐藏元素剥离、ARIA 正则、URL 黑名单content-security.ts编译进二进制L4TestSavantAI ONNX 分类器security-classifier.tssidebar-agent非编译L4bClaude Haiku transcript 分类器security-classifier.tssidebar-agentL5金丝雀令牌泄漏检测security.tsL1-L3 在 save 时运行daemon 侧L4 ML 分类器在 load 时运行即每个把 skill 载入 prompt 的会话都会重新校验内容——这样才能抓住只在分类器模型更新后才显现的问题。下面结合源码展开各层。L1-L3save 时执行的内容过滤器save命令在落盘前调用 browse/src/content-security.ts 的runContentFilters(body, page.url(), domain-skill-save)被拦截则抛出Save blocked: ...并记录domain_skill_save_blocked遥测。该模块的四个防御层L1 数据水印DatamarkingdatamarkContent在每第 3 个句尾句号后插入由零宽空格包裹的会话级随机标记randomBytes(3)取 base64 前 4 位用于检测内容外泄——若这些不可见水印出现在别处说明页面文本被原样搬运出去了。L2 隐藏元素剥离markHiddenElements在页面内检测 opacity 0.1、字号 1px、屏幕外定位、前后景色相同、clip 隐藏、visibility:hidden等“人看不见、机器看得见”的元素打上data-gstack-hidden标记后从提取文本中剔除同时用一组注入特征正则如ignore (previous|above|all) instructions、you are (now|a)、system:、do not follow/obey、new instructions:等扫描 ARIA label命中即标记。L3 URL 黑名单内置一份 10 个数据外泄汇点域名的清单requestbin.com、pipedream.com、webhook.site、hookbin.com、requestcatcher.com、burpcollaborator.net、interact.sh、canarytokens.com、ngrok.io、ngrok-free.app对页面 URL 和正文中出现的所有http(s)://URL 做大小写不敏感的子串匹配源码注释指出这是为了防https://WEBHOOK.SITE/x这类大写绕过。过滤器受环境变量BROWSE_CONTENT_FILTER控制off/warn默认/block。此外还有一个信封逃逸防护wrapUntrustedPageContent会把内容中伪造的═══ BEGIN/END UNTRUSTED WEB CONTENT ═══哨兵串中间插入零宽空格使其失效防止恶意页面提前闭合“不可信内容”边界、伪造出 LLM 眼中的“可信段”。L4TestSavantAI ONNX 注入分类器docs/domain-skills.md 提到的 L4 在 browse/src/security-classifier.ts 中实现几个可验证的实现细节模型是 TestSavantAI 的 BERT-small ONNX约 112MB首次使用时从 HuggingFace 下载到~/.gstack/models/testsavant-small/按huggingface/transformers期望的onnx/model.onnx布局暂存运行位置有讲究该模块只能被 security sidecar 子进程导入不能进入编译后的 browse 二进制——因为onnxruntime-node这个原生模块无法从 Bun 编译二进制的临时解包目录dlopenfail-open 降级模型加载失败或推理异常时返回degraded状态、置信度 0上层判定组合器回落到“仅 L1-L3”的结果只有这一层额外防线消失整体服务不中断提供GSTACK_SECURITY_OFF1环境开关可强制关闭 ML 分类器输入先经htmlToPlainText剥掉 HTML 标签分类器是按纯文本训练的标签噪声会稀释注入信号再做 4000 字符上限与 512 token 截断对应 BERT-small 的max_position_embeddings。而 skill 存储层对分数的硬门槛写在writeSkill中classifier_score 0.85直接抛错Save blocked: classifier flagged content as potential injection这正是文档错误表中“L4 score ≥ 0.85 at save”一行的来源见 browse/src/domain-skills.ts 第 252-259 行的实现。防“confused deputy”host 永不接受 agent 参数文档特别强调的一条设计save 从激活标签页的顶层 origin推导 host而不是接受 agent 传参。这关闭了 Codex 评审标记的一个 confused-deputy 漏洞——否则恶意页面的重定向链可以诱骗 agent 把投毒内容写到另一个域名名下。deriveHostFromActiveTab还会拒绝about:blank与chrome://页面抛出文档错误表中对应的Cannot save domain-skill: no top-level URL on active tab。六、错误参考表docs/domain-skills.md 的 Error reference 完整继承如下每条错误信息在 browse/src/domain-skill-commands.ts 与 browse/src/domain-skills.ts 中均有逐字对应的 throw 语句格式统一为“现象 Cause Action”三段式ErrorCauseActionSave blocked: classifier flagged content as potential injectionsave 时 L4 分数 ≥ 0.85改写 skill 去掉指令式语句后重试Save blocked: L1-L3 messageURL 黑名单命中或 ARIA 注入检查 skill 正文中的可疑模式Save failed: empty bodystdin 与--from-file都没内容用管道把 markdown 喂给$B domain-skill save或传--from-file pathCannot save domain-skill: no top-level URL on active tab标签页是about:blank或chrome://...先$B goto target-site再 saveCannot promote: skill is in state quarantined尚未自动晋升在本项目继续使用该站点直到 3 次成功使用且无分类器标记Cannot rollback: host has fewer than 2 versions只有 1 个版本改用$B domain-skill rm删除七、遥测只记 host不记内容当遥测开启时默认community模式除非显式关闭domain-skill 相关事件写入~/.gstack/analytics/browse-telemetry.jsonl事件清单同时记录在 browse/src/telemetry.ts 头注释中domain_skill_saved {host, scope, state, bytes}—— save 成功后记录handleSave中logTelemetry调用可验证domain_skill_save_blocked {host, reason}—— L1-L3 拦截时记录domain_skill_fired {host, source, version}—— skill 被注入 prompt 时记录domain_skill_state_changed {host, from_state, to_state}—— 文档标注为 planned规划中。隐私边界只上报主机名不含正文、不含 agent 文本。完全关闭可用gstack-config set telemetry off或环境变量GSTACK_TELEMETRY_OFF1。八、测试与延伸阅读该机制的测试覆盖集中在browse/test/目录可作为行为佐证browse/test/domain-skills-storage.test.ts —— 存储层单测追加、解析、晋升门槛、回滚等browse/test/domain-skills-e2e.test.ts —— CLI 端到端测试browse/test/security-classifier.test.ts —— L4 分类器加载与降级行为。相关源码入口存储层 browse/src/domain-skills.tsreadSkill/writeSkill/recordSkillUse/promoteToGlobal/rollbackSkill/deleteSkill、CLI 层 browse/src/domain-skill-commands.ts、内容安全 browse/src/content-security.ts、ML 分类器 browse/src/security-classifier.ts。命令注册与用法说明见 browse/src/commands.ts 中domain-skill条目项目 slug 的解析决定 per-project 存储路径见 browse/src/project-slug.ts。小结Domain Skills 用一个极简的存储原语追加写 JSONL 墓碑 版本单调递增实现了 Agent 的跨会话站点记忆而真正的工程重心放在信任链上quarantined 隔离态、3 次无标记才自动激活、跨项目晋升必须人工、host 强制来自浏览器受控状态、save/load 双时点过滤与 L1-L5 分层防御。它展示了一个典型的思路——当“Agent 生成的内容会进入 Agent 的未来 prompt”时这套内容必须按外部不可信输入来设防。【免费下载链接】gstackUse Garry Tans exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考