Skip to content

Stabilize OpenClaw tool loops through MoA#808

Open
michaelneale wants to merge 26 commits into
mainfrom
fix/openclaw-moa-telegram-timeouts
Open

Stabilize OpenClaw tool loops through MoA#808
michaelneale wants to merge 26 commits into
mainfrom
fix/openclaw-moa-telegram-timeouts

Conversation

@michaelneale

Copy link
Copy Markdown
Collaborator

OpenClaw/Gateway users can now route Telegram-style agent turns through model=mesh without small-context workers or slow peers turning ordinary tool loops into timeouts.

What changed

  • Bound MoA worker/reducer context for OpenClaw-sized sessions and tool schemas.
  • Rank mesh workers by usable context and latency, with remote backend hedging.
  • Normalize OpenClaw flat tool schemas into OpenAI function tools.
  • Preserve tool-result loops and retry answer-only when native tool-schema reducers fail.
  • Emit direct native tool calls when a single safe required tool and arguments can be inferred, avoiding slow worker arbitration for obvious read/search-style requests.

Validation

  • cargo fmt --all -- --check
  • cargo test -p mesh-mixture-of-agents
  • cargo test -p mesh-llm-host-runtime --lib moa_gateway
  • just build
  • cargo clippy -p mesh-mixture-of-agents --all-targets -- -D warnings
  • cargo clippy -p mesh-llm --all-targets -- -D warnings

Lab proof on mini

  • Deployed debug binary hash 9c882b66a51409cd658334fbf8994d32472692ad to mini as ~/mesh-llm-main and ~/mesh-llm-fastjoin; OpenClaw Gateway remained running.
  • Confirmed /v1/models exposed mesh with context_length=131072 and local unsloth/Qwen3.5-9B-GGUF:Q4_K_M ready.
  • Reproduced the real failure class from existing OpenClaw logs: Telegram inbound agent:main:main turns timing out with FailoverError: LLM request timed out.
  • Verified no-delivery Telegram-channel OpenClaw turn on the real agent:main:main session: SENTINEL_OPENCLAW_TELEGRAM_MAIN_OK_606 in 5.65s.
  • Verified Telegram-channel read-tool loop on the real agent:main:main session: native read tool call executed and returned SENTINEL_FILE_VALUE_DIRECT_TOOL_606; final answer SENTINEL_OPENCLAW_TELEGRAM_DIRECT_TOOL_OK_606 SENTINEL_FILE_VALUE_DIRECT_TOOL_606 in 14.70s.
  • Session JSONL shows direct tool call emitted at 16:15:13.587, tool result at 16:15:13.610, final answer at 16:15:25.553.

Notes

  • I did not use --deliver; this exercised OpenClaw Gateway with --channel telegram --reply-channel telegram --reply-to telegram:8454832168 against the real Telegram session without sending a test message back to Telegram.
  • Current mini mesh process layout after validation: OpenClaw Gateway PID 18559, mesh serve PID 75938, mesh client PID 75939.

Copilot AI review requested due to automatic review settings June 6, 2026 06:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the MoA (Mixture-of-Agents) gateway for OpenClaw-style agent/tool loops by bounding context, improving worker selection (context + latency), normalizing incoming tool schemas into OpenAI function-tool form, and adding resilience paths when reducer tool grammar fails.

Changes:

  • Add tool-schema normalization in Session::ingest and introduce direct-tool short-circuiting when a single required tool/arguments can be inferred safely.
  • Bound worker/reducer context sizes with compaction and tighter history windows; add an answer-only tool-result fallback packing path.
  • Improve mesh worker backend selection (context/latency ranking) and add a hedged remote backend that retries across multiple peer candidates.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/mesh-mixture-of-agents/tests/sim_tool_result_routes_to_reducer.rs Adds coverage for tool-result reducer failure falling back to answer-only retry.
