🥥 xskill
1 / 11
🥥
Architecture Story · 架构故事

4 + 1 视图
读懂 xskill

一套讲清"复杂系统怎么从不同角度看"的方法
顺便把 类图 / 时序图 / story 各自的位置一次说明白

→ 按 → 键 / 点右下箭头 / 手指左滑 翻页

问题 · The Problem

一张图,喂不饱所有人

同一个系统,不同人关心完全不同的侧面:程序员想知道代码怎么分包,运维想知道跑在哪台机器,用户只关心"它到底给我带来什么"。硬要用一张图全部说清,结果是谁都看不懂。

4+1 的解法

Philippe Kruchten 的主意很简单:别强求一张图。把架构拆成 4 个视图,各自只回答一类人的问题;再用 1 组场景(story) 把它们串起来、互相验证。这就是 4+1 View Model

放到 xskill

下面每一页,上半讲这个视图是什么、解决谁的问题;下半用 xskill 的真实结构做例子。xskill 我们按三个模块来讲 👇

① 轨迹的入口与索引 ② 轨迹生成 Skill ③ 轨迹的评价与推送

全景 · The Big Picture

4+1 = 4 个视图 + 1 个场景

四角是四个视图,中间的「场景」是把它们黏在一起的那个 +1。

逻辑视图 Logical

给分析师 / 看功能

系统由什么构成:实体、职责、关系 → 用类图

进程视图 Process

给集成者 / 看运行

运行时怎么动:时间顺序、并发 → 用时序图

+1 场景视图 Scenarios

给所有人 · 这就是 story

几条关键用户故事,驱动设计、最后验收,把上下左右四个视图串起来

开发视图 Development

给程序员 / 看代码

代码怎么组织:包、模块、依赖 → 用包图

物理视图 Physical

给运维 / 看部署

软件跑在哪:进程、机器、网络 → 用部署图

案例床 · Our Running Example

xskill 三个模块,一句话各是什么

xskill 做的事:把 coding agent 的轨迹(trajectory)自动炼成可复用的技能(skill)——"一个人解决,人人复用"。这条流水线分三段:

模块 ①

轨迹的入口与索引

从各家 agent(Claude Code / Codex / Trae …)捕获对话轨迹,桥接成统一 traj_*.md,再切成原子任务、建索引。让原料进得来、找得到。

模块 ②

轨迹生成 Skill

把同类原子任务聚类、归并,蒸馏出一个 baby skill(SKILL.md + git 分支)。把零散经验炼成成型技能。

模块 ③

轨迹的评价与推送

新技能先走灰度(canary),收 ux_score 决定 promote / reject;通过后安装/推送给所有人与所有生态。让好技能经得起检验、传得出去。

视图一 · 那个「+1」

场景视图 Scenarios = story

概念

场景视图就是几条关键用户故事 / 用例。它不是"第五个并列视图",而是把其余四个视图串起来、驱动设计、最后用来验收的那根线。Kruchten 把它画在正中间,是有意的——story 是圆心

xskill 的主 story
  • Bob 的 agent 解决了一个棘手问题 → 轨迹被捕获
  • 系统把它蒸馏成一个 skill
  • skill 先灰度、拿到好评分 → promote
  • Alice 第二天遇到同类问题,agent 已经会了

这条 story 同时说明了价值、又规定了四个视图必须支撑什么。你写的 story,就是在写这个 +1。

视图二 · 静态结构

逻辑视图 Logical → 类图

概念

逻辑视图回答"系统由什么构成":有哪些核心实体、各自职责、谁拥有谁、谁继承谁。它的主力表达工具就是 类图(class diagram)——一张静态快照,不关心时间。

xskill 类图(跨三模块)
classDiagram
    class Ingester {
        +scan_and_bridge()
    }
    Ingester <|-- TraeIngester
    Ingester <|-- JsonlIngester
    Ingester <|-- SqliteIngester
    class Trajectory {
        +traj_id
        +session_id
        +markdown
    }
    class DirectoryWatcher {
        +scan_once()
        +state_machine
    }
    class AtomTask {
        +atom_id
    }
    class Skill {
        +name
        +SKILL_md
        +branch_baby_main
    }
    class Canary {
        +ux_score
        +promote_or_reject()
    }
    Ingester --> Trajectory : produces
    DirectoryWatcher --> Trajectory : consumes
    DirectoryWatcher --> AtomTask : splits
    AtomTask --> Skill : cluster_merge
    Skill --> Canary : canary
        

抽象 Ingester 派生出 Trae/Jsonl/Sqlite 三种——新增一家 agent 只是多一个子类。

视图三 · 动态运行

进程视图 Process → 时序图

概念

进程视图回答"运行时怎么动":消息按什么时间顺序流转、谁和谁并发、轮询与状态怎么推进。主力工具是 时序图(sequence diagram)。它也常用来把某一条 story 展开成具体运行轨迹——所以时序图是 story 通往进程视图的桥

