Rewrite rules
A rewrite rule changes the conversation before a model sees it. Use rewrite rules to:
- Save tokens. Compact long tool output, such as large Git diffs or search results.
- Share instructions. Add the same instructions to the system prompt for everyone who uses a model.
- Protect sensitive content. Remove secrets or other patterns before they reach the model.
Rewrite rules only change what the model receives. They don't change its response.
Every model, whether Built-in, Virtual, or BYOK, has its own selection of rewrite rules. For a Virtual model, its rewrite rules run before its routing rule, and the rules of the model it picks don't run.
Built-in rewrite rules
Radius includes three rules that compact the output of bash tool calls:
- Compact long Git diffs shortens long
git diffoutput by limiting how many changed lines each section and the whole diff show. - Compact verbose Git status rewrites
git statusoutput in a compact format and lists a limited number of files per section, followed by a count of the rest. - Compact large grep results shortens long
grepandrgoutput by limiting the number of matches in total and per file, and by cutting long lines.
Each rule only changes output it recognizes, and only when the result gets shorter. Unusual formats and failed commands are left unchanged. The Git diff and grep rules also leave short output unchanged. See Built-in rule details for the exact limits.
To get the original output for one command, prefix it with RADIUS_NO_PROCESS=1, for example RADIUS_NO_PROCESS=1 git diff.
The Rewrite rules page, linked from Models, shows the source of every rule.
Choose rules for a model
Open a model on the Models page and select its rewrite rules. New models use none, and publishing a rule doesn't add it to any model. A model can use up to 8 rules.
Selected rules always run in the order shown on the Rewrite rules page, from lowest to highest priority. The order is the same for every model, and a model can't change it. Owners and admins can change priorities on that page. See Priorities.
Custom rewrite rules
A custom rewrite rule is a small JavaScript module your organization publishes through the Radius API. To create one, ask Pi. Set up Pi as described in Manage models with an agent, then describe the rule you want. Pi uses these operations:
rewriteRules.testruns a draft rule against a sample conversation.rewriteRules.publishpublishes it.models.upsertselects it for a model. You can also select it in the model's settings on the Models page.
Owners and admins can publish and delete rules.
Write a rewrite rule
A rule default-exports an object with a rewrite(input) method, which can be async. This rule adds an instruction to the system prompt:
export default {
rewrite(input) {
const systemPrompt = input.request.context.systemPrompt ?? "";
return { systemPrompt: `${systemPrompt}\nKeep answers concise.` };
},
};
input.request.context contains the conversation: systemPrompt, messages, and tools. Unlike routing rules, rewrite rules receive the complete conversation, including image data, tool-call arguments, and tool schemas. See Rule input.
A rule returns the new messages, the new systemPrompt, or both. It can also change input.request.context directly and return nothing. Rules can't change tools or any other part of the request. See Return values.
Keep the conversation valid. Every tool result must still follow its tool call, and the conversation can't be empty. Change existing messages rather than creating new assistant or tool-result messages.
Rules run in a sandbox without network access, with up to 50 ms of CPU time and a 1 second timeout. A rule's top-level variables can carry over from one request to the next, including requests from other people in your organization. This isn't guaranteed, so don't rely on it, and don't keep conversation data in top-level variables.
If a rule fails, times out, or returns an invalid conversation, the request fails. Radius never silently skips a failing rule, so a rule that removes sensitive content can't be bypassed by an error. This also applies to built-in rules.
Use built-in rules and helpers
Custom rules can import helpers from #radius/rewriter-helpers: analyzeBashInvocation, rewriteBashToolResults, rewriteBashResultText, stripAnsi, and truncateText.
They can also apply the built-in rules themselves, for example only under certain conditions:
import { applyBuiltinRewriteRules } from "#radius/rewrite-rules/v1";
export default {
rewrite(input) {
return applyBuiltinRewriteRules(input, {
enabled: ["compact_git_diff", "compact_git_status"],
});
},
};
The built-in rule IDs are compact_git_diff, compact_git_status, and compact_grep. Applying a built-in rule twice can change the output again, so don't also select a rule for the model if your custom rule already applies it.
Test a rewrite rule
rewriteRules.test runs one rule against a sample conversation without publishing it, charging credits, or calling a model. Test a draft by sending source:
{
"source": "export default { rewrite(input) { return { systemPrompt: (input.request.context.systemPrompt ?? '') + '\\nKeep answers concise.' }; } };",
"request": {
"model": "balanced",
"context": {
"systemPrompt": "You are a helpful assistant.",
"messages": [{ "role": "user", "content": "Explain what this project does.", "timestamp": 0 }]
}
}
}
To test a published custom rule, send its rule_id instead of source. The response contains the rewritten conversation in rewritten.context and the estimated effect in impact.
Test conversations where the rule should change something and where it shouldn't, including complete tool calls with their results. See Testing details.
When rewrite rules run
Rewrite rules run on every request, before routing. That includes follow-up requests and retries in the same reply, even when a Virtual model reuses its earlier routing choice.
When you publish a new version, change priorities, or change a model's rules, new requests use the change within moments, and within an hour at most.
Radius records the estimated change in input tokens for each request, and Pi stores it with the assistant message in the session.
Update or delete a rule
Publishing again with the same rule_id creates a new version and switches every model that uses the rule to it. The rule keeps its priority unless you send a new one.
A custom rule's page shows its earlier versions, and so do rewriteRules.versions.list and rewriteRules.versions.read. Viewing an earlier version doesn't make it active again: to go back, publish its source as a new version.
To delete a rule, use its page or rewriteRules.revoke. You can't delete a rule that an enabled model uses.
Custom rules see the complete conversation, including anything sensitive in it. Keep their source short and review it like any other code that handles your data.
Reference
Rule input
input contains:
request: the complete request, includingmodel,options(such as the session ID), andcontext. Nothing is removed or shortened.profile: the settings of the model that received the request, including its selected rewrite rules and maximum thinking level. Tests don't include it.
request.context contains systemPrompt, messages, and tools in Pi's message format. Before your rule runs, it already contains the changes of rules with a lower priority.
Return values
A rule can return:
{ messages },{ systemPrompt }, or both.- An array, which replaces
messages. - The same fields inside
contextorrequest.context, such as{ context: { messages } }. nullorundefined, after changinginput.request.contextdirectly.
If a returned object sets the same field at more than one level, request.context wins over context, which wins over the top level. When a rule returns an object, Radius ignores any direct changes it made to input.
Radius then checks the result:
- At least one of
messagesorsystemPromptmust be present. Empty objects and other values are invalid. messagesmust be a valid Pi conversation and can't be empty.toolsand all other request fields stay as they were.
Built-in rule details
All three rules only change bash tool results that contain exactly one text block, aren't errors, and follow a matching tool call. A rule only replaces a result when its version is shorter. Setting RADIUS_NO_PROCESS to any value other than empty or 0 in the command turns all three off for that result.
- Compact long Git diffs applies when
git diffis the last command, optionally piped throughhead,tail,cat, orsed, and the output has at least 25 lines. It shows at most 100 changed lines per section and 500 lines in total.--stat,--name-only, and similar summary modes are left unchanged. - Compact verbose Git status applies to English long-form
git statusoutput anywhere in the command, at any length. It shows the branch and each file as a short status code, such asM file, and drops Git's hints. It lists at most 15 files per section and 10 untracked files, then a count of the rest.--porcelain,--short, and-zoutput is left unchanged. - Compact large grep results applies when
rg,grep,egrep,fgrep, orggrepproduces the final output, optionally throughxargs,head,tail,cat, orsort, and the output has at least 30 lines. At least 90% of the lines must be in a standardfile:line:text,line:text, orfile:textformat. It shows at most 200 matches, 25 per file, and cuts lines to 250 characters. Output modes such as-c,-l, and-oare left unchanged.
Priorities
- Priorities are whole numbers from
-2147483648to2147483647. Lower numbers run first, and rules with the same priority run in order of rule ID. - Built-in and custom rules share one order. The built-in rules start at
1(Git diff),2(Git status), and3(grep). - The first
rewriteRules.publishfor a newrule_idmust includepriority. Later versions keep the current priority if you leave it out. rewriteRules.updatePrioritychanges the priority of a built-in or custom rule without publishing a new version.rewriteRules.listreturns the active custom rules and aprioritieslist that also includes the built-in rules.
Rule IDs
Custom rule IDs use lowercase letters, digits, and hyphens, start and end with a letter or digit, and are up to 64 characters long. Built-in and custom rules are told apart by kind. Built-in rule IDs contain underscores, so they never match a custom rule ID.
Model selections in the API
In models.upsert, rewrite_rules lists the selected rules as { "kind": "builtin" | "custom", "rule_id": "…" }. The order of the list doesn't matter, because rules always run in priority order. An empty list [] turns rewriting off for the model. Leaving out rewrite_rules keeps the model's current selection.
Testing details
- A test runs one rule. It doesn't run the model's other selected rules or any built-in rules.
- Without
request, Radius uses the modelbalancedand a sample user message. rule_idcan only refer to an active custom rule.- The rule receives no
profile, so test rules that depend on it with care. - Tests use the same sandbox and output checks as real requests.
- Publishing doesn't run the source, so test before you publish.
Errors
| Situation | Response |
|---|---|
| A rule fails during a request | HTTP 503, error: { code: "profile_context_rewriting_component_execution_failed", message } |
Publishing a new rule_id without priority |
HTTP 400, error: "priority_required" |
| Deleting a rule that an enabled model uses | HTTP 409, error: "rewrite_rule_in_use", referencing_profiles: [profileId] |
| Testing a rule that fails | HTTP 200, ok: false, message, and a phase: resolve, setup, load, execute, or invalid_output |