源码解析 · 社媒内容宿主 · v0.1.0 @ 765f5a6

不写主循环
把 OpenClaw 焊成工作台

Easel 不是又一个编程 Agent——浙大 REAL + 北大 OpenDCAI 把它做成国内社媒创作者的内容工作台:发现、策划、创作、发布、归因五层写给模型看,真正执行的是 112 个带脚本的 SKILL 和真实浏览器发布。循环外包给 OpenClaw;宿主负责画像不互踩、产物是项目、真发前扫密钥、SSE 断线不杀进程。全文按动机 → 约束 → 被否方案 → 选择 → 代价展开,标注 文件:行号。

112
可执行 Skill
768
行 Python 整合包
2509
行 Web 后端
733
GitHub stars

分析对象:Easel(ZJU-REAL/Easel),基于 commit 765f5a6(2026-09-10 10:52:51 +0800,main)。许可 Apache-2.0。版本号 pyproject.toml0.1.0easel/__init__.py 仍是 0.0.1——两处还没对齐。 代码规模(sparse 检出,跳过 assets/ 宣传视频):705 个文件。真正干活的代码是 easel/ 9 个 Python 文件 / 768 行 + web/app.py 2 509 行 + 前端 web/frontend/src 约 5 238 行 TS/TSX + 112 个 OpenClaw SKILL + 44 个 skills/shared/scripts/*.py。测试 650 行。仓库很大(GitHub 上约 300MB+),绝大部分是展示素材。 社区体量733 star / 90 fork / 2 open issue(2026-09-10 GitHub API)。仓库 2026-08-28 创建——分析当天大约两周。浙大 REAL Lab + 北大 OpenDCAI。主页 zju-real.github.io/Easel一句话定位面向国内社媒创作者的开源内容工作台。它自己不写 Agent 主循环,把 OpenClaw 当成执行引擎,用账号画像、112 个可执行 SKILL、真实浏览器发布,把「发现 → 策划 → 创作 → 发布 → 归因」焊成一条能落盘、能真发的流水线。 读者对象:读过本系列任一份分析的读者。请先换轴:前面多数项目在回答「怎么让 AI 写代码 / 干活」;Open Design 回答「怎么不写循环、把别人的 CLI 当引擎做设计」;MiroFish 回答「怎么让一群 AI 把未来预演一遍」。Easel 坐在 Open Design 同一侧——宿主——但客体不是设计文件,是小红书卡片、短视频、知乎回答,以及它们发到真实平台之后的数据


目录

第一部分 · 它是什么 1. 项目概览:两周 700 星,它到底在卖什么 2. 技术栈全解:一张薄整合层 + 一个借来的引擎 + 一座技能库 3. 五层工作流:产品叙事,不是运行时调度器

第二部分 · 宿主怎么接住引擎 4. 三入口单一真相源:chat / skill / web 5. Prompt 四层栈:SOUL → AGENTS → CONTEXT → SKILL 6. sync.sh:workspace 是剧本,项目根才是工地 7. 超时、代理、doctor:把环境做成可检查的产品

第三部分 · 技能即产品 8. SKILL-SPEC:三层加载与 200 行上限 9. 112 个 Skill:按层看完这座库 10. outputs 契约:内容是项目,不是聊天记录 11. 代表作:xhs-note-creator 怎么把一张笔记做出来

第四部分 · 真发与工作台 12. 两道闸门:content_guard 硬拦,persona_gate 只提醒 13. 登录与一键发布:Playwright + 短信墙 14. SSE 对话:断线不杀、双锁、uuid5、thinking 自愈 15. 前端工作台:11 页 SPA 与跨标签会话

第五部分 · 评价 16. 真正新的东西 17. 工程质量:亮点与硬伤 18. 对做新产品的十二条启发 19. 它在本系列里的位置 20. 🔍 源码指路表


Part 1

第 1 章 项目概览:两周 700 星,它到底在卖什么

1.1 先看一组反差数字

指标 数值
Star 733(分析当日)
Fork 90
仓库年龄 约 13 天(2026-08-28 → 2026-09-10)
Python 整合包 768 行 / 9 个文件
Web 后端 web/app.py 单文件 2 509 行
前端 约 5 238 行 TS/TSX
Skill 112 个(discover 9 / plan 16 / produce 50 / publish 20 / attribute 11 / general 6)
共享脚本 44 个 skills/shared/scripts/*.py
许可 Apache-2.0
主页 https://zju-real.github.io/Easel/

Python 包只有 768 行,Skill 有 112 个。 这组数字本身就是架构声明:

Easel 的产品不在「再写一个 Agent 循环」,而在「给一个现成的 Agent 配上社媒领域的手、规矩和工位」。

README 开篇那句最重要:

你的私人、持续进化的社媒运营助手。从一个想法开始,完成发现、策划、创作、发布与复盘。

六个平台写进能力表:小红书、抖音、快手、知乎、B 站、微信视频号。研究愿景写得很直白——把实验室里的社交智能,接到创作者每天真的要发的那条内容上。

1.2 它明确不是什么

不是
又一个编程 Agent 社媒内容工作台
自己实现的 ReAct 循环 OpenClaw 的 profile = easel 隔离工作区
「功能清单」式的 skill 目录 带可运行脚本、产物落盘、真发闸门的技能库
全局一份 USER.md / MEMORY.md 每个请求自包含的画像前缀 + 按画像隔离的 memory.md
聊天记录里的半成品 outputs/<人类可读主题>/ 的项目目录

README 还写了两条使用警告,值得当成产品约束读:

  1. 推荐 Web 前端,CLI 只是入口之一。
  2. 谨慎自动发布到小红书——平台可能检测自动化,有验证、限流、风控。预览 + 人确认后再发。

1.3 谁在做、向谁致谢

浙大 REAL Lab + 北大 OpenDCAI。docs/ACKNOWLEDGMENTS.md 把 112 个 Skill 的来源摊开:卡片模板明确写了借鉴 nexu-io/open-design,去 AI 味借鉴「说人话」,模板复用借鉴 fabric……每个 Skill 目录另有 EASEL-META.md 记来源。这很诚实:技能库是汇编 + 自研,不是从零发明社媒方法论。工程贡献在把它们收成同一套契约(frontmatter、outputs、闸门、画像)。

🧠 一句话:Easel 卖的是「会记住你账号的内容搭档」,工程上它是 OpenClaw 的垂直宿主。本系列里,Open Design 把 25 个 CLI 当引擎做设计;Easel 把 1 个 OpenClaw 当引擎做社媒。


Part 2

第 2 章 技术栈全解:一张薄整合层 + 一个借来的引擎 + 一座技能库

2.1 装配图

flowchart TB
    subgraph UI["① 你能看到的"]
        WEB["React 工作台
对话 / 技能库 / 内容库 / 账号 / 画像
热点 / 日历 / 发布 / 复盘"] CLI["easel chat / skill / doctor"] end subgraph HOST["② 整合层(Easel 自己写的)"] PY["easel/ 768 行
persona · timeouts · CLI"] API["web/app.py 2509 行
SSE · 登录 · 发布 · 产物树"] SS["skills/shared/scripts 44 个
output_paths · manifest · content_guard"] end subgraph ENGINE["③ 借来的引擎"] OC["openclaw --profile easel
gateway :18789"] WS["~/.openclaw/workspace-easel/
SOUL.md + AGENTS.md + skills 副本"] end subgraph DISK["④ 项目磁盘(唯一真相)"] SK["skills/openclaw/ 112 SKILL"] PF["profiles/ 六维画像"] OUT["outputs/ 内容项目"] ENV[".env 模型与媒体 Key"] end WEB --> API CLI --> PY API -->|subprocess + 画像前缀| OC PY -->|subprocess + 画像前缀| OC OC --> WS WS -.symlink.-> PF WS -.symlink.-> OUT OC -->|"cd 到项目根再跑脚本"| SK SK --> SS SS --> OUT

2.2 完整技术栈

技术 源码位置
CLI argparse 子命令:chat / doctor / gateway / ping / skill / web easel/cli.py:124-171
画像 六维 md 拼接 + 消息前缀 + 每轮 TURN_REMINDER easel/persona.py
超时 TIMEOUT_PRODUCE=7200 / TIMEOUT_DIRECT=300 / TIMEOUT_CHAT=7200 easel/timeouts.py:11-13
Web FastAPI + SSE(sse-starlette)+ CORS * web/app.py:190-191
前端 React + Vite,11 个页面组件 web/frontend/src/App.tsxSidebar.tsx:11
引擎 全局 openclaw CLI,profile 名写死 "easel" easel/cli.py:31web/app.py:46
Gateway 127.0.0.1:18789/healthz easel/commands/doctor.py:75-81
发布 Playwright Chromium + biliup + 各平台脚本 pyproject.toml:20-21web/app.py:1996-2053
媒体 Pillow / OpenCV / faster-whisper / edge-tts / rembg / librosa pyproject.toml:18-21
运行时 Python ≥3.10、Node ≥22.19、FFmpeg easel/commands/doctor.py:148-158

pyproject.toml:25-26 的 CLI 入口只有一行:easel = "easel.cli:main"。Web 不是 Python 包里的 ASGI 应用,而是 easel websubprocess 去跑 web/app.pyeasel/cli.py:157-166)。

2.3 被否方案,从注释里读出来

整合层非常薄,但注释里全是「我们试过、翻过车、才收敛到这里」:

被否方案 为什么否 现在怎么做
全局 USER.md 写当前画像 并发会话互相覆盖 画像作为消息内联easel/persona.py:1-7
全局 MEMORY.md 承载账号知识 跨画像污染 sync 时把 workspace 的 MEMORY.md 清空openclaw/sync.sh:101-103
三入口各写各的超时 同一任务经 CLI 能跑完、经 Web 被掐断 easel/timeouts.py:1-8
三入口各写各的画像逻辑 行为不一致 收敛到 easel/persona.py
openclaw.json5 当生效配置 setup 从不应用它,会和真实配置漂移 文件自己在头部警告(openclaw/openclaw.json5:2-5
OpenClaw 默认 thinking=high thinking 块存进历史时丢签名,Bedrock 回放 400 默认 low + session_heal 清洗(web/app.py:51-54
只靠 --session-key 续会话 空闲约 24h 绑定过期,隔天历史全丢 用 uuid5 钉死 --session-idweb/app.py:823-833

🧠 一句话:Easel 的 Python 代码少,是因为它把循环外包了;它真正花力气的地方,是把外包之后必然出现的竞态、超时、会话丢失、密钥泄露,一个一个焊死


Part 3

第 3 章 五层工作流:产品叙事,不是运行时调度器

3.1 五层写在哪

产品层(README)和 Agent 层(AGENTS.md)用的是同一套词:

README 说法 AGENTS.md 说法 Skill layer 字段
发现 热点与机会 热点、爆款、二创机会 discover(9)
策划 选题、标题、脚本、排期 选题、脚本、分镜、封面构思 plan(16)
创作 / 制作 图文、音频、视频 任何要创建文件的任务,写入 outputs/ produce(50)
发布 检查、适配、真发 合规、平台适配、登录、真发 publish(20)
归因 表现回流画像 数据、评论、Profile 回流 attribute(11)
(横切) 工作台基础 general(6)

openclaw/workspace/AGENTS.md:41 写得很克制:

清晰单层任务直接执行;跨两层以上或明显多步骤任务先给简短 Plan。一个任务可组合多个 SKILL,但不要运行无关层。

没有一个 Python 类叫 WorkflowEngine 按层调度。五层是写给模型看的分工规则,落地靠两样东西:

  1. 每个 SKILL.md 的 YAML layer:(路由与文档)
  2. 跨层时 manifest.pyoutputs/<主题>/.easel.json 里记「产物路径 + 一句结论」(docs/SKILL-SPEC.md:136-149

3.2 纵向编排的薄索引

AGENTS.md 对跨层的规定(openclaw/workspace/AGENTS.md:43-45):

  • 跨两层以上才建 manifest;单层不建
  • 每层完成或失败都登记
  • 下游用 latest / read 取上游,不重新推导、不整块转发
  • 完整载荷写文件,关键决策写 brief.md

docs/SKILL-SPEC.md:157-159 把原则说得更狠:manifest 只当薄索引summary 一行给编排层路由,outputs[] 指路径,不复制内容

这和编程 Agent 把中间结果堆在对话里,是相反的哲学:对话是控制面,磁盘是数据面

sequenceDiagram
    participant U as 用户
    participant A as OpenClaw Agent
    participant M as manifest.py
    participant D as outputs/主题/
    U->>A: 帮我做一条小红书并排期发布
    A->>A: Plan:discover → plan → produce → publish
    A->>D: 写 brief.md / 卡片 / 文案
    A->>M: record --layer produce --outputs card_1.png,...
    A->>M: latest --layer produce
    M-->>A: 路径 + 一句结论
    A->>D: 发布脚本 --exec
    A->>M: meta --status published

3.3 制作层的特殊地位

AGENTS.md 把「制作」定义成凡是要创建文件的任务openclaw/workspace/AGENTS.md:31),不只是「好看的视频」。超时也按这个定义给预算:chat 入口按制作层 7200 秒,因为对话中途可能生视频(easel/timeouts.py:7-13)。

制作流程固定五步(openclaw/workspace/AGENTS.md:53-63):凝练 Profile → 明确规格 → 按 SKILL 产出 → 自检 → 仅失败时返工一次。制作层不读 platforms.md,原样遵守 preferences 红线。这是故意的:做内容时先把东西做对,平台适配留给发布层。


Part 4

第 4 章 三入口单一真相源:chat / skill / web

4.1 三个入口,同一句话

入口 怎么进 OpenClaw 画像怎么注入 超时
easel chat openclaw --profile easel chat --session ... --message <prefix> 选画像后,前缀作为初始消息 TIMEOUT_CHATeasel/cli.py:103-117
easel skill openclaw agent --session-key skill-<ts> --message "请执行 /<skill>…" persona_prefix 拼在指令前 一律 TIMEOUT_PRODUCEeasel/commands/skill.py:156-164
Web /api/chat/stream 同上 openclaw agent,外加 --session-id 钉死 transcript chat_turn_message = 前缀 + 用户原文 + TURN_REMINDER TIMEOUT_CHATweb/app.py:1080-1084
Web /api/skill run_agent_sync _persona_prefix + 请执行 /… TIMEOUT_PRODUCEweb/app.py:1453-1463

CLI chat 不加 TURN_REMINDER,Web 对话才加。easel/persona.py:72-76 解释了为什么:SKILL 单跑不必加;对话会长,系统提示会衰减。

4.2 画像前缀长什么样

我当前使用的画像是「科技数码达人」。本会话的账号长期记忆仅使用 profiles/科技数码达人/memory.md,不要使用工作区全局 MEMORY.md 作为账号记忆。

实现:easel/persona.py:58-69。六维文件顺序写死(identity / style / audience / platforms / preferences / memoryeasel/persona.py:16-20)。load_profile_text 按这个顺序拼接,其它 .md 追加——但注入进 OpenClaw 的并不是全文,只是「去读哪个目录」的指针。真正凝练由 AGENTS.md 要求模型自己去读 easel-profiles/<名>/

4.3 每轮行为提醒:用近因效应对抗指令衰减

TURN_REMINDEReasel/persona.py:77-83)拼在用户消息末尾,并标明「内部提醒、勿复述」。内容就三句:

  1. 动手前先查技能库,别凭记忆裸做
  2. 五层都由你自己把成品写到 outputs/
  3. 问账号先查登录态;对外文案不许泄密钥、也不许自曝「AI 生成」

注释把动机写得很清楚(easel/persona.py:72-76):AGENTS.md / SOUL.md 只在会话开头新鲜,长对话后面模型会「凭记忆裸做」。把最关键的反射放末尾,成本极低。

这是本系列里很少被写成代码的一件事:系统提示会衰减,所以把承重规则再喂一遍,而且喂在用户话后面。

4.4 skill 入口如何消化输入

easel/commands/skill.py:48-66:输入可能是文本,也可能是路径。图片改写成「请处理这个图片:绝对路径」;音视频/PDF/压缩包只传路径,避免 read_text 把二进制当 UTF-8 炸;普通文本文件才读进消息。过长的字符串(超过文件名上限)Path.is_file 会抛 OSError,被当成文本——tests/test_core.py:71-74 专门锁了这个坑。

Skill 名允许省略 skill- 前缀(easel/commands/skill.py:39-45),Web find_skill 同一套逻辑(web/app.py:213-219)。


Part 5

第 5 章 Prompt 四层栈:SOUL → AGENTS → CONTEXT → SKILL

docs/prompt-stack.md 把组合顺序写死。对照 Open Design 的「20 层按缓存频率分带」,Easel 只分四层,而且常驻层刻意不写具体 skill 名

5.1 Layer 1 — SOUL.md(人格,30 行)

openclaw/workspace/SOUL.md:你是创作者的「搭子」——懂策略,也能上手。能力总览按五层写「能做什么」,不写 skill 名,避免像某个平台下架那样过时(docs/prompt-stack.md:25)。沟通风格:中文、给 2–3 个选项、对外文案绝不暴露工具痕迹。

5.2 Layer 2 — AGENTS.md(业务宪法,110 行)

这是最重要的文件。核心执行规则六条(openclaw/workspace/AGENTS.md:5-12):

  1. 先路由 SKILL——精确匹配 → 最接近 → 才用通用能力
  2. 先到项目根——第一个脚本前必须 cd 到 sync 注入的绝对路径
  3. 不在 workspace 跑项目副本——禁止从 OpenClaw workspace 的 shared/ 跑脚本
  4. 查现有信息再提问——登录态、画像、历史产物先查
  5. 付费操作先确认——生图/生视频/音乐先给范围和费用
  6. 真实产物才算完成——计划、空壳、提示词不算

另外还有一整节「配置检查」:模型 Key 只能以项目根 .env 为准;env / printenv 看不到未 export 的值;workspace ls -a 也看不到项目根 .env——二者都不能用来宣称缺配置(openclaw/workspace/AGENTS.md:16-23)。这是被真实翻车逼出来的:Agent 在错误的 cwd 里喊「你没配 Key」,用户其实配好了。

5.3 Layer 3 — CONTEXT.md(半静态路径)

sync.sh 生成,含项目绝对路径。注释强调:本 claude 版本不支持 --cwd,所以必须先 cd 再跑脚本(openclaw/sync.sh:128-137)。

5.4 Layer 4 — SKILL(按需)

触发时加载 SKILL.md,references 按引用再读,scripts 执行时调用、代码不进 promptdocs/prompt-stack.md:36-39)。这和 Claude Code 的 skill 三层披露是同一类想法,Easel 把它写进了自己的 SKILL-SPEC。

flowchart LR
    S["SOUL.md
人格 · 类目级能力"] --> A["AGENTS.md
分工 · 编排 · 安全"] A --> C["CONTEXT.md
项目根绝对路径"] C --> K["SKILL.md
被触发才加载"] K --> R["references/
领域知识按需"] K --> SC["scripts/
不进 prompt"] P["画像前缀
每条消息"] -.-> A T["TURN_REMINDER
仅 Web 对话末尾"] -.-> K

Part 6

第 6 章 sync.sh:workspace 是剧本,项目根才是工地

6.1 隔离 profile 的真实路径

workspace → ~/.openclaw/workspace-easel/
config    → ~/.openclaw-easel/openclaw.json

openclaw/sync.sh:6-8

--profile easel 把 Easel 和用户机器上可能存在的其它 OpenClaw 工作区切开。

6.2 同步做了什么

  1. 删除源里已经没有的 SKILLopenclaw/sync.sh:30-38
  2. 整目录 cp -r 每个 SKILL 到 workspace/skills(openclaw/sync.sh:46-53
  3. 复制 skills/shared/ 到 workspace/shared(给 SKILL 里 ../../shared/ 相对引用)
  4. 复制 AGENTS.md / SOUL.md,再追加「运行时项目根」绝对路径openclaw/sync.sh:77-93
  5. 删掉残留 USER.md清空 MEMORY.mdopenclaw/sync.sh:97-103
  6. symlink easel-profiles → 项目 profiles/outputs → 项目 outputs/openclaw/sync.sh:106-124
  7. CONTEXT.md

symlink 那段写了个真实坑:必须先 rmln -s,否则 ln -sf 会跟着旧链接走进目标目录造成循环(openclaw/sync.sh:107)。

6.3 为什么脚本必须在项目根跑

manifest.py:58-61 自己招认:脚本会被 sync 拍平复制workspace/shared/scripts/,那里 __file__ 少一层 skills/,上溯会算成 ~/.openclaw,产物写错地方。所以 EASEL_ROOT 环境变量不可省。CLI 和 Web 都在 _proxy_envsetdefault("EASEL_ROOT", 项目根)easel/cli.py:36web/app.py:303)。

AGENTS.md 同步后追加的那段,等于每轮都把「去哪 cd」焊进系统提示。workspace 里那份 shared/ 只给模型读文档用,不是执行 cwd

6.4 openclaw.json5 是一份不会生效的模板

文件头写得毫不留情(openclaw/openclaw.json5:2-5):

本文件仅作参考,实际配置由 setup.sh 通过 openclaw config set 直接写入 ~/.openclaw-easel/openclaw.json,此模板从不被 setup.sh 应用。

Gateway 端口 18789、模型 anthropic/claude-sonnet-4-6 只是示意。读源码的人如果把 json5 当运行时配置,会错。 这是一种少见的诚实:把「可能漂移的模板」标成危险品,而不是假装它是单一真相源。


Part 7

第 7 章 超时、代理、doctor:把环境做成可检查的产品

7.1 超时三常数

TIMEOUT_PRODUCE = 7200   # 生视频 / 多镜合成
TIMEOUT_DIRECT  = 300    # 发现 / 策划 / 发布 / 归因
TIMEOUT_CHAT    = TIMEOUT_PRODUCE

easel/timeouts.py:11-13

chat 按制作层给预算,是因为用户在对话里随时可能说「做成视频」。skill CLI 一律用制作层上界,简单粗暴,避免再按 layer 分支出错。

7.2 代理:保护内网直连

_proxy_envEASEL_PROXY 填进 http_proxy / https_proxy,同时 no_proxy 包含 localhost127.0.0.1*.xiaohongshu.com*.devops.xiaohongshu.com10.*easel/cli.py:33-40)。这是一张实验室内网痕迹:小红书内部域名要直连,外网走代理。content_guard 稍后会把这些内部域名当成 BLOCK 级泄露

7.3 doctor 检查清单

easel doctoreasel/commands/doctor.py:143-207)按产品依赖逐项打勾:

Python ≥3.10 / venv / Node ≥22.19 / FFmpeg / openclaw 在 PATH / fastapi·uvicorn·sse_starlette·multipart / 前端 dist/index.html / Playwright Chromium / .env 里任一认证通道 / gateway :18789/healthz / ~/.openclaw/workspace-easel/skills/ 非空 / 关键文件存在。

认证通道是「任一即可」(easel/commands/doctor.py:92-140):ANTHROPIC_API_KEY,或 EASEL_LLM_API_KEY + BASE_URL,或 ANTHROPIC_AUTH_TOKEN + BASE_URL,或 OPENAI_API_KEY,或火山 MaaS。占位符 REPLACE_ME 不算配好。

easel ping 再做一次真连通:healthz + 让 agent 说 PONG(easel/commands/ping.py:48-78)。doctor 是静态存在性,ping 才是权威。


Part 8

第 8 章 SKILL-SPEC:三层加载与 200 行上限

docs/SKILL-SPEC.md 是技能库的接口规范 v0.3。

8.1 目录与三层

skills/
├── openclaw/     五层 SKILL,OpenClaw 直接执行
└── shared/       跨 SKILL 脚本与配置
skill-xxx/
├── SKILL.md      必须,< 200 行,只写「怎么做」
├── references/   领域知识,按需加载
├── scripts/      运行时调用,代码不进 prompt
└── tests/        test1.prompt / test1.expected
加载时机 token
Metadata(name, description, layer) 常驻,用于路由 极小
Instructions(SKILL.md 主体) 被触发 中等
Resources(references + scripts) 执行中按需 按需

frontmatter 只保留三个常规字段name / description / layer。禁止 versionallowed-toolstags 等(docs/SKILL-SPEC.md:73-78)。description 必须用中文写清能力、触发说法、与相邻 SKILL 的边界。

校验两条命令:scripts/validate_skills.py(frontmatter、资源链接、输出与发布安全契约)和 scripts/validate_skill_commands.py(SKILL 里的 Python 命令 vs 脚本 argparse,防路径漂移)。

8.2 设计约束五条

独立可调、无 Profile 也能用、接口稳定、SKILL.md 精简、泛化不给具体 case(docs/SKILL-SPEC.md:167-173)。

「无 Profile 也能用」决定了产品可以先玩起来再慢慢建画像;「独立可调」决定了 112 个目录可以并行改,而不是一张大网。

8.3 和编程 Agent 的 skill 有何不同

Claude Code / Codex 的 skill 多半是「教模型怎么用工具」。Easel 的 skill 自己就是一条制作流水线:SKILL.md 里写着要跑哪条 python skills/.../scripts/foo.py,产物路径被 output_paths.py 门禁,发布前被 content_guard.py 扫描。Skill 不是说明书,是带执行器的 SOP


Part 9

第 9 章 112 个 Skill:按层看完这座库

数量以 docs/skill-function-mapping.md 与检出目录为准,frontmatter layer 统计:

数量 这一层在干什么
general 6 产物管理、批量处理、登录账号查询、画像构建/管理、模板库
discover 9 热搜、竞品、内容缺口、跨平台差异、节日、行业资讯、RSS、算法更新、UGC
plan 16 定位、受众、人设、选题矩阵、评分、日历、钩子、大纲、分镜、直播、商单
produce 50 文字 / 视觉 / 音频 / 视频 / 小说 / 短剧 / 论文解读——半壁江山
publish 20 六平台上传、跨平台分发、排期、质量门禁、评论回复、短链、通知
attribute 11 数据、评论洞察、ROI、复盘、画像记忆

制作层 50 个不是「50 种文案」,而是把 ffmpeg、TTS、生图、生视频、字幕、绿幕、卡点、相册……收成 Agent 可调的原子。共享脚本层才是真正的多媒体 SDK。

Web 侧 get_skills()web/app.py:270-286)扫 skills/openclaw/*/SKILL.md,解析 description/layer,并对少数 Skill 标 needsApi(生图/生视频/音乐/克隆/短剧/论文解读,web/app.py:142-170)。前端技能库页用这张表决定要不要感叹号「去配 Key」。

