Runtime Controls
Runtime controls are options passed to turn() / TurnAsync(). They are not
.prompty frontmatter fields. This keeps the prompt portable while letting the
host application decide policy, budgets, cancellation, observability, and live
steering.
The prompt is prepared before these per-iteration controls run. Steering, compaction, and tool result messages mutate the in-memory message array for the current turn; they do not re-render the original template.
Execution order
Section titled “Execution order”During each loop iteration Prompty applies controls in this order:
- Check cancellation.
- Drain steering messages.
- Trim messages to the context budget.
- Apply compaction if messages were trimmed and a compaction strategy exists.
- Run input guardrails.
- Check cancellation again before the LLM call.
- Call the LLM, with retry policy.
- Run output guardrails on model text.
- Run tool guardrails for each requested tool call.
- Execute tools, serially or in parallel.
- Append provider-formatted tool result messages.
Control pages
Section titled “Control pages”- Events & Cancellation covers observability callbacks, token/status updates, and cooperative stop behavior.
- Context & Compaction covers trimming the message array and replacing dropped turns with summaries.
- Guardrails covers input, output, and tool policy hooks.
- Steering covers injecting guidance between loop iterations.
- Tool Execution covers serial vs. parallel tool dispatch, tool failures, max iterations, and LLM retries.
Example: combine controls
Section titled “Example: combine controls”from prompty import turnfrom prompty.core import CancellationToken, Guardrails
token = CancellationToken()
result = turn( agent, inputs={"question": question}, tools=tools, on_event=lambda event_type, data: print(event_type, data), cancel=token, context_budget=50_000, compaction=lambda dropped: summarize_locally(dropped), guardrails=Guardrails( input=lambda messages: check_input(messages), output=lambda message: check_output(message), tool=lambda name, args: check_tool(name, args), ), steering=steering, parallel_tool_calls=True, max_llm_retries=3,)import { Guardrails, turn } from "@prompty/core";
const result = await turn(agent, { question }, { tools, onEvent: (eventType, data) => console.log(eventType, data), signal: abortController.signal, contextBudget: 50_000, compaction: async (dropped) => summarizeLocally(dropped), guardrails: new Guardrails({ input: (messages) => checkInput(messages), output: (message) => checkOutput(message), tool: (name, args) => checkTool(name, args), }), steering, parallelToolCalls: true, maxLlmRetries: 3,});var result = await Pipeline.TurnAsync( agent, inputs, tools: tools, onEvent: (eventType, data) => Console.WriteLine(eventType), cancellationToken: cancellationToken, contextBudget: 50_000, compaction: CompactionStrategy.FromFunction(SummarizeLocallyAsync), guardrails: new Guardrails( input: CheckInput, output: CheckOutput, tool: CheckTool), steering: steering, parallelToolCalls: true, maxLlmRetries: 3);use prompty::{Compaction, TurnOptions};
let options = TurnOptions { context_budget: Some(50_000), compaction: Some(Compaction::Function(compaction_fn)), guardrails: Some(guardrails), steering: Some(steering), parallel_tool_calls: true, max_llm_retries: 3, ..Default::default()};
let result = prompty::turn(&agent, Some(&inputs), Some(options)).await?;Choosing controls
Section titled “Choosing controls”Start small:
- Use events first so you can observe the loop.
- Add cancellation if the turn can take more than a few seconds.
- Add context budget and compaction when conversations can grow.
- Add guardrails when host policy needs to enforce allow/deny/rewrite behavior.
- Add steering only when a user or operator needs to intervene mid-turn.