Watch · The Capture Daemon

The Capture Daemon

The quiet engine under everything RepoOps proves. The capture daemon is an always-on background agent that captures your Claude Code work and keeps your dashboard current, with no server running. It opens no inbound network port, stays inert until you bind a device and opt in, and runs as a real operating-system service that starts at login and restarts itself on a crash. It is what makes your proof-of-work and cost visibility always current instead of something you have to go refresh.

What it is

The capture daemon is a single background process. Once installed it captures every Claude Code session within a few minutes, survives a laptop-lid-close and a reboot, and needs no dashboard server running to do its job. It is deliberately small and honest about its boundaries:

  • No inbound network port. The daemon never listens. It only makes outbound HTTPS calls, and only after you opt in. There is no remote surface to attack.
  • Inert until you say so. It ships doing nothing over the network. Nothing leaves your machine until you bind the device to a team and turn on cloud sync. On the local app, capture stays entirely on your machine.
  • A real OS service. It installs as a launchd agent on macOS, a systemd user service on Linux, and a Scheduled Task on Windows (with a no-admin fallback when group policy blocks task creation). That means auto-start at login and auto-restart on crash, handled by the operating system itself, not a bundled supervisor.
  • Redaction at the point of writing. Every captured event is redacted before the line is ever written to local disk, let alone sent anywhere. Secrets, tokens, keys, and terminal output are stripped at the chokepoint.
You do not have to run anything
The daemon is an optimization, not a requirement. Every dashboard view still computes on demand if the daemon is off. A stopped daemon makes RepoOps slower, never wrong, and never down.

Architecture

The operating system supervises the daemon. The daemon runs a handful of fixed-cadence loops, writes to a local database and a warm cache, and (only when you opt in) pushes redacted data outward. The local dashboard reads what the daemon already wrote.

               YOUR MACHINE (laptop or build host)
 +-------------------------------------------------------------+
 |  OS service manager  (launchd / systemd / Scheduled Task)   |
 |     auto-start at login, auto-restart on crash              |
 |                          |                                  |
 |                          v                                  |
 |  +-----------  capture daemon (one process)  ------------+  |
 |  |  every 30s     heartbeat   status write + memory       | |
 |  |                            sample                       | |
 |  |  every 5min    capture     scan Claude Code sessions,   | |
 |  |                            redact, push redacted events | |
 |  |  every 1h      brain       redacted brain snapshot      | |
 |  |  every night   learning    a distilled brief for the    | |
 |  |                            morning                       | |
 |  |                                                         | |
 |  |  self-sizing memory ceiling -> graceful recycle         | |
 |  |  crash-loop guard . hang watchdog . single-instance lock| |
 |  +------+---------------------------------+----------------+ |
 |         | writes                          | writes           |
 |         v                                 v                  |
 |    status file                 local database + warm cache   |
 |         ^                                 ^                   |
 |         | reads                           | reads            |
 |    local dashboard  <---------------------+                  |
 |    (no server required for capture)                          |
 +----------------------------+--------------------------------+
                              | outbound HTTPS only, no inbound
                              | port; inert until bound + opted in
                              v
            repoops.ai hosted dashboard (your device fleet)

The loops, and what guards each one:

  • Capture, every five minutes. Scan each tracked repo's Claude Code sessions, redact, and drain the redacted events outward. A four-minute watchdog aborts a hung cycle so cycles never stack.
  • Brain snapshot, hourly. A redacted, compressed snapshot of your brain, so the knowledge surfaces stay current.
  • A nightly learning pass. Once per day the daemon runs the reflection and digest work, so you wake up to a distilled brief instead of a raw log.
  • Heartbeat, every thirty seconds. A status write plus a memory sample. This is how status stays honest: a frozen number is never shown as live.
  • A self-sizing memory ceiling. The ceiling is a share of your machine's RAM, not a hand-set constant, so it scales with the box. Three readings in a row over the ceiling trigger a graceful recycle: the daemon writes a final honest status, releases its lock, exits, and the OS restarts it fresh. A transient spike that drops back never recycles.

Where it is strong: reliability and privacy

Fault tolerance is layered, so no single failure takes capture down for long:

  • The OS restarts it. launchd keep-alive, systemd restart-on-failure with rate limiting, and a Windows restart policy each bring the daemon back after a crash.
  • A crash-loop guard stops a hot loop. If the daemon crashes repeatedly in a short window it exits cleanly with an honest disabled status, so it does not thrash. A healthy boot resets the counter.
  • A hang watchdog aborts a stuck cycle before it can freeze the process.
  • A graceful memory recycle hands control back to the OS instead of being killed hard.
  • A single-instance lock means two copies never fight over the same data.
  • Liveness on Linux. The systemd unit uses the operating system's own watchdog: the daemon signals liveness on every heartbeat, so a fully wedged process is detected and restarted in minutes.
  • A cloud dead-man's switch. When you use the hosted dashboard, a device that claimed it was running but goes silent raises an alert to team owners and admins, once per dark episode.

