plungeai-memory
Memory — run data (SharedMemory) vs long-term memory. Two systems, never confuse them
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 {execution_id: "exec-…"}
# One step of a fan-out
plungeai_get_result {execution_id: "exec-…", task_id: "web"}# 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_readyFacts 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
memorytool (missions write learnings as they work; episodes append automatically at run end). - From outside:
plungeai_memory(MCP),ocean-cli memory, or the One API (next section):
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.
Over the One API — /v1/memory, /v1/skills
Auth: Authorization: Bearer ozk_YOUR_KEY. Every call acts on the key's own user; format (json default,
yaml, markdown, text) is accepted in the body or as ?format=.
curl -s "https://api.plungeai.com/v1/memory/recall?q=weekly&limit=20" -H "Authorization: Bearer ozk_YOUR_KEY"
# 200 {"entries":[{"id":"mem_3f2a9c01b7de","scope":"user","text":"Prefers weekly summaries"}],"count":1}
curl -s -X POST https://api.plungeai.com/v1/memory/remember -H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"text":"Prefers weekly summaries, no emojis","scope":"user"}'
# 201 {"id":"mem_…","scope":"user","text":"…","usage":"…","message":"added"} (200 when already stored)
curl -s -X POST https://api.plungeai.com/v1/memory/forget -H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"id":"mem_3f2a9c01b7de"}' # or {"text":"weekly"}
# 200 {"removed":1}| Route | Notes |
|---|---|
GET /v1/memory/recall | q keeps entries containing it (case-insensitive) across the MEMORY and USER stores; limit 1–100, default 50 |
POST /v1/memory/remember | text 1–65,536 chars; scope memory (default) or user; 422 memory_rejected carries current_entries |
POST /v1/memory/forget | exactly one of id / text (else 422 invalid_params); 404 memory_not_found; 409 memory_conflict |
GET /v1/memory/runs | q required (422 invalid_params); hits are {run_id, ts, goal, snippet} |
GET /v1/memory/runs/{id} | {run_id, content}; 404 run_not_found, also for another user's run |
GET /v1/skills, POST /v1/skills/learn, DELETE /v1/skills/{id} | your private skills only ({skills: [{id, description}], count}); learn saves the markdown body you send under name — it does not distil a source |
An entry's id is mem_ plus twelve hex characters derived from its scope and text, so it is stable between
reads and cannot name an entry your stores do not hold. REST recall returns structured entries; MCP
recall returns the MEMORY.md + USER.md snapshot a mission starts with.
Namespaces and special runs
- Bots: policy since 2026-08-23 is to omit
memory_ownerentirely (a saved workflow/agent/bot/campaign YAML carrying it is refused,422 invalid_workflow) — the first 20 bots were authored with a hardcodedmemory_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 hardcodedmemory_owneronly 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'sfollowup.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
memorytool 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)..." → pollplungeai_get_workflow_status, fetch withplungeai_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.