@turmind/halo 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/298-4fac7d44a7949eec.js +1 -0
- package/admin-out/_next/static/chunks/{503-7c6d9935df6e0cea.js → 307-d7a99f9a4a436ec3.js} +1 -1
- package/admin-out/_next/static/chunks/app/page-b12a2e8e48fe4ea0.js +1 -0
- package/admin-out/_next/static/css/{8fce4e1a54dac12a.css → 372e3dbd4a0d1c25.css} +1 -1
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +4 -4
- package/bundled-docs/dev/acp-adapter.md +5 -4
- package/bundled-docs/dev/desktop-packaging.md +94 -8
- package/bundled-docs/guide/getting-started.md +3 -2
- package/bundled-docs/requirements/explorer.md +11 -0
- package/dist/index.js +13373 -13246
- package/package.json +1 -1
- package/templates/agents/executor/agent.yaml +2 -2
- package/templates/models/aws-bedrock-claude-invoke.yaml +26 -0
- package/templates/skills/acp/SKILL.md +8 -1
- package/templates/skills/acp/templates/SKILL.md.tmpl +2 -1
- package/templates/skills/acp/templates/ask.py +2 -1
- package/templates/skills/acp/templates/config.yaml.tmpl +5 -0
- package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +0 -1
- package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +0 -1
- package/bundled-docs/plans/cron-tasks.md +0 -362
- package/bundled-docs/plans/design-suggestions.md +0 -203
- package/bundled-docs/plans/issue-session-deep-nesting.md +0 -208
- package/bundled-docs/plans/launch-plan.md +0 -111
- package/bundled-docs/plans/md-and-agent-refactor.md +0 -197
- package/bundled-docs/plans/remote-mac-worker.md +0 -196
- package/bundled-docs/plans/roadmap.md +0 -40
- package/bundled-docs/plans/self-evolution.md +0 -1379
- package/bundled-docs/plans/todo.md +0 -32
- package/bundled-docs/plans/weixin-channel.md +0 -255
- package/bundled-docs/test/docs-seeding.md +0 -101
- package/bundled-docs/test/session.md +0 -374
- /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_ssgManifest.js +0 -0
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# Deep Session Nesting — Investigation & Fixes
|
|
2
|
-
|
|
3
|
-
## Current state of session tools
|
|
4
|
-
|
|
5
|
-
### Tool list
|
|
6
|
-
|
|
7
|
-
Agent-accessible session tools are defined in `session-manager.ts:437-673`, 9 in total:
|
|
8
|
-
|
|
9
|
-
| Tool | Purpose |
|
|
10
|
-
|---|---|
|
|
11
|
-
| `start_session` | Create a child session, run asynchronously, auto-report on completion |
|
|
12
|
-
| `query_session` | Send a message to an existing session — run immediately if idle, queue if busy |
|
|
13
|
-
| `stop_session` | Stop a session, set `stoppedAt`; recoverable via `query_session` or a user message |
|
|
14
|
-
| `archive_session` | Cascade soft-delete a subtree, set `archivedAt`; not recoverable |
|
|
15
|
-
| `interrupt_session` | Interrupt and immediately re-run with a new message |
|
|
16
|
-
| `session_list` | List the current session's children and their status |
|
|
17
|
-
| `get_session_output` | Read a child session's output text |
|
|
18
|
-
| `list_agents` | List delegatable agents |
|
|
19
|
-
| `query_agent` | Inspect an agent's config and AGENT.md |
|
|
20
|
-
|
|
21
|
-
### start_session flow
|
|
22
|
-
|
|
23
|
-
`session-manager.ts:440-485`
|
|
24
|
-
|
|
25
|
-
1. Validate `agent_id` exists and isn't default
|
|
26
|
-
2. `createSession(agentId, parentId=currentSessionId)` → write SQLite + memory map + emit `context`
|
|
27
|
-
3. Emit `agent_start` (frontend shows sub-agent starting)
|
|
28
|
-
4. **Async** `runSession(childId, message)` — the tool returns immediately without blocking the parent turn
|
|
29
|
-
5. Attach a one-shot `.then()`: when the child session's first runSession resolves:
|
|
30
|
-
- emit `agent_done`
|
|
31
|
-
- `querySession(parentId, childId, result.slice(0, 2000))` forwards the result to the parent
|
|
32
|
-
|
|
33
|
-
### query_session flow
|
|
34
|
-
|
|
35
|
-
`session-manager.ts:497-529` (tool) + `1401-1452` (core method)
|
|
36
|
-
|
|
37
|
-
1. Emit `agent_start` (frontend initialises the sub-session log)
|
|
38
|
-
2. `sm.querySession(targetId, sourceId, message, onComplete)`:
|
|
39
|
-
- target idle → `runSession` runs immediately
|
|
40
|
-
- target busy → message goes to `target.messageQueue`, drained by `drainQueue`
|
|
41
|
-
3. `onComplete`: after the target finishes → emit `agent_done` + `querySession` back to source
|
|
42
|
-
|
|
43
|
-
### stop_session flow
|
|
44
|
-
|
|
45
|
-
`session-manager.ts:571-588` (tool) + `1377-1399` (core method)
|
|
46
|
-
|
|
47
|
-
1. `abortController.abort('stop')` cancels the LLM call
|
|
48
|
-
2. Await promise completion
|
|
49
|
-
3. Clear `messageQueue` + `pendingUserMessages`
|
|
50
|
-
4. `repairConversationMessages`
|
|
51
|
-
5. `releaseSession` (persist + drop from memory)
|
|
52
|
-
6. DB write `stoppedAt = Date.now()`
|
|
53
|
-
|
|
54
|
-
Stop is recoverable: `query_session` or a user message clears `stoppedAt` and returns the session to idle. `listSessions` shows `status: 'stopped'`.
|
|
55
|
-
|
|
56
|
-
### archive_session flow
|
|
57
|
-
|
|
58
|
-
`session-manager.ts` (tool) + core method `archiveSessionTree`
|
|
59
|
-
|
|
60
|
-
1. Recursively walk DB to find every descendant session
|
|
61
|
-
2. For each: DB write `archivedAt`; `archiveSession` (abort → await promise → clear queues → drop from map)
|
|
62
|
-
3. Return count archived
|
|
63
|
-
|
|
64
|
-
Difference vs stop: archive cascades the whole subtree + sets `archivedAt` (not recoverable); stop only stops one and sets `stoppedAt` (recoverable).
|
|
65
|
-
|
|
66
|
-
### Event routing
|
|
67
|
-
|
|
68
|
-
`session-manager.ts:214-222` `emitEvent` does three things:
|
|
69
|
-
1. Append to `.events.jsonl` (persistent audit log)
|
|
70
|
-
2. `reduceIntoUIState` (update in-memory UIState + save `.json`)
|
|
71
|
-
3. Push via `eventListeners` to the frontend (WS)
|
|
72
|
-
|
|
73
|
-
Event routing uses `findRootSessionId` (`session-manager.ts:178-196`): walk up the parentId chain to the root, then use the root's listener to send.
|
|
74
|
-
|
|
75
|
-
### Session lifecycle
|
|
76
|
-
|
|
77
|
-
- **idle**: `promise === null`, waiting for input
|
|
78
|
-
- **running**: `promise !== null`, inside an LLM or tool call
|
|
79
|
-
- **stopped**: `stoppedAt` is set, recoverable via `query_session` or a user message
|
|
80
|
-
- **archived**: `archivedAt` is set, not recoverable
|
|
81
|
-
|
|
82
|
-
Transitions:
|
|
83
|
-
- idle → running: `sendUserMessage` / `runSession` / `querySession`
|
|
84
|
-
- running → idle: turn finishes + empty queue → finally sets `promise = null`
|
|
85
|
-
- idle → stopped: `stopSession` or `tryReportToParent` (auto-report marks done)
|
|
86
|
-
- stopped → idle: `querySession` / `sendUserMessage` clears `stoppedAt`
|
|
87
|
-
- any → archived: `archiveSessionTree`
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Deep nesting problems (16-layer scenario)
|
|
92
|
-
|
|
93
|
-
### Problem 1 (fatal): auto-report chain breaks beyond 2 levels
|
|
94
|
-
|
|
95
|
-
`start_session`'s `.then()` is bound to the **first** runSession promise of the child — it's one-shot.
|
|
96
|
-
|
|
97
|
-
Three-layer example:
|
|
98
|
-
```
|
|
99
|
-
L1 start_session → L2 .then() bound to L2's runSession promise
|
|
100
|
-
L2's agent decides to delegate → start_session → L3
|
|
101
|
-
L2's first turn completes → runSession resolves → L1's .then() fires
|
|
102
|
-
→ result = "I have delegated to L3" (garbage intermediate info)
|
|
103
|
-
→ querySession to L1 (user receives useless report)
|
|
104
|
-
|
|
105
|
-
L3 completes → auto-report to L2 → L2 handles result
|
|
106
|
-
→ but L2 → L1's .then() has already fired
|
|
107
|
-
→ L2's new result has nowhere to go
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Result: **the real work result only propagates one level; it never reaches the user.**
|
|
111
|
-
|
|
112
|
-
### Problem 2: sub-agents can't find their parent
|
|
113
|
-
|
|
114
|
-
The session tools don't expose the parent session ID. An agent can't proactively `query_session` its parent. System auto-report is the only upward channel — and it fires only once.
|
|
115
|
-
|
|
116
|
-
### Problem 3: `findRootSessionId` is O(depth) DB lookup
|
|
117
|
-
|
|
118
|
-
Every `emitEvent` walks the parentId chain up to the root. When intermediate layers have been released from memory by `releaseSession`, each level hits SQLite.
|
|
119
|
-
|
|
120
|
-
At layer 16, each event (stream, tool_call, tool_result, …) walks 16 levels. Dozens of events per turn × 16 = hundreds of DB lookups.
|
|
121
|
-
|
|
122
|
-
### Problem 4: stale session references
|
|
123
|
-
|
|
124
|
-
`start_session` at line 467 captures the parent session object:
|
|
125
|
-
```typescript
|
|
126
|
-
const session = sm.sessions.get(sessionId)
|
|
127
|
-
```
|
|
128
|
-
When the parent session's turn completes, `releaseSession → sessions.delete()`. The new object built by `ensureSession` is a different instance. `.then()`'s `session?.interruptedSessionIds` operates on the now-dead old object.
|
|
129
|
-
|
|
130
|
-
### Problem 5: no depth limit
|
|
131
|
-
|
|
132
|
-
No nesting check — pathological agent behaviour can nest infinitely until OOM or rate-limit blowout.
|
|
133
|
-
|
|
134
|
-
### Problem 6: concurrent rate limits
|
|
135
|
-
|
|
136
|
-
During the report phase, multiple layers wake up simultaneously via `querySession`, hit Bedrock concurrently, trigger ThrottlingException. Linear backoff + many sessions retrying simultaneously → cascading failure.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Fix plan (implemented)
|
|
141
|
-
|
|
142
|
-
### Hierarchical session_id + level-by-level bubbling
|
|
143
|
-
|
|
144
|
-
**session_id format**: hierarchy encoded with `>` separators. `createSession` appends a new segment for child ids: `{parentId}>{new_segment}`.
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
root: sid_abc
|
|
148
|
-
child: sid_abc>sid_def
|
|
149
|
-
grand: sid_abc>sid_def>sid_ghi
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
- Depth = `id.split('>').length`; exceeding `config.session.maxNestingDepth` (default 16) rejects `start_session`
|
|
153
|
-
- `findRootSessionId` = `id.split('>')[0]`, O(1)
|
|
154
|
-
|
|
155
|
-
**Auto-report mechanism**: `tryReportToParent(session)` method, called inside `runSession`'s finally after `promise = null`.
|
|
156
|
-
|
|
157
|
-
Report conditions:
|
|
158
|
-
1. `parentId !== null` (not root)
|
|
159
|
-
2. DB query `WHERE parent_id = session.id AND stopped_at IS NULL AND archived_at IS NULL` finds no active children
|
|
160
|
-
3. `!session.skipRelease` (not in an interrupt flow)
|
|
161
|
-
|
|
162
|
-
All three → set `stoppedAt` + emit `agent_done` + `querySession(parentId, id, result)`.
|
|
163
|
-
|
|
164
|
-
**Removed**:
|
|
165
|
-
- Manual `.then()` reports in `start_session` / `interrupt_session` / `query_session`
|
|
166
|
-
- The `interruptedSessionIds` field
|
|
167
|
-
- `onComplete` callbacks (`runSession`, `querySession`, `QueuedMessage`)
|
|
168
|
-
|
|
169
|
-
### Rate-limit retry (done)
|
|
170
|
-
|
|
171
|
-
Exponential backoff + jitter: 2s → 4s → 8s → 16s… capped at 60s. `maxRetries` defaults to 5.
|
|
172
|
-
|
|
173
|
-
### Duplicate child guard (done, mitigation)
|
|
174
|
-
|
|
175
|
-
In `start_session`'s callback, before `createSession` we query DB: if a non-stopped-non-archived child session with the same parentId + same agentId already exists, reject with a hint.
|
|
176
|
-
|
|
177
|
-
Mitigates the case where conversation repair makes the LLM re-delegate the same agent. Not a full fix.
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## Still open: conversation repair erases delegation history (likely root cause)
|
|
182
|
-
|
|
183
|
-
**Status**: suspected issue, needs more verification
|
|
184
|
-
|
|
185
|
-
**Symptom**: in 16-layer depth tests, some layers branch — one parent session creates multiple same-agent children.
|
|
186
|
-
|
|
187
|
-
**Analysis**:
|
|
188
|
-
|
|
189
|
-
`conversation-repair.ts` Phase 2 and Phase 2b can't distinguish "structural damage" from "valid tool-call history" when repairing a damaged message array.
|
|
190
|
-
|
|
191
|
-
Flow:
|
|
192
|
-
1. `runAgentTurn` hits a Bedrock error (e.g. `unexpected tool_use_id found in tool_result`)
|
|
193
|
-
2. Calls `repairConversationMessages`
|
|
194
|
-
3. Phase 2 (lines 72-119): scan assistant→user pairs, drop entries where tool_use ID and tool_result ID don't match
|
|
195
|
-
4. Phase 2b (lines 122-151): drop user tool_result with no corresponding assistant tool_use
|
|
196
|
-
5. `start_session`'s record (tool_use + tool_result) gets treated as "damage" and swept up
|
|
197
|
-
6. On LLM retry the conversation history has no delegation trace → a new `start_session` call creates a duplicate child
|
|
198
|
-
|
|
199
|
-
**Impact chain**:
|
|
200
|
-
- Direct: branching child sessions at the same level, abnormal tree structure
|
|
201
|
-
- Indirect: `session_list` shows multiple running same-agent children; `get_session_output` reading different children confuses the LLM
|
|
202
|
-
- Orphaned branches can't complete their lifecycle, stuck in running/idle forever
|
|
203
|
-
|
|
204
|
-
**Fix direction**: make conversation repair protect session management tool calls (`start_session`, `query_session`, …) from being deleted.
|
|
205
|
-
|
|
206
|
-
**To verify**:
|
|
207
|
-
- Does this also happen at shallower depths (2-3), or only at deep nesting with high retry rates?
|
|
208
|
-
- Is the duplicate-child guard enough of a mitigation in practice?
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# Halo 对外推广计划 (Go-to-Market)
|
|
2
|
-
|
|
3
|
-
> 起草人:halo仔 · 状态:草案,待咚咚总拍板方向
|
|
4
|
-
> 一句话:**先把"门面 + 弹药"修好,再开渠道。子弹没上膛之前别扣扳机。**
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 0. 先认清现状 —— 你的底气比你以为的足
|
|
9
|
-
|
|
10
|
-
**底气(别人砸钱都买不到的):**
|
|
11
|
-
- 作者自用一个半月**无暗伤**,另有 2-3 人**持续留存几个月** → 这是 PMF(产品-市场契合)的雏形,已被真实验证了一小块。多少项目 Show HN 几百 star、三天 DAU 归零,你刚好反过来:没声量,但有留存。
|
|
12
|
-
- **独门视觉牌**:粒子脸(Express Self)+ 像素城市(Halo City)。绝大多数 agent 工具就是个丑终端,你有天生适合短视频传播的东西。像素城市那张图质量已经够发。
|
|
13
|
-
|
|
14
|
-
**短板(要么修、要么坦诚):**
|
|
15
|
-
- 无声量、单人维护、无自动化测试 → README 的 "Status & Limitations" 已经很诚实,**保留**,这反而是加分项(海外开发者吃这套)。
|
|
16
|
-
- **门面有空截图硬伤**(见第 3 节 P0)。
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 1. 定位:先解决"一句话"
|
|
21
|
-
|
|
22
|
-
2025 年 agent 工具卷到飞起(Cursor / Claude Code / Devin / Manus…),路人第一反应一定是"又一个?凭啥用你"。必须一句话把差异点砸进去。
|
|
23
|
-
|
|
24
|
-
**Halo 真正的差异点(按稀缺度排序):**
|
|
25
|
-
1. **多渠道指挥** —— 微信/TG/飞书/Slack 都能直接指挥,不绑死在 IDE 里。**这个位几乎没人占。**
|
|
26
|
-
2. **自托管 + 多模型 + 自己的密钥边界** —— 数据不出你的机器。
|
|
27
|
-
3. **一切皆文件、无隐藏记忆** —— 对控制欲强的开发者是真卖点。
|
|
28
|
-
4. **视觉化的 agent 运行时** —— 看得见每个 agent 在干啥。
|
|
29
|
-
|
|
30
|
-
**英文定位候选(待选):**
|
|
31
|
-
- A. *"Your AI dev team — command it from any chat app, running entirely on your own machine."*
|
|
32
|
-
- B. *"A self-hosted AI agent team you command from Slack, Telegram, or WeChat — not locked inside an IDE."*
|
|
33
|
-
- C. *"The multi-agent dev workspace that lives in your terminal and your chat apps — not someone else's cloud."*
|
|
34
|
-
|
|
35
|
-
> ⚠️ 最终选哪句,**取决于那 2-3 个留存用户最离不开的功能**。他们用脚投票留下的那个点,大概率就是对外该喊的话。所以这句先别急着定死。
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## 2. 公用弹药库 —— 所有渠道都要用,先做这个
|
|
40
|
-
|
|
41
|
-
不管最后走哪条渠道,这三样是公用子弹,先做绝不亏:
|
|
42
|
-
|
|
43
|
-
| 弹药 | 说明 | 负责 |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| **① 一句话定位** | 见上,等留存用户反馈后敲定 | 咚咚总拍板 |
|
|
46
|
-
| **② 60 秒 demo 视频/GIF** | 押视觉牌:粒子脸说话 + 像素城市里 agent 跑动 + 多渠道手机指挥。**这是 Show HN / PH 的钩子,比任何文字都强** | halo仔出脚本,咚咚总录 |
|
|
47
|
-
| **③ README 改版** | 换掉空截图 hero(见 P0),把"多渠道 + 视觉"提到最前 | halo仔 |
|
|
48
|
-
| ④ landing page(可选) | 一个能在线瞄一眼的页面,降低门槛 | 二期 |
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## 3. 门面急救清单
|
|
53
|
-
|
|
54
|
-
### 🔴 P0 —— 推广前必须修
|
|
55
|
-
|
|
56
|
-
- **README 首图(`assets/workspace.jpg`)是一个空工作区!** Canvas 写着 "Open a file to start editing",Chat 写着 "Start a conversation" —— 整个项目最重要的第一张图,却展示了一个**什么都没发生的空壳**。这是第一印象的致命伤。
|
|
57
|
-
- **改法**:换成"火力全开"的截图 —— agent 正在思考、sub-agent 在跑、文件树有改动标记、代码在 Canvas 里、对话有来有回。让人一眼看到"这玩意儿在干活"。
|
|
58
|
-
|
|
59
|
-
### 🟡 P1 —— 强烈建议
|
|
60
|
-
- 体检报告(deep-executor 正在跑)回来后补充。
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 4. 渠道排期 —— 别贪多,挑水塘蹲
|
|
65
|
-
|
|
66
|
-
### 主线:海外开发者(自托管多 agent 的人群密度最高)
|
|
67
|
-
|
|
68
|
-
| 顺序 | 渠道 | 说明 | 你要做的 |
|
|
69
|
-
|---|---|---|---|
|
|
70
|
-
| 首发 | **Show HN**(Hacker News) | 全球科技圈最猛的水塘。"Show HN: 给大家看看我做了个啥"。一旦上首页,几小时几万开发者刷到,免费 | 发帖后头 1-2 小时回评论 |
|
|
71
|
-
| 跟进 | **Reddit** r/LocalLLaMA · r/selfhosted | 自托管 AI 的高浓度人群,正好是你的目标用户 | 同上 |
|
|
72
|
-
| 持续 | **Twitter/X** build-in-public | 持续发 demo 短视频,养长期关注 | 每周 1-2 条 |
|
|
73
|
-
| 拿牌 | **Product Hunt** | 等有点声量后冲,拿个 badge | 单独排期 |
|
|
74
|
-
|
|
75
|
-
### 第二梯队:AWS(不是拉用户,是背书 + 省钱)
|
|
76
|
-
|
|
77
|
-
> 咚咚总的直觉对一半:**写软文夸 Halo 发 AWS 博客确实戆居**(读者来学技术不是来找产品,转化≈0)。但 AWS 渠道的真实价值在别处:
|
|
78
|
-
|
|
79
|
-
- **`AWS Activate` / startup credits** —— 跟推广无关,纯省钱。**今天就该申请,顺手的事,无脑做。**
|
|
80
|
-
- **`community.aws` 技术稿**(自助发布、门槛低)—— 写"如何解决一个通用难题",Halo 只当案例出场。你真有干货:Bedrock 上做 prompt caching、用 Nova grounding 取代传统 web search、多 provider 的 agent runtime 抽象、自托管多 agent 协作系统怎么搭。立技术口碑 + SEO 长尾 + 以后见客户/投资人的背书。
|
|
81
|
-
- ⚠️ 注意:你的产品命脉之一**就是 Bedrock**(默认引擎 Claude on Bedrock、GPT 走 bedrock-mantle、联网搜索用 Nova)。所以"我做的东西不用 AWS"这个前提不成立 —— AWS 内容有得写,只是别放第一发。
|
|
82
|
-
|
|
83
|
-
### 可选:国内
|
|
84
|
-
即刻 · V2EX · 掘金(长文讲架构)· 小红书/B站(发视觉 demo)· 公众号。
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## 5. 两周执行排期(建议)
|
|
89
|
-
|
|
90
|
-
**第一周 —— 上膛**
|
|
91
|
-
- [ ] 敲定一句话定位(等留存用户反馈)
|
|
92
|
-
- [ ] 修 P0:换 README hero 图
|
|
93
|
-
- [ ] 录 60 秒 demo 视频(halo仔出脚本)
|
|
94
|
-
- [ ] README 改版(多渠道 + 视觉提前)
|
|
95
|
-
- [ ] 顺手申请 AWS Activate credits
|
|
96
|
-
- [ ] 处理体检报告里的 P0/P1
|
|
97
|
-
|
|
98
|
-
**第二周 —— 开火**
|
|
99
|
-
- [ ] Show HN 首发(配 demo 视频)
|
|
100
|
-
- [ ] 同步 Reddit r/LocalLLaMA + r/selfhosted
|
|
101
|
-
- [ ] 启动 Twitter build-in-public
|
|
102
|
-
- [ ] community.aws 技术稿(可延后)
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## 6. 待咚咚总拍板
|
|
107
|
-
|
|
108
|
-
1. **主线方向**:开源涨 star(build in public)还是商业化接客户?→ 建议先走开源,默认采纳。
|
|
109
|
-
2. **主战场**:海外为主?→ 建议海外(人群密度),国内顺带。
|
|
110
|
-
3. **那 2-3 个留存用户是啥人、最离不开哪个功能?** → 这个直接决定定位语怎么写。
|
|
111
|
-
4. demo 视频你想自己录,还是要 halo仔先把素材/分镜全做好?
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# Refactor plan: drop is_default + working_dir + MD-load simplification + externalise system prompts
|
|
2
|
-
|
|
3
|
-
**Status**: shipped (2026-04 / 2026-05). All phases A–F landed: `priority` field replaced `is_default`, `agent_sessions.working_dir` column added, MD-loader simplified (INDEX.md root-only, INSTRUCTIONS.md stacks per directory level), system prompts externalised under `prompts/{bootstrap,all,root}/`. This doc is kept as a design log — see the reference state in [design/prompt-system.md](../design/prompt-system.md).
|
|
4
|
-
**Filed**: 2026-04-27
|
|
5
|
-
|
|
6
|
-
This doc preserves every design decision, rationale, and trade-off so we can rapidly recover context if it's lost.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Background
|
|
11
|
-
|
|
12
|
-
Halo uses `is_default` as a synonym for "root agent", which has bad knock-on effects:
|
|
13
|
-
- Prompt injection logic, tool behaviour, and UI default selection all bind to this field
|
|
14
|
-
- User-defined "direct-chat agents" can't receive USER.md, PLATFORM_KNOWLEDGE, or other root-only prompts
|
|
15
|
-
- Hard-coded system prompts (BOOTSTRAP / TOOL_GUIDELINES / PLATFORM_KNOWLEDGE / ORCHESTRATOR_GUIDELINES) live in source — users wanting to customise must edit source
|
|
16
|
-
- INDEX.md reserves a subdirectory-loading interface but never calls it (dead code)
|
|
17
|
-
- INSTRUCTIONS.md only supports two layers (global + project); no subdirectory granularity
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Core decisions
|
|
22
|
-
|
|
23
|
-
### 1. Drop `is_default`
|
|
24
|
-
|
|
25
|
-
- The `is_default` field is deprecated. YAML uses `priority: number` instead — default 0, higher first.
|
|
26
|
-
- The `default` agent seed stays, `priority: 99` (remains the initial default selection).
|
|
27
|
-
- All `isDefault`-semantic branches become `!parentId` ("root agent") checks.
|
|
28
|
-
- `list_agents` no longer filters `default`; agents can spawn themselves (same agent_id, independent session instances).
|
|
29
|
-
- Delete protection: at least 1 agent in the global scope; workspace scope can be emptied.
|
|
30
|
-
- UI default selection: sort by `priority desc, name asc`, pick the first.
|
|
31
|
-
- Remove the "default" badge.
|
|
32
|
-
|
|
33
|
-
### 2. Add `working_dir`
|
|
34
|
-
|
|
35
|
-
- New `working_dir` argument on `start_session`
|
|
36
|
-
- Accepts absolute or workspace-relative paths
|
|
37
|
-
- Default: `workspaceRoot` (project root)
|
|
38
|
-
- Enforce resolved path within workspace (reject traversal)
|
|
39
|
-
- Non-existent directory → reject spawn
|
|
40
|
-
- `agent_sessions` gets a `workingDir TEXT` column (null = project root)
|
|
41
|
-
- On resume, DB → `buildAgentInstance`
|
|
42
|
-
|
|
43
|
-
### 3. INDEX.md — project-root-only
|
|
44
|
-
|
|
45
|
-
- `<ws>/.halo/INDEX.md` is the sole source, injected for every agent
|
|
46
|
-
- Sub-directory `INDEX.md` files are **not auto-loaded**; user / agent uses `file_read` on demand
|
|
47
|
-
- Drop `subDirIndex` logic in `resolveMdPaths` (dead code)
|
|
48
|
-
- Rationale: multi-level INDEX is an untested requirement; users reference sub-INDEX from the root INDEX and the agent reads on demand (progressive disclosure)
|
|
49
|
-
|
|
50
|
-
### 4. INSTRUCTIONS.md — stacked by level
|
|
51
|
-
|
|
52
|
-
- From global → workspaceRoot → every level of the workingDir path, load `.halo/INSTRUCTIONS.md`
|
|
53
|
-
- Injection order: outer → inner (global first, workingDir last)
|
|
54
|
-
- Each block labelled with its path relative to workspaceRoot (root itself uses `.`)
|
|
55
|
-
- Rationale: INSTRUCTIONS encode "how to work here"; directory depth is a natural scope, more specific wins
|
|
56
|
-
|
|
57
|
-
### 5. Externalise system prompts
|
|
58
|
-
|
|
59
|
-
The four hard-coded constants (BOOTSTRAP / TOOL_GUIDELINES / PLATFORM_KNOWLEDGE / ORCHESTRATOR_GUIDELINES) move to:
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
~/.halo/global/prompts/
|
|
63
|
-
├── bootstrap/ ← only injected for root agent + needsBootstrap
|
|
64
|
-
├── all/ ← injected for every agent
|
|
65
|
-
└── root/ ← only injected for root agent
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
- Subdirectory categorisation decides injection scope
|
|
69
|
-
- Inside each subdirectory, every `.md` is concatenated by filename ascending
|
|
70
|
-
- User extends by dropping MDs in the relevant directory (e.g. `prompts/all/my-style-guide.md`)
|
|
71
|
-
- Startup seed: first run writes four defaults; existing files preserved
|
|
72
|
-
- Live-read (every `buildAgentInstance`)
|
|
73
|
-
- Missing: warn + skip (don't break the server)
|
|
74
|
-
- `tool-guidelines.ts` remains the seed source + hard-coded fallback
|
|
75
|
-
- **Global only**; workspace doesn't override
|
|
76
|
-
- **Language**: seed content stays English (already is); UI copy stays English; section headers stay English; Chinese only in developer docs
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Target directory layout
|
|
81
|
-
|
|
82
|
-
### Global
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
~/.halo/global/
|
|
86
|
-
├── USER.md ← bootstrap-generated
|
|
87
|
-
├── INSTRUCTIONS.md ← user-global preferences
|
|
88
|
-
├── prompts/
|
|
89
|
-
│ ├── bootstrap/BOOTSTRAP.md
|
|
90
|
-
│ ├── all/TOOL_GUIDELINES.md
|
|
91
|
-
│ └── root/PLATFORM_KNOWLEDGE.md, ORCHESTRATOR_GUIDELINES.md
|
|
92
|
-
├── agents/<id>/{agent.yaml, AGENT.md}
|
|
93
|
-
└── skills/<id>/SKILL.md
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Workspace
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
<ws>/.halo/
|
|
100
|
-
├── USER.md ← optional, overrides global
|
|
101
|
-
├── INSTRUCTIONS.md ← project-root preferences
|
|
102
|
-
├── INDEX.md ← sole project index
|
|
103
|
-
├── <subdir>/.halo/INSTRUCTIONS.md ← per-subdir preferences, stacked
|
|
104
|
-
├── agents/<id>/{agent.yaml, AGENT.md}
|
|
105
|
-
└── skills/<id>/SKILL.md
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Root vs sub-agent behaviour matrix
|
|
111
|
-
|
|
112
|
-
| Injected content | Root (`!parentId`) | Sub-agent |
|
|
113
|
-
|---|---|---|
|
|
114
|
-
| USER.md | ✓ | ✗ |
|
|
115
|
-
| AGENT.md | ✓ | ✓ |
|
|
116
|
-
| INSTRUCTIONS.md (global + stacked) | ✓ | ✓ |
|
|
117
|
-
| INDEX.md (project root) | ✓ | ✓ |
|
|
118
|
-
| `prompts/all/` | ✓ | ✓ |
|
|
119
|
-
| `prompts/root/` | ✓ | ✗ |
|
|
120
|
-
| `prompts/bootstrap/` | ✓ (only when no USER.md) | ✗ |
|
|
121
|
-
| `"workspace at..."` | ✓ | ✓ |
|
|
122
|
-
| `"Working directory: ..."` | only when workingDir != root | always |
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Execution phases
|
|
127
|
-
|
|
128
|
-
Grouped into 6 todo buckets:
|
|
129
|
-
|
|
130
|
-
### A. is_default → priority (backend + frontend)
|
|
131
|
-
- session-manager.ts: drop isDefault; replace prompt branches with `!parentId`; merge tool order; drop `list_agents` filter
|
|
132
|
-
- agent-loader.ts: YAML gains priority; `scanAvailableAgents` returns priority
|
|
133
|
-
- agent-configs.ts: `parseAgentYaml` reads priority; sort by priority desc + name asc; seed template gains `priority: 99`; DELETE adds last-global guard
|
|
134
|
-
- frontend types.ts + api-client.ts: rename fields
|
|
135
|
-
- chat-panel.tsx: default-selection logic; remove "default" badge
|
|
136
|
-
- agent-management-main.tsx + agent-form.tsx: delete isDefault UI
|
|
137
|
-
|
|
138
|
-
### B. working_dir feature
|
|
139
|
-
- `start_session` schema adds `working_dir`
|
|
140
|
-
- `createSession` / `buildAgentInstance` pass it through
|
|
141
|
-
- DB migration: `agent_sessions` adds `workingDir` column
|
|
142
|
-
- Resume restores from DB
|
|
143
|
-
|
|
144
|
-
### C. md-loader simplification
|
|
145
|
-
- INDEX.md reads only `<ws>/.halo/INDEX.md`; drop `subDirIndex`
|
|
146
|
-
- INSTRUCTIONS.md becomes `workspaceInstructionsChain: Array<{rel, content}>`
|
|
147
|
-
- `composeMdPrompt` expands the chain as multiple sections, each header labelled with relative path (root = `.`)
|
|
148
|
-
- Implement `collectInstructionsChain(workspaceRoot, workingDir)`
|
|
149
|
-
|
|
150
|
-
### D. Externalise system prompts
|
|
151
|
-
- Create `~/.halo/global/prompts/{bootstrap,all,root}/`
|
|
152
|
-
- `init.ts` startup seeds four MD files (content from `tool-guidelines.ts`)
|
|
153
|
-
- New `loadSystemPrompts()` reads live per subdir, filename-ascending concat
|
|
154
|
-
- Replace `import { ... } from './tool-guidelines'` with `loadSystemPrompts()` calls
|
|
155
|
-
- Missing: warn + fall back to the hard-coded value (keep `tool-guidelines.ts`)
|
|
156
|
-
|
|
157
|
-
### E. Doc sync
|
|
158
|
-
- CLAUDE.md: describe the MD load flow, agent model
|
|
159
|
-
- `.halo/docs/backend/md-loading-flow.md` (already in target state)
|
|
160
|
-
- `.halo/docs/knowledge-system.md`: directory layout, injection rules
|
|
161
|
-
- `.halo/docs/data-storage-protocol.md`: agent YAML fields + new agent_sessions column
|
|
162
|
-
- `.halo/docs/session-tools-reference.md`: `start_session` schema
|
|
163
|
-
- `.halo/docs/test-case.md`: updated test cases
|
|
164
|
-
|
|
165
|
-
### F. Deployment smoke test
|
|
166
|
-
- `pnpm --filter core/server/web build`
|
|
167
|
-
- Kill 9527, restart with Node 22 (PATH=$HOME/.nvm/versions/node/v22.21.1/bin:$PATH)
|
|
168
|
-
- Smoke: /api/health, WS connect, send one message end-to-end
|
|
169
|
-
- Verify: root agent receives prompts/root, sub-agent doesn't; INSTRUCTIONS stacking works; working_dir wires through; deletion guard fires
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Key constraints and risks
|
|
174
|
-
|
|
175
|
-
- **Node version**: must be v22+ (better-sqlite3 native binding bound to v22). Scripts must load nvm.
|
|
176
|
-
- **Compatibility**: legacy YAML's `is_default: true` stays readable but ignored; DB's `is_default` column stays untouched, just unread.
|
|
177
|
-
- **Backwards-compat seed**: `default` agent still auto-seeds with id `default`, but YAML uses `priority: 99`.
|
|
178
|
-
- **Path safety**: `working_dir` must be verified inside workspace (block `../` escapes).
|
|
179
|
-
- **Cache key**: with merged tool list, `cache_control` still goes on the last tool — stability unaffected.
|
|
180
|
-
- **Bootstrap idempotence**: once USER.md exists, `needsBootstrap` is false forever, `prompts/bootstrap/` no longer injects.
|
|
181
|
-
- **Self-spawn allowed**: `session_id` depth limit (`maxNestingDepth`) already guards against infinite recursion.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## Rollback plan
|
|
186
|
-
|
|
187
|
-
If something breaks in production:
|
|
188
|
-
- Git revert to pre-refactor commit
|
|
189
|
-
- `~/.halo/global/prompts/` directory is harmless to older versions (they don't read it)
|
|
190
|
-
- `agent_sessions.workingDir` column harmless (old code reads null → falls back to workspaceRoot)
|
|
191
|
-
- YAML `priority` harmless to older code (unknown field = default 0); `is_default: true` can be seeded back into the default agent's YAML
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## Todo index
|
|
196
|
-
|
|
197
|
-
See the todo list. All 12 pending items run A → F; the last one is the smoke test.
|