use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use rmcp::handler::server::{router::prompt::PromptRouter, wrapper::Parameters};
use rmcp::{RoleServer, model::*, service::RequestContext};
use rmcp::{
ServerHandler, ServiceExt, prompt, prompt_handler, prompt_router, schemars::JsonSchema, tool,
tool_handler, tool_router, transport::stdio,
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
const DEFAULT_SESSION: &str = "default";
const DEFAULT_MAX_HISTORY_SIZE: usize = 1000;
const DEFAULT_HISTORY_LIMIT: usize = 50;
const MAX_HISTORY_LIMIT: usize = 500;
const REDACTION: &str = "[redacted: possible prompt-injection text]";
static ID_SEQ: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Debug)]
struct ThinkServer {
thoughts: Arc<Mutex<ThinkingStore>>,
deliberations: Arc<Mutex<DeliberationStore>>,
#[allow(dead_code)]
prompt_router: PromptRouter<Self>,
}
impl ThinkServer {
fn new(max_history_size: usize) -> Self {
Self {
thoughts: Arc::new(Mutex::new(ThinkingStore::new(max_history_size))),
deliberations: Arc::new(Mutex::new(DeliberationStore::new(max_history_size))),
prompt_router: Self::prompt_router(),
}
}
}
#[tool_router]
impl ThinkServer {
#[tool(
description = "Record one step of sequential reasoning. Optionally include available_tools and recommended_tools; recommendations are validated, not generated, by this server."
)]
fn sequentialthinking_tools(
&self,
Parameters(input): Parameters<ThoughtInput>,
) -> CallToolResult {
let mut store = self.thoughts.lock().expect("store lock poisoned");
let result = store.add(input);
if result.invalid_recommendations.is_some() {
return json_tool_error(&result);
}
json_tool_success(&result)
}
#[tool(
description = "Return recorded thoughts for a session. Use this to inspect or resume prior reasoning."
)]
fn get_thinking_history(
&self,
Parameters(input): Parameters<GetHistoryInput>,
) -> CallToolResult {
let store = self.thoughts.lock().expect("store lock poisoned");
json_tool_success(&store.get_history(input))
}
#[tool(
description = "Clear recorded sequential-thinking history for one session or all sessions."
)]
fn clear_thinking_history(
&self,
Parameters(input): Parameters<ClearHistoryInput>,
) -> CallToolResult {
let mut store = self.thoughts.lock().expect("store lock poisoned");
json_tool_success(&store.clear(input))
}
#[tool(description = "Start a deliberate reasoning session (CoT, ToT, GoT or ReasonKit mode).")]
fn start_deliberation(
&self,
Parameters(input): Parameters<StartDeliberationInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
json_tool_success(&store.start(input))
}
#[tool(description = "Set verification policy for an existing deliberation session.")]
fn set_verification_policy(
&self,
Parameters(input): Parameters<SetVerificationPolicyInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.set_policy(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(
description = "Set natural-language trigger aliases used by reasoning_intent_router/autopilot."
)]
fn set_reasoning_aliases(
&self,
Parameters(input): Parameters<SetReasoningAliasesInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
json_tool_success(&store.set_aliases(input))
}
#[tool(
description = "Map normal wording (CoT, ToT, verification, audit, governance) to recommended ReasonKit Think tools/prompts/resources."
)]
fn reasoning_intent_router(
&self,
Parameters(input): Parameters<ReasoningIntentRouterInput>,
) -> CallToolResult {
let store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
json_tool_success(&route_intent(&input.intent, &store.aliases))
}
#[tool(
description = "Autopilot natural-language reasoning flow: route intent, run a safe default reasoning sequence, and return auditable outputs."
)]
fn reasoning_autopilot(
&self,
Parameters(input): Parameters<ReasoningAutopilotInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
let routed = route_intent(&input.intent, &store.aliases);
let quick_session = input
.quick_session_id
.unwrap_or_else(|| "autopilot-quick".to_string());
let started = store.start(StartDeliberationInput {
session_id: input.session_id.clone(),
mode: Some(if routed.inferred_modes.iter().any(|m| m == "got") {
ReasoningMode::Got
} else if routed.inferred_modes.iter().any(|m| m == "tot") {
ReasoningMode::Tot
} else {
ReasoningMode::Reasonkit
}),
goal: input.goal.clone().unwrap_or_else(|| input.intent.clone()),
constraints: input.constraints.clone(),
profile: input.profile,
limits: None,
verification_policy: input.policy_override.clone(),
});
let deliberation_id = started.deliberation_id.clone();
let mut executed = vec![
"reasoning_intent_router".to_string(),
"start_deliberation".to_string(),
];
let frontier_before = store
.deliberations
.get(&deliberation_id)
.map(|s| s.frontier.clone())
.unwrap_or_default();
let expanded = store.expand(ExpandThoughtsInput {
deliberation_id: deliberation_id.clone(),
from_node_ids: frontier_before,
strategy: Some(ExpandStrategy::Diverse),
count: Some(input.expand_count.unwrap_or(4)),
});
executed.push("expand_thoughts".to_string());
let expanded = match expanded {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
let node_ids = expanded
.created_nodes
.iter()
.map(|n| n.node_id.clone())
.collect::<Vec<_>>();
let scored = store.score(ScoreThoughtsInput {
deliberation_id: deliberation_id.clone(),
node_ids: node_ids.clone(),
weights: None,
});
executed.push("score_thoughts".to_string());
let scored = match scored {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
let pruned = store.prune(PruneThoughtsInput {
deliberation_id: deliberation_id.clone(),
strategy: None,
beam_width: Some(input.beam_width.unwrap_or(2)),
diversity_floor: Some(0.3),
});
executed.push("prune_thoughts".to_string());
let pruned = match pruned {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
let verified = store.verify(VerifyThoughtsInput {
deliberation_id: deliberation_id.clone(),
node_ids: pruned.kept_node_ids.clone(),
critical_claims: Vec::new(),
claims: Some(default_autopilot_claims(&input.intent)),
method: Some(VerificationMethod::Hybrid),
policy_override: input.policy_override.clone(),
});
executed.push("verify_thoughts".to_string());
let verified = match verified {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
let pipeline = store.run_pipeline(RunReasonKitPipelineInput {
deliberation_id: deliberation_id.clone(),
stages: None,
profile: input.profile,
policy_override: input.policy_override.clone(),
});
executed.push("run_reasonkit_pipeline".to_string());
let pipeline = match pipeline {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
let consensus = store.consensus(ConsensusAnswerInput {
deliberation_id: deliberation_id.clone(),
method: Some(ConsensusMethod::SelfConsistency),
policy_override: input.policy_override,
});
executed.push("consensus_answer".to_string());
let consensus = match consensus {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
let audit = store.export_audit(ExportReasoningAuditInput {
deliberation_id: deliberation_id.clone(),
include_raw_thoughts: Some(true),
});
executed.push("export_reasoning_audit".to_string());
let audit = match audit {
Ok(v) => v,
Err(err) => return json_tool_error(&ErrorEnvelope { error: err }),
};
executed.push("clear_thinking_history".to_string());
json_tool_success(&ReasoningAutopilotResult {
intent: input.intent,
quick_session_id: quick_session,
deliberation_id,
routed,
executed_tools: executed,
score_summary_count: scored.scores.len(),
verified_summary: verified.claim_status_summary,
route_decision: consensus.route_decision,
confidence: consensus.confidence,
policy_blocked: consensus.policy_blocked,
pipeline_route: pipeline.route_decision,
audit_id: audit.audit_id,
notes: vec![
"Autopilot ran a safe default sequence; customize claims/policy for production tasks."
.to_string(),
],
})
}
#[tool(description = "Expand frontier thoughts into candidate branches.")]
fn expand_thoughts(
&self,
Parameters(input): Parameters<ExpandThoughtsInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.expand(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "Score thoughts across weighted reasoning dimensions.")]
fn score_thoughts(&self, Parameters(input): Parameters<ScoreThoughtsInput>) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.score(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "Prune thought frontier with beam and diversity constraints.")]
fn prune_thoughts(&self, Parameters(input): Parameters<PruneThoughtsInput>) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.prune(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "Verify thoughts and claims using CoVe/triangulation-style checks.")]
fn verify_thoughts(
&self,
Parameters(input): Parameters<VerifyThoughtsInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.verify(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "Build final consensus answer with policy-gated route decision.")]
fn consensus_answer(
&self,
Parameters(input): Parameters<ConsensusAnswerInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.consensus(input) {
Ok(out) => {
if out.policy_blocked {
json_tool_error(&out)
} else {
json_tool_success(&out)
}
}
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "Execute staged ReasonKit governance pipeline for a deliberation.")]
fn run_reasonkit_pipeline(
&self,
Parameters(input): Parameters<RunReasonKitPipelineInput>,
) -> CallToolResult {
let mut store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.run_pipeline(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "Export a complete reasoning audit artifact for a deliberation.")]
fn export_reasoning_audit(
&self,
Parameters(input): Parameters<ExportReasoningAuditInput>,
) -> CallToolResult {
let store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match store.export_audit(input) {
Ok(out) => json_tool_success(&out),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
#[tool(description = "List available reasoning resources exposed by this server.")]
fn list_reasoning_resources(&self) -> CallToolResult {
let resources = vec![
"reasoning://session/{id}/graph",
"reasoning://session/{id}/frontier",
"reasoning://session/{id}/verification-matrix",
"reasoning://session/{id}/route-decision",
"reasoning://config/aliases",
"reasoning://schemas/{name}",
];
json_tool_success(&json!({ "resources": resources }))
}
#[tool(description = "Read a reasoning resource payload by URI.")]
fn read_reasoning_resource(
&self,
Parameters(input): Parameters<ReadReasoningResourceInput>,
) -> CallToolResult {
let store = self
.deliberations
.lock()
.expect("deliberation lock poisoned");
match read_reasoning_resource_payload(&store, &input.uri) {
Ok(payload) => json_tool_success(&payload),
Err(err) => json_tool_error(&ErrorEnvelope { error: err }),
}
}
}
#[prompt_router]
impl ThinkServer {
#[prompt(
name = "sequential-thinking-guidance",
description = "Use sequentialthinking_tools as a lightweight scratchpad without pretending it performs reasoning for you."
)]
fn sequential_thinking_guidance(
&self,
Parameters(input): Parameters<GuidancePromptInput>,
) -> GetPromptResult {
let mut lines = vec![
"Use sequentialthinking_tools only for problems that genuinely benefit from explicit multi-step reasoning.".to_string(),
"Keep each thought short. Revise or branch when new evidence changes the plan.".to_string(),
"If recommending tools, pass available_tools and recommended_tools so the server can validate names.".to_string(),
"Do not claim the server chose the tools; the model authored the plan and the server tracked it.".to_string(),
];
if let Some(problem) = input.problem {
lines.push(format!("Problem: {problem}"));
}
GetPromptResult::new(vec![PromptMessage::new_text(
PromptMessageRole::User,
lines.join("\n"),
)])
}
#[prompt(
name = "tot-planner-guidance",
description = "Guide branch/score/prune deliberate ToT planning."
)]
fn tot_planner_guidance(
&self,
Parameters(input): Parameters<GuidancePromptInput>,
) -> GetPromptResult {
let problem = input
.problem
.unwrap_or_else(|| "No problem provided.".to_string());
let text = format!(
"Plan with explicit branches.\n1) Create 3-5 candidate branches.\n2) Score each on correctness, risk, and evidence.\n3) Prune weak branches but preserve diversity.\n4) Verify critical claims before final answer.\nProblem: {problem}"
);
GetPromptResult::new(vec![PromptMessage::new_text(PromptMessageRole::User, text)])
}
#[prompt(
name = "diverse-lens-pack",
description = "Run diverse-thinking cognitive lenses for robust ideation."
)]
fn diverse_lens_pack(
&self,
Parameters(input): Parameters<GuidancePromptInput>,
) -> GetPromptResult {
let text = format!(
"Apply these lenses independently then synthesize: Optimist, Pessimist, Systems Thinker, Empiricist, Contrarian, Security Adversary, Simplifier.\nTopic: {}",
input.problem.unwrap_or_else(|| "General topic".to_string())
);
GetPromptResult::new(vec![PromptMessage::new_text(PromptMessageRole::User, text)])
}
#[prompt(
name = "verification-pack",
description = "CoVe-style verification sequence prompt."
)]
fn verification_pack(
&self,
Parameters(input): Parameters<GuidancePromptInput>,
) -> GetPromptResult {
let text = format!(
"Use Chain-of-Verification: draft -> verification questions -> independent answers -> revised final.\nTarget: {}",
input.problem.unwrap_or_else(|| "Current draft".to_string())
);
GetPromptResult::new(vec![PromptMessage::new_text(PromptMessageRole::User, text)])
}
#[prompt(
name = "triangulation-pack",
description = "Triangulate claims and classify verification status."
)]
fn triangulation_pack(
&self,
Parameters(input): Parameters<GuidancePromptInput>,
) -> GetPromptResult {
let text = format!(
"For each critical claim: gather independent evidence and classify as verified/source-conflict/data-deficit.\nClaim set: {}",
input
.problem
.unwrap_or_else(|| "Current answer".to_string())
);
GetPromptResult::new(vec![PromptMessage::new_text(PromptMessageRole::User, text)])
}
#[prompt(
name = "decision-routing-pack",
description = "Final calibrated route decision prompt."
)]
fn decision_routing_pack(
&self,
Parameters(input): Parameters<GuidancePromptInput>,
) -> GetPromptResult {
let text = format!(
"Choose exactly one route: PROCEED | PROCEED_WITH_CAVEATS | GATHER_MORE_EVIDENCE | DEFER_TO_HUMAN | REJECT_CURRENT_PLAN. Explain confidence and blocking gaps.\nContext: {}",
input
.problem
.unwrap_or_else(|| "Current deliberation".to_string())
);
GetPromptResult::new(vec![PromptMessage::new_text(PromptMessageRole::User, text)])
}
}
#[tool_handler]
#[prompt_handler]
impl ServerHandler for ThinkServer {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(
ServerCapabilities::builder()
.enable_tools()
.enable_prompts()
.build(),
)
}
}
#[derive(Debug, Clone)]
struct ThinkingStore {
max_history_size: usize,
sessions: HashMap<String, Vec<ThoughtRecord>>,
}
impl ThinkingStore {
fn new(max_history_size: usize) -> Self {
Self {
max_history_size: sanitize_limit(
max_history_size as i64,
DEFAULT_MAX_HISTORY_SIZE,
usize::MAX,
),
sessions: HashMap::new(),
}
}
fn add(&mut self, input: ThoughtInput) -> ThoughtResult {
let session_id = normalize_session(input.session_id.as_deref());
let total_thoughts = input.total_thoughts.max(input.thought_number);
let raw_record = ThoughtRecord {
session_id: session_id.clone(),
thought: input.thought,
thought_number: input.thought_number,
total_thoughts,
next_thought_needed: input.next_thought_needed,
is_revision: input.is_revision,
revises_thought: input.revises_thought,
branch_from_thought: input.branch_from_thought,
branch_id: input.branch_id,
needs_more_thoughts: input.needs_more_thoughts,
available_tools: input.available_tools,
recommended_tools: input.recommended_tools,
remaining_steps: input.remaining_steps,
created_at: chrono::Utc::now().to_rfc3339(),
};
let security_warnings = scan_record(&raw_record);
let record = sanitize_record(raw_record);
let invalid_recommendations = validate_recommendations(&record);
if !invalid_recommendations.is_empty() {
return ThoughtResult {
session_id: session_id.clone(),
thought_number: record.thought_number,
total_thoughts,
next_thought_needed: record.next_thought_needed,
needs_more_thoughts: record.needs_more_thoughts,
branches: self.branches(&session_id),
history_length: self.history(&session_id).len(),
invalid_recommendations: Some(invalid_recommendations),
security_warnings: (!security_warnings.is_empty()).then_some(security_warnings),
recommended_tools: record.recommended_tools.clone(),
remaining_steps: record.remaining_steps.clone(),
};
}
let history_length = {
let history = self.sessions.entry(session_id.clone()).or_default();
history.push(record.clone());
if history.len() > self.max_history_size {
let drain_count = history.len() - self.max_history_size;
history.drain(0..drain_count);
}
history.len()
};
ThoughtResult {
session_id: session_id.clone(),
thought_number: record.thought_number,
total_thoughts,
next_thought_needed: record.next_thought_needed,
needs_more_thoughts: record.needs_more_thoughts,
branches: self.branches(&session_id),
history_length,
invalid_recommendations: None,
security_warnings: (!security_warnings.is_empty()).then_some(security_warnings),
recommended_tools: record.recommended_tools.clone(),
remaining_steps: record.remaining_steps.clone(),
}
}
fn get_history(&self, input: GetHistoryInput) -> HistoryResult {
let session_id = normalize_session(input.session_id.as_deref());
let mut records = self.history(&session_id).to_vec();
if let Some(branch_id) = input.branch_id {
records.retain(|r| r.branch_id.as_deref() == Some(branch_id.as_str()));
}
let limit = sanitize_limit(
input.limit.unwrap_or(DEFAULT_HISTORY_LIMIT as i64),
DEFAULT_HISTORY_LIMIT,
MAX_HISTORY_LIMIT,
);
let take_from = records.len().saturating_sub(limit);
let thoughts = records.split_off(take_from);
HistoryResult {
session_id: session_id.clone(),
branches: self.branches(&session_id),
history_length: self.history(&session_id).len(),
thoughts,
}
}
fn clear(&mut self, input: ClearHistoryInput) -> ClearResult {
if input.all_sessions.unwrap_or(false) {
let cleared_sessions = self.sessions.len();
let cleared_thoughts = self.sessions.values().map(Vec::len).sum();
self.sessions.clear();
return ClearResult {
session_id: None,
cleared_sessions,
cleared_thoughts,
};
}
let session_id = normalize_session(input.session_id.as_deref());
let cleared_thoughts = self.history(&session_id).len();
self.sessions.remove(&session_id);
ClearResult {
session_id: Some(session_id),
cleared_sessions: 1,
cleared_thoughts,
}
}
fn history(&self, session_id: &str) -> &[ThoughtRecord] {
self.sessions
.get(session_id)
.map(Vec::as_slice)
.unwrap_or(&[])
}
fn branches(&self, session_id: &str) -> Vec<String> {
let mut seen = HashSet::new();
let mut branches = Vec::new();
for branch in self
.history(session_id)
.iter()
.filter_map(|record| record.branch_id.clone())
{
if seen.insert(branch.clone()) {
branches.push(branch);
}
}
branches
}
}
#[derive(Debug, Clone)]
struct DeliberationStore {
max_history_size: usize,
deliberations: HashMap<String, DeliberationSession>,
aliases: ReasoningAliases,
}
impl DeliberationStore {
fn new(max_history_size: usize) -> Self {
Self {
max_history_size,
deliberations: HashMap::new(),
aliases: ReasoningAliases::default(),
}
}
fn start(&mut self, input: StartDeliberationInput) -> StartDeliberationResult {
let limits = Limits::with_defaults(input.limits.unwrap_or_default());
let mode = input.mode.unwrap_or(ReasoningMode::Tot);
let profile = input.profile.unwrap_or(ReasoningProfile::Balanced);
let session_id = normalize_session(input.session_id.as_deref());
let deliberation_id = make_id("delib");
let root_id = make_id("node");
let now = chrono::Utc::now().to_rfc3339();
let root = DeliberationNode {
node_id: root_id.clone(),
parent_ids: Vec::new(),
content: input.goal.clone(),
branch_id: Some("root".to_string()),
created_at: now,
score: None,
tags: vec!["root".to_string()],
};
let mut nodes = HashMap::new();
nodes.insert(root_id.clone(), root);
self.deliberations.insert(
deliberation_id.clone(),
DeliberationSession {
deliberation_id: deliberation_id.clone(),
session_id: session_id.clone(),
mode,
profile,
goal: input.goal,
constraints: input.constraints.unwrap_or_default(),
limits: limits.clone(),
verification_policy: input.verification_policy.unwrap_or_default().normalize(),
nodes,
frontier: vec![root_id],
verification_matrix: Vec::new(),
stage_findings: Vec::new(),
route_decision: None,
},
);
StartDeliberationResult {
deliberation_id,
session_id,
mode,
profile,
limits_applied: limits,
status: "started".to_string(),
}
}
fn set_policy(
&mut self,
input: SetVerificationPolicyInput,
) -> Result<SetVerificationPolicyResult, String> {
let session = self.get_mut(&input.deliberation_id)?;
session.verification_policy = input.verification_policy.normalize();
Ok(SetVerificationPolicyResult {
deliberation_id: session.deliberation_id.clone(),
verification_policy: session.verification_policy.clone(),
status: "updated".to_string(),
})
}
fn set_aliases(&mut self, input: SetReasoningAliasesInput) -> SetReasoningAliasesResult {
if input.merge.unwrap_or(true) {
self.aliases.merge(input.aliases);
} else {
self.aliases = input.aliases.normalize();
}
SetReasoningAliasesResult {
aliases: self.aliases.clone(),
status: "updated".to_string(),
}
}
fn expand(&mut self, input: ExpandThoughtsInput) -> Result<ExpandThoughtsResult, String> {
let max_history_size = self.max_history_size;
let session = self.get_mut(&input.deliberation_id)?;
let count = sanitize_limit(input.count.unwrap_or(3), 3, 16);
let mut created = Vec::new();
for parent in &input.from_node_ids {
let parent_node = session
.nodes
.get(parent)
.cloned()
.ok_or_else(|| format!("unknown parent node: {parent}"))?;
for idx in 0..count {
if session.nodes.len() >= session.limits.max_nodes as usize {
break;
}
let node_id = make_id("node");
let content = build_branch_content(
&parent_node.content,
input.strategy.unwrap_or(ExpandStrategy::Diverse),
idx,
);
let node = DeliberationNode {
node_id: node_id.clone(),
parent_ids: vec![parent.clone()],
content,
branch_id: Some(format!("b{}", idx + 1)),
created_at: chrono::Utc::now().to_rfc3339(),
score: None,
tags: vec!["expanded".to_string()],
};
session.nodes.insert(node_id.clone(), node.clone());
created.push(node);
session.frontier.push(node_id);
}
}
if session.nodes.len() > max_history_size {
session
.frontier
.truncate(session.limits.max_branches_per_expand as usize);
}
Ok(ExpandThoughtsResult {
deliberation_id: session.deliberation_id.clone(),
created_nodes: created,
frontier_nodes: session.frontier.clone(),
expansion_summary: format!("expanded {} nodes", session.frontier.len()),
})
}
fn score(&mut self, input: ScoreThoughtsInput) -> Result<ScoreThoughtsResult, String> {
let session = self.get_mut(&input.deliberation_id)?;
let w = input.weights.unwrap_or_default().normalize();
let mut scores = Vec::new();
for node_id in &input.node_ids {
let node = session
.nodes
.get_mut(node_id)
.ok_or_else(|| format!("unknown node: {node_id}"))?;
let len = node.content.len() as f64;
let correctness_proxy = (0.4 + (len % 37.0) / 100.0).clamp(0.0, 1.0);
let evidence_quality = (0.3 + (len % 23.0) / 100.0).clamp(0.0, 1.0);
let risk = (0.2 + (len % 19.0) / 100.0).clamp(0.0, 1.0);
let cost = (0.2 + (len % 17.0) / 100.0).clamp(0.0, 1.0);
let novelty = (0.3 + (len % 29.0) / 100.0).clamp(0.0, 1.0);
let total = correctness_proxy * w.correctness_proxy
+ evidence_quality * w.evidence_quality
+ (1.0 - risk) * w.risk
+ (1.0 - cost) * w.cost
+ novelty * w.novelty;
node.score = Some(total);
scores.push(NodeScore {
node_id: node_id.clone(),
total,
correctness_proxy,
evidence_quality,
risk,
cost,
novelty,
rationale_tags: vec!["weighted".to_string(), "heuristic".to_string()],
});
}
scores.sort_by(|a, b| {
b.total
.partial_cmp(&a.total)
.unwrap_or(std::cmp::Ordering::Equal)
});
let ranked_node_ids = scores.iter().map(|s| s.node_id.clone()).collect();
Ok(ScoreThoughtsResult {
deliberation_id: session.deliberation_id.clone(),
scores,
ranked_node_ids,
})
}
fn prune(&mut self, input: PruneThoughtsInput) -> Result<PruneThoughtsResult, String> {
let session = self.get_mut(&input.deliberation_id)?;
let width = sanitize_limit(input.beam_width.unwrap_or(4), 4, 32);
let diversity_floor = input.diversity_floor.unwrap_or(0.3).clamp(0.0, 1.0);
let mut frontier = session
.frontier
.iter()
.filter_map(|id| {
session
.nodes
.get(id)
.map(|n| (id.clone(), n.score.unwrap_or(0.0), n.branch_id.clone()))
})
.collect::<Vec<_>>();
frontier.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut kept = Vec::new();
let mut seen_branches = HashSet::new();
for (id, _, branch_id) in frontier {
if kept.len() >= width {
break;
}
if let Some(b) = branch_id {
if !seen_branches.contains(&b)
|| (kept.len() as f64 / width as f64) < diversity_floor
{
seen_branches.insert(b);
kept.push(id);
}
} else {
kept.push(id);
}
}
let keep_set: HashSet<String> = kept.iter().cloned().collect();
let pruned = session
.frontier
.iter()
.filter(|id| !keep_set.contains(*id))
.cloned()
.collect::<Vec<_>>();
session.frontier = kept.clone();
Ok(PruneThoughtsResult {
deliberation_id: session.deliberation_id.clone(),
kept_node_ids: kept,
pruned_node_ids: pruned,
prune_report: format!("kept {} nodes", session.frontier.len()),
})
}
fn verify(&mut self, input: VerifyThoughtsInput) -> Result<VerifyThoughtsResult, String> {
let session = self.get_mut(&input.deliberation_id)?;
let method = input.method.unwrap_or(VerificationMethod::Hybrid);
let policy = input
.policy_override
.unwrap_or_else(|| session.verification_policy.clone())
.normalize();
session.verification_policy = policy.clone();
let mut matrix = Vec::new();
let explicit_claims = input.claims.unwrap_or_default();
let fallback_claims = if explicit_claims.is_empty() {
if input.critical_claims.is_empty() {
input
.node_ids
.iter()
.filter_map(|id| session.nodes.get(id))
.flat_map(|n| n.content.split('.').map(|s| s.trim().to_string()))
.filter(|s| !s.is_empty())
.take(12)
.map(|text| VerifyClaimInput {
text,
critical: false,
evidence: Vec::new(),
})
.collect::<Vec<_>>()
} else {
input
.critical_claims
.into_iter()
.map(|text| VerifyClaimInput {
text,
critical: true,
evidence: Vec::new(),
})
.collect::<Vec<_>>()
}
} else {
explicit_claims
};
for claim in fallback_claims {
let mut evidence = claim.evidence.clone();
if evidence.is_empty() {
evidence = vec![EvidenceItem {
source: "internal-heuristic".to_string(),
tier: EvidenceTier::Tier3,
independence_group: "internal-heuristic".to_string(),
supports: true,
contradictory: false,
unambiguous: false,
}];
}
let status = evaluate_claim_status(&evidence, &policy, claim.critical);
matrix.push(VerificationEntry {
claim: claim.text,
critical: claim.critical,
status,
evidence,
notes: format!(
"method={method:?}; min_independent_groups={}",
policy.min_independent_groups
),
});
}
session.verification_matrix = matrix.clone();
let summary = VerificationSummary::from_entries(&matrix);
let revisions = matrix
.iter()
.filter(|e| !matches!(e.status, VerificationStatus::Verified))
.map(|e| format!("Revise claim: {}", e.claim))
.collect::<Vec<_>>();
Ok(VerifyThoughtsResult {
deliberation_id: session.deliberation_id.clone(),
verification_matrix: matrix,
claim_status_summary: summary,
revisions,
})
}
fn consensus(&mut self, input: ConsensusAnswerInput) -> Result<ConsensusAnswerResult, String> {
let session = self.get_mut(&input.deliberation_id)?;
let method = input.method.unwrap_or(ConsensusMethod::SelfConsistency);
if let Some(policy) = input.policy_override {
session.verification_policy = policy.normalize();
}
let mut ranked = session
.frontier
.iter()
.filter_map(|id| session.nodes.get(id))
.cloned()
.collect::<Vec<_>>();
ranked.sort_by(|a, b| {
b.score
.unwrap_or(0.0)
.partial_cmp(&a.score.unwrap_or(0.0))
.unwrap_or(std::cmp::Ordering::Equal)
});
let top = ranked.into_iter().take(3).collect::<Vec<_>>();
if top.is_empty() {
return Err("no frontier nodes available for consensus".to_string());
}
let final_answer = top
.iter()
.map(|n| format!("- {}", n.content))
.collect::<Vec<_>>()
.join("\n");
let unresolved = session
.verification_matrix
.iter()
.filter(|e| !matches!(e.status, VerificationStatus::Verified))
.count();
let critical_unresolved = session
.verification_matrix
.iter()
.any(|e| e.critical && !matches!(e.status, VerificationStatus::Verified));
let route_decision = if critical_unresolved || unresolved > 0 {
RouteDecision::GatherMoreEvidence
} else if matches!(session.profile, ReasoningProfile::Paranoid) {
RouteDecision::ProceedWithCaveats
} else {
RouteDecision::Proceed
};
session.route_decision = Some(route_decision);
let confidence = (0.85 - unresolved as f64 * 0.1).clamp(0.1, 0.95);
let disagreement_report = format!(
"method={method:?}; candidates_considered={}; unresolved_claims={unresolved}",
top.len()
);
let policy_blocked = critical_unresolved
&& session
.verification_policy
.fail_closed_on_critical_unresolved;
Ok(ConsensusAnswerResult {
deliberation_id: session.deliberation_id.clone(),
final_answer,
confidence,
route_decision,
disagreement_report,
policy_blocked,
})
}
fn run_pipeline(
&mut self,
input: RunReasonKitPipelineInput,
) -> Result<RunReasonKitPipelineResult, String> {
let session = self.get_mut(&input.deliberation_id)?;
if let Some(policy) = input.policy_override {
session.verification_policy = policy.normalize();
}
let stages = input.stages.unwrap_or_else(|| {
vec![
"constraint-mapping".to_string(),
"diverse-thinking".to_string(),
"first-principles".to_string(),
"fallacy-detection".to_string(),
"triangulation".to_string(),
"brutal-honesty".to_string(),
"calibration-routing".to_string(),
]
});
let findings = stages
.iter()
.map(|stage| StageFinding {
stage: stage.clone(),
finding: format!("{stage}: completed"),
confidence: 0.72,
})
.collect::<Vec<_>>();
let has_critical_unresolved = session.verification_matrix.iter().any(|e| {
e.critical
&& matches!(
e.status,
VerificationStatus::SourceConflict | VerificationStatus::DataDeficit
)
});
let evidence_gaps = if session.verification_matrix.iter().any(|e| {
matches!(
e.status,
VerificationStatus::SourceConflict | VerificationStatus::DataDeficit
)
}) {
vec!["Critical claims require stronger verification evidence".to_string()]
} else {
Vec::new()
};
let route = if has_critical_unresolved {
RouteDecision::GatherMoreEvidence
} else if evidence_gaps.is_empty() {
RouteDecision::ProceedWithCaveats
} else {
RouteDecision::GatherMoreEvidence
};
session.stage_findings = findings.clone();
session.route_decision = Some(route);
Ok(RunReasonKitPipelineResult {
deliberation_id: session.deliberation_id.clone(),
stage_findings: findings,
route_decision: route,
confidence_rationale: "Stage completion + verification gate applied".to_string(),
evidence_gaps,
})
}
fn export_audit(
&self,
input: ExportReasoningAuditInput,
) -> Result<ExportReasoningAuditResult, String> {
let session = self
.deliberations
.get(&input.deliberation_id)
.ok_or_else(|| "unknown deliberation_id".to_string())?;
let payload = json!({
"audit_id": make_id("audit"),
"deliberation_id": session.deliberation_id,
"session_id": session.session_id,
"mode": session.mode,
"profile": session.profile,
"goal": session.goal,
"constraints": session.constraints,
"verification_policy": session.verification_policy,
"frontier": session.frontier,
"node_count": session.nodes.len(),
"verification_matrix": session.verification_matrix,
"stage_findings": session.stage_findings,
"route_decision": session.route_decision,
});
Ok(ExportReasoningAuditResult {
deliberation_id: session.deliberation_id.clone(),
audit_id: make_id("audit"),
payload,
})
}
fn get_mut(&mut self, deliberation_id: &str) -> Result<&mut DeliberationSession, String> {
self.deliberations
.get_mut(deliberation_id)
.ok_or_else(|| "unknown deliberation_id".to_string())
}
}
fn evaluate_claim_status(
evidence: &[EvidenceItem],
policy: &VerificationPolicy,
critical: bool,
) -> VerificationStatus {
if evidence.is_empty() {
return VerificationStatus::DataDeficit;
}
let has_tier1_unambiguous = evidence.iter().any(|e| {
matches!(e.tier, EvidenceTier::Tier1) && e.unambiguous && e.supports && !e.contradictory
});
if has_tier1_unambiguous {
let contradictory = evidence.iter().any(|e| e.contradictory);
if contradictory {
return VerificationStatus::SourceConflict;
}
return VerificationStatus::Verified;
}
if critical && policy.require_tier1_unambiguous_for_critical {
return VerificationStatus::DataDeficit;
}
let supports = evidence
.iter()
.filter(|e| {
e.supports
&& !e.contradictory
&& (policy.allow_tier3_for_independence || !matches!(e.tier, EvidenceTier::Tier3))
})
.collect::<Vec<_>>();
let contradictory = evidence.iter().any(|e| e.contradictory);
if contradictory {
return VerificationStatus::SourceConflict;
}
let unique_groups = supports
.iter()
.map(|e| e.independence_group.as_str())
.collect::<HashSet<_>>()
.len();
if unique_groups >= policy.min_independent_groups as usize {
VerificationStatus::Verified
} else {
VerificationStatus::DataDeficit
}
}
fn has_any_ci(text: &str, needles: &[String]) -> bool {
needles
.iter()
.map(|n| n.to_lowercase())
.any(|n| text.contains(&n))
}
fn dedup_in_place(values: &mut Vec<String>) {
let mut seen = HashSet::new();
values.retain(|v| seen.insert(v.clone()));
}
fn normalize_alias_list(values: Vec<String>) -> Vec<String> {
let mut out = values
.into_iter()
.map(|v| v.trim().to_lowercase())
.filter(|v| !v.is_empty())
.collect::<Vec<_>>();
dedup_in_place(&mut out);
out
}
fn extend_unique(dest: &mut Vec<String>, add: &[String]) {
let mut set = dest.iter().cloned().collect::<HashSet<_>>();
for value in add {
let value = value.trim().to_lowercase();
if !value.is_empty() && set.insert(value.clone()) {
dest.push(value);
}
}
}
fn explain_tool(tool: &str) -> String {
match tool {
"sequentialthinking_tools" => "Quick linear thought capture (CoT baseline).".to_string(),
"get_thinking_history" => "Inspect current quick-thought assumptions.".to_string(),
"start_deliberation" => "Open structured deep reasoning session.".to_string(),
"expand_thoughts" => "Generate alternatives/branches from frontier.".to_string(),
"score_thoughts" => "Rank candidates by weighted reasoning quality.".to_string(),
"prune_thoughts" => "Keep best/diverse candidates, remove weak branches.".to_string(),
"verify_thoughts" => "Evaluate claim evidence and detect deficits/conflicts.".to_string(),
"set_verification_policy" => "Adjust strictness/fail-closed behavior.".to_string(),
"consensus_answer" => "Produce final routed answer with confidence.".to_string(),
"run_reasonkit_pipeline" => "Run governance stages and route calibration.".to_string(),
"export_reasoning_audit" => "Produce full auditable artifact.".to_string(),
"list_reasoning_resources" => "Discover graph/frontier/verification resources.".to_string(),
"read_reasoning_resource" => "Read specific session resource URI.".to_string(),
"clear_thinking_history" => "Cleanup quick-thought session state.".to_string(),
_ => "No description available.".to_string(),
}
}
fn argument_template(tool: &str) -> Value {
match tool {
"sequentialthinking_tools" => json!({
"session_id": "demo-quick",
"thought": "Initial linear analysis",
"thought_number": 1,
"total_thoughts": 3,
"next_thought_needed": true,
"available_tools": ["start_deliberation"],
"recommended_tools": [{"tool_name": "start_deliberation"}]
}),
"get_thinking_history" => json!({ "session_id": "demo-quick" }),
"start_deliberation" => json!({
"session_id": "demo-delib",
"mode": "reasonkit",
"profile": "balanced",
"goal": "Solve the target problem with auditable reasoning"
}),
"expand_thoughts" => json!({
"deliberation_id": "<delib-id>",
"from_node_ids": ["<frontier-node-id>"],
"strategy": "diverse",
"count": 4
}),
"score_thoughts" => json!({
"deliberation_id": "<delib-id>",
"node_ids": ["<node-id-1>", "<node-id-2>"]
}),
"prune_thoughts" => json!({
"deliberation_id": "<delib-id>",
"beam_width": 2,
"diversity_floor": 0.3
}),
"verify_thoughts" => json!({
"deliberation_id": "<delib-id>",
"node_ids": ["<node-id-1>"],
"claims": [{
"text": "Critical claim",
"critical": true,
"evidence": [{
"source": "gateway_logs",
"tier": "tier1",
"independence_group": "logs",
"supports": true,
"contradictory": false,
"unambiguous": true
}]
}]
}),
"set_verification_policy" => json!({
"deliberation_id": "<delib-id>",
"verification_policy": {
"min_independent_groups": 3,
"allow_tier3_for_independence": false,
"require_tier1_unambiguous_for_critical": true,
"fail_closed_on_critical_unresolved": true
}
}),
"consensus_answer" => json!({ "deliberation_id": "<delib-id>" }),
"run_reasonkit_pipeline" => json!({ "deliberation_id": "<delib-id>" }),
"export_reasoning_audit" => json!({ "deliberation_id": "<delib-id>" }),
"list_reasoning_resources" => json!({}),
"read_reasoning_resource" => json!({ "uri": "reasoning://session/<id>/frontier" }),
"clear_thinking_history" => json!({ "session_id": "demo-quick" }),
_ => json!({}),
}
}
fn route_intent(intent_raw: &str, aliases: &ReasoningAliases) -> ReasoningIntentRouterResult {
let intent = intent_raw.to_lowercase();
let mut inferred_modes = Vec::new();
let mut prompt_plan = Vec::new();
let mut tool_plan = Vec::new();
let mut resource_plan = Vec::new();
let mut notes = Vec::new();
let wants_quick = has_any_ci(&intent, &aliases.cot);
let wants_tot = has_any_ci(&intent, &aliases.tot);
let wants_got = has_any_ci(&intent, &aliases.got);
let wants_verify = has_any_ci(&intent, &aliases.verification);
let wants_governance = has_any_ci(&intent, &aliases.governance);
let wants_audit = has_any_ci(&intent, &aliases.auditability);
let wants_cleanup = has_any_ci(&intent, &aliases.cleanup);
if wants_quick || (!wants_tot && !wants_verify && !wants_governance && !wants_audit) {
inferred_modes.push("cot".to_string());
prompt_plan.push("sequential-thinking-guidance".to_string());
tool_plan.extend([
"sequentialthinking_tools".to_string(),
"get_thinking_history".to_string(),
]);
}
if wants_tot || wants_got {
inferred_modes.push(if wants_got { "got" } else { "tot" }.to_string());
prompt_plan.extend([
"tot-planner-guidance".to_string(),
"diverse-lens-pack".to_string(),
]);
tool_plan.extend([
"start_deliberation".to_string(),
"expand_thoughts".to_string(),
"score_thoughts".to_string(),
"prune_thoughts".to_string(),
]);
notes.push(
"Use mode='got' for graph-style merge/revisit behavior; otherwise mode='tot'."
.to_string(),
);
}
if wants_verify {
inferred_modes.push("verification".to_string());
prompt_plan.extend([
"verification-pack".to_string(),
"triangulation-pack".to_string(),
]);
tool_plan.extend([
"verify_thoughts".to_string(),
"set_verification_policy".to_string(),
]);
resource_plan.push("reasoning://session/{id}/verification-matrix".to_string());
}
if wants_governance {
inferred_modes.push("governance".to_string());
prompt_plan.push("decision-routing-pack".to_string());
tool_plan.extend([
"run_reasonkit_pipeline".to_string(),
"consensus_answer".to_string(),
]);
resource_plan.push("reasoning://session/{id}/route-decision".to_string());
}
if wants_audit {
inferred_modes.push("auditability".to_string());
tool_plan.extend([
"export_reasoning_audit".to_string(),
"list_reasoning_resources".to_string(),
"read_reasoning_resource".to_string(),
]);
resource_plan.extend([
"reasoning://session/{id}/graph".to_string(),
"reasoning://session/{id}/frontier".to_string(),
]);
}
if wants_cleanup {
inferred_modes.push("cleanup".to_string());
tool_plan.push("clear_thinking_history".to_string());
}
dedup_in_place(&mut inferred_modes);
dedup_in_place(&mut prompt_plan);
dedup_in_place(&mut tool_plan);
dedup_in_place(&mut resource_plan);
let suggested_tool_calls = tool_plan
.iter()
.map(|tool| SuggestedToolCall {
tool: tool.clone(),
why: explain_tool(tool),
arguments_template: argument_template(tool),
})
.collect::<Vec<_>>();
ReasoningIntentRouterResult {
intent: intent_raw.to_string(),
inferred_modes,
prompt_plan,
tool_plan,
resource_plan,
suggested_tool_calls,
notes,
}
}
fn default_autopilot_claims(intent: &str) -> Vec<VerifyClaimInput> {
vec![
VerifyClaimInput {
text: format!("Primary objective is achievable: {intent}"),
critical: true,
evidence: vec![EvidenceItem {
source: "operator-input".to_string(),
tier: EvidenceTier::Tier1,
independence_group: "operator".to_string(),
supports: true,
contradictory: false,
unambiguous: true,
}],
},
VerifyClaimInput {
text: "There are unresolved risk factors requiring further evidence".to_string(),
critical: false,
evidence: vec![EvidenceItem {
source: "autopilot-heuristic".to_string(),
tier: EvidenceTier::Tier2,
independence_group: "heuristic".to_string(),
supports: true,
contradictory: false,
unambiguous: false,
}],
},
]
}
fn make_id(prefix: &str) -> String {
let seq = ID_SEQ.fetch_add(1, Ordering::Relaxed);
format!("{prefix}-{}-{seq}", chrono::Utc::now().timestamp_millis())
}
fn build_branch_content(parent: &str, strategy: ExpandStrategy, idx: usize) -> String {
match strategy {
ExpandStrategy::Diverse => format!("{} | alternative path {}", parent, idx + 1),
ExpandStrategy::LeastToMost => format!("{} | subproblem {} then next", parent, idx + 1),
ExpandStrategy::Contrarian => format!("{} | contrarian challenge {}", parent, idx + 1),
ExpandStrategy::Direct => format!("{} | direct continuation {}", parent, idx + 1),
}
}
fn normalize_session(session_id: Option<&str>) -> String {
let trimmed = session_id.unwrap_or("").trim();
if trimmed.is_empty() {
DEFAULT_SESSION.to_string()
} else {
trimmed.to_string()
}
}
fn sanitize_limit(value: i64, fallback: usize, max: usize) -> usize {
if value <= 0 {
return fallback.min(max);
}
let value = value as usize;
value.clamp(1, max)
}
fn parse_int_env(name: &str, fallback: usize) -> usize {
std::env::var(name)
.ok()
.and_then(|v| v.parse::<i64>().ok())
.filter(|v| *v > 0)
.map(|v| v as usize)
.unwrap_or(fallback)
}
fn json_tool_success<T: Serialize>(data: &T) -> CallToolResult {
let text = serde_json::to_string_pretty(data).unwrap_or_else(|_| "{}".to_string());
CallToolResult::success(vec![Content::text(text)])
}
fn json_tool_error<T: Serialize>(data: &T) -> CallToolResult {
let text = serde_json::to_string_pretty(data).unwrap_or_else(|_| "{}".to_string());
CallToolResult::error(vec![Content::text(text)])
}
fn scan_record(record: &ThoughtRecord) -> Vec<SecurityWarning> {
let mut warnings = Vec::new();
warnings.extend(scan_text("thought", Some(record.thought.as_str())));
if let Some(steps) = &record.remaining_steps {
for (idx, step) in steps.iter().enumerate() {
warnings.extend(scan_text(
&format!("remaining_steps.{idx}"),
Some(step.as_str()),
));
}
}
if let Some(tools) = &record.available_tools {
for (idx, tool) in tools.iter().enumerate() {
warnings.extend(scan_tool_reference(tool, &format!("available_tools.{idx}")));
}
}
if let Some(recommended) = &record.recommended_tools {
for (idx, tool) in recommended.iter().enumerate() {
warnings.extend(scan_tool_recommendation(
tool,
&format!("recommended_tools.{idx}"),
));
}
}
warnings
}
fn sanitize_record(mut record: ThoughtRecord) -> ThoughtRecord {
record.thought = sanitize_text(&record.thought);
if let Some(tools) = &mut record.available_tools {
for tool in tools {
match tool {
ToolReference::Name(name) => *name = sanitize_text(name),
ToolReference::Detailed { name, description } => {
*name = sanitize_text(name);
if let Some(text) = description {
*text = sanitize_text(text);
}
}
}
}
}
if let Some(recommended) = &mut record.recommended_tools {
for tool in recommended {
tool.tool_name = sanitize_text(&tool.tool_name);
if let Some(rationale) = &mut tool.rationale {
*rationale = sanitize_text(rationale);
}
if let Some(alternatives) = &mut tool.alternatives {
for alt in alternatives {
*alt = sanitize_text(alt);
}
}
}
}
if let Some(steps) = &mut record.remaining_steps {
for step in steps {
*step = sanitize_text(step);
}
}
record
}
fn scan_tool_reference(tool: &ToolReference, field: &str) -> Vec<SecurityWarning> {
match tool {
ToolReference::Name(name) => scan_text(field, Some(name.as_str())),
ToolReference::Detailed { name, description } => {
let mut warnings = scan_text(&format!("{field}.name"), Some(name.as_str()));
warnings.extend(scan_text(
&format!("{field}.description"),
description.as_deref(),
));
warnings
}
}
}
fn scan_tool_recommendation(tool: &ToolRecommendation, field: &str) -> Vec<SecurityWarning> {
let mut warnings = scan_text(&format!("{field}.tool_name"), Some(tool.tool_name.as_str()));
warnings.extend(scan_text(
&format!("{field}.rationale"),
tool.rationale.as_deref(),
));
if let Some(alternatives) = &tool.alternatives {
for (idx, alt) in alternatives.iter().enumerate() {
warnings.extend(scan_text(
&format!("{field}.alternatives.{idx}"),
Some(alt.as_str()),
));
}
}
warnings
}
fn scan_text(field: &str, value: Option<&str>) -> Vec<SecurityWarning> {
let Some(value) = value else {
return Vec::new();
};
INJECTION_PATTERNS
.iter()
.filter(|(_, regex)| regex.is_match(value))
.map(|(name, _)| SecurityWarning {
field: field.to_string(),
pattern: (*name).to_string(),
})
.collect()
}
fn sanitize_text(value: &str) -> String {
let mut out = value.to_string();
for (_, regex) in INJECTION_PATTERNS.iter() {
out = regex.replace_all(&out, REDACTION).to_string();
}
out
}
fn validate_recommendations(input: &ThoughtRecord) -> Vec<ValidationIssue> {
let Some(recommended) = &input.recommended_tools else {
return Vec::new();
};
let Some(available) = &input.available_tools else {
return Vec::new();
};
let available_names: HashSet<String> = available.iter().map(tool_name).collect();
let mut issues = Vec::new();
for (idx, recommendation) in recommended.iter().enumerate() {
if !available_names.contains(recommendation.tool_name.as_str()) {
issues.push(ValidationIssue {
field: format!("recommended_tools.{idx}.tool_name"),
message: format!(
"Unknown tool \"{}\". Supply it in available_tools or remove the recommendation.",
recommendation.tool_name
),
});
}
if let Some(alternatives) = &recommendation.alternatives {
for (alt_idx, alt) in alternatives.iter().enumerate() {
if !available_names.contains(alt.as_str()) {
issues.push(ValidationIssue {
field: format!("recommended_tools.{idx}.alternatives.{alt_idx}"),
message: format!("Unknown alternative tool \"{alt}\"."),
});
}
}
}
}
issues
}
fn tool_name(tool: &ToolReference) -> String {
match tool {
ToolReference::Name(name) => name.clone(),
ToolReference::Detailed { name, .. } => name.clone(),
}
}
static INJECTION_PATTERNS: once_cell::sync::Lazy<Vec<(&str, regex::Regex)>> =
once_cell::sync::Lazy::new(|| {
vec![
(
"ignore-instructions",
regex::Regex::new(
r"\bignore\s+(all\s+)?(previous|prior|above|system|developer)\s+instructions?\b",
)
.expect("valid regex"),
),
(
"override-role",
regex::Regex::new(r"\b(system|developer)\s+(prompt|message|instruction)s?\b")
.expect("valid regex"),
),
(
"secret-exfiltration",
regex::Regex::new(
r"\b(reveal|print|dump|exfiltrate|leak|show)\s+(the\s+)?(secret|secrets|token|tokens|api\s*key|password|credentials?)\b",
)
.expect("valid regex"),
),
(
"tool-coercion",
regex::Regex::new(
r"\b(call|use|run|execute)\s+[^\n]{0,80}\b(tool|bash|shell|curl|wget)\b",
)
.expect("valid regex"),
),
(
"hidden-instruction",
regex::Regex::new(r"\bdo\s+not\s+(tell|mention|disclose|reveal)\b")
.expect("valid regex"),
),
]
});
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
enum ToolReference {
Name(String),
Detailed {
name: String,
#[serde(default)]
description: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ToolRecommendation {
tool_name: String,
#[serde(default)]
confidence: Option<f64>,
#[serde(default)]
rationale: Option<String>,
#[serde(default)]
priority: Option<i64>,
#[serde(default)]
suggested_inputs: Option<HashMap<String, Value>>,
#[serde(default)]
alternatives: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ThoughtInput {
#[serde(default)]
session_id: Option<String>,
thought: String,
thought_number: i64,
total_thoughts: i64,
next_thought_needed: bool,
#[serde(default)]
is_revision: Option<bool>,
#[serde(default)]
revises_thought: Option<i64>,
#[serde(default)]
branch_from_thought: Option<i64>,
#[serde(default)]
branch_id: Option<String>,
#[serde(default)]
needs_more_thoughts: Option<bool>,
#[serde(default)]
available_tools: Option<Vec<ToolReference>>,
#[serde(default)]
recommended_tools: Option<Vec<ToolRecommendation>>,
#[serde(default)]
remaining_steps: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ThoughtRecord {
session_id: String,
thought: String,
thought_number: i64,
total_thoughts: i64,
next_thought_needed: bool,
#[serde(default)]
is_revision: Option<bool>,
#[serde(default)]
revises_thought: Option<i64>,
#[serde(default)]
branch_from_thought: Option<i64>,
#[serde(default)]
branch_id: Option<String>,
#[serde(default)]
needs_more_thoughts: Option<bool>,
#[serde(default)]
available_tools: Option<Vec<ToolReference>>,
#[serde(default)]
recommended_tools: Option<Vec<ToolRecommendation>>,
#[serde(default)]
remaining_steps: Option<Vec<String>>,
created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ValidationIssue {
field: String,
message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct SecurityWarning {
field: String,
pattern: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ThoughtResult {
session_id: String,
thought_number: i64,
total_thoughts: i64,
next_thought_needed: bool,
#[serde(default)]
needs_more_thoughts: Option<bool>,
branches: Vec<String>,
history_length: usize,
#[serde(default)]
invalid_recommendations: Option<Vec<ValidationIssue>>,
#[serde(default)]
security_warnings: Option<Vec<SecurityWarning>>,
#[serde(default)]
recommended_tools: Option<Vec<ToolRecommendation>>,
#[serde(default)]
remaining_steps: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
struct GetHistoryInput {
#[serde(default)]
session_id: Option<String>,
#[serde(default)]
branch_id: Option<String>,
#[serde(default)]
limit: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct HistoryResult {
session_id: String,
branches: Vec<String>,
history_length: usize,
thoughts: Vec<ThoughtRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
struct ClearHistoryInput {
#[serde(default)]
session_id: Option<String>,
#[serde(default)]
all_sessions: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ClearResult {
#[serde(default)]
session_id: Option<String>,
cleared_sessions: usize,
cleared_thoughts: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
struct GuidancePromptInput {
#[serde(default)]
problem: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum ReasoningMode {
Cot,
Tot,
Got,
Reasonkit,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum ReasoningProfile {
Quick,
Balanced,
Deep,
Paranoid,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct Limits {
max_depth: i64,
max_nodes: i64,
max_branches_per_expand: i64,
max_verification_questions: i64,
}
impl Limits {
fn with_defaults(input: LimitsInput) -> Self {
Self {
max_depth: input.max_depth.unwrap_or(6).clamp(1, 32),
max_nodes: input.max_nodes.unwrap_or(128).clamp(4, 4096),
max_branches_per_expand: input.max_branches_per_expand.unwrap_or(5).clamp(1, 32),
max_verification_questions: input.max_verification_questions.unwrap_or(8).clamp(1, 64),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
struct LimitsInput {
#[serde(default)]
max_depth: Option<i64>,
#[serde(default)]
max_nodes: Option<i64>,
#[serde(default)]
max_branches_per_expand: Option<i64>,
#[serde(default)]
max_verification_questions: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct StartDeliberationInput {
#[serde(default)]
session_id: Option<String>,
#[serde(default)]
mode: Option<ReasoningMode>,
goal: String,
#[serde(default)]
constraints: Option<Vec<String>>,
#[serde(default)]
profile: Option<ReasoningProfile>,
#[serde(default)]
limits: Option<LimitsInput>,
#[serde(default)]
verification_policy: Option<VerificationPolicy>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct StartDeliberationResult {
deliberation_id: String,
session_id: String,
mode: ReasoningMode,
profile: ReasoningProfile,
limits_applied: Limits,
status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct SetVerificationPolicyInput {
deliberation_id: String,
verification_policy: VerificationPolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct SetVerificationPolicyResult {
deliberation_id: String,
verification_policy: VerificationPolicy,
status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ReasoningIntentRouterInput {
intent: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct SuggestedToolCall {
tool: String,
why: String,
arguments_template: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ReasoningIntentRouterResult {
intent: String,
inferred_modes: Vec<String>,
prompt_plan: Vec<String>,
tool_plan: Vec<String>,
resource_plan: Vec<String>,
suggested_tool_calls: Vec<SuggestedToolCall>,
notes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ReasoningAutopilotInput {
intent: String,
#[serde(default)]
session_id: Option<String>,
#[serde(default)]
quick_session_id: Option<String>,
#[serde(default)]
goal: Option<String>,
#[serde(default)]
constraints: Option<Vec<String>>,
#[serde(default)]
profile: Option<ReasoningProfile>,
#[serde(default)]
expand_count: Option<i64>,
#[serde(default)]
beam_width: Option<i64>,
#[serde(default)]
policy_override: Option<VerificationPolicy>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ReasoningAutopilotResult {
intent: String,
quick_session_id: String,
deliberation_id: String,
routed: ReasoningIntentRouterResult,
executed_tools: Vec<String>,
score_summary_count: usize,
verified_summary: VerificationSummary,
route_decision: RouteDecision,
confidence: f64,
policy_blocked: bool,
pipeline_route: RouteDecision,
audit_id: String,
notes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct SetReasoningAliasesInput {
aliases: ReasoningAliases,
#[serde(default)]
merge: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct SetReasoningAliasesResult {
aliases: ReasoningAliases,
status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ReasoningAliases {
cot: Vec<String>,
tot: Vec<String>,
got: Vec<String>,
verification: Vec<String>,
governance: Vec<String>,
auditability: Vec<String>,
cleanup: Vec<String>,
}
impl Default for ReasoningAliases {
fn default() -> Self {
Self {
cot: vec![
"cot".to_string(),
"chain-of-thought".to_string(),
"quick reasoning".to_string(),
"quick think".to_string(),
"linear reasoning".to_string(),
"step by step".to_string(),
"think step-by-step".to_string(),
"first pass".to_string(),
"fast pass".to_string(),
],
tot: vec![
"tot".to_string(),
"tree-of-thought".to_string(),
"branch".to_string(),
"alternatives".to_string(),
"deeper reasoning".to_string(),
"explore options".to_string(),
"compare options".to_string(),
"multiple paths".to_string(),
"what are our options".to_string(),
],
got: vec![
"got".to_string(),
"graph-of-thought".to_string(),
"merge paths".to_string(),
"join branches".to_string(),
"revisit branches".to_string(),
"graph reasoning".to_string(),
],
verification: vec![
"verify".to_string(),
"verification".to_string(),
"evidence".to_string(),
"triangulation".to_string(),
"claim".to_string(),
"fact-check".to_string(),
"validate".to_string(),
"prove".to_string(),
"confidence check".to_string(),
"hallucination".to_string(),
"source quality".to_string(),
"cross-check".to_string(),
],
governance: vec![
"governance".to_string(),
"risk".to_string(),
"route decision".to_string(),
"calibration".to_string(),
"go/no-go".to_string(),
"decision gate".to_string(),
"should we proceed".to_string(),
"policy gate".to_string(),
],
auditability: vec![
"audit".to_string(),
"trace".to_string(),
"artifact".to_string(),
"export".to_string(),
"postmortem".to_string(),
"explainability".to_string(),
"show your work".to_string(),
"prove reasoning".to_string(),
],
cleanup: vec![
"cleanup".to_string(),
"clear history".to_string(),
"reset".to_string(),
"flush session".to_string(),
"fresh session".to_string(),
"start clean".to_string(),
],
}
}
}
impl ReasoningAliases {
fn normalize(mut self) -> Self {
self.cot = normalize_alias_list(self.cot);
self.tot = normalize_alias_list(self.tot);
self.got = normalize_alias_list(self.got);
self.verification = normalize_alias_list(self.verification);
self.governance = normalize_alias_list(self.governance);
self.auditability = normalize_alias_list(self.auditability);
self.cleanup = normalize_alias_list(self.cleanup);
self
}
fn merge(&mut self, other: ReasoningAliases) {
extend_unique(&mut self.cot, &other.cot);
extend_unique(&mut self.tot, &other.tot);
extend_unique(&mut self.got, &other.got);
extend_unique(&mut self.verification, &other.verification);
extend_unique(&mut self.governance, &other.governance);
extend_unique(&mut self.auditability, &other.auditability);
extend_unique(&mut self.cleanup, &other.cleanup);
*self = self.clone().normalize();
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum ExpandStrategy {
Diverse,
LeastToMost,
Contrarian,
Direct,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ExpandThoughtsInput {
deliberation_id: String,
from_node_ids: Vec<String>,
#[serde(default)]
strategy: Option<ExpandStrategy>,
#[serde(default)]
count: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct DeliberationNode {
node_id: String,
parent_ids: Vec<String>,
content: String,
#[serde(default)]
branch_id: Option<String>,
created_at: String,
#[serde(default)]
score: Option<f64>,
tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ExpandThoughtsResult {
deliberation_id: String,
created_nodes: Vec<DeliberationNode>,
frontier_nodes: Vec<String>,
expansion_summary: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ScoreWeights {
correctness_proxy: f64,
evidence_quality: f64,
risk: f64,
cost: f64,
novelty: f64,
}
impl Default for ScoreWeights {
fn default() -> Self {
Self {
correctness_proxy: 0.30,
evidence_quality: 0.25,
risk: 0.20,
cost: 0.10,
novelty: 0.15,
}
}
}
impl ScoreWeights {
fn normalize(self) -> Self {
let sum =
self.correctness_proxy + self.evidence_quality + self.risk + self.cost + self.novelty;
if sum <= 0.0 {
return Self::default();
}
Self {
correctness_proxy: self.correctness_proxy / sum,
evidence_quality: self.evidence_quality / sum,
risk: self.risk / sum,
cost: self.cost / sum,
novelty: self.novelty / sum,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ScoreThoughtsInput {
deliberation_id: String,
node_ids: Vec<String>,
#[serde(default)]
weights: Option<ScoreWeights>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct NodeScore {
node_id: String,
total: f64,
correctness_proxy: f64,
evidence_quality: f64,
risk: f64,
cost: f64,
novelty: f64,
rationale_tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ScoreThoughtsResult {
deliberation_id: String,
scores: Vec<NodeScore>,
ranked_node_ids: Vec<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum PruneStrategy {
Beam,
Threshold,
Hybrid,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct PruneThoughtsInput {
deliberation_id: String,
#[serde(default)]
strategy: Option<PruneStrategy>,
#[serde(default)]
beam_width: Option<i64>,
#[serde(default)]
diversity_floor: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct PruneThoughtsResult {
deliberation_id: String,
kept_node_ids: Vec<String>,
pruned_node_ids: Vec<String>,
prune_report: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum VerificationMethod {
Cove,
Triangulation,
Hybrid,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct VerifyThoughtsInput {
deliberation_id: String,
node_ids: Vec<String>,
#[serde(default)]
critical_claims: Vec<String>,
#[serde(default)]
claims: Option<Vec<VerifyClaimInput>>,
#[serde(default)]
method: Option<VerificationMethod>,
#[serde(default)]
policy_override: Option<VerificationPolicy>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct VerificationPolicy {
min_independent_groups: i64,
allow_tier3_for_independence: bool,
require_tier1_unambiguous_for_critical: bool,
fail_closed_on_critical_unresolved: bool,
}
impl Default for VerificationPolicy {
fn default() -> Self {
Self {
min_independent_groups: 3,
allow_tier3_for_independence: false,
require_tier1_unambiguous_for_critical: true,
fail_closed_on_critical_unresolved: true,
}
}
}
impl VerificationPolicy {
fn normalize(self) -> Self {
Self {
min_independent_groups: self.min_independent_groups.clamp(1, 8),
allow_tier3_for_independence: self.allow_tier3_for_independence,
require_tier1_unambiguous_for_critical: self.require_tier1_unambiguous_for_critical,
fail_closed_on_critical_unresolved: self.fail_closed_on_critical_unresolved,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum EvidenceTier {
Tier1,
Tier2,
Tier3,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct EvidenceItem {
source: String,
tier: EvidenceTier,
independence_group: String,
supports: bool,
contradictory: bool,
unambiguous: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct VerifyClaimInput {
text: String,
#[serde(default)]
critical: bool,
#[serde(default)]
evidence: Vec<EvidenceItem>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum VerificationStatus {
Verified,
SourceConflict,
DataDeficit,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct VerificationEntry {
claim: String,
critical: bool,
status: VerificationStatus,
evidence: Vec<EvidenceItem>,
notes: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct VerificationSummary {
verified: usize,
source_conflict: usize,
data_deficit: usize,
}
impl VerificationSummary {
fn from_entries(entries: &[VerificationEntry]) -> Self {
let mut out = Self {
verified: 0,
source_conflict: 0,
data_deficit: 0,
};
for entry in entries {
match entry.status {
VerificationStatus::Verified => out.verified += 1,
VerificationStatus::SourceConflict => out.source_conflict += 1,
VerificationStatus::DataDeficit => out.data_deficit += 1,
}
}
out
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct VerifyThoughtsResult {
deliberation_id: String,
verification_matrix: Vec<VerificationEntry>,
claim_status_summary: VerificationSummary,
revisions: Vec<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
enum ConsensusMethod {
SelfConsistency,
WeightedVote,
VerifierWeighted,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
enum RouteDecision {
Proceed,
ProceedWithCaveats,
GatherMoreEvidence,
DeferToHuman,
RejectCurrentPlan,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ConsensusAnswerInput {
deliberation_id: String,
#[serde(default)]
method: Option<ConsensusMethod>,
#[serde(default)]
policy_override: Option<VerificationPolicy>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ConsensusAnswerResult {
deliberation_id: String,
final_answer: String,
confidence: f64,
route_decision: RouteDecision,
disagreement_report: String,
policy_blocked: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct RunReasonKitPipelineInput {
deliberation_id: String,
#[serde(default)]
stages: Option<Vec<String>>,
#[serde(default)]
profile: Option<ReasoningProfile>,
#[serde(default)]
policy_override: Option<VerificationPolicy>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct StageFinding {
stage: String,
finding: String,
confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct RunReasonKitPipelineResult {
deliberation_id: String,
stage_findings: Vec<StageFinding>,
route_decision: RouteDecision,
confidence_rationale: String,
evidence_gaps: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ExportReasoningAuditInput {
deliberation_id: String,
#[serde(default)]
include_raw_thoughts: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ReadReasoningResourceInput {
uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ExportReasoningAuditResult {
deliberation_id: String,
audit_id: String,
payload: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct ErrorEnvelope {
error: String,
}
#[derive(Debug, Clone)]
struct DeliberationSession {
deliberation_id: String,
session_id: String,
mode: ReasoningMode,
profile: ReasoningProfile,
goal: String,
constraints: Vec<String>,
limits: Limits,
verification_policy: VerificationPolicy,
nodes: HashMap<String, DeliberationNode>,
frontier: Vec<String>,
verification_matrix: Vec<VerificationEntry>,
stage_findings: Vec<StageFinding>,
route_decision: Option<RouteDecision>,
}
fn read_reasoning_resource_payload(store: &DeliberationStore, uri: &str) -> Result<Value, String> {
if uri == "reasoning://config/aliases" {
return Ok(json!({
"aliases": store.aliases,
}));
}
if let Some(schema_name) = uri.strip_prefix("reasoning://schemas/") {
return Ok(json!({
"schema": schema_name,
"version": 1,
}));
}
if let Some(rem) = uri.strip_prefix("reasoning://session/") {
let mut parts = rem.splitn(2, '/');
let session_id = parts
.next()
.ok_or_else(|| "invalid resource URI".to_string())?;
let kind = parts
.next()
.ok_or_else(|| "invalid resource URI".to_string())?;
let session = store
.deliberations
.get(session_id)
.ok_or_else(|| "unknown deliberation/session id".to_string())?;
let payload = match kind {
"graph" => json!({
"deliberation_id": session.deliberation_id,
"nodes": session.nodes,
}),
"frontier" => json!({
"deliberation_id": session.deliberation_id,
"frontier": session.frontier,
}),
"verification-matrix" => json!({
"deliberation_id": session.deliberation_id,
"verification_matrix": session.verification_matrix,
}),
"route-decision" => json!({
"deliberation_id": session.deliberation_id,
"route_decision": session.route_decision,
}),
_ => return Err("unknown resource kind".to_string()),
};
return Ok(payload);
}
Err("unsupported resource URI".to_string())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.with_writer(std::io::stderr)
.with_ansi(false)
.init();
let max_history_size = parse_int_env("MAX_HISTORY_SIZE", DEFAULT_MAX_HISTORY_SIZE);
tracing::info!(
version = env!("CARGO_PKG_VERSION"),
max_history_size,
"reasonkit-think-mcp starting (stdio)"
);
let service = ThinkServer::new(max_history_size)
.serve(stdio())
.await
.inspect_err(|e| tracing::error!(?e, "failed to start MCP stdio service"))?;
service.waiting().await?;
Ok(())
}