Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions skills/agent-plugin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,37 @@ hotspots).
- One statement per call; results are capped (a truncation note tells you when) — narrow
the query or paginate. **Stable pagination requires `ORDER BY`**: `SKIP` without an
`ORDER BY` gives no guaranteed order between calls, so pages can overlap or miss rows.
- **Edge properties are a JSON string in Cypher.** `r.ready`, `r.operations`, `r.via` are
null on a relationship; the props live in `r.props_json`. Read them with
`apoc.convert.fromJsonMap(r.props_json).ready`, or use `get_related` /
`get_resource_details`, which return them parsed.

## Observed topology is not current traffic

Every edge is an observation with an age, not a live probe:

- APM edges (`CALLS_TO`, `USES_DATASTORE`, `PRODUCES_TO`, `CONSUMED_BY`, `USES_ENDPOINT`)
**accumulate**: a dependency seen once stays until pruned, and `get_resource_details`
lists a July edge next to today's. Pass `max_age_hours` (24 for "current") to
`get_related`, or filter on `r.currentAsOf` in Cypher, before saying "A currently calls B".
Read `observedAt` on every edge you cite.
- Structural Kubernetes edges (`EXPOSES`, `SCHEDULED_ON`, `CONTROLS`, …) are maintained
from the cluster and carry no `observedAt`; they say what the control plane declared,
not that packets flow. `EXPOSES {ready}` is the endpoint's readiness at the last
EndpointSlice the agent shipped.
- "Receives traffic right now" is a claim only a live source supports (APM within
`max_age_hours`, or the native tool below). Report graph edges as "declared / last
observed at T", not as present-tense traffic.

## Verify current-state claims with a native source

When the session also mounts a native source for the layer in question (`kubectl`, a cloud
CLI, the APM, PagerDuty), confirm any **current-state** claim there before reporting it as
current: which pods back a Service, whether a resource is reachable, who is on call, what
an incident is about. The graph is the evidence for topology, relationships and history;
the native source is the evidence for "right now". Cite both, and say which one each
statement rests on. When no native source is mounted, keep the claim time-stamped
("observed at T") rather than present-tense.

## Safety and trust

Expand Down
10 changes: 8 additions & 2 deletions skills/agent-plugin/references/query-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ mandatory diagnosis step list, and do not encode alert-specific conclusion recip
- `get_resource_events` (`ref`/`refs` + `from`/`to` window, `type`/`source` filters) for its change history;
- `get_related` for its graph neighborhood — read the NEIGHBOURHOOD SUMMARY block
first (exact edge counts per relationship type from the degree store), then drill
into one type with `relationship_types` + a higher `limit`; event edges are
summarized only (`get_resource_events` lists them);
into one type with `relationship_types` + a higher `limit`; pass `max_age_hours`
(24) when the question is about CURRENT APM topology, since those edges accumulate;
event edges are summarized only (`get_resource_events` lists them);
- `get_correlated_events` for a full incident chain from a `correlationId`;
- `get_recent_events` for the project-wide feed — `since`+`until` give a two-sided
window; `type`/`source`/`label`/`only_root`/`exclude_noise_classes` cut volume
Expand Down Expand Up @@ -44,6 +45,11 @@ mandatory diagnosis step list, and do not encode alert-specific conclusion recip
## Reporting discipline

- Cite `hashedID`s and event timestamps for every load-bearing claim.
- An edge is not live traffic. APM edges carry `observedAt`: report them as "last observed
at T". Structural Kubernetes edges carry none: report them as declared state ("the
Service selects these pods"), never as traffic. Confirm any present-tense claim (current
backends, current callers, current on-call, what an incident concerns) with a native
source when one is mounted (`kubectl`, cloud CLI, APM, PagerDuty).
- Distinguish observed platform events from provider API records when it matters.
- An empty result is not proof of absence — say what you searched and its bounds.
- Do not claim causality from temporal proximity alone.
43 changes: 43 additions & 0 deletions skills/agent-plugin/references/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,49 @@ project has no data for that kind, which is not the same finding. Property names
(`replicas`, `storageClass`, `image`) are per-source: confirm with `get_resource_details`
on one node first.

## PagerDuty incident → owning service → workload

"Which incident is about this workload?" and "what does this incident concern?" resolve
through the PagerDuty **service**, never through the incident title: titles are free text
and the loudest one is not the relevant one. The graph stores
`PAGERDUTY_ALERT -[:TRIGGERED]-> PAGERDUTY_INCIDENT -[:AFFECTS]-> PAGERDUTY_SERVICE`, and
`PAGERDUTY_SERVICE -[:RESOLVES_TO]-> <workload>` when the service is mapped (an
`anyshift.resource_id=` / `anyshift.qualified_name=` directive in the service description,
or a service name / URL that normalizes to one hostname). Labels appear in `describe_schema`
only when the project has the PagerDuty integration.

From a workload (seed by `hashedID` from `find_resources`):

```cypher
MATCH (w:RESOURCE:ALIVE {hashedID: '<workload hashedID>'})<-[:RESOLVES_TO]-(s:PAGERDUTY_SERVICE:ALIVE)
OPTIONAL MATCH (i:PAGERDUTY_INCIDENT:ALIVE)-[:AFFECTS]->(s)
WHERE i.status IN ['triggered', 'acknowledged']
OPTIONAL MATCH (a:PAGERDUTY_ALERT:ALIVE)-[:TRIGGERED]->(i)
RETURN s.name AS service, i.hashedID AS incidentId, i.title AS incident, i.status AS status,
i.urgency AS urgency, count(DISTINCT a) AS alerts
ORDER BY urgency, incident LIMIT 50
```

From an incident (its `hashedID` from `find_resources` on the incident label):

```cypher
MATCH (i:RESOURCE {hashedID: '<incident hashedID>'})-[:AFFECTS]->(s:PAGERDUTY_SERVICE:ALIVE)
OPTIONAL MATCH (s)-[:RESOLVES_TO]->(w:ALIVE)
RETURN i.title AS incident, i.status AS status, s.name AS service,
collect(DISTINCT CASE WHEN w IS NULL THEN null
ELSE {cluster: w.clusterID, workload: coalesce(w.namespace + '/', '') + coalesce(w.name, w.hashedID), hashedID: w.hashedID} END) AS workloads
```

Keep `incidentId` in the first RETURN (titles repeat, so grouping on title alone merges
concurrent incidents) and `cluster`/`hashedID` in the collected workloads (the same
`namespace/name` exists once per cluster).

Caveats: an empty `workloads` means the PagerDuty service is **unmapped**, not that the
incident concerns nothing — say so and fall back to the service name. Incident `status`
and the on-call roster are current only in PagerDuty itself: confirm there before
reporting who is paged or whether an incident is still open. Property names (`title`,
`status`, `urgency`) are per source; confirm on one node with `get_resource_details`.

## Events scoped to one cluster (or namespace)

"What happened in cluster X yesterday?" on a multi-cluster project must never be answered
Expand Down
Loading