crates/mesh-mixture-of-agents/src/session.rs Normalizes flat tool schemas into OpenAI type:function tool format during ingest.
crates/mesh-mixture-of-agents/src/lib.rs Adds DirectTool turn kind, inferred required tool selection, improved tool arg inference, and tool-result reducer retry without native tools.
crates/mesh-mixture-of-agents/src/context.rs Bounds/compacts context windows and adds answer-only tool-result context packing.
crates/mesh-llm-host-runtime/src/network/openai/moa_gateway/remote_backend.rs Introduces a multi-peer hedged remote backend over QUIC HTTP tunnels.
crates/mesh-llm-host-runtime/src/network/openai/moa_gateway/mod.rs Integrates context budgeting, ranked worker selection, remote backend hedging, and updated MoA timeouts/grace.
crates/mesh-llm-host-runtime/src/network/openai/moa_gateway/context_selection.rs Expands remote selection to return ordered host lists and adds context/latency helpers.
crates/mesh-llm-host-runtime/src/network/openai/moa_gateway/context_budget.rs Adds MoA-specific required-token estimation + reserves for chat/tools/tool-results.

Comment on lines +1 to +4
use crate::mesh;
use mesh_mixture_of_agents as moa;

type RemoteJoinSet = tokio::task::JoinSet<(iroh::EndpointId, Result<serde_json::Value, String>)>;
Comment on lines +611 to +615
let marker = format!(
"\n\n[MoA compacted this message from {} chars. Middle content was omitted. \
The text after this notice is the preserved ending of the original message.]\n\n",
text.len()
);
Comment on lines +73 to +82
pub(in crate::network::openai) async fn best_remote_latency_ms(
node: &mesh::Node,
hosts: &[iroh::EndpointId],
) -> Option<u32> {
let latencies = remote_latency_map(node).await;
hosts
.iter()
.filter_map(|host| latencies.get(host).copied())
.min()
}
Comment on lines +23 to +28
const MOA_LOCAL_INFLIGHT_SOFT_LIMIT: u64 = 3;
const MOA_MAX_WORKERS: usize = 3;
const MOA_REMOTE_PEER_HEDGE_DELAY: std::time::Duration = std::time::Duration::from_secs(3);
const MOA_WORKER_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
const MOA_REDUCER_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
const MOA_FIRST_ANSWER_GRACE: std::time::Duration = std::time::Duration::from_secs(2);
@michaelneale

Copy link
Copy Markdown
Collaborator Author

🤖 Additional validation after commit 86037c8:

Fixed the remaining OpenClaw Telegram-shaped failure. The failed path was not Telegram transport: MoA was letting Minimax XML tool syntax leak as assistant text after repeated web_search results. The repeated-tool guard disabled all tools, so a legitimate web_fetch proposal was flattened into text and OpenClaw marked replayInvalid=true.

Changes added:

  • Parse Minimax <minimax:tool_call><invoke name="..."> XML into native ToolProposal output.
  • Keep the repeated-tool loop guard from calling the same tool again, but allow a different declared tool such as web_fetch.
  • Added regression coverage for three repeated web_search results followed by Minimax XML web_fetch.

Validation:

  • cargo fmt --all -- --check
  • cargo test -p mesh-mixture-of-agents
  • cargo test -p mesh-llm-host-runtime --lib moa_gateway
  • just build
  • cargo clippy -p mesh-mixture-of-agents --all-targets -- -D warnings
  • cargo clippy -p mesh-llm --all-targets -- -D warnings

Mini deploy/proof:

  • Deployed debug hash f5d89e1edb916a27a8391f27fe3d25b871676f38 to mini as mesh-llm-main and mesh-llm-fastjoin.
  • Real OpenClaw main Telegram-shaped session, model mesh/mesh, asked for recent important Mesh-LLM GitHub issues/PRs.
  • The run executed web_search then web_fetch, completed with replayInvalid=false, livenessState=working, toolSummary calls=4, tools=[web_search, web_fetch], and returned SENTINEL_OPENCLAW_GH_SCAN_608.
  • Follow-up in the same session picked Issue skipped branch prediction exploration #803 and returned SENTINEL_OPENCLAW_GH_FOLLOWUP_608.
  • Third recall turn returned 803 SENTINEL_OPENCLAW_GH_CONTEXT_608.

Copilot AI review requested due to automatic review settings June 6, 2026 07:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Comment thread crates/mesh-mixture-of-agents/src/lib.rs Outdated
Comment on lines +611 to +615
let marker = format!(
"\n\n[MoA compacted this message from {} chars. Middle content was omitted. \
The text after this notice is the preserved ending of the original message.]\n\n",
text.len()
);
Comment on lines +15 to +23
pub(in crate::network::openai) async fn select_remote_hosts(
node: &mesh::Node,
model: &str,
required_tokens: Option<u32>,
hosts: Vec<iroh::EndpointId>,
) -> Option<iroh::EndpointId> {
let Some(required_tokens) = required_tokens else {
return hosts.into_iter().next();
};

let mut unknown = None;
) -> Vec<iroh::EndpointId> {
let latencies = remote_latency_map(node).await;
let mut adequate = Vec::new();
let mut unknown = Vec::new();
Comment on lines +73 to +82
pub(in crate::network::openai) async fn best_remote_latency_ms(
node: &mesh::Node,
hosts: &[iroh::EndpointId],
) -> Option<u32> {
let latencies = remote_latency_map(node).await;
hosts
.iter()
.filter_map(|host| latencies.get(host).copied())
.min()
}
Comment on lines +1 to +3
use crate::mesh;
use mesh_mixture_of_agents as moa;

Comment on lines +766 to +770
'"' | '\'' | '`' | '<' | '>' | '(' | ')' | '[' | ']' | '{' | '}' | ',' | ';' | ':'
)
})
.trim_end_matches(['.', ',', ';', ':', ')', ']', '}'])
.to_string()
@michaelneale

Copy link
Copy Markdown
Collaborator Author

🤖 Update after live Telegram/OpenClaw rerun:

  • Added MoA web-tool budget guard: after 6 completed web_search/web_fetch calls in one active tool chain, MoA switches to compact answer-only reducer context and stops exposing web tools.
  • This addresses the real Telegram failure observed on mini: the old binary reached 20 web tool calls, then OpenClaw hit context overflow after 48 messages and had to auto-compact.
  • Deployed the new debug binary to mini and restarted the mini mesh serve/client pair.
  • Fresh OpenClaw gateway proof with Telegram-shaped metadata completed through mesh/mesh: web_fetch path, replayInvalid=false, toolSummary.calls=1, stopReason=stop.
  • Deployed runtime budget proof against mini /v1/chat/completions: synthetic 6-call web tool history returned finish_reason=stop, has_tool_calls=false, x-moa-turn=tool-result, and sentinel SENTINEL_RUNTIME_WEB_BUDGET_606 in ~5.3s.

Validation:

  • cargo fmt --all -- --check
  • cargo test -p mesh-mixture-of-agents
  • cargo test -p mesh-llm-host-runtime --lib moa_gateway
  • cargo clippy -p mesh-mixture-of-agents --all-targets -- -D warnings
  • cargo clippy -p mesh-llm --all-targets -- -D warnings
  • just build
  • CI on d1ca96ba is green. The initial two_node_split_smoke failure was Hugging Face HTTP 429 during model download before tests ran; rerun passed.

Copilot AI review requested due to automatic review settings June 6, 2026 08:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment on lines +595 to +608
fn compact_chat_message(msg: &Value, max_chars: usize) -> Value {
let Some(content) = msg.get("content").and_then(Value::as_str) else {
return msg.clone();
};
let compacted = compact_text_for_context(content, max_chars);
if compacted == content {
return msg.clone();
}
let mut compact = msg.clone();
if let Some(obj) = compact.as_object_mut() {
obj.insert("content".to_string(), Value::String(compacted));
}
compact
}
Comment on lines +637 to +646
fn xml_unescape(value: &str) -> String {
value
.replace("&quot;", "\"")
.replace("&#34;", "\"")
.replace("&apos;", "'")
.replace("&#39;", "'")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&amp;", "&")
}