Privacy is a default, not a setting you have to find:

  • Inert-by-default egress. Nothing leaves the machine until you bind a device and opt in. Every outbound path is double-gated on a real binding and an explicit sync toggle.
  • Redaction at the point of writing. Events are redacted before the line touches disk. The hosted side re-redacts on receipt as a second layer.
  • Least-privilege service units. The generated Linux service runs with restricted privileges (no new privileges, a read-only system view, a private temp directory). On Windows the daemon tightens the access control on its own data directories when it creates them. On macOS it runs as a low-priority background job.
  • Honest status. Status is marked stale the moment the daemon stops, push timestamps are stamped only when data actually left the machine, and the status command cross-checks that the process is really alive.
  • A one-command support bundle. daemon flare zips a redacted diagnostic bundle (status, recent logs, boot history, crash reports, version) with secret values masked, so you can attach it to a support request without leaking anything.
  • Native crash reports. A fatal crash leaves a diagnostic report on disk, so a hard failure can be understood after the fact instead of guessed at.

How it compares to world-class daemons

We audited the capture daemon against the daemons the industry treats as the reference points, one axis at a time. The short version: it meets them on supervision, security posture, memory governance, and telemetry push, and it beats most of them on two properties that matter more than any single feature.

  • Supervision the way a systemd service is supervised: the operating system owns start, restart, and rate limiting, on all three platforms, with no bundled supervisor to rot.
  • A local daemon in the shape of the Docker Engine daemon: one long-lived local process the rest of the product talks to, except this one has no socket and no API to talk to at all.
  • The no-inbound-surface posture of a well-run SSH daemon, taken further: there is no listener to harden, because the daemon never accepts a connection.
  • Telemetry push in the shape of a host-monitoring agent like the Datadog Agent: capture at the source, push outward, back off under pressure. The difference is the default: this agent sends nothing until you opt in, where a monitoring agent starts full egress the moment it has a key.
  • A percent-of-RAM memory limiter like the OpenTelemetry Collector's: the ceiling is a share of the host, not a magic number, so it is right on a small laptop and a big build box alike.
  • Signed, staged updates and device identity in the shape of Tailscale and Syncthing: updates are verified before they run and rolled out in stages, and each device carries its own identity rather than a shared secret.

Two things most peers do not have:

  • Daemon-optional correctness. Most agents are a single point of failure for the feature they power. This one is not. Every view still computes on demand; the daemon only pre-fills. A dead daemon degrades to slower, never to down.
  • Inert-by-default privacy with redaction at the point of writing. The stronger privacy default: the daemon sends nothing until you say so, and what it does keep is redacted before it is ever written, not scrubbed later.

Trust and updates

A background agent that can update itself has to earn that trust cryptographically. The RepoOps update model is built so that no unsigned bytes ever run:

  • Verify before anything runs. An update is checked against a signature and a content hash before a single byte is trusted. A tampered artifact fails verification and is never staged.
  • Stage, then swap. A verified update is staged alongside the running version and swapped in on a clean restart, keeping the previous version in reserve.
  • Roll out in stages. A release reaches a slice of the fleet first and widens from there, so a bad build is caught small.
  • Self-heal on a bad update. If a freshly swapped version starts crash-looping, the same crash-loop guard that protects the running daemon flips back to the previous version and quarantines the bad one.

Today the daemon surfaces “update available” on the tray and in its status, and new code arrives through the signed desktop updater. Device identity follows the same trust posture: each device is bound with its own token rather than a shared secret, and the identity model provides for tokens that expire and rotate in place without a capture gap, plus owner and admin revocation of any device from the team.

Mechanism, not a marketing date
We describe how these protections work rather than promising a ship date. The point is the shape of the trust model: verify first, stage, roll out gradually, and roll back automatically.

One daemon, one data path

There is one daemon codebase, and it feeds two dashboards through a single seam. Locally it writes a local database that the on-machine dashboard reads. When you connect a team, the same daemon pushes through one authenticated adapter into a single shared rollup substrate that the hosted dashboard reads. A version handshake on both sides means a reader never serves a payload shaped for a different version: on a mismatch it recomputes rather than showing something stale-shaped.

  • Local and hosted read the same substrate. The finalized daily rollups the hosted dashboard reads are the same rollups the daemon produces. There is no bespoke second aggregation to drift out of sync.
  • Today is always live. The current day is recomputed on read; only finalized past days are served from the rollup, so the numbers you see for today are never a frozen snapshot.
  • A server-side option for teams. The same daemon can run in a container so a team whose developers are not always online still gets capture. It reuses the hosted image substrate, keeps the no-inbound-port posture (its health check is a status-file freshness probe, not a port check), and binds with a normal device token. Standing up that host is an explicit operator step.

How to run it

  • Install as a service: npm run daemon:install registers the OS service (launchd, systemd, or a Windows Scheduled Task) so the daemon starts at login and restarts on crash. npm run daemon:uninstall stops it gracefully and removes the service.
  • Check status: npm run daemon:status prints the honest truth in the terminal (running or not, version, last capture, warm count). The Today tab shows the same “this machine” card, and Settings has a plain on/off toggle that takes effect without a restart.
  • The tray control. On the desktop app, the tray lets you start, stop, and check the daemon, and badges “Update available” when a newer build is published.
  • The support bundle: npm run daemon then flare (or node scripts/daemon.mjs flare) writes a redacted diagnostic bundle you can attach to a support request.
  • The container option. To run the daemon server-side for a team, build the daemon container image, mount a bound data directory, and set a restart-on-failure policy. There is no port to expose and no bind secret to set: the container is egress-only and binds with the device token in its mounted volume.

Read more