@turmind/halo 0.1.9

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 (296) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/admin-out/404.html +1 -0
  4. package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_buildManifest.js +1 -0
  5. package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_ssgManifest.js +1 -0
  6. package/admin-out/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js +18 -0
  7. package/admin-out/_next/static/chunks/200.3b8f8a74f005a134.js +1 -0
  8. package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +1 -0
  9. package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +1 -0
  10. package/admin-out/_next/static/chunks/304eda7d.b1ee7f523653e027.js +1 -0
  11. package/admin-out/_next/static/chunks/330.3b784cf0dcd69163.js +1 -0
  12. package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +1 -0
  13. package/admin-out/_next/static/chunks/443.d1b289781793f8de.js +25 -0
  14. package/admin-out/_next/static/chunks/503-7c6d9935df6e0cea.js +1 -0
  15. package/admin-out/_next/static/chunks/533.5f2d4bfe3d076095.js +1 -0
  16. package/admin-out/_next/static/chunks/579.a2718ddd395a2f3a.js +1 -0
  17. package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +1 -0
  18. package/admin-out/_next/static/chunks/65.859665ba6717967a.js +1 -0
  19. package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +1 -0
  20. package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +1 -0
  21. package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +1 -0
  22. package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +1 -0
  23. package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +1 -0
  24. package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +1 -0
  25. package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +1 -0
  26. package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +1 -0
  27. package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +1 -0
  28. package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +1 -0
  29. package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +1 -0
  30. package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +1 -0
  31. package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +1 -0
  32. package/admin-out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  33. package/admin-out/_next/static/chunks/webpack-8bd7ef88a6527feb.js +1 -0
  34. package/admin-out/_next/static/css/659eccb5db697b76.css +1 -0
  35. package/admin-out/_next/static/css/8fce4e1a54dac12a.css +3 -0
  36. package/admin-out/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  37. package/admin-out/_next/static/media/21350d82a1f187e9-s.woff2 +0 -0
  38. package/admin-out/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  39. package/admin-out/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  40. package/admin-out/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  41. package/admin-out/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  42. package/admin-out/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  43. package/admin-out/favicon.ico +0 -0
  44. package/admin-out/icon.svg +18 -0
  45. package/admin-out/index.html +1 -0
  46. package/admin-out/index.txt +23 -0
  47. package/admin-out/monaco/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
  48. package/admin-out/monaco/vs/abap-D-t0cyap.js +1 -0
  49. package/admin-out/monaco/vs/apex-CcIm7xu6.js +1 -0
  50. package/admin-out/monaco/vs/assets/css.worker-HnVq6Ewq.js +93 -0
  51. package/admin-out/monaco/vs/assets/editor.worker-Be8ye1pW.js +26 -0
  52. package/admin-out/monaco/vs/assets/html.worker-B51mlPHg.js +470 -0
  53. package/admin-out/monaco/vs/assets/json.worker-DKiEKt88.js +58 -0
  54. package/admin-out/monaco/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
  55. package/admin-out/monaco/vs/azcli-BA0tQDCg.js +1 -0
  56. package/admin-out/monaco/vs/basic-languages/monaco.contribution.js +1 -0
  57. package/admin-out/monaco/vs/bat-C397hTD6.js +1 -0
  58. package/admin-out/monaco/vs/bicep-DF5aW17k.js +2 -0
  59. package/admin-out/monaco/vs/cameligo-plsz8qhj.js +1 -0
  60. package/admin-out/monaco/vs/clojure-Y2auQMzK.js +1 -0
  61. package/admin-out/monaco/vs/coffee-Bu45yuWE.js +1 -0
  62. package/admin-out/monaco/vs/cpp-CkKPQIni.js +1 -0
  63. package/admin-out/monaco/vs/csharp-CX28MZyh.js +1 -0
  64. package/admin-out/monaco/vs/csp-D8uWnyxW.js +1 -0
  65. package/admin-out/monaco/vs/css-CaeNmE3S.js +3 -0
  66. package/admin-out/monaco/vs/cssMode-CjiAH6dQ.js +1 -0
  67. package/admin-out/monaco/vs/cypher-DVThT8BS.js +1 -0
  68. package/admin-out/monaco/vs/dart-CmGfCvrO.js +1 -0
  69. package/admin-out/monaco/vs/dockerfile-CZqqYdch.js +1 -0
  70. package/admin-out/monaco/vs/ecl-30fUercY.js +1 -0
  71. package/admin-out/monaco/vs/editor/editor.main.css +1 -0
  72. package/admin-out/monaco/vs/editor/editor.main.js +5 -0
  73. package/admin-out/monaco/vs/editor.api-CalNCsUg.js +903 -0
  74. package/admin-out/monaco/vs/elixir-xjPaIfzF.js +1 -0
  75. package/admin-out/monaco/vs/flow9-DqtmStfK.js +1 -0
  76. package/admin-out/monaco/vs/freemarker2-Cz_sV6Md.js +3 -0
  77. package/admin-out/monaco/vs/fsharp-BOMdg4U1.js +1 -0
  78. package/admin-out/monaco/vs/go-D_hbi-Jt.js +1 -0
  79. package/admin-out/monaco/vs/graphql-CKUU4kLG.js +1 -0
  80. package/admin-out/monaco/vs/handlebars-OwglfO-1.js +1 -0
  81. package/admin-out/monaco/vs/hcl-DTaboeZW.js +1 -0
  82. package/admin-out/monaco/vs/html-Pa1xEWsY.js +1 -0
  83. package/admin-out/monaco/vs/htmlMode-Bz67EXwp.js +1 -0
  84. package/admin-out/monaco/vs/ini-CsNwO04R.js +1 -0
  85. package/admin-out/monaco/vs/java-CI4ZMsH9.js +1 -0
  86. package/admin-out/monaco/vs/javascript-PczUCGdz.js +1 -0
  87. package/admin-out/monaco/vs/jsonMode-DULH5oaX.js +7 -0
  88. package/admin-out/monaco/vs/julia-BwzEvaQw.js +1 -0
  89. package/admin-out/monaco/vs/kotlin-IUYPiTV8.js +1 -0
  90. package/admin-out/monaco/vs/language/css/monaco.contribution.js +1 -0
  91. package/admin-out/monaco/vs/language/html/monaco.contribution.js +1 -0
  92. package/admin-out/monaco/vs/language/json/monaco.contribution.js +1 -0
  93. package/admin-out/monaco/vs/language/typescript/monaco.contribution.js +1 -0
  94. package/admin-out/monaco/vs/less-C0eDYdqa.js +2 -0
  95. package/admin-out/monaco/vs/lexon-iON-Kj97.js +1 -0
  96. package/admin-out/monaco/vs/liquid-DqKjdPGy.js +1 -0
  97. package/admin-out/monaco/vs/loader.js +1368 -0
  98. package/admin-out/monaco/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
  99. package/admin-out/monaco/vs/lua-DtygF91M.js +1 -0
  100. package/admin-out/monaco/vs/m3-CsR4AuFi.js +1 -0
  101. package/admin-out/monaco/vs/markdown-C_rD0bIw.js +1 -0
  102. package/admin-out/monaco/vs/mdx-DEWtB1K5.js +1 -0
  103. package/admin-out/monaco/vs/mips-CiYP61RB.js +1 -0
  104. package/admin-out/monaco/vs/monaco.contribution-D2OdxNBt.js +1 -0
  105. package/admin-out/monaco/vs/monaco.contribution-DO3azKX8.js +1 -0
  106. package/admin-out/monaco/vs/monaco.contribution-EcChJV6a.js +1 -0
  107. package/admin-out/monaco/vs/monaco.contribution-qLAYrEOP.js +1 -0
  108. package/admin-out/monaco/vs/msdax-C38-sJlp.js +1 -0
  109. package/admin-out/monaco/vs/mysql-CdtbpvbG.js +1 -0
  110. package/admin-out/monaco/vs/nls.messages-loader.js +1 -0
  111. package/admin-out/monaco/vs/nls.messages.cs.js.js +17 -0
  112. package/admin-out/monaco/vs/nls.messages.de.js.js +17 -0
  113. package/admin-out/monaco/vs/nls.messages.es.js.js +17 -0
  114. package/admin-out/monaco/vs/nls.messages.fr.js.js +15 -0
  115. package/admin-out/monaco/vs/nls.messages.it.js.js +15 -0
  116. package/admin-out/monaco/vs/nls.messages.ja.js.js +17 -0
  117. package/admin-out/monaco/vs/nls.messages.js.js +10 -0
  118. package/admin-out/monaco/vs/nls.messages.ko.js.js +25 -0
  119. package/admin-out/monaco/vs/nls.messages.pl.js.js +17 -0
  120. package/admin-out/monaco/vs/nls.messages.pt-br.js.js +6 -0
  121. package/admin-out/monaco/vs/nls.messages.ru.js.js +17 -0
  122. package/admin-out/monaco/vs/nls.messages.tr.js.js +15 -0
  123. package/admin-out/monaco/vs/nls.messages.zh-cn.js.js +17 -0
  124. package/admin-out/monaco/vs/nls.messages.zh-tw.js.js +15 -0
  125. package/admin-out/monaco/vs/objective-c-CntZFaHX.js +1 -0
  126. package/admin-out/monaco/vs/pascal-r6kuqfl_.js +1 -0
  127. package/admin-out/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
  128. package/admin-out/monaco/vs/perl-DABw_TcH.js +1 -0
  129. package/admin-out/monaco/vs/pgsql-me_jFXeX.js +1 -0
  130. package/admin-out/monaco/vs/php-D_kh-9LK.js +1 -0
  131. package/admin-out/monaco/vs/pla-VfZjczW0.js +1 -0
  132. package/admin-out/monaco/vs/postiats-BBSzz8Pk.js +1 -0
  133. package/admin-out/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
  134. package/admin-out/monaco/vs/powershell-B-7ap1zc.js +1 -0
  135. package/admin-out/monaco/vs/protobuf-BmtuEB1A.js +2 -0
  136. package/admin-out/monaco/vs/pug-BRpRNeEb.js +1 -0
  137. package/admin-out/monaco/vs/python-Cr0UkIbn.js +1 -0
  138. package/admin-out/monaco/vs/qsharp-BzsFaUU9.js +1 -0
  139. package/admin-out/monaco/vs/r-f8dDdrp4.js +1 -0
  140. package/admin-out/monaco/vs/razor-BYAHOTkz.js +1 -0
  141. package/admin-out/monaco/vs/redis-fvZQY4PI.js +1 -0
  142. package/admin-out/monaco/vs/redshift-45Et0LQi.js +1 -0
  143. package/admin-out/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
  144. package/admin-out/monaco/vs/ruby-CZO8zYTz.js +1 -0
  145. package/admin-out/monaco/vs/rust-Bfetafyc.js +1 -0
  146. package/admin-out/monaco/vs/sb-3GYllVck.js +1 -0
  147. package/admin-out/monaco/vs/scala-foMgrKo1.js +1 -0
  148. package/admin-out/monaco/vs/scheme-CHdMtr7p.js +1 -0
  149. package/admin-out/monaco/vs/scss-C1cmLt9V.js +3 -0
  150. package/admin-out/monaco/vs/shell-ClXCKCEW.js +1 -0
  151. package/admin-out/monaco/vs/solidity-MZ6ExpPy.js +1 -0
  152. package/admin-out/monaco/vs/sophia-DWkuSsPQ.js +1 -0
  153. package/admin-out/monaco/vs/sparql-AUGFYSyk.js +1 -0
  154. package/admin-out/monaco/vs/sql-32GpJSV2.js +1 -0
  155. package/admin-out/monaco/vs/st-CuDFIVZ_.js +1 -0
  156. package/admin-out/monaco/vs/swift-n-2HociN.js +3 -0
  157. package/admin-out/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
  158. package/admin-out/monaco/vs/tcl-D74tq1nH.js +1 -0
  159. package/admin-out/monaco/vs/tsMode-CZz1Umrk.js +11 -0
  160. package/admin-out/monaco/vs/twig-C6taOxMV.js +1 -0
  161. package/admin-out/monaco/vs/typescript-DfOrAzoV.js +1 -0
  162. package/admin-out/monaco/vs/typespec-D-PIh9Xw.js +1 -0
  163. package/admin-out/monaco/vs/vb-Dyb2648j.js +1 -0
  164. package/admin-out/monaco/vs/wgsl-BhLXMOR0.js +298 -0
  165. package/admin-out/monaco/vs/workers-DcJshg-q.js +1 -0
  166. package/admin-out/monaco/vs/xml-CdsdnY8S.js +1 -0
  167. package/admin-out/monaco/vs/yaml-DYGvmE88.js +1 -0
  168. package/bin/halo.js +9 -0
  169. package/bundled-docs/design/agent.md +145 -0
  170. package/bundled-docs/design/architecture.md +270 -0
  171. package/bundled-docs/design/background-dispatch.md +151 -0
  172. package/bundled-docs/design/command.md +58 -0
  173. package/bundled-docs/design/cron.md +88 -0
  174. package/bundled-docs/design/evolution.md +383 -0
  175. package/bundled-docs/design/express-self.md +145 -0
  176. package/bundled-docs/design/feishu.md +224 -0
  177. package/bundled-docs/design/halo-city.md +112 -0
  178. package/bundled-docs/design/prompt-system.md +411 -0
  179. package/bundled-docs/design/session.md +321 -0
  180. package/bundled-docs/design/slack.md +212 -0
  181. package/bundled-docs/design/storage.md +578 -0
  182. package/bundled-docs/design/telegram.md +161 -0
  183. package/bundled-docs/design/web.md +222 -0
  184. package/bundled-docs/design/wechat.md +146 -0
  185. package/bundled-docs/design/ws.md +144 -0
  186. package/bundled-docs/dev/acp-adapter.md +337 -0
  187. package/bundled-docs/dev/add-channel.md +376 -0
  188. package/bundled-docs/dev/add-model-provider.md +386 -0
  189. package/bundled-docs/dev/api.md +686 -0
  190. package/bundled-docs/dev/deploy.md +198 -0
  191. package/bundled-docs/dev/desktop-packaging.md +327 -0
  192. package/bundled-docs/dev/env.md +158 -0
  193. package/bundled-docs/dev/logging.md +85 -0
  194. package/bundled-docs/dev/previews.md +225 -0
  195. package/bundled-docs/dev/tools.md +331 -0
  196. package/bundled-docs/guide/README.md +29 -0
  197. package/bundled-docs/guide/agents.md +264 -0
  198. package/bundled-docs/guide/channels/README.md +62 -0
  199. package/bundled-docs/guide/channels/acp.md +150 -0
  200. package/bundled-docs/guide/channels/feishu.md +154 -0
  201. package/bundled-docs/guide/channels/slack.md +156 -0
  202. package/bundled-docs/guide/channels/telegram.md +112 -0
  203. package/bundled-docs/guide/channels/web.md +149 -0
  204. package/bundled-docs/guide/channels/wechat.md +110 -0
  205. package/bundled-docs/guide/chat.md +112 -0
  206. package/bundled-docs/guide/cli.md +197 -0
  207. package/bundled-docs/guide/getting-started.md +93 -0
  208. package/bundled-docs/guide/secrets-and-credentials.md +161 -0
  209. package/bundled-docs/guide/sessions.md +104 -0
  210. package/bundled-docs/guide/skills.md +302 -0
  211. package/bundled-docs/guide/testing-agents-and-skills.md +136 -0
  212. package/bundled-docs/guide/workspace.md +131 -0
  213. package/bundled-docs/plans/cron-tasks.md +362 -0
  214. package/bundled-docs/plans/design-suggestions.md +203 -0
  215. package/bundled-docs/plans/issue-session-deep-nesting.md +208 -0
  216. package/bundled-docs/plans/launch-plan.md +111 -0
  217. package/bundled-docs/plans/md-and-agent-refactor.md +197 -0
  218. package/bundled-docs/plans/remote-mac-worker.md +196 -0
  219. package/bundled-docs/plans/roadmap.md +40 -0
  220. package/bundled-docs/plans/self-evolution.md +1379 -0
  221. package/bundled-docs/plans/todo.md +32 -0
  222. package/bundled-docs/plans/weixin-channel.md +255 -0
  223. package/bundled-docs/requirements/agent.md +92 -0
  224. package/bundled-docs/requirements/chat.md +81 -0
  225. package/bundled-docs/requirements/command.md +130 -0
  226. package/bundled-docs/requirements/editor.md +82 -0
  227. package/bundled-docs/requirements/explorer.md +89 -0
  228. package/bundled-docs/requirements/overview.md +62 -0
  229. package/bundled-docs/requirements/session.md +73 -0
  230. package/bundled-docs/requirements/settings.md +224 -0
  231. package/bundled-docs/requirements/skill.md +125 -0
  232. package/bundled-docs/requirements/source-control.md +82 -0
  233. package/bundled-docs/requirements/terminal.md +118 -0
  234. package/bundled-docs/requirements/workspace.md +80 -0
  235. package/bundled-docs/test/docs-seeding.md +101 -0
  236. package/bundled-docs/test/session.md +374 -0
  237. package/dist/index.js +26750 -0
  238. package/package.json +67 -0
  239. package/templates/INSTRUCTIONS.md +25 -0
  240. package/templates/agents/__apply_agent__/AGENT.md +229 -0
  241. package/templates/agents/__apply_agent__/agent.yaml +30 -0
  242. package/templates/agents/__evo_agent__/AGENT.md +323 -0
  243. package/templates/agents/__evo_agent__/agent.yaml +29 -0
  244. package/templates/agents/__score__/AGENT.md +177 -0
  245. package/templates/agents/__score__/agent.yaml +29 -0
  246. package/templates/agents/deep-executor/AGENT.md +60 -0
  247. package/templates/agents/deep-executor/agent.yaml +25 -0
  248. package/templates/agents/default/AGENT.md +38 -0
  249. package/templates/agents/default/agent.yaml +51 -0
  250. package/templates/agents/executor/AGENT.md +38 -0
  251. package/templates/agents/executor/agent.yaml +25 -0
  252. package/templates/aliases.yaml +26 -0
  253. package/templates/canvas/self.html +660 -0
  254. package/templates/config.yaml +69 -0
  255. package/templates/models/anthropic.yaml +47 -0
  256. package/templates/models/aws-bedrock-claude-invoke.yaml +156 -0
  257. package/templates/models/aws-bedrock-mantle.yaml +89 -0
  258. package/templates/models/deepseek.yaml +62 -0
  259. package/templates/models/doubao.yaml +137 -0
  260. package/templates/models/hunyuan.yaml +56 -0
  261. package/templates/models/kimi.yaml +41 -0
  262. package/templates/models/mimo-token-plan-china.yaml +61 -0
  263. package/templates/models/minimax.yaml +80 -0
  264. package/templates/models/openai.yaml +51 -0
  265. package/templates/models/qwen.yaml +78 -0
  266. package/templates/optional-skills/tavily-web-search/SKILL.md +91 -0
  267. package/templates/optional-skills/tavily-web-search/config.yaml +10 -0
  268. package/templates/prompts/all/TOOL_GUIDELINES.md +35 -0
  269. package/templates/prompts/all/TOOL_SHELL.md +4 -0
  270. package/templates/prompts/all/TOOL_SHELL.windows.md +7 -0
  271. package/templates/prompts/bootstrap/BOOTSTRAP.md +26 -0
  272. package/templates/prompts/root/WORKSPACE_MEMORY.md +11 -0
  273. package/templates/schema.sql +30 -0
  274. package/templates/skills/acp/SKILL.md +236 -0
  275. package/templates/skills/acp/templates/SKILL.md.tmpl +74 -0
  276. package/templates/skills/acp/templates/ask.py +176 -0
  277. package/templates/skills/acp/templates/config.yaml.tmpl +33 -0
  278. package/templates/skills/agent/SKILL.md +128 -0
  279. package/templates/skills/aws-knowledge/SKILL.md +82 -0
  280. package/templates/skills/aws-knowledge/scripts/aws_kb.py +61 -0
  281. package/templates/skills/cron/SKILL.md +217 -0
  282. package/templates/skills/cron/manage-cron.py +434 -0
  283. package/templates/skills/halo/SKILL.md +98 -0
  284. package/templates/skills/nova-web-search/SKILL.md +65 -0
  285. package/templates/skills/self/SKILL.md +139 -0
  286. package/templates/skills/send-file/SKILL.md +28 -0
  287. package/templates/skills/send-file/feishu.md +16 -0
  288. package/templates/skills/send-file/slack.md +7 -0
  289. package/templates/skills/send-file/telegram.md +9 -0
  290. package/templates/skills/send-file/web.md +6 -0
  291. package/templates/skills/send-file/wechat.md +8 -0
  292. package/templates/skills/skill/SKILL.md +131 -0
  293. package/templates/skills/workspace/SKILL.md +28 -0
  294. package/templates/skills/workspace/setup-tidy.md +265 -0
  295. package/templates/skills/workspace/share.md +187 -0
  296. package/templates/skills/workspace/stage.py +487 -0
