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.

Informações da origem

Repositório
elastic/example-mcp-app-observability
Última atividade na origem
7 de junho de 2026 às 15:03
Idioma detectado do SKILL.md
inglês
Estrelas
17
Forks
8

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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 ```
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub