Studio kinds
plungeai-campaign-agent
Design and emit ONE PlungeAI (Ocean Studio) campaign agent — a long-running, list-driven, resumable agent that works an owned ledger of items in short scheduled runs until the current cycle is exhausted — as CNL YAML (a claim task plus a type: batch with ledger: campaign) and a campaign-config block (list source, cycle, schedule, max_attempts, then, deliver). Use when the Studio Think composer has "Campaign agent" selected, or when a user asks for an agent that works through a list, a backlog, a catalog, "N things to check", "run until the list is done", "start again every week", or "then move to the next list". NOT for single scheduled reports (plungeai-bot-agent), interactive agents (plungeai-agentic-agent) or multi-step pipelines (plungeai-workflows).
Download zip (opens in a new tab) · View raw SKILL.md (opens in a new tab)
Used by Studio: Ocean Studio's agent builder loads this skill by its id.
A campaign is a task list with an owner. Each run claims a slice of the not-yet-done items, works them at bounded concurrency, and records every outcome before it ends; the scheduler re-fires runs until the cycle is exhausted. Runs are short by law — the platform cannot hold one invocation past ~10 minutes — so never design a run that "keeps going". Repetition is the schedule's job, resumption is the ledger's job.
The conversation (before any YAML)
- Unit. What is one item? A product, a lead, a URL, a document. Name the key that identifies it.
- List source. Exactly one: a Task-app table + filter (point-and-pick; never raw SQL), an agent op
that returns a JSON array (discover the id with
registry_search, never from memory), a CSV the user will upload in the Task app after save, or an inline list for a handful of items. - Per-item job. A plain agent op when the item is "call one agent, store one row" (cheapest). A harness
mission only when the item genuinely needs a tool loop; then the same rules as a bot: no
ask_user, noplatform_action,effort: quickunless argued, mission ends with the task_complete sentence. The job MUST upsert by{item.key}+{item.cycle}— say so in the mission or pass both to the op. - Cycle.
once(a backlog),hourly | daily | weekly | monthly(a recurring pass),continuous(restart as soon as exhausted).cycle_start: resetreuses the same keys;refillre-queries the source. - Schedule. When runs may happen — a 5-field cron. Propose a window that finishes the list with margin:
batch_size × seconds per item ÷ concurrency ≤ 600 s, then enough ticks to cover the list. - Deliver. Where the cycle summary and alerts go:
inapp,emailorslack(seedeliverbelow).
Confirm, then emit BOTH blocks.
Output contract — two fenced blocks, in this order
workflow:
name: "<short campaign name>"
description: "<one line: what one item is and how often the list is worked>"
tasks:
- type: task
id: claim
agent: data-table-agent
operation: campaignClaim
campaign_id: "<written by Step-2 at save>"
batch_size: 40
- type: batch
id: work
items_from: claim
concurrency: 5
ledger: campaign
tasks:
- type: task # or type: harness + agent: one-agent when a tool loop is needed
id: one
agent: <agent id>
operation: <op>
key: "{item.key}"
cycle: "{item.cycle}"list:
table: { project: <project>, table: <table>, filter: { <column>: <value> }, key: <column> }
cycle: weekly
cycle_start: refill
schedule: "*/5 9 * * 1"
max_attempts: 2
then: ""
deliver:
- channel: inapp
local: falseRules the lint enforces — violating them makes the save fail:
- Exactly one top-level claim task (
agent: data-table-agent,operation: campaignClaim) and exactly onetype: batchwithitems_from: claim,ledger: campaign, andconcurrency≤ 5. - Inner tasks are unattended: no
ask_user, noplatform_action; a harness inner task MUST includetask_completeinallowed_toolsand end its mission with the task_complete sentence. - The per-item task must reference
{item.key}(warning if it looks insert-only — the ledger is at-least-once). - An inner task that can reach
data-table-agent(e.g.allowed_agents: [data-table-agent]) needsmax_items: amax_items: <n>line in the YAML (on the claim and batch tasks) and the same cap in campaign-config. max_attemptsis required. Nomemory_owner. No emails or ids in the YAML — recipients belong indeliver.local: trueonly with a harness inner task onagent: one-agent, and in BOTH blocks.
campaign-config fields
list— one oftable {project, table, filter, key},agent {id, operation},csv: true,inline: [...].cycle—once | hourly | daily | weekly | monthly | continuous. Calendar cycles never restart inside their bucket.cycle_start—reset | refill. Defaultrefillfor table/agent sources,resetfor csv/inline.schedule— 5-field cron; each tick is one run (an empty claim returns in about a second). Optional: left out, no cron job is made and the ledger schedules its own next run.max_attempts— dead-letter threshold on explicit failures; required.then— optional campaign id to start when a cycle completes. Never chain a dependent report onto a collection campaign; keep reports time-triggered.deliver—{channel, to?}items (defaultinapp). Onlyinapp,email(to= address; bare = the owner) andslack(bare = DM the owner, orto) deliver from a campaign today: the block carries nochat_idorphone_number_id, which telegram, discord, whatsapp and teams need (accepted, then skipped at delivery).batch_size(positive integer, the per-run claim slice, default 40),max_items(positive integer, rows per cycle),pace(seconds ≥ 0) — optional.local— run-on-my-computer, as for bots.
Worked examples for all four use cases plus the full field reference: references/campaign-config.md.
Outside Studio, one call saves the campaign and sets up its ledger and schedule: MCP
(https://mcp.plungeai.com/v1) plungeai_workflow {action: "create", kind: "campaign", yaml, campaign_config: {…}}, or
REST POST https://api.plungeai.com/v1/workflows with kind: campaign and campaign_config (the block's fields as an
object; list, cycle and max_attempts required, 422 invalid_params otherwise); ocean-cli campaigns setup <workflow_id> --config @file arms an existing one (Studio session). Discover the agent ids you name in the per-item
task live — plungeai_list_agents from a client, or registry_search inside the loop — never from memory.