Creating a Halo Digital Human — What the Tools Don't Tell You
Last updated: 2026-09-03
create_automation_app's tool description explains the JSON shape of an App Spec, but not
how the platform actually behaves at runtime. Everything below was verified against Halo's
source (file paths cited). If a statement here contradicts observed behavior, trust the
observed behavior and report the discrepancy — this document is maintained by hand.
0. How to read the rest of this guide
The companion documents live next to this one. Read them with the same tool and the same
directory prefix you used for this file:
| Document |
Read it when |
create-digital-human/interview-checklist.md |
Always, before calling create_automation_app — what to ask the user, and what NOT to ask |
create-digital-human/im-triggers.md |
The digital human should be reachable via WeCom / any IM channel, or should proactively push IM messages — covers the App Spec side (what fields exist, notify_bot) |
create-digital-human/spec-reference.md |
You need exact field syntax, permission defaults, or output/escalation/memory semantics |
create-digital-human/examples.md |
You want a production-quality system_prompt structure to model after |
Read at least interview-checklist.md before every creation. Read the others on demand.
This topic (creating and configuring a digital human's own spec) has a hard boundary with
several sibling topics — read across it, don't duplicate it:
| Document |
Read it when |
message-channels/index.md (+ wecom-bot.md, weixin-ilink.md) |
The user is doing anything on the Settings → Message Channels side: creating/authorizing a bot instance, binding it to an app, reply scope, permission control (owners/guests), name resolution. im-triggers.md covers what the App Spec can and cannot express about IM; message-channels/ covers the channel/instance layer that exists entirely outside the spec |
spaces-and-agents/index.md |
The user asks how a digital human relates to the space it lives in — working directory, persistent memory (digital humans have it, the plain space conversation does not), MCP tool access differences between chatting with it directly vs. its scheduled/triggered runs, or whether it can move to another space (it effectively cannot — no UI path) |
ai-browser/index.md + ai-browser/scripting.md |
The digital human's task involves web automation — how AI Browser tools work, login-state reuse, and how to write a browser_run script instead of ad-hoc clicking |
skills-and-mcp/index.md |
You need to know how requires.skills / requires.mcps dependencies are actually installed, scoped (global vs. space), or authored — this guide only covers how those two fields behave inside an App Spec, not the mechanics of skills/MCP servers themselves |
ai-model-setup/index.md |
A run fails with a model/provider/credential error, or the user asks about recommended_model (informational only — it does not select or configure a provider) — model/provider configuration is entirely outside the App Spec, at the space or global level |
1. Decide the trigger type first — it changes everything downstream
| The user wants it to run... |
Use |
Detail |
| On a timer / repeating interval |
subscriptions: [{ source: { type: "schedule", config: { every / cron } } }] |
spec-reference.md |
| When a local file changes |
subscriptions: [{ source: { type: "file", config: {...} } }] |
spec-reference.md |
| When an external system POSTs to Halo |
subscriptions: [{ source: { type: "webhook", config: {...} } }] |
spec-reference.md |
| When someone messages it on WeCom / an IM channel |
No subscriptions at all. This is NOT a subscription source. |
im-triggers.md |
Only when explicitly asked (main chat, or trigger_automation_app) |
subscriptions may be omitted entirely |
— |
A digital human can have a schedule and be reachable over IM at the same time — those are
two independent mechanisms, not alternatives.
2. The #1 mistake: don't invent trigger-matching questions for IM/WeCom
Do not ask the user things like "should the bot respond to every message, or only when
mentioned / only messages with a certain prefix?" for a WeCom digital human. In WeCom group
chats, the WeCom platform itself only forwards a message to the bot when the bot is
@-mentioned — Halo never receives the other messages, so there is nothing to filter and
nothing to configure (src/main/apps/runtime/dispatch-inbound.ts, LEADING_GROUP_MENTION).
Direct 1:1 chats deliver every message, also not configurable per app.
Full detail — including what is configurable and where — in create-digital-human/im-triggers.md.
3. Schema fields that exist but do nothing yet
subscriptions[].source.type accepts webpage, rss, and wecom at the schema level
(src/main/apps/spec/schema.ts), but:
webpage and rss have no event producer implemented (stated in
src/main/apps/spec/PROTOCOL.md §3.2 — "V2 planned feature, not yet implemented").
wecom as a subscription source ({ type: "wecom", config: { chatId } }) builds an event
filter for a wecom.message event (src/main/apps/runtime/service.ts), but nothing in
the runtime ever emits that event. Real WeCom delivery uses channel-instance binding
(dispatch-inbound.ts), not the event bus.
Never offer these three as working options. For page-change or RSS monitoring, use a
schedule subscription that polls with AI Browser instead.
4. Never put credentials in config_schema
The digital human runs inside the user's own Halo browser session — cookies and auth are
already there. Never create a config_schema field for a password, cookie, or session token.
If a task needs the user logged into a specific site, declare it in browser_login:
"browser_login": [{ "url": "https://internal-system.example.com", "label": "Internal System" }]
This prompts the user to log in before the automation runs and stores no secret
(src/shared/apps/spec-types.ts; used by a real production browser-automation app — see
create-digital-human/examples.md §3).
5. Quick facts you need on almost every call
- Permissions are additive-only and default ON.
ai-browser, ai-terminal, email,
im-push are all enabled unless the user explicitly turned one off in the UI. Listing a
permission never turns anything off; omitting one never disables it. Declare what the app
genuinely uses (im-push is required for the notify_bot tool to appear).
- Any web-interaction task must declare
permissions: ["ai-browser"] and instruct the agent
to use AI Browser tools — never HTTP fetch or a generic MCP for browser work.
output.notify is schema-only — the runtime never reads it. Don't put
output.notify.system/output.notify.channels in a spec expecting it to trigger desktop
toasts or channel pushes; it silently does nothing. The desktop toast is actually a per-app
user override (app.userOverrides.notificationLevel) set afterward in the app's own
Settings panel, not something create_automation_app configures. External channel delivery
(email/wecom/dingtalk/feishu/webhook) and IM push are both AI-driven at runtime: the agent
itself decides during a run whether to call notify_channel or notify_bot (needs
permissions: ["im-push"] + a bound IM contact) — if a task must reliably notify, say so in
system_prompt, don't rely on a spec field. See create-digital-human/spec-reference.md's
output section.
update_automation_app requires a prior get_automation_status call — it is a JSON
Merge Patch, so guessing the current spec corrupts fields you didn't intend to touch.
create_automation_app rolls back on dependency failure — if requires.skills
installation fails, the app is uninstalled automatically. A failure means nothing was left
behind; it is safe to fix the spec and retry.
- Web search, memory, and OCR are always on and not permission-gated — never ask the user
to enable them.
- Persistent memory is what actually distinguishes a digital human from "just chatting in a
space". A digital human's
memory.md ({space}/.halo/apps/{appId}/memory.md) is read and
updated on every run; the plain space conversation the user might otherwise use has no
equivalent file and nothing persists across sessions there. If the task's value is "remember
what happened last time", that alone justifies creating a digital human. Full detail:
spaces-and-agents/index.md.
requires.mcps is a real least-privilege gate for scheduled/webhook/file-triggered runs, not
documentation — a user-installed MCP server not listed there is silently unavailable to this
app's autonomous runs even if it's active in the space and the user can see it working when
they chat with the digital human directly. It has no effect on built-in capabilities
(ai-browser, ai-terminal, etc. — those follow permissions[] only). See
create-digital-human/spec-reference.md's requires section before assuming an MCP-dependent
automation will "just work" on its schedule.
1---2name: create-digital-human3description: Authoring guide for Halo digital humans (automation apps). Read before calling create_automation_app or update_automation_app.4---56# Creating a Halo Digital Human — What the Tools Don't Tell You78Last updated: 2026-09-03910`create_automation_app`'s tool description explains the JSON **shape** of an App Spec, but not11how the platform actually behaves at runtime. Everything below was verified against Halo's12source (file paths cited). If a statement here contradicts observed behavior, trust the13observed behavior and report the discrepancy — this document is maintained by hand.1415## 0. How to read the rest of this guide1617The companion documents live next to this one. Read them with the same tool and the same18directory prefix you used for this file:1920| Document | Read it when |21|---|---|22| `create-digital-human/interview-checklist.md` | **Always**, before calling `create_automation_app` — what to ask the user, and what NOT to ask |23| `create-digital-human/im-triggers.md` | The digital human should be reachable via WeCom / any IM channel, or should proactively push IM messages — covers the App Spec side (what fields exist, `notify_bot`) |24| `create-digital-human/spec-reference.md` | You need exact field syntax, permission defaults, or output/escalation/memory semantics |25| `create-digital-human/examples.md` | You want a production-quality `system_prompt` structure to model after |2627Read at least `interview-checklist.md` before every creation. Read the others on demand.2829This topic (creating and configuring a digital human's *own spec*) has a hard boundary with30several sibling topics — read across it, don't duplicate it:3132| Document | Read it when |33|---|---|34| `message-channels/index.md` (+ `wecom-bot.md`, `weixin-ilink.md`) | The user is doing anything on the **Settings → Message Channels** side: creating/authorizing a bot instance, binding it to an app, reply scope, permission control (owners/guests), name resolution. `im-triggers.md` covers what the App Spec can and cannot express about IM; `message-channels/` covers the channel/instance layer that exists entirely outside the spec |35| `spaces-and-agents/index.md` | The user asks how a digital human relates to the space it lives in — working directory, persistent memory (digital humans have it, the plain space conversation does not), MCP tool access differences between chatting with it directly vs. its scheduled/triggered runs, or whether it can move to another space (it effectively cannot — no UI path) |36| `ai-browser/index.md` + `ai-browser/scripting.md` | The digital human's task involves web automation — how AI Browser tools work, login-state reuse, and how to write a `browser_run` script instead of ad-hoc clicking |37| `skills-and-mcp/index.md` | You need to know how `requires.skills` / `requires.mcps` dependencies are actually installed, scoped (global vs. space), or authored — this guide only covers how those two fields behave inside an App Spec, not the mechanics of skills/MCP servers themselves |38| `ai-model-setup/index.md` | A run fails with a model/provider/credential error, or the user asks about `recommended_model` (informational only — it does not select or configure a provider) — model/provider configuration is entirely outside the App Spec, at the space or global level |3940## 1. Decide the trigger type first — it changes everything downstream4142| The user wants it to run... | Use | Detail |43|---|---|---|44| On a timer / repeating interval | `subscriptions: [{ source: { type: "schedule", config: { every / cron } } }]` | `spec-reference.md` |45| When a local file changes | `subscriptions: [{ source: { type: "file", config: {...} } }]` | `spec-reference.md` |46| When an external system POSTs to Halo | `subscriptions: [{ source: { type: "webhook", config: {...} } }]` | `spec-reference.md` |47| When someone messages it on WeCom / an IM channel | **No `subscriptions` at all.** This is NOT a subscription source. | `im-triggers.md` |48| Only when explicitly asked (main chat, or `trigger_automation_app`) | `subscriptions` may be omitted entirely | — |4950A digital human can have a schedule *and* be reachable over IM at the same time — those are51two independent mechanisms, not alternatives.5253## 2. The #1 mistake: don't invent trigger-matching questions for IM/WeCom5455Do **not** ask the user things like "should the bot respond to every message, or only when56mentioned / only messages with a certain prefix?" for a WeCom digital human. In WeCom **group57chats**, the WeCom platform itself only forwards a message to the bot when the bot is58`@`-mentioned — Halo never receives the other messages, so there is nothing to filter and59nothing to configure (`src/main/apps/runtime/dispatch-inbound.ts`, `LEADING_GROUP_MENTION`).60Direct 1:1 chats deliver every message, also not configurable per app.6162Full detail — including what *is* configurable and where — in `create-digital-human/im-triggers.md`.6364## 3. Schema fields that exist but do nothing yet6566`subscriptions[].source.type` accepts `webpage`, `rss`, and `wecom` at the schema level67(`src/main/apps/spec/schema.ts`), but:6869- `webpage` and `rss` have no event producer implemented (stated in70 `src/main/apps/spec/PROTOCOL.md` §3.2 — "V2 planned feature, not yet implemented").71- `wecom` as a *subscription source* (`{ type: "wecom", config: { chatId } }`) builds an event72 filter for a `wecom.message` event (`src/main/apps/runtime/service.ts`), but **nothing in73 the runtime ever emits that event**. Real WeCom delivery uses channel-instance binding74 (`dispatch-inbound.ts`), not the event bus.7576Never offer these three as working options. For page-change or RSS monitoring, use a77`schedule` subscription that polls with AI Browser instead.7879## 4. Never put credentials in `config_schema`8081The digital human runs inside the user's own Halo browser session — cookies and auth are82already there. Never create a `config_schema` field for a password, cookie, or session token.83If a task needs the user logged into a specific site, declare it in `browser_login`:8485```json86"browser_login": [{ "url": "https://internal-system.example.com", "label": "Internal System" }]87```8889This prompts the user to log in before the automation runs and stores no secret90(`src/shared/apps/spec-types.ts`; used by a real production browser-automation app — see91`create-digital-human/examples.md` §3).9293## 5. Quick facts you need on almost every call9495- **Permissions are additive-only and default ON.** `ai-browser`, `ai-terminal`, `email`,96 `im-push` are all enabled unless the user explicitly turned one off in the UI. Listing a97 permission never turns anything off; omitting one never disables it. Declare what the app98 genuinely uses (`im-push` is required for the `notify_bot` tool to appear).99- **Any web-interaction task must declare `permissions: ["ai-browser"]` and instruct the agent100 to use AI Browser tools** — never HTTP fetch or a generic MCP for browser work.101- **`output.notify` is schema-only — the runtime never reads it.** Don't put102 `output.notify.system`/`output.notify.channels` in a spec expecting it to trigger desktop103 toasts or channel pushes; it silently does nothing. The desktop toast is actually a per-app104 **user override** (`app.userOverrides.notificationLevel`) set afterward in the app's own105 Settings panel, not something `create_automation_app` configures. External channel delivery106 (email/wecom/dingtalk/feishu/webhook) and IM push are both **AI-driven at runtime**: the agent107 itself decides during a run whether to call `notify_channel` or `notify_bot` (needs108 `permissions: ["im-push"]` + a bound IM contact) — if a task must reliably notify, say so in109 `system_prompt`, don't rely on a spec field. See `create-digital-human/spec-reference.md`'s110 `output` section.111- **`update_automation_app` requires a prior `get_automation_status` call** — it is a JSON112 Merge Patch, so guessing the current spec corrupts fields you didn't intend to touch.113- **`create_automation_app` rolls back on dependency failure** — if `requires.skills`114 installation fails, the app is uninstalled automatically. A failure means nothing was left115 behind; it is safe to fix the spec and retry.116- **Web search, memory, and OCR are always on** and not permission-gated — never ask the user117 to enable them.118- **Persistent memory is what actually distinguishes a digital human from "just chatting in a119 space".** A digital human's `memory.md` (`{space}/.halo/apps/{appId}/memory.md`) is read and120 updated on every run; the plain space conversation the user might otherwise use has no121 equivalent file and nothing persists across sessions there. If the task's value is "remember122 what happened last time", that alone justifies creating a digital human. Full detail:123 `spaces-and-agents/index.md`.124- **`requires.mcps` is a real least-privilege gate for scheduled/webhook/file-triggered runs, not125 documentation** — a user-installed MCP server not listed there is silently unavailable to this126 app's autonomous runs even if it's active in the space and the user can see it working when127 they chat with the digital human directly. It has no effect on built-in capabilities128 (`ai-browser`, `ai-terminal`, etc. — those follow `permissions[]` only). See129 `create-digital-human/spec-reference.md`'s `requires` section before assuming an MCP-dependent130 automation will "just work" on its schedule.