@ndizazzo ndizazzo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally this looks like the right direction... but a thing I noticed was that some of the late repair logic is pretty GitHub/web-specific for generic MoA. Hard-coded web_search / web_fetch policy and GitHub answer grounding feel like the one-off tail of for this structural change.

Overall quality looks good and well checked, but I’d prefer front-loading our work so that we make some kind of "tool family" or "provider policy" course correction system that we can extend with other specific fixes - I'm sure as we use Hermes and other agents we'll find lots of this stuff and need to have a cohesive place for it.

@michaelneale

Copy link
Copy Markdown
Collaborator Author

Yes still working on this to make it not harness specific. That was a mistake. Should be generic

…gram-timeouts

* origin/main:
  Reuse Skippy decode wire messages
  Reduce Skippy decode hot-path overhead
Copilot AI review requested due to automatic review settings June 7, 2026 06:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.

Comment on lines +206 to +229
let recent = session.recent_messages(SPECIALIST_CONTEXT_WINDOW);
let user_text = session.last_user_text();
let mut has_last_user = false;
for msg in &recent {
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role == "user" || (role == "assistant" && msg.get("tool_calls").is_none()) {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(
msg,
SPECIALIST_MESSAGE_CONTEXT_MAX_CHARS,
));
}
}

// Ensure the last message is the current user turn
let user_text = session.last_user_text();
if messages
.last()
.and_then(|m| m.get("content").and_then(|c| c.as_str()))
!= Some(&user_text)
{
messages.push(json!({"role": "user", "content": user_text}));
if !has_last_user {
messages.push(json!({
"role": "user",
"content": compact_text_for_context(
&user_text,
SPECIALIST_MESSAGE_CONTEXT_MAX_CHARS,
),
}));
Comment on lines +256 to 273
let recent = session.recent_messages(STRONG_CONTEXT_WINDOW);
let user_text = session.last_user_text();
let mut has_last_user = false;
for msg in &recent {
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role != "system" && !role.is_empty() {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(msg, STRONG_MESSAGE_CONTEXT_MAX_CHARS));
}
}

let user_text = session.last_user_text();
if messages
.last()
.and_then(|m| m.get("content").and_then(|c| c.as_str()))
!= Some(&user_text)
{
messages.push(json!({"role": "user", "content": user_text}));
if !has_last_user {
messages.push(json!({
"role": "user",
"content": compact_text_for_context(&user_text, STRONG_MESSAGE_CONTEXT_MAX_CHARS),
}));
}
Comment on lines +553 to +564
while let Some(parameter_start) = cursor.find("<parameter") {
cursor = &cursor[parameter_start..];
let tag_end = cursor.find('>')?;
let tag = &cursor[..=tag_end];
let name = extract_xml_attr(tag, "name")?;
let value_start = tag_end + 1;
let value_tail = &cursor[value_start..];
let value_end = value_tail.find("</parameter>")?;
let value = xml_unescape(value_tail[..value_end].trim());
arguments.insert(name, Value::String(value));
cursor = &value_tail[value_end + "</parameter>".len()..];
}
Copilot AI review requested due to automatic review settings June 7, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Comment on lines +1731 to +1740
_ => {
let repaired = repair_tool_result_answer(session, &reduced.payload);
chat_or_schema_command_tool_response(
&repaired,
session.tools(),
response_allowed_tools,
response_prompt_profiles,
Some(&session.last_user_text()),
)
}
Comment on lines +827 to +841
fn prompt_tool_catalog_source(session: &Session) -> Option<String> {
if let Some(system) = session
.system_prompt()
.filter(|prompt| !prompt.trim().is_empty())
{
return Some(system);
}

let parts: Vec<String> = session
.messages()
.iter()
.filter_map(message_text_content)
.collect();
(!parts.is_empty()).then(|| parts.join("\n"))
}
Comment on lines +2702 to +2707
fn xml_attr_value(tag: &str, attr: &str) -> Option<String> {
let needle = format!("{attr}=\"");
let value_start = tag.find(&needle)? + needle.len();
let value_end = tag[value_start..].find('"')? + value_start;
Some(xml_text_unescape(&tag[value_start..value_end]))
}
Comment on lines +2747 to +2754
fn xml_text_unescape(text: &str) -> String {
text.replace("&quot;", "\"")
.replace("&apos;", "'")
.replace("&#39;", "'")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&amp;", "&")
}
Copilot AI review requested due to automatic review settings June 7, 2026 12:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Comment on lines +32 to +40
fn body_has_tool_result(body: &serde_json::Value) -> bool {
body.get("messages")
.and_then(serde_json::Value::as_array)
.map(|messages| {
messages.iter().any(|message| {
message.get("role").and_then(serde_json::Value::as_str) == Some("tool")
})
})
.unwrap_or(false)
Comment on lines 209 to +217
for msg in &recent {
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role == "user" || (role == "assistant" && msg.get("tool_calls").is_none()) {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(
msg,
SPECIALIST_MESSAGE_CONTEXT_MAX_CHARS,
));
Comment on lines +256 to 265
let recent = session.recent_messages(STRONG_CONTEXT_WINDOW);
let user_text = session.last_user_text();
let mut has_last_user = false;
for msg in &recent {
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role != "system" && !role.is_empty() {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(msg, STRONG_MESSAGE_CONTEXT_MAX_CHARS));
}
Comment on lines +73 to +92
pub(in crate::network::openai) async fn best_remote_latency_ms(
node: &mesh::Node,
hosts: &[iroh::EndpointId],
) -> Option<u32> {
let latencies = remote_latency_map(node).await;
hosts
.iter()
.filter_map(|host| latencies.get(host).copied())
.min()
}

async fn remote_latency_map(node: &mesh::Node) -> HashMap<iroh::EndpointId, u32> {
node.peers()
.await
.into_iter()
.filter_map(|peer| {
let latency = peer.display_latency().latency_ms?;
Some((peer.id, latency))
})
.collect()
Comment on lines +763 to +765
if should_skip_local_for_inflight(name, resolution.node.inflight_requests()) {
return false;
}
Comment on lines +1 to +4
use crate::mesh;
use mesh_mixture_of_agents as moa;

type RemoteJoinSet = tokio::task::JoinSet<(iroh::EndpointId, Result<serde_json::Value, String>)>;
Copilot AI review requested due to automatic review settings June 8, 2026 01:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Comment on lines +538 to +547
let mut user = String::new();
let last_user = session.last_user_text();
if !last_user.trim().is_empty() {
user.push_str("User request:\n");
user.push_str(&compact_text_for_context(
&last_user,
REDUCER_USER_CONTEXT_MAX_CHARS / 2,
));
user.push_str("\n\n");
}
Comment on lines +44 to +57
adequate.sort_by_key(|candidate| (Reverse(candidate.1), candidate.2.unwrap_or(u32::MAX)));
if !adequate.is_empty() {
return adequate
.into_iter()
.map(|(host, _, _)| host)
.collect::<Vec<_>>();
}

unknown.sort_by_key(|candidate| candidate.1.unwrap_or(u32::MAX));
unknown
.into_iter()
.map(|(host, _)| host)
.collect::<Vec<_>>()
}
Copilot AI review requested due to automatic review settings June 8, 2026 02:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Comment on lines +538 to +547
let mut user = String::new();
let last_user = session.last_user_text();
if !last_user.trim().is_empty() {
user.push_str("User request:\n");
user.push_str(&compact_text_for_context(
&last_user,
REDUCER_USER_CONTEXT_MAX_CHARS / 2,
));
user.push_str("\n\n");
}
Comment on lines +364 to +370
if let Some(parameters) = tool
.get("parameters")
.or_else(|| tool.get("input_schema"))
.cloned()
{
function.insert("parameters".to_string(), parameters);
}
Comment on lines +15 to +22
pub(in crate::network::openai) async fn select_remote_hosts(
node: &mesh::Node,
model: &str,
required_tokens: Option<u32>,
hosts: Vec<iroh::EndpointId>,
) -> Option<iroh::EndpointId> {
let Some(required_tokens) = required_tokens else {
return hosts.into_iter().next();
};

let mut unknown = None;
) -> Vec<iroh::EndpointId> {
let latencies = remote_latency_map(node).await;
let mut adequate = Vec::new();
Copilot Bot review requested due to automatic review settings June 8, 2026 02:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

);

let mut user = String::new();
let last_user = session.last_user_text();
Comment on lines 211 to +217
if role == "user" || (role == "assistant" && msg.get("tool_calls").is_none()) {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(
msg,
SPECIALIST_MESSAGE_CONTEXT_MAX_CHARS,
));
Comment on lines 260 to 265
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role != "system" && !role.is_empty() {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(msg, STRONG_MESSAGE_CONTEXT_MAX_CHARS));
}
Comment on lines +619 to +632
fn compact_chat_message(msg: &Value, max_chars: usize) -> Value {
let Some(content) = msg.get("content").and_then(Value::as_str) else {
return msg.clone();
};
let compacted = compact_text_for_context(content, max_chars);
if compacted == content {
return msg.clone();
}
let mut compact = msg.clone();
if let Some(obj) = compact.as_object_mut() {
obj.insert("content".to_string(), Value::String(compacted));
}
compact
}
Comment on lines +362 to +368
if let Some(parameters) = tool
.get("parameters")
.or_else(|| tool.get("input_schema"))
.cloned()
{
function.insert("parameters".to_string(), parameters);
}
Comment on lines +73 to +82
pub(in crate::network::openai) async fn best_remote_latency_ms(
node: &mesh::Node,
hosts: &[iroh::EndpointId],
) -> Option<u32> {
let latencies = remote_latency_map(node).await;
hosts
.iter()
.filter_map(|host| latencies.get(host).copied())
.min()
}
Copilot Bot review requested due to automatic review settings June 8, 2026 04:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Comment on lines +1 to +4
use crate::mesh;
use mesh_mixture_of_agents as moa;

type RemoteJoinSet = tokio::task::JoinSet<(iroh::EndpointId, Result<serde_json::Value, String>)>;
Comment on lines +73 to +82
pub(in crate::network::openai) async fn best_remote_latency_ms(
node: &mesh::Node,
hosts: &[iroh::EndpointId],
) -> Option<u32> {
let latencies = remote_latency_map(node).await;
hosts
.iter()
.filter_map(|host| latencies.get(host).copied())
.min()
}
Comment on lines 211 to +214
if role == "user" || (role == "assistant" && msg.get("tool_calls").is_none()) {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(
Comment on lines 260 to +264
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
if role != "system" && !role.is_empty() {
messages.push(msg.clone());
has_last_user |= role == "user"
&& msg.get("content").and_then(Value::as_str) == Some(user_text.as_str());
messages.push(compact_chat_message(msg, STRONG_MESSAGE_CONTEXT_MAX_CHARS));
Copilot Bot review requested due to automatic review settings June 8, 2026 09:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Comment on lines +538 to +540
let mut user = String::new();
let last_user = session.last_user_text();
if !last_user.trim().is_empty() {
Comment on lines +73 to +78
pub(in crate::network::openai) async fn best_remote_latency_ms(
node: &mesh::Node,
hosts: &[iroh::EndpointId],
) -> Option<u32> {
let latencies = remote_latency_map(node).await;
hosts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants