Shared memory and context tools for agentic work.
Code Rooms
// === m1nd-mcp/src/protocol/perspective.rs ===
// Input/Output types for the 12 perspective MCP tools.
// From 12-PERSPECTIVE-SYNTHESIS, PRD sections 5-14.
use serde::{Deserialize, Serialize};
use crate::perspective::state::{
AffinityCandidate, Diagnostic, PeekContent, PerspectiveLens, PerspectiveMode, Route,
RouteFamily, SuggestResult,
};
// ---------------------------------------------------------------------------
// perspective.start (PRD §6)
#[derive(Clone, Debug, Deserialize)]
pub struct PerspectiveStartInput {
pub agent_id: String,
/// Seed query for route synthesis.
pub query: String,
/// Optional: anchor to a specific node (activates anchored mode).
#[serde(default)]
pub anchor_node: Option<String>,
/// Optional: starting lens configuration.
pub lens: Option<PerspectiveLens>,
}
#[derive(Clone, Debug, Serialize)]
pub struct PerspectiveStartOutput {
pub perspective_id: String,
pub mode: PerspectiveMode,
pub focus_node: Option<String>,
/// Initial route set (first page).
pub routes: Vec<Route>,
pub total_routes: usize,
pub page: u32,
pub total_pages: u32,
pub route_set_version: u64,
pub cache_generation: u64,
pub suggested: Option<String>, // e.g. "inspect R03"
pub proof_state: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_suggested_tool: Option<String>,
pub next_suggested_target: Option<String>,
pub next_step_hint: Option<String>,
// perspective.routes (PRD §7)
pub struct PerspectiveRoutesInput {
/// Page number (1-based). Default: 1. Must be >= 1.
#[serde(default = "default_page")]
/// Page size. Clamped to [1, 10]. Default: 6.
#[serde(default = "default_page_size")]
pub page_size: u32,
/// Required: route_set_version from previous response. Staleness check.
pub route_set_version: Option<u64>,
pub struct PerspectiveRoutesOutput {
pub mode_effective: String, // "anchored" or "local" (degraded if >8 hops from anchor)
pub anchor: Option<String>,
pub focus: Option<String>,
pub lens_summary: String, // compact one-line lens description
pub suggested: Option<String>,
/// Diagnostic if routes are empty (Theme 12).
pub diagnostic: Option<Diagnostic>,
/// Warning if all routes are from one family.
pub family_diversity_warning: Option<String>,
pub dominant_family: Option<RouteFamily>,
/// Whether page_size was clamped.
pub page_size_clamped: bool,
// perspective.inspect (PRD §9)
pub struct PerspectiveInspectInput {
/// Exactly one of route_id or route_index must be provided.
pub route_id: Option<String>,
pub route_index: Option<u32>,
/// Route set version for staleness check.
pub struct PerspectiveInspectOutput {
pub route_id: String,
pub route_index: u32,
pub family: RouteFamily,
pub target_node: String,
pub target_label: String,
pub target_type: String,
/// Fuller path preview.
pub path_preview: Vec<String>,
/// Route family explanation.
pub family_explanation: String,
/// Stronger metrics than the route list.
pub score: f32,
pub score_breakdown: InspectScoreBreakdown,
/// Provenance summary.
pub provenance: Option<InspectProvenance>,
/// Whether peek is available.
pub peek_available: bool,
/// Affinity candidates for this route (Theme 12).
pub affinity_candidates: Vec<AffinityCandidate>,
/// Total chars in this response (for Theme 5 cap enforcement).
pub response_chars: usize,
pub struct InspectScoreBreakdown {
pub local_activation: f32,
pub path_coherence: f32,
pub novelty: f32,
pub anchor_relevance: Option<f32>, // None in local mode
pub continuity: Option<f32>, // None in local mode
pub struct InspectProvenance {
pub source_path: Option<String>,
pub line_start: Option<u32>,
pub line_end: Option<u32>,
pub namespace: Option<String>,
pub provenance_stale: bool,
// perspective.peek (PRD §10)
pub struct PerspectivePeekInput {
pub struct PerspectivePeekOutput {
/// Security-checked peek content.
pub content: PeekContent,
// perspective.follow (PRD §8 implied)
pub struct PerspectiveFollowInput {
pub struct PerspectiveFollowOutput {
pub previous_focus: Option<String>,
pub new_focus: String,
pub mode_effective: String,
/// New routes from the new focus.
// perspective.suggest (PRD §11)
pub struct PerspectiveSuggestInput {
pub struct PerspectiveSuggestOutput {
pub suggestion: SuggestResult,
// perspective.affinity (PRD §12)
pub struct PerspectiveAffinityInput {
pub struct PerspectiveAffinityOutput {
/// Epistemic notice (Theme 13).
pub notice: String, // "Probable connections, not verified edges."
/// Up to 8 candidates (Theme 5 cap). Min threshold: 0.15.
pub candidates: Vec<AffinityCandidate>,
// perspective.branch (PRD implied)
pub struct PerspectiveBranchInput {
/// Optional branch name. Auto-generated if not provided.
pub branch_name: Option<String>,
pub struct PerspectiveBranchOutput {
/// The new branch's perspective_id.
pub branch_perspective_id: String,
pub branch_name: String,
pub branched_from_focus: Option<String>,
// perspective.back (PRD §14.2)
pub struct PerspectiveBackInput {
pub struct PerspectiveBackOutput {
pub restored_focus: Option<String>,
pub restored_mode: PerspectiveMode,
/// New routes from the restored focus.
// perspective.compare (PRD §9 implied)
pub struct PerspectiveCompareInput {
/// Two perspective IDs to compare. Must be same agent (V1 restriction).
pub perspective_id_a: String,
pub perspective_id_b: String,
/// Dimensions to compare on. Empty = all.
pub dimensions: Vec<String>,
pub struct PerspectiveCompareOutput {
pub shared_nodes: Vec<String>,
pub unique_to_a: Vec<String>,
pub unique_to_b: Vec<String>,
pub dimension_deltas: Vec<DimensionDelta>,
/// Total chars (for Theme 5 cap of 3000).
/// Warning if comparing across different generations.
pub generation_mismatch_warning: Option<String>,
pub struct DimensionDelta {
pub dimension: String,
pub score_a: f32,
pub score_b: f32,
pub delta: f32,
// perspective.list (Theme 2 — management tool)
pub struct PerspectiveListInput {
pub struct PerspectiveListOutput {
pub perspectives: Vec<PerspectiveSummary>,
pub total_memory_bytes: usize,
pub struct PerspectiveSummary {
pub route_count: usize,
pub nav_event_count: usize,
pub stale: bool,
pub created_at_ms: u64,
pub last_accessed_ms: u64,
// perspective.close (Theme 2 — management tool)
pub struct PerspectiveCloseInput {
pub struct PerspectiveCloseOutput {
pub closed: bool,
/// Locks that were cascade-released (Theme 5).
pub locks_released: Vec<String>,
// Default helpers
fn default_page() -> u32 {
1
fn default_page_size() -> u32 {
6
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn start_input_deserializes_minimal() {
let json = r#"{"agent_id": "jimi", "query": "session management"}"#;
let input: PerspectiveStartInput = serde_json::from_str(json).unwrap();
assert_eq!(input.agent_id, "jimi");
assert_eq!(input.query, "session management");
assert!(input.anchor_node.is_none());
assert!(input.lens.is_none());
fn routes_input_defaults() {
let json = r#"{"agent_id": "jimi", "perspective_id": "persp_jimi_001"}"#;
let input: PerspectiveRoutesInput = serde_json::from_str(json).unwrap();
assert_eq!(input.page, 1);
assert_eq!(input.page_size, 6);
fn inspect_input_requires_route_ref() {
// Both route_id and route_index are optional at serde level;
// validation layer rejects if neither is provided.
let json = r#"{"agent_id": "jimi", "perspective_id": "p1", "route_set_version": 100}"#;
let input: PerspectiveInspectInput = serde_json::from_str(json).unwrap();
assert!(input.route_id.is_none());
assert!(input.route_index.is_none());
fn follow_input_accepts_route_id() {
let json = r#"{"agent_id": "jimi", "perspective_id": "p1", "route_id": "R_abc123", "route_set_version": 100}"#;
let input: PerspectiveFollowInput = serde_json::from_str(json).unwrap();
assert_eq!(input.route_id.as_deref(), Some("R_abc123"));
fn compare_input_deserializes() {
let json = r#"{"agent_id": "jimi", "perspective_id_a": "p1", "perspective_id_b": "p2"}"#;
let input: PerspectiveCompareInput = serde_json::from_str(json).unwrap();
assert!(input.dimensions.is_empty()); // empty = all