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

# Missions — bounded autonomous agent runs (type: harness)

> A mission bounds what an agent may do for ONE goal: a standing purpose, a fenced tool set, an iteration cap, optional identity/knowledge injection, and self-checked success criteria.


<!-- sources-of-truth: orchestration/cnl-engine/harness-mission.ts, orchestration/cnl-engine/schema-types.ts (InlineMission), orchestration/HARNESS-README.md, orchestration/BOT-CREATION.md (memory_owner policy), orchestration/mcp-gateway/server.ts, orchestration/mcp-gateway/tools.ts | last-synced: 2026-09-24 -->
A **mission** bounds what an agent may do for ONE goal: a standing purpose, a fenced
tool set, an iteration cap, optional identity/knowledge injection, and self-checked
success criteria. You give a `goal`; the platform runs a real ReAct tool loop
(think → tool call → result → …) against it, hard-stopped at every bound. This is
the capability to reach for when the steps are NOT known in advance — one harness
task, not a chain of guessed small tasks.

Missions are humans-and-workflows-in-control by construction: the agent executes
*inside* the bounds; it cannot widen them.

## Minimal and typical

```yaml
name: Claim verification
tasks:
  - type: harness
    goal: "Verify the claims in {input} and produce a sourced verdict"
    mission: |
      You are a careful researcher. Verify claims against primary sources.
      Refuse to conclude beyond the evidence.
    skills: [research]
    effort: standard
    allowed_tools: [web_search, web_fetch, task_complete]
    success_criteria:
      - Every verdict cites at least one primary source
```

Or from an agent context, without YAML — but note the MCP tool accepts only a
**subset** of the mission contract:

```
plungeai_run_mission {goal, mission?, allowed_tools?, max_iterations? (≤50),
                      success_criteria?, persona?, skills?, mode?: "sync"|"async"}
```

Those are ALL the fields. Anything beyond them — `experts`, `backgrounds`,
`plugins`, `mcp`, `model`/`provider`, `effort`, `max_parallel`, `permissions`,
`memory_owner`, `local` — requires authoring a one-task `type: harness` workflow
(the full table below) and running it via `plungeai_execute_workflow`.

Defaults to async — poll `plungeai_get_workflow_status`, fetch with
`plungeai_get_result`. The run recalls and writes long-term memory automatically
(see `plungeai-memory`). If status reports `⏸ AWAITING USER APPROVAL` or
`AWAITING USER`, relay it and continue with `plungeai_continue` after the user
decides.

## The authoring contract — flat peers, one internal shape

Capability fields are written FLAT on the task (the engine folds them into the
mission internally). The nested `mission: { … }` object form also works; flat wins
per key over nested.

### Full field reference

| Field | Type | What it bounds |
|---|---|---|
| `goal` | string | The per-run input — becomes the agent's prompt |
| `mission` | multi-line string | Standing purpose → system-prompt frame. A SINGLE-TOKEN string is a card reference instead (below) |
| `persona` | string | One voice per run (`digital_twin` alias; `personas: [x]` → first entry) |
| `skills` | string[] | Instruction packs — first 5 eager, rest on demand (`plungeai-skills-plugins`) |
| `experts` | string[] | Domain lenses — first 3 eager (`plungeai-skills-plugins`) |
| `backgrounds` | string[] | Ambient context cards — first 3 eager, injected first |
| `plugins` | string[] | Bundles: skill index + MCP servers + scripts (`plungeai-skills-plugins`) |
| `mcp` | string[] | MCP server ids connected in-loop |
| `model`, `provider` | string | Model/provider override (`plungeai-models`) |
| `effort` | `quick`\|`standard`\|`deep` | Turn-budget preset: **4 / 12 / 20** turns |
| `max_turns` | number | Explicit loop cap (alias `max_iterations`); wins over `effort`; runtime default 50 |
| `max_tokens` | number | Per-turn OUTPUT token budget for the loop's model calls (runtime default 16384) |
| `budget_usd_run` | number | Per-run spend cap in USD — the loop prices its running token cost each turn and stops (`stopReason: "budget"`) once exceeded; absent = no cap |
| `max_parallel` | number | Widest fan-out one `delegate` call may spawn — cap-and-refuse, never silently batched |
| `allowed_tools` | string[] | The tool fence (below) |
| `allowed_agents` | string[] \| `'all'` | Agent fence for `call_agent` |
| `denied_agents` | string[] | Subtracted from the agent fence |
| `permissions` | map tool → `allow`\|`ask`\|`deny` | Per-tool human gate: `ask` pauses the run with `needs_approval` before the tool executes; `deny` rejects the call. Keys may be tool names or permission classes (`send`\|`write`\|`pay`\|`delete`) |
| `permission_locks` | string[] | Admin-locked permission classes: a class listed here forces its `allow` up to `ask`, so a bot author's own `allow` cannot silently auto-run it. Sourced from the DB at the Studio execute boundary, not trusted from raw YAML for enforcement — carried through, not authored |
| `success_criteria` | string[] | Self-checked statements the agent verifies before finishing |
| `instructions` | string | Extra author instructions, appended after skills — always lands |
| `python_executor` | `auto`\|`pyodide`\|`anthropic`\|`gemini` | `run_python` tier routing |
| `memory_owner` | string | Memory namespace override — **do not use; omit entirely** (policy since 2026-08-23). Identity does not belong in mission YAML: ownership lives on the workflow row and runtime identity is injected per run; a hardcoded id that isn't the runner's own is rejected by the harness guard anyway. See `plungeai-memory` and `orchestration/BOT-CREATION.md` |
| `local`, `local_agents` | bool, string[] | Local bot: actions route to the user's own machine via the desktop daemon (cloud brain, local hands); requires a connected local node |

## The tool fence

`allowed_tools` is a hard whitelist — only listed tools ever reach the LLM; an
out-of-fence call is refused by the runtime, not merely discouraged. Omit it and the
loop runtime applies its own **fail-closed default fence**.

The full loop-tool catalog (fence vocabulary): `read_file`, `write_file`,
`edit_file`, `list_files`, `search_files`, `delete_file`, `web_search`, `web_fetch`,
`load_skill`, `memory`, `knowledge`, `skill_manage`, `recall`, `recall_history`,
`delegate`, `local_agent`, `registry_search`, `registry_lookup`, `call_agent`,
`invoke_workflow`, `run_python`, `ask_user`, `platform_action`, `task_complete`.

Fence design rules:

- **Always include `task_complete`** — it is how a run ends cleanly.
- Smallest set that can achieve the goal. A verification mission needs
  `web_search, web_fetch, task_complete` — not the file tools.
- `ask_user` keeps human-question continuations available on every surface; include
  it when the goal may need clarification mid-run.
- Two loop runtimes exist: the default full-surface runtime (all 24 tools; in
  unattended runs — the engine/bot default — `platform_action` is stripped from that
  *default* fence, which is why a scheduled mission can behave differently from the
  same mission run interactively) and `universal-agent` (thin default fence: the
  registry triad `registry_search` + `registry_lookup` + `call_agent`, plus
  research + `load_skill` + `ask_user` + `task_complete` — an unfenced
  universal-agent mission CAN call registry agents) — select with `agent:` on the
  harness task. An explicit `allowed_tools` always wins over either default.

## The agent fence and money-class protection

`call_agent` lets the loop invoke registry agents. `allowed_agents: [a, b]` limits it
to those; `'all'` opens the catalog minus `denied_agents`. **Payment/blockchain-class
agents stay deny-unless-explicitly-named even under `'all'`** — naming them is the
only way a mission can touch money, and `permissions: {call_agent: ask}` adds a human
gate on top. This is a trust fence: surface refusals, never work around them.

## Recursion and fan-out guards

- `delegate` spawns parallel child agents; children get a minimal default tool set
  and ALWAYS have `delegate`, `invoke_workflow`, and `memory` stripped — no fan-out
  explosions, no child memory writes.
- Orchestrator depth is propagated (`depth` → children send `depth+1`) and
  hard-capped (default max depth 3); a too-deep dispatch is refused.
- `max_parallel` caps one delegate call's width by refusal, never by serializing —
  silent batching would multiply wall clock.

## Pre-built agent cards (`mission_ref`)

A stored agent card (markdown frontmatter + body, compiled by the platform) packages
a whole mission for reuse:

```yaml
- type: harness
  goal: "{input}"
  mission_ref: research-analyst    # canonical; `pack:` is an alias
# equivalent shorthand: mission: research-analyst   (single token = card id)
```

A card with no explicit `agent:` hosts on `universal-agent` — predict its default
fence (the thin one above) accordingly.

**Merge order, per key, last wins: card → workflow root → task.** Two rules that
bite:

- **Arrays REPLACE, never union.** Task-level `skills: [x]` replaces the card's
  skill list entirely; an explicit `skills: []` deliberately clears it.
- A blank inline `mission` text does not override the card's purpose; any non-blank
  inline text does.

Cards are also directly schedulable: `plungeai_schedule {action: "create",
mission_ref: "…", schedule: "0 7 * * *", …}` (`plungeai-scheduling`).

## Memory lifecycle

At run start the engine recalls the owner's long-term memory as a **frozen
snapshot** prepended to the system prompt; the loop's `memory` tool writes back
durably (writes do not mutate the running prompt). Bots do **not** set
`memory_owner` — policy since 2026-08-23 is to omit it entirely; a bot run reads
and writes the owner's general memory namespace, shared across all their bots and
runs (a hardcoded per-bot value is rejected by the harness guard unless it names
the runner's own identity, so it is redundant at best). Scheduled user runs keep
the full memory lifecycle; only system/heartbeat jobs run memory-free. Details,
limits, threat-scanning, and the bot-memory policy: `plungeai-memory`.

## Execution semantics worth engraving

- **No per-task `retry` on harness.** Mission runs are not idempotent — the engine
  never re-fires them automatically, and neither should you without checking what
  the first run already did (`plungeai_executions`).
- `success_criteria` are self-checked by the agent (no external verifier in the
  loop) — write them as verifiable statements, and treat them as guidance-grade, not
  proof.
- Runs pause (never die) on `ask_user` questions and `permissions: ask` gates —
  status carries a `continuation` block; resume with `plungeai_continue` (`message`
  for answers, `approve: true` only after an explicit human yes).
- Budget honestly: `quick` (4 turns) suits a lookup-and-answer; `deep` (20) a real
  investigation; explicit `max_turns` for anything unusual. A run that hits its cap
  ends with whatever it has — better a tight cap and a follow-up mission than an
  unbounded loop.

## Mission vs workflow — the decision

| Signal | Choose |
|---|---|
| Steps enumerable in advance, repeatable | Workflow of plain tasks (`plungeai-workflows`) |
| Open-ended goal, tool choice needs judgment | ONE harness mission |
| Both: fixed pipeline with one judgment-heavy stage | Workflow with a single embedded `type: harness` task |

The classic failure is decomposing an open-ended goal into six guessed tasks — the
guesses are wrong and the pipeline is brittle. Give the goal to one fenced mission
instead.

---

## MCP tool: `plungeai_run_mission`

**Purpose:** run a bounded autonomous agent mission — a loop agent with a
goal, a tool fence, and an iteration cap. It recalls and writes the user's
long-term memory automatically. **Async by default** (missions are long);
pass `mode: "sync"` only for short, tightly-bounded goals.

**Parameters**

| Param | Type | Notes |
|---|---|---|
| `goal` | string ≤65536, required | What to achieve. |
| `mission` | string | The mission brief / operating instructions (default: a capable general agent that cites sources). |
| `allowed_tools` | string[] ≤50 | The tool fence (default `web_search`, `web_fetch`, `task_complete`). The agent cannot use tools outside it. |
| `max_iterations` | int 1–50 | Loop cap (default 8). |
| `success_criteria` | string[] ≤20 | Explicit done-conditions. |
| `persona` | string | Persona id to inject. |
| `skills` | string[] ≤20 | Skill ids to inject. |
| `mode` | `sync` \| `async` | Default async. |

**Example**

```json
{"user_request": "research the EU AI Act's impact on medical devices",
 "goal": "Produce a sourced brief on how the EU AI Act affects medical-device software vendors",
 "max_iterations": 12,
 "success_criteria": ["cites primary sources", "covers timelines and penalties"]}
```

**Returns (async):** "Started mission `<id>` (async). Poll
plungeai_get_workflow_status with this id, then fetch output with
plungeai_get_result once completed. The agent recalls and writes long-term
memory automatically. If the status reports ⏸ AWAITING USER APPROVAL or
AWAITING USER, relay it to the user and continue with plungeai_continue." —
follow that script. Sync mode returns the finished result (or a ⏸ pause)
directly.

**Failures & fixes:** timeouts in sync mode → go async (the remediation says
so). A mission's own tool failures are the agent's to work around inside its
loop; the envelope classifies anything terminal. Note: async runs emit no
progress notifications — poll for status instead of waiting for a stream.