xskill 时序图(主 story 的运行展开)
sequenceDiagram
    autonumber
    participant Bob as Agent(Bob)
    participant I as Ingester ①
    participant W as Watcher ②
    participant S as Skill 仓库 ②
    participant C as Canary ③
    participant Al as 同事 Alice ③
    Bob->>I: 产生轨迹 (vscdb/jsonl)
    I->>W: 桥接 traj_*.md (discovered)
    W->>W: split → index → cluster
    W->>S: 归并出 baby skill
    S->>C: 达阈值 → 进 staging 灰度
    C->>Al: 按概率分流试用
    Al-->>C: 回传 ux_score
    C->>S: 收齐样本 → promote 到 main
    S->>Al: install 到 ~/.claude/skills 等
        

同一条 story(第 5 页),在这里变成了带时间轴、可落地的交互序列。

视图四 · 代码组织

开发视图 Development → 包结构

概念

开发视图回答"代码怎么组织":源码分成哪些包/模块、谁依赖谁。它服务的是程序员,关心的是可维护、可分工。三个功能模块会落到具体的包上。

xskill 源码包 ↔ 三模块
# src/xskill/ ├─ ecosystems/ # ① 各家 agent 捕获+安装(trae/jsonl/sqlite…) ├─ pipeline/ # ①②③ 流水线核心:watcher / atom / registry │ ├─ runner.py # 状态机 discovered→…→done │ └─ atom.py # 原子任务存储 ├─ skill/ # ② 技能本体:SKILL.md / candidates / git 分支 ├─ team/ # ③ C/S 模式:client collector + server 推送 └─ api/ # 对外服务 + 控制台

ecosystems/pipeline · skill · team + canary。视图四看的是"文件夹",不是"运行"。

视图五 · 部署落地

物理视图 Physical → 部署图

概念

物理视图回答"软件跑在哪":进程怎么分布到机器、走什么网络、装到哪个目录。它服务运维。同一套逻辑,可以有不同的物理部署。

🏝️ Standalone 单机

一个 t2s serve 进程,自己捕获①、自己蒸馏②、自己灰度③,skill 直接 install 到本机生态目录 ~/.claude/skills / ~/.trae-cn/skills

🌊 Team(C/S)

client 的 collector 捕获并上传轨迹 → server 集中蒸馏+灰度 → 再下发给组织里每个 client。前面有 nginx,跑在 Docker 里。同样的三模块,换了张部署图。

收束 · 你最想要的那一页

类图 / 时序图 / story 与 4+1 的关系

它们不是三个并列的"图",而是各自挂在不同视图上的表达工具:

📖
story(用户故事/用例)= 那个 +1(场景视图)
不是第五个并列视图,而是圆心:驱动设计、串起其余四视图、最后用来验收。你写的 story 就是它。
🧩
类图 = 逻辑视图的表达
画"系统由什么构成"的静态结构(实体/继承/拥有)。
⏱️
时序图 = 进程视图的表达,也用于展开 story
画"运行时按时间怎么动"的动态序列;把一条 story 落成可执行的交互——所以它是 story ↔ 进程视图 的桥。

🥥 记忆法:story 给出题目,类图说明"长什么样",时序图说明"怎么跑起来"。

回顾 · One-liners

五个视图,各回答一句话

视图回答的问题工具xskill 例子
场景 +1这东西带来什么?story / 用例Bob 解决 → Alice 复用
逻辑由什么构成?类图Ingester · Skill · Canary
进程运行时怎么动?时序图discovered→…→done→灰度
开发代码怎么组织?包图ecosystems/pipeline/skill/team
物理跑在哪?部署图standalone vs team(C/S)

写 xskill 的 story 时:先用场景(+1)把主线讲清,再分别用类图(逻辑)时序图(进程)把"长什么样 / 怎么跑"补齐,开发与物理视图交代给程序员和运维。🥥

↓ 继续翻 · 深入「模块① 轨迹的入口与索引」的 4+1 →

深入 · Deep Dive

模块 ①
轨迹的入口与索引

把「① 这一段」单独拉出来,完整走一遍它的 4+1

模块边界

负责从「原料产生」到「原料就绪」:从各家 agent 落盘的轨迹开始,到每条被切成原子任务、建好索引为止。状态机上 owns discovered → indexed;之后的 cluster→done 交给模块②。

agent 产生轨迹 → traj_*.md → [indexed] → ②cluster…

① · 场景视图 +1

驱动这个模块的 story

先讲清「①要支撑哪几条故事」,其余四视图都为它服务。

S1 多平台接入

Claude Code / Codex / Trae / OpenCode…… 不管哪家,Bob 干完活的轨迹都要能被捕获,统一成一种格式

S2 自动就绪

轨迹进来后无需人工:自动切原子任务、建索引,静静等蒸馏。Bob 不做任何额外操作。

S3 不重不漏

daemon 重启、轮询重跑,同一条轨迹不被重复摄入;新轨迹要被及时发现。

S4 脏数据挡门外

空的/损坏的/不成功的轨迹被识别并 filtered,不污染后面的技能。

① · 逻辑视图 Logical

类图:① 由什么构成

