From e15fd4966e21bd8d31dbf580ede8a309c994816d Mon Sep 17 00:00:00 2001 From: main Date: Sat, 21 Mar 2026 19:19:49 -0400 Subject: Sharpen frontier grounding and experiment comparators --- crates/fidget-spinner-cli/src/mcp/projection.rs | 67 +++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) (limited to 'crates/fidget-spinner-cli/src/mcp/projection.rs') diff --git a/crates/fidget-spinner-cli/src/mcp/projection.rs b/crates/fidget-spinner-cli/src/mcp/projection.rs index a36e915..c93d3ec 100644 --- a/crates/fidget-spinner-cli/src/mcp/projection.rs +++ b/crates/fidget-spinner-cli/src/mcp/projection.rs @@ -6,10 +6,10 @@ use fidget_spinner_core::{ RunDimensionValue, TagRecord, }; use fidget_spinner_store_sqlite::{ - ArtifactDetail, ArtifactSummary, EntityHistoryEntry, ExperimentDetail, ExperimentSummary, - FrontierOpenProjection, FrontierSummary, HypothesisCurrentState, HypothesisDetail, - MetricBestEntry, MetricKeySummary, MetricObservationSummary, ProjectStore, StoreError, - VertexSummary, + ArtifactDetail, ArtifactSummary, EntityHistoryEntry, ExperimentDetail, ExperimentNearestHit, + ExperimentNearestResult, ExperimentSummary, FrontierOpenProjection, FrontierSummary, + HypothesisCurrentState, HypothesisDetail, MetricBestEntry, MetricKeySummary, + MetricObservationSummary, ProjectStore, StoreError, VertexSummary, }; use libmcp::{ ProjectionError, SelectorProjection, StructuredProjection, SurfaceKind, SurfacePolicy, @@ -56,6 +56,7 @@ pub(crate) struct FrontierBriefProjection { pub(crate) situation: Option, pub(crate) roadmap: Vec, pub(crate) unknowns: Vec, + pub(crate) scoreboard_metric_keys: Vec, pub(crate) revision: u64, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) updated_at: Option, @@ -106,6 +107,7 @@ pub(crate) struct FrontierListOutput { pub(crate) struct FrontierOpenOutput { pub(crate) frontier: FrontierOpenFrontierProjection, pub(crate) active_tags: Vec, + pub(crate) scoreboard_metrics: Vec, pub(crate) active_metric_keys: Vec, pub(crate) active_hypotheses: Vec, pub(crate) open_experiments: Vec, @@ -518,6 +520,32 @@ pub(crate) struct MetricBestOutput { pub(crate) entries: Vec, } +#[derive(Clone, Serialize)] +pub(crate) struct ExperimentNearestHitProjection { + pub(crate) experiment: ExperimentSummaryProjection, + pub(crate) hypothesis: HypothesisSummaryProjection, + pub(crate) dimensions: BTreeMap, + pub(crate) reasons: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) metric_value: Option, +} + +#[derive(Clone, Serialize, libmcp::ToolProjection)] +#[libmcp(kind = "read")] +pub(crate) struct ExperimentNearestOutput { + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) metric: Option, + pub(crate) target_dimensions: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) accepted: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) kept: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) rejected: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) champion: Option, +} + #[derive(Clone, Serialize)] pub(crate) struct TagRecordProjection { pub(crate) name: String, @@ -649,6 +677,11 @@ pub(crate) fn frontier_open(projection: &FrontierOpenProjection) -> FrontierOpen .iter() .map(ToString::to_string) .collect(), + scoreboard_metrics: projection + .scoreboard_metric_keys + .iter() + .map(metric_key_summary) + .collect(), active_metric_keys: projection .active_metric_keys .iter() @@ -863,6 +896,17 @@ pub(crate) fn metric_best(entries: &[MetricBestEntry]) -> MetricBestOutput { } } +pub(crate) fn experiment_nearest(result: &ExperimentNearestResult) -> ExperimentNearestOutput { + ExperimentNearestOutput { + metric: result.metric.as_ref().map(metric_key_summary), + target_dimensions: dimension_map(&result.target_dimensions), + accepted: result.accepted.as_ref().map(experiment_nearest_hit), + kept: result.kept.as_ref().map(experiment_nearest_hit), + rejected: result.rejected.as_ref().map(experiment_nearest_hit), + champion: result.champion.as_ref().map(experiment_nearest_hit), + } +} + pub(crate) fn tag_record(tag: &TagRecord) -> TagRecordOutput { TagRecordOutput { record: tag_record_projection(tag), @@ -963,6 +1007,11 @@ fn frontier_brief_projection( situation: brief.situation.as_ref().map(ToString::to_string), roadmap, unknowns: brief.unknowns.iter().map(ToString::to_string).collect(), + scoreboard_metric_keys: brief + .scoreboard_metric_keys + .iter() + .map(ToString::to_string) + .collect(), revision: brief.revision, updated_at: brief.updated_at.map(timestamp_value), } @@ -1142,6 +1191,16 @@ fn metric_best_entry(entry: &MetricBestEntry) -> MetricBestEntryProjection { } } +fn experiment_nearest_hit(hit: &ExperimentNearestHit) -> ExperimentNearestHitProjection { + ExperimentNearestHitProjection { + experiment: experiment_summary(&hit.experiment), + hypothesis: hypothesis_summary(&hit.hypothesis), + dimensions: dimension_map(&hit.dimensions), + reasons: hit.reasons.iter().map(ToString::to_string).collect(), + metric_value: hit.metric_value.as_ref().map(metric_observation_summary), + } +} + fn metric_observation_summary( metric: &MetricObservationSummary, ) -> MetricObservationSummaryProjection { -- cgit v1.2.3