SKILL_API_REQUIREMENTSskills/shared/scripts/model_registry.py 共用同一真相——tests/test_core.py:33-39 断言 Web 的 provider 列表等于注册表,序列化结果里不得出现密钥。

9.1 制作层里值得单独点名的几类

类型 代表 Skill 特点
平台总入口 xhs-note-creator 小红书图文/视频的 SOP,强制去 AI 化、走 card-design
一键出片 auto-short-video 文案→配图/AI 视频→配音→字幕→BGM→合成
长内容 novel-writer / paper-explainer / short-drama 文件化状态、跨章一致性、剧集圣经
确定性媒体 image-editing / video-editing / audio-editing 不靠模型,靠 OpenCV/ffmpeg
去 AI 味 text-polisher 七轮扫描 + 中文 AI 标记表,被其它 Skill 引用为权威源

xhs-note-creator 的 SKILL.md 明确写:整套笔记用本 SKILL;仅渲染卡片用 card-xiaohongshu;其它平台用 social-contentskills/openclaw/xhs-note-creator/SKILL.md:4-6)。边界写在 description 里,这就是路由的全部机制——没有 Python 路由器。


Part 10

第 10 章 outputs 契约:内容是项目,不是聊天记录

10.1 目录规约

outputs/<人类可读主题>/
├── note.md / final.mp4 / card_1.png   成品,放项目根
├── assets/                            中间件:帧、切片、草稿
└── .easel.json                        展示头 + steps[](隐藏)

