Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ For details, refer to Commands (use the `nemoclaw-user-reference` skill).
|---------|-----------------|-------------------|
| Telegram | `TELEGRAM_BOT_TOKEN` | `TELEGRAM_ALLOWED_IDS` for DM allowlisting, `TELEGRAM_REQUIRE_MENTION` for group-chat replies |
| Discord | `DISCORD_BOT_TOKEN` | `DISCORD_SERVER_ID`, `DISCORD_USER_ID`, `DISCORD_REQUIRE_MENTION` |
| Slack | `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` | `SLACK_ALLOWED_USERS` for DM and channel `@mention` user allowlisting |
| Slack | `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` | `SLACK_ALLOWED_USERS` for DM and channel `@mention` user allowlisting, `SLACK_ALLOWED_CHANNELS` for channel ID allowlisting |
| WhatsApp | None. Pair via QR after rebuild | None |

Telegram uses a bot token from [BotFather](https://t.me/BotFather).
Expand All @@ -44,6 +44,8 @@ Set `DISCORD_USER_ID` to restrict access to one user; otherwise, any member of t
Slack uses Socket Mode and requires two tokens.
Use `SLACK_BOT_TOKEN` for the bot user OAuth token (`xoxb-...`) and `SLACK_APP_TOKEN` for the app-level Socket Mode token (`xapp-...`).
Set `SLACK_ALLOWED_USERS` to comma-separated Slack member IDs to authorize those users for DMs and for channel `@mention` events in channels where the Slack app is present.
Set `SLACK_ALLOWED_CHANNELS` to comma-separated Slack channel IDs to restrict channel `@mention` handling to those channels.
When both Slack allowlists are set, NemoClaw requires the mention to come from one of the allowed channels and one of the allowed members.
Channel messages still require an explicit bot mention.

WhatsApp Web does not use a host-side token or OpenShell credential provider.
Expand Down Expand Up @@ -80,6 +82,7 @@ $ export DISCORD_SERVER_ID=<your-discord-server-id>
$ export SLACK_BOT_TOKEN=<your-slack-bot-token>
$ export SLACK_APP_TOKEN=<your-slack-app-token>
$ export SLACK_ALLOWED_USERS=<your-slack-member-id>
$ export SLACK_ALLOWED_CHANNELS=<your-slack-channel-id>
```

Then run onboarding:
Expand Down Expand Up @@ -112,7 +115,7 @@ $ nemoclaw my-assistant channels add whatsapp
The command accepts mixed-case input such as `Telegram`, then stores and prints the canonical lowercase channel name.
If a matching built-in network policy preset exists, `channels add` applies it to the sandbox automatically before the rebuild so the bridge has egress to its upstream API; if applying the preset fails, NemoClaw warns and tells you to re-apply manually with `nemoclaw <sandbox> policy-add <channel>` after the rebuild.
Choose the rebuild so the running sandbox image picks up the new channel.
If you need optional channel settings such as `TELEGRAM_ALLOWED_IDS`, `TELEGRAM_REQUIRE_MENTION`, `DISCORD_SERVER_ID`, `DISCORD_USER_ID`, or `DISCORD_REQUIRE_MENTION`, export them before the rebuild starts.
If you need optional channel settings such as `TELEGRAM_ALLOWED_IDS`, `TELEGRAM_REQUIRE_MENTION`, `DISCORD_SERVER_ID`, `DISCORD_USER_ID`, `DISCORD_REQUIRE_MENTION`, `SLACK_ALLOWED_USERS`, or `SLACK_ALLOWED_CHANNELS`, export them before the rebuild starts.
If you defer the rebuild, apply the change later:

```console
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ The following environment variables configure optional services and local access
|---|---|
| `TELEGRAM_BOT_TOKEN` | Telegram bot token you provide before `nemoclaw onboard`. OpenShell stores it in a provider; the sandbox receives placeholders, not the raw secret. |
| `TELEGRAM_ALLOWED_IDS` | Comma-separated Telegram user or chat IDs for allowlists when onboarding applies channel restrictions. |
| `SLACK_BOT_TOKEN` | Slack bot token (`xoxb-...`) you provide before `nemoclaw onboard`. Stored as an OpenShell provider; never passed directly to the sandbox. |
| `SLACK_APP_TOKEN` | Slack app-level token (`xapp-...`) required for Socket Mode. Stored alongside `SLACK_BOT_TOKEN` during onboarding. |
| `SLACK_ALLOWED_USERS` | Comma-separated Slack member IDs for DM and channel `@mention` user allowlisting. |
| `SLACK_ALLOWED_CHANNELS` | Comma-separated Slack channel IDs where channel `@mention` events are enabled (e.g. `C012AB3CD,C987ZY6XW`). Baked into the sandbox image at build time. Combine with `SLACK_ALLOWED_USERS` to restrict both channel and member. |
| `CHAT_UI_URL` | URL for the optional chat UI endpoint. |
| `NEMOCLAW_DISABLE_DEVICE_AUTH` | Build-time-only toggle that disables gateway device pairing when set to `1` before the sandbox image is created. |

Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/nemoclaw-user-reference/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ This flag takes precedence over `CHAT_UI_URL`, `NEMOCLAW_DASHBOARD_PORT`, the pr
If you enable Slack during onboarding, the wizard collects both the Bot Token (`SLACK_BOT_TOKEN`) and the App-Level Token (`SLACK_APP_TOKEN`).
Socket Mode requires both tokens.
The app-level token is stored in a dedicated `slack-app` OpenShell provider and forwarded to the sandbox alongside the bot token.
The wizard also accepts optional `SLACK_ALLOWED_USERS` and `SLACK_ALLOWED_CHANNELS` values so you can restrict Slack DMs, channel `@mention` users, and channel IDs before the sandbox image is built.

If you enable Discord during onboarding, the wizard can also prompt for a Discord Server ID, whether the bot should reply only to `@mentions` or to all messages in that server, and an optional Discord User ID.
NemoClaw bakes those values into the sandbox image as Discord guild workspace config so the bot can respond in the selected server, not just in DMs.
Expand Down Expand Up @@ -664,6 +665,7 @@ $ nemoclaw my-assistant channels add telegram
| `--dry-run` | Validate the channel and token inputs without saving credentials or rebuilding |

Slack requires both `SLACK_BOT_TOKEN` (bot user OAuth) and `SLACK_APP_TOKEN` (app-level Socket Mode token); the command prompts for each in turn.
Optional Slack allowlists come from `SLACK_ALLOWED_USERS` and `SLACK_ALLOWED_CHANNELS` at rebuild time.
When `NEMOCLAW_NON_INTERACTIVE=1` is set, any missing token fails fast and no rebuild prompt is shown — instead, the change is queued and you are told to run `nemoclaw <name> rebuild` manually.
If you omit the required `<channel>` argument, the CLI prints the `channels add <channel>` usage with the supported channel list instead of falling back to top-level help.

Expand Down
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,11 @@ ARG NEMOCLAW_TELEGRAM_CONFIG_B64=e30=
# metadata only — the bot token flows through the OpenShell provider, never
# baked into the image. Default: empty map.
ARG NEMOCLAW_WECHAT_CONFIG_B64=e30=
# Base64-encoded JSON Slack config (e.g.
# {"allowedChannels":["C012AB3CD","C987ZY6XW"]}).
# Channel IDs scope Slack channel @mention handling. User allowlists still come
# from NEMOCLAW_MESSAGING_ALLOWED_IDS_B64. Default: empty map.
ARG NEMOCLAW_SLACK_CONFIG_B64=e30=
# Set to "1" to force-disable device-pairing auth. Also auto-disabled when
# CHAT_UI_URL is a non-loopback address (Brev Launchable, remote deployments)
# since terminal-based pairing is impossible in those contexts.
Expand Down Expand Up @@ -442,6 +447,7 @@ ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
NEMOCLAW_DISCORD_GUILDS_B64=${NEMOCLAW_DISCORD_GUILDS_B64} \
NEMOCLAW_TELEGRAM_CONFIG_B64=${NEMOCLAW_TELEGRAM_CONFIG_B64} \
NEMOCLAW_WECHAT_CONFIG_B64=${NEMOCLAW_WECHAT_CONFIG_B64} \
NEMOCLAW_SLACK_CONFIG_B64=${NEMOCLAW_SLACK_CONFIG_B64} \
NEMOCLAW_OPENCLAW_WECHAT_PLUGIN_PREINSTALLED=1 \
NEMOCLAW_DISABLE_DEVICE_AUTH=${NEMOCLAW_DISABLE_DEVICE_AUTH} \
NEMOCLAW_PROXY_HOST=${NEMOCLAW_PROXY_HOST} \
Expand Down
2 changes: 2 additions & 0 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ ARG NEMOCLAW_TELEGRAM_CONFIG_B64=e30=
# is never baked here — it flows through the OpenShell L7 proxy via the
# WECHAT_BOT_TOKEN credential slot.
ARG NEMOCLAW_WECHAT_CONFIG_B64=e30=
ARG NEMOCLAW_SLACK_CONFIG_B64=e30=
ARG NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER=0
ARG NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64=W10=
ARG NEMOCLAW_BUILD_ID=default
Expand All @@ -104,6 +105,7 @@ ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
NEMOCLAW_DISCORD_GUILDS_B64=${NEMOCLAW_DISCORD_GUILDS_B64} \
NEMOCLAW_TELEGRAM_CONFIG_B64=${NEMOCLAW_TELEGRAM_CONFIG_B64} \
NEMOCLAW_WECHAT_CONFIG_B64=${NEMOCLAW_WECHAT_CONFIG_B64} \
NEMOCLAW_SLACK_CONFIG_B64=${NEMOCLAW_SLACK_CONFIG_B64} \
NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER=${NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER} \
NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64=${NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64}

Expand Down
6 changes: 6 additions & 0 deletions agents/hermes/config/build-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export type WechatConfig = {
userId?: string;
};

export type SlackConfig = {
allowedChannels?: string[];
};

export type HermesBuildSettings = {
model: string;
baseUrl: string;
Expand All @@ -43,6 +47,7 @@ export type HermesBuildSettings = {
discordGuilds: DiscordGuilds;
telegramConfig: TelegramConfig;
wechatConfig: WechatConfig;
slackConfig: SlackConfig;
};
};

Expand Down Expand Up @@ -79,6 +84,7 @@ export function readHermesBuildSettings(env: NodeJS.ProcessEnv): HermesBuildSett
"e30=",
),
wechatConfig: readBase64Json<WechatConfig>(env, "NEMOCLAW_WECHAT_CONFIG_B64", "e30="),
slackConfig: readBase64Json<SlackConfig>(env, "NEMOCLAW_SLACK_CONFIG_B64", "e30="),
},
};
}
Expand Down
16 changes: 15 additions & 1 deletion agents/hermes/config/messaging-config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { DiscordGuilds, MessagingAllowedIds, WechatConfig } from "./build-env.ts";
import type { DiscordGuilds, MessagingAllowedIds, SlackConfig, WechatConfig } from "./build-env.ts";
import { loadManagedToolGatewayMatrix } from "./managed-tool-gateway.ts";

