refactor(tools): rename rlm_process → rlm, rlm_query → parallel_fanout
Two top-level tools shared the rlm_ prefix but did completely different things — rlm_query was a flat parallel-completion fan-out wearing an RLM-shaped name, and rlm_process was the actual recursive language model. The overlap was the source of the "our rlm query is completely wrong" confusion. rlm_process → rlm # single, honest name for the recursive tool rlm_query → parallel_fanout # honest name for the flat fanout Internal renames follow: Op::RlmQuery → Op::Rlm AppAction::RlmQuery → AppAction::Rlm handle_rlm_query → handle_rlm RlmProcessTool → RlmTool RlmQueryTool → ParallelFanoutTool RlmChildClient → FanoutChildClient with_rlm_process_tool → with_rlm_tool with_rlm_query_tool → with_parallel_fanout_tool The REPL helpers `rlm_query` / `rlm_query_batched` / `llm_query` / `llm_query_batched` keep their names — those are correctly named (they ARE recursive within the REPL) and the model knows them from the system prompt and metadata. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -464,7 +464,7 @@ pub fn rlm(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
child_model,
|
||||
max_depth,
|
||||
),
|
||||
AppAction::RlmQuery {
|
||||
AppAction::Rlm {
|
||||
prompt,
|
||||
model,
|
||||
child_model,
|
||||
|
||||
@@ -412,8 +412,8 @@ fn should_default_defer_tool(name: &str, mode: AppMode) -> bool {
|
||||
| "grep_files"
|
||||
| "file_search"
|
||||
| "diagnostics"
|
||||
| "rlm_query"
|
||||
| "rlm_process"
|
||||
| "parallel_fanout"
|
||||
| "rlm"
|
||||
| MULTI_TOOL_PARALLEL_NAME
|
||||
| "update_plan"
|
||||
| "todo_write"
|
||||
@@ -1312,13 +1312,13 @@ impl Engine {
|
||||
Op::CompactContext => {
|
||||
self.handle_manual_compaction().await;
|
||||
}
|
||||
Op::RlmQuery {
|
||||
Op::Rlm {
|
||||
content,
|
||||
model,
|
||||
child_model,
|
||||
max_depth,
|
||||
} => {
|
||||
self.handle_rlm_query(content, model, child_model, max_depth)
|
||||
self.handle_rlm(content, model, child_model, max_depth)
|
||||
.await;
|
||||
}
|
||||
Op::Shutdown => {
|
||||
@@ -1450,8 +1450,8 @@ impl Engine {
|
||||
|
||||
builder = builder
|
||||
.with_review_tool(self.deepseek_client.clone(), self.session.model.clone())
|
||||
.with_rlm_query_tool(self.deepseek_client.clone())
|
||||
.with_rlm_process_tool(self.deepseek_client.clone(), self.session.model.clone())
|
||||
.with_parallel_fanout_tool(self.deepseek_client.clone())
|
||||
.with_rlm_tool(self.deepseek_client.clone(), self.session.model.clone())
|
||||
.with_user_input_tool()
|
||||
.with_parallel_tool();
|
||||
|
||||
@@ -1663,7 +1663,7 @@ impl Engine {
|
||||
/// only sees metadata about the REPL state, never the prompt text
|
||||
/// directly. The model generates Python code, which is executed by
|
||||
/// the REPL. When FINAL() is called, the loop ends.
|
||||
async fn handle_rlm_query(
|
||||
async fn handle_rlm(
|
||||
&mut self,
|
||||
content: String,
|
||||
model: String,
|
||||
|
||||
@@ -67,8 +67,8 @@ pub enum Op {
|
||||
|
||||
/// Run a Recursive Language Model (RLM) turn per Algorithm 1 of
|
||||
/// Zhang et al. (arXiv:2512.24601). The prompt is stored in the REPL
|
||||
/// as the `PROMPT` variable; the root LLM only sees metadata.
|
||||
RlmQuery {
|
||||
/// as `context`; the root LLM only sees metadata.
|
||||
Rlm {
|
||||
/// The user's prompt — stored in REPL, NOT in the LLM context.
|
||||
content: String,
|
||||
/// The model to use for root LLM calls.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Mode: agent
|
||||
|
||||
Read-only tools (reads, searches, `rlm_query`, agent status queries, git inspection) run silently.
|
||||
Read-only tools (reads, searches, `parallel_fanout`, agent status queries, git inspection) run silently.
|
||||
Any write, patch, shell execution, sub-agent spawn, or CSV batch operation will ask for approval first.
|
||||
|
||||
Before requesting approval for writes, lay out your work with `todo_write` so the user can see what
|
||||
|
||||
@@ -9,7 +9,7 @@ Your default workflow for any non-trivial request:
|
||||
2. **Execute** — work through each todo, updating status as you go.
|
||||
3. **For complex initiatives**, layer `update_plan` (high-level strategy) above `todo_write` (granular steps).
|
||||
4. **For parallel work**, spawn sub-agents (`agent_spawn` / `agent_swarm`) — each does one thing well. Link them to plan/todo items in your thinking.
|
||||
5. **For LM-only fan-out** (summarization, classification, analysis across many items), use `rlm_query` for fast parallel inference.
|
||||
5. **For LM-only fan-out** (summarization, classification, analysis across many items), use `parallel_fanout` for fast parallel inference.
|
||||
6. **For persistent cross-session memory**, use `note` sparingly for important decisions, open blockers, and architectural context.
|
||||
|
||||
**Key principle**: make your work visible. The sidebar shows Plan / Todos / Tasks / Agents. When these panels are empty, the user has no idea what you're doing. Keep them populated.
|
||||
@@ -28,7 +28,7 @@ Model notes: DeepSeek V4 models emit *thinking tokens* (`ContentBlock::Thinking`
|
||||
- **Git / diag / tests**: `git_status`, `git_diff`, `git_show`, `git_log`, `git_blame`, `diagnostics`, `run_tests`, `review`.
|
||||
- **Sub-agents**: `agent_spawn` (`spawn_agent`, `delegate_to_agent`), `agent_swarm`, `agent_result`, `agent_cancel` (`close_agent`), `agent_list`, `agent_wait` (`wait`), `agent_send_input` (`send_input`), `agent_assign` (`assign_agent`), `resume_agent`.
|
||||
- **CSV batch**: `spawn_agents_on_csv`, `report_agent_job_result`.
|
||||
- **LM fan-out**: `rlm_query` — `prompts: [...]` runs up to 16 children on the fast cheap model concurrently. Read-only.
|
||||
- **LM fan-out**: `parallel_fanout` — `prompts: [...]` runs up to 16 children on the fast cheap model concurrently. Read-only.
|
||||
- **Other**: `code_execution` (Python sandbox), `validate_data` (JSON/TOML), `request_user_input`, `finance` (market quotes), `tool_search_tool_regex`, `tool_search_tool_bm25` (deferred tool discovery).
|
||||
|
||||
Multiple `tool_calls` in one turn run in parallel. `web_search` returns `ref_id`s — cite as `(ref_id)`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Mode: normal
|
||||
|
||||
Reads and `rlm_query` run silently. Writes, patches, and shell commands ask for approval.
|
||||
Reads and `parallel_fanout` run silently. Writes, patches, and shell commands ask for approval.
|
||||
|
||||
Before requesting writes, use `todo_write` to outline your approach — visible plans build trust.
|
||||
For complex work, layer `update_plan` (strategy) above `todo_write` (tactics).
|
||||
@@ -14,8 +14,8 @@ pub mod plan;
|
||||
pub mod project;
|
||||
pub mod registry;
|
||||
pub mod review;
|
||||
pub mod rlm_process;
|
||||
pub mod rlm_query;
|
||||
pub mod rlm;
|
||||
pub mod parallel_fanout;
|
||||
pub mod search;
|
||||
pub mod shell;
|
||||
mod shell_output;
|
||||
|
||||
@@ -34,7 +34,7 @@ const MAX_PARALLEL: usize = 16;
|
||||
const DEFAULT_CHILD_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RlmChildClient — dyn-compatible wrapper around LLM completion.
|
||||
// FanoutChildClient — dyn-compatible wrapper around LLM completion.
|
||||
//
|
||||
// The workspace's `LlmClient` trait uses native `async fn`, which is not dyn
|
||||
// compatible in stable Rust (RPITIT vtable limitations). We define a small
|
||||
@@ -47,13 +47,13 @@ const DEFAULT_CHILD_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
/// operation. `#[async_trait]` desugars the async method into a boxed future
|
||||
/// so the trait is object-safe.
|
||||
#[async_trait]
|
||||
pub(crate) trait RlmChildClient: Send + Sync {
|
||||
pub(crate) trait FanoutChildClient: Send + Sync {
|
||||
async fn complete(&self, request: MessageRequest) -> anyhow::Result<MessageResponse>;
|
||||
}
|
||||
|
||||
/// Blanket impl: any `DeepSeekClient` is a valid child client.
|
||||
#[async_trait]
|
||||
impl RlmChildClient for DeepSeekClient {
|
||||
impl FanoutChildClient for DeepSeekClient {
|
||||
async fn complete(&self, request: MessageRequest) -> anyhow::Result<MessageResponse> {
|
||||
self.create_message(request).await
|
||||
}
|
||||
@@ -61,28 +61,28 @@ impl RlmChildClient for DeepSeekClient {
|
||||
|
||||
/// Tool: `rlm_query`. Runs one or more prompts in parallel and joins the
|
||||
/// results. Structured tool call so the model can trigger fan-out reliably.
|
||||
pub struct RlmQueryTool {
|
||||
/// Boxed child client — `Arc<dyn RlmChildClient>` lets tests inject a
|
||||
pub struct ParallelFanoutTool {
|
||||
/// Boxed child client — `Arc<dyn FanoutChildClient>` lets tests inject a
|
||||
/// mock without going through a real HTTP connection. `None` when no API
|
||||
/// key is configured.
|
||||
client: Option<Arc<dyn RlmChildClient>>,
|
||||
client: Option<Arc<dyn FanoutChildClient>>,
|
||||
default_model: String,
|
||||
}
|
||||
|
||||
impl RlmQueryTool {
|
||||
impl ParallelFanoutTool {
|
||||
/// Construct with a concrete `DeepSeekClient` (production path).
|
||||
#[must_use]
|
||||
pub fn new(client: Option<DeepSeekClient>) -> Self {
|
||||
Self {
|
||||
client: client.map(|c| Arc::new(c) as Arc<dyn RlmChildClient>),
|
||||
client: client.map(|c| Arc::new(c) as Arc<dyn FanoutChildClient>),
|
||||
default_model: DEFAULT_CHILD_MODEL.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct with a pre-boxed `RlmChildClient` — used by tests to inject
|
||||
/// Construct with a pre-boxed `FanoutChildClient` — used by tests to inject
|
||||
/// a `MockRlmClient` without an active API connection.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new_with_arc(client: Option<Arc<dyn RlmChildClient>>) -> Self {
|
||||
pub(crate) fn new_with_arc(client: Option<Arc<dyn FanoutChildClient>>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
default_model: DEFAULT_CHILD_MODEL.to_string(),
|
||||
@@ -91,9 +91,9 @@ impl RlmQueryTool {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ToolSpec for RlmQueryTool {
|
||||
impl ToolSpec for ParallelFanoutTool {
|
||||
fn name(&self) -> &'static str {
|
||||
"rlm_query"
|
||||
"parallel_fanout"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
@@ -178,16 +178,16 @@ impl ToolSpec for RlmQueryTool {
|
||||
};
|
||||
|
||||
if prompts.is_empty() {
|
||||
return Err(ToolError::invalid_input("rlm_query: prompts list is empty"));
|
||||
return Err(ToolError::invalid_input("parallel_fanout: prompts list is empty"));
|
||||
}
|
||||
if prompts.len() > MAX_PARALLEL {
|
||||
return Err(ToolError::invalid_input(format!(
|
||||
"rlm_query: too many prompts ({}, max {MAX_PARALLEL})",
|
||||
"parallel_fanout: too many prompts ({}, max {MAX_PARALLEL})",
|
||||
prompts.len(),
|
||||
)));
|
||||
}
|
||||
|
||||
// client is already Arc<dyn RlmChildClient> — clone the Arc, not the client.
|
||||
// client is already Arc<dyn FanoutChildClient> — clone the Arc, not the client.
|
||||
let model = Arc::new(model);
|
||||
let system = Arc::new(system);
|
||||
let total = prompts.len();
|
||||
@@ -211,7 +211,7 @@ impl ToolSpec for RlmQueryTool {
|
||||
peak.fetch_max(now, Ordering::Relaxed);
|
||||
debug!(
|
||||
target: "deepseek_cli::tools",
|
||||
tool = "rlm_query",
|
||||
tool = "parallel_fanout",
|
||||
idx,
|
||||
in_flight = now,
|
||||
"child request start"
|
||||
@@ -260,7 +260,7 @@ impl ToolSpec for RlmQueryTool {
|
||||
|
||||
debug!(
|
||||
target: "deepseek_cli::tools",
|
||||
tool = "rlm_query",
|
||||
tool = "parallel_fanout",
|
||||
idx,
|
||||
elapsed_ms,
|
||||
ok = response.is_ok(),
|
||||
@@ -277,7 +277,7 @@ impl ToolSpec for RlmQueryTool {
|
||||
let dispatch_elapsed_ms = dispatch_started.elapsed().as_millis() as u64;
|
||||
debug!(
|
||||
target: "deepseek_cli::tools",
|
||||
tool = "rlm_query",
|
||||
tool = "parallel_fanout",
|
||||
total,
|
||||
peak = peak.load(Ordering::Relaxed),
|
||||
dispatch_elapsed_ms,
|
||||
@@ -372,8 +372,8 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn tool_without_client() -> RlmQueryTool {
|
||||
RlmQueryTool::new(None)
|
||||
fn tool_without_client() -> ParallelFanoutTool {
|
||||
ParallelFanoutTool::new(None)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -402,7 +402,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RlmChildClient for MockRlmClient {
|
||||
impl FanoutChildClient for MockRlmClient {
|
||||
async fn complete(&self, request: MessageRequest) -> anyhow::Result<MessageResponse> {
|
||||
// Record start time before sleeping.
|
||||
self.start_times.lock().unwrap().push(Instant::now());
|
||||
@@ -459,7 +459,7 @@ mod tests {
|
||||
let mock = Arc::new(MockRlmClient::new(delay));
|
||||
let start_times_ref = Arc::clone(&mock.start_times);
|
||||
|
||||
let tool = RlmQueryTool::new_with_arc(Some(mock as Arc<dyn RlmChildClient>));
|
||||
let tool = ParallelFanoutTool::new_with_arc(Some(mock as Arc<dyn FanoutChildClient>));
|
||||
let prompts: Vec<&str> = vec!["a", "b", "c", "d"];
|
||||
|
||||
let overall_start = Instant::now();
|
||||
@@ -511,7 +511,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn rlm_single_prompt_returns_plain_text() {
|
||||
let mock = Arc::new(MockRlmClient::new(std::time::Duration::from_millis(1)));
|
||||
let tool = RlmQueryTool::new_with_arc(Some(mock as Arc<dyn RlmChildClient>));
|
||||
let tool = ParallelFanoutTool::new_with_arc(Some(mock as Arc<dyn FanoutChildClient>));
|
||||
|
||||
let result = tool
|
||||
.execute(json!({ "prompt": "hello" }), &ctx())
|
||||
@@ -527,7 +527,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn rlm_multi_prompt_returns_indexed_blocks() {
|
||||
let mock = Arc::new(MockRlmClient::new(std::time::Duration::from_millis(1)));
|
||||
let tool = RlmQueryTool::new_with_arc(Some(mock as Arc<dyn RlmChildClient>));
|
||||
let tool = ParallelFanoutTool::new_with_arc(Some(mock as Arc<dyn FanoutChildClient>));
|
||||
|
||||
let result = tool
|
||||
.execute(json!({ "prompts": ["alpha", "beta"] }), &ctx())
|
||||
@@ -384,18 +384,18 @@ impl ToolRegistryBuilder {
|
||||
/// Include the native RLM tool (`rlm_query`). Parallel/batched LLM
|
||||
/// fan-out runs through the existing DeepSeek client.
|
||||
#[must_use]
|
||||
pub fn with_rlm_query_tool(self, client: Option<DeepSeekClient>) -> Self {
|
||||
use super::rlm_query::RlmQueryTool;
|
||||
self.with_tool(Arc::new(RlmQueryTool::new(client)))
|
||||
pub fn with_parallel_fanout_tool(self, client: Option<DeepSeekClient>) -> Self {
|
||||
use super::parallel_fanout::ParallelFanoutTool;
|
||||
self.with_tool(Arc::new(ParallelFanoutTool::new(client)))
|
||||
}
|
||||
|
||||
/// Include the heavy-lift RLM tool (`rlm_process`). Runs the full
|
||||
/// recursive language-model loop on a long input (file or inline
|
||||
/// content); the long input never enters the calling model's context.
|
||||
#[must_use]
|
||||
pub fn with_rlm_process_tool(self, client: Option<DeepSeekClient>, root_model: String) -> Self {
|
||||
use super::rlm_process::RlmProcessTool;
|
||||
self.with_tool(Arc::new(RlmProcessTool::new(client, root_model)))
|
||||
pub fn with_rlm_tool(self, client: Option<DeepSeekClient>, root_model: String) -> Self {
|
||||
use super::rlm::RlmTool;
|
||||
self.with_tool(Arc::new(RlmTool::new(client, root_model)))
|
||||
}
|
||||
|
||||
/// Include the review tool.
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_MAX_DEPTH: u32 = 1;
|
||||
/// context in the first place.
|
||||
const MAX_INLINE_CONTENT_CHARS: usize = 200_000;
|
||||
|
||||
pub struct RlmProcessTool {
|
||||
pub struct RlmTool {
|
||||
/// Production HTTP client. `None` when no API key is configured.
|
||||
client: Option<DeepSeekClient>,
|
||||
/// Root model to drive the RLM loop. Set at registration time; matches
|
||||
@@ -39,7 +39,7 @@ pub struct RlmProcessTool {
|
||||
root_model: String,
|
||||
}
|
||||
|
||||
impl RlmProcessTool {
|
||||
impl RlmTool {
|
||||
#[must_use]
|
||||
pub fn new(client: Option<DeepSeekClient>, root_model: String) -> Self {
|
||||
Self { client, root_model }
|
||||
@@ -47,9 +47,9 @@ impl RlmProcessTool {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ToolSpec for RlmProcessTool {
|
||||
impl ToolSpec for RlmTool {
|
||||
fn name(&self) -> &'static str {
|
||||
"rlm_process"
|
||||
"rlm"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
@@ -101,7 +101,7 @@ impl ToolSpec for RlmProcessTool {
|
||||
}
|
||||
|
||||
fn approval_requirement(&self) -> ApprovalRequirement {
|
||||
// Same level as rlm_query: the model decided to invoke this, the
|
||||
// Same level as parallel_fanout: the model decided to invoke this, the
|
||||
// user already enabled tools by being in Agent/YOLO mode, and
|
||||
// every concrete side-effect (file read, LLM call) is bounded.
|
||||
ApprovalRequirement::Auto
|
||||
@@ -128,7 +128,7 @@ impl ToolSpec for RlmProcessTool {
|
||||
})?
|
||||
.trim();
|
||||
if task.is_empty() {
|
||||
return Err(ToolError::invalid_input("rlm_process: `task` is empty"));
|
||||
return Err(ToolError::invalid_input("rlm: `task` is empty"));
|
||||
}
|
||||
|
||||
let file_path = input.get("file_path").and_then(|v| v.as_str());
|
||||
@@ -137,12 +137,12 @@ impl ToolSpec for RlmProcessTool {
|
||||
let body = match (file_path, content) {
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(ToolError::invalid_input(
|
||||
"rlm_process: pass `file_path` OR `content`, not both",
|
||||
"rlm: pass `file_path` OR `content`, not both",
|
||||
));
|
||||
}
|
||||
(None, None) => {
|
||||
return Err(ToolError::invalid_input(
|
||||
"rlm_process: requires `file_path` (preferred) or `content`",
|
||||
"rlm: requires `file_path` (preferred) or `content`",
|
||||
));
|
||||
}
|
||||
(Some(path), None) => {
|
||||
@@ -156,7 +156,7 @@ impl ToolSpec for RlmProcessTool {
|
||||
(None, Some(c)) => {
|
||||
if c.chars().count() > MAX_INLINE_CONTENT_CHARS {
|
||||
return Err(ToolError::invalid_input(format!(
|
||||
"rlm_process: inline `content` is {} chars (cap {MAX_INLINE_CONTENT_CHARS}). Pass `file_path` for larger inputs.",
|
||||
"rlm: inline `content` is {} chars (cap {MAX_INLINE_CONTENT_CHARS}). Pass `file_path` for larger inputs.",
|
||||
c.chars().count()
|
||||
)));
|
||||
}
|
||||
@@ -166,7 +166,7 @@ impl ToolSpec for RlmProcessTool {
|
||||
|
||||
if body.trim().is_empty() {
|
||||
return Err(ToolError::invalid_input(
|
||||
"rlm_process: input is empty after loading",
|
||||
"rlm: input is empty after loading",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ impl ToolSpec for RlmProcessTool {
|
||||
if let Some(err) = result.error {
|
||||
return Err(ToolError::ExecutionFailed {
|
||||
message: format!(
|
||||
"rlm_process: {err} (iterations={}, termination={:?})",
|
||||
"rlm: {err} (iterations={}, termination={:?})",
|
||||
result.iterations, result.termination
|
||||
),
|
||||
});
|
||||
@@ -217,7 +217,7 @@ impl ToolSpec for RlmProcessTool {
|
||||
if result.answer.trim().is_empty() {
|
||||
return Err(ToolError::ExecutionFailed {
|
||||
message: format!(
|
||||
"rlm_process: empty answer (termination={:?}, iterations={})",
|
||||
"rlm: empty answer (termination={:?}, iterations={})",
|
||||
result.termination, result.iterations
|
||||
),
|
||||
});
|
||||
@@ -303,8 +303,8 @@ impl ToolSpec for RlmProcessTool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn tool() -> RlmProcessTool {
|
||||
RlmProcessTool::new(None, "deepseek-v4-pro".to_string())
|
||||
fn tool() -> RlmTool {
|
||||
RlmTool::new(None, "deepseek-v4-pro".to_string())
|
||||
}
|
||||
|
||||
fn ctx() -> ToolContext {
|
||||
@@ -321,7 +321,7 @@ mod tests {
|
||||
#[test]
|
||||
fn name_and_schema() {
|
||||
let t = tool();
|
||||
assert_eq!(t.name(), "rlm_process");
|
||||
assert_eq!(t.name(), "rlm");
|
||||
let schema = t.input_schema();
|
||||
assert!(schema["properties"]["task"].is_object());
|
||||
assert!(schema["properties"]["file_path"].is_object());
|
||||
@@ -362,7 +362,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_missing_task() {
|
||||
let t = RlmProcessTool::new(None, "x".into());
|
||||
let t = RlmTool::new(None, "x".into());
|
||||
let ctx = ctx();
|
||||
let res = t
|
||||
.execute(json!({"content": "abc"}), &ctx)
|
||||
@@ -1927,7 +1927,7 @@ pub enum AppAction {
|
||||
/// Run a Recursive Language Model (RLM) turn — Algorithm 1 from
|
||||
/// Zhang et al. (arXiv:2512.24601). The prompt is stored in the REPL;
|
||||
/// the root LLM only sees metadata.
|
||||
RlmQuery {
|
||||
Rlm {
|
||||
/// The user's prompt — stored in REPL, NOT in LLM context.
|
||||
prompt: String,
|
||||
/// Model for the root LLM.
|
||||
|
||||
@@ -2229,7 +2229,7 @@ mod tests {
|
||||
// its own row instead of the inline `args:` summary so the user can
|
||||
// read what each child was asked.
|
||||
let cell = HistoryCell::Tool(ToolCell::Generic(GenericToolCell {
|
||||
name: "rlm_query".to_string(),
|
||||
name: "parallel_fanout".to_string(),
|
||||
status: ToolStatus::Running,
|
||||
input_summary: Some("prompts: <3 items>".to_string()),
|
||||
output: None,
|
||||
|
||||
@@ -2337,7 +2337,7 @@ async fn apply_command_result(
|
||||
let queued = build_queued_message(app, content);
|
||||
submit_or_steer_message(app, engine_handle, queued).await?;
|
||||
}
|
||||
AppAction::RlmQuery {
|
||||
AppAction::Rlm {
|
||||
prompt,
|
||||
model,
|
||||
child_model,
|
||||
@@ -2345,7 +2345,7 @@ async fn apply_command_result(
|
||||
} => {
|
||||
app.status_message = Some("RLM turn starting...".to_string());
|
||||
let _ = engine_handle
|
||||
.send(Op::RlmQuery {
|
||||
.send(Op::Rlm {
|
||||
content: prompt,
|
||||
model,
|
||||
child_model,
|
||||
@@ -4610,7 +4610,7 @@ fn handle_tool_call_started(app: &mut App, id: &str, name: &str, input: &serde_j
|
||||
/// user can read what each child was asked. Returns `None` for tools that
|
||||
/// don't expose a prompt list.
|
||||
fn extract_fanout_prompts(name: &str, input: &serde_json::Value) -> Option<Vec<String>> {
|
||||
if name != "rlm_query" {
|
||||
if name != "parallel_fanout" {
|
||||
return None;
|
||||
}
|
||||
if let Some(arr) = input.get("prompts").and_then(|v| v.as_array()) {
|
||||
|
||||
@@ -1862,7 +1862,7 @@ fn rlm_query_tool_cell_wired_with_prompts_on_start() {
|
||||
handle_tool_call_started(
|
||||
&mut app,
|
||||
"rlm-1",
|
||||
"rlm_query",
|
||||
"parallel_fanout",
|
||||
&serde_json::json!({
|
||||
"prompts": [
|
||||
"What is the capital of France?",
|
||||
@@ -1878,7 +1878,7 @@ fn rlm_query_tool_cell_wired_with_prompts_on_start() {
|
||||
panic!("expected GenericToolCell for rlm_query");
|
||||
};
|
||||
|
||||
assert_eq!(generic.name, "rlm_query");
|
||||
assert_eq!(generic.name, "parallel_fanout");
|
||||
assert_eq!(generic.status, ToolStatus::Running);
|
||||
|
||||
// Core assertion: prompts populated from the JSON input.
|
||||
@@ -1902,7 +1902,7 @@ fn rlm_query_singular_prompt_wired_as_single_element_vec() {
|
||||
handle_tool_call_started(
|
||||
&mut app,
|
||||
"rlm-2",
|
||||
"rlm_query",
|
||||
"parallel_fanout",
|
||||
&serde_json::json!({ "prompt": "Explain the engine loop" }),
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Product Catalog - Winter 2025
|
||||
|
||||
## Electronics
|
||||
|
||||
### SKU-001: QuantumBook Pro Laptop
|
||||
- Price: $1,299.99
|
||||
- Category: Computing
|
||||
- Stock: 45 units
|
||||
- Rating: 4.7/5
|
||||
- Features: 16" 4K display, 32GB RAM, 1TB SSD, AI accelerator
|
||||
- Warranty: 2 years
|
||||
- Supplier: TechGlobal Inc.
|
||||
|
||||
### SKU-002: SmartWatch X5
|
||||
- Price: $349.99
|
||||
- Category: Wearables
|
||||
- Stock: 120 units
|
||||
- Rating: 4.3/5
|
||||
- Features: Heart rate, ECG, GPS, 7-day battery
|
||||
- Warranty: 1 year
|
||||
- Supplier: GadgetWorld Ltd.
|
||||
|
||||
### SKU-003: NoiseCancel Pro Headphones
|
||||
- Price: $199.99
|
||||
- Category: Audio
|
||||
- Stock: 8 units (LOW STOCK)
|
||||
- Rating: 4.9/5
|
||||
- Features: ANC, 40hr battery, spatial audio
|
||||
- Warranty: 1 year
|
||||
- Supplier: AudioTech Corp.
|
||||
|
||||
## Home & Kitchen
|
||||
|
||||
### SKU-004: SmartBrew Coffee Maker
|
||||
- Price: $89.99
|
||||
- Category: Kitchen Appliances
|
||||
- Stock: 200 units
|
||||
- Rating: 4.5/5
|
||||
- Features: App-controlled, 12-cup, thermal carafe
|
||||
- Warranty: 2 years
|
||||
- Supplier: HomeEssentials LLC
|
||||
|
||||
### SKU-005: AeroChef Air Fryer
|
||||
- Price: $129.99
|
||||
- Category: Kitchen Appliances
|
||||
- Stock: 75 units
|
||||
- Rating: 4.6/5
|
||||
- Features: 8qt capacity, 10 presets, dishwasher safe
|
||||
- Warranty: 1 year
|
||||
- Supplier: HomeEssentials LLC
|
||||
|
||||
### SKU-006: PureFlow Water Filter
|
||||
- Price: $49.99
|
||||
- Category: Kitchen Accessories
|
||||
- Stock: 0 units (OUT OF STOCK)
|
||||
- Rating: 4.4/5
|
||||
- Features: 3-stage filtration, 6-month filter life
|
||||
- Warranty: 1 year
|
||||
- Supplier: CleanWater Systems
|
||||
|
||||
## Sports & Outdoors
|
||||
|
||||
### SKU-007: TrailBlazer Hiking Boots
|
||||
- Price: $159.99
|
||||
- Category: Footwear
|
||||
- Stock: 60 units
|
||||
- Rating: 4.8/5
|
||||
- Features: Waterproof, Vibram sole, ankle support
|
||||
- Warranty: 1 year
|
||||
- Supplier: OutdoorGear Co.
|
||||
|
||||
### SKU-008: FlexCore Yoga Mat
|
||||
- Price: $39.99
|
||||
- Category: Fitness
|
||||
- Stock: 300 units
|
||||
- Rating: 4.2/5
|
||||
- Features: 6mm thick, non-slip, carrying strap
|
||||
- Warranty: 6 months
|
||||
- Supplier: FitLife Products
|
||||
|
||||
### SKU-009: PowerLift Adjustable Dumbbells
|
||||
- Price: $299.99
|
||||
- Category: Fitness
|
||||
- Stock: 15 units
|
||||
- Rating: 4.7/5
|
||||
- Features: 5-52.5lbs range, quick-change, storage tray
|
||||
- Warranty: 2 years
|
||||
- Supplier: FitLife Products
|
||||
|
||||
## Automotive
|
||||
|
||||
### SKU-010: DashCam Ultra 4K
|
||||
- Price: $179.99
|
||||
- Category: Car Electronics
|
||||
- Stock: 35 units
|
||||
- Rating: 4.5/5
|
||||
- Features: 4K recording, night vision, GPS, parking mode
|
||||
- Warranty: 1 year
|
||||
- Supplier: AutoGadget Inc.
|
||||
@@ -0,0 +1,51 @@
|
||||
This is a test document for rlm_process.
|
||||
|
||||
# Security Model Overview
|
||||
|
||||
The system uses a zero-trust architecture where every request is authenticated and authorized independently.
|
||||
|
||||
## Authentication
|
||||
|
||||
Authentication is handled via JWT tokens with a 15-minute expiry. Refresh tokens are stored in an HTTP-only cookie.
|
||||
|
||||
## Authorization
|
||||
|
||||
Role-based access control (RBAC) is used with three tiers:
|
||||
- Admin: full access
|
||||
- Editor: can modify content but not system settings
|
||||
- Viewer: read-only access
|
||||
|
||||
## Encryption
|
||||
|
||||
All data at rest is encrypted using AES-256-GCM. Data in transit uses TLS 1.3.
|
||||
|
||||
## Audit Logging
|
||||
|
||||
Every action is logged to an append-only audit trail stored in a separate database.
|
||||
|
||||
# API Endpoints
|
||||
|
||||
- GET /api/users - List users (Admin only)
|
||||
- POST /api/users - Create user (Admin only)
|
||||
- GET /api/documents - List documents
|
||||
- POST /api/documents - Create document (Admin, Editor)
|
||||
- PUT /api/documents/:id - Update document (Admin, Editor)
|
||||
- DELETE /api/documents/:id - Delete document (Admin only)
|
||||
|
||||
# Data Model
|
||||
|
||||
User {
|
||||
id: UUID
|
||||
email: String
|
||||
role: Enum(Admin, Editor, Viewer)
|
||||
created_at: Timestamp
|
||||
}
|
||||
|
||||
Document {
|
||||
id: UUID
|
||||
title: String
|
||||
content: Text
|
||||
author_id: UUID (FK -> User)
|
||||
created_at: Timestamp
|
||||
updated_at: Timestamp
|
||||
}
|
||||
Reference in New Issue
Block a user