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).