禁止:泛名(xhs / test / tmp / 主题……,output_paths.py:25-29);成品散落 outputs/ 根;内容写入 _ 系统目录。系统目录白名单:_analytics _debug _inbox _login _probe _profile_build _publish _scratch _sessionsoutput_paths.py:30-33)。

validate_output_pathoutput_paths.py:41-75)是所有新脚本的门禁:解析到项目根、必须在 outputs/ 下、内容至少两级路径、泛名直接抛 OutputPathError。系统写入必须 allow_system=True 且 top 在白名单。

10.2 manifest 的两份工作

skills/shared/scripts/manifest.py

  1. 展示头meta):title / platform / kind / status / tags / cover / deliverables——给前端内容库做卡片
  2. steps[]record):layer / skill / at / status(done|failed) / outputs[] / upstream[] / summary——给下游编排

kind ∈ article / xhs-note / video / cards / poster / audio / other;项目 status ∈ draft / ready / published。atomic_write 用 mkstemp + os.replacemanifest.py:107-118)。

Web _read_project_metaweb/app.py:505-520)只取展示字段,不把 steps 泄露给内容库 UI。封面解析:声明的 cover → 首个成品媒体 → 目录里第一张图/视频。

10.3 附件隔离

用户上传进 outputs/_inbox/,按会话隔离。本轮消息附「系统附件清单」,只允许用清单里的路径,禁止扫描 inbox 其它文件(openclaw/workspace/AGENTS.md:91web/app.py:790-795)。纳入项目时复制到 outputs/<项目>/assets/,inbox 原件保留以便重试。

