apus channels

Manage chat channel accounts and their runtime status on the Gateway.

Related docs:

Common commands

apus channels list
apus channels list --all
apus channels status
apus channels status --probe
apus channels capabilities
apus channels capabilities --channel discord --target channel:123
apus channels resolve --channel slack "#general" "@jane"
apus channels logs --channel all
apus channels dead-letters list --channel telegram --account default

channels status keeps configured channels visible when their plugin fails to load or register. Affected accounts report running: false, lifecycle: "blocked", and the plugin error instead of a stale successful check. Run apus doctor, repair or update the plugin, and restart the Gateway before checking again.

channels list shows chat channels only: configured accounts by default, with installed, configured, and enabled status tags per account (--json for machine output). Pass --all to also surface bundled channels that have no configured account yet and installable catalog channels that are not yet on disk. Provider auth and model usage live elsewhere: apus models auth list for provider auth profiles, apus status or apus models list for usage/quota.

--json returns a local account inventory from plugin metadata without contacting the Gateway or executing channel setup/runtime code. Configured accounts remain visible even when their plugin has a setup entry. Use channels status --probe for live checks.

Disabling a plugin does not uninstall it. Its channels remain installed: true in the inventory; with --all, an installed channel without configured accounts has origin: "available" even when its plugin is disabled.

In an explicit multi-agent setup, workspace-scoped channel plugins come from agents.defaults.systemAgent.agentId. Without that owner, channels list returns the shared bundled, managed, and global inventory with a diagnostic; it does not guess one agent workspace.

For add, login, logout, remove, and resolve, or capabilities --channel, use --agent <id> to select the workspace used for channel plugin discovery. The option works before or after the subcommand; a subcommand value takes precedence. Without it, discovery uses the configured System Agent or the existing sole/legacy owner. In an interactive guided channels add, an explicit fleet with no such owner prompts for the setup owner before workspace-scoped discovery; flag-driven or non-interactive setup still requires --agent. Selecting a workspace does not create account routing bindings; guided setup asks about routing separately.

add, login, logout, and remove also take --account <id>. Omitting it selects the default account. A blank value is rejected instead of falling back to the default, as with the dead-letter commands, so an unset shell variable cannot silently select an account you did not name.

With --json, every channel entry includes label alongside its accounts, install state, and origin. Entries also include docsPath when verified official channel metadata provides a validated root-relative docs path. Automation can join this path with https://ami-docs.dev.apuscorp.com without trusting plugin-supplied URLs. Untracked or inconsistent installed-plugin provenance omits docsPath; repair verified legacy provenance with apus doctor --fix or reinstall the official package.

Status / capabilities / resolve / logs

capabilities and resolve reject explicitly empty or whitespace-only --account values. Omit the option to keep each command's default or broader account scope; do not pass an empty shell variable to request that scope.

channels logs --lines requires a positive integer. Omit --lines to use the default of 200; explicitly empty values are rejected.

channels logs --channel <name> matches subsystem or module names rooted at <name>, channels/<name>, or gateway/channels/<name>, including slash-separated descendants. Similar names such as discord-archive do not match discord.

channels status reads the Gateway's recorded channel lifecycle and health facts, plus secret-free account metadata. It does not resolve operational credentials or run live status hooks. Unknown configuration and stale connections remain visible; provider details not recorded by the channel may require an explicit probe. WhatsApp's recorded authentication age refreshes with the channel heartbeat.

channels status --probe is the live path: on a reachable gateway it runs per-account probeAccount and optional auditAccount checks, so output can include transport state plus successful or failed account checks and audits. Channels are checked concurrently with a bounded deadline per channel, including account resolution and status hooks. A timed-out channel returns its recorded state with a warning instead of preventing healthy channels from returning their results. If the gateway is unreachable, channels status falls back to config-only summaries instead of live check output.

If the Gateway answers with an error, such as an unknown --channel, the command reports that error and exits nonzero instead of showing an unreachable fallback.

Before checking channels, the command waits for local Gateway startup using the shared readiness budget and reports its observed phase. Startup still in progress at the deadline is a non-failing result, not an unreachable Gateway. In that case --json returns { "status": "starting", "startupPhase": "…" }; rerun after startup to collect channel results.

The command reads existing local device authentication without creating an identity or persisting tokens returned by the Gateway, including when --probe is enabled.

channels status does not support --deep; use apus channels status --probe for channel checks. The separate top-level apus status --deep command provides a broader status check.

Inbound dead letters

Inbound events that exhaust their retry policy remain in the shared state database for the queue's existing failed-entry retention period. Inspect one channel account with:

apus channels dead-letters list --channel telegram --account default
apus channels dead-letters list --channel telegram --account default --json

The text view shows event ids, failure reasons, attempt counts, and failure ages. JSON output also includes the retained payload, metadata, lane, and attempt timestamps for diagnostics.

