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

# plungeai-bot-agent

> Design and emit ONE PlungeAI (Ocean Studio) bot agent — a scheduled, unattended `type: harness` mission that reports to the user's channels (in-app, email, Slack, Telegram, WhatsApp, Discord, Teams) — as CNL YAML plus a bot-config block (cron schedule, delivery targets, optional run-on-my-computer).


Design and emit ONE PlungeAI (Ocean Studio) bot agent — a scheduled, unattended `type: harness` mission that reports to the user's channels (in-app, email, Slack, Telegram, WhatsApp, Discord, Teams) — as CNL YAML plus a bot-config block (cron schedule, delivery targets, optional run-on-my-computer). Use when the Studio Think composer has "Bot agent" selected, or when a user asks for a bot, a scheduled agent, a daily/weekly brief, a watcher, a monitor, a digest, or "something that runs on its own and tells me". Triggers: "bot", "every morning", "schedule", "report to Slack", "watch and alert", "digest". NOT for interactive agents (plungeai-agentic-agent) or multi-task pipelines (plungeai-workflows).

[Download zip](https://skills.plungeai.com/plungeai-bot-agent.zip) · [View raw SKILL.md](https://skills.plungeai.com/plungeai-bot-agent/SKILL.md)

<Note>
Used by Studio: Ocean Studio's agent builder loads this skill by its id.
</Note>

A bot is an agentic agent plus three facts: WHEN it runs, WHERE the result goes, and whether its
hands are the user's own computer. Studio saves it under "Bots" and creates the schedule and delivery
from your bot-config block. Unattended runs cannot ask anything: no `ask_user`, no `platform_action`,
every risky tool excluded or `permissions: deny`.

## The conversation (before any YAML)

1. **Job.** One sentence: what each run produces (a brief, an alert, a digest).
2. **Cadence.** Ask for the schedule in words, propose the cron, confirm it (five fields); set
   `timezone` (IANA, e.g. `Europe/Berlin`) in the block to the user's zone, never convert to UTC.
3. **Where it reports.** `inapp` (Studio inbox, always safe), `email`, `slack`, `telegram`,
   `whatsapp`, `discord` (these three need a paired chat: tell the user to pair first), `teams`
   (needs the owner's Microsoft connection). Default `inapp`.
4. **Hands.** Cloud (default) or "runs on this computer" (`local: true`): local files, local apps,
   local agents. Both run on `agent: one-agent`.
5. **Fence, money, spend.** Smallest `allowed_tools`; ids only from AVAILABLE CAPABILITIES and
   live `registry_search`; `effort: standard` unless argued; 2–4 `success_criteria`. Ask what
   the bot may never do alone (pay, send, delete) and what a run may cost.

Confirm, then emit BOTH blocks.

## Output contract — two fenced blocks, in this order

```yaml
workflow:
  name: "<short bot name>"
  description: "<one line: what it reports and how often>"
  tasks:
    - type: harness
      id: bot
      agent: one-agent
      goal: "{input}"
      mission: |
        You are <name>, a bot that <job>. Each run you <steps in 2–4 sentences>.
        Output <format: bullets / table / one paragraph>. Keep it under <n> words.
        End with `[SILENT]` (or make your whole result `[SILENT]`) when nothing is worth
        reporting this run — the platform then delivers to no channel.
        When your task is finished, call the task_complete tool with your final result.
      effort: standard
      allowed_tools: [web_search, web_fetch, task_complete]
      success_criteria:
        - <statement the bot verifies before finishing>
```

```bot-config
schedule: "0 7 * * 1-5"
deliver:
  - channel: inapp
  - channel: slack
    chat_id: C0123ABCD
local: false
prompt: "<the per-run input the schedule sends as {input}>"
```

Rules the save lint enforces — violating them fails the save:
- ONE task, `type: harness`; a present `allowed_tools` MUST include `task_complete`; no `memory_owner`.
- `posture`, `peers`, `review`, `max_runtime_s` and `workspace` apply only on `agent: one-agent`;
  on `harness-agent`, `agentic-agent` or `universal-agent` they fail the save, not ignored.
- A `local: true` bot must name `agent: one-agent`; put `local: true` in BOTH blocks (task and bot-config,
  so the scheduler skips cleanly when the machine is offline) and add `local_agents: [ids]` only when the
  user names them.

Also expected: `agent: one-agent`, `goal: "{input}"`, a mission ending with the task_complete sentence, no
emails or ids in the YAML (recipients go in `deliver`), no `ask_user` / `platform_action` (nobody answers).
`skill_manage` needs `permissions: {skill_manage: allow}` unattended: omit it unless asked.

## bot-config fields

- `schedule` — 5-field cron, required (`0 7 * * 1-5` weekdays 07:00, `*/30 * * * *`). Optional
  `timezone` (IANA).
- `deliver` — list of `{channel, chat_id?, to?, phone_number_id?}`: `inapp` (none), `email`
  (`to` optional = owner), `slack` (`chat_id` C… or `to` U…; none = DM the owner),
  `telegram`/`discord` (`chat_id`), `whatsapp` (`to` + `phone_number_id`), `teams` (`chat_id` =
  the Graph chat id; posts as the owner through their Microsoft connection; text capped at 4000
  characters).
- `local`, `prompt` (the per-run input; default empty).
- `budget_usd_run`, `budget_usd_month` — USD caps above 0, at most 100000; `0` is refused, omit the key for no cap.
- `permissions` — class presets `{send|write|pay|delete: allow|ask|deny|hand_off}` stored on the bot.
- `peers`, `posture`, `review`, `max_runtime_s`, `workspace`, `max_turns`, `skills`, `persona`,
  `allowed_tools` — the mission keys below, written onto the task. `memory_consolidate: weekly` —
  opt-in weekly memory consolidation.

## Mission keys (on the harness task, snake_case)

| Key | Value | Effect |
|---|---|---|
| `posture` | `read_only` | denies write, send, delete, pay, `delegate`, `invoke_workflow`, every MCP tool |
| `peers` | `[bot name \| id \| "a2a:<url>"]` | the only bots `message_bot` may reach; none by default |
| `review` | `auto` | a model reviews each allowed write/send/pay/delete call (one extra call each) |
| `max_runtime_s` | seconds | wall-clock cap; the run stops with `timeout` |
| `workspace` | `bot` \| `run` | `bot` (default for a saved bot) keeps files across runs; `run` starts clean |
| `budget_usd_run` | USD | stops the loop at the next turn once cost passes it |
| `permissions` | `{class or tool: allow\|ask\|deny\|hand_off}` | see Permissions |
| `allowed_tools` | list | the fence; opt-in tools below |
| `skills`, `persona` | ids | capability injection |
| `max_turns` | number | turn cap (`effort` sets a default) |

An off-contract `posture` or `review` is forced to `read_only` / `auto`: it never widens a run.

## Permissions and budgets

Classes: `send`, `write`, `pay`, `delete` (reads are always allowed). Presets: `allow`, `ask`
(the run pauses `needs_approval`, up to 7 days), `deny`, and **`hand_off`**: the call never
runs and does not retry; the result ends with `[handed off to you — not run: …]` so the owner
does it themselves (a leading `[SILENT]` is dropped). Use `hand_off` for what a bot should
prepare but never do (`pay: hand_off`). A call resolves: read-only posture, then `deny`, then
`hand_off`, then the money floor (`buy, pay, transfer, send, withdraw, …` always `ask`, even
with `pay: allow`), then the tool-name preset, then the class preset, then allow. An unknown preset
is `ask`. A team admin can LOCK a class: its `allow` rises to `ask` (never lifts a
deny). Locks live on the saved bot, not in YAML. Three denied calls in a row stop an unattended
run (`stop_reason: denied`). `hand_off` is `one-agent` only (other runtimes get `deny`); the task's `permissions:`,
bot-config `permissions` and the permissions verbs all accept it.
Safe default: `permissions: { pay: ask, delete: ask }`.

Budgets: `budget_usd_run` caps one run; `budget_usd_month` (bot-config, Studio, or the verbs
below) is enforced by the scheduler. The lower of the YAML and the stored per-run cap applies,
and a capped run's result says so. Blank or omitted = no cap; the block and Settings refuse `0`, and the verbs
clear a cap with `null` / `none` — never send `0` (the runtime ignores it).

## Always-on abilities (opt-in)

Add to `allowed_tools` only what the job needs; each needs a saved bot.
- `schedule` — `action: create | list | pause | resume | delete | run_now | wake`. `create {name,
  cron, timezone?, prompt?, deliver?, ends_at?}`; one run at a clock time is cron `@once` with
  `run_at` (ISO-8601 with offset); `wake {in, note}` re-runs the bot once (min 60 s). The bot
  writes the cron and says it back in words; 50 live jobs per bot.
- `message_bot` — `{to, text, wait?}`; `to` is in `peers`, or `group:<name>` (the sender must be a
  member of that group; `wait: true` is refused for a group). Replies arrive as
  `[MESSAGE from <bot>]` at the start of a later run; `wait: true` runs the peer now as a plain
  child (no memory, inbox, workspace). A **bot group** (2–6 of the owner's bots, made in Studio
  or via the verbs) fans a message out: membership is the fence, every other member's inbox
  gets it, the next member wakes, 8 hops max.
- `browser` — `{steps, url?, session?: 'bot' | 'none'}`; `'bot'` keeps cookies. Click, type and key
  steps count as writes.
- `memory` — `action: forget {query}` purges matching entries.
- `code` — `{prompt, directory?, session_id?, timeout_s?}` hands one turn (max 300 s) to the
  owner's OceanCode over the Local Tunnel. Class `write`; no daemon connected, no tool.
- `mcp_tool_search` — only with MCP servers attached: past 15 tools or 8 KB, schemas are listed by
  name and a search loads up to 5 for the next turn (never ones the permissions refuse).

Shared bots: inbox, `schedule`, `message_bot`, the browser session and workspace are owner-only;
a teammate runs a shared bot without identity.

## Channels in

A chat can be bound to a bot (Telegram, WhatsApp, Discord, Teams): `/bot <name>` in the chat
(Slack: `/plunge bind`), or the verbs below; pair first (Teams two-way chat also needs
the platform's Teams app registration). Voice notes (Telegram and WhatsApp only, up to 25 MB)
from an authorised sender are transcribed with Whisper and answered like text (as audio where
the channel can); Discord and Teams are text only.

## Outside Studio — the same bot over REST, MCP and CLI

REST `https://api.plungeai.com` (One API 2.3.0, live contract `GET /v1/openapi.json`), MCP
`https://mcp.plungeai.com/v1`, CLI `ocean-cli`.

| Job | REST | MCP | CLI |
|---|---|---|---|
| policy, locks, caps | `GET`/`PUT /v1/workflows/{id}/permissions` | `plungeai_workflow` `permissions_get` / `permissions_set` | `workflows permissions get\|set --class pay=ask --lock pay --budget-run 0.5 --budget-month 20` |
| workspace files | `GET …/workspace-files`, `…/download?path=` | `files` / `file` | `workflows files <id> [--download <path> --out <file>]` |
| inbox | `GET …/inbox` | `inbox` | `workflows inbox <id>` |
| chat bindings | `GET`/`POST …/bindings`, `DELETE …/bindings/{channel}/{sender_id}`, `POST /v1/channels/pair` | `bindings` / `bind` / `unbind` | `workflows bindings list\|bind --channel telegram --sender <id>\|unbind --channel --sender --yes` |
| groups | `/v1/bot-groups`, `/{id}`, `/{id}/thread` | `groups` / `group_set` / `group_delete` / `group_thread` | `bot-groups list\|create --name --members a,b\|update\|delete\|thread <id>` |
| steer or list a run's files | `POST /v1/executions/{id}/steer`, `GET …/files` | `plungeai_executions` `steer` / `files` | `executions steer <id> <text>`, `executions files <id>` |
| schedule once, wake | `POST /v1/schedules` (`@once` + `run_at`), `…/{id}/wake` | `plungeai_schedule` `run_at`, `wake` | `schedules create NAME --type workflow --target <id> --cron @once --at <iso>`, `schedules wake <job_id> --note` |

A saved bot runs under its stored policy on every door, including the Agent Call door:
`POST /v1/execute {"agent": "<bot id>@3", "input": "…"}`.

## The fence for unattended runs

Same 24 loop tools as an agentic agent (`plungeai-agentic-agent`) plus the always-on ones above. Leave out
`ask_user` and `platform_action`; prefer read paths (`web_search web_fetch read_file recall_history knowledge`)
plus the agents the job needs via `call_agent` with a tight `allowed_agents`; anything that sends or pays
needs the user's explicit yes in the conversation AND stays `deny`/`hand_off`/`ask`.
Delivery is NOT a tool: the platform sends `task_complete`'s result to the `deliver` targets
([`references/bot-config.md`](/skills/plungeai-bot-agent/references/bot-config): limits, pairing). Discover agent ids live (`plungeai_list_agents`,
`registry_search`), never from memory.

## Reference pages

<CardGroup cols={2}>
<Card title="bot-config → scheduler job" icon="file-text" href="/skills/plungeai-bot-agent/references/bot-config">
The block becomes one scheduled job that delivers taskcomplete's result to every target.
</Card>
</CardGroup>