两层:入口层 ecosystems/(每家 agent 一个适配器) + 索引层 pipeline/。两层通过「桥接目录 + 统一格式」解耦。

classDiagram
    class Ingester {
        +scan_and_bridge()
        +start()
        +stop()
    }
    Ingester <|-- JsonlIngester
    Ingester <|-- SqliteIngester
    Ingester <|-- CCSessionIngester
    Ingester <|-- TraeIngester
    class Trajectory {
        +traj_id
        +session_id
        +markdown
    }
    class DirectoryWatcher {
        +scan_once()
    }
    class AtomTask {
        +atom_id
    }
    class AtomTaskStore {
        +index_pkl
    }
    class Registry {
        +trajectories_table
    }
    Ingester --> Trajectory : bridge
    DirectoryWatcher --> Trajectory : discover
    DirectoryWatcher --> AtomTask : split
    AtomTask --> AtomTaskStore : store
    DirectoryWatcher --> Registry : status
    
① · 进程视图 Process

时序图:一条轨迹的旅程

两个独立循环:Ingester 各自轮询桥接 / Watcher 线程池推进 split+index。

sequenceDiagram
    autonumber
    participant AG as Agent 本地目录
    participant IN as Ingester
    participant BR as 桥接目录
    participant W as DirectoryWatcher
    participant R as Registry / Store
    AG->>IN: 落盘原生轨迹
    IN->>IN: dedup(seen) 去重
    IN->>BR: 写 traj md + json(meta)
    W->>BR: _scan_once 轮询发现
    W->>R: discover → discovered
    W->>W: split → 拆 AtomTask
    W->>R: split_done
    W->>W: embed → index.pkl
    W->>R: indexed(交给模块②)
    
① · 进程视图 Process

状态机:registry 里的 status 流转

flowchart LR
    D[discovered] -->|split| S[splitting] --> SD[split_done] -->|embed| I[indexed]
    D -->|校验不合格| F[filtered]
    S -->|异常| E[error] -->|retry 带计数| D
    I --> C["② clustering …"]
    style I fill:#E2F1F2,stroke:#0E7C86
    style C fill:#FCE5DC,stroke:#E0613B
    
健壮性

去重(S3):内存 seen + 重启 _scan_seen_sessions 从落盘 .json 重建。僵尸回滚:重启时 in-flight 中间态(splitting/clustering)无对应 future → 退回前一阶段重调度,避免卡死。脏数据(S4):validate_trajectory_source 不合格 → filtered。

① · 开发视图 Development

代码怎么组织 · 程序员在哪改

# src/xskill/ ├─ ecosystems/ # 入口层:每家 agent 一个文件 │ ├─ _shared.py # Ingester 基类 / EcosystemSpec / detect_known_ecosystems │ │ # / _scan_seen_sessions(去重重建) │ ├─ claude_code.py # CCSessionIngester │ ├─ codex.py opencode.py ngagent.py # Jsonl / Sqlite spec │ └─ trae.py # TraeIngester(IDE vscdb + CLI json 混合) └─ pipeline/ # 索引层(前段属①) ├─ registry.py # trajectories/watch_dirs 表:discover_trajectories ├─ trajectory.py # 统一格式解析 / validate_trajectory_source ├─ atom.py # AtomTask / AtomTaskStore / index.pkl └─ runner.py # DirectoryWatcher:_scan_dir/_do_split/_do_atom_index

依赖方向:pipeline 不依赖具体 agent;ecosystems 只负责「产出统一 traj_*.md」——这正是类图里那条 produces/consumes 边界。

① · 物理视图 Physical

数据从哪进 · 落在哪 · 跑在哪

📥 输入(只读捕获)

Claude Code ~/.claude/projects/**.jsonl
Codex ~/.codex/sessions/
OpenCode/ngagent 本地 SQLite
Trae state.vscdb + CLI trajectory_*.json

🗂️ 中间产物

桥接 ~/.xskill/<eco>_sessions/traj_*.md
原子任务 <traj_id>/tasks/
向量索引 index.pkl(落 watch_dir)
真相源:registry 的 SQLite

🏝️ Standalone

t2s serve 一个进程,本机捕获 + 本机索引,全在一台机器。

🌊 Team(C/S)

client 的 collector 起 ingester,把桥接出的 traj 按 traj_id→sha256 上传 server;索引/蒸馏在 server 侧。同一套入口逻辑,换了部署位置。

① · 小结 & 下一步

模块① 一句话回顾

视图① 里是什么
场景 +1S1 多平台接入 · S2 自动就绪 · S3 不重不漏 · S4 脏数据挡门外
逻辑Ingester 家族 → Trajectory → Watcher → AtomTask/Store → Registry
进程双循环:轮询桥接 / 状态机 discovered→splitting→split_done→indexed
开发ecosystems/(入口) + pipeline/{registry,trajectory,atom,runner}(索引)
物理读各 agent 本地目录 → 桥接目录 → index.pkl;standalone / 上传 server

模块① 完。模块②(生成 Skill)模块③(评价与推送) 将照同一结构展开。🥥

← 回头再看