这是发布类 Agent 特有的威胁模型:inbox 里可能有别的会话刚上传的素材,模型如果 ls 一下就串味。


Part 11

第 11 章 代表作:xhs-note-creator 怎么把一张笔记做出来

小红书是 Easel 的主场。这个 SKILL 把「笔记」定义成 3–9 张 3:4 卡片或 15–90 秒竖屏分镜,长文只是中间产物(skills/openclaw/xhs-note-creator/SKILL.md:14-19)。

工作流(不可跳步):

  1. Intake:主题 / 形态 / 素材 / 风格——一次问完
    0.5 卖点公式:稀缺性 × 实用性 × 可感知
  2. 有素材则 analyze_material.py 出清单
  3. 观点类必须外部参考,核心数据 ≥2 源
  4. 写 2000–4000 字长文原稿,先给用户确认
  5. 强制去 AI 化——规则不在本 SKILL 维护副本,统一走 text-polisher 的 references(SKILL.md:73-79
  6. 视觉走 card-design 九种风格,渲染走 card-xiaohongshu
  7. 质检、落 outputs/、登记 manifest

爆款五原则里有一句很关键:视觉不能糙。想要「素人/手账」也要选 card-design 的手账贴纸风格,而不是真的用糙 t2i 加大 emoji(SKILL.md:25)。这是 Open Design 那套「反 AI 味」在社媒卡片上的落地,ACKNOWLEDGMENTS 也承认卡片模板借鉴了 open-design。

脚本目录里有 validate_meta.pyanalyze_material.pynormalize_slug.pycrop_watermark.pytext_on_image.pycollage_3x4.py——模型负责判断和文案,像素级操作交给确定性脚本。


Part 12

第 12 章 两道闸门:content_guard 硬拦,persona_gate 只提醒

这是 Easel 最值得抄的产品决策之一:会真发到公开平台的 Agent,必须假设模型会把内部设置写进文案。

12.1 content_guard:fail-closed,退出码 7

skills/shared/scripts/content_guard.py 头部把事故写出来了(:3-7):过去从生成到 --exec 之间没有任何过滤,密钥、内部 URL、代理 IP、「由 AI 生成」一旦发出去就删不掉。

两级强制(content_guard.py:97-105):

级别 类别 真发时
BLOCK api-key / env-value / internal-host / proxy-ip / internal-path / env-name exit 7,改稿重发
WARN ai-disclosure / model-name 只告警。论文解读里「Claude」可能是正文

扫描是双路:正则表 + .env 里敏感键的字面值content_guard.py:141-176)。命中片段在报告里打码(_mask / _snippet),避免日志二次泄露。dry-run 全部只告警;放行硬拦必须显式 --allow-unsafe。AGENTS.md 要求:除非用户明确要求,不要用这个开关绕过openclaw/workspace/AGENTS.md:72)。

