-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathframework-evolution.html
More file actions
220 lines (205 loc) · 15.7 KB
/
Copy pathframework-evolution.html
File metadata and controls
220 lines (205 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OpenProgram · 框架演进</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@600;900&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
:root{
--ink:#0a1322; --panel:#101d31; --panel2:#0d1828;
--line:rgba(154,178,204,.22); --line-strong:rgba(154,178,204,.45);
--text:#d9e5f1; --muted:#8aa0b8; --faint:#5f7a90;
--cyan:#56d9e9; --violet:#b79bf5; --amber:#f2b544; --red:#f07a6a; --green:#6fd98f;
--serif:"Noto Serif SC","Songti SC",serif;
--sans:"PingFang SC","Hiragino Sans GB",sans-serif;
--mono:"JetBrains Mono","SF Mono",Menlo,monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
background:var(--ink); color:var(--text); font-family:var(--sans);
font-size:15.5px; line-height:1.85; letter-spacing:.01em;
background-image:
radial-gradient(1200px 600px at 80% -10%, rgba(111,217,143,.05), transparent 60%),
radial-gradient(900px 500px at -10% 30%, rgba(240,122,106,.04), transparent 60%),
repeating-linear-gradient(0deg, rgba(154,178,204,.045) 0 1px, transparent 1px 56px),
repeating-linear-gradient(90deg, rgba(154,178,204,.045) 0 1px, transparent 1px 56px);
}
nav{
position:fixed; left:0; top:0; bottom:0; width:200px; padding:42px 0 0 30px;
border-right:1px solid var(--line); background:rgba(10,19,34,.82); backdrop-filter:blur(6px); z-index:50;
}
nav .brand{font-family:var(--mono); font-size:11px; letter-spacing:.22em; color:var(--green); margin-bottom:6px}
nav h1{font-family:var(--serif); font-weight:900; font-size:21px; letter-spacing:.06em; margin-bottom:34px}
nav a{display:block; padding:7px 0 7px 14px; margin-left:-16px; color:var(--muted); text-decoration:none; font-size:13px; border-left:2px solid transparent; transition:all .18s}
nav a:hover{color:var(--text); border-left-color:var(--green)}
nav a .no{font-family:var(--mono); font-size:10.5px; color:var(--faint); margin-right:8px}
nav .foot{position:absolute; bottom:26px; left:30px; right:20px; font-size:11px; color:var(--faint); font-family:var(--mono); line-height:1.8}
nav .foot a{display:inline; padding:0; margin:0; border:none; color:var(--cyan)}
main{margin-left:200px; padding:0 56px 120px}
.wrap{max-width:1080px; margin:0 auto}
section{padding-top:84px}
.kicker{font-family:var(--mono); font-size:11px; letter-spacing:.28em; color:var(--green); margin-bottom:10px}
.kicker.r{color:var(--red)} .kicker.c{color:var(--cyan)}
h2{font-family:var(--serif); font-weight:900; font-size:30px; letter-spacing:.04em; margin-bottom:18px}
p.lead{color:var(--muted); max-width:66ch}
.hl{color:var(--text); font-weight:600}
code{font-family:var(--mono); font-size:.88em; color:var(--cyan)}
.figure{margin-top:26px; border:1px solid var(--line); border-radius:14px; overflow:hidden; background:var(--panel2)}
.figure img{display:block; width:100%}
header.hero{padding:110px 0 0; opacity:0; transform:translateY(18px)}
header .sys{font-family:var(--mono); font-size:12px; letter-spacing:.3em; color:var(--muted)}
header h2.big{font-size:50px; line-height:1.28; margin:14px 0 22px}
header h2.big em{font-style:normal; color:var(--green)}
header h2.big s{text-decoration-color:var(--red); color:var(--muted)}
header .sub{font-size:17px; color:var(--muted); max-width:64ch}
.shift{display:grid; grid-template-columns:repeat(3,1fr); gap:18px; margin-top:42px}
.shift .s{padding:20px 24px; border:1px solid var(--line); border-radius:12px; background:linear-gradient(180deg, rgba(16,29,49,.9), rgba(13,24,40,.9))}
.shift .tag{font-family:var(--mono); font-size:12px; font-weight:600; margin-bottom:8px; display:block}
.shift .up .tag{color:var(--green)} .shift .down .tag{color:var(--amber)} .shift .new .tag{color:var(--cyan)}
.shift b{color:var(--text); font-size:15px}
.shift p{font-size:13px; color:var(--muted); margin-top:6px}
table{width:100%; border-collapse:collapse; margin-top:28px; font-size:13.5px}
th{font-family:var(--mono); font-size:11px; letter-spacing:.16em; color:var(--faint); text-align:left; font-weight:400; padding:10px 14px; border-bottom:1px solid var(--line-strong)}
td{padding:11px 14px; border-bottom:1px solid var(--line); color:var(--muted); vertical-align:top}
td b{color:var(--text); font-weight:600}
tr:hover td{background:rgba(111,217,143,.025)}
.chg{display:inline-block; font-family:var(--mono); font-size:11px; border-radius:5px; padding:2px 10px; white-space:nowrap}
.chg.xs{color:var(--green); border:1px solid rgba(111,217,143,.5); background:rgba(111,217,143,.07)}
.chg.s{color:var(--cyan); border:1px solid rgba(86,217,233,.5); background:rgba(86,217,233,.07)}
.chg.m{color:var(--amber); border:1px solid rgba(242,181,68,.5); background:rgba(242,181,68,.07)}
.chg.core{color:var(--violet); border:1px solid rgba(183,155,245,.5); background:rgba(183,155,245,.07)}
td .bad{color:var(--red)} td .good{color:var(--green)}
.steps{margin-top:30px; position:relative; padding-left:34px}
.steps::before{content:""; position:absolute; left:11px; top:8px; bottom:8px; width:2px; background:repeating-linear-gradient(180deg, rgba(111,217,143,.5) 0 6px, transparent 6px 12px)}
.step{position:relative; padding:0 0 30px 18px}
.step::before{content:""; position:absolute; left:-31px; top:4px; width:18px; height:18px; border-radius:50%; background:var(--ink); border:2px solid var(--green)}
.step.warn::before{border-color:var(--amber)}
.step .t{display:flex; align-items:baseline; gap:12px}
.step b{font-size:15.5px; color:var(--text)}
.step .badge{font-family:var(--mono); font-size:10.5px; padding:2px 9px; border-radius:99px}
.step .add{color:var(--green); border:1px solid rgba(111,217,143,.5)}
.step .touch{color:var(--amber); border:1px solid rgba(242,181,68,.5)}
.step p{font-size:13.5px; color:var(--muted); margin-top:4px; max-width:70ch}
.step .ver{font-size:12.5px; color:var(--faint); margin-top:4px}
.step .ver b{color:var(--muted); font-weight:600; font-size:12.5px}
.keep{display:grid; grid-template-columns:1fr 1fr 1fr; gap:14px; margin-top:28px}
.keep span{font-size:13px; color:var(--muted); border:1px dashed var(--line-strong); border-radius:9px; padding:12px 16px; background:rgba(16,29,49,.5)}
.keep span b{color:var(--text); display:block; font-size:13.5px; margin-bottom:2px}
.note{margin-top:26px; padding:14px 18px; border:1px dashed var(--line-strong); border-radius:8px; color:var(--muted); font-size:13.5px; max-width:74ch; background:rgba(16,29,49,.5)}
.note a{color:var(--cyan); text-decoration:none}
footer{margin-top:90px; padding-top:26px; border-top:1px solid var(--line); display:flex; justify-content:space-between; font-size:12.5px; color:var(--faint); font-family:var(--mono)}
footer a{color:var(--cyan); text-decoration:none}
@media (max-width:1100px){ nav{display:none} main{margin-left:0} .shift,.keep{grid-template-columns:1fr} header h2.big{font-size:36px} }
</style>
</head>
<body>
<nav>
<div class="brand">OPENPROGRAM</div>
<h1>框架演进</h1>
<a href="#asis"><span class="no">01</span>现状</a>
<a href="#tobe"><span class="no">02</span>目标</a>
<a href="#delta"><span class="no">03</span>各子系统怎么变</a>
<a href="#migrate"><span class="no">04</span>迁移路径</a>
<a href="#keep"><span class="no">05</span>刻意不动的</a>
<div class="foot">design doc · v1<br>姊妹篇 → <a href="event-layer.html">事件层</a></div>
</nav>
<main><div class="wrap">
<header class="hero">
<div class="sys">DESIGN · FRAMEWORK EVOLUTION</div>
<h2 class="big">从「<s>各自为政</s>」<br>到「<em>一条总线当中枢</em>」。</h2>
<p class="sub">有了事件层之后,框架怎么调整:现状什么样、目标什么样、每个子系统怎么变、按什么顺序迁过去。<span class="hl">不大爆炸重写——新旧并行,逐源切换。</span></p>
<div class="shift">
<div class="s up"><span class="tag">↑ 上位</span><b>EventBus</b><p>从闲置死码升为唯一中枢:进程级单例、统一 Event、按类型订阅</p></div>
<div class="s down"><span class="tag">↓ 降级</span><b>webui server</b><p>从被迫的信号中枢降为普通订阅者:订阅总线 → 转发前端 WS</p></div>
<div class="s new"><span class="tag">+ 进场</span><b>proactive 与未来功能</b><p>只是又一个订阅者。一行 subscribe(types=…) 接入,审计 / 统计 / 回放同理</p></div>
</div>
</header>
<section id="asis">
<div class="kicker r">01 · AS-IS</div>
<h2>现状:webui 被迫当信号中枢</h2>
<p class="lead">几乎所有信号存在的目的都是"让前端看到",所以全部<span class="hl">硬连到 webui 的 <code>_broadcast</code></span>——五种线型就是五套互不相同的机制。auth 做对了但没人听;memory 和文件改动干脆无信号;hooks 能看不能拦;EventBus 在角落落灰。想加一个新消费者,得分别对接五六套机制,有的时机根本没信号可接。</p>
<div class="figure"><img src="diagrams/framework-asis.svg" alt="现状架构图"></div>
</section>
<section id="tobe">
<div class="kicker">02 · TO-BE</div>
<h2>目标:一条总线当中枢</h2>
<p class="lead">组件位置与现状图<span class="hl">一一对应</span>,变的只是连线和角色。所有源统一 <code>emit(Event)</code>,所有消费者统一 <code>subscribe(types=…)</code>;全框架唯一的拦截位是 <code>tool.before</code> 同步问询点(复用现有批准机制,对 subagent 生效),其余一切都是异步观察。</p>
<div class="figure"><img src="diagrams/framework-tobe.svg" alt="目标架构图"></div>
</section>
<section id="delta">
<div class="kicker c">03 · DELTA</div>
<h2>各子系统怎么变</h2>
<table>
<tr><th>子系统</th><th>现在</th><th>将来</th><th>改动量</th></tr>
<tr><td><b>EventBus</b></td><td><span class="bad">闲置,零订阅</span></td><td>升级(类型订阅 + 进程级单例)并启用</td><td><span class="chg core">核心</span></td></tr>
<tr><td><b>agent loop</b></td><td>AgentEvent 内部流;<span class="bad">hooks 返回值被丢</span></td><td>关键节点同时 emit 总线;tool.before 加同步问询<span class="good">(可拦了)</span></td><td><span class="chg s">小</span></td></tr>
<tr><td><b>webui server</b></td><td><span class="bad">被所有人直连,事实上的中枢</span></td><td>订阅总线转发前端;旧直连逐源退役</td><td><span class="chg m">中</span></td></tr>
<tr><td><b>task runner</b></td><td>直连 _broadcast job_status</td><td>emit <code>subagent.*</code>,广播由 webui 订阅转发</td><td><span class="chg s">小</span></td></tr>
<tr><td><b>dispatcher</b></td><td>on_event 回调链直达 webui</td><td>保留(过渡期),另 emit <code>user.prompt_submitted</code> 等</td><td><span class="chg s">小</span></td></tr>
<tr><td><b>channels</b></td><td>broadcast_channel_turn 直连</td><td>emit <code>channel.*</code>;直连保留过渡</td><td><span class="chg s">小</span></td></tr>
<tr><td><b>auth</b></td><td>自己的 _emit/subscribe<span class="good">(已经做对了)</span></td><td><b>自身不动</b>,一段桥接翻译进总线</td><td><span class="chg xs">极小</span></td></tr>
<tr><td><b>context</b></td><td>on_event 回调</td><td>回调里顺手 emit <code>context.*</code></td><td><span class="chg xs">极小</span></td></tr>
<tr><td><b>memory</b></td><td><span class="bad">定时 poll,无信号</span></td><td>处理起止 emit,"定时"包装成事件</td><td><span class="chg xs">极小</span></td></tr>
<tr><td><b>文件改动</b></td><td><span class="bad">默默备份,无信号</span></td><td>checkpoint_before_edit 处 emit <code>file.changed</code></td><td><span class="chg xs">极小</span></td></tr>
<tr><td><b>plugin hooks</b></td><td>observe-only,6 个 fire 点</td><td>内部统一走总线;hooks 保留为插件 API 或逐步退役</td><td><span class="chg m">决策点</span></td></tr>
</table>
</section>
<section id="migrate">
<div class="kicker">04 · MIGRATION</div>
<h2>怎么迁:新旧并行,逐源切换</h2>
<p class="lead">前三步<span class="hl">全是加法</span>——总线并行于现有路径跑,零行为变化,随时可停可退。只有第四步动旧路径,且有影子比对兜底。</p>
<div class="steps">
<div class="step">
<div class="t"><b>总线启用 + A 类源接入</b><span class="badge add">纯加法</span><span class="badge add">✅ 已落地 2026-06-13</span></div>
<p>升级 EventBus(类型订阅 + 单例),agent loop / task runner / dispatcher 在关键节点并行 emit。旧路径原样跑。</p>
<div class="ver"><b>验收</b>:订阅总线,打出一个真实 turn 的完整事件序列</div>
</div>
<div class="step">
<div class="t"><b>补两个洞</b><span class="badge add">纯加法</span><span class="badge add">✅ 已落地 2026-06-13</span></div>
<p><code>file.changed</code> 事件(挂 checkpoint_before_edit);<code>tool.before</code> 同步问询点(复用 _approval,对 subagent 生效)。</p>
<div class="ver"><b>验收</b>:改文件能收到事件;测试订阅者能真的拦下指定命令</div>
</div>
<div class="step">
<div class="t"><b>B 类源桥接</b><span class="badge add">纯加法</span><span class="badge add">✅ 已落地 2026-06-13</span></div>
<p>auth / context / channels / memory 各一段单向桥,翻译成统一 Event 进总线。源头逻辑不动。</p>
<div class="ver"><b>验收</b>:触发一次凭据限流 / 一次压缩,从同一个总线收到</div>
</div>
<div class="step warn">
<div class="t"><b>webui 切换为订阅者</b><span class="badge touch">动旧路</span><span class="badge add">✅ 已落地 2026-06-13</span></div>
<p>先影子模式:总线转发与旧直连并行,比对输出一致;确认后逐源切断旧直连。对前端透明。</p>
<div class="ver"><b>验收</b>:影子比对零差异后切换,前端行为不变</div>
</div>
<div class="step">
<div class="t"><b>新消费者进场</b><span class="badge add">纯加法</span></div>
<p>proactive 规则层接入——从此任何新功能只面对总线,一行 subscribe 接入。</p>
<div class="ver"><b>验收</b>:proactive 不碰任何子系统内部,仅靠订阅工作</div>
</div>
</div>
</section>
<section id="keep">
<div class="kicker c">05 · UNCHANGED</div>
<h2>刻意不动的东西</h2>
<p class="lead">动什么和不动什么一样重要。以下<span class="hl">不在</span>本次演进范围:</p>
<div class="keep">
<span><b>dispatcher 七阶段编排</b>process_user_turn 对外签名不变</span>
<span><b>session git DAG 存储</b>contextgit 与节点模型不动</span>
<span><b>JobRunner 线程池模型</b>subagent 执行方式不变</span>
<span><b>ApprovalRegistry</b>被复用,不被改写</span>
<span><b>AuthStore 自身</b>桥接,不动它</span>
<span><b>前端 WS 协议</b>webui 切换后对前端透明</span>
</div>
<div class="note">文字源:<a href="framework-evolution.html">framework-evolution.md</a> · 事件层本体设计:<a href="event-layer.html">event-layer.html</a> · 接线点(file:line)与分步验证:docs/plans/proactive-implementation.md</div>
</section>
<footer>
<span>OpenProgram · docs/design/proactive/framework-evolution</span>
<span><a href="#asis">现状</a> · <a href="#tobe">目标</a> · <a href="event-layer.html">事件层 →</a></span>
</footer>
</div></main>
<script>
requestAnimationFrame(()=>{ const h=document.querySelector('header.hero'); h.style.transition='opacity .8s ease, transform .8s ease'; h.style.opacity=1; h.style.transform='none'; });
</script>
</body>
</html>