swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/crates/fidget-spinner-cli/src/mcp/projection.rs
diff options
context:
space:
mode:
authormain <main@swarm.moe>2026-03-21 19:19:49 -0400
committermain <main@swarm.moe>2026-03-21 19:19:49 -0400
commite15fd4966e21bd8d31dbf580ede8a309c994816d (patch)
tree691d38d549959a59c02b982dd96cab9542dc3d85 /crates/fidget-spinner-cli/src/mcp/projection.rs
parent3a523c3c8ac1bf9094dbe65a6f53b71085438c0c (diff)
downloadfidget_spinner-main.zip
Sharpen frontier grounding and experiment comparatorsHEADmain
Diffstat (limited to 'crates/fidget-spinner-cli/src/mcp/projection.rs')
-rw-r--r--crates/fidget-spinner-cli/src/mcp/projection.rs67
1 files changed, 63 insertions, 4 deletions
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<String>,
pub(crate) roadmap: Vec<RoadmapItemProjection>,
pub(crate) unknowns: Vec<String>,
+ pub(crate) scoreboard_metric_keys: Vec<String>,
pub(crate) revision: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) updated_at: Option<TimestampText>,
@@ -106,6 +107,7 @@ pub(crate) struct FrontierListOutput {
pub(crate) struct FrontierOpenOutput {
pub(crate) frontier: FrontierOpenFrontierProjection,
pub(crate) active_tags: Vec<String>,
+ pub(crate) scoreboard_metrics: Vec<MetricKeySummaryProjection>,
pub(crate) active_metric_keys: Vec<MetricKeySummaryProjection>,
pub(crate) active_hypotheses: Vec<HypothesisCurrentStateProjection>,
pub(crate) open_experiments: Vec<ExperimentSummaryProjection>,
@@ -519,6 +521,32 @@ pub(crate) struct MetricBestOutput {
}
#[derive(Clone, Serialize)]
+pub(crate) struct ExperimentNearestHitProjection {
+ pub(crate) experiment: ExperimentSummaryProjection,
+ pub(crate) hypothesis: HypothesisSummaryProjection,
+ pub(crate) dimensions: BTreeMap<String, Value>,
+ pub(crate) reasons: Vec<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) metric_value: Option<MetricObservationSummaryProjection>,
+}
+
+#[derive(Clone, Serialize, libmcp::ToolProjection)]
+#[libmcp(kind = "read")]
+pub(crate) struct ExperimentNearestOutput {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) metric: Option<MetricKeySummaryProjection>,
+ pub(crate) target_dimensions: BTreeMap<String, Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) accepted: Option<ExperimentNearestHitProjection>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) kept: Option<ExperimentNearestHitProjection>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) rejected: Option<ExperimentNearestHitProjection>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) champion: Option<ExperimentNearestHitProjection>,
+}
+
+#[derive(Clone, Serialize)]
pub(crate) struct TagRecordProjection {
pub(crate) name: String,
pub(crate) description: 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 {