Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hunvreus/heypi/llms.txt

Use this file to discover all available pages before exploring further.

Heypi can create proactive agent turns on a schedule — no external cron daemon, no queue server, no separate service. The scheduler runs inside your Node process alongside the adapters. When a scheduled turn fires, it uses the same thread history, the same tools, and the same approval flows as any user-initiated message.

Two Job Types

cron

Run an agent turn at a wall-clock schedule. Requires explicit targets — concrete channel or user IDs where the message will be delivered.

heartbeat

Run proactive turns over known chats that have been seen by the agent. Fires after a schedule and an optional idle window. Requires either scope or targets.
Scheduling is not a workflow engine. A job creates a normal heypi turn and delivers through the configured adapter. Keep the process running for scheduled work to fire.

Job Model

A job is defined by the following fields:
FieldTypeDescription
idstringUnique job identifier (scoped per agent)
kind"cron" | "heartbeat"Job type
schedule{ at }, { everyMs }, or { cron, timezone }When to fire
everyMsnumberShorthand for { everyMs } schedule
targetsRecord<string, { channels?, users? }>Concrete delivery destinations, keyed by adapter name
scopeRecord<string, { teams?, channels?, users? }>Heartbeat filter over known threads, keyed by adapter name
idleMsnumberOptional idle window: skip if the chat has been active within this duration
promptstringThe synthetic message sent into the agent
state"active" | "paused"Optional initial state

Routing Rules

1

cron jobs

Must have targets. Fires at the wall-clock schedule and delivers to every listed channel and user.
2

heartbeat jobs with scope

Must have scope. Fans out over all stored threads matching the adapter filters. Use idleMs to skip chats that have been recently active.
3

heartbeat jobs with targets

Must have targets. Acts like a cron but delivers to the listed channels/users using heartbeat semantics.
targets and scope are mutually exclusive. idleMs only applies to heartbeat jobs using scope.

Adapter Keys

Adapter keys in targets and scope are the configured adapter names, not the provider kind. If you set up your adapter as slack({ name: "acme", ... }), use "acme" as the key — not "slack".
targets: {
  acme: {                  // "acme" is the adapter name, not "slack"
    channels: ["C123", "C456"],
    users: ["U123"],
  },
}

Full Example

createHeypi({
  state: { root: "./state" },
  // ...adapters, agent, runtime
  jobs: [
    {
      id: "daily-checkin",
      kind: "heartbeat",
      everyMs: 24 * 60 * 60 * 1000,
      idleMs: 8 * 60 * 60 * 1000,
      scope: { telegram: {} },
      prompt: "Run the daily check-in skill.",
    },
    {
      id: "weekly-ops",
      kind: "cron",
      schedule: { cron: "0 9 * * 1", timezone: "America/Los_Angeles" },
      targets: { slack: { channels: ["C123"] } },
      prompt: "Run the weekly ops review.",
    },
  ],
});
The daily-checkin heartbeat fires every 24 hours over all Telegram threads, but skips any chat that has had activity in the last 8 hours. The weekly-ops cron fires every Monday at 9 AM Pacific and delivers to a specific Slack channel.

Scoped Heartbeat Filters

scope can filter to specific teams or channels:
scope: {
  acme: { teams: ["T123"], channels: ["C123"] },
}

Startup Reconciliation

At startup, heypi reconciles the code-defined jobs for the current agent:
  • Configured jobs are installed or updated in the database.
  • Jobs removed from config are paused (not deleted).
  • Manual CLI pause/resume state is preserved unless the job config explicitly sets state.
Jobs are stored under (agent, id). Two agents can share the same job ID in the same database without conflicting.

Reliability

The scheduler is backed by SQLite with durable locking:
  • Job definitions and run attempts are stored in job_run.
  • Durable locks prevent duplicate execution across processes.
  • Idempotent event IDs are used for each job run target.
  • Delivery is tracked separately from execution.
Target failures are recorded as failed job_run rows. The job cursor still advances after the scheduled attempt, so transient provider delivery failures are visible in history but are not retried automatically.

Suppressing Delivery with [SILENT]

An agent can suppress delivery for a scheduled turn by returning a structured silent response. In prompt-level terms, the built-in Pi adapter maps an exact [SILENT] response to the silent flag:
[SILENT]
When the agent returns this exact string for a scheduled turn, heypi records the run but does not deliver any message to the chat.

CLI Commands

Inspect and manage jobs from the command line:
heypi jobs list              --db ./state/heypi.db [--agent <id>] [--json]
heypi jobs show <id>         --db ./state/heypi.db [--agent <id>] [--json]
heypi jobs run  <id>         --db ./state/heypi.db [--agent <id>]
heypi jobs pause <id>        --db ./state/heypi.db [--agent <id>]
heypi jobs resume <id>       --db ./state/heypi.db [--agent <id>]
Use --agent when a database contains more than one agent, or whenever mutating a job. jobs run marks the job as due immediately; the running heypi process executes it on its next scheduler tick.

Limitations

The scheduler is intentionally simple. The following are out of scope:
  • Workflow DAGs or multi-step pipelines
  • Chat-based job editing or scheduling via the bot
  • Arbitrary pre-run scripts
  • Chat-based target discovery

Build docs developers (and LLMs) love