# Jobs

Jobs are automated agent runs — either run once on demand, or set up to trigger on a schedule or in response to events.

---

## What Are Jobs?

The Jobs app is a single searchable list that mixes two kinds of items:

| Item | Icon | What it is |
|------|------|-----------|
| **Job** | Zap | A one-off agent run you start on demand. |
| **Watch** | Eye | A standing automation that triggers an agent on a schedule (cron) or when an event arrives (push). |

Both are the same underlying primitive — a definition plus a trigger — so everything you learn about runs, filters, and rate limits applies to both. Each row carries a live status dot so you can see at a glance which items are healthy, running, or failing.

Every trigger creates a **run** record you can inspect, so you always have visibility into what ran, when, and whether it succeeded.

![The Jobs app — one list mixing manual Jobs (Zap) and Watches (Eye), each with a status dot; Watches show their cron expression and an enable/disable toggle](images/06-insulin-jobs.png)

## How Jobs Are Created

There is no "Create Job" form in the Jobs app — the list only lets you search, open, and manage existing items. You create a job or watch in one of two ways:

- **Ask the assistant in chat.** Tell the built-in Insulin agent what you want to automate (for example, "every weekday at 9am, summarize new AWS entitlements"). The agent creates the job or watch for you. See [Chat](./2.chat.md).
- **Use the REST API.** `POST /jobs` creates a job or watch programmatically.

If you don't specify an agent when creating a job, it runs under the built-in **Insulin** agent. Otherwise it runs under the [agent](./3.agents.md) you name, with that agent's access to resources and integrations.

:::info
Jobs run within the creating user's scope, so the agent has access to the same resources and integrations that user does. Only an organization **admin** or the **owning user** can run, edit, or delete a job — other members cannot.
:::

## Triggers

Every job has one of exactly three trigger kinds. In the list, manual items appear as **Jobs** (Zap) and the two automatic kinds appear as **Watches** (Eye).

| Trigger | Appears as | How it works |
|---------|-----------|--------------|
| **Manual** | Job | Runs the agent once, when you start it. |
| **Cron** | Watch | Runs the agent on a recurring schedule defined by a cron expression. |
| **Push** | Watch | Listens for events. When one arrives, the watch evaluates its filter and runs the agent if the event passes. |

### Push sources

Push watches subscribe to an event source:

- **Suger events** are the marketplace events Insulin already knows about. When created over REST, the source must match `suger.<entity>.<action>` — for example `suger.OFFER.ACCEPT`. Sources that don't match this pattern are rejected.
- **Third-party events** — GitHub (`github.<event>`), Google Calendar (`calendar.<event>`), and Gmail (`gmail.<event>`) — require a live OAuth connection to that provider and can only be created by asking the assistant, which registers the subscription for you.

:::info
Suger event payloads are scrubbed of personal data (emails, secrets, tokens) before the agent sees them or a run record is stored.
:::

## Cron Scheduling

Cron expressions define when a scheduled watch runs. Standard cron syntax is supported:

| Expression | Schedule |
|------------|----------|
| `0 9 * * 1-5` | Every weekday at 9:00 AM |
| `0 */6 * * *` | Every 6 hours |
| `0 0 1 * *` | First day of every month at midnight |
| `*/30 * * * *` | Every 30 minutes |

A watch's detail view shows its cron expression as a badge and in the header text, along with **Next run** and **Last triggered** timestamps.

## Filter Expressions

Filters let you control which events actually trigger an agent run. When an event arrives on a push watch — or when a cron tick fires with a payload — the filter expression is evaluated against the event payload. If the filter returns false, the run is skipped and recorded with the **filtered** status.

Filters use [filtrex](https://github.com/m93a/filtrex) syntax — a safe, sandboxed expression language. The event payload is available under the `event` namespace.

**Examples:**

```
event.source == "aws-marketplace"
```

```
event.amount > 1000
```

```
event.action == "entitlement_created" and event.partner == "AWS"
```

```
lower(event.status) == "active"
```

**Available functions:** `lower()`, `upper()`, `includes()` (checks if an array contains a value).

**Dot notation** is supported for nested fields: `event.buyer.name`, `event.offer.info.commits`.

:::tip
If no filter expression is set, every event passes and triggers an agent run.
:::

## Runs

Every trigger creates a **run** record. Each run ends in one of these statuses:

| Status | Meaning |
|--------|---------|
| **Filtered** | The event did not pass the filter expression and was skipped. |
| **Rate limited** | The run was skipped because the job or organization hit its rate limit. |
| **Pending** | The run is queued and waiting to start. |
| **Running** | The agent is currently running. |
| **Completed** | The agent finished successfully. |
| **Failed** | The agent encountered an error. |
| **Cancelled** | An in-flight run was cancelled. |

Filtered and rate-limited events still create a run row, so nothing that fired is invisible.

Where you inspect runs depends on the item type:

- On a **Watch**, the detail view has an **Executions** list — each run shows its status badge, any error, and a relative timestamp.
- On a manual **Job**, the detail view shows the latest run inline: the agent's **Plan** steps, the rendered **Result** (as markdown), or the **Error** if it failed.

<!-- screenshot needed: manual Job detail — title + status badge, model badge, timestamps, prompt, Plan steps card, and Result/Error card with Run/Cancel/Delete actions (app-only) -->

<!-- screenshot needed: Watch detail — title + mode badge (Cron/Push) + Active/Off badge, agent + cron expression, Next/Last-triggered timestamps, Source and Filter rows, and the Executions history list (app-only) -->

### Rate limits

To keep automations from overwhelming your workspace, runs are rate limited:

| Limit | Default |
|-------|---------|
| Per job | 60 runs per hour |
| Per organization | 300 runs per hour |

Limits apply over a rolling one-hour window. Runs that exceed a limit are recorded with the **rate limited** status rather than executing.

## Managing Jobs

Open an item from the list to reach its detail view. Available controls depend on the item type.

**Manual Job:**

- **Run** — Start the job. Available only while the job is pending; a job allows at most one in-flight run at a time, so if a run is already active a second Run is rejected with "Job already has an in-flight run."
- **Cancel** — Stop a pending or running job.
- **Delete** — Permanently remove the job and its run history.

**Watch:**

- **Enable / Disable** — Pause a watch without deleting it. Disabled watches do not trigger.
- **Trigger now** — Fire the watch immediately; a run is created as if an event had arrived. (The REST trigger endpoint and the assistant also accept an optional payload to exercise the filter against sample data; the in-app **Trigger now** button fires with an empty payload.)
- **Delete** — Permanently remove the watch (with confirmation) and its run history.

:::info
There is no edit form in the Jobs app. To change a job's name, schedule, filter, or assigned agent, ask the assistant (it uses an `update_job` action) or send a `PATCH` request to the REST API.
:::

## Use Cases

- **Marketplace monitoring** — A cron watch that checks for new entitlements or expiring offers daily and summarizes changes.
- **Automated alerts** — A push watch on Suger marketplace events that runs an agent to evaluate severity and notify your team.
- **Recurring reports** — A cron watch that runs a data analyst agent weekly to generate revenue or usage reports.
- **Co-sell pipeline tracking** — A cron watch that reviews partner referrals on a schedule and flags stale opportunities.
