Watch · The Capture Daemon
The Capture Daemon
What it is
The capture daemon is a single background process. Once installed it captures Claude Code sessions from each tracked repository 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. No code, brain content, or telemetry leaves your machine until you bind the device to a team and turn on cloud sync. The one exception is an anonymous reliability beacon (crash and version counts), on by default and switchable off in the app.
- 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 both boundaries. Events going to the universal event store, the terminal tail among them, are redacted before the line touches disk. Captured transcript content (your prompts, the assistant replies, proposed code, and opt-in tool output) is held in a local store that is never committed, and is redacted on the way out, so nothing unredacted leaves the machine.
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 and redact them into the local store. A four-minute watchdog aborts a hung cycle so cycles never stack.
- Delivery to repoops.ai, once an hour. On the hour, plus a fixed offset of 30 to 89 seconds for each machine, one capture cycle drains the redacted events outward and makes every other call to repoops.ai: the health report, the cost rollups, transcript requests, connector and config pulls. The hourly publishers follow two minutes later. Bunching them lets the hosted database sleep between bursts. REPOOPS_HOSTED_FULL_INTERVAL_MS changes the period.
- Urgent requests, every 15 minutes. On each other quarter hour the machine asks repoops.ai one question that never touches its database: is a transcript request or a containment request waiting for this team? Only when the answer changed does it pull them. REPOOPS_HOSTED_BURST_INTERVAL_MS changes that period.
- 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. No code, brain content, or telemetry leaves the machine until you bind a device and opt in; every such outbound path is double-gated on a real binding and an explicit sync toggle. The anonymous reliability beacon is the one default-on path, and it carries neither.
- Redaction at two chokepoints. Envelope events are redacted before the line touches disk, and a policy that fails to load falls back to the strictest profile rather than the loosest. Captured Claude Code transcripts stay in a gitignored local store and are redacted at the egress boundary, before anything is sent. Brain snapshots are redacted again on the hosted side.
- 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 flarewrites a redacted diagnostic directory (status, recent logs, boot history, the crash-report file names, version) for you to zip and attach to a support request. Environment variable values never enter it; the bundle records names only. - 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.
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.
Keeping the heavy artifacts warm
Some of what the dashboard shows is expensive to build: the relationship map, the code and recall graphs, the causal edges behind blast radius. The daemon can rebuild those in the background so a tab opens on a finished file instead of waiting for the build. It is off until you turn it on.
The switch is on the Today tab, on the card headed “this machine”: Let the daemon keep these artifacts current. Settings carries the same setting as a row. Both write it to this machine’s configuration, which survives app updates, and share it with your other connected machines.
- The card tells you who built what. It reports whether the pass is on and when it last ran. With the pass off, the artifacts you see were built by the dashboard’s own startup warm and by opening the tabs that use them, and the card says that instead of implying the daemon did it.
- It costs memory while it runs. On a machine with 32 GB, one full pass over five repositories took under a minute and added roughly a gigabyte to the daemon’s footprint for the duration. After the first pass it only rebuilds what changed. On a small machine, leaving it off is a reasonable choice.
- It never changes an answer. Every one of these reads still builds on demand when no warm file exists. The pass only moves that work off the moment you click.
The native collector
RepoOps in the default browser carries a second way to capture: the native collector, a small program that reads the same Claude Code transcripts as the daemon and starts Node only for the daemon's other work. It is an evaluation and it is off by default. With it off, nothing on this page changes.
The switch is in Settings, under Capture daemon: Capture with the native collector. The same card says which capture path runs now, and the Today tab's card for this machine names it beside the version.
- It applies when the service starts. Turning it on takes effect the next time the RepoOps service starts, after you sign out and back in or restart the computer. Turning it off takes effect after the pages have been idle for ten minutes, when the service starts again on its own. Until then the card says the saved choice is waiting.
- Only the browser build reads it. In the desktop app, or a server started from a checkout, the switch is disabled and says why: those run the Node daemon.
- The capture switch still rules. With Run the background capture daemon off, the collector captures nothing either.
- Where it is saved. As
REPOOPS_NATIVE_COLLECTORin this machine's data folder.env, where a saved choice wins over the same name set in the environment.
How to run it
If you installed the desktop app, it already runs the daemon. The app starts it when it opens and restarts it if it stops, as long as Run the background capture daemon is on in Settings (the default). Restarts wait 10 seconds, then twice as long each time; after five restarts in 30 minutes the app stops trying and the tray reads Daemon: off (crash loop). Use the tray to start, stop, or check it, and the Today tab to see what it last captured. The commands below are for running RepoOps from a checkout, and need Node and a terminal.
The daemon is the part that stays on. Once no RepoOps window has been open for 10 minutes, the desktop app stops its local server and frees that memory; the tray then reads Server stopped while no window is open; capture continues. The daemon keeps capturing, and it also runs what the server used to run for your team: the telemetry and agent trace stream, transcript requests (the tray badge still counts them), operator configuration, the team policy refresh, license renewal and the store retention sweeps, and also the hosted rollups (brain pulse, open risks, Spend Efficiency), fleet control, prevention sync, the session signals scan and its alerts, Sentry, Vercel and GitHub imports, the nightly brain passes, the opt-in captures and syncs, and your team's decisions on local cases. The heavy ones run in a worker thread that stops when it is done. The session signals scan runs every 15 minutes while new sessions are being captured, and once a day when nothing new has arrived. Show dashboard, opening the app again, or any request to the old local address starts the server again, and the first request waits while it starts. Set REPOOPS_DESKTOP_SERVER_IDLE_MIN to a number of minutes in the app's environment to change the wait, or to 0 to keep the server running.
- Install as a service:
npm run daemon:installregisters the OS service (launchd, systemd, or a Windows Scheduled Task) so the daemon starts at login and restarts on crash.npm run daemon:uninstallstops it gracefully and removes the service. - Check status:
npm run daemon:statusprints the honest truth in the terminal (running or not, version, last capture, last push). The Today tab shows a “this machine” card with that state plus the warm-artifact count, 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 -- flare(ornode 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
- Daemon status: the hosted fleet view of every device's daemon, its version, and when a device goes dark.
- Install: get RepoOps and the daemon running on your machine.
- Building a world-class daemon: the audit behind this page, and what we changed.
Last updated