Skip to main content
Every turn runs a deterministic-first loop:
  1. Apply inbound set: button taps (when the user text is a valid payload from the latest agent message). The engine writes that memory field and runs its memory validation tool before any LLM call. Stale or non-writable taps utter utter_set_memory_button_skipped and skip the rest of this loop. See Set-memory payloads.
  2. Execute deterministic steps first: ordered-block steps that need no model call (execute_tool:, noop:, set_memory:, action:, and collect: steps whose value is already known, including a field just written by a set: tap). The framework also narrows what the LLM may do next, omitting hidden / disabled / engine-managed skills and hiding gated tools, though the routing choice itself is made by the LLM in step 4.
  3. Build a scoped prompt from live state: the active skill’s instructions filtered by if: markers, the tools whose requires: conditions currently hold, and the memory values readable in scope. A suppressed set: payload is omitted from this prompt.
  4. Call the LLM.
  5. If the LLM returns a tool call: dispatch it, apply any memory writes the tool makes as it runs, check for newly-available deterministic steps, and loop back to step 2.
  6. Otherwise: send any text. Then either wait for the user or loop from step 2. See When the engine waits.
One user message can go around this loop several times.

When the engine waits

The engine waits for the next user message when:
  • The stack is empty after a delivered response (the skill or block finished).
  • An engaged collect: needs the user’s answer.
  • An ordered-block action: action_listen step runs.
  • The LLM calls listen() alone (not mixed with text or other tools).
After a canned ordered-block utter_*, leftover hybrid prose or further deterministic steps can still run in the same turn. On that follow-up LLM call, a canned-response reminder tells the model not to repeat or paraphrase the canned reply. See listen.

When the engine ends the conversation

Hangup is destructive and is not offered as an LLM tool. A skill must explicitly execute action: hangup in an ordered block to end the conversation. Place an utter_* goodbye before that step when a farewell is needed.

State tracked per control level

These coexist. A hybrid skill, meaning prose with if: markers that also references an ordered block, tracks both a step pointer and memory state at once.

Tool execution

A tool may be invoked by the LLM (a tool call) or by the framework (an execute_tool: step). The function does not know or care which. tool_constraints are checked in both cases, and in two places:
  • when the tool schema is built, deciding whether the LLM sees the tool at all
  • again at dispatch, before the function runs
Both fail closed. A requires: condition that cannot be evaluated hides and blocks the tool rather than allowing it. When the LLM issues a local or MCP tool call, Mantle validates the arguments against the JSON schema offered to the model before confirmation or execution. Missing, extra, or mistyped arguments are returned as a retryable error so the model can correct the call in the same turn. Because the tool did not run, this does not trigger its on_failure response. An imported MCP tool without a usable object schema is treated the same way: the remote tool is not called. Ordered execute_tool steps use the same check after memory fill. Those arguments come from the step’s parameters:, not from the model, so the LLM cannot repair a schema-invalid payload. The engine stays on the step, does not run the tool or on_failure, and yields so a later deterministic pass can retry if missing memory has been filled. If the parameters themselves stay invalid, the tool cannot run.

Knowledge search changes the loop

When search_knowledge runs during a turn, the tool list for the remainder of that turn collapses to activate, search_knowledge, and cannot_help, plus resolve_tool_confirmation when a confirmation is pending so the pending gate is never stranded. The active skill’s lifecycle, collect, builder, and listen tools are dropped so a grounded answer neither re-drives the skill nor ends silently before presenting the retrieved information. They return on the next turn. Relatedly, cannot_help is withheld until a search has run whenever a knowledge base is configured, so the model cannot decline before retrieval could have answered. If the model still mixes listen with a user-facing answer after a search, the engine ignores listen and treats the text as the grounded reply: the post-search trim ends so the skill can be parked and resume offered.

Where conditions are evaluated

See Conditions for expression grammar, and skill.md complete_when for the skill-level mapping.

Tracker events

Mantle appends these events to the conversation tracker. They are what Inspector and Copilot read; you do not author them in YAML. These are the only lifecycle, tool, and memory events Mantle writes. Skill lifecycle is skill_*, block lifecycle is ordered_block_*, tool runs are tool_executed, and memory writes are memory_set / memory_cleared. block_id is the authored id (submit, main). Inspector shows each ordered_block_* as a Block row. Session-start channel metadata is recorded as memory_set (key session_started_metadata, after the session starts). A later session start without channel metadata does not reuse the previous session’s value.

See also