Skip to main content

observe

The agent's Elastic-access primitive. Four modes: wait for an ML anomaly to fire, poll an ES|QL metric (live-sample or wait for a threshold), read a single-instance scalar value, or return a full ES|QL table. Use when the user says "tell me when...", "let me know if...", "wait until X drops below Y", "watch for anything unusual", "monitor for the next N minutes", "poll until stable", "what is X right now", "list …", "which … are …", or wants transient (session-scoped) monitoring or ad-hoc querying without creating a persistent Kibana rule. Also trigger for "keep an eye on" and post-remediation validation.

Quellinformationen

Repository
elastic/example-mcp-app-observability
Letzte Quellaktivität
7. Juni 2026 um 15:03
Erkannte Sprache von SKILL.md
Englisch
Sterne
17
Forks
8

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
observe
description
The agent's Elastic-access primitive. Four modes: wait for an ML anomaly to fire, poll an ES|QL metric (live-sample or wait for a threshold), read a single-instance scalar value, or return a full ES|QL table. Use when the user says "tell me when...", "let me know if...", "wait until X drops below Y", "watch for anything unusual", "monitor for the next N minutes", "poll until stable", "what is X right now", "list …", "which … are …", or wants transient (session-scoped) monitoring or ad-hoc querying without creating a persistent Kibana rule. Also trigger for "keep an eye on" and post-remediation validation.
# Observe Transient, session-scoped monitoring and ad-hoc querying. Unlike `manage-alerts` (which creates a durable saved object in Kibana), `observe` polls in-process and returns once fired, once its window closes, or — in `now` / `table` mode — immediately. ## Modes ### Decision tree — pick based on tense FIRST, then on shape ``` Does the user phrase it as past or future? PAST tense / windowed lookback FUTURE tense / live monitoring ("what WAS / over the past N / ("watch / monitor / wait until / in the last N / how did X look") live-sample / for the NEXT N") │ │ ▼ ▼ Is it a single number or a series? Is there a threshold to fire on? │ │ ┌───┴────┐ ┌──┴──┐ single series yes no │ │ │ │ ▼ ▼ ▼ ▼ now table metric metric (with (live- condition) sample) Other: • "tell me when anything unusual fires" → anomaly (default) • "list X / which X are Y / top N" (any tense) → table • "page me whenever..." → use manage-alerts instead ``` | Mode | When to pick it | Blocks? | |------|-----------------|---------| | `now` | **Past-tense windowed scalar.** "What was X right now / over the past 60 seconds / in the last 15 minutes / average X for the last hour". Put the window inside the ES\|QL via `WHERE @timestamp > NOW() - <window>`. Single number out. | Returns immediately | | `table` | **Past-tense time series OR group-by.** "How did X look over the past hour" (with `BUCKET()` for the chart), "list X", "which X are Y", any group-by / top-N. The view auto-charts 2-column time-series tables. | Returns immediately | | `metric` | **Forward-looking only.** "Watch X", "wait until X drops", "live-sample X for the next 60s", "wake me when X exceeds Y". Polls live for `max_wait` seconds. **Never use this for past-tense / "what was X" queries** — it will block for `max_wait` seconds before returning a value, which is the opposite of what the user asked. | Polls for `max_wait` seconds (default 60s) | | `anomaly` (default) | "Tell me when anything unusual fires", open-ended monitoring | Until an anomaly fires or `max_wait` elapses | > ⚠️ **Most common mistake: picking `metric` for a past-tense query.** A prompt like "what was the frontend memory over the past 60 seconds?" is asking about data that already exists — it's a windowed lookup, not a request to wait 60 more seconds and watch. Always inspect the verb before choosing `metric`: > > - **was / were / averaged / hit / spiked / over the past / in the last / for the last** → `now` or `table`. NEVER `metric`. > - **watch / monitor / poll / wait until / wake me / for the next / until X happens / live-sample** → `metric`. > > If the user wants durable alerting ("page me whenever..."), use `manage-alerts` instead. > > ⚠️ **Don't use observe for health rollups.** "Show me the health of X", "status of the X > environment", "how is X doing" — even with a time qualifier like "over the past hour" — should > route to `apm-health-summary`, not observe. observe is for raw-metric / single-query investigations; > apm-health-summary returns the full rollup (degraded services, anomalies, pod resources). Pick > apm-health-summary whenever the user is asking a HEALTH question rather than a specific metric > question. ## Prerequisites | Mode | Requires | |------|----------| | `anomaly` | Elastic ML anomaly detection jobs | | `metric` | Any ES\|QL-queryable numeric field | | `now` | Any ES\|QL-queryable numeric field | | `table` | Any ES\|QL-queryable data | ## How to call observe ### Anomaly mode (default) ```json { "mode": "anomaly", "min_score": 75, "max_wait": 600, "namespace": "otel-demo" } ``` - `min_score`: 75 default (major+), 50 for minor inclusion, 90 for critical-only. - `max_wait`: generous (600s default). Returns immediately on trigger — long waits are free. - `namespace`: only if the user scopes to a K8s namespace. ### Metric mode — threshold condition ```json { "mode": "metric", "esql": "FROM metrics-kubeletstatsreceiver.otel* | WHERE resource.attributes.k8s.pod.name == \"frontend-7d4b8f9c5-x2k9m\" | STATS v = AVG(metrics.k8s.pod.memory.working_set)", "condition": "< 80000000", "description": "frontend pod memory working set", "max_wait": 300 } ``` Condition format: `<comparator> <threshold>` — valid comparators: `<`, `<=`, `>`, `>=`, `==`. ### Metric mode — live sample (no threshold) Omit `condition` and the tool live-samples for the full `max_wait` window — use for "show me a live chart of X" prompts. The view renders an accumulating sparkline. ```json { "mode": "metric", "esql": "FROM metrics-kubeletstatsreceiver.otel* | WHERE resource.attributes.k8s.namespace.name == \"oteldemo-esyox-default\" | STATS v = AVG(metrics.k8s.pod.memory.working_set)", "description": "oteldemo-esyox-default avg pod memory", "max_wait": 60 } ``` ### Now mode — single read ```json { "mode": "now", "esql": "FROM metrics-kubeletstatsreceiver.otel* | WHERE resource.attributes.k8s.namespace.name == \"oteldemo-esyox-default\" | STATS v = AVG(metrics.k8s.pod.memory.working_set)", "description": "current avg pod memory in oteldemo-esyox-default" } ``` **Past-tense windowed read** — when the user asks "what was X over the past N seconds/minutes/hours", put the window in the ES\|QL `WHERE` clause, not in `max_wait`. The tool returns immediately with the aggregate; nothing is polled. ```json { "mode": "now", "esql": "FROM metrics-kubeletstatsreceiver.otel* | WHERE resource.attributes.k8s.pod.name == \"frontend-7d4b8f9c5-x2k9m\" AND @timestamp > NOW() - 60 seconds | STATS v = AVG(metrics.k8s.pod.memory.working_set)", "description": "frontend memory, last 60 seconds (avg)" } ``` If the user wants the time series (not just one number), use `table` mode with `BUCKET()` instead — that returns rows the view can chart. ### Table mode — full ES|QL rows and columns Use when the query groups, lists, or returns mixed-type rows (strings + numbers + dates). `now` mode discards everything except the first numeric cell — `table` mode preserves the whole result. ```json { "mode": "table", "esql": "FROM metrics-kubeletstatsreceiver.otel* | WHERE metrics.k8s.pod.memory.working_set IS NOT NULL | STATS avg_mem = AVG(metrics.k8s.pod.memory.working_set) BY resource.attributes.k8s.pod.name, resource.attributes.k8s.namespace.name | SORT avg_mem DESC | LIMIT 10", "description": "top 10 pods by memory" } ``` **Past-tense time series** — use `table` with `BUCKET()` to return one row per time slice the view can chart: ```json { "mode": "table", "esql": "FROM metrics-kubeletstatsreceiver.otel* | WHERE resource.attributes.k8s.pod.name == \"frontend-7d4b8f9c5-x2k9m\" AND @timestamp > NOW() - 60 seconds | STATS v = AVG(metrics.k8s.pod.memory.working_set) BY bucket = BUCKET(@timestamp, 5 second) | SORT bucket ASC", "description": "frontend memory · 60s · 5s buckets" } ``` Rows are capped at 50 by default. Prefer tightening the ES|QL with `LIMIT` / `SORT` over raising `row_cap` — very wide tables clog the context window. ## Picking the right index pattern Fields live where the data is emitted — ES|QL rejects queries that reference a field the target index doesn't map (`verification_exception`). Before writing the query, match the user's question to the right layer: | User asks about… | Index | Carries | |---|---|---| | Node / pod / namespace topology, resource usage | `metrics-kubeletstatsreceiver.otel*` or `metrics-*` | `k8s.node.name`, `k8s.pod.name`, `k8s.namespace.name`, `service.name` (via resource attrs), CPU/memory/fs gauges | | Service behavior — latency, errors, throughput, spans | `traces-*.otel-*` or `traces-apm*` | `service.name`, `transaction.duration.us`, `event.outcome`, `span.*` | | Log rate / log content | `logs-*` | `message`, `log.level`, `service.name` | | ML anomalies | `.ml-anomalies-*` | `record_score`, `by_field_value`, `partition_field_value` | Cross-layer questions ("which **node** runs the most **services**") need the index that carries **both** fields — that's almost always `metrics-*`, because OTel resource attributes propagate through the Collector, so metrics docs carry `k8s.node.name` *and* `service.name`. Trace indices (`traces-apm*`, `traces-*.otel-*`) don't carry infra attributes like `k8s.node.name` — don't reach for them when the question is about nodes. Example: "which node is running the most services" ``` FROM metrics-* | WHERE @timestamp > NOW() - 5m AND k8s.node.name IS NOT NULL AND service.name IS NOT NULL | STATS service_count = COUNT_DISTINCT(service.name) BY k8s.node.name | SORT service_count DESC | LIMIT 20 ``` ## Common query patterns These are the field paths this deployment's data actually uses — prefer them over guessing. > ⛔ **BEFORE WRITING ANY QUERY — check the field type first:** > > Look up the field you intend to aggregate in the tables below. If its type is `counter_long`, **stop — do not write `FROM`**. Use `TS` + `RATE()` as described in the "Counter fields" section. This is a hard syntax requirement: `FROM` is rejected for `counter_long` fields regardless of which aggregation function you use, and the error cannot be fixed by changing `AVG` to `MAX` or `SUM`. The source command must be `TS`. ### OTel Kubernetes (kubeletstats receiver) Index: `metrics-kubeletstatsreceiver.otel*` Each kubeletstats scrape emits **separate documents per metric** — a CPU doc, a memory doc, a network doc, etc. Always filter `WHERE <field> IS NOT NULL` for the field you're aggregating, otherwise most rows carry nulls for it. **Gauge fields — use `AVG` / `MAX` / `MIN`, never `SUM`:** | Signal | Field | Type | |--------|-------|------| | Pod memory working set | `k8s.pod.memory.working_set` | `long` (bytes) | | Pod memory RSS | `k8s.pod.memory.rss` | `long` (bytes) | | Pod memory available | `k8s.pod.memory.available` | `long` (bytes) | | Pod CPU usage | `k8s.pod.cpu.usage` | `double` (cores — 1.0 = one full core) | | Pod filesystem usage | `k8s.pod.filesystem.usage` | `long` (bytes) | | Node memory working set | `k8s.node.memory.working_set` | `long` (bytes) | | Node memory available | `k8s.node.memory.available` | `long` (bytes) | | Node CPU usage | `k8s.node.cpu.usage` | `double` (cores) | | Node filesystem usage | `k8s.node.filesystem.usage` | `long` (bytes) | > For **counter** fields (network I/O, network errors, uptime), see the "Counter fields" section below — these require `TS` + `RATE()`. **Fields that may NOT exist in every cluster's kubeletstats schema** — depends on whether the OTel collector / kubelet is configured to scrape pod-spec data: | Field | When it's missing | What to do | |---|---|---| | `metrics.k8s.pod.cpu.limit` | Pods don't declare CPU limits, OR the collector isn't scraping pod-spec | Don't reference it — use raw `metrics.k8s.pod.cpu.usage` and report cores instead of % utilization | | `metrics.k8s.pod.memory.limit` | Same as above for memory | Same — report bytes instead of % | | `metrics.k8s.container.restart_count` | Collector isn't scraping pod-status (this is COMMON) | Don't reference it. Use APM error rates or alert reasons as a proxy for "things crashing." There is no canonical alternative field name (`k8s.pod.restarts` is wrong — don't try it) | **Schema-aware error recovery.** When ESQL returns `Unknown column [X], did you mean [Y, Z]?`, read the suggestion and retry with one of `Y` or `Z` — don't blindly retry the same query or guess a different name from memory. **Dimension fields — use for filtering and `BY` grouping:** | Dimension | Unprefixed | Prefixed (equivalent) | |---|---|---| | Pod name | `k8s.pod.name` | `resource.attributes.k8s.pod.name` | | Namespace | `k8s.namespace.name` | `resource.attributes.k8s.namespace.name` | | Node name | `k8s.node.name` | `resource.attributes.k8s.node.name` | | Cluster name | `k8s.cluster.name` | (same) | Both forms work on `metrics-kubeletstatsreceiver.otel*`. Prefer the unprefixed form — it's shorter and also works on counter-field queries via `TS`. **Common recipes:** Top pods by memory (last 5m, across all namespaces): ``` FROM metrics-kubeletstatsreceiver.otel* | WHERE @timestamp > NOW() - 5 minutes AND k8s.pod.memory.working_set IS NOT NULL | STATS avg_mem = AVG(k8s.pod.memory.working_set), max_mem = MAX(k8s.pod.memory.working_set) BY k8s.pod.name, k8s.namespace.name | SORT max_mem DESC | LIMIT 20 ``` Which pods are on a specific node: ``` FROM metrics-kubeletstatsreceiver.otel* | WHERE @timestamp > NOW() - 5 minutes AND k8s.node.name == "<node>" AND k8s.pod.name IS NOT NULL | STATS last_seen = MAX(@timestamp) BY k8s.pod.name, k8s.namespace.name | SORT last_seen DESC ```
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen