Signal Onboarding (setup & fleet distribution)¶
A task-oriented how-to for getting the amplihack Signal channel
working out of the box — on one host with amplihack signal setup, or across
an entire Azure Linux (azlin) fleet with amplihack signal distribute.
Why onboarding exists. The per-session Signal channel already ships in amplihack (
amplihack-signal, wired viaamplihack-hooksSessionStart). But it needs a local, low-latency signal-cli JSON-RPC daemon and a valid config on each host. Doing that by hand — install signal-cli, link a device, start a daemon, hand-write TOML — is the friction these commands remove. Resolves issues #921–#924.
- Before you start
- Onboard one host:
signal setup - Automatic in-session prompt
- Onboard a fleet:
signal distribute - Key constraints
- Verifying it worked
- Reference
Before you start¶
You need:
- amplihack built with the
signalfeature. Thesignalsubcommand is always registered, but with the feature compiled out it exits with a clean "rebuild with--features signal" error (never a silent no-op).
- Your phone with Signal installed and access to Settings → Linked devices → Link new device (you scan a QR code per host).
- For fleet distribution: the azlin CLI configured for your Azure subscription and an operator resource group (or an explicit VM list).
You do not need to pre-install signal-cli — setup installs it, or prints
actionable guidance if it cannot.
Onboard one host: signal setup¶
Run:
Walkthrough:
- signal-cli check/install. If signal-cli is missing,
setupinstalls it where it can, or prints exact install instructions and exits non-zero. - Device linking. A QR code is drawn in your terminal, with the raw
device-link URI printed underneath as a fallback (
sgnl://linkdevice?...on recent signal-cli, or the legacytsdevice:/?...on older builds —setupencodes whichever signal-cli emits). On your phone: Signal → Settings → Linked devices → Link new device → scan.setupwaits using idle/liveness detection — there is no wall-clock timeout, so take as long as you need. - Local daemon. Once linked,
setupstarts signal-cli in JSON-RPC daemon mode on127.0.0.1:7583(loopback only) as a managed background service — a systemd--userunit if available, otherwise a detachednohupprocess. - Config. It writes
~/.amplihack/signal-config.toml(0600) with:
endpoint = "127.0.0.1:7583"
account = "+15551230000" # your linked Signal number
allowlist = ["+15551230000"] # the account's OWN number — see below
That's it. The next amplihack session on this host automatically opens its own
Signal group — no environment variables required, because the channel's config
loader falls back to ~/.amplihack/signal-config.toml (the default path the
onboarding feature adds; see
Per-session wiring).
Re-running is safe¶
setup is idempotent. It probes three things — linked,
daemon-running, config-written — and repairs only what's missing. It
never re-links an already-linked device and never clobbers a valid config
(unless you pass --force).
Common flags:
amplihack signal setup --port 7600 # custom loopback port
amplihack signal setup --json # machine-readable status (URI never printed)
amplihack signal setup --force # repair/overwrite existing setup
Automatic in-session prompt¶
You don't have to remember to run signal setup in advance. The first time
you launch amplihack (Copilot CLI or Claude Code) on an un-onboarded host, the
SessionStart hook offers a fast, skippable prompt:
Signal channel is not configured on this host.
Add this host as a Signal device and mirror your sessions to Signal? [y/N]
- Yes records your intent and spawns
amplihack signal setupin the background (device linking is user-paced and cannot run inside the ~30s hook timeout). Signal stays off for the current session and turns on automatically on the next launch, once~/.amplihack/signal-config.tomlexists. - No / Enter writes a decline sentinel
(
~/.amplihack/runtime/signal/.onboarding-declined) so you are not asked again on this host.
The prompt is skipped entirely — never blocking a session — when any of
these hold: not a TTY, AMPLIHACK_NONINTERACTIVE=1, config already present, or
the decline sentinel exists. To be asked again after declining:
This is the interactive counterpart to signal setup; both write the same
~/.amplihack/signal-config.toml. Full behavior:
Signal Channel → In-session onboarding prompt.
Works on both hosts. Onboarding, mirroring, and operator injection are host-aware and function identically on GitHub Copilot CLI and Claude Code; the channel detects the host from
AMPLIHACK_AGENT_BINARY. See Host awareness.
Onboard a fleet: signal distribute¶
Roll the same onboarding out to every VM so each host runs its own local daemon:
amplihack signal distribute --resource-group <rg>
# target specific hosts instead of auto-discovery:
amplihack signal distribute --vms vm-a,vm-b --resource-group <rg>
# alias:
amplihack signal setup --all-vms --resource-group <rg>
What happens:
- VMs are discovered generically via azlin (
azlin list/az vm list) or your explicit--vmslist. No host is hardcoded. - Each VM is reached with
azlin connect <vm> --resource-group <rg> --no-tmux -y -- '<cmd>'. - You scan one QR code per VM. Each VM becomes its own linked device on
your single Signal number (one chat identity across the fleet). Because a
human scans one QR at a time and interleaved QR codes on one terminal are
unscannable, onboarding runs one VM at a time.
--concurrencyis accepted for forward-compatibility with future non-interactive rollout phases but is not applied to the interactive device-link step (passing> 1prints a notice and proceeds sequentially). - The rollout is resumable via
~/.amplihack/signal-distribute-state.json. Re-run to retry onlypending/failedVMs. - One VM failing never aborts the run. Failures are recorded with a reason and shown in the final per-VM summary — nothing is silently degraded.
Per-VM statuses: pending → linking → linked → daemon-running →
config-written, or failed (with a reason). Example:
Very large fleets¶
Signal enforces a linked-device count limit per account. If your fleet exceeds it, use the config-selectable extension point:
dedicated-number gives each VM its own Signal number. It is a documented
extension point; today it returns an explicit "not yet implemented" error rather
than any partial behavior. The default linked-device mode is fully supported.
Key constraints¶
- The daemon must be local. Each session host needs its own signal-cli
daemon on
127.0.0.1. A shared remote daemon reached over a bastion/tunnel does not work — JSON-RPC responses never return and calls time out.distributetherefore installs a daemon on every VM, never a shared one. - Single-number setups must allowlist their own number. When signal-cli is a
linked device on your own number, your phone replies arrive as the
account's own synced messages — so the
accountnumber itself is on the allowlist (allowlist = [account]). This is exactly whatsetupwrites. - Linked-device limit. Signal caps linked devices per account. Fleets larger
than that cap should plan for
--identity-mode dedicated-number. - No wall-clock caps on the interactive step. Device linking waits on idle detection, so slow phone approvals never fail spuriously.
- Fail-closed, no silent fallback. Missing signal-cli, port conflicts, and
link failures are always surfaced as explicit errors — never worked around
silently. Each maps to a distinct
exit code (
3unsupported,4precondition,5partial fleet,6daemon,7link) so scripts can branch on the cause.
Verifying it worked¶
# 1. Config is present and valid.
cat ~/.amplihack/signal-config.toml
# 2. The local daemon answers on loopback.
ss -ltnp | grep 127.0.0.1:7583 # or your --port
# 3. Start any amplihack session; you should receive a "session started"
# message in a fresh Signal group named amplihack-<session-id>-<timestamp>,
# followed by the WHOLE conversation mirrored live — every prompt you type
# and every assistant turn. Reply in the Signal chat and your message is
# injected into the running agent as if typed at the CLI input box.
If nothing arrives, check the session's warnings[] — every Signal operation is
non-fatal, so problems are reported there rather than crashing the session. See
Troubleshooting.
Reference¶
- Signal Channel — full channel documentation, config schema, security model, and per-session wiring.
- Signal External Service Integration — the
seam architecture (
signal-cli, daemon poll,azlin/azdiscovery), resumable fleet state,amplihack-remotere-exports, and how to test the external boundaries. examples/signal-config.toml— annotated config.- Related issues: #921 (onboarding command), #922 (device linking), #923 (fleet distribution), #924 (idempotent local daemon + config), #971 (external service integration), #972 (TDD contract tests).