@@ -0,0 +1,411 @@
1
+ # Prompt System — MD loading flow
2
+
3
+ On agent startup, Halo assembles the system prompt by concatenating various MD files in a fixed order.
4
+
5
+ Entry point: `session-manager.ts` `buildAgentInstance(agentId, sessionId, parentId?, workingDir?, accessLevel?)`
6
+
7
+ **Root agent rule**: `!parentId` (parentId null/undefined = root). Does not depend on any `is_default` field.
8
+
9
+ ## Final directory layout
10
+
11
+ ### Global (machine-wide)
12
+
13
+ ```
14
+ ~/.halo/global/
15
+ ├── USER.md ← user profile (generated on bootstrap)
16
+ ├── INSTRUCTIONS.md ← global user preferences
17
+ ├── prompts/ ← user-editable system prompts (externalised)
18
+ │ ├── bootstrap/BOOTSTRAP.md ← first-run guidance
19
+ │ ├── all/ ← every-agent rules (TOOL_GUIDELINES.md, TOOL_SHELL[.windows].md)
20
+ │ └── root/ ← root-agent-only (empty by default; user-set)
21
+ ├── agents/<id>/{agent.yaml, AGENT.md}
22
+ └── skills/<id>/SKILL.md ← built-in `halo` skill carries platform self-knowledge (loaded on demand via activate_skill)
23
+ ```
24
+
25
+ ### Workspace
26
+
27
+ ```
28
+ <workspaceRoot>/.halo/
29
+ ├── USER.md ← optional, overrides global USER.md
30
+ ├── INSTRUCTIONS.md ← project-level (overrides global INSTRUCTIONS.md)
31
+ ├── INDEX.md ← project overview + doc index
32
+ ├── <subdir>/.halo/INSTRUCTIONS.md ← directory-scoped: baked into the system prompt when a sub-agent's working_dir points here; injected per-turn via user @scope otherwise
33
+ ├── prompts/ ← optional, directory-level override of global prompts/
34
+ │ ├── bootstrap/ ← overrides global prompts/bootstrap/ if dir exists
35
+ │ ├── all/ ← overrides global prompts/all/ if dir exists
36
+ │ └── root/ ← overrides global prompts/root/ if dir exists
37
+ ├── agents/<id>/{agent.yaml, AGENT.md}
38
+ └── skills/<id>/SKILL.md
39
+ ```
40
+
41
+ `.halo/` is listed in grep/glob `SKIP_DIRS`, so agents read via `file_read`.
42
+
43
+ ## Step 1 — Load agent YAML
44
+
45
+ [agent-loader.ts](../../../packages/server/src/agents/agent-loader.ts) `loadAgentYaml(agentId, workspaceRoot)`
46
+
47
+ Resolution is by the agent's **folder** (`agentSourceDir`), not per file:
48
+ 1. if `<workspaceRoot>/.halo/agents/<id>/` exists → read `agent.yaml` from there
49
+ 2. otherwise → read from `~/.halo/global/agents/<id>/`
50
+
51
+ **Whole-folder override**: when the workspace folder exists it serves the
52
+ agent entirely (both `agent.yaml` and `AGENT.md`), with no per-file fallback
53
+ to global — a missing file inside it is just absent. YAML fields: `name` /
54
+ `description` / `model` / `tools` / `skills` / `system_prompt` / `context` /
55
+ `priority`.
56
+
57
+ ## Step 2 — Resolve MD file paths
58
+
59
+ [md-loader.ts](../../../packages/server/src/prompts/md-loader.ts) `resolveMdPaths(agentId, workspaceRoot?)`
60
+
61
+ ### USER.md (workspace > global)
62
+ - `<ws>/.halo/USER.md`
63
+ - `~/.halo/global/USER.md`
64
+
65
+ ### AGENT.md (whole-folder override, follows the agent folder)
66
+ Read from the same folder Step 1 resolved (`agentSourceDir`):
67
+ - if `<ws>/.halo/agents/<id>/` exists → `<ws>/.halo/agents/<id>/AGENT.md`
68
+ - otherwise → `~/.halo/global/agents/<id>/AGENT.md`
69
+
70
+ No independent per-file fallback: when the workspace agent folder exists but
71
+ has no `AGENT.md`, AGENT.md is empty (it does **not** fall back to the global
72
+ agent's AGENT.md), because the workspace folder replaces the global one whole.
73
+
74
+ ### INSTRUCTIONS.md — system prompt holds global + workspace-root only
75
+ - `~/.halo/global/INSTRUCTIONS.md` — global (suppressed when the workspace-root INSTRUCTIONS.md exists)
76
+ - `<ws>/.halo/INSTRUCTIONS.md` — project root
77
+
78
+ **Override rule.** The workspace-root INSTRUCTIONS.md suppresses the global one (same override as USER.md / AGENT.md), so a cloned workspace is self-contained — its conventions travel with the repo and don't depend on the machine's global file. When the workspace root has no INSTRUCTIONS.md, the global one is used.
79
+
80
+ **Sub-directory INSTRUCTIONS.md — two paths, by who supplies the directory** (see [Directory-scoped instructions](#directory-scoped-instructions-scope) below):
81
+ - **A sub-agent's `working_dir`** is persistent session identity (stored in the DB, restored on resume), so its directory-chain INSTRUCTIONS.md are baked into the **system prompt every turn** — the agent never forgets the rules of the directory it lives in.
82
+ - **A user's `@scope <dir>`** is ad-hoc, so it's injected **per-turn** into that one message only (loop-scoped — relevant to the turn that asked for it, not a permanent part of identity).
83
+
84
+ ### INDEX.md (**project root only**)
85
+ - `<ws>/.halo/INDEX.md`
86
+
87
+ Subdirectory `.halo/INDEX.md` files are **not auto-loaded** — use `file_read` on demand.
88
+
89
+ ## Step 3 — Concurrent disk reads
90
+
91
+ `loadAllMdContents()` concurrently reads every candidate. Returns:
92
+
93
+ ```ts
94
+ interface MdContents {
95
+ userMd: string
96
+ agentMd: string
97
+ globalInstructions: string
98
+ workspaceInstructions: string // <ws>/.halo/INSTRUCTIONS.md (root only)
99
+ projectIndex: string
100
+ needsBootstrap: boolean // no USER.md anywhere
101
+ }
102
+ ```
103
+
104
+ ## Step 4 — Load system prompts
105
+
106
+ [system-prompts.ts](../../../packages/server/src/prompts/system-prompts.ts) keeps the hard-coded defaults for seeding and fallback.
107
+
108
+ ### Seed (init.ts startup hook)
109
+ First startup writes the four default MDs to `~/.halo/global/prompts/{bootstrap,all,root}/`. Existing files are not overwritten.
110
+
111
+ ### Live load (every `buildAgentInstance`)
112
+ `loadSystemPrompts(workspaceRoot?)` resolves each scope directory with workspace > global precedence:
113
+ - For each user-editable scope (`bootstrap`, `all`, `root`): if `<ws>/.halo/prompts/<scope>/` **directory exists**, use it; otherwise fall back to `~/.halo/global/prompts/<scope>/`.
114
+ - Override is at the **directory level** — the workspace directory entirely replaces the global one (no per-file merge).
115
+ - Within the resolved directory, the `.md` files are filtered by `selectForPlatform` (see below), then concatenated by filename ascending.
116
+
117
+ > Historical: a `builtin/` scope used to be force-prepended to `root`, carrying `PLATFORM_KNOWLEDGE.md`. That content has moved to the built-in `halo` skill (see `templates/skills/halo/SKILL.md`), loaded on demand via `activate_skill('halo')`. Root agents no longer pay context for platform self-knowledge they don't need this turn.
118
+
119
+ #### Platform variant files (`*.windows.md`)
120
+
121
+ `selectForPlatform` lets a scope ship a Windows-specific version of any prompt file without duplicating the platform-neutral parts:
122
+
123
+ - `<stem>.windows.md` is the Windows variant of `<stem>.md`.
124
+ - **On Windows**: the variant is loaded and its same-stem base (`<stem>.md`) is suppressed — the variant *replaces* the base.
125
+ - **On mac / linux**: every `*.windows.md` is ignored; only the plain `.md` files load.
126
+ - Files with no `*.windows.md` sibling load on every platform.
127
+
128
+ Replacement is **whole-file**, not per-section — so split the platform-divergent content into its own file and keep the common content in an un-suffixed file. Example in `prompts/all/`: `TOOL_GUIDELINES.md` (common) + `TOOL_SHELL.md` (unix Shell section) / `TOOL_SHELL.windows.md` (cmd.exe, `dir`/`findstr`, `%USERPROFILE%`, `python` vs `python3`, `.py` skill caveats). To add a new platform difference anywhere, extract that block to `FOO.md` and add `FOO.windows.md`.
129
+
130
+ Result:
131
+ - `prompts/bootstrap/*.md` → `bootstrapPrompt`
132
+ - `prompts/all/*.md` → `allPrompt`
133
+ - `prompts/root/*.md` → `rootPrompt`
134
+
135
+ Missing directory or read failure: warn + use built-in fallback.
136
+
137
+ `loadSystemPrompts` also returns `files: { bootstrap: string[]; all: string[]; root: string[] }` — the absolute paths of the `.md` files actually read for each scope (empty when the built-in fallback was used). `session-manager.ts` forwards this list into `AgentMeta.mdFiles`, which `/session context` renders one line per file (label `prompt/<scope>/<basename>`). When a scope falls back to the built-in constants, a single line `prompt/<scope> (built-in fallback): <dir>` is shown instead.
138
+
139
+ ## Step 5 — Compose the MD prompt
140
+
141
+ `composeMdPrompt(contents, roster = '', scopeBody = '')` joins non-empty sections with `\n\n---\n\n`:
142
+
143
+ 1. `## User Profile` (USER.md) — root agent only
144
+ 2. AGENT.md body
145
+ 3. The live agent roster (`## Know Your Team Before You Act` for root, `## Your Team` for sub-agents), slotted directly behind AGENT.md (see [Agent roster](#agent-roster) below). Empty string for non-delegating agents (no `team` / empty `team`) / internal agents, so the section is dropped.
146
+ 4. `## User Instructions` — `~/.halo/global/INSTRUCTIONS.md` (suppressed when the workspace root has its own — see Step 2)
147
+ 5. `## User Instructions` — `<ws>/.halo/INSTRUCTIONS.md` (workspace root).
148
+ 6. `## User Instructions` — a sub-agent's `working_dir` directory-chain INSTRUCTIONS.md (`loadScopeBody`, plain markdown, headed `### <dir>`), folded into the same region right after #4/#5 so the order reads general → specific. Empty for a root agent / a working_dir with no sub-dir file. (User `@scope` does NOT come through here — it injects per-turn into the message, wrapped in `<workspace-instructions>`.)
149
+ 7. `## Project Knowledge` — `<ws>/.halo/INDEX.md` (skipped entirely when no INDEX.md exists)
150
+
151
+ Global and workspace-root INSTRUCTIONS share the same `## User Instructions` heading: they're mutually exclusive (workspace overrides global, see Step 2), so only one ever lands in a prompt and there's no sibling to disambiguate from. A sub-agent's `working_dir` directory-chain (#6) reuses the same heading and is a third, *additive* layer in that region — sub-dir rules add to, never replace, the global/ws-root base, so the order reads general → specific. The roster rides as section 3 — passing it through `composeMdPrompt` rather than concatenating it afterward gives it the same `---` separators as every other section (no glue-to-next-block).
152
+
153
+ ## Step 6 — Layer in the system prompts
154
+
155
+ ### Root agent (`!parentId`)
156
+
157
+ ```
158
+ mdPrompt ← incl. roster + working_dir scope (both inside composeMdPrompt)
159
+ + "\n\nThe project workspace is at: {workspaceRoot}\n"
160
+ + [optional] "Working directory: {workingDir}\n"
161
+ + allPrompt
162
+ + rootPrompt
163
+ ```
164
+
165
+ (The `working_dir` directory-chain INSTRUCTIONS.md live *inside* `mdPrompt` — folded into the `## User Instructions` region by `composeMdPrompt` — not after the `Working directory:` tagline. That tagline is just a one-line focus marker.)
166
+
167
+ The roster is computed once (`canDelegate(yaml)` → `buildAgentRoster(agentId, team)`, else `''`, where `canDelegate` is "not internal AND a non-empty `team`") and handed to `composeMdPrompt`, so it lands inside `mdPrompt` behind AGENT.md. Only when there's no AGENT.md at all (the fallback branch) is a non-empty roster appended at the tail instead — there's no MD layer to slot it behind.
168
+
169
+ When `needsBootstrap`, the whole block is prefixed with `bootstrapPrompt + "\n\n---\n\n"`.
170
+
171
+ ### Sub-agent (has parentId)
172
+
173
+ `userMd` is cleared and the prompt recomposed:
174
+
175
+ ```
176
+ mdPrompt ← incl. working_dir scope (inside composeMdPrompt, see Root)
177
+ + "\n\nThe workspace root is: {workspaceRoot}\n"
178
+ + "Working directory: {workingDir}\n"
179
+ + allPrompt
180
+ ```
181
+
182
+ Not injected: USER.md / `prompts/root/` / `prompts/bootstrap/`.
183
+
184
+ ### Internal agents (`internal: true`)
185
+
186
+ The self-evolution agents (`__evo_agent__`, `__score__`, `__apply_agent__`) are platform tooling, not workspace-resident assistants. They get **none** of the workspace context: USER.md, INSTRUCTIONS.md (global + workspace-root), INDEX.md, and all three prompt scopes (`prompts/all` / `root` / `bootstrap`) are cleared in `composeSystemPrompt`. Only their own AGENT.md (which carries the full procedure) plus the tool list remains — this keeps their token budget clean.
187
+
188
+ > Caveat: `prompts/all` is where `TOOL_SHELL.md` (platform shell guidance) lives, so a `shell_exec`-capable internal agent (`__apply_agent__`) doesn't inherit it. `__apply_agent__`'s AGENT.md carries its own "Shell usage & platform" section to cover this — keep that in mind if adding shell to another internal agent.
189
+
190
+ ### Fallback
191
+
192
+ If `mdPrompt` is empty:
193
+ - Use `yamlConfig.system_prompt` if present
194
+ - Otherwise use the hard-coded default string
195
+
196
+ ## Agent roster
197
+
198
+ `buildAgentRoster(selfAgentId, team, isRoot)` ([session-agent-builder.ts](../../../packages/server/src/agents/session-agent-builder.ts)) builds a live team block listing the agents this session can delegate to — one `- \`<id>\` — <name>: <description>` line per teammate. The framing depends on `isRoot` (see "Root vs. sub-agent framing" below): a root gets the full `## Know Your Team Before You Act` orchestrator block, a sub-agent a lean `## Your Team` block.
199
+
200
+ **Who's on the list.** `scanAvailableAgents` minus `disabled` (workspace `disabled_items` table) minus `internal: true`, then narrowed to the agent's `team` whitelist via `isTeamMember(team, id)` — the same filter `start_session` / `query_agent` enforce server-side, so the roster never lists someone the agent can't actually reach. Self is treated like any other agent: it appears only when the agent's own id is in `team`. When listed it's pinned to the top and tagged `(you)` — purely a reading order ("who am I" before "who else") — with guidance to spawn parallel instances of itself for independent sub-tasks and to do serial work directly rather than self-delegate. The remaining teammates follow in scan order. (Add the agent's own id to its `team` to enable parallel self-spawn — the seed `default` agent does exactly this.)
201
+
202
+ **Gated on a non-empty team.** A roster is computed only when `canDelegate(yaml)` ([agent-loader.ts](../../../packages/server/src/agents/agent-loader.ts)) holds: `!internal` (evo / score / apply are platform tooling, not orchestrators) **and** a non-empty `team`. The very same predicate gates the session-tool bundle in `resolveBaseToolSet` — so the roster and the tools that act on it are granted together or not at all, never half. No team, no delegation: no session tools, no roster.
203
+
204
+ **Root and sub-agents both get a roster** — there's no `isRoot` gate on *whether* a roster appears (any agent with a non-empty `team` gets one). Runaway re-subcontracting used to be stopped by a blanket "root only" ban; it's now bounded by the per-agent `team` whitelist (which is also the delegation switch) plus `maxNestingDepth` (default 16). This lets a sub-agent legitimately delegate further (grandchild sessions) when its `agent.yaml` declares a team, while the depth cap and whitelist keep cascades finite.
205
+
206
+ **Root vs. sub-agent framing.** `isRoot` controls the *framing* around the roster, not its membership:
207
+ - **Root** gets `## Know Your Team Before You Act` — the roster plus the full orchestrator pep-talk (prefer delegation, fan out in parallel, don't poll, "I'll just do it myself" is rarely right). A root's job is to orchestrate, so the steering is on-message.
208
+ - **Sub-agent** gets `## Your Team` — the same roster plus a single line on when to hand off. A sub-agent's job is to *finish what it was handed*, not to keep re-subcontracting, so the orchestrator pep-talk would be noise (or actively push it to over-delegate). Self's line is also trimmed to just `(you)` (no "spawn parallel instances of yourself" nudge).
209
+
210
+ **Team whitelist = the delegation switch.** `agent.yaml` carries an optional `team: [id, …]`. A **non-empty** list is what *enables* delegation — it grants the whole session-tool bundle plus the roster (see `canDelegate`) AND restricts reach to exactly those ids. **Unset or empty `[]` means the agent cannot delegate at all** (no session tools, no roster) — this is a breaking change from the earlier "unset = every agent reachable" default; agents authored before this change that relied on the implicit-all behavior must now list their team explicitly. The `isTeamMember(team, targetId)` predicate still gates the three reach surfaces consistently — the roster (what the agent sees), `start_session`, and `query_agent` (server-side enforcement, so a hand-crafted call to a non-team agent is rejected). Self gets no special-casing: include the agent's own id to allow parallel self-spawn (the seed `default` agent lists `default`), omit it to block self-spawn — exactly like any other agent.
211
+
212
+ > Note: `isTeamMember(undefined, id)` still returns `true` in isolation (its job is "is this id reachable *given* a list"). The "unset = no delegation" rule lives one level up in `canDelegate`, which never calls `isTeamMember` when `team` is empty/unset — the roster is `''` and no session tools are built, so reachability is moot.
213
+
214
+ **Placement.** The roster is passed into `composeMdPrompt` as section 3, landing directly behind AGENT.md so the "who's on my team" read happens while model attention is still high (delegation is an orchestrator's first decision). Routing it through `composeMdPrompt` — rather than string-concatenating it after `mdPrompt` — is what gives it proper `\n\n---\n\n` separators on both sides; an earlier version appended it with a single `\n` and it glued onto the following `allPrompt` heading.
215
+
216
+ **Static, the single team source.** The roster is a *static* teammate list baked into the system prompt at session creation — it's the only place an orchestrator learns who its team is. (There used to be a `list_agents` tool that returned the same set at runtime; it was removed once the roster covered the discovery need — the static block is always present for a delegating agent, so a per-turn tool call was redundant.) For *detail* on a specific teammate (full tools / skills / model), the agent calls `query_agent <id>` — itself team-gated, so it can only inspect agents already on its roster.
217
+
218
+ ## Step 7 — Append skills and tool list
219
+
220
+ [session-manager.ts:682-697](../../../packages/server/src/agents/session-manager.ts#L682)
221
+
222
+ ### Skills (progressive disclosure)
223
+
224
+ When the YAML lists `skills`:
225
+ 1. `loadSkillMetadata()` — parses only each SKILL.md's frontmatter; skills disabled in the workspace DB (`disabled_items` table) are filtered out
226
+ 2. `buildSkillPrompt()` generates an `<available_skills>` XML block appended to the prompt
227
+ 3. Inject the `activate_skill` tool — the agent loads the full SKILL.md on demand
228
+
229
+ **Placeholder injection**: `{{var}}` inside SKILL.md and AGENT.md bodies is replaced by `renderMdBody` in [md-vars.ts](../../../packages/server/src/prompts/md-vars.ts). See [Placeholder rendering pipeline](#placeholder-rendering-pipeline) below.
230
+
231
+ ### Tool list
232
+
233
+ Tail-append:
234
+ ```
235
+ Your available tools: <tool1>, <tool2>, .... Only use tools in this list.
236
+ ```
237
+
238
+ ### Self-review (`draft`)
239
+
240
+ When the YAML `tools:` lists `draft`, `resolveBaseToolSet` ([session-manager.ts](../../../packages/server/src/agents/session-manager.ts)) builds the self-review tool via `createDraftTool()` and stashes its per-turn `reset` on the session (`session.draftReset`). It carries no prompt text of its own beyond the tool description — the reflection contract lives in the tool's `tool_result` (an adversarial review checklist), not in the system prompt.
241
+
242
+ It exists because a plain-text answer is single-pass: the agent loop only re-calls the model on a `tool_use` block, and `thinking` runs before the answer in the same call. `draft` lets the model materialise its answer as a tool call, get back a hostile-reviewer checklist, and critique the now-concrete draft on the next call. Opt-in per agent (no global switch); bounded to 3 rounds/turn by a closure counter reset at each turn-attempt. Full mechanics in [dev/tools.md](../dev/tools.md#self-review-tool).
243
+
244
+ ## Final injection order
245
+
246
+ ### Root agent
247
+
248
+ ```
249
+ [bootstrapPrompt] ← prefixed when needsBootstrap (ws > global)
250
+ USER.md ← workspace > global
251
+ AGENT.md ← workspace > global
252
+ ## Know Your Team Before You Act ← root's roster framing (dropped when team is empty/unset)
253
+ ## User Instructions ← ~/.halo/global/INSTRUCTIONS.md (suppressed when ws has its own)
254
+ ## User Instructions ← <ws>/.halo/INSTRUCTIONS.md (workspace root)
255
+ ## Project Knowledge ← <ws>/.halo/INDEX.md (or nudge)
256
+ "The project workspace is at: ..."
257
+ "Working directory: ..." ← if workingDir is set
258
+ rootPrompt ← prompts/root/*.md (ws > global)
259
+ allPrompt ← prompts/all/*.md (ws > global)
260
+ <available_skills> ← if yaml.skills non-empty
261
+ Your available tools: ...
262
+ ```
263
+
264
+ root-scope leads all-scope: root-only orchestrator guidance lands while attention is high, the generic tool layer trails (same rationale as the roster riding behind AGENT.md).
265
+
266
+ (A sub-agent's `working_dir` directory-chain INSTRUCTIONS.md sit in the `## User Instructions` region — plain markdown, right after the global/ws-root layer — see Sub-agent below. A user's `@scope` injects per-turn into the message, not here — below.)
267
+
268
+ ### Sub-agent
269
+
270
+ ```
271
+ AGENT.md
272
+ ## Your Team ← lean roster (only when the agent has a non-empty team)
273
+ ## User Instructions ← ~/.halo/global/INSTRUCTIONS.md (suppressed when ws has its own)
274
+ ## User Instructions ← <ws>/.halo/INSTRUCTIONS.md (workspace root)
275
+ ## User Instructions ← working_dir's directory-chain INSTRUCTIONS.md, headed ### <dir> (when working_dir set)
276
+ ## Project Knowledge
277
+ "The workspace root is: ..."
278
+ "Working directory: ..." ← one-line focus marker (the rules above are the actual content)
279
+ allPrompt ← prompts/all/*.md (ws > global)
280
+ <available_skills>
281
+ Your available tools: ...
282
+ ```
283
+
284
+ (Sub-agents get the lean `## Your Team` roster — not the root's `## Know Your Team Before You Act` block — and only when the agent has a non-empty team; no USER.md, no root-scope prompts.)
285
+
286
+ ## Directory-scoped instructions
287
+
288
+ Sub-directory `.halo/INSTRUCTIONS.md` files reach the model two ways, split by **who supplies the directory and how long it lives**:
289
+
290
+ Shared core: [`loadScopeBody(workspaceRoot, relDir)`](../../../packages/server/src/prompts/md-loader.ts) reads `.halo/INSTRUCTIONS.md` at every level along `workspaceRoot → relDir` **excluding the root** (the root's file is already in the system prompt) and returns a bare markdown body, each present level headed `### <dir>` (levels with no file are skipped; outer levels first). So a directory `a/b/c` pulls `a`, `a/b`, `a/b/c` — and if only an ancestor like `a/b` has a file, you still get it. The two consumers wrap that body differently:
291
+
292
+ | Trigger | Who | Lifetime | How it reaches the model |
293
+ |---|---|---|---|
294
+ | `working_dir` on `start_session` | parent agent | **persistent** — every turn, for the session's life | `composeMdPrompt` folds the **bare `loadScopeBody` markdown** into the `## User Instructions` region, right after the global/ws-root layer ([`session-agent-builder.composeSystemPrompt`](../../../packages/server/src/agents/session-agent-builder.ts)). No `<workspace-instructions>` wrapper — the section heading already frames it. `working_dir` is stored on `agent_sessions.working_dir` and restored on resume, so the rules are rebuilt into the system prompt on every `buildAgentInstance`. |
295
+ | `@scope <dir>` in a message | user (TUI / admin / channels) | **one-shot** — only the turn it rides on | `loadScopeInstructions` wraps the body in a self-describing `<workspace-instructions dir=... note=...>` block (it lands in the message stream, so it needs the tag + note to read as turn context); `SessionManager.expandScopeMarkers` strips the marker from the visible text and prepends the block to that message. Not re-injected on later turns. |
296
+
297
+ Why the split: `working_dir` is the sub-agent's persistent identity (it lives in that directory for its whole life), so its rules belong in the system prompt where they're present every turn — a first-turn-only injection would let the agent forget them after a few turns. `@scope` is ad-hoc context a user attaches to one message, so it stays loop-scoped. (The earlier `scope` args on `query_session` / `interrupt_session` were removed — they let a parent temporarily swap a child's directory rules, which contradicts working_dir being persistent identity.)
298
+
299
+ Invalid `@scope` dirs (outside workspace / missing / no INSTRUCTIONS.md along the path) are dropped with a `system` warning event; the turn still runs. The block does **not** change where tools execute — it is purely guidance.
300
+
301
+ Input completion: both the TUI (`@scope ` → directory-only path-suggest) and the admin composer (`@scope ` → `/files/search?dirsOnly=1` picker) complete directories as you type. `@scope` stays as literal text (unlike admin's `@file` mention, which is lifted into a separate reference list) so the server sees and expands it.
302
+
303
+ ## Placeholder rendering pipeline
304
+
305
+ Implementation: [md-vars.ts](../../../packages/server/src/prompts/md-vars.ts)
306
+
307
+ ### Two entry points, one renderer
308
+
309
+ 1. **AGENT.md**: in Step 3 (after MD reads), `buildAgentInstance` calls `renderMdBody(agentMd, ctx)` and then composes the prompt (Step 5). Renders on every session spawn (env/settings changes take effect next spawn).
310
+ 2. **SKILL.md**: on activation (skill-as-command or the `activate_skill` tool), the body is rendered before being returned to the agent.
311
+
312
+ Both entries build context via `buildRenderContext({args, workspaceRoot, workingDir, agentName})` and then call `renderMdBody(body, ctx)`.
313
+
314
+ ### RenderContext shape
315
+
316
+ ```ts
317
+ interface RenderContext {
318
+ builtin: { // built-ins
319
+ args?: string
320
+ workspace_root?: string
321
+ working_dir?: string
322
+ now?: string
323
+ user_name?: string // from USER.md frontmatter
324
+ ai_name?: string
325
+ agent_name?: string
326
+ // Channel origin (skill invocations only — undefined for AGENT.md
327
+ // render and admin/WS skill invocations). Filled by channel handlers
328
+ // when they call execSkillCommand. Skills like cron use
329
+ // these to default targets to the originating chat.
330
+ 'channel.type'?: string // 'telegram' | 'wechat' | 'web'
331
+ 'channel.account_id'?: string
332
+ 'channel.chat_id'?: string // telegram chat id / wechat openId
333
+ }
334
+ settings: Record<string, unknown> // merged settings.yaml (env not yet substituted)
335
+ }
336
+ ```
337
+
338
+ `settings` comes from `loadMergedSettings(workspaceRoot)`:
339
+ - `~/.halo/secrets/settings.yaml` is the base
340
+ - `<workspaceRoot>/.halo/settings.yaml` deep-merges on top (field-level, not block-level)
341
+
342
+ ### Substitution rules
343
+
344
+ Two regexes:
345
+ - `{{\s*([\w-][\w.-]*)\s*}}` — placeholders
346
+ - `<<([A-Z_][A-Z0-9_]*)>>` — env vars
347
+
348
+ Resolving `{{xxx}}`:
349
+
350
+ 1. No dot → look up `builtin[xxx]`; hit returns the value
351
+ 2. **Channel built-ins**: dotted names starting with `channel.` (e.g. `channel.type`, `channel.chat_id`) are looked up directly in `builtin` by full key. Empty string when the field is unset (e.g. ws-origin skill invocations have no `chat_id`) — keeps skill bodies clean rather than leaving literal `{{channel.chat_id}}` in the rendered output.
352
+ 3. **Whitelist check**: any other dotted name must match `^[\w-]+\.params\.[\w-][\w.-]*$` (e.g. `tavily-web-search.params.api_key`); anything else (including `<id>.secrets.*`, `general.*`, `logging.*`) keeps the literal `{{xxx}}` and logs a warning
353
+ 4. `resolvePath(settings, xxx)` walks the settings tree
354
+ 5. Stringify the leaf, then replace `<<ENV_NAME>>` → `process.env.ENV_NAME` (**missing env → keep the literal `<<ENV_NAME>>`** so users see the typo and the API call fails loudly)
355
+ 6. No match or value is null → keep `{{xxx}}` literal + warn
356
+
357
+ ### Scope convention
358
+
359
+ settings.yaml has three top-level scopes with strict roles:
360
+
361
+ | Path shape | Purpose | Replaced in MD? | Replaced in `shell_exec`? |
362
+ |---|---|---|---|
363
+ | `general.<key>` | System config (session limits, sandbox, compact, logging) — read by `config.ts` | No (renderer rejects) | No |
364
+ | `<id>.secrets.<key>` | Server-side credentials (model providers, OAuth apps, signing keys) — read by `config.ts getServerSecret()` | **No (renderer hard-rejects)** | **No** |
365
+ | `<id>.params.<key>` | Parameters referenced by agents / skills | Yes | Yes |
366
+
367
+ `<id>` is a provider id (declared in `models/<id>.yaml`) or a skill id (declared in `skills/<id>/config.yaml`). Each declarer ships its own schema; values land in `~/.halo/secrets/settings.yaml` at the same path.
368
+
369
+ The two-renderer enforcement (MD render + shell substitute) is the **single security boundary** preventing a skill or agent from exfiltrating server-side `<id>.secrets.<key>` keys via shell. See [requirements/settings.md](../requirements/settings.md) for details.
370
+
371
+ ### Skill short-form rewrite
372
+
373
+ Inside a SKILL.md, authors can write `{{params.<key>}}` (no namespace) for ergonomics. At `activate_skill` time the loader rewrites every short form into `{{<this-skill-id>.params.<key>}}` before handing the body to the agent. AGENT.md does **not** get this rewrite — it must use the fully-qualified form, since there's no single "owning" skill id.
374
+
375
+ ### Environment-variable semantics
376
+
377
+ - **Trust boundary**: `<<ENV_NAME>>` is *only* expanded inside values that came from settings.yaml — i.e. inside MD content rendered by `renderMdBody` resolving a `{{<id>.params.<key>}}` lookup, or inside cmd text after a `{{<id>.params.<key>}}` substitution in `shell_exec`. **Cmd or MD text the agent itself writes is never scanned** — `shell_exec "echo <<HOME>>"` keeps the literal `<<HOME>>`. This prevents an agent from naming an env var (whose value it learned by reading settings.yaml or guessing) and forcing the server to dump it.
378
+ - `/api/settings/schema` returning values to the frontend **does not** substitute — the user editing settings sees the literal `<<NANO_BANANA_KEY>>`, not the real key. Secrets are masked further (e.g. `AK****ST`) before transport.
379
+ - If the env var is unset, the agent receives the literal `<<NANO_BANANA_KEY>>`; API calls will fail and the error message tells the user what to fix.
380
+
381
+ ### Why this layout
382
+
383
+ Schema declared by each package + values stored centrally is the same model VSCode uses for `contributes.configuration` + `settings.json`:
384
+ - Removing a package only removes its schema; values stay in `settings.yaml` (orphans), surfacing in the Settings UI's "Unclaimed values" tab for the user to prune.
385
+ - Reinstalling the package re-attaches its values automatically.
386
+ - A skill ships `config.yaml` next to `SKILL.md`, so the schema travels with the code — no central registry to maintain.
387
+
388
+ ## working_dir behaviour
389
+
390
+ - `start_session` accepts a `working_dir` argument (absolute or workspace-relative)
391
+ - Default: `workspaceRoot`
392
+ - Enforced inside workspace (path traversal is rejected)
393
+ - Non-existent directory → spawn refused
394
+ - Persisted as a workspace-relative path on the `agent_sessions.working_dir` column (null = project root)
395
+ - On resume, the relative path is resolved against `workspaceRoot` and passed to `buildAgentInstance`
396
+ - The directory-chain `.halo/INSTRUCTIONS.md` along root→working_dir is baked into the system prompt every turn (see [Directory-scoped instructions](#directory-scoped-instructions)) — because working_dir is persistent identity, the rules are present on every turn, not just the first
397
+
398
+ ## Key invariants
399
+
400
+ - **Override precedence**: workspace > global (USER.md, AGENT.md, INSTRUCTIONS.md, prompts/)
401
+ - **agents/ & skills/ override**: whole-folder. `<ws>/.halo/agents/<id>/` (or `skills/<id>/`) exists → serves that agent/skill entirely (agent.yaml + AGENT.md, or SKILL.md + resources), global folder ignored, no per-file fallback
402
+ - **INSTRUCTIONS.md**: workspace root INSTRUCTIONS.md suppresses global; subdirectory chain stacks on top — in the system prompt every turn when it's a sub-agent's `working_dir` (persistent), or per-turn in the message via user `@scope` (one-shot)
403
+ - **prompts/ override**: `<ws>/.halo/prompts/<scope>/` directory exists → entirely replaces global's same scope (directory-level, no per-file merge)
404
+ - **INDEX.md is project-root only**: subdirectory INDEX files are not auto-loaded
405
+ - **Root vs sub-agent**: determined by `!parentId`
406
+ - **Sub-agent does not inject**: USER.md / `prompts/root/` / `prompts/bootstrap/`
407
+ - **Internal agents (`internal: true`)**: get no workspace context at all — USER.md / INSTRUCTIONS.md / INDEX.md / `prompts/{all,root,bootstrap}` all cleared; only their own AGENT.md + tool list remain
408
+ - **Bootstrap trigger**: `!parentId && !userMd`
409
+ - **System prompts are external**: resolved from workspace then global `prompts/{bootstrap,all,root}/*.md`, read live, sorted by filename.
410
+ - **System prompt missing**: warn + use built-in fallback; `system-prompts.ts` is the seed source and fallback
411
+ - **`.halo/` is not grep/globbed**: use `file_read` + INDEX.md navigation