The Console

The Console is loopkeep's web control plane. It manages accounts, paired devices, and the GitHub and Slack connections that let outside events trigger workflows on your machine. It is deliberately not a place your work goes — the architecture is built so the Console knows as little as possible.

How events reach your machine

GitHub / Slack / your webhook ──▶ gateway ──verify → match──▶ your daemon ──▶ run

Your daemon keeps one outbound WebSocket connection to the Console — no inbound ports on your machine. When an event arrives, the gateway verifies its signature, evaluates it against the trigger declarations your daemon uploaded, and delivers the ones that matched along with the text that caused them: the comment, the message, the issue title.

Pairing also synchronizes a workflow catalog for listings and controls, plus a separate set of routing declarations for event delivery. When remote supervision is enabled, the daemon separately uploads its latest snapshot of active runs. That snapshot contains state and source locators, not workflow bodies, prompts, event bodies, or transcripts.

That text is carried, not kept. It is held only as long as delivery needs — store-and-forward, so an event that arrives while your machine is asleep lands on reconnect — it goes only to devices on your own account, and nothing is done with it beyond routing and delivery. Text over 64 KB is truncated and marked as such.

What never crosses: your repositories and their source, your .env and secrets, your policy files, your prompts, and your run transcripts. Signing in uploads none of it. Connector credentials are used for the GitHub and Slack features you configure, never to read your code. The Slack bot token supports directory and channel visibility or membership checks, requested thread context, and outbound replies, posts, and notifications; a separate signing secret authenticates incoming Slack events.

Your daemon starts what the gateway matched, without re-checking the event. The supervision that matters is local and unchanged: every action a run proposes is still graded by your policy and floor, whatever triggered the run.

Coming back online

While your device is offline the gateway holds what matched — up to 1000 signals, for up to 24 hours. Reconnecting delivers all of it, which after a long outage can be a lot at once.

loopkeep does not let that turn into a hundred runs. On reconnect it spends five seconds collecting the burst. If fewer than ten signals arrived, they simply run, as they would have at the time. If ten or more arrived, none of them run yet: loopkeep saves them and puts one card per workflow in your inbox, saying how many there are and how old the oldest one is. Each card offers three answers:

  • Run all — one run per event, oldest first.
  • Run latest only — a single run on the newest event. Every event is still handed to the agent, so nothing is hidden from it.
  • Deny — discard the backlog without running anything.

Nothing is lost while you decide. The backlog is written to disk before the gateway is told it was delivered, so it survives a daemon restart and the card comes back with it. A workflow set to launch: confirm does not ask a second time — answering the card is the confirmation.

Devices

Pair with lk login or the desktop app's Log in and pair this device (see Pair with the Console). Each device holds its own token and signing key, listed under Devices in the Console, revocable individually — the recovery path for a lost or stolen laptop.

On an entitled device that advertises remote automation control, Stop automation sets the same persisted global trigger gate as lk pause --all. It stops future trigger-driven runs across every workspace; manual runs and runs already in flight continue. Resume is local-only, with lk resume --all or the desktop/TUI setting.

Stop remains available when the device is offline or its presence is unknown. That does not make it a queued command: the Console reports when the request was not delivered, and local automation keeps running unless the device accepted the gate. A control request fails closed before it is sent. Failure to fetch presence from the gateway returns HTTP 503 (presence_unavailable); when the fetch succeeds but the selected device is not online, the request returns HTTP 409 (device_offline).

After a Stop is accepted, the Console recommends Revoke as a separate next step to cut remote access. The sequence is not one transaction. Revoke disables remote features and deletes that device's uploaded run snapshot, workflow catalog, and trigger-routing declarations; the device's local data and local automation remain. If Stop is rejected, expires, or remains unconfirmed, inspect the device before retrying.

Active runs

The Console's Runs page shows nonterminal runs from paired devices with remote supervision enabled. It is the latest full snapshot from each device, not cloud run history. An uploaded snapshot identifies the workspace by ID and display name. Each run payload includes the workflow, run ID, state, supervision flags, start time, and a source locator such as a GitHub repository and issue number or Slack channel and timestamp. Titles, bodies, authors, sessions, worktrees, engine settings, prompts, and transcripts are excluded. While the daemon knows remote_supervision_enabled is false, it does not build a snapshot and discards any snapshot waiting to be uploaded. A request already in flight, or sent using stale entitlement state, can still put a body on the network, so this is not an absolute zero-byte guarantee.

The table does not expose every uploaded field. Its visible columns are State, Run, Workflow, Device, Origin, and Supervision. Start time is used only to sort the rows. You can filter by device, workspace, and state.

