@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.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/admin-out/404.html +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_buildManifest.js +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_ssgManifest.js +1 -0
- package/admin-out/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js +18 -0
- package/admin-out/_next/static/chunks/200.3b8f8a74f005a134.js +1 -0
- package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +1 -0
- package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +1 -0
- package/admin-out/_next/static/chunks/304eda7d.b1ee7f523653e027.js +1 -0
- package/admin-out/_next/static/chunks/330.3b784cf0dcd69163.js +1 -0
- package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +1 -0
- package/admin-out/_next/static/chunks/443.d1b289781793f8de.js +25 -0
- package/admin-out/_next/static/chunks/503-7c6d9935df6e0cea.js +1 -0
- package/admin-out/_next/static/chunks/533.5f2d4bfe3d076095.js +1 -0
- package/admin-out/_next/static/chunks/579.a2718ddd395a2f3a.js +1 -0
- package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +1 -0
- package/admin-out/_next/static/chunks/65.859665ba6717967a.js +1 -0
- package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +1 -0
- package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +1 -0
- package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +1 -0
- package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +1 -0
- package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +1 -0
- package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +1 -0
- package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +1 -0
- package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +1 -0
- package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +1 -0
- package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +1 -0
- package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +1 -0
- package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +1 -0
- package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +1 -0
- package/admin-out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/admin-out/_next/static/chunks/webpack-8bd7ef88a6527feb.js +1 -0
- package/admin-out/_next/static/css/659eccb5db697b76.css +1 -0
- package/admin-out/_next/static/css/8fce4e1a54dac12a.css +3 -0
- package/admin-out/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/admin-out/_next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/admin-out/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- package/admin-out/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/admin-out/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/admin-out/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- package/admin-out/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- package/admin-out/favicon.ico +0 -0
- package/admin-out/icon.svg +18 -0
- package/admin-out/index.html +1 -0
- package/admin-out/index.txt +23 -0
- package/admin-out/monaco/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
- package/admin-out/monaco/vs/abap-D-t0cyap.js +1 -0
- package/admin-out/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/admin-out/monaco/vs/assets/css.worker-HnVq6Ewq.js +93 -0
- package/admin-out/monaco/vs/assets/editor.worker-Be8ye1pW.js +26 -0
- package/admin-out/monaco/vs/assets/html.worker-B51mlPHg.js +470 -0
- package/admin-out/monaco/vs/assets/json.worker-DKiEKt88.js +58 -0
- package/admin-out/monaco/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
- package/admin-out/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/admin-out/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/bat-C397hTD6.js +1 -0
- package/admin-out/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/admin-out/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/admin-out/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/admin-out/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/admin-out/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/admin-out/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/admin-out/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/admin-out/monaco/vs/css-CaeNmE3S.js +3 -0
- package/admin-out/monaco/vs/cssMode-CjiAH6dQ.js +1 -0
- package/admin-out/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/admin-out/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/admin-out/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/admin-out/monaco/vs/ecl-30fUercY.js +1 -0
- package/admin-out/monaco/vs/editor/editor.main.css +1 -0
- package/admin-out/monaco/vs/editor/editor.main.js +5 -0
- package/admin-out/monaco/vs/editor.api-CalNCsUg.js +903 -0
- package/admin-out/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/admin-out/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/admin-out/monaco/vs/freemarker2-Cz_sV6Md.js +3 -0
- package/admin-out/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/admin-out/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/admin-out/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/admin-out/monaco/vs/handlebars-OwglfO-1.js +1 -0
- package/admin-out/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/admin-out/monaco/vs/html-Pa1xEWsY.js +1 -0
- package/admin-out/monaco/vs/htmlMode-Bz67EXwp.js +1 -0
- package/admin-out/monaco/vs/ini-CsNwO04R.js +1 -0
- package/admin-out/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/admin-out/monaco/vs/javascript-PczUCGdz.js +1 -0
- package/admin-out/monaco/vs/jsonMode-DULH5oaX.js +7 -0
- package/admin-out/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/admin-out/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/admin-out/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/less-C0eDYdqa.js +2 -0
- package/admin-out/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/admin-out/monaco/vs/liquid-DqKjdPGy.js +1 -0
- package/admin-out/monaco/vs/loader.js +1368 -0
- package/admin-out/monaco/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
- package/admin-out/monaco/vs/lua-DtygF91M.js +1 -0
- package/admin-out/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/admin-out/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/admin-out/monaco/vs/mdx-DEWtB1K5.js +1 -0
- package/admin-out/monaco/vs/mips-CiYP61RB.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-D2OdxNBt.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-DO3azKX8.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-EcChJV6a.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-qLAYrEOP.js +1 -0
- package/admin-out/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/admin-out/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/admin-out/monaco/vs/nls.messages-loader.js +1 -0
- package/admin-out/monaco/vs/nls.messages.cs.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.de.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.es.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.fr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.it.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.ja.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.js.js +10 -0
- package/admin-out/monaco/vs/nls.messages.ko.js.js +25 -0
- package/admin-out/monaco/vs/nls.messages.pl.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.pt-br.js.js +6 -0
- package/admin-out/monaco/vs/nls.messages.ru.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.tr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.zh-cn.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.zh-tw.js.js +15 -0
- package/admin-out/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/admin-out/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/admin-out/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/admin-out/monaco/vs/perl-DABw_TcH.js +1 -0
- package/admin-out/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/admin-out/monaco/vs/php-D_kh-9LK.js +1 -0
- package/admin-out/monaco/vs/pla-VfZjczW0.js +1 -0
- package/admin-out/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/admin-out/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/admin-out/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/admin-out/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/admin-out/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/admin-out/monaco/vs/python-Cr0UkIbn.js +1 -0
- package/admin-out/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/admin-out/monaco/vs/r-f8dDdrp4.js +1 -0
- package/admin-out/monaco/vs/razor-BYAHOTkz.js +1 -0
- package/admin-out/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/admin-out/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/admin-out/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/admin-out/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/admin-out/monaco/vs/rust-Bfetafyc.js +1 -0
- package/admin-out/monaco/vs/sb-3GYllVck.js +1 -0
- package/admin-out/monaco/vs/scala-foMgrKo1.js +1 -0
- package/admin-out/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/admin-out/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/admin-out/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/admin-out/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/admin-out/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/admin-out/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/admin-out/monaco/vs/sql-32GpJSV2.js +1 -0
- package/admin-out/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/admin-out/monaco/vs/swift-n-2HociN.js +3 -0
- package/admin-out/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/admin-out/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/admin-out/monaco/vs/tsMode-CZz1Umrk.js +11 -0
- package/admin-out/monaco/vs/twig-C6taOxMV.js +1 -0
- package/admin-out/monaco/vs/typescript-DfOrAzoV.js +1 -0
- package/admin-out/monaco/vs/typespec-D-PIh9Xw.js +1 -0
- package/admin-out/monaco/vs/vb-Dyb2648j.js +1 -0
- package/admin-out/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/admin-out/monaco/vs/workers-DcJshg-q.js +1 -0
- package/admin-out/monaco/vs/xml-CdsdnY8S.js +1 -0
- package/admin-out/monaco/vs/yaml-DYGvmE88.js +1 -0
- package/bin/halo.js +9 -0
- package/bundled-docs/design/agent.md +145 -0
- package/bundled-docs/design/architecture.md +270 -0
- package/bundled-docs/design/background-dispatch.md +151 -0
- package/bundled-docs/design/command.md +58 -0
- package/bundled-docs/design/cron.md +88 -0
- package/bundled-docs/design/evolution.md +383 -0
- package/bundled-docs/design/express-self.md +145 -0
- package/bundled-docs/design/feishu.md +224 -0
- package/bundled-docs/design/halo-city.md +112 -0
- package/bundled-docs/design/prompt-system.md +411 -0
- package/bundled-docs/design/session.md +321 -0
- package/bundled-docs/design/slack.md +212 -0
- package/bundled-docs/design/storage.md +578 -0
- package/bundled-docs/design/telegram.md +161 -0
- package/bundled-docs/design/web.md +222 -0
- package/bundled-docs/design/wechat.md +146 -0
- package/bundled-docs/design/ws.md +144 -0
- package/bundled-docs/dev/acp-adapter.md +337 -0
- package/bundled-docs/dev/add-channel.md +376 -0
- package/bundled-docs/dev/add-model-provider.md +386 -0
- package/bundled-docs/dev/api.md +686 -0
- package/bundled-docs/dev/deploy.md +198 -0
- package/bundled-docs/dev/desktop-packaging.md +327 -0
- package/bundled-docs/dev/env.md +158 -0
- package/bundled-docs/dev/logging.md +85 -0
- package/bundled-docs/dev/previews.md +225 -0
- package/bundled-docs/dev/tools.md +331 -0
- package/bundled-docs/guide/README.md +29 -0
- package/bundled-docs/guide/agents.md +264 -0
- package/bundled-docs/guide/channels/README.md +62 -0
- package/bundled-docs/guide/channels/acp.md +150 -0
- package/bundled-docs/guide/channels/feishu.md +154 -0
- package/bundled-docs/guide/channels/slack.md +156 -0
- package/bundled-docs/guide/channels/telegram.md +112 -0
- package/bundled-docs/guide/channels/web.md +149 -0
- package/bundled-docs/guide/channels/wechat.md +110 -0
- package/bundled-docs/guide/chat.md +112 -0
- package/bundled-docs/guide/cli.md +197 -0
- package/bundled-docs/guide/getting-started.md +93 -0
- package/bundled-docs/guide/secrets-and-credentials.md +161 -0
- package/bundled-docs/guide/sessions.md +104 -0
- package/bundled-docs/guide/skills.md +302 -0
- package/bundled-docs/guide/testing-agents-and-skills.md +136 -0
- package/bundled-docs/guide/workspace.md +131 -0
- package/bundled-docs/plans/cron-tasks.md +362 -0
- package/bundled-docs/plans/design-suggestions.md +203 -0
- package/bundled-docs/plans/issue-session-deep-nesting.md +208 -0
- package/bundled-docs/plans/launch-plan.md +111 -0
- package/bundled-docs/plans/md-and-agent-refactor.md +197 -0
- package/bundled-docs/plans/remote-mac-worker.md +196 -0
- package/bundled-docs/plans/roadmap.md +40 -0
- package/bundled-docs/plans/self-evolution.md +1379 -0
- package/bundled-docs/plans/todo.md +32 -0
- package/bundled-docs/plans/weixin-channel.md +255 -0
- package/bundled-docs/requirements/agent.md +92 -0
- package/bundled-docs/requirements/chat.md +81 -0
- package/bundled-docs/requirements/command.md +130 -0
- package/bundled-docs/requirements/editor.md +82 -0
- package/bundled-docs/requirements/explorer.md +89 -0
- package/bundled-docs/requirements/overview.md +62 -0
- package/bundled-docs/requirements/session.md +73 -0
- package/bundled-docs/requirements/settings.md +224 -0
- package/bundled-docs/requirements/skill.md +125 -0
- package/bundled-docs/requirements/source-control.md +82 -0
- package/bundled-docs/requirements/terminal.md +118 -0
- package/bundled-docs/requirements/workspace.md +80 -0
- package/bundled-docs/test/docs-seeding.md +101 -0
- package/bundled-docs/test/session.md +374 -0
- package/dist/index.js +26750 -0
- package/package.json +67 -0
- package/templates/INSTRUCTIONS.md +25 -0
- package/templates/agents/__apply_agent__/AGENT.md +229 -0
- package/templates/agents/__apply_agent__/agent.yaml +30 -0
- package/templates/agents/__evo_agent__/AGENT.md +323 -0
- package/templates/agents/__evo_agent__/agent.yaml +29 -0
- package/templates/agents/__score__/AGENT.md +177 -0
- package/templates/agents/__score__/agent.yaml +29 -0
- package/templates/agents/deep-executor/AGENT.md +60 -0
- package/templates/agents/deep-executor/agent.yaml +25 -0
- package/templates/agents/default/AGENT.md +38 -0
- package/templates/agents/default/agent.yaml +51 -0
- package/templates/agents/executor/AGENT.md +38 -0
- package/templates/agents/executor/agent.yaml +25 -0
- package/templates/aliases.yaml +26 -0
- package/templates/canvas/self.html +660 -0
- package/templates/config.yaml +69 -0
- package/templates/models/anthropic.yaml +47 -0
- package/templates/models/aws-bedrock-claude-invoke.yaml +156 -0
- package/templates/models/aws-bedrock-mantle.yaml +89 -0
- package/templates/models/deepseek.yaml +62 -0
- package/templates/models/doubao.yaml +137 -0
- package/templates/models/hunyuan.yaml +56 -0
- package/templates/models/kimi.yaml +41 -0
- package/templates/models/mimo-token-plan-china.yaml +61 -0
- package/templates/models/minimax.yaml +80 -0
- package/templates/models/openai.yaml +51 -0
- package/templates/models/qwen.yaml +78 -0
- package/templates/optional-skills/tavily-web-search/SKILL.md +91 -0
- package/templates/optional-skills/tavily-web-search/config.yaml +10 -0
- package/templates/prompts/all/TOOL_GUIDELINES.md +35 -0
- package/templates/prompts/all/TOOL_SHELL.md +4 -0
- package/templates/prompts/all/TOOL_SHELL.windows.md +7 -0
- package/templates/prompts/bootstrap/BOOTSTRAP.md +26 -0
- package/templates/prompts/root/WORKSPACE_MEMORY.md +11 -0
- package/templates/schema.sql +30 -0
- package/templates/skills/acp/SKILL.md +236 -0
- package/templates/skills/acp/templates/SKILL.md.tmpl +74 -0
- package/templates/skills/acp/templates/ask.py +176 -0
- package/templates/skills/acp/templates/config.yaml.tmpl +33 -0
- package/templates/skills/agent/SKILL.md +128 -0
- package/templates/skills/aws-knowledge/SKILL.md +82 -0
- package/templates/skills/aws-knowledge/scripts/aws_kb.py +61 -0
- package/templates/skills/cron/SKILL.md +217 -0
- package/templates/skills/cron/manage-cron.py +434 -0
- package/templates/skills/halo/SKILL.md +98 -0
- package/templates/skills/nova-web-search/SKILL.md +65 -0
- package/templates/skills/self/SKILL.md +139 -0
- package/templates/skills/send-file/SKILL.md +28 -0
- package/templates/skills/send-file/feishu.md +16 -0
- package/templates/skills/send-file/slack.md +7 -0
- package/templates/skills/send-file/telegram.md +9 -0
- package/templates/skills/send-file/web.md +6 -0
- package/templates/skills/send-file/wechat.md +8 -0
- package/templates/skills/skill/SKILL.md +131 -0
- package/templates/skills/workspace/SKILL.md +28 -0
- package/templates/skills/workspace/setup-tidy.md +265 -0
- package/templates/skills/workspace/share.md +187 -0
- package/templates/skills/workspace/stage.py +487 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Session persistence and orchestration — design suggestions
|
|
2
|
+
|
|
3
|
+
Captured while debugging the "third message freeze" bug (`text content blocks must be non-empty`) — documents the structural issues found and proposes improvements over the current state. Everything revolves around **session message state**: multiple write paths, multiple sources of truth, and lossy backward inferences — the shared root of that bug.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Make message persistence one-way — `rawMessages` as the single source of truth
|
|
8
|
+
|
|
9
|
+
### Current state
|
|
10
|
+
|
|
11
|
+
Each session maintains two copies:
|
|
12
|
+
|
|
13
|
+
- **`rawMessages`**: Strands Agent's internal `agent.messages`, Bedrock's native format including full `tool_use` / `tool_result` pairs. Persisted by `saveAgentState` at turn end.
|
|
14
|
+
- **`messages`**: the UI display log, assembled by `event-processor` from streaming events (assistant text, tool_call cards, usage badges, etc.). Rendered directly on refresh.
|
|
15
|
+
|
|
16
|
+
The two paths write independently; consistency relies on order-of-ops going right.
|
|
17
|
+
|
|
18
|
+
### Problem
|
|
19
|
+
|
|
20
|
+
This bug came from a third path — `handler.ts`'s `restoreHistory(sid, client.messageLog)` — that tried to reverse the UI log back into `rawMessages`. Tool-only assistant turns in the UI log have empty-string content (tool cards don't live in the content field), so the inverse maps them to `{type: 'text', text: ''}` and Bedrock rejects it.
|
|
21
|
+
|
|
22
|
+
As long as the "UI log → rawMessages" direction exists, "restore agent state from UI" will keep producing bugs like this.
|
|
23
|
+
|
|
24
|
+
### Suggestion
|
|
25
|
+
|
|
26
|
+
- **`rawMessages` is the sole source of truth**; the UI log is derived.
|
|
27
|
+
- **Never infer `rawMessages` from the UI log.**
|
|
28
|
+
- On refresh / reconnect: UI log is loaded directly from disk (for rendering), `rawMessages` is lazily loaded by `ensureSession` (for the next Bedrock call). The two paths run **in parallel and don't rebuild each other.**
|
|
29
|
+
- Compact should also operate on `rawMessages` — summarise, replace the old slice, then derive the new UI log from the event stream. Not "edit UI log then reverse-derive".
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Run repair once before every Bedrock call
|
|
34
|
+
|
|
35
|
+
### Current state
|
|
36
|
+
|
|
37
|
+
`repairConversationMessages` currently runs:
|
|
38
|
+
|
|
39
|
+
- At turn end (end of `runAgentTurn`)
|
|
40
|
+
- After interrupt / stop
|
|
41
|
+
- After compact
|
|
42
|
+
|
|
43
|
+
But **not before `sendUserMessage` hands over to Bedrock**.
|
|
44
|
+
|
|
45
|
+
### Problem
|
|
46
|
+
|
|
47
|
+
On-disk `rawMessages` may be unhealthy due to older bugs, external edits, version upgrades, etc. Today, once `ensureSession` loads bad state from disk, it gets sent to Bedrock and triggers the same kind of crash we just hit.
|
|
48
|
+
|
|
49
|
+
I've added "drop empty text blocks" as a safety net in `conversation-repair.ts`; still, moving the defensive step forward catches future unknown bad data.
|
|
50
|
+
|
|
51
|
+
### Suggestion
|
|
52
|
+
|
|
53
|
+
- In `sendUserMessage`, before hitting `handleUserTurn`, call `repairConversationMessages(session.agent.messages)`.
|
|
54
|
+
- Repair becomes the "last gate into Bedrock", so wherever rawMessages came from (new / disk restore / older-version file), it's cleansed.
|
|
55
|
+
- Performance is fine: repair is O(n) and n is not large.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 3. Sub-session log persistence: move "merge-save" out of event-processor, into SessionManager
|
|
60
|
+
|
|
61
|
+
### Current state
|
|
62
|
+
|
|
63
|
+
`event-processor.ts`'s `saveSubSession`:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
On every query_session / start_session
|
|
67
|
+
→ subSessionLogs initialised in memory
|
|
68
|
+
→ event stream writes into it
|
|
69
|
+
→ on agent_done: read from disk → merge by message id → write back
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`subSessionLogs` is client-connection-level (WebSocket), rebuilt per query and holding only this turn's events. Hence the merge.
|
|
73
|
+
|
|
74
|
+
### Problem
|
|
75
|
+
|
|
76
|
+
- **merge-by-id depends on "ids are consistent and unique"**. Any id jitter, reconnect-leftover state, or duplicate `initSubSessionLog` call produces lost or duplicate messages.
|
|
77
|
+
- **Persistence is coupled to the network layer**. event-processor should just forward events to the WS; here it also takes on "cross-connection disk merge". Violates CLAUDE.md's "persistence must not depend on in-memory transient state" — `subSessionLogs` is in-memory transient state.
|
|
78
|
+
- **Reconnect / refresh is error-prone**: a new connection creates a new `subSessionLogs`; unflushed events from the old one are lost; detach creates yet another independent copy in `backgroundHandler`, and reconciling both is a hazard.
|
|
79
|
+
|
|
80
|
+
### Event loss risk (extended)
|
|
81
|
+
|
|
82
|
+
This is a separate risk beyond the merge mechanism — an **eye-vs-disk** inconsistency where **events actually happened, but the log doesn't reflect them**. Scenarios:
|
|
83
|
+
|
|
84
|
+
1. **Stream buffer not flushed before a crash**. `stream` events aggregate into in-memory `turnContentBlocks` / `streamBuffer`; only `usage` / `agent_done` / `complete` trigger `flushAssistantMessage` + `saveSubSession`. Crash beforehand (OOM, uncaught exception, kill) loses every piece of streaming text and tool_call for the turn — disk has no trace the agent said anything.
|
|
85
|
+
|
|
86
|
+
2. **WS-disconnect brief window**. Between disconnect and `backgroundHandler` takeover is a window where `sendJson` fails and the event is silently dropped; grace expiry triggers a single `save()`, and a crash in between loses the whole stretch.
|
|
87
|
+
|
|
88
|
+
3. **Merge id jitter losing messages**. `saveSubSession` dedupes by `m.id`, but `genId()`'s counter is process-scoped — process restart resets the counter. A restart between two flushes can produce same-id-different-message entries; merge overwrites them.
|
|
89
|
+
|
|
90
|
+
4. **Event semantics vs disk state out of sync**. Frontend / logs already saw a `tool_call` fire (went through `sendJson`), but it's only in the in-memory `messageLog`; the next `saveSubSession` writes it. Checking disk in between makes it look like nothing happened. Fatal for audit-log use cases.
|
|
91
|
+
|
|
92
|
+
### Suggestion
|
|
93
|
+
|
|
94
|
+
**Short-term (bundle with the merge issue)**:
|
|
95
|
+
- SessionManager **owns** the sub-session UI log (persisted alongside `rawMessages`), append-only, single-process concurrency-safe.
|
|
96
|
+
- event-processor reverts to **pure forwarding**: receive event → push to WS; no persistence duty.
|
|
97
|
+
- Tighten flush granularity from "key events" to "every tool_call / tool_result / every N seconds" to shrink the crash window.
|
|
98
|
+
|
|
99
|
+
**Long-term (fully eliminate event loss)**:
|
|
100
|
+
- Switch to an **append-only JSONL event log**: every agent event lands as a new line in `.halo/sessions/{agentId}/{sessionId}.jsonl` the moment it fires.
|
|
101
|
+
- Renderers aggregate to `SessionMessage[]` on load (event-sourcing projection).
|
|
102
|
+
- Cost: higher disk I/O (one-write-per-stream-delta is too much). Compromise: "every tool_call / tool_result / end-of-assistant-paragraph / every 500 ms batch append".
|
|
103
|
+
- Benefit: crash window shrinks from "whole turn" to "last flush interval"; disk is a faithful log of events, no code-path skipping `save()` loses a whole stretch.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. `query_session` to an IDLE target: `onComplete` vs `releaseSession` race
|
|
108
|
+
|
|
109
|
+
### Current state
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// session-manager.ts :1166
|
|
113
|
+
this.runSession(targetSessionId, prefix + message).then((result) => {
|
|
114
|
+
onComplete?.(result)
|
|
115
|
+
})
|
|
116
|
+
return `Message sent to session ${targetSessionId}...`
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`runSession` is fire-and-forget with `onComplete` in `.then()`. But `runSession`'s own `finally` (:1030) immediately calls `releaseSession`.
|
|
120
|
+
|
|
121
|
+
### Problem
|
|
122
|
+
|
|
123
|
+
JavaScript microtask scheduling usually keeps `finally` before the `.then()` callback, but the race exists:
|
|
124
|
+
|
|
125
|
+
- `finally`'s `releaseSession` drops the session from memory.
|
|
126
|
+
- Immediately after, `.then(onComplete)` fires; `onComplete` itself calls `querySession(sessionId, childSessionId, result)` (auto-report to source), and that `querySession` does `ensureSession(childSessionId)` — which has to go through the disk restore path since memory is gone.
|
|
127
|
+
- If the disk save hasn't fully flushed (`saveAgentState` is synchronous but can be interleaved by the Node event loop), or something else is writing concurrently, `ensureSession` might read stale state.
|
|
128
|
+
|
|
129
|
+
Not guaranteed, but the window exists. E3 (query_session to an idle sub-agent) can likely reproduce under concurrency / repetition.
|
|
130
|
+
|
|
131
|
+
### Suggestion
|
|
132
|
+
|
|
133
|
+
- Chain `onComplete` onto `session.promise` itself, so "`runSession` fully finishes (including `releaseSession`) → `onComplete` → next `querySession`" runs strictly in sequence.
|
|
134
|
+
- Or simpler: have `querySession` itself `await runSession` and call `onComplete` synchronously, avoiding `.then()` entirely.
|
|
135
|
+
- Add a "just-released session stays in memory for N ms" cache window in `ensureSession` to damp rapid release/restore cycles.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 5. Post-disconnect event replay needs a barrier on reconnect
|
|
140
|
+
|
|
141
|
+
### Current state
|
|
142
|
+
|
|
143
|
+
WS disconnects:
|
|
144
|
+
- Session marked "detached", `backgroundHandler` registered
|
|
145
|
+
- Events flow into `detached.pendingEvents`
|
|
146
|
+
|
|
147
|
+
WS reconnects:
|
|
148
|
+
- `handleSubscribe` detects the detached session → replays `pendingEvents` one by one via `sendJson`
|
|
149
|
+
- Meanwhile switches the listener back to the new WS's live callback
|
|
150
|
+
|
|
151
|
+
### Problem
|
|
152
|
+
|
|
153
|
+
There's no barrier between listener switch and `pendingEvents` replay. If SessionManager is currently emitting a new event:
|
|
154
|
+
|
|
155
|
+
- The new event (B) goes through the live listener to the new WS
|
|
156
|
+
- Older `pendingEvents` (A) are still being sent one-by-one
|
|
157
|
+
- Frontend sees B before A — out of order
|
|
158
|
+
|
|
159
|
+
F2 ("disconnect → reconnect") + "steady events during disconnect + fresh events at the moment of reconnect" can reproduce it.
|
|
160
|
+
|
|
161
|
+
### Suggestion
|
|
162
|
+
|
|
163
|
+
- On reconnect, hold a lock: `pendingEvents` keeps accepting but doesn't **drain**; the listener is still on the background handler.
|
|
164
|
+
- Atomic switchover: `listener replace + pendingEvents drain + subSessionLogs restore` is one transaction; new events during this keep flowing to `pendingEvents`.
|
|
165
|
+
- After the transaction, flush `pendingEvents` and switch the listener to the new WS.
|
|
166
|
+
- Add an integration test: F2 scenario + forced concurrent events; watch the sequence numbers arriving on the frontend are monotonically increasing.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 6. Clean up the deprecated `destroySessions`
|
|
171
|
+
|
|
172
|
+
### Current state
|
|
173
|
+
|
|
174
|
+
`session-manager.ts` has a `@deprecated`-tagged `destroySessions` method with a doc saying "Use deleteSession + deleteSessionLogs in Phase 3".
|
|
175
|
+
|
|
176
|
+
### Problem
|
|
177
|
+
|
|
178
|
+
- It's debt — deprecated methods can still be called accidentally, and they raise eyebrows in review.
|
|
179
|
+
- New readers get confused: which one do I use?
|
|
180
|
+
- Keeping two deletion paths means every future change has to consider both.
|
|
181
|
+
|
|
182
|
+
### Suggestion
|
|
183
|
+
|
|
184
|
+
- Just delete `destroySessions`.
|
|
185
|
+
- Grep-verify no callers (from `.halo/docs/` through `packages/`).
|
|
186
|
+
- If stragglers exist, migrate them to `deleteSession` + `deleteSessionLogs`, then delete.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Priority
|
|
191
|
+
|
|
192
|
+
By fix-value vs change-footprint:
|
|
193
|
+
|
|
194
|
+
| # | Suggestion | Priority | Scope |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| 2 | Repair before sendUserMessage | High | one line |
|
|
197
|
+
| 6 | Delete destroySessions | High | one method |
|
|
198
|
+
| 1 | One-way (forbid UI→raw inverse) | High | architectural principle |
|
|
199
|
+
| 4 | Fix onComplete race | Medium | one callback |
|
|
200
|
+
| 5 | Reconnect event barrier | Medium | one subsystem |
|
|
201
|
+
| 3 | Sink sub-session persistence | Low, long-term | larger refactor |
|
|
202
|
+
|
|
203
|
+
1, 2, 6 can be done right away; 4, 5 deserve integration tests first. 3 is the long-term direction — not urgent, but inevitable.
|
|
@@ -0,0 +1,208 @@
|
|
|
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?
|
|
@@ -0,0 +1,111 @@
|
|
|
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仔先把素材/分镜全做好?
|