Omitting --account inspects the default account. Both dead-letter commands reject a blank value instead of falling back to default, so an unset shell variable cannot silently select an account you did not name. You can place --account before or after list or resubmit; a value after the leaf command takes precedence.

After correcting the underlying problem, re-enqueue one event with its original event id:

apus channels dead-letters resubmit <event-id> --channel telegram --account default

Run these commands on the Gateway host so they access the same shared state database as the channel runtime. Resubmission preserves the payload, metadata, and lane, but resets the attempt counter and queue age. It atomically replaces that event's failed marker, so repeating the command while the event is pending or claimed refuses instead of creating a second dispatch. The running channel picks it up on its next ingress drain. Completed events remain terminal and cannot be resubmitted. Failed rows created before payload retention was added can still appear in the list, but resubmission refuses them because their payload is unavailable.

apus health reports dead-letter counts and oldest failure age per channel account. apus doctor names affected accounts and points back to the inspection command.

Do not use apus sessions, Gateway sessions.list, or the agent sessions_list tool as a channel socket-health signal. Those surfaces report stored conversation rows, not provider runtime state. After a Discord provider restart, a connected but quiet account may be healthy while no Discord session row appears until the next inbound or outbound conversation event.

Add / remove accounts

apus channels add --channel telegram --token <bot-token>
apus channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY"
apus channels remove --channel telegram --delete

For a headless host, complete non-interactive onboarding first, then add each channel with explicit credential flags or its environment-backed setup option:

export OPENAI_API_KEY="<provider-key>"
export TELEGRAM_BOT_TOKEN="<bot-token>"

apus onboard --non-interactive --accept-risk --skip-health \
  --mode local \
  --auth-choice openai-api-key \
  --secret-input-mode ref \
  --skip-channels \
  --no-install-daemon
apus channels add --channel telegram --use-env

--use-env validates the environment variables declared by the selected channel plugin before writing config. For Telegram, the command requires TELEGRAM_BOT_TOKEN; other plugins name their missing variables in the error. The Gateway service must receive the same environment variables as the bootstrap shell. If the Gateway is already running with config reload enabled, it watches the config write and restarts the affected channel automatically.

See CLI automation for additional non-interactive provider and Gateway options. Container deployments should also follow the Docker headless bootstrap environment guidance.

apus channels add telegram --help or apus channels add --channel telegram --help shows only Telegram's setup flags. apus channels add --help shows only the shared command envelope.

channels remove only operates on installed/configured channel plugins. Use channels add first for installable catalog channels. Without --delete it asks to disable the account and keeps its config; --delete removes the config entries without prompting. For runtime-backed channel plugins, channels remove also asks the running Gateway to stop the selected account before it updates config, so disabling or deleting an account does not leave the old listener active until restart.

The shared control envelope contains --agent, --channel, --account, and the optional account display --name. Each modern channel plugin owns its credential, transport, and provider-specific semantics. Once a channel is selected by positional id or --channel <id>, the CLI builds only that channel's options from bundled or installed plugin package metadata without loading channel runtime code.

Common-looking flags such as --token, --url, or --use-env are still channel-owned when a modern contract handles them. When a selected third-party plugin still uses the legacy shared setup adapter, core registers the released compatibility flag set for that channel only, alongside its legacy cliAddOptions. Unrelated legacy fields do not leak into other channels, and a modern selected channel rejects compatibility flags it did not declare.

Examples of channel-owned flags include:

Channel Flags
Google Chat --webhook-path, --webhook-url, --audience-type, --audience
iMessage --cli-path, --db-path, --service, --region
Matrix --homeserver, --user-id, --access-token, --password, --device-name, --initial-sync-limit
Nostr --private-key, --relay-urls
Signal --signal-number, --signal-transport, --cli-path, --http-url, --http-host, --http-port
Tlon --ship, --url, --code, --group-channels, --dm-allowlist, --auto-discover-channels
WhatsApp --auth-dir

If a channel plugin needs to be installed during a flag-driven add command, Apus uses the channel's default install source without opening the interactive plugin install prompt.

Both guided setup and flag-driven setup pass through the selected channel's parser, validation, account resolution, config writer, and post-write hooks. Unsupported flags fail with the owning channel's setup error instead of being accepted through a global input bag.

When you run apus channels add with no direct account, credential, or channel-config flags, the interactive wizard can prompt. A positional channel id and --channel <id> both open that channel's guided setup immediately. Back returns to the full channel picker:

apus channels add telegram
apus channels add --channel telegram

Guided setup requires an interactive terminal. In a non-TTY shell, Apus exits immediately instead of waiting for input. Run apus channels add --channel <id> --help to list the setup flags that channel declares, then pass them for non-interactive setup. --use-env appears there only where it is registered: a modern channel declares the matching field in its setup contract, and a plugin still on the legacy shared setup adapter gets it from the compatibility set described above. When the selected channel leaves --use-env out, the exit message points at that channel's --help instead of at --use-env.