The empty area distinguishes four outcomes: there are no active runs; the account's plan does not include remote supervision; an online device has not reported run state and needs an update; or active runs exist but the current filters match none of them. An offline device with no prior snapshot cannot prove it is old, so it falls back to the no-active-runs view.

For a finishable attended run on an online capable device, Finish asks that device to apply its existing local Finish operation. It detaches supervision without aborting, rolling back, or killing the terminal session. A run that has already emitted output becomes completed; without output it becomes interrupted and resumable. If the result cannot be confirmed, inspect the device before retrying.

GitHub

Install the loopkeep GitHub App on your account or organization from the Console's Connections page. Repository events (issues, comments, pull requests, reviews, pushes, CI runs) then reach your daemon.

Authorizing the install also links your GitHub login — the person who authorized it, not the account the App is installed on. That login is what me resolves to in a trigger like mentions: me, and Connections shows it as Linked as @you. Connecting again overwrites it. Until it is linked, me matches nothing rather than matching everyone.

Workflows bind to repositories implicitly: a workspace whose origin remote points at a repository receives that repository's events, with no configuration. To subscribe across repositories, name them explicitly:

on:
  github:
    issues: {}
    repos: ["org/other-repo"]

Disconnect on the Connections page releases loopkeep's side of the connection so events stop reaching your daemon; the GitHub App itself is uninstalled from GitHub's own settings.

Slack

Connect a Slack workspace from the Console with Add to Slack — one shared app, no tokens to create or paste. Workflows can then trigger on mentions, DMs, keywords, and reactions (see Triggers).

Identity is per workspace: the Slack user who authorizes a workspace is who me resolves to for events from it. Connect from each workspace you want to be recognized in.

Disconnect on the Connections page revokes the stored token and removes the connection.

Why a remote event did not arrive

The Connections page shows today's gateway delivery-stop counters grouped by reason: no declaration or filter match, inaccessible or unverified resources, quota/rate limits, offline-queue outcomes, and related routing decisions. The day resets at midnight UTC. These are aggregate diagnostics, not per-event logs or historical audit records, and one incoming event may add counts for several devices or workspaces. If the gateway cannot be reached, the count is unknown, not zero. lk trigger explain describes the local declaration and points here for these delivery counters.

Webhooks

For anything with no connector — a CI job, a home server, a service that can POST — issue a personal URL:

lk hooks add deploy-done

The URL is printed once, and knowing it is the entire credential: whoever holds it can wake your workflows. Keep it where you keep other secrets, and lk hooks rotate <name> if it leaks — the name stays the same, so workflows that declare it don't change. lk hooks lists the names you have; lk hooks rm <name> refuses posts from then on. Declaring them is covered in Triggers.

Start a run from the Console

Workflows that declare a manual trigger appear on the Console's Workflows page, grouped by workspace and device. Press Run and the Console asks that device to start it — the workflow, its worktree, and everything it reads stay on that machine; only the request travels.

A device has to be online to be asked, and if the same workflow exists on several devices you pick which one. The request is a one-shot permit that expires after two minutes, so a request the device never picked up can't surface as a run later.

This is a paid-plan feature. On the free plan your workflows are still listed; the run buttons are hidden.

On an entitled account, the same page can enable or disable a workflow's automatic triggers on one device. Manual runs and runs already in flight are unaffected. The switch is shown only when the device reports a known disabled state and the remote_workflow_control capability; older devices stay read-only and show an update hint. That compatibility state requires current online presence: an offline device shows neither the read-only chip nor the update hint.

Plans and quotas

FreePro
Paired devices15
Connections110
Wake signals per day1005,000
Start runs from the Console
Remote supervision and controls

A connection is one GitHub installation or one Slack workspace; both count against the same limit.

Wake signals are counted per delivery, not per event: an event that matches workflows on two of your devices spends two. Once the day's quota is gone, further signals are dropped for the rest of the day (UTC); the Console's overview says how many were dropped. Local triggers — cron, file-watch, git, manual — are not affected and never count. Runs you start from the Console don't count either, so an exhausted quota never blocks you from starting a run yourself.

The guarantees

  • Your code stays home. Repositories, files, .env, secrets, prompts, and run transcripts are never uploaded. What crosses is the event you subscribed to on its way to your machine, plus the minimal workflow catalog, routing declarations, and active-run metadata described above.
  • Nothing you didn't subscribe to is delivered. Matching happens before delivery, and an event nobody's triggers matched is discarded.
  • The runtime is never gated. Signed out, offline, Console down — local triggers, runs, the inbox, and the desktop app keep working, indefinitely. Console Stop is a narrow, persisted, locally reversible gate on future automatic firings, not a process or runtime kill switch.
  • Revocation is real. A revoked device can no longer connect to the Console or obtain connector credentials. It does not stop local automation by itself.