Skip to content

Digital Human Collaboration & Communication

When Remote Access is enabled in Halo, each digital human exposes a REST API. One digital human can call another using curl, enabling task distribution and collaboration.

This is a hands-on guide — follow the steps top to bottom. All commands are copy-paste ready.


Step 1: Enable Remote Access

Open Halo, go to Settings (gear icon in bottom-left) → Remote Access.

  1. Toggle "Enable Remote Access" on
  2. Note the Local Address shown, e.g. http://localhost:3847
  3. Click "Show" next to the password to reveal the 6-digit PIN, e.g. 583921

Set a Custom Password

The default PIN changes on every restart. Click "Edit" next to the password and set a 4-32 character alphanumeric password. You won't need to update it after restarts.

These three values are all you need going forward:

ItemExampleWhere to find it
URLhttp://localhost:3847Settings → "Local Address"
Port3847Part after the colon in the URL
Password583921 (or custom)Settings → "Access Password"

Step 2: Try It Out

Open your terminal and replace the placeholders with your actual values.

2.1 List All Digital Humans

bash
curl -s http://localhost:3847/api/apps \
  -H "Authorization: Bearer your-password"

Response:

json
{
  "success": true,
  "data": [
    {
      "id": "e7b3c9a1-2f4d-4e6b-8a5c-1d3f5e7b9a2c",
      "specId": "stock-monitor",
      "spaceId": "f8a3b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "spec": { "name": "Stock Monitor", "type": "automation", "description": "Monitors stock prices" },
      "status": "active"
    },
    {
      "id": "d6f8e2b1-3c5a-4d7e-9f1b-2a4c6d8e0f1a",
      "specId": "alert-notifier",
      "spaceId": "f8a3b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
      "spec": { "name": "Alert Notifier", "type": "automation", "description": "Sends alert notifications" },
      "status": "active"
    }
  ]
}

From the response, note two key fields:

  • id — The digital human's UUID. You'll use this to send messages and trigger runs.
  • spaceId — The space UUID. Required when sending messages.

Filter by space

Add ?spaceId=<spaceId> to filter:

bash
curl -s "http://localhost:3847/api/apps?spaceId=f8a3b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c" \
  -H "Authorization: Bearer your-password"

2.2 Get a Single Digital Human's Details

Replace <appId> with the id from the previous step:

bash
curl -s http://localhost:3847/api/apps/<appId> \
  -H "Authorization: Bearer your-password"

Returns the full digital human config, including system_prompt, subscriptions, config_schema, and more.

2.3 Send a Message to a Digital Human

This is the core of inter-digital-human communication — send a message to trigger another digital human to act.

bash
curl -s -X POST http://localhost:3847/api/apps/<targetAppId>/chat/send \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-password" \
  -d '{
    "spaceId": "<spaceId>",
    "message": "This is a message from another digital human"
  }'

Two required fields in the -d body:

FieldMeaningSource
spaceIdSpace UUIDspaceId field from step 2.1
messageMessage contentWhatever you want to tell the target

Success response:

json
{ "success": true, "data": { "conversationId": "conv-xxx..." } }

WARNING

Sending is async. curl returns immediately. The target digital human executes in the background. Want to know when it's done? See the next step.

2.4 Check Digital Human State

bash
curl -s http://localhost:3847/api/apps/<targetAppId>/state \
  -H "Authorization: Bearer your-password"

Response:

json
{
  "success": true,
  "data": {
    "status": "idle",
    "lastRunAt": 1715000000000,
    "lastRunStatus": "completed"
  }
}

status values:

  • idle — Not running
  • running — Currently executing

2.5 Manually Trigger a Run

bash
curl -s -X POST http://localhost:3847/api/apps/<targetAppId>/trigger \
  -H "Authorization: Bearer your-password"

Triggers the digital human to run immediately, regardless of its schedule.


Step 3: Parameter Cheat Sheet

Where does each parameter come from? Here's the complete reference:

ParameterWhat it isWhere to find it
PortRemote access port, default 3847Settings → Remote Access → "Local Address", the number after the colon
Password (Token)Remote access PIN or custom passwordSettings → Remote Access → "Access Password", click "Show"
appIdDigital human's UUIDFrom curl /api/apps, the id field, e.g. "e7b3c9a1-..."
spaceIdSpace UUIDSame response, the spaceId field. Or Settings → Space Management

Step 4: Make It Automatic

Now that you can curl manually, let's make digital humans do it themselves:

  1. Expose password and port as user-configurable fields via config_schema
  2. Write system_prompt rules for when and who to call
  3. The digital human's AI will use its Bash tool to run curl commands

Full Example: Stock Monitor + Alert Notifier

Scenario: App A (Stock Monitor) checks prices every 10 minutes. When a stock crosses the threshold, it notifies App B (Alert Notifier) to send email alerts.

App A — The Monitor

yaml
spec_version: "1"
name: "Stock Monitor"
version: "1.0.0"
author: "your-name"
description: "Checks stock prices every 10 min, notifies Alert Notifier on threshold breach"
type: automation
icon: "trending-up"