The wizard can prompt for:

If you confirm bind now, the wizard asks which agent should own each configured channel account and writes account-scoped routing bindings.

You can also manage the same routing rules later with apus agents bindings, apus agents bind, and apus agents unbind (see agents).

When you add a non-default account to a channel that is still using single-account top-level settings, Apus promotes those top-level values into the channel's account map before writing the new account. Promotion reuses an existing named account when the channel has exactly one, or when defaultAccount points at one; otherwise the values land in channels.<channel>.accounts.default.

Routing behavior stays consistent:

If your config was already in a mixed state (named accounts present and top-level single-account values still set), run apus doctor --fix to move account-scoped values into the promoted account chosen for that channel.

Login and logout (interactive)

Before channels add or channels login writes local credentials or configuration, Apus compares the selected CLI state/config paths with the local Gateway or its installed service. A proven mismatch stops before the write. A remote Gateway or an authenticated path that cannot be verified produces a warning instead.

apus channels login --channel whatsapp
apus channels logout --channel whatsapp

Per-account recovery (non-destructive)

When one account needs to reconnect while keeping its pairing and credentials, call the channels.stop and channels.start Gateway RPCs. Both require operator.admin. Invoke them through apus gateway call:

# Stop one WhatsApp account without clearing its pairing.
apus gateway call channels.stop --params '{"channel":"whatsapp","accountId":"<accountId>"}'
# Start the same account again.
apus gateway call channels.start --params '{"channel":"whatsapp","accountId":"<accountId>"}'
apus channels status --channel whatsapp --probe

Use the same accountId in both calls. Omit it from both to select the default account.

channels.stop returns { channel, accountId, stopped }; channels.start returns { channel, accountId, started, outcome }. These booleans reflect the account's runtime snapshot after the operation: started is true only when running is true, and stopped is true when running is not true. A started: false response does not by itself establish that the account is stopped, and started: true does not establish that the provider connection is healthy. Check channel status and logs after recovery.

An explicitly started account appears in runtime status while the Gateway owns its lifecycle, even if the plugin's static account list does not yet include it. After a successful stop, that unlisted account disappears from status. Default-account selection and automatic health-monitor and host-thaw recovery continue to use the plugin's static account list.

Host-thaw recovery detects maintenance gaps at least 45 seconds beyond the normal cadence. If process CPU time consumed at least half of the gap, the Gateway logs event-loop load and skips thaw recovery, preserving event-loop health measurements. Otherwise, it refreshes health and presence and attempts channel recovery when tracked work is idle. Busy checks leave work admission open. Channel restart retries expire ten minutes after thaw detection, including time spent waiting for admission to reopen; deferral and abandonment are each logged once per thaw. Ordinary channel health monitoring continues after this window expires.

outcome explains the lifecycle owner's decision for the requested account:

Accounts explicitly disabled in channel or account configuration are skipped without resolving inactive credentials. An unavailable configured secret on an enabled account still returns an RPC error instead of starting with another credential.

Unlike this recovery path, apus channels logout clears the account's credentials and requires login again; apus gateway restart restarts the whole Gateway. See Restart recovery for the crash-loop breaker and its manual channels.start override.

Troubleshooting

Capabilities check

Fetch provider capability hints (intents/scopes where available) plus static feature support:

apus channels capabilities
apus channels capabilities --channel discord --target channel:123

Notes:

  • --channel is optional; omit it to list every channel (including plugin-provided channels).
  • --account is only valid with --channel.
  • Each account check and diagnostics step has its own timeout. A stalled step is reported in both text and JSON output, and the command continues with the remaining accounts.
  • --target accepts channel:<id> or a raw numeric channel id and only applies to Discord. For Discord voice channels, the permission check flags missing ViewChannel, Connect, Speak, SendMessages, and ReadMessageHistory.
  • Checks are provider-specific: Discord bot identity + intents plus optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; Microsoft Teams app token + Graph roles/scopes (annotated where known). Channels without checks report that the live check is unavailable.

Resolve names to IDs

Resolve channel/user names to IDs using the provider directory:

apus channels resolve --channel slack "#general" "@jane"
apus channels resolve --channel discord "My Server/#support" "@someone"
apus channels resolve --channel matrix "Project Room"
apus channels --agent ops resolve --channel slack "#general"
apus channels resolve --agent ops --channel slack "#general"

Notes:

  • In multi-agent configurations, use --agent <id> in either parent or leaf position to select the agent-owned workspace and channel plugin context.
  • Use --kind user|group|channel|auto to force the target type.
  • Resolution prefers active matches when multiple entries share the same name.
  • channels resolve is read-only. If a selected account is configured via SecretRef but that credential is unavailable in the current command path, the command returns degraded unresolved results with notes instead of aborting the entire run.
  • channels resolve does not install channel plugins. Use channels add --channel <name> before resolving names for an installable catalog channel.

Related