正则里能看到实验室拓扑:maas.devops.xiaohongshu.comwebide-gateway.devops.xiaohongshu.com/mnt/tidal-alsh01happyhorse 生视频内部名(content_guard.py:50-85)。闸门既是通用产品,也是这份代码从内部工具开源出来时的脱敏层。

12.2 persona_gate:人设检查永不阻断发布

发布层 SKILL 执行前,有 Profile 就跑 skill-persona-check(LLM 打分),再交给 persona_gate.py check 把分数变成 pass/warn。阈值 80(persona_gate.py:29)。退出码永远是 0persona_gate.py:10-11, 54):publish_allowed: true

AGENTS.md 把理由说死(openclaw/workspace/AGENTS.md:74-78):人设检查只提醒;用户已明确要发就继续,不额外索要确认。内容安全与平台合规仍独立硬拦。

record 把评分写进 manifest 的 publish 步,便于事后审计。

🧠 一句话:密钥泄露是不可逆的公共事故,所以硬拦;「这条不很符合人设」是创作判断,所以只提醒。两道闸门的力度反着来,是对「自动化发布」责任边界的正确划分。

flowchart TD
    C[待发文案] --> G{content_guard}
    G -->|BLOCK 命中| X[exit 7 改稿]
    G -->|通过或仅 WARN| P{有画像?}
    P -->|是| S[skill-persona-check LLM 评分]
    S --> PG[persona_gate classify]
    PG -->|pass / warn 都继续| E[--exec 真发]
    P -->|否| E

