MCP
Runs, results & follow-ups
Source: orchestration/mcp-gateway/tools.ts (getResult, getWorkflowStatus, plungeaiExecutions, plungeaiFollowup, plungeaiContinue), orchestration/mcp-gateway/ops/executions.ts (reconcileRunningRow), orchestration/mcp-gateway/conversation.ts, orchestration/mcp-gateway/execution.ts (loadStoredResult, STALE_RUN_SECONDS), orchestration/mcp-gateway/leg-state.ts, orchestration/mcp-gateway/tool-outcome.ts (pauseOutcome, renderAs), orchestration/mcp-gateway/server.ts (TOOL_DEFS, StatusOutputSchema, server instructions).
Every execution tool (plungeai_execute_workflow, plungeai_execute_agent, plungeai_execute_tool, plungeai_run_mission, plungeai_learn) creates an execution and answers with its execution id. This chapter covers what you do with that id afterwards: read the output, check the status, browse your history, ask a follow-up, and answer a run that paused for you. The examples use https://mcp.plungeai.com/v1. https://api.plungeai.com/v1/mcp is the same server.
| Tool | What it does | Read-only | Runs a model |
|---|---|---|---|
plungeai_get_result | the output of one execution, or of one task in it | yes | no |
plungeai_get_workflow_status | status, error, duration, and whether the run is paused for you | yes | no |
plungeai_executions | your run history; also output, thread, files, steer, cancel, delete | no (steer, cancel and delete change state) | no |
plungeai_followup | a new turn on an execution: continues it, or steers it while it runs | no | yes |
plungeai_continue | answers a paused run, or approves its staged action | no | yes |
Full parameter tables are in the Tool reference.
9.1 Execution ids
Where the id comes from. An ok answer ends with a footer line:
---
execution: `<execution_id>` — use this id for plungeai_get_result / plungeai_followup on this run.A guidance answer for a run that did start (needs_approval, a paused needs_input, a classified failure) ends with _(execution: <execution_id>)_. A mode:"async" call returns the id at once. With format:"json" the id is the outcome's execution_id field.
An execution id is not a workflow id. A workflow id names the recipe, a saved workflow. Every run of it gets a new execution id. The result and status tools need the execution id. If you only have the workflow, list its runs with plungeai_executions {action:"list", workflow_id:"<workflow_id>"} and take the id from there.
Which forms are accepted.
| Id | Example shape | Accepted by |
|---|---|---|
| execution id | a UUID | every tool in this chapter |
| engine id | exec-… (hyphen), as scheduler surfaces show it | plungeai_get_workflow_status, plungeai_get_result, plungeai_executions get / output / cancel. plungeai_executions delete and conversation need the execution id itself. plungeai_followup and plungeai_continue resolve an exec-… id but save the new turn under it, so the thread does not show in plungeai_get_result or Studio (and a follow-up misses the run's stored result as context): pass them the execution id. plungeai_executions {action:"list"} shows it in the ID column; plungeai_get_workflow_status does not (its execution_id echoes what you passed) |
| scheduler run id | exec_… (underscore), from old scheduled runs | none. plungeai_get_result and plungeai_get_workflow_status answer with a pointer to plungeai_schedule {action:"runs"}, whose Execution ID column gives the id to use (Scheduling) |
Ownership. Every tool here checks that the execution belongs to the key's user before it reads anything. You see your own runs only.
"Show me #2." A numbered reference means the Nth row of a list as you saw it. The server instructions tell clients to resolve it against the list already shown in the conversation, never against a freshly fetched one: new runs land all the time and shift the newest-first numbering.
A refusal is not a run. A call stopped before dispatch (missing field, unknown agent, unknown model) creates no execution and has no id. See Errors, feedback & limits.
9.2 Get a result: plungeai_get_result
Pass execution_id. workflow_id is accepted as an older alias, and it still means an execution id. Add task_id to read one task's output instead of the final result.
A full result is assembled in this order:
- An identity line. The workflow name and the first 80 characters of the question, then the start time in UTC and the first 8 characters of the execution id. People rerun the same question often; this line says which run you are looking at.
- The final output. Finished Markdown, as the run produced it.
- Steps in this run (N). Only for runs with two or more steps: each step's task id and the agent that ran it, with the final step marked. Any step is one call away with
task_id. - Conversation thread (N messages). Only when the run has follow-up or continue turns: the whole thread, as
**User:**/**Agent:**turns. - A ⏸ block when the run is paused and waiting for you (9.6).
The server instructions tell clients to show this output verbatim and in full. Some chat clients still summarize long answers; asking for it "verbatim, don't summarize" stops that.
Where results live. When a run finishes, its final output is saved with the execution and does not expire; Studio and this server write the same record. When that saved copy is missing (an older run, or a failed save), the final output is read from the run's shared memory instead, which does expire. The outputs of the other steps live only in shared memory. So task_id for an old run can come back empty while the full result still reads fine.
One step. task_id for a non-final step reads that step's own output (plus a ⏸ block if the run is paused). Passing the final step's id returns the full result. If the step output is gone, the answer is No result found for workflow=<id>, task=<task_id>. The task may still be running, or its output has expired from SharedMemory. (isError: true).
When there is no output, the answer says why. Before it answers, the tool self-heals a stuck running row the same way the status tool does (9.3).
| Situation | Answer |
|---|---|
| No id passed | 📝 NEEDS INPUT: Provide the id of the execution to fetch (as `execution_id`). |
| Unknown id, or not yours | 📝 NEEDS INPUT: No result found for `<id>`, or it is not one of your executions. The remediation points to plungeai_executions {action:"list"} |
Scheduler run id exec_… | That is a scheduler run id, which this tool cannot resolve. Use plungeai_schedule {action: "runs"} … (isError: true) |
| The run failed | This run failed: <reason>. (execution: <id>) (isError: true). Fix the input and rerun; polling will not change it |
| Still running | No result yet for <id> — it is still running. Poll plungeai_get_workflow_status, then retry. (isError: true) |
| Completed, but nothing is stored any more | Run <id> completed, but its stored result is no longer retrievable (expired from SharedMemory). (isError: true) |
JSON. With format:"json" a result is the body GET /v1/executions/{id}/output returns on the One API, field for field: execution_id, status (needs_input / needs_approval while the run waits on you), content (the stored output, raw), final_task_id, steps (task_id, agent, final) and continuation, in the text and in structuredContent; it carries no rendered thread. With task_id it is {execution_id, task_id, result}. The not-ready answers above stay {"content": "<the same text>"}, and a NEEDS INPUT answer becomes the outcome object itself.
Try asking:
- "Show me the result of that last run."
- "Show me the full result for that specific task, not just the summary." Your client takes the task id from the Steps in this run index.
The full result of one run (replace <execution_id>):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_get_result","arguments":{"user_request":"show me the result of that last run","execution_id":"<execution_id>"}}}'One step of it:
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_get_result","arguments":{"user_request":"show me the full output of the search step","execution_id":"<execution_id>","task_id":"<task_id>"}}}'An id that is not yours comes back as guidance, not an error:
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_get_result","arguments":{"user_request":"show me that result","execution_id":"00000000-0000-4000-8000-000000000003"}}}'The same output is also readable as the resource plungeai://executions/{id} (Prompts & resources).
9.3 Check status: plungeai_get_workflow_status
Pass execution_id. The tool has no format parameter: every answer carries both Markdown and structuredContent, which matches the tool's declared outputSchema.
The Markdown answer:
# Execution <execution_id>
- **Workflow:** <name>
- **Status:** <status>
- **Error:** <reason> (failed runs only)
- **Note:** The run stopped at its turn budget before finishing; the partial output is retrievable. (incomplete only)
- **Started:** <ISO time>
- **Duration:** <n>ms
- **Final task:** <task_id>
Fetch output with plungeai_get_result. (completed or incomplete)A ⏸ block follows when the run is paused (9.6).
Status values.
status | Meaning | What to do |
|---|---|---|
running | still executing | poll again |
completed | finished; the output is stored | plungeai_get_result |
incomplete | stopped at its turn or iteration budget before finishing; the partial output is stored (async runs only, below) | plungeai_get_result reads the partial output. Rerun with a larger budget if you need more |
failed | failed; error_message carries the reason (null on the one poll that self-heals the row, below) | fix the input and rerun. Do not keep polling |
cancelled | stopped with plungeai_executions {action:"cancel"} | nothing; rerun if you still want it |
An incomplete run is stored as completed with an incomplete outcome. This tool reports it as incomplete so a status-only poller never reads it as success. The history list (9.4) shows the stored completed.
This holds for mode:"async" runs only. A sync execute or mission call stamps the outcome ok when it answers, which replaces the incomplete mark, so the same run then reports completed here, and its answer reads as a normal success (7.7, 8.5). This is current behaviour. Use async when you need to tell a finished run from one that ran out of turns.
Self-heal. Each read of a running row checks it first:
- the run's output exists but the row still says
running→ the row is set tocompleted; - the row has had no heartbeat for more than 960 seconds (16 minutes) → it is set to
failedwith the reasonInterrupted (run did not complete).
The rule keys on the last heartbeat, not the start time. A live run keeps heartbeating, so a long run is not cut off by it. A background run that died without finishing is reported as failed instead of reading "running" forever. The answer that performs this fix already says failed, but its error_message is still null (and plungeai_get_result says no error was recorded); the next read carries Interrupted (run did not complete).
structuredContent (the registered outputSchema):
| Field | Type | Notes |
|---|---|---|
execution_id | string | the id you passed |
status | string | one of the values above |
error_message | string or null | set for failed runs only |
workflow_name | string or null | the display name, e.g. <name> · <agents that ran> |
started_at | string or null | ISO 8601 |
duration_ms | number or null | |
final_task_id | string or null | |
continuation | object or null | non-null when the run is paused for you: { status, agent, question, pending_action }. status is needs_approval or question; question is the question or null; pending_action is { summary, price? } or null |
Errors (all isError: true):
| Answer | Cause |
|---|---|
Execution not found. | no execution with this id is yours (an unknown id and another account's id read the same) |
That is a scheduler run id, which this tool cannot resolve. … | an exec_… id; use plungeai_schedule {action:"runs"} |
❌ ERROR Status storage is temporarily unavailable — please retry in a moment. (request <id>) | a storage hiccup; retry once after a few seconds |
Polling. A mode:"async" run sends no live progress, so poll this tool until the status leaves running. Then fetch the output with plungeai_get_result. A paused background run shows up here as a continuation, so it never sits unseen.
Try asking: "Is that still running?"
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_get_workflow_status","arguments":{"user_request":"is that still running?","execution_id":"<execution_id>"}}}'An unknown id:
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_get_workflow_status","arguments":{"user_request":"is that still running?","execution_id":"00000000-0000-4000-8000-000000000003"}}}'9.4 Run history: plungeai_executions
One tool with eight actions. Every action except list needs execution_id.
action | Does | Markdown answer | With format:"json" |
|---|---|---|---|
list | your runs, newest first | # Executions (N) and a table: ID, Question (first 60 characters), Workflow, Status, Source, When | {"executions": [{id, input, workflow_name, status, start_time, duration, source}]}; start_time is Unix seconds, duration is milliseconds, source is the surface that started the run (null when unrecorded) |
get | the same answer as plungeai_get_workflow_status | the status block | the status object |
output | the same full result as plungeai_get_result | the result | {"content": "<markdown>"} |
conversation | the run's follow-up thread only | # Conversation — <id> and the turns, or No conversation thread for this execution yet. | {"execution_id", "messages": [...]}, or {"messages": []} |
files | the files the run wrote | # Files of <id> (N) and a table: Path, Size, Type, or This run wrote no files. | {"content": "…"} |
steer | queues message for a running execution | Queued your message to running execution `<id>` (delivered at its next turn). | {"content": "…"} |
cancel | stops a running execution | Cancelling execution `<id>` — the run stops at its next turn or tool boundary. | {"content": "…"} |
delete | deletes the execution record and its stored result | Deleted execution `<id>`. | {"content": "…"} |
list. limit defaults to 20 (1–100); offset defaults to 0 (up to 100,000). workflow_id narrows the list to runs of one workflow. The Status column shows the stored status, so an incomplete run shows completed here; get gives the honest status. The Source column names the surface that started the run: mcp (this server), api (the One API), cli (either of those, called with the header X-Ocean-Client: cli), studio, mini-app, scheduler or campaign-agent; older rows without one show —.
cancel works on a running execution only. Anything else answers Execution is <status>, not running — nothing to cancel. (isError: true). The cancel is cooperative: the tool writes a cancel marker that the engine and the harness read at their next safe boundary, and it sets the row to cancelled (reason Cancelled by user) at once. A step that finishes late does not relabel the row.
steer works on a running execution only: the message is queued for the live loop and delivered at its next turn. A finished run answers Execution is <status>, not running — nothing to steer. (isError: true); continue a finished run with plungeai_followup (9.5).
delete cannot be undone. It removes the execution row and its stored result, and tells connected apps (Studio and the others) that the run is gone.
Errors. execution_id is required for this action. and Execution not found. (unknown, or not yours) are isError: true. A storage hiccup while reading a thread comes back as the ❌ ERROR envelope with a request id.
Try asking:
- "Show me my last 10 executions."
- "Cancel that run."
- "What files did that run write?" The client runs
files. - "Tell the running bot to skip the Berlin leads." The client runs
steerwith the message.
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_executions","arguments":{"user_request":"show me my last 10 executions","action":"list","limit":10,"format":"json"}}}'Cancelling a run changes its state (not verified here):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_executions","arguments":{"user_request":"cancel that run","action":"cancel","execution_id":"<execution_id>"}}}'Listing the files a run wrote is read-only (it needs a real id, so it has no verifier marker):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_executions","arguments":{"user_request":"what files did that run write","action":"files","execution_id":"<execution_id>"}}}'Steering a running bot or agent queues a message for its next turn and changes the run (not verified here):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_executions","arguments":{"user_request":"tell the running bot to skip the Berlin leads","action":"steer","execution_id":"<execution_id>","message":"Skip the Berlin leads."}}}'9.5 Follow-ups: plungeai_followup
A follow-up keeps a run's context instead of starting over. Pass execution_id and prompt. What happens depends on the state of the run:
| State of the run | What happens | Answer |
|---|---|---|
still running | your message is queued into the live run and delivered at its next turn (steering). No new run starts | Queued your message to running execution `<id>` (delivered at its next turn). |
| finished, with an open agent session | the same agent continues its conversation with your message | the agent's reply, plus a ⏸ block if it pauses again |
| finished, no session | the researcher fallback: harness-agent answers with the run's stored result as context | the answer |
The researcher fallback takes the first 9,000 characters of the run's stored output as prior context. It runs with the tools recall, recall_history, knowledge, web_search, web_fetch, registry_search and task_complete, for up to 12 turns. It is told to verify any new factual question with web search, cite source URLs, and never answer from the prior context alone. Because the stored result does not expire, an old run can still take follow-ups, when you pass the execution id itself (9.1).
Both turns are saved. Your message and the answer are appended to the run's conversation thread. When you passed the execution id, the thread shows up in plungeai_get_result, in plungeai_executions {action:"conversation"}, and in Studio.
It narrates itself. While it works it sends live status on the progress channel (Protocol & transport):
🔎 Looking up the run and checking for an open session…
💬 <agent> working — continuing the conversation… (open session)
📚 No open session — loading the run's stored result as context… (fallback)
🔬 Researching with harness-agent — <n> chars of prior context…
⏳ Collecting the answer…
💾 Saving this turn to the conversation thread…
✅ Complete — continued the existing sessionThe answer is plain Markdown text, not the outcome envelope, and the tool has no format parameter. Errors are isError: true: Execution not found., Followup agent call failed, Failed to retrieve follow-up response.
Follow-up or continue? A follow-up is for a finished run, or for steering a running one. A run that stopped to ask you something is answered with plungeai_continue (9.6).
Try asking: "Now do the same analysis but focus on the competitive landscape."
This runs a model and costs credits (not verified here):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_followup","arguments":{"user_request":"now do the same analysis but focus on the competitive landscape","execution_id":"<execution_id>","prompt":"Do the same analysis, but focus on the competitive landscape."}}}'9.6 Paused runs: plungeai_continue
Some agents stop instead of finishing: always before spending money, and sometimes to ask a question ("which wallet?", "what should the call say?"). Nothing has been executed when a run pauses.
How a pause shows up.
On the run's own answer (the sync execute tools): the outcome
needs_approvalwith the summary⏸ AWAITING USER APPROVAL: <action> (<price>). Relay this to the user verbatim and ask them to decide — NEVER approve on your own., or the outcomeneeds_inputwith⏸ AWAITING USER: <question>. The remediation namesplungeai_continueand the execution id.On
plungeai_get_result,plungeai_get_workflow_statusand follow-ups: a closing block.--- ⏸ AWAITING USER APPROVAL: <action> (<price>) Ask the user to confirm, then call plungeai_continue with execution_id "<id>" and approve: true (or message: "<their words>").For a question:
⏸ AWAITING USER: <question>andRelay this to the user, then call plungeai_continue with execution_id "<id>" and message: "<their answer>".In the status tool's
structuredContent.continuation(9.3). This is howmode:"async"runs surface a pause.
The loop.
- The run comes back paused. Nothing has run yet.
- Your client relays the ⏸ block and asks you to decide. The server instructions tell it never to approve on its own.
- You answer in your own words. "Yes" or "approve" becomes
approve: true. Anything else, including "no", goes back asmessage. - On approve, the held action runs exactly once. A repeated or stale approve is harmless: the agent answers that nothing is awaiting approval, so it can never pay twice.
Parameters. execution_id plus message, or approve: true. If you pass both, the approval is sent. approve: false on its own is rejected; to deny, pass the user's words as message.
What comes back. The agent's reply, then either a new ⏸ block (it paused again) or ✅ Conversation resolved (no further input needed). The tool waits up to about 30 seconds for the reply, because an approved payment or outside call runs during this turn. The turn is saved to the run's thread; an approval is recorded as ✅ Approved.
Errors (all isError: true):
| Answer | Cause | What to do |
|---|---|---|
Provide `message` or `approve: true` | neither was passed | pass one |
Execution not found | unknown id, or not yours | check the id |
No conversation session for this execution (expired or not a conversational agent) | the run holds no paused session | nothing to continue; use plungeai_followup for more on a finished run |
This run isn't paused — it already completed. Use plungeai_followup to ask a follow-up about it. | the session is finished and you sent a message | use plungeai_followup |
Agent continuation failed | the engine call failed, or no reply arrived in the ~30-second window | check plungeai_get_workflow_status and plungeai_get_result before you retry: an approved action may already have run |
The trust fence is different. A structured tool called directly with plungeai_execute_tool (or a workflow task) for an outward, irreversible operation (send, pay, transfer, buy, withdraw) comes back ⏸ NEEDS APPROVAL from the engine's fence, with nothing run, no approve_via action and no paused session. plungeai_continue does not apply to it (it answers No conversation session…). Relay it to the user: such an operation runs only from an interactive session such as Ocean Studio, where a person can approve it. See 6.6.
Try asking: "Yes, go ahead" / "Use the second option".
Approve, only after the user said yes. This runs the held action, which may spend money (not verified here):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_continue","arguments":{"user_request":"yes, go ahead","execution_id":"<execution_id>","approve":true}}}'Answer a question instead:
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_continue","arguments":{"user_request":"use the second option","execution_id":"<execution_id>","message":"Use the second option."}}}'9.7 Which tool when
| You want | Call |
|---|---|
| the answer of a run | plungeai_get_result {execution_id} |
| one step's full output | plungeai_get_result {execution_id, task_id}; the ids are in Steps in this run |
| to know if it is done, failed or paused | plungeai_get_workflow_status {execution_id} |
| your recent runs, or the runs of one workflow | plungeai_executions {action:"list"} (with workflow_id to filter) |
| to stop a running run | plungeai_executions {action:"cancel", execution_id} |
| more on a finished run, with its context | plungeai_followup {execution_id, prompt} |
| to change course while it still runs | plungeai_followup (it queues the message into the run) |
| to answer a ⏸ block | plungeai_continue {execution_id, message} or {approve: true} |