The session MCP¶
In short
- Every session gets two MCP servers:
parleml, the business tools, andsession, a small server for steps, decisions, waiting and the final outcome. sessionhas four tools:run_skills,ask_gate,await_operationandreport_run_outcome.- An agent waits on long work with
await_operationand ajob:handle, never by polling. Waiting costs no turns. - Decide automatically answers a decision only when the agent proposed a complete answer, and never approves with changes.
Every session gets two kinds of MCP server:
parleml— ParleML's own MCP (the tools), granted through the profile's connections with a fresh token per attempt;session— the runtime's small MCP at/session-mcp/, for what no CLI provides: supervised steps, decisions, waiting on long work, and the final outcome.
The session MCP knows the caller from the bearer token the runtime gives each attempt. A tool never takes a run id, so an agent cannot act for another session, and the token stops working when the attempt ends.
Tools¶
| Tool | What it does |
|---|---|
run_skills(runs) |
Starts one or more steps ({skill, instructions}) and waits for their results. |
ask_gate(gate, questions, context_markdown?, subject?) |
Asks the person a structured question and waits for the answer. |
await_operation(operation_id) |
Waits on a job:<experiment_id> (a run, its run_all chain included) or job:<experiment_id>/<job_id> (a batch scoring job) handle, or a deferred operation. |
report_run_outcome(outcome, explanation) |
Records the business result. The last tool call of every run. |
A CLI lists these tools under the server's name: Claude Code as
mcp__session__await_operation, Gemini CLI as mcp_session_await_operation.
Every briefing states the names its CLI lists (Tool names), and text the
agent reads — skills, references, tool results — names a tool by its short
name and its arguments by name ("the session tool await_operation
(operation_id="job:…")"), never as a call such as await_operation("job:…"),
which a model can copy as a tool name of its own.
run_skills¶
- Only the orchestrator may start steps; a step cannot start its own.
- A step starts in a new conversation: its
instructionsare the whole handoff, and its returnedsummaryis all the orchestrator sees of it. - Batch several entries only for independent, read-only consultations.
Steps that write share the work directory, so start them one at a time.
A batch holds at most the profile's
max_concurrent_children(default 5). - Each result has the execution
status,summary,error, the step's reportedoutcomeandrequires_resume. A failed, cancelled or stopped execution overrides a reported success. A step stopped by a cap still returns what it finished, and its files stay in the work directory.
ask_gate¶
One question may be open per run; parallel steps may each have one.
Question kind |
Notes |
|---|---|
single_select, multi_select |
Options are {value, label} objects. allow_free_text: true lets the person type their own answer, which the agent receives verbatim. |
text |
Free prose. |
approval |
Recommended option first. An affirmative option (approve, yes, proceed) is followed by approve_with_changes, which gets a required changes field; an option meaning decline (reject, no, decline, deny) gets a required reason field. Each field shows only when its option is chosen. |
upload |
The person attaches files; the answer names their absolute paths, which the agent reads from disk. |
defaultis the agent's proposal: the form opens with it filled in and marked proposed, but nothing is sent until the person submits. Approval and upload questions take no default.whenshows a question only after another answer matches.context_markdownis the immutable record of what was decided on.subjectbinds the answer to exact content, for examplesha256:<64 hex>.- A gate holds at most 20 questions.
An approval with changes is an approval. The agent receives
{"approve": "approve_with_changes", "changes": "…"}, applies the changes,
records them in its log as conditions the person set and carries on; it asks
again only if a change makes the step impossible. The workspace's decision
record carries the changes as conditions, and a release approved at that
gate carries them on its approval.
A decline is an instruction, not a dead end. The agent reads the reason, revises, and reopens the gate, or asks what to change when the reason is too vague.
The person answers in the session (or on the workspace's Decisions page,
or from Needs attention); the route is
POST /api/agent/runs/{root_id}/runs/{run_id}/answer. The answer is stored
in the run's run.json before the agent sees it, so it survives a restart:
if the process is gone, the session shows Resume and the answer is
delivered then.
Auto-approval¶
Decide automatically (PUT /api/agent/runs/{root_id}/auto-approval) lets
the runtime answer a gate when the agent proposed a complete answer:
validated defaults for text and select questions, auto_approval_value
for approvals. A required upload, a missing proposal or an ambiguous
approval keeps the whole gate open for a person, with the reason shown; the
runtime never submits half a gate. It never approves with changes: setting
conditions is a person's answer, so auto_approval_value may not name
approve_with_changes. Automatic answers are recorded as
Auto-approval · System. New sessions start with
auto_approval_default from the profile (off unless the profile turns it on).
Auto-approval does not resume a lost process.
An automatic gate answer does not widen the business token's permissions: promotion and deletion remain unavailable through the business MCP. Launches that use paid external embedding services require a gate answered by a person; an automatic approval does not satisfy that requirement.
await_operation and job: handles¶
An agent waits on ParleML work with a job: handle:
job:<experiment_id>— a training run. A run launched withrun_all(the default for a plan launch) is done when its wholetrain → explain → score → publishchain is, socompareand the drivers read right after it find the explanations; the result names each stage's state. An explanation computed later (run_experiment(action="explain"), another preset) holds the same handle until it is done — the result'sexplanationssays how it went.job:<experiment_id>/<job_id>— a batch scoring job.score_batchreturns this exact handle aswait_handlewhile the job is in flight; found on disk after a server restart too.
Work a tool call left running on the server has its own handle, which the
call's 202 result names as wait_handle:
probe:<kind>/<dataset_id>/<digest>— a Signal Check (probe_dataset).eda:<dataset_id>— a dataset's EDA profile or leakage scan (prepare_data); completed says which, and thatget_edareads it.autodoc:<experiment_id>— a model-card build (run_experiment(action="autodoc"),get_autodoc); completed saysget_autodocreads it.call:<Idempotency-Key>— a builder call still running after its client gave up.
A job waiting for a slot — the server runs a capped number of heavy jobs (fits, explanations, scoring) at once — reports where it stands in the queue while the wait is pending.
await_operation(handle) holds the call until the work finishes, sending
MCP progress every 30 seconds so the CLI does not time out, and returns its
final status. Waiting this way costs no turns. Work in another workspace is
refused.
One hold lasts at most PARLEML_AGENTS_AWAITABLE_MAX_HOLD_SECONDS (15 min):
work still running then returns pending with waited_s — how long the run has
waited on the handle, across re-awaits — and a next_action that says so and
names what the agent can do if it should have finished by now: stop it
(prepare_data(action="cancel") for an EDA job, run_experiment(action="abort")
for a fit, score_batch(action="cancel") for a scoring job), or, for a call:
handle, stop the work the call started. Awaiting again keeps waiting. The
server bounds the work itself too: a job that makes no progress for
PARLEML_JOBS_STALL_AFTER_S (15 min) is failed as stalled, which ends the wait
with that error (failure_kind: "stalled" on a run).
A message a person queues for the run ends the wait early with a pending
result carrying the message; the agent reads it and awaits the same handle
again (Talking to it while it works).
A stopped run, or one whose process ended, gets requires_resume; a
cancelled one gets cancelled.
report_run_outcome¶
outcome |
Meaning |
|---|---|
succeeded |
The requested deliverable exists and is verified. |
partial |
An explicitly accepted, reduced scope was delivered. |
blocked |
A named required criterion cannot be met. |
failed |
The attempted outcome ended unsuccessfully. |
The explanation is at most 20,000 characters (longer is truncated). The
outcome is separate from the execution status: a run that completed may
still be blocked, and a resumed attempt must report its own outcome. A
run that ends without reporting one fails with missing_outcome.
The healthy call sequence¶
- Read the workspace (
get_workspace_brief) and do the work through theparlemltools, or start steps withrun_skills. - Wait on long jobs with
await_operation, not by polling:job:<experiment_id>for a run,job:<experiment_id>/<job_id>(thewait_handle) for a scoring job. - Ask with
ask_gatewhenever a decision is the person's. - Continue from the answer: after an approval with changes, apply them and go on; after a decline, revise and ask again.
- Verify the requested result.
- Call
report_run_outcomelast, then write the short summary.
Deferred operations¶
Details: Cursor's deferred operations
Cursor's MCP client cancels a tool call after about a minute, so for
cursor-cli (while PARLEML_AGENTS_CURSOR_DEFERRED_MCP_WAITS_ENABLED is
true) run_skills and ask_gate do not hold the call. They wait one bounded
slice (PARLEML_AGENTS_CURSOR_DEFERRED_WAIT_SECONDS, 50 s), so a quick step
or an instant auto-approval still returns in the same call, and otherwise
return a handle:
{
"status": "pending",
"operation_id": "019…",
"kind": "skills",
"next_action": "Call await_operation with this operation_id again immediately. Do not wait, sleep, or do other work between calls: the call itself is the wait."
}
The agent passes the id unchanged to await_operation, which waits another
slice. job: handles are waited on in the same slices. The terminal
envelopes are:
status |
Means |
|---|---|
completed |
result is exactly what the blocking tool would have returned. |
requires_resume |
A step is stopped or lost its process; the person uses Resume. |
failed |
The operation itself could not start or be read (error). A step that failed is still a completed result. |
cancelled |
The session was cancelled. |
- The operation is stored in the run's
run.json(current_operation), so it survives a restart. Calling the same tool again with the same input returns the same handle; different input is refused until the current result has been read. report_run_outcomeis refused while an operation is pending or its result unread.- Resume resumes unfinished steps first and marks their results read before the orchestrator continues.
- A turn that only calls
await_operationon the run's own handle costs no turns. The same holds on every CLI forjob:waits.
Claude Code, Codex and Gemini CLI hold the call (up to
PARLEML_AGENTS_MCP_TOOL_TIMEOUT_MS, 24 hours, for a gate; up to
PARLEML_AGENTS_AWAITABLE_MAX_HOLD_SECONDS, 15 minutes, for a handle of server
work) and never see a deferred handle.
Next¶
- Recovery & resume: what happens to an open question across a restart.
- Tool catalog: the
parlemltools.