Loading…
Skip to main content

Customer agent setup

Deploy and operate a Signal Diff agent on your infrastructure for crawls that cloud workers cannot reach. No repository clone or .NET SDK required.

On this page

When to use a customer agent vs cloud crawl

Cloud crawl (default) Customer agent
Public sitemaps reachable from the internet Internal, staging, VPN-only, or firewall-restricted targets
No extra infrastructure Worker on a VM, build agent, or container in your network
Fastest path for CI on public URLs CI or schedules that must hit URLs only visible from your network

Cloud crawls run on Signal Diff infrastructure. A customer agent pulls work from the API, runs checks locally, and reports results over HTTPS.

Prerequisites

  1. Paid API access — Create an API key on Developers → API keys (for CI triggers, schedules, and agent enroll/claim). Without an active paid key, enroll, rotate, claim, refresh-credential, and enrollment-token issue return 403 with reason: subscription-lapsed; heartbeat still works. See Expiry recovery.
  2. Outbound HTTPS — The agent host must reach your API origin (e.g. https://signaldiff.dev) on port 443 for enroll (UI session or one-time sdet_ token — see Headless enrollment), heartbeat, claim, refresh-credential, and report.
  3. Agent routing enabled — The API must have configuration Features:EnableAgentRouting set to true. On signaldiff.dev this is enabled; self-hosted API operators must set it in app settings or environment variables.
  4. No repository access — Install from published downloads or hosted install scripts.

Setup: enroll → install → configure → verify

1. Enroll in the UI

  1. Open Customer agents and sign in (requires an active paid API key).
  2. Under Agent management, optionally set Agent ID, name, and pool.
  3. Click Enroll agent. Copy the one-time credential from the install commands. Re-enrolling the same agent ID rotates the previous credential.
  4. If a notification email is set in Settings, Signal Diff emails 7-day and 1-day warnings before the credential (or paid API key) expires, with links back here to rotate or refresh from Stripe.

2. Install and configure

After enrollment, use the Windows, Linux, or Docker tab on Customer agents:

  • Install — Downloads /install/agent.ps1 or /install/agent.sh.
  • Configure — Downloads the release zip, verifies SHA256, extracts, and writes appsettings.json.
  • Start — Runs the agent in the foreground (use a service for production; see below).

Default foreground paths: Windows %LOCALAPPDATA%\SignalDiff\Agent; Linux ~/.local/share/signaldiff/agent. Docker image: ghcr.io/funkysi1701/signaldiff-agent with SignalDiffAgent__* environment variables.

3. Verify heartbeat

  1. Start the agent (foreground, service, or container).
  2. On Customer agents, click Send test heartbeat.
  3. Confirm the agent row shows a recent Last heartbeat and a semver under Version (see Version tracking).

For CI runners and servers without a browser, use Headless enrollment instead of pasting a UI credential on the agent host.

Headless enrollment

Bootstrap agents on hosts that cannot open GitHub login. An operator with a signed-in session issues a short-lived one-time token (sdet_…); the agent calls POST /api/agents/enroll with that Bearer token, then heartbeats normally. Paid API keys (sck_) never authorize enroll.

1. Issue a machine enrollment token

From a signed-in machine (SWA session + paid entitlement):

curl -X POST 'https://signaldiff.dev/api/agents/enrollment-tokens' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: <SWA session cookie>' \
  -d '{"suggestedAgentId":"ci-runner-1","suggestedAgentPoolId":"production","ttlHours":1}'

Plaintext token is returned once. Default TTL is 1 hour (max 24). List with GET /api/agents/enrollment-tokens; revoke unused tokens with DELETE /api/agents/enrollment-tokens/{id}.

2. Configure and start (no browser on the agent)

"$HOME/signaldiff-install-agent.sh" \
  --api-base-url 'https://signaldiff.dev' \
  --enrollment-token 'sdet_…' \
  --agent-id 'ci-runner-1' \
  --agent-pool-id 'production' \
  --skip-start

Windows: -EnrollmentToken. Docker: SignalDiffAgent__EnrollmentToken plus SignalDiffAgent__CredentialTokenFile so the issued credential survives restarts. The agent never treats a failed OTT as a credential.

3. After enroll

  • Renewal: POST /api/agents/{agentId}/refresh-credential remains the headless renewal path (credential or RefreshSecret).
  • Rotate: UI Rotate or POST /api/agents/{agentId}/rotate — separate from revoking an unused enrollment OTT.
  • Rate limits: issue / revoke / enroll share AgentProtocol:RateLimits:EnrollPerMinute (default 20/min).
  • Errors: enrollment-token-expired, -used, -revoked, -binding-mismatch, paid-key-not-allowed.

Agent pools and CI

Pools route jobs to specific agents. Use the same agentPoolId on the enrolled agent and on the crawl (schedule, dashboard, or CI). An empty pool on both sides uses the default pool.

curl -X POST https://signaldiff.dev/api/trigger/ci \
  -H "x-ci-api-key: sck_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"sitemapUrl":"https://internal.example/sitemap.xml","failMode":"never","executionMode":"agent","agentPoolId":"production"}'

In Schedules, choose Customer agent as execution mode. See the Schedules guide for pool alignment. For GitHub Actions, set execution_mode: agent and agent_pool_id on signal-diff-action (CI key examples on API keys; agent routing on Customer agents).

Run as a service

Re-run the install script with elevated privileges:

  • Windows (Administrator): -InstallService — service signaldiff-agent, default %ProgramData%\SignalDiff\Agent
  • Linux (sudo): --install-service — systemd signaldiff-agent, default /opt/signaldiff/agent
  • macOS (v1): foreground or nohup until launchd support is added

Credential rotation

  1. On Customer agents, click Rotate on the agent row (blocked while a job is active).
  2. Copy the one-time refresh secret into durable deploy config (SignalDiffAgent:RefreshSecret or install -RefreshSecret / --refresh-secret).
  3. Restart the agent process or service on every host that uses that agent ID.

Current agents auto-refresh the short-lived credential when expiry is within 7 days, and once on claim token-expired. Prefer storing the refresh secret (it does not expire with the credential). Re-enrolling the same Agent ID also rotates; Rotate is the supported UI path.

Recover from credential expiry vs subscription lapse

These are different failures. Read the JSON reason before changing config or billing.

What you see Cause Fix
401 reason: token-expired on heartbeat, claim, or report Short-lived agent credential expired (or rotated elsewhere). Restart the agent if RefreshSecret is configured (auto-refresh within 7 days, plus a 72-hour grace window for refresh-credential). Otherwise Rotate on Customer agents and update every host. Do not start a new Stripe checkout for this.
403 reason: subscription-lapsed on enroll, rotate, claim, or refresh-credential No active paid API key. Heartbeat still works. If Stripe is still active, use Refresh expiry from Stripe on Customer agents or API keys. If the subscription ended, renew on Pricing, then enroll or rotate if needed.

If both are true, fix billing first: refresh-credential also returns subscription-lapsed until a paid key is active. Settings notification email (when set) sends 7-day and 1-day warnings. Full operator playbook: Troubleshooting — customer agents.

Version tracking

Each agent reports its build version on heartbeat (User-Agent: SignalDiff.Agent/<version>). On Customer agents, the Version column shows the reported semver and a badge:

Badge Meaning
Up to date Matches the latest release on downloads/agent
Update available A newer release is published—open Deploy for pinned upgrade commands
Unknown No version reported yet (agent offline or pre-version build)

When updates are pending, the fleet summary shows Need update and a Latest agent release link. Download pages list each zip with an approximate file size and a .sha256 sidecar.

Upgrades

When Update available appears, click Deploy on the agent row. An Update available banner shows copy-paste commands pinned to the latest release (-UpdateOnly / --update-only, optional version flag). Your credential and appsettings.json are preserved.

  1. Check downloads/agent or index.json for the latest release.
  2. From Deploy wizard: use the pinned update snippet when the banner is shown (recommended).
  3. Manual script: re-run the install script with -UpdateOnly / --update-only and -RestartService / --restart-service for service installs.
  4. Manual binary: stop the agent, replace binaries, keep appsettings.json, restart.
  5. Docker: docker pull with an explicit version tag and recreate the container.

Troubleshooting

Symptom What to check
Heartbeat fails (401 token-expired) Short-lived credential expired — see Expiry recovery. Heartbeat is not blocked for a lapsed subscription. Other 401 reason values: rotate after version-mismatch, enroll after revoked, check ApiBaseUrl for format/signature errors.
Enroll / rotate / claim / refresh-credential / enrollment-token issue returns 403 subscription-lapsed Paid API key expired or missing. Renew on Pricing, or use Refresh expiry from Stripe on Customer agents or API keys if the subscription is still active. Heartbeat still works for fleet visibility.
Headless enroll fails (enrollment-token-* / paid-key-not-allowed) Issue a fresh sdet_ token (Headless enrollment). Do not paste a paid sck_ key or a credential into EnrollmentToken. Binding mismatch does not consume the token — align agent id / pool and retry.
Heartbeat OK, no crawls Job executionMode must be agent. API Features:EnableAgentRouting must be true. Job tenant must match the agent owner. Also check for subscription-lapsed on claim.
Jobs stay Pending Agent not running, outbound HTTPS blocked, or pool mismatch.
UI shows Running, agent idle Stuck run without report; agent only claims Pending jobs. Wait for stale reconciliation or clear the job.
429 Too Many Requests Too many agents or aggressive polling; avoid duplicate processes with the same agent ID.
Version shows Unknown Agent not heartbeating or on an old build. Start the agent and wait for the next heartbeat.
Firewall Allow outbound HTTPS to the API host; no inbound ports on the agent.

More scenarios (stuck Running, report chunk limits, CI pool alignment): Troubleshooting — customer agents.