system_prompt: |
  You are a stock monitor. You run every 10 minutes.

  ## Configuration (from User Configuration)
  - Access Password: {halo_token}
  - Port: {halo_port}      ← default 3847
  - Target App ID: {alert_app_id}
  - Space ID: {space_id}
  - Symbols: {symbols}    ← comma-separated, e.g. AAPL,GOOGL

  ## Steps

  1. Fetch current prices for {symbols}. Use web_search or your configured data source.
  2. Compare against the last recorded prices from memory (last_prices).

  3. For any symbol exceeding the threshold, run this Bash command:

curl -s -X POST http://localhost:{halo_port}/api/apps/{alert_app_id}/chat/send
-H "Content-Type: application/json"
-H "Authorization: Bearer {halo_token}"
-d '{"spaceId":"{space_id}","message":"ALERT: {symbol} price change {pct}%, current price {price}"}'


4. Update last_prices in memory.

## Forbidden
- Do NOT use WebFetch for POST requests (WebFetch is GET-only and returns LLM summaries)
- All message sending must use Bash + curl

subscriptions:
- id: watch-trigger
  source:
    type: schedule
    config:
      every: "10m"

config_schema:
- key: halo_token
  label: "Halo Remote Access Password"
  type: text
  required: true
  description: "Settings → Remote Access → Access Password"
- key: halo_port
  label: "Halo HTTP Port"
  type: number
  required: false
  description: "Default 3847"
- key: alert_app_id
  label: "Alert Notifier App ID"
  type: text
  required: true
  description: "The target digital human's id from curl /api/apps"
- key: space_id
  label: "Space ID"
  type: text
  required: true
  description: "Current space UUID"
- key: symbols
  label: "Symbols to Monitor"
  type: text
  required: true
  description: "Comma-separated, e.g. AAPL,GOOGL,TSLA"

memory_schema:
last_prices:
  type: object
  description: "Last recorded prices {AAPL: 150.23, GOOGL: 140.56}"

output:
notify:
  system: false

App B — The Alert Handler

yaml
spec_version: "1"
name: "Alert Notifier"
version: "1.0.0"
author: "your-name"
description: "Receives alert messages and sends notifications"
type: automation
icon: "bell"

system_prompt: |
  You are an alert handler.
  - Normally idle
  - When you receive a message from another digital human, analyze and act immediately

  ## Processing Rules
  1. Analyze the alert content and determine severity
  2. Use notify_channel to send notifications
  3. Log to memory to prevent duplicate alerts within 1 hour for the same symbol

  ## Forbidden
  - Do not act unless triggered
  - Notification content must include "alert source" and "recommended action"

output:
  notify:
    system: true
    channels:
      - email

memory_schema:
  alerted_symbols:
    type: object
    description: "{AAPL: 1715000000000, ...} timestamps for deduplication"

Install and Configure

  1. Install both digital humans in Halo

  2. Open App A's config and fill in:

    • halo_token: your remote access password
    • halo_port: 3847
    • alert_app_id: App B's UUID (from curl /api/apps)
    • space_id: current space UUID
    • symbols: AAPL,GOOGL
  3. App B needs no extra config — it only receives, never calls out


Security Notes

Never hardcode passwords in specs

Use config_schema to let the user fill in the token. Reference it as {halo_token} in the system_prompt.

yaml
# ✓ Correct: via variable
"Authorization: Bearer {halo_token}"

# ✗ Wrong: hardcoded (leaks to anyone with spec access)
"Authorization: Bearer 583921"

Public Tunnel Scenarios

If you enable the public tunnel, anyone with the URL and password can call your digital human APIs. Recommendations:

  • Turn off the tunnel when not in use
  • Use a custom password instead of random PIN (easier to manage)
  • Rotate passwords periodically

Limit Collaboration Scope

  • Don't want a digital human callable? Just don't give it the password in config_schema
  • Only want specific digital humans to collaborate? Only configure the password in those specific digital humans

Alternative: Webhook (No Password)

If you prefer not to manage passwords, use Webhooks with HMAC signatures instead.

App B subscribes to a webhook:

yaml
subscriptions:
  - id: from-app-a
    source:
      type: webhook
      config:
        path: "app-a-to-b"
        secret: "your-shared-secret"

App A calls via Bash with HMAC:

bash
BODY='your message content'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "your-shared-secret" | sed 's/^.* //')
curl -s -X POST http://localhost:3847/hooks/app-a-to-b \
  -H "Content-Type: application/json" \
  -H "x-hub-signature-256: sha256=$SIGNATURE" \
  -d "$BODY"

No password needed — just a shared secret agreed upon by both parties.


Current Limitations

LimitationImpact
Messages are async — returns before the target finishesPoll /api/apps/:appId/state or /api/apps/:appId/activity to confirm completion
No callback mechanismThe caller has no automatic way to learn the callee's execution result
Automation digital humans lack built-in list_automation_apps or trigger_automation_app toolsTarget App IDs must be pre-configured; no runtime auto-discovery
Webhook path names must be globally uniqueMultiple digital humans cannot share the same webhook path

Next Steps