08/MCP Orchestration

The Agent Loop

The loop's defining contract: the request never drops.


Once a binding kicks in, the model is told about the Hierarchy's tools via `tools`; if the model emits a `tool_call`, the gateway runs it against Trellis, appends the result as a `role: "tool"` message, and calls the model AGAIN — that is one "turn". Turns continue until the model gives a final answer or a budget runs out.

#The request never drops

This is the subsystem's defining contract: no way a tool call can fail ever surfaces to the user as an error — each one comes back to the model as a readable tool result, and the loop keeps running. The table below names three of them; the judge's own way of blocking a call is covered separately, further down.

SituationWhat the model receives
Hierarchy gate refusalText prefixed with "⛔" naming which parent must be called first — the model sees it and self-corrects by calling the parent.
Transport error (Trellis unreachable, the tool raises…)A `tool error: <reason>` text.
TimeoutA `tool error: '<tool>' timed out after <N>s` text.
A gate refusal is a FEATURE, not an error: a child server unlocks only after its parent has been used. That text is never silently rewritten or swallowed — it is the only hint the model has to correct itself.

#Turn and time budgets

max_turns
Set on the binding (1–50, default 6 in the panel). Without a binding (a web_search-only loop) a global default is used.
tool_timeout_sec
Set on the binding (1–300s, default 30s). Applied to each tool call individually.
toplam duvar-saati bütçesi
A single hard ceiling, independent of the binding and admin-tunable (default 120s) — it covers the TOTAL time across ALL turns of the loop. This ceiling can end the loop even before max_turns is reached.

When a turn or the total budget runs out, the request STILL does not error: the last assistant answer available is returned to the client with a short note explaining the budget ran out. If the model never produced anything at all (the very first turn's self-call itself failed), the loop silently returns nothing and the request continues down its normal, non-agentic path — as if the feature did not exist.

A single turn's tool-call count also has a cap (default 8): calls beyond it are simply never executed in that turn, AND the assistant message carried into the next turn is rewritten to list only the calls that actually ran — the model never believes a call executed when it didn't.

#Every turn is a self-call

tool_callnext turn (self-call)no tool_calls left
User message
routerAgent loopself-call turn
Trellis toolor web_search
Final answerno more tool_calls
Anatomy of one turn

Every turn of the loop is made as a self-call back through the gateway's OWN front door (`/v1/chat/completions`) — the loop routes its own traffic through the exact path it processes everyone else's. Each self-call carries an `X-MCP-Internal` header; seeing it, the gateway SKIPS every return-path injection (system prompt, Second Brain, guardrails, and the loop itself) for that turn — without the skip, every turn would re-apply the system prompt and memory injection, and the loop would call itself forever.

This is why the intermediate turns never show up as their own row in the user's activity log: requests carrying `X-MCP-Internal` are exempt from activity logging. Instead, every turn's `usage` block is summed across the whole loop, and that total is what gets written into the ONE activity row logged for the user's request. Otherwise the same tokens would be counted twice (once per intermediate turn, once for the outer request) and, since the intermediate turns authenticate as the admin/service identity, they would end up billed to the wrong account instead of the user. To reconcile that one row against token counts, see Observability → Logs & Analytics (Request Logs).

#The judge runs inside this loop

When a judge policy is configured, every tool call passes through the `tool_call` surface right BEFORE it runs, and its result through the `context` surface right BEFORE it reaches the model. The details of those two surfaces (actions, clustering, modes) are covered on Platform & Admin → Judge and Judge Policies; the one thing specific to this loop is that a `block` verdict here never produces a 422 — the model gets a readable "tool error: blocked by policy…" result instead, and the loop keeps running under the exact same "the request never drops" contract as above.