use super::host::CapabilityListing;
use super::types::{RlmPolicy, RlmTemplate, TemplateSpec};
use crate::error::{Result, TinyAgentsError};
const LOOP_CONTRACT: &str = r#"You are operating a sandboxed code notebook. Every reply MUST contain exactly
one fenced code block, opened with ```{{language}} and closed with ``` — code
outside a fence is never executed. The host executes the block and shows you
the captured output, the cell's value, and any error; variables persist
between cells. Keep cells small and observe intermediate results
instead of writing one giant script. When you have the answer, call
final_answer("...") from code. Never fabricate outputs you have not observed.
{{usage}}
Available capabilities:
{{capabilities}}
Resource limits (exceeding them aborts the run):
{{limits}}"#;
pub fn general() -> RlmTemplate {
RlmTemplate {
name: "general".to_string(),
system_prompt: format!(
"{LOOP_CONTRACT}\n\nSolve the user's task with code. Use sub-LLM calls for fuzzy \
subproblems (summarization, extraction, judgment) and plain code for exact ones \
(counting, filtering, arithmetic)."
),
}
}
pub fn context_explorer() -> RlmTemplate {
RlmTemplate {
name: "context-explorer".to_string(),
system_prompt: format!(
"{LOOP_CONTRACT}\n\nA variable named `context` holds material that is too large to \
read in one glance. NEVER print all of it. Probe it programmatically: inspect its \
length and structure first, then slice/search it, and delegate fuzzy analysis of \
individual chunks to sub-LLM calls (`llm`). Combine the per-chunk findings with \
code, then answer."
),
}
}
pub fn orchestrator() -> RlmTemplate {
RlmTemplate {
name: "orchestrator".to_string(),
system_prompt: format!(
"{LOOP_CONTRACT}\n\nYou are an orchestrator. Decompose the task into independent \
pieces, delegate each to the most suitable registered agent with `agent(name, \
input)`, inspect their replies, iterate if a piece came back weak, and synthesize \
the final answer yourself."
),
}
}
pub fn resolve(spec: &TemplateSpec) -> Result<RlmTemplate> {
match spec {
TemplateSpec::Inline(template) => Ok(template.clone()),
TemplateSpec::Named(name) => match name.as_str() {
"general" => Ok(general()),
"context-explorer" => Ok(context_explorer()),
"orchestrator" => Ok(orchestrator()),
other => Err(TinyAgentsError::Validation(format!(
"unknown rlm template `{other}` (built-ins: general, context-explorer, \
orchestrator)"
))),
},
}
}
pub fn render_system_prompt(
template: &RlmTemplate,
language: &str,
usage: &str,
capabilities: &CapabilityListing,
policy: &RlmPolicy,
) -> String {
template
.system_prompt
.replace("{{language}}", language)
.replace("{{usage}}", usage)
.replace("{{capabilities}}", &render_capabilities(capabilities))
.replace("{{limits}}", &render_limits(policy))
}
fn render_capabilities(listing: &CapabilityListing) -> String {
let mut out = String::new();
if listing.models.is_empty() {
out.push_str("- models: (none registered)\n");
} else {
out.push_str(&format!("- models: {}\n", listing.models.join(", ")));
}
if listing.tools.is_empty() {
out.push_str("- tools: (none registered)\n");
} else {
out.push_str("- tools:\n");
for (name, description) in &listing.tools {
if description.is_empty() {
out.push_str(&format!(" - {name}\n"));
} else {
out.push_str(&format!(" - {name}: {description}\n"));
}
}
}
if listing.agents.is_empty() {
out.push_str("- agents: (none registered)");
} else {
out.push_str(&format!("- agents: {}", listing.agents.join(", ")));
}
out
}
fn render_limits(policy: &RlmPolicy) -> String {
let timeout = policy
.cell_timeout
.map(|t| format!("{}s", t.as_secs()))
.unwrap_or_else(|| "none".to_string());
format!(
"- max cells: {}\n- max sub-LLM calls: {}\n- max tool calls: {}\n- max agent calls: \
{}\n- per-cell timeout: {timeout}",
policy.max_cells, policy.max_llm_calls, policy.max_tool_calls, policy.max_agent_calls
)
}