> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simplefunctions.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Heartbeat

> Per-thesis monitoring loop — news scan cadence, social cadence, evaluation model tier, monthly budget, and closed-loop intent behavior.

Heartbeat is the per-thesis monitor loop. It controls how often SimpleFunctions scans news and social context for a thesis, which model tier evaluates new evidence, the monthly cost ceiling, and whether the monitor is allowed to create closed-loop entry or exit intents.

Heartbeat work is independent of thesis lifecycle status. A thesis can be `active` while its heartbeat is `paused`, and pausing the heartbeat never archives or closes the thesis.

## CLI

```bash theme={null}
# show config + this-month cost summary
sf heartbeat <thesis-id>
sf heartbeat <thesis-id> --json

# update fields (any combination)
sf heartbeat <thesis-id> --news-interval 60      # minutes, 15-1440
sf heartbeat <thesis-id> --x-interval 240        # minutes, 60-1440
sf heartbeat <thesis-id> --model base            # cheap | base | medium | heavy
sf heartbeat <thesis-id> --budget 15             # USD per month, 0 = unlimited

# pause / resume
sf heartbeat <thesis-id> --pause
sf heartbeat <thesis-id> --resume

# closed-loop intent creation
sf heartbeat <thesis-id> --closed-loop-entry
sf heartbeat <thesis-id> --no-closed-loop-entry
sf heartbeat <thesis-id> --closed-loop-exit
sf heartbeat <thesis-id> --no-closed-loop-exit
```

## API

```http theme={null}
GET   /api/thesis/{id}/heartbeat
PATCH /api/thesis/{id}/heartbeat
```

Auth: `Authorization: Bearer sf_live_...` (or browser session). 404 if the thesis isn't owned by the caller.

### GET response

```json theme={null}
{
  "thesisId": "thesis_abc123",
  "config": {
    "mode": "active",
    "newsIntervalMin": 240,
    "xIntervalMin": 240,
    "evalModelTier": "cheap",
    "monthlyBudgetUsd": 15,
    "paused": false,
    "smartModel": true,
    "closedLoop": { "entry": false, "exit": false }
  },
  "defaults": { "newsIntervalMin": 240, "xIntervalMin": 240, "evalModelTier": "cheap", "monthlyBudgetUsd": 15, "smartModel": true, "...": "..." },
  "costs": {
    "monthlyTotal": 2.4831,
    "llmCalls": 18,
    "searchCalls": 6,
    "inputTokens": 84210,
    "outputTokens": 11034,
    "budgetRemaining": 12.5169
  }
}
```

### PATCH body

All fields optional. Any combination is accepted; unknown fields are ignored. `400 No valid fields to update` if nothing recognised is sent.

| Field | Type | Default | Range / values | Notes |
| - | - | - | - | - |
| `newsIntervalMin` | number | `240` | 15 – 1440 | Minutes between news/context scans. |
| `xIntervalMin` | number | `240` | 60 – 1440 | Minutes between social/X scans. |
| `evalModelTier` | string | `"cheap"` | `cheap`, `base`, `medium`, `heavy` | Model tier for the evaluation step. |
| `monthlyBudgetUsd` | number | `15` | `>= 0` (`0` = unlimited) | Monthly LLM + search ceiling. Heartbeat auto-pauses when exceeded. |
| `paused` | boolean | `false` | — | Pauses the loop without changing thesis lifecycle. Server tags the pause as `manual` so it does not auto-resume next month. |
| `smartModel` | boolean | `true` | — | Run `cheap` by default, auto-escalate to `medium` on a significant change (confidence delta > 3%, kill condition, or node prob shift > 15%). Forces a `medium` deep eval at least every 24 h. |
| `closedLoop` | boolean \| object | `{ entry: false, exit: false }` | `true` ⇒ both, `false` ⇒ neither, or `{ entry, exit }` | Allow the monitor to create entry / exit intents. Booleans inside the object are individually validated. |

### PATCH response

```json theme={null}
{
  "thesisId": "thesis_abc123",
  "config": { "...": "merged config" },
  "updated": ["newsIntervalMin", "evalModelTier"]
}
```

### Errors

| Status | Body `error` | Cause |
| - | - | - |
| `400` | `newsIntervalMin must be 15-1440` | Out-of-range value. |
| `400` | `xIntervalMin must be 60-1440` | Out-of-range value. |
| `400` | `evalModelTier must be cheap, base, medium, or heavy` | Invalid tier. |
| `400` | `monthlyBudgetUsd must be >= 0` | Negative budget. |
| `400` | `closedLoop must be boolean or { entry, exit }` | Wrong shape. |
| `400` | `closedLoop.entry must be boolean` / `closedLoop.exit must be boolean` | Wrong member type. |
| `400` | `No valid fields to update` | Body had no recognised fields. |
| `401` | `Unauthorized. Provide a valid API key (Bearer sf_live_xxx) or session.` | Missing / invalid auth. |
| `404` | `Thesis not found` | Thesis does not exist or does not belong to the caller. |

### Curl

```bash theme={null}
# read
curl -H "Authorization: Bearer $SF_API_KEY" \
  "https://simplefunctions.dev/api/thesis/thesis_abc123/heartbeat"

# update
curl -X PATCH \
  -H "Authorization: Bearer $SF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"newsIntervalMin": 60, "evalModelTier": "base"}' \
  "https://simplefunctions.dev/api/thesis/thesis_abc123/heartbeat"
```

## What heartbeat is not

* Not an order router. It can write evaluations, signals, alerts, and (when `closedLoop` is enabled) execution intents. Orders still go through the intent / runtime / risk-gate path.
* Not the same as thesis lifecycle status. To archive, close, or publish a thesis, use the [Thesis API](/api-reference/thesis). To stop background evaluation only, set `paused: true` here.
* Not a global setting. Each thesis has its own heartbeat config, and the dashboard / CLI lets you tune them independently.

## Next steps

<CardGroup cols={2}>
  <Card title="Thesis API" href="/api-reference/thesis">
    Full thesis lifecycle HTTP surface.
  </Card>

  <Card title="Portfolio autopilot" href="/guides/portfolio-autopilot">
    Separate portfolio-tick loop and risk gates.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.