// Maps each Hermes-supported channel to the in-sandbox env-var name(s) the
Expand All @@ -24,6 +24,7 @@ export function buildMessagingEnvLines(
allowedIds: MessagingAllowedIds,
discordGuilds: DiscordGuilds,
wechatConfig: WechatConfig,
slackConfig: SlackConfig,
managedToolGatewayPresets: string[] = [],
): string[] {
const envLines = ["API_SERVER_PORT=18642", "API_SERVER_HOST=127.0.0.1"];
Expand Down Expand Up @@ -74,6 +75,10 @@ export function buildMessagingEnvLines(
if (allowedIds.slack?.length) {
envLines.push(`SLACK_ALLOWED_USERS=${allowedIds.slack.map(String).join(",")}`);
}
const slackAllowedChannels = collectSlackAllowedChannels(slackConfig);
if (enabledChannels.has("slack") && slackAllowedChannels.length > 0) {
envLines.push(`SLACK_ALLOWED_CHANNELS=${slackAllowedChannels.join(",")}`);
}

return envLines;
}
Expand Down Expand Up @@ -179,3 +184,12 @@ function collectDiscordAllowedUsers(
}
return [...users];
}

function collectSlackAllowedChannels(slackConfig: SlackConfig): string[] {
const channels = Array.isArray(slackConfig.allowedChannels) ? slackConfig.allowedChannels : [];
return [
...new Set(
channels.map((channel) => String(channel).replace(/[\r\n]/g, "").trim()).filter(Boolean),
),
];
}
1 change: 1 addition & 0 deletions agents/hermes/generate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function main(): void {
settings.messaging.allowedIds,
settings.messaging.discordGuilds,
settings.messaging.wechatConfig,
settings.messaging.slackConfig,
settings.managedToolGateways.brokerEnabled
? settings.managedToolGateways.presets
: [],
Expand Down
7 changes: 5 additions & 2 deletions docs/manage-sandboxes/messaging-channels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ For details, refer to [Commands](/reference/commands).
|---------|-----------------|-------------------|
| Telegram | `TELEGRAM_BOT_TOKEN` | `TELEGRAM_ALLOWED_IDS` for DM allowlisting, `TELEGRAM_REQUIRE_MENTION` for group-chat replies |
| Discord | `DISCORD_BOT_TOKEN` | `DISCORD_SERVER_ID`, `DISCORD_USER_ID`, `DISCORD_REQUIRE_MENTION` |
| Slack | `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` | `SLACK_ALLOWED_USERS` for DM and channel `@mention` user allowlisting |
| Slack | `SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN` | `SLACK_ALLOWED_USERS` for DM and channel `@mention` user allowlisting, `SLACK_ALLOWED_CHANNELS` for channel ID allowlisting |
| WhatsApp | None. Pair via QR after rebuild | None |

Telegram uses a bot token from [BotFather](https://t.me/BotFather).
Expand All @@ -55,6 +55,8 @@ Set `DISCORD_USER_ID` to restrict access to one user; otherwise, any member of t
Slack uses Socket Mode and requires two tokens.
Use `SLACK_BOT_TOKEN` for the bot user OAuth token (`xoxb-...`) and `SLACK_APP_TOKEN` for the app-level Socket Mode token (`xapp-...`).
Set `SLACK_ALLOWED_USERS` to comma-separated Slack member IDs to authorize those users for DMs and for channel `@mention` events in channels where the Slack app is present.
Set `SLACK_ALLOWED_CHANNELS` to comma-separated Slack channel IDs to restrict channel `@mention` handling to those channels.
When both Slack allowlists are set, NemoClaw requires the mention to come from one of the allowed channels and one of the allowed members.
Channel messages still require an explicit bot mention.

WhatsApp Web does not use a host-side token or OpenShell credential provider.
Expand Down Expand Up @@ -91,6 +93,7 @@ $ export DISCORD_SERVER_ID=<your-discord-server-id>
$ export SLACK_BOT_TOKEN=<your-slack-bot-token>
$ export SLACK_APP_TOKEN=<your-slack-app-token>
$ export SLACK_ALLOWED_USERS=<your-slack-member-id>
$ export SLACK_ALLOWED_CHANNELS=<your-slack-channel-id>
```

Then run onboarding:
Expand Down Expand Up @@ -123,7 +126,7 @@ $ nemoclaw my-assistant channels add whatsapp
The command accepts mixed-case input such as `Telegram`, then stores and prints the canonical lowercase channel name.
If a matching built-in network policy preset exists, `channels add` applies it to the sandbox automatically before the rebuild so the bridge has egress to its upstream API; if applying the preset fails, NemoClaw warns and tells you to re-apply manually with `nemoclaw <sandbox> policy-add <channel>` after the rebuild.
Choose the rebuild so the running sandbox image picks up the new channel.
If you need optional channel settings such as `TELEGRAM_ALLOWED_IDS`, `TELEGRAM_REQUIRE_MENTION`, `DISCORD_SERVER_ID`, `DISCORD_USER_ID`, or `DISCORD_REQUIRE_MENTION`, export them before the rebuild starts.
If you need optional channel settings such as `TELEGRAM_ALLOWED_IDS`, `TELEGRAM_REQUIRE_MENTION`, `DISCORD_SERVER_ID`, `DISCORD_USER_ID`, `DISCORD_REQUIRE_MENTION`, `SLACK_ALLOWED_USERS`, or `SLACK_ALLOWED_CHANNELS`, export them before the rebuild starts.
If you defer the rebuild, apply the change later:

```console
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ The following environment variables configure optional services and local access
|---|---|
| `TELEGRAM_BOT_TOKEN` | Telegram bot token you provide before `nemoclaw onboard`. OpenShell stores it in a provider; the sandbox receives placeholders, not the raw secret. |
| `TELEGRAM_ALLOWED_IDS` | Comma-separated Telegram user or chat IDs for allowlists when onboarding applies channel restrictions. |
| `SLACK_BOT_TOKEN` | Slack bot token (`xoxb-...`) you provide before `nemoclaw onboard`. Stored as an OpenShell provider; never passed directly to the sandbox. |
| `SLACK_APP_TOKEN` | Slack app-level token (`xapp-...`) required for Socket Mode. Stored alongside `SLACK_BOT_TOKEN` during onboarding. |
| `SLACK_ALLOWED_USERS` | Comma-separated Slack member IDs for DM and channel `@mention` user allowlisting. |
| `SLACK_ALLOWED_CHANNELS` | Comma-separated Slack channel IDs where channel `@mention` events are enabled (e.g. `C012AB3CD,C987ZY6XW`). Baked into the sandbox image at build time. Combine with `SLACK_ALLOWED_USERS` to restrict both channel and member. |
| `CHAT_UI_URL` | URL for the optional chat UI endpoint. |
| `NEMOCLAW_DISABLE_DEVICE_AUTH` | Build-time-only toggle that disables gateway device pairing when set to `1` before the sandbox image is created. |

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ This flag takes precedence over `CHAT_UI_URL`, `NEMOCLAW_DASHBOARD_PORT`, the pr
If you enable Slack during onboarding, the wizard collects both the Bot Token (`SLACK_BOT_TOKEN`) and the App-Level Token (`SLACK_APP_TOKEN`).
Socket Mode requires both tokens.
The app-level token is stored in a dedicated `slack-app` OpenShell provider and forwarded to the sandbox alongside the bot token.
The wizard also accepts optional `SLACK_ALLOWED_USERS` and `SLACK_ALLOWED_CHANNELS` values so you can restrict Slack DMs, channel `@mention` users, and channel IDs before the sandbox image is built.

If you enable Discord during onboarding, the wizard can also prompt for a Discord Server ID, whether the bot should reply only to `@mentions` or to all messages in that server, and an optional Discord User ID.
NemoClaw bakes those values into the sandbox image as Discord guild workspace config so the bot can respond in the selected server, not just in DMs.
Expand Down Expand Up @@ -682,6 +683,7 @@ $ nemoclaw my-assistant channels add telegram
| `--dry-run` | Validate the channel and token inputs without saving credentials or rebuilding |

Slack requires both `SLACK_BOT_TOKEN` (bot user OAuth) and `SLACK_APP_TOKEN` (app-level Socket Mode token); the command prompts for each in turn.
Optional Slack allowlists come from `SLACK_ALLOWED_USERS` and `SLACK_ALLOWED_CHANNELS` at rebuild time.
When `NEMOCLAW_NON_INTERACTIVE=1` is set, any missing token fails fast and no rebuild prompt is shown — instead, the change is queued and you are told to run `nemoclaw <name> rebuild` manually.
If you omit the required `<channel>` argument, the CLI prints the `channels add <channel>` usage with the supported channel list instead of falling back to top-level help.

Expand Down
34 changes: 34 additions & 0 deletions scripts/generate-openclaw-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
NEMOCLAW_DISCORD_GUILDS_B64 Base64-encoded Discord guild config
NEMOCLAW_TELEGRAM_CONFIG_B64 Base64-encoded Telegram config (e.g. {"requireMention": true})
NEMOCLAW_WECHAT_CONFIG_B64 Base64-encoded WeChat config (e.g. {"accountId": "...", "baseUrl": "...", "userId": "..."})
NEMOCLAW_SLACK_CONFIG_B64 Base64-encoded Slack config (e.g. {"allowedChannels": ["C012AB3CD"]})
NEMOCLAW_DISABLE_DEVICE_AUTH Set to "1" to force-disable device auth
NEMOCLAW_PROXY_HOST Egress proxy host (default: 10.200.0.1)
NEMOCLAW_PROXY_PORT Egress proxy port (default: 3128)
Expand Down Expand Up @@ -510,6 +511,27 @@ def build_config(env: dict | None = None) -> dict:
env.get("NEMOCLAW_TELEGRAM_CONFIG_B64", "e30=") or "e30="
).decode("utf-8")
)
_slack_config = json.loads(
base64.b64decode(
env.get("NEMOCLAW_SLACK_CONFIG_B64", "e30=") or "e30="
).decode("utf-8")
)
_raw_slack_channels = (
_slack_config.get("allowedChannels")
if isinstance(_slack_config, dict)
else []
)
_slack_allowed_channels = (
list(
dict.fromkeys(
str(channel).replace("\r", "").replace("\n", "").strip()
for channel in _raw_slack_channels
if str(channel).replace("\r", "").replace("\n", "").strip()
)
)
if isinstance(_raw_slack_channels, list)
else []
)
# NEMOCLAW_WECHAT_CONFIG_B64 is intentionally not decoded here. The
# WeChat plugin's per-account state (accountId/baseUrl/userId) is read by
# seed-wechat-accounts.py, which runs after the base image has installed
Expand Down Expand Up @@ -577,6 +599,18 @@ def _placeholder(channel: str, env_key: str) -> str:
"users": _allowed_ids[ch],
}
}
if ch == "slack" and _slack_allowed_channels:
account["groupPolicy"] = "allowlist"
slack_channel_config = {
"enabled": True,
"requireMention": True,
}
if ch in _allowed_ids and _allowed_ids[ch]:
slack_channel_config["users"] = _allowed_ids[ch]
account["channels"] = {
channel_id: dict(slack_channel_config)
for channel_id in _slack_allowed_channels
}
_ch_cfg[ch] = {"accounts": {"default": account}}

# WeChat (openclaw-weixin) is NOT added to channels.* here in build
Expand Down
Loading
Loading