Change Agent Parameters at Runtime¶
Adjust a node's configuration while the agent is idle — for example to
switch personalities by updating system_prompt, or to tune retrieval
precision by changing top_k — without recreating the agent.
Prerequisites
- An agent created and ready to receive messages — see Build a Chat Agent with a Graph.
- The agent must not be processing a turn when you call
ChangeParams/change_params. The server rejects the request with error3004 AgentBusyif a turn is in progress.
How it works¶
Parameters are now typed — each node type has a dedicated params object
(e.g. GenerateParams, RetrieveParams) instead of a string key/value map.
The canonical mutation pattern is clone-set-send:
- Clone the baseline params object (preserves structural/immutable fields automatically).
- Set only the fields you want to change.
- Send the modified object via
ChangeParams.
The server diffs the supplied params against the node's current params,
vetoes any structural fields that have changed (3006 ParamNotMutable),
validates values (3007 InvalidParamValue), and applies the delta.
Which fields can be changed?¶
Fields marked (structural) in the schema cannot be changed at runtime.
All other fields are mutable unless a node-specific validator rejects the value.
| Node | Structural (immutable) fields | Mutable fields |
|---|---|---|
Generate |
model_name |
system_prompt, template, placement, stream, all sampling.* fields |
ToolCall |
model_name, tool_call_format, tools |
system_prompt, generate_on_no_tool, notify_client, all sampling.* fields |
Retrieve |
embedded_string_storage, embedding_model |
top_k, threshold, source, filter |
CannedResponse |
inline_strings |
string_storage (rebind), selection_strategy |
HumanMessageGuardrail |
inline_strings |
string_storage (rebind) |
Instruction |
— | instruction_name, instruction |
ClassifyIntent |
embedded_string_storage, embedding_model |
metadata_field, threshold, filter, notify_client, diagnostic_topk |
ClassifyIntentLLM |
model_name, intents_ids, intents_prompt |
system_prompt, history_turns, threshold, margin, notify_client, not_found_intent |
IntentToInstruction |
inline_keys, inline_strings |
string_storage (rebind), instruction_name |
Examples¶
using Tryll.Client;
var agentComp = GetComponent<TryllAgentComponent>();
// Clone the baseline, change one field, send.
var p = (TryllGenerateParams)agentComp.Agent.GetNodeParamsBaseline("answer").Clone();
p.SystemPrompt = "You are a pirate.";
agentComp.Agent.ChangeParams("answer", p,
err => {
if (err == null)
Debug.Log("Persona updated");
else
Debug.LogError($"Error {err.Code}: {err.Message}");
});
// Relax the retrieval threshold.
var rp = (TryllRetrieveParams)agentComp.Agent.GetNodeParamsBaseline("knowledge").Clone();
rp.Threshold = 0.9f;
agentComp.Agent.ChangeParams("knowledge", rp);
// Turn on streaming.
var gp = (TryllGenerateParams)agentComp.Agent.GetNodeParamsBaseline("answer").Clone();
gp.Stream = true;
agentComp.Agent.ChangeParams("answer", gp);
#include "Generated/Nodes/TryllGenerateParams.h"
#include "Generated/Nodes/TryllNodeParamsFactory.h"
// Clone the baseline, change one field, send.
UTryllNodeParamsBase* Base = AgentComponent->GetNodeParamsBaseline(TEXT("answer"));
UTryllGenerateParams* P = Cast<UTryllGenerateParams>(
UTryllNodeParamsFactory::CloneParams(Base, this));
P->SystemPrompt = TEXT("You are a pirate.");
AgentComponent->ChangeParams(
TEXT("answer"), P,
[](const FTryllError& Err)
{
if (Err.Code == 0)
UE_LOG(LogTemp, Log, TEXT("Persona updated"));
else
UE_LOG(LogTemp, Warning, TEXT("Error %d: %s"), Err.Code, *Err.Message);
});
// Relax the retrieval threshold.
UTryllRetrieveParams* RP = Cast<UTryllRetrieveParams>(
UTryllNodeParamsFactory::CloneParams(
AgentComponent->GetNodeParamsBaseline(TEXT("knowledge")), this));
RP->Threshold = 0.9f;
AgentComponent->ChangeParams(TEXT("knowledge"), RP);
- Call Get Node Params Baseline on the
TryllAgentComponentto get the current params object for a node. - Call Clone Params (from
TryllNodeParamsFactory) on the result. - Set any fields on the cloned object.
- Call Change Params on the
TryllAgentComponentand bind On Param Changed to handle success or error.
The C++ client does not cache a baseline copy of authored params. Keep your
own copy of the params you last sent (or authored at CreateAgent) and
start each mutation from that local copy. Structural fields must match the
create-time values or the server returns ParamNotMutable.
#include <tryll/AgentProxy.h>
// XxxT PODs come from the flatc-generated messages_generated.h (--gen-object-api)
#include <NodeParams_generated.h>
using namespace Tryll::NodeParams;
using namespace Tryll::Client;
// Clone the caller-owned baseline, change system prompt, send asynchronously.
GenerateParamsT p = answerBaseline; // caller-owned copy
p.system_prompt = "You are a pirate.";
agent.ChangeParamsAsync("answer", std::move(p)).get(); // or .then(...)
// Relax the retrieval threshold (synchronous).
RetrieveParamsT rp = knowledgeBaseline; // caller-owned copy
rp.threshold = 0.9f;
agent.ChangeParams("knowledge", std::move(rp));
The Python client does not cache a baseline copy of authored params. Keep
your own copy of the params you last sent (or authored at create_agent)
and start each mutation from a deepcopy of that local copy.
from copy import deepcopy
from tryll_client.graph import GenerateParams, RetrieveParams
# Clone the caller-owned baseline, change system_prompt, send.
p = deepcopy(answer_baseline) # caller-owned copy
p.system_prompt = "You are a pirate."
agent.change_params("answer", p)
# Relax the retrieval threshold.
rp = deepcopy(knowledge_baseline)
rp.threshold = 0.9
agent.change_params("knowledge", rp)
# Turn on streaming.
gp = deepcopy(answer_baseline)
gp.stream = True
agent.change_params("answer", gp)
change_params raises a TryllError on failure (unknown node, structural
field changed, or invalid value).
system_prompt and KV-cache rewind¶
Changing system_prompt does not immediately flush the KV cache.
The change is stored and picked up on the next send_message call.
At that point the projection pipeline detects the new token sequence,
trims the stale tail of the cache, and re-decodes only the changed
prefix. This is the most efficient path: back-to-back change_params /
send_message calls impose only one re-decode, not two.
Error codes¶
| Code | Name | Cause |
|---|---|---|
3004 |
AgentBusy |
A turn is in progress. Wait for TurnComplete before calling ChangeParams. |
3005 |
UnknownNode |
node_name does not match any node instance name in the graph. |
3006 |
ParamNotMutable |
A structural field was included in the mutation request. |
3007 |
InvalidParamValue |
A mutable field failed schema-range or node-specific validation. |