@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.
Files changed (36) hide show
  1. package/README.md +1 -5
  2. package/admin-out/404.html +1 -1
  3. package/admin-out/_next/static/chunks/298-4fac7d44a7949eec.js +1 -0
  4. package/admin-out/_next/static/chunks/{503-7c6d9935df6e0cea.js → 307-d7a99f9a4a436ec3.js} +1 -1
  5. package/admin-out/_next/static/chunks/app/page-b12a2e8e48fe4ea0.js +1 -0
  6. package/admin-out/_next/static/css/{8fce4e1a54dac12a.css → 372e3dbd4a0d1c25.css} +1 -1
  7. package/admin-out/index.html +1 -1
  8. package/admin-out/index.txt +4 -4
  9. package/bundled-docs/dev/acp-adapter.md +5 -4
  10. package/bundled-docs/dev/desktop-packaging.md +94 -8
  11. package/bundled-docs/guide/getting-started.md +3 -2
  12. package/bundled-docs/requirements/explorer.md +11 -0
  13. package/dist/index.js +13373 -13246
  14. package/package.json +1 -1
  15. package/templates/agents/executor/agent.yaml +2 -2
  16. package/templates/models/aws-bedrock-claude-invoke.yaml +26 -0
  17. package/templates/skills/acp/SKILL.md +8 -1
  18. package/templates/skills/acp/templates/SKILL.md.tmpl +2 -1
  19. package/templates/skills/acp/templates/ask.py +2 -1
  20. package/templates/skills/acp/templates/config.yaml.tmpl +5 -0
  21. package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +0 -1
  22. package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +0 -1
  23. package/bundled-docs/plans/cron-tasks.md +0 -362
  24. package/bundled-docs/plans/design-suggestions.md +0 -203
  25. package/bundled-docs/plans/issue-session-deep-nesting.md +0 -208
  26. package/bundled-docs/plans/launch-plan.md +0 -111
  27. package/bundled-docs/plans/md-and-agent-refactor.md +0 -197
  28. package/bundled-docs/plans/remote-mac-worker.md +0 -196
  29. package/bundled-docs/plans/roadmap.md +0 -40
  30. package/bundled-docs/plans/self-evolution.md +0 -1379
  31. package/bundled-docs/plans/todo.md +0 -32
  32. package/bundled-docs/plans/weixin-channel.md +0 -255
  33. package/bundled-docs/test/docs-seeding.md +0 -101
  34. package/bundled-docs/test/session.md +0 -374
  35. /package/admin-out/_next/static/{IBP_mwBDf-cPwZSZZGpi8 → ep7BWneNhTuIPwJfFFh72}/_buildManifest.js +0 -0
  36. /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.