> ## 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.

# Memory — run data (SharedMemory) vs long-term memory. Two systems, never confuse them

> PlungeAI has TWO memory systems with different jobs:


<!-- sources-of-truth: core/core-memory/core-memory.ts, CLAUDE.md (SharedMemory usage), orchestration/BOT-CREATION.md (memory_owner policy), orchestration/mcp-gateway/server.ts, orchestration/mcp-gateway/tools.ts, orchestration/mcp-gateway/extras.ts, orchestration/api-gateway/openapi.ts, orchestration/cnl-engine/schema-types.ts | last-synced: 2026-09-24 -->
PlungeAI has TWO memory systems with different jobs:

| | **SharedMemory** | **Long-term memory** |
|---|---|---|
| Holds | Every task's output within/after a run | What the agent LEARNED about the user and its work |
| Keyed by | `(workflow_id, task_id, user)` | Per user (or per bot namespace) |
| Lifetime | Per execution, durable for retrieval | Standing, across all runs |
| You read it via | `plungeai_get_result`, `/v1/*/results/…` | Automatic recall in runs; `plungeai_memory` |
| You write it via | Never directly — agents store results | `plungeai_memory remember`; the agent's own `memory` tool |

"Fetch what that run produced" is SharedMemory. "Remember that the user prefers
weekly summaries" is long-term memory.

## SharedMemory — the run-data fabric

Every task result — including single agent calls — is stored under
`(workflow_id, task_id)` scoped to your user. That is why every execution
acknowledgement returns those ids: they are redemption tickets.

```
# MCP — final result (full conversation thread for conversational runs)
plungeai_get_result {workflow_id: "exec-…"}
# One step of a fan-out
plungeai_get_result {workflow_id: "exec-…", task_id: "web"}
```

```bash
# One API
curl -s https://api.plungeai.com/v1/workflows/results/{workflowId}/{taskId} \
  -H "Authorization: Bearer ozk_YOUR_KEY"
curl -s https://api.plungeai.com/v1/agents/results/{workflowId}/{taskId} \
  -H "Authorization: Bearer ozk_YOUR_KEY"
# 200 {content, content_type, workflow_id, task_id} | 404 not_ready
```

Facts to rely on:

- `404 not_ready` = still running (or a beat behind) — poll; not an error.
- Results are scoped to the executing user — you cannot redeem another user's ids.
- The final result of a run lists its steps (MCP renders them); open any step in
  full with its `task_id`. Use this to inspect ONE branch of a parallel block
  instead of re-running.
- Within a workflow, data handoff between tasks IS SharedMemory — automatic; never
  hand-wire results between tasks in YAML.
- A scheduler run id is not a SharedMemory id: go through
  `plungeai_schedule {action: "runs"}` and use its Execution ID column
  (`plungeai-scheduling`).

## Long-term memory — the agent's durable brain

Per user, three layers of plain markdown:

| Layer | File | What it holds |
|---|---|---|
| L3 | `USER.md` | The agent's learned model of the user (preferences, context) |
| L2 | `MEMORY.md` | Curated agent notes shared across this user's missions |
| L1 | `episodes.md` | Append-only run log: goal + self-reported outcome per mission |

### Recall — a frozen snapshot per run

At mission start the platform builds ONE snapshot of the **curated stores only —
MEMORY.md + USER.md. `episodes.md` is never injected** (task outcomes go stale; pull
run history explicitly with `search_runs` or the mission's `recall` tool). The
snapshot is prepended to the agent's system prompt. Mid-run writes are durable but
do **not** mutate the running prompt (prompt-prefix stays cache-stable). So: what a
mission "knows" is fixed at launch; what it learns benefits the NEXT run. Two
missions launched together do not see each other's writes.

### Writing

- **In-loop:** the agent's `memory` tool (missions write learnings as they work;
  episodes append automatically at run end).
- **From outside:** `plungeai_memory`:

```
plungeai_memory {action: "recall"}                       # read the durable store
plungeai_memory {action: "remember", target: "user",     # user | memory
                 operation: "add",                       # add | replace | remove | read
                 content: "Prefers weekly summaries, no emojis in reports"}
plungeai_memory {action: "search_runs", query: "competitor analysis"}
plungeai_memory {action: "get_run", run_id: "…"}
```

Use `remember` whenever the user says "remember this". Note the split: `recall`
reads the curated stores; the **run journal** (`search_runs`/`get_run`) is separate
and deliberately NOT part of recall — history is queried, not ambient.

### Budgets — memory is curated, not a dump

The stores are small on purpose (defaults: MEMORY ~2200 chars, USER ~1375;
configurable per user, clamped to 500–20000). Writes beyond the limit force curation —
replace/remove stale entries rather than appending forever. Write memory like an
engineer writes a runbook: few, load-bearing, current facts. Ten vague notes crowd
out the one that matters.

### Safety

Every entry is threat-scanned twice: at write (a prompt-injection-looking entry is
**rejected**) and at snapshot build (a poisoned stored entry is replaced with a
`[BLOCKED]` placeholder; the raw text stays inspectable by the user). Do not try to
store instructions-to-future-agents phrased as commands — that is exactly the
pattern the scanner exists to stop. Store facts.

## Namespaces and special runs

- **Bots:** policy since 2026-08-23 is to **omit `memory_owner` entirely** — the
  first 20 bots were authored with a hardcoded
  `memory_owner: "<ownerUserId>:bot:<workflowId>"` and all had it stripped. Without
  it, a bot run reads/writes the **owner's general memory namespace**, shared
  across all their bots and runs ("check memory" / "store in memory" phrasing
  still works; prompts are a bit larger since the recall snapshot is built from the
  whole store). The harness guard (`resolveMemoryUser`) honors a hardcoded
  `memory_owner` only when it names the runner's own identity — anything else is
  rejected with a warning and falls back to the owner's store, so a copied YAML can
  never read or write another user's memory even if it tries. Full policy and
  rationale: `orchestration/BOT-CREATION.md`. (Do not confuse this with a
  workflow's `followup.memory_scope: all_tasks|last_task`, which only scopes
  follow-up chat context.)
- **Scheduled runs** keep the full per-user memory lifecycle — a daily mission
  genuinely accumulates. Only system jobs and heartbeat condition checks are
  memory-free (heartbeat is a user-facing job type — see `plungeai-scheduling` —
  whose ticks are watch-and-notify checks, not learning runs).
- **Delegate children** never write memory: the `memory` tool is stripped from
  fan-out children by design — one writer per run.

## Choosing the right memory move

| Need | Move |
|---|---|
| Output of a run/step | `plungeai_get_result` (+ `task_id`) |
| Pass data between workflow steps | Nothing — automatic |
| "Remember X about me/us" | `plungeai_memory remember` |
| Mission should apply past learnings | Automatic recall — just run it; curate memory if recall is noisy |
| "What did we do about X before?" | `plungeai_memory search_runs` → `get_run` |
| Standing company/product context for many runs | Not memory — a **background** card (`plungeai-skills-plugins`) |
| Reusable method/knowledge pack | Not memory — a **skill**, created with `plungeai_learn` (below) |

The last two rows matter: memory is per-user learned state. Shared, versioned,
deliberately-authored context belongs in backgrounds and skills, where injection is
explicit and reviewable.

---

## MCP tool: `plungeai_memory`

**Purpose:** the user's long-term memory. Two stores, deliberately separate:
the durable memory that `recall` reads (and `remember` writes), and the run
journal of past mission runs (`search_runs`/`get_run`).

**Actions**

| Action | Requires | Notes |
|---|---|---|
| `recall` | — | Everything the platform remembers about the user (the same recall missions get automatically). |
| `remember` | `content` (add/replace) or `old_text` (remove); neither for read | Durable write. `target`: `user` (profile facts) \| `memory` (working knowledge, default). `operation`: `add` (default) \| `replace` (needs `old_text` + `content`) \| `remove` (needs `old_text`) \| `read`. `recall` sees writes immediately. |
| `search_runs` | `query` | Search the run journal (optional `limit`, default 10). |
| `get_run` | `run_id` | One journal entry in full. |

**Example:** `{"user_request": "remember that I prefer summaries under 200 words", "action": "remember", "target": "user", "content": "Prefers summaries under 200 words"}`

**Returns:** `recall` → the memory document (or "No memory recorded yet.");
`remember` → "Saved to your user profile/memory ... recall will see it
immediately."; failed writes return the error plus the CURRENT entries so a
`replace`/`remove` can be retried with an exact `old_text`.

**Failures & fixes:** "content is required for remember" / "query is required
for search_runs" / "run_id is required for get_run" → supply them. A
`replace`/`remove` that misses names the current entries — copy `old_text`
verbatim from that list and retry. Use `remember` whenever the user says
"remember this" — chat context alone does not persist.

---

## MCP tool: `plungeai_learn`

**Purpose:** distill a source into a reusable skill, saved PRIVATE to the
user — future runs can inject it. The source can be a URL, pasted
text/markdown, or a description of what was just accomplished in this chat
(learn-back). Runs the platform's `learn` agent card, which resolves live at
run time. **Async by default.**

**Parameters**

| Param | Type | Notes |
|---|---|---|
| `action` | `learn` (default) \| `list` \| `forget` | `learn`: distill `source` into a skill. `list`: your learned skills. `forget`: delete one (`name` required). |
| `source` | string | Required for `action: learn` (a URL, pasted text/markdown to distill, or a description of what you just did). Not used by `list`/`forget`. |
| `name` | string (kebab-case) | On `learn`: optional skill id — the agent picks one if omitted. On `forget`: required (the id from `action: list`). |
| `mode` | `sync` \| `async` | Only applies to `learn`; default async. |

**Example (learn):** `{"user_request": "save what we learned about CNL debugging as a skill", "source": "Distilled findings from this session: ...", "name": "cnl-debugging"}`

**Example (list):** `{"user_request": "what skills have I saved?", "action": "list"}`

**Example (forget):** `{"user_request": "delete my cnl-debugging skill", "action": "forget", "name": "cnl-debugging"}`

**Returns:**
- `learn`, async (default): "Started distilling `<id>` (async)..." → poll
  `plungeai_get_workflow_status`, fetch with `plungeai_get_result` (the result
  names the saved skill).
- `list`: a table of your learned skills (`- \`id\` — description`), or "No
  learned skills yet."
- `forget`: "Forgot skill `<name>`."

**Failures & fixes:** `learn` without `source` → `needs_input`. `forget` without
`name` → "`forget` needs `name` (the skill id from `action: list`)." `forget` on
an id you don't own, or that doesn't exist → "Not yours to delete" /
"No skill named `<name>`. Use `action: list` to see yours." Authentication
required (needs a real user key); unreachable URLs on `learn` surface in the run
result — retry with pasted text instead.

This is the "learn-back" move: when a session produced real research, offer to
distill it so future runs can inject it as a skill — see `plungeai-skills-plugins`
for how a saved skill gets declared and injected on later runs.
