# Room (Proactive Mode)

The **Room** section (in the agent's *Automations* group) turns an agent from a *reactive* chatbot into a *proactive worker*. With the room enabled, the agent runs cycles **on its own clock**, reacting to events delivered via inbound webhooks rather than waiting for a human to type a message.

## What the Room does

Every 30 seconds (`TICK_INTERVAL_MS`), the **Room scheduler** wakes up and asks: *"For each room with `enabled = true`, are there any pending events?"* If yes, it spawns one supervisor cycle per room. Inside the cycle, the agent sees:

- The room's prompt, added to the agent's own prompt as its mandate.
- The pending events as the user-message side of the conversation.
- Three harness tools: `send_message_to_human`, `mark_events_completed`, `compact_room_history`.

Each cycle creates a **new conversation** - the conversation id format is `room:<instanceId>:<timestamp>`, never persistent. Rolling state lives in the **activity log** instead (see below).

A human reply on the outbound channel triggers an **immediate** cycle (skipping the 30 s wait) so that the agent is responsive when a human steps in mid-flow.

## Configuration

The Room section has three sub-sections: **Configuration**, **Backlog**, and **Activity Log**. Event sources are **not** a Room sub-section any more - they are their own **Webhooks** section, a sibling of Room under the agent's **Automation** row.

### Configuration

- **Enabled** - master switch.
- **Prompt** - the room's mandate. During a room cycle it is appended to the system prompt as a `## Room Mandate` block, after the agent's seven sections (Identity, Soul, Tooling, Safety, Skills, Memory, User Identity), which all still apply: the mandate adds to the agent's identity rather than replacing it. It sits in the cacheable prefix with the rest of the system prompt. There is no Datetime section - the date and time are injected into the message context, not the prompt. See [Prompt](https://docs.polyant.ai/concepts/prompt#also-in-the-prefix-when-applicable).
- **Outbound channel** - `slack`, `telegram`, `whatsapp`, or `none`.
- **Outbound target** - channel id or user id where `send_message_to_human` will deliver.

The 30-second tick is a **global** scheduler interval (`TICK_INTERVAL_MS`), not a per-room field - it is not configurable from the Room tab.

### Event Sources (now the Webhooks section)

Event sources live in the agent's **Webhooks** section (`?tab=webhooks`), alongside Room in the *Automations* group. The model has not changed; the configuration UI simply relocated. An **event source** is a connection between an external system and the Room. Each event source has:

- **Webhook URL** - auto-generated, formatted as `/webhooks/<webhookToken>`. The token IS the secret - anyone who knows the URL can post events. Treat it as a credential; rotate it from the UI when leaked.
- **Payload size cap** - 64 KB. Oversize payloads receive a `200 OK` with body `{ ok: false, error: "payload too large" }` (the receiver is always fire-and-forget).
- **Backlog cap** - **100 pending events per agent**, counted across every source. Beyond that, **new** events are refused at insert time rather than displacing older ones: the insert is conditional on the count, so nothing already queued is lost.
- **Event Definitions** - the heart of routing. Matching is evaluated by an **LLM** (tier `fast`) against **natural-language prompts** you define per event definition - there are no rules, regex, or JSONPath predicates. Each definition has:
  - **Name**.
  - **`matchingPrompt`** - a natural-language description of when this definition applies (e.g. *"The payload indicates a Stripe invoice has just been paid by a customer"*). Definitions are evaluated one at a time and **the first match wins**; there is no priority to set, so write prompts that do not overlap.
  - **`interpretationPrompt`** - a natural-language instruction on how to summarize the matched payload into the user-message side of the room cycle (e.g. *"Extract the customer email, amount, and invoice id, and phrase it as a single sentence"*).
  - A target room behavior.

  When a webhook fires, the engine sends the payload + each definition's `matchingPrompt` to the matcher until one matches, then uses the winning definition's `interpretationPrompt` to render the event for the room cycle.

### Backlog and Activity Log

- **Backlog** - pending events waiting to be processed. Status filter: `pending`, `processing`, `completed`. Each row shows the start of the payload, its status and when it was received, under a search box; clicking a row opens the full payload in a side drawer.
- **Activity log** - a chronological log of every room cycle. Old entries are compacted automatically:
  - Daily entries older than 7 days collapse into a weekly summary.
  - Weekly entries older than 28 days collapse into a monthly summary.
  - Monthly entries are kept for 12 months, then dropped.

Compaction keeps the log readable and the database lean.

#### Storage retention

The room activity log is auto-compacted on a fixed schedule: **daily** rows for the last 7 days, then **weekly** rows after 7d, then **monthly** rows after 4 weeks. The compaction logic lives in `packages/engine/src/room/activity-log.store.ts:49-70` and runs as part of the scheduler's housekeeping pass - no manual maintenance is required.

## Worked example: Stripe invoice paid

Suppose you want the agent to send a Slack DM whenever a customer pays an invoice.

1. **Create an instance** with the right identity for the use case.
2. **On the Room tab → Configuration**, set:
   - Enabled: yes
   - Prompt: *"You watch Stripe invoice events. When an invoice is paid, send a short congratulatory DM to the account owner on Slack."*
   - Outbound channel: `slack`
   - Outbound target: the Slack channel id `C012345678`
3. **On the Channels tab**, configure Slack and start the channel.
4. **In Automation → Webhooks**, click **Add** and create an event source:
   - Source Name: `Stripe`
   - Open it and, under **Event Definitions**, click **Add**:
     - Definition Name: `Invoice paid`
     - Matching Prompt: *"A Stripe invoice has been paid. The payload includes `event.type = 'invoice.paid'`."*
5. **In Stripe**, point a webhook at `https://<your-host>/webhooks/<webhookToken>`. Pick the `invoice.paid` event type only (or all if you do not mind extra traffic).
6. **Pay a test invoice.** Within ~30 seconds you should see a Slack DM from the bot, and a new entry in the activity log.

For the inbound HTTP contract (size limits, signatures, response codes) see [Webhooks (Inbound)](https://docs.polyant.ai/reference/webhooks).

## The Automations group

There is no "Triggers" section. The agent's sidebar has an *Automations* group whose rows are **Hooks**, **Webhooks**, **Scheduled** and **Room**, each its own section with its own `?tab=` address and each shown to whoever can read it. The three sections this page is about:

- **Webhooks** - the event sources above (full CRUD: create / edit / delete / event definitions). Needs `agent.room:read`.
- **Scheduled** - recurring or one-shot tasks, also created by `scheduleTask`. Each row: cron expression, next run, status. Needs `agent.task:read`. See [Scheduled tasks](https://docs.polyant.ai/admin-panel/scheduled-tasks).
- **Room** - this page.

Execution history is not here: scheduled runs and webhook conversations are in the agent's **Monitor** section, under the *Overview* group. A bookmark to `?tab=triggers` names nothing and lands on Status.
