LLM_CALL, MCP_LIST_TOOLS, MCP_TOOL_CALL, SESSION_MEMORY and the decision plugins) are optional modules chosen when the server is built. See Built-in plugins.
Yukta keeps the control flow in the graph, not in the model. The model suggests an action; a node you wired runs it, and you decide what happens next. That makes an agent easy to inspect, limit, pause and approve like any other workflow. This page shows how to put the pieces together. For the options of each plugin, see AI plugins and Decision plugins.
The pieces
Calling a model
LLM_CALL supports three kinds of provider:
Give a key as
decrypted:<key> so the secret is read from the environment variable YUKTA_SECRET_<KEY> and not stored in the workflow. The prompt can be built from the message with ${payload.message} and the system prompt is a plain option. The answer is in payload.content, with token usage in payload.usage.
A failed call fails the node by default. With failureMode CONTINUE and routeByStatus on, the node emits on success or failure, so the failure port can lead to a second LLM_CALL with a different model as a fallback. See Failure handling.
Chat with memory
A chat turn is aSESSION_MEMORY node in load mode, the model, and a SESSION_MEMORY node in append mode:
{"sessionId": "user-42", "message": "My name is Ada."}. A later start with the same sessionId sees the earlier turns in payload['history']. messages is written as an expression on purpose: a ${...} reference would turn the list into text.
Tools and the agent loop
An agent loop has four steps: the model reads the task and the tools on offer, decides whether it needs a tool, the workflow runs the tool, and the result goes back to the model.MCP_LIST_TOOLSreads the tool names, descriptions and input schemas of an MCP server.allowedToolsnarrows the list.LLM_CALLtakes them in itstoolsoption (an expression such aspayload.tools). If the model wants a tool, the call appears inpayload.actionsas{"tool", "arguments", "id"}. The node does not run it.- A
BRANCHnode tests whetherpayload.actionshas entries. MCP_TOOL_CALLruns the tool. Give itallowedToolsso that a model can never reach a tool you did not intend, even by choosing a name.- The result, in
payload.textandpayload.structured, goes into a secondLLM_CALLthat writes the answer.
LLM_CALL, BRANCH and MCP_TOOL_CALL group, which gives a hard upper bound that is easy to read. Or move one round into its own workflow and run it again with SUB_WORKFLOW; nesting is capped at 5 levels, which bounds the loop. See Sub-workflows. LOOP_PREDICATE repeats a single processor, so it suits polling and retrying one call, not a whole round.
Each node replaces the payload with its own result, so carry what you still need in the message metadata. The example below sets the task into the metadata and reads it again with ${metadata.task}.
Complete example
This session runs a one-tool agent against a hypothetical issue tracker. The firstLLM_CALL may ask for one tool. If it does, the workflow runs the tool and a second LLM_CALL writes the answer; if not, the first answer is final. Both model nodes and the tool node share one budget named triage, and hardStop ends the whole execution if it is used up.
POST /api/sessions, then start the workflow with POST /api/workflow/start and the body {"sessionId": "issue-agent", "workflowId": "triage"}. The tool server, tool names and token here are placeholders for your own. The prices are per million tokens; use your provider’s current prices.
Things to notice:
- If the model asks for several tools,
picktakes the first one. To run all of them, splitpayload.actionswith aSPLITTERnode first. - The
agentname puts the three nodes in one budget. Without it, each execution has one budget for all its nodes. - A trigger-set
metadata.tasksurvives every node, so the final prompt can quote the task.
Decisions instead of free text
When a node must choose, not write, a decision plugin is cheaper and easier to route on than a free-text answer:CLASSIFY gives one label from your list, GATE a yes/no with a probability, SCORE a position on a scale, ASK several answers in one call. They use a small local decision model (Ollama System One) or any chat model. Typical uses:
CLASSIFYwithrouteByLabelat the start of a workflow, so each kind of request takes its own branch, with anuncertainport for the unclear ones.- A
GATEin front ofMCP_TOOL_CALLthat asks whether the arguments the model proposed are safe, withstateset topayload.args. ANOMALYandDEDUPEin a long-running, scheduled workflow, to react to an unusual or repeated event. See Scheduling.
Guardrails
Models can loop, overspend and be fed hostile input. Yukta gives you limits at four levels.
A few facts about budgets:
- The ledger is shared by every model and tool node of the execution (and
agent), andmaxCallscounts tool calls only. - The check runs before a call and the usage is added after it, so one call can overshoot, and calls that run at the same time can each pass the check.
- Budgets live in server memory. They start again from zero after a restart or a resume.