# Scheduled Execution & Heartbeats

## Overview

EDDI supports **scheduled agent execution** — agents can be triggered automatically on a timer without any user input. This enables proactive agents, background maintenance, periodic data processing, and memory consolidation.

### Use Cases

| Use Case                | Description                                                                                 |
| ----------------------- | ------------------------------------------------------------------------------------------- |
| **Proactive Agents**    | Check for updates, send notifications, or perform monitoring at regular intervals           |
| **Dream Consolidation** | Background memory maintenance — prune stale entries, detect contradictions, summarize facts |
| **Data Pipelines**      | Periodically fetch data from external APIs and process it through the agent pipeline        |
| **Health Checks**       | Run diagnostic agents that verify system health and report anomalies                        |
| **Report Generation**   | Generate daily/weekly summary reports through conversational agents                         |

## Concepts

### Schedule

A **Schedule** defines when and how often an agent fires:

```json
{
  "agentId": "agent-123",
  "agentVersion": 0,
  "triggerType": "CRON",
  "cronExpression": "0 2 * * *",
  "conversationStrategy": "persistent",
  "message": "Run maintenance cycle",
  "userId": "system:scheduler",
  "timeZone": "Europe/Vienna",
  "enabled": true
}
```

### Trigger Types

| Type        | Description                        | Default Strategy | Example                    |
| ----------- | ---------------------------------- | ---------------- | -------------------------- |
| `CRON`      | Wall-clock aligned cron expression | `new`            | `0 2 * * *` (daily at 2am) |
| `HEARTBEAT` | Fixed-interval, drift-proof        | `persistent`     | Every 300 seconds          |

### Conversation Strategies

| Strategy     | Behavior                                                             | Use When                                                 |
| ------------ | -------------------------------------------------------------------- | -------------------------------------------------------- |
| `persistent` | Reuses the same conversation across all fires. Context accumulates.  | Dream consolidation, ongoing monitoring, stateful agents |
| `new`        | Creates a fresh conversation for each fire. Clean context each time. | Report generation, data pipelines, stateless tasks       |

## Configuration

### Creating a Schedule

```bash
curl -X POST http://localhost:7070/schedulestore/schedules \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent-123",
    "agentVersion": 0,
    "triggerType": "CRON",
    "cronExpression": "*/30 * * * *",
    "conversationStrategy": "persistent",
    "message": "heartbeat ping",
    "timeZone": "UTC",
    "enabled": true
  }'
```

### Cron Expression Reference

EDDI uses **standard 5-field cron expressions**:

```
┌────── minute (0-59)
│ ┌──── hour (0-23)
│ │ ┌── day of month (1-31)
│ │ │ ┌ month (1-12)
│ │ │ │ ┌ day of week (0-7, 0=Sun)
│ │ │ │ │
* * * * *
```

**Common patterns:**

| Expression     | Schedule                            |
| -------------- | ----------------------------------- |
| `0 2 * * *`    | Daily at 2:00 AM                    |
| `*/30 * * * *` | Every 30 minutes                    |
| `0 */4 * * *`  | Every 4 hours                       |
| `0 9 * * 1-5`  | Weekdays at 9:00 AM                 |
| `0 0 1 * *`    | First day of each month at midnight |

### Heartbeat Configuration

For heartbeat triggers, use `heartbeatIntervalSeconds` instead of `cronExpression`:

```json
{
  "agentId": "agent-123",
  "triggerType": "HEARTBEAT",
  "heartbeatIntervalSeconds": 300,
  "conversationStrategy": "persistent",
  "message": "heartbeat check",
  "enabled": true
}
```

Heartbeats are **drift-proof** — after a fire completes, the next fire is calculated as `lastFired + interval`, not `now + interval`.

### Schedule Fields

| Field                      | Type    | Default            | Description                                 |
| -------------------------- | ------- | ------------------ | ------------------------------------------- |
| `agentId`                  | string  | required           | Agent to trigger                            |
| `agentVersion`             | int     | `0` (latest)       | Agent version (0 = latest deployed)         |
| `triggerType`              | enum    | `CRON`             | `CRON` or `HEARTBEAT`                       |
| `cronExpression`           | string  | —                  | 5-field cron (for CRON type)                |
| `heartbeatIntervalSeconds` | long    | —                  | Interval in seconds (for HEARTBEAT type)    |
| `conversationStrategy`     | string  | varies             | `new` or `persistent`                       |
| `message`                  | string  | —                  | Message text sent to the agent on each fire |
| `userId`                   | string  | `system:scheduler` | User identity for the fire                  |
| `timeZone`                 | string  | `UTC`              | IANA timezone (e.g., `Europe/Vienna`)       |
| `environment`              | string  | `production`       | Deployment environment                      |
| `enabled`                  | boolean | `true`             | Whether the schedule is active              |
| `maxCostPerFire`           | double  | `-1` (unlimited)   | Dollar ceiling per fire                     |

### Managing Schedules

| Method   | Path                                    | Description                                      |
| -------- | --------------------------------------- | ------------------------------------------------ |
| `POST`   | `/schedulestore/schedules`              | Create a schedule                                |
| `GET`    | `/schedulestore/schedules`              | List all schedules (optional `?agentId=` filter) |
| `GET`    | `/schedulestore/schedules/{id}`         | Get a specific schedule                          |
| `PUT`    | `/schedulestore/schedules/{id}`         | Update a schedule                                |
| `DELETE` | `/schedulestore/schedules/{id}`         | Delete a schedule                                |
| `POST`   | `/schedulestore/schedules/{id}/enable`  | Enable a schedule                                |
| `POST`   | `/schedulestore/schedules/{id}/disable` | Disable a schedule                               |
| `POST`   | `/schedulestore/schedules/{id}/fire`    | Manually trigger a fire immediately              |

### Admin Endpoints

| Method | Path                                    | Description                               |
| ------ | --------------------------------------- | ----------------------------------------- |
| `GET`  | `/schedulestore/schedules/{id}/fires`   | Read fire history (optional `?limit=20`)  |
| `GET`  | `/schedulestore/schedules/admin/failed` | List all failed/dead-lettered fires       |
| `POST` | `/schedulestore/schedules/{id}/retry`   | Re-queue a dead-lettered schedule         |
| `POST` | `/schedulestore/schedules/{id}/dismiss` | Reset dead-letter without immediate retry |

## Dream Consolidation

Dream Consolidation is a specialized schedule that performs **background memory maintenance** on an agent's persistent user memories. It's configured in the agent's `UserMemoryConfig`, not as a standalone schedule.

### What It Does

1. **Stale entry pruning** — Removes outdated facts that are no longer relevant
2. **Contradiction detection** — Identifies conflicting memories (e.g., "user likes coffee" vs "user hates coffee") and logs them for review. Resolution is planned for a future version.
3. **Fact summarization** — Consolidates verbose entries into concise summaries

### Configuration

Dream consolidation is configured in the agent configuration:

```json
{
  "agentConfiguration": {
    "enableMemoryTools": true,
    "userMemoryConfig": {
      "dream": {
        "enabled": true,
        "schedule": "0 3 * * *",
        "detectContradictions": true,
        "contradictionResolution": "keep_newest",
        "pruneStaleAfterDays": 90,
        "summarizeInteractions": true,
        "summarizeMinEntries": 5,
        "summarizeTargetEntries": 2,
        "summarizeGroupBy": "category",
        "preserveAgentProvenance": false,
        "maxSummarizationCalls": 10,
        "llmProvider": "anthropic",
        "llmModel": "claude-sonnet-4-6",
        "maxCostPerRun": 0.50,
        "batchSize": 50,
        "maxUsersPerRun": 1000
      }
    }
  }
}
```

### Cost Control

Dream cycles consume LLM tokens. Use `maxCostPerRun` (in the **Agent Configuration**) to set a dollar ceiling per run:

```json
{
  "agentConfiguration": {
    "userMemoryConfig": {
      "dream": {
        "maxCostPerRun": 0.50
      }
    }
  }
}
```

When the budget is exceeded, the agent stops processing. This prevents runaway costs on large memory stores.

> **Tip:** Use a cheaper model (e.g., `claude-sonnet-4-6` or `gpt-4o-mini`) for dream consolidation — the task doesn't require top-tier reasoning.

## Fire Logging

Every scheduled execution is logged. View fire history via the REST API:

```bash
# List recent fires for a schedule
curl http://localhost:7070/schedulestore/schedules/{scheduleId}/fires?limit=20

# List all failed fires across all schedules
curl http://localhost:7070/schedulestore/schedules/admin/failed?limit=50
```

### State Machine

Each schedule follows a state machine:

```
PENDING → CLAIMED → EXECUTING → COMPLETED
                              → FAILED → (retry) → PENDING
                              → DEAD_LETTERED → (manual retry/dismiss)
```

## Cluster Awareness

The `SchedulePollerService` is cluster-aware — in multi-instance deployments, only one instance executes each scheduled fire. This is achieved via atomic claim operations (`tryClaim`), preventing duplicate execution when running EDDI behind a load balancer.

## Best Practices

1. **Start with longer intervals** — Begin with hourly or daily schedules and increase frequency only if needed
2. **Use `persistent` strategy for stateful work** — Dream consolidation and monitoring agents benefit from accumulated context
3. **Set cost ceilings** — Always configure `maxCostPerFire` or `maxCostPerRun` for LLM-powered scheduled tasks
4. **Monitor fire logs** — Check for recurring failures that might indicate configuration issues
5. **Use cheap models for maintenance** — Background tasks rarely need expensive frontier models

## See Also

* [Managed Agents](/conversations-and-orchestration/managed-agents.md) — Intent-based agent routing
* [User Memory](/architecture-and-concepts/user-memory.md) — Persistent user memory (target of dream consolidation)
* [LLM Configuration](/agent-configuration/langchain.md) — Agent configuration reference
* [Metrics](/deployment-and-infrastructure/metrics.md) — Monitoring scheduled execution performance


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.labs.ai/conversations-and-orchestration/scheduling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