Part 13

第 13 章 登录与一键发布:Playwright + 短信墙

13.1 六平台登录后端

LOGIN_RUNNERSweb/app.py:98-105):

前端 key 显示名 backend
xiaohongshu 小红书 xhs
douyin 抖音 douyin
kuaishou 快手 web + wp=kuaishou
weixin-channels 微信视频号 web + wp=weixin-channels
zhihu 知乎 web + wp=zhihu
bilibili B 站 biliup

浏览器 profile 在 ~/.easel-browser-profilesweb/app.py:82)。登录状态写 outputs/_login/。whoami 真校验要起 headless 浏览器,进程内缓存 600 秒(web/app.py:93-96),避免账号页和工作台重复开浏览器。

13.2 /api/publish/{platform}

二次确认在前端。后端拼出 --exec 命令(web/app.py:1996-2053):

  • 小红书:xhs_publish.py publishpublish-video
  • 抖音:同样结构,但异步——可能触发短信墙,状态写 outputs/_publish/douyin.json,验证码写 douyin.code,前端轮询到 sms_required 弹框
  • B 站:直接 biliup upload,默认分区 tid=36「知识」,无标签兜底「日常」
  • 其它:web_publisher.py --platform <wp>

约束:有的平台必须带媒体;图和视频不能同时发;视频号类只能发视频。超时 600 秒。每次发布追加 outputs/_publish.log

发布走 _publish_env(),和对话代理环境分开,避免把代理打到平台域名上把登录态搞丢。

13.3 为什么 README 要你小心小红书

自动化操作会被风控。Easel 选择「能真发」而不是「只给文案」——这是产品差异化,也是合规与账号安全的代价。代码把 dry-run、预览、人确认、content_guard 叠在 --exec 前面,但挡不住平台侧的行为检测。这不是 bug,是宿主选择进入真实世界之后必须写在包装上的警告。


Part 14

第 14 章 SSE 对话:断线不杀、双锁、uuid5、thinking 自愈

web/app.py 里的对话实现,是这份薄整合层里最「系统」的一段。注释密度极高,几乎每条都对应一次线上事故。

14.1 问题 1:CLI 不流式

openclaw agent 会把模型输出缓冲到结束才打印(web/app.py:1028-1032)。解法:设 OPENCLAW_RAW_STREAM=1 + 每轮独立 jsonl 路径,后端 tail 文件,把 assistant_text_streamtext_delta 转成 SSE token,thinking 转成 thinking。每轮独立文件,无并发串扰。

14.2 问题 2:代理会掐断 SSE,但制作要跑很久

supervisor(跑 openclaw)和 forward(给浏览器)拆开(web/app.py:1037-1039, 1352-1374):

  • 客户端断开只结束 forward,不取消 supervisor
  • 完整结果落 outputs/_sessions/<sk>.json,前端用 /api/chat/last 取回
  • 另有 /api/chat/jobs/{turn_id}/stream 按 jsonl 续 SSE(web/app.py:986-1021
  • 只有用户点停止terminate 进程(web/app.py:1408-1415);断线不杀

长任务时 WebIDE 代理掐 SSE,是注释里点名的场景(web/app.py:946-947)。

14.3 问题 3:同一会话两个 openclaw = takeover 崩溃

OpenClaw 并发写同一 session 会抛 EmbeddedAttemptSessionTakeoverError,表现是「答一半停在冒号」(web/app.py:808-811)。双层锁:

  1. 进程内 asyncio.Lock 按 session-key(web/app.py:812-820
  2. 跨进程 fcntl.flock(Windows 走 msvcrt.locking)(web/app.py:841-896

不同会话仍可并行。拿不到 flock 就返回「正在另一个窗口运行」。前端还有 BroadcastChannel 探测跨标签占用(第 15 章)。

14.4 问题 4:隔天再问就忘了

OpenClaw 靠 --session-key 解析 transcript,空闲超过约 24h(threadBindings.idleHours)绑定过期,会新起空 transcript(web/app.py:823-827)。解法:用固定命名空间做 uuid5,同一 web sessionId 永远同一 --session-idweb/app.py:828-833),绕开 key→绑定过期。

14.5 问题 5:thinking 块丢签名,回放 400

OpenClaw 把 Claude thinking 存进 jsonl 时丢了 signature,内网 Bedrock 回放校验失败(scripts/session_heal.py:3-6web/app.py:51-54)。每轮 spawn 前 _heal_openclaw_session 删 thinking / redacted_thinking 块和空消息。默认 EASEL_THINKING_LEVEL=low 减少产生量。

14.6 问题 6:「答完了」其实没答完

收尾检测(web/app.py:1267-1290)不把「已经吐了字」当成成功。正常收尾的唯一标志是 raw 流最后一个事件为 assistant_message_end。否则追加一段给用户看的警告:触顶截断、进程被杀、停在 tool_use、流被掐、正文停在中文冒号(用户实测「所有莫名停止都停在冒号」)。

每次收尾还往 outputs/_debug/chat-stream.jsonl 打一行诊断(web/app.py:1313-1333):rc、stop_reason、last_ev、token/thinking 字数、是否误收了别的 session 的 raw 事件。

flowchart TB
    B[浏览器] -->|POST /api/chat/stream| F[forward 生成器]
    F -->|SSE token/thinking/activity| B
    S[supervisor 后台任务] -->|client_q| F
    S --> L[asyncio 锁 + flock]
    L --> P[openclaw agent 子进程]
    P --> RAW[每轮 jsonl raw stream]
    S -->|tail| RAW
    S -->|原子写| T[outputs/_sessions/sk.json]
    B -.断线.-> F
    S -.断线后继续.-> T
    B -->|/api/chat/last| T

Part 15

第 15 章 前端工作台:11 页 SPA 与跨标签会话

15.1 页面

Sidebar.tsx:11Page 类型:

dashboard | chat | trends | ideas | calendar | publish | breakdown | skills | outputs | accounts | profile

侧栏只放六个主导航(工作台 / 对话 / 技能库 / 内容库 / 账号 / 画像),热点、选题、日历、发布、复盘收进工作台 SubNav,避免侧栏爆炸(Sidebar.tsx:31-39)。

App.tsx流式状态放在根组件,切页不卸载、不丢流(web/frontend/src/App.tsx:164 注释)。这是对「用户去内容库看一眼生成物、对话还在跑」的正确结构。

15.2 跨标签:BroadcastChannel + flock 兜底

挂载逻辑(App.tsx:55-125):

  1. 同标签刷新:sessionStorage 记着本标签会话 → 续上
  2. 新标签:用 BroadcastChannel 问「谁在用上次活跃会话」;有人应答 owned 就开新会话,避免两个窗口撞同一 OpenClaw session
  3. 不支持 BroadcastChannel:退回旧逻辑,后端 flock 仍能挡住崩溃

Onboarding:没有任何画像且没看过引导 → 推荐配置向导(App.tsx:144-147)。还处理了一次品牌更名:旧 localStorage key 从 postcraft_onboarding_seen 迁到 easel_onboarding_seen(用 ['post','craft'].join('') 躲开简单的字面扫描,App.tsx:36)。

15.3 API 客户端

web/frontend/src/lib/api.ts 用当前 pathname 推 BASE,方便子路径部署。错误优先展示 FastAPI detail。类型里把 .easel.json 展示头和产物树节点写全,和后端 _read_project_meta / _build_output_node 对齐。


Part 16

第 16 章 真正新的东西

在本系列坐标系里,Easel 不是「又一个更强的循环」。它新在五件事:

16.1 垂直宿主,而且宿主只接一个引擎

Open Design 的宿主接 25 个 CLI,适配器即数据。Easel 只接 OpenClaw,把差异化全部做在 SKILL + 画像 + 真发闸门。更窄,所以能把社媒 SOP 做深(112 个、带脚本、带契约)。

16.2 对话是控制面,项目目录是数据面

编程 Agent 的产物经常活在 diff 和聊天里。Easel 强制 outputs/<主题>/ + 成品/中间件分离 + 薄 manifest。这是内容团队能理解的「一个选题一个文件夹」,不是工程师的 session 日志。

16.3 画像是请求的一部分,不是全局文件

否掉 USER.md 之后,并发会话不再互踩。记忆按 profiles/<名>/memory.md 隔离,全局 MEMORY.md 被同步脚本保持为空。这是多账号运营的正确内存模型。

16.4 真发之前的分级闸门

content_guard 的 BLOCK/WARN 分级、persona_gate 永不阻断、.env 字面值扫描——针对的是「Agent 会把内部世界说出去」这个发布场景特有的事故,编程 Agent 的沙箱解决不了。

16.5 把 OpenClaw 的坑当成产品表面来修

thinking 丢签名、24h session 绑定过期、并发 takeover、SSE 被代理掐断、指令衰减、cwd 跑错导致误报缺 Key——这些都不是「调用 CLI」就能自动好的。Easel 的 2509 行后端,一大半是在为上游引擎做适配器式的伤口包扎。这和 Open Design 为 25 个 CLI 写解析器,是同一类工作。


Part 17

第 17 章 工程质量:亮点与硬伤

17.1 亮点

证据
三入口收敛单一真相源 persona.pytimeouts.py,注释写明历史分叉
确定性脚本带 selftest content_guard / persona_gate / manifest / output_paths
原子写 manifest os.replace;turn 结果 .tmp 再 replace
测试锁契约 tests/test_core.py 覆盖输入分类、路径安全、注册表脱敏、persona_gate
Skill 来源可追溯 ACKNOWLEDGMENTS + 每 Skill 的 EASEL-META.md
配置模板标明无效 openclaw.json5 头部警告
诊断日志 chat-stream.jsonl 把「莫名停下」分类

17.2 硬伤与代价

  1. web/app.py 单文件 2509 行。 路由、锁、SSE、登录、发布、产物树、画像 CRUD 全挤在一起。对两周的仓库可以理解,对后续贡献者不友好。
  2. 版本号分裂。 pyproject.toml:70.1.0 vs easel/__init__.py:30.0.1
  3. 引擎不在仓库里。 OpenClaw 是 npm i -g openclaw。分析无法核对循环本身;Easel 的正确性依赖上游 CLI 的 flags(--session-idOPENCLAW_RAW_STREAM)继续存在。
  4. json5 模板与真实配置漂移是已知的,但新人仍会被文件名骗。
  5. Skill 质量必然不均。 112 个目录,有的是完整流水线(xhs-note-creator),有的更接近提示词包装。validate_skills.py 能锁格式,锁不住「这个 SOP 在真实运营里好不好用」。
  6. 测试只有 650 行,且明确只测整合层纯函数(tests/test_core.py:1-4)。发布脚本、Playwright、OpenClaw 交互是测试盲区——真发路径最危险的部分反而最难测。
  7. CORS allow_origins=["*"]web/app.py:191)对本地工作台够用,不是能暴露到公网的形状。
  8. 小红书自动发布的风控写进了 README,但代码路径仍然提供 --exec。产品把选择权交给用户,也把风险交给用户。
  9. sync 是整目录复制,不是增量。112 个 Skill 每次全量 cp,workspace 和源可能在 Agent 跑着的时候被覆盖。
  10. TURN_REMINDER 每轮追加,长对话的 token 成本线性涨;换来的是路由命中率。这是公开的取舍(easel/persona.py:72-76)。

Part 18

第 18 章 对做新产品的十二条启发

  1. 先决定循环归谁。 如果领域 SOP 比通用推理更值钱,就别写循环,写宿主。
  2. 宿主的核心工作是包扎引擎伤口,不是 subprocess.run 一行搞定。
  3. 领域产物用文件系统当一等公民,别让它们活在聊天记录里。
  4. 跨步骤传递用薄索引 + 路径,不要口传全文。
  5. 会发到公网的文本,必须有确定性扫描。 提示词里的「不要泄露」不够。
  6. 闸门分级:不可逆事故硬拦,审美/人设只提醒。
  7. 多租户记忆不要放全局文件。 画像是请求的一部分。
  8. 长对话要假定系统提示会衰减,把承重规则用近因再喂一遍。
  9. SSE 与长任务解耦:断线不杀,结果落盘,前端来取。
  10. 同一会话同一时刻只允许一个引擎进程——锁要跨到进程外。
  11. 环境检查做成产品命令(doctor / ping),不要只写 README。
  12. 把「这份文件不生效」写在文件头上。 漂移的模板比没有模板更危险。

Part 19

第 19 章 它在本系列里的位置

flowchart LR
    subgraph LOOP["自己写循环"]
        CC["Claude Code / Codex / dsh
通用:写代码、干活"] MF["MiroFish
垂直:仿真预演"] end subgraph HOST["不写循环 · 宿主"] OD["Open Design
垂直:设计文件"] EA["Easel
垂直:社媒内容 + 真发"] end CC -.skill 思想.-> EA OD -.卡片视觉 / 宿主形态.-> EA
项目 问题 和 Easel 的关系
Open Design 不写循环,接 25 个 CLI 做设计 最近亲缘:都是宿主。Easel 只接 1 个引擎,技能库更垂直;卡片视觉还借鉴了它
OpenWorker 通用任务同事,收件箱 + 审批 都面向「非编程工作」;OW 强调人不在时的恢复,Easel 强调内容项目与真发
MiroFish 仿真,不干活 同为非编程;一个预演世界,一个生产内容
Claude Code / Codex 写循环的编程 Agent Easel 把它们那种 skill 思想用在社媒 SOP 上,循环本身外包
DeepSeek Harness 循环也是插件 对照:dsh 把可替换做到极致;Easel 把可替换让给上游,自己锁死领域契约

谱系补丁:前七家编程 Agent → OpenWorker 同事 → Open Design 设计宿主 → MiroFish 仿真 → Easel 社媒宿主。它让「不写主循环」这条路线从设计工具走进了国内创作者每天要面对的六个平台。

配套阅读:从零构建社媒内容工作台(Easel 系) · 交互实验台 Easel-解析.html


Part 20

第 20 章 🔍 源码指路表

全部对照 commit 765f5a6

你想看 去这里
CLI 入口与 chat 注入画像 easel/cli.py:49-121
三入口画像前缀 / TURN_REMINDER easel/persona.py:1-99
超时常量 easel/timeouts.py
skill 路由与二进制输入 easel/commands/skill.py:31-164
doctor / ping / gateway easel/commands/doctor.pyping.pygateway.py
Prompt 栈说明 docs/prompt-stack.md
SKILL 规范 docs/SKILL-SPEC.md
112 Skill 能力地图 docs/skill-function-mapping.md
Agent 宪法 openclaw/workspace/AGENTS.md
人格 openclaw/workspace/SOUL.md
同步与 symlink openclaw/sync.sh
「此配置不生效」 openclaw/openclaw.json5:1-35
SSE supervisor / 双锁 / uuid5 web/app.py:808-1401
停止 vs 断线 web/app.py:1408-1415
Skill HTTP web/app.py:1453-1463
产物树与展示头 web/app.py:488-545
登录表 web/app.py:98-105
一键发布 web/app.py:1996-2068
前端跨标签 web/frontend/src/App.tsx:55-125
页面枚举 web/frontend/src/components/Sidebar.tsx:11-39
路径门禁 skills/shared/scripts/output_paths.py
层间契约 skills/shared/scripts/manifest.py
出站扫描 skills/shared/scripts/content_guard.py
人设提醒 skills/shared/scripts/persona_gate.py
thinking 自愈 scripts/session_heal.py
小红书 SOP skills/openclaw/xhs-note-creator/SKILL.md
依赖与版本 pyproject.toml
整合层测试 tests/test_core.py
Skill 致谢 docs/ACKNOWLEDGMENTS.md