Paperclip Heartbeat System Configuration Options: Complete Guide

Paperclip's heartbeat system is configured through a typed JSON schema with options for enabling the scheduler, setting the wake-up interval, controlling concurrent runs, and tracking runtime state across agents and the UI.

The heartbeat system in paperclipai/paperclip drives periodic "wake-up" cycles that keep agents responsive and the internal scheduler healthy. Operators control this behavior through instance-level configuration options defined in the shared type system and exposed via API and UI. This guide covers every configuration field, its purpose, and how to modify it programmatically.

Core Configuration Options

The heartbeat configuration lives on the instance level and synchronizes across the server, UI, and adapter utilities. All options are defined in packages/shared/src/types/heartbeat.ts and packages/shared/src/types/instance.ts.

Static Configuration (Editable)

These settings persist and control baseline behavior:

Option Type Default Purpose
heartbeatEnabled boolean false Master toggle for the entire heartbeat scheduler. When false, no timer-driven runs queue.
intervalSec number 900 (15 min) Seconds between automatic heartbeat runs. Lower values increase agent responsiveness at higher resource cost.
schedulerActive boolean true Allows the UI to pause scheduling without disabling the system entirely. Distinguishes capability from current state.
maxConcurrentRuns number 2 Internal ceiling on simultaneous heartbeat runs per company. Prevents cascade failures when runs stall.

Runtime State (System-Managed)

These fields track live execution and appear in API responses:

Option Type Description
lastHeartbeatAt Date | null Timestamp of the most recent successful run. The UI displays this as a health indicator; the watchdog uses it to detect stalls.
heartbeatRunId string | null Unique identifier attached to all logs, cost records, and task updates for attribution.
runLivenessState "queued" | "running" | "completed" | "failed" Lifecycle state rendered as progress bars and status pills in the UI.
heartbeatRunOutputSilence object Tracks silent periods with suspicionThresholdMs and criticalThresholdMs thresholds. Surfaces warnings when no stdout/stderr appears.
heartbeatInvocationSource "timer" | "manual" | "api" Origin of the run: automatic timer, admin action, or external API.
triggerDetail object WakeupTriggerDetail payload containing the triggering issue and reason, propagated through the run-graph for tracing.

Where Configuration Lives

Paperclip's type-safe architecture ensures consistency across components:

Reading and Modifying Configuration

Retrieve Current Settings

import { heartbeatsApi } from '@/api/heartbeats';

const companyId = 'c123';
const agents = await heartbeatsApi.listInstanceSchedulerAgents();
const scheduler = agents.find(a => a.companyId === companyId);

console.log('Heartbeat enabled?', scheduler?.heartbeatEnabled);
console.log('Run interval (sec)', scheduler?.intervalSec);
console.log('Last successful run', scheduler?.lastHeartbeatAt);

The listInstanceSchedulerAgents() method returns all scheduler agents visible to the authenticated session, filtered by permissions.

Enable and Tune the Scheduler

await heartbeatsApi.updateInstanceSchedulerAgent(
  scheduler!.id,
  {
    heartbeatEnabled: true,
    intervalSec: 600,      // 10-minute wake-up cycle
    schedulerActive: true
  }
);

Changes take effect immediately. The server reconfigures its timer without requiring restart. Validation in runtime-config.ts rejects invalid combinations (e.g., negative intervals).

Inspect Live Run State

const runs = await heartbeatsApi.liveRunsForCompany(companyId);
const activeRun = runs.find(r => r.status === 'running');

if (activeRun) {
  console.log('Run ID:', activeRun.heartbeatRunId);
  console.log('Liveness:', activeRun.runLivenessState);
  console.log('Invoked via:', activeRun.heartbeatInvocationSource);
  
  if (activeRun.heartbeatRunOutputSilence) {
    console.log('Silence warnings:', activeRun.heartbeatRunOutputSilence.level);
  }
}

The liveRunsForCompany() endpoint returns full runtime state including silence detection data used for operational monitoring.

Triggering Runs Manually

For testing or ad-hoc agent activation, bypass the timer:

await heartbeatsApi.triggerRun({
  companyId,
  issueId: 'PAP-101',      // Optional: focus on specific issue
  source: 'manual',         // 'timer' | 'manual' | 'api'
  triggerDetail: {
    reason: 'deployment-verification',
    requestedBy: 'admin-007'
  }
});

Manual triggers respect maxConcurrentRuns and populate the same heartbeatRunId tracking as automatic runs.

UI Administration

Administrators configure heartbeat settings in ui/src/pages/InstanceSettings.tsx, which:

  • Reads current values via heartbeatsApi.listInstanceSchedulerAgents()
  • Renders status pills from runLivenessState
  • Writes changes through updateInstanceSchedulerAgent()
  • Displays lastHeartbeatAt as relative health indicators

The UI separates heartbeatEnabled (system capability) from schedulerActive (operational state), allowing emergency pauses without configuration loss.

Summary

  • Enablement control: heartbeatEnabled is the master switch; schedulerActive allows runtime pausing
  • Frequency tuning: intervalSec sets wake-up cadence in seconds (default 900)
  • Resource protection: maxConcurrentRuns caps parallel execution at 2
  • Observability: Runtime fields (heartbeatRunId, runLivenessState, heartbeatRunOutputSilence) provide full traceability
  • Type safety: All options validated in packages/shared/src/validators/runtime-config.ts

Frequently Asked Questions

What happens when heartbeatEnabled is set to false?

The scheduler stops queuing timer-driven runs entirely. Existing runs complete, but no new automatic cycles begin. Manual triggers via triggerRun() still function, allowing ad-hoc agent execution without re-enabling the system.

How does maxConcurrentRuns protect against resource exhaustion?

When a heartbeat run stalls (infinite loop, blocked I/O), additional timer ticks would normally queue overlapping runs. maxConcurrentRuns: 2 prevents more than two simultaneous runs per company, containing the blast radius until the watchdog detects the stall via lastHeartbeatAt age.

Can I change the interval without restarting Paperclip?

Yes. The updateInstanceSchedulerAgent() API applies intervalSec changes immediately. The server recalculates the next wake-up time from the current moment, not the previous schedule. This is implemented in the server-side scheduler without process restart.

What is the difference between heartbeatRunOutputSilence and runLivenessState?

runLivenessState tracks coarse execution phase (queued → running → completed/failed). heartbeatRunOutputSilence monitors fine-grained I/O health during the running phase, detecting when agents produce no output for configured thresholds. A run can be running while flagged for silence, triggering UI warnings before eventual failure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →