Join keys
Find the label that names the same entity on both the Log10x side and your customer metric backend, so the two can be compared safely. Returns the best-matching label with per-candidate overlap, or "no shared label" instead of guessing. Used as a prerequisite step before the three-step cross-pillar flow (metrics_that_moved → rank_by_shape_similarity → metric_overlay).
Example
You
join key for logs ↔ metrics
Log10x
Found service (matches in 87% of overlap). Also considered: pod (54%, too many gone replicas), namespace (100%, too coarse with only 3 values), tenant (no shared values in window).
More to ask
- "re-run join keys, force refresh"
- "why did
podlose toservice?" - "join keys with 10m window"
Prerequisites
LOG10X_CUSTOMER_METRICS_URL configured.
Schema and samples
Input example
Real call against the demo env (captured by scripts/capture-tool-envelopes.mjs).
Input schema
Agent-facing JSON Schema (the canonical shape the MCP server publishes via tools/list):
{
"type": "object",
"properties": {
"force_refresh": {
"type": "boolean",
"default": false,
"description": "When true, bypass the session cache and re-run the Jaccard pass against the live backends."
},
"minimum_jaccard": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.7,
"description": "Minimum Jaccard similarity to accept as a primary join. Default 0.7. Lower to 0.5 for exploratory discovery, 0.3 for noisy environments with historical stale values."
},
"candidate_labels": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional subset of customer-side labels to probe. When omitted, all labels from the customer backend are probed in preferred-first order."
},
"window": {
"type": "string",
"description": "Time window for label value enumeration (e.g., \"10m\", \"1h\", \"30m\"). When set, both the Log10x and customer backends are queried with [now - window, now] filtering, excluding stale label values from series that stopped emitting samples. CRITICAL for environments with historical replay data, decommissioned pods, or otherwise orphan label values — stale values drag Jaccard down and cause false `no_join_available` refusals. Recommended: \"10m\" for steady-state clusters, \"1h\" for bursty traffic. Omit to include all-time values (default Prometheus behavior). Alias: `timeRange`."
},
"timeRange": {
"type": "string",
"description": "Alias for `window` for consistency with other Log10x tools."
},
"environment": {
"type": "string",
"description": "Environment nickname (for multi-env setups)."
}
},
"additionalProperties": false
}
Source: src/tools/discover-join.ts.
Output example
Real envelope from the demo env. view: "summary" returns the full StructuredOutput with typed data. Long arrays + base64 PNG bodies trimmed for readability; the real call returns them in full.
Headline (the 1-line agent-facing answer):
Join key: k8s_namespace ↔ k8s_namespace (Jaccard 1.000, 5 runner-ups).
{
"schema_version": "1.0",
"schema_epoch": "2026-05-25",
"tool": "log10x_discover_join",
"generated_at": "2026-05-26T15:38:01.892Z",
"view": "summary",
"summary": {
"headline": "Join key: k8s_namespace ↔ k8s_namespace (Jaccard 1.000, 5 runner-ups)."
},
"data": {
"status": "joined",
"backend": "log10x",
"endpoint": "https://prometheus.log10x.com",
"cached": true,
"labels_probed_log10x": [
"tenx_user_service",
"k8s_pod",
"k8s_namespace",
"... 3 more elided"
],
"labels_probed_customer": [
"TENX_Tenant",
"http_code",
"http_message",
"... 18 more elided"
],
"join_key": {
"log10x_side": "k8s_namespace",
"customer_side": "k8s_namespace",
"jaccard": 1,
"shared_values": 1,
"log10x_only_values": 0,
"customer_only_values": 0
},
"runner_ups": [
{
"log10x_side": "severity_level",
"customer_side": "severity_level",
"jaccard": 0.8333333333333334,
"shared_values": 5,
"log10x_only_values": 0,
"customer_only_values": 1
},
{
"log10x_side": "tenx_user_service",
"customer_side": "k8s_container",
"jaccard": 0.7391304347826086,
"shared_values": 17,
"log10x_only_values": 0,
"customer_only_values": 6
},
{
"log10x_side": "tenx_user_service",
"customer_side": "tenx_user_service",
"jaccard": 0.7391304347826086,
"shared_values": 17,
"log10x_only_values": 0,
"customer_only_values": 6
},
"... 2 more elided"
],
"top_below_threshold": []
},
"actions": [
{
"tool": "log10x_metrics_that_moved",
"args": {
"anchor_type": "log10x_pattern"
},
"reason": "step 1 of cross-pillar flow: find which customer metrics moved with a log10x pattern"
},
{
"tool": "log10x_metrics_that_moved",
"args": {
"anchor_type": "customer_metric"
},
"reason": "reverse direction: find which log10x patterns moved with a customer metric"
}
],
"truncated": false,
"warnings": []
}
Output schema
The data block inside the StructuredOutput envelope:
interface ToolData {
status: string;
backend: string;
endpoint: string;
cached: boolean;
labels_probed_log10x: string[];
labels_probed_customer: string[];
join_key: { log10x_side: string; customer_side: string; jaccard: number; shared_values: number; log10x_only_values: number; customer_only_values: number };
runner_ups: Array<{
log10x_side: string;
customer_side: string;
jaccard: number;
shared_values: number;
log10x_only_values: number;
customer_only_values: number;
}>;
top_below_threshold: unknown[];
}
Envelope-level fields the agent should also read: summary.headline (1-line answer), actions[] (next-call chain hints as {tool, args, reason}), truncated: boolean, images[] (PNG attachments where applicable), schema_epoch (engine-ID stability boundary).