Skip to content

Add fastly-stats skill - #12

Merged
jedisct1 merged 9 commits into
fastly:mainfrom
zshenker:stats/add-fastly-stats-skill
Aug 11, 2026
Merged

jedisct1 merged 9 commits into
fastly:mainfrom
zshenker:stats/add-fastly-stats-skill

Conversation

@zshenker

Copy link
Copy Markdown
Contributor

Summary

Adds a new fastly-stats skill: a dedicated, self-contained reference for Fastly's stats/metrics APIs so an agent can get to the data without reading Fastly's docs — usable via the fastly CLI or by building raw URLs for curl/direct HTTP.

Stats were previously only lightly covered inside fastly-cli (references/stats.md, CLI-only). This skill goes deep on the full family of stats APIs across two hosts and three response shapes, with endpoints, query parameters, and response schemas documented inline.

What's included

skills/fastly-stats/
  SKILL.md                          # entrypoint: the two-hosts/three-shapes model,
                                    # auth + token safety, "choose the right API" table,
                                    # accurate CLI quick-reference, raw-URL templates,
                                    # workflows, POP-level diagnosis, limits
  references/
    historical-stats-api.md         # /stats* on api.fastly.com — from/to/by, flat envelope,
                                    # usage/billing (SI units, billable_units), regions
    inspector-api.md                # Origin & Domain Inspector, historical + real-time:
                                    # start/end/downsample, dimensions+values, cursor pagination
    realtime-api.md                 # rt.fastly.com /v1/channel polling model, Timestamp chaining
    fields.md                       # measurement/metric catalog (with TOC) + all_/compute_/waf_
    debugging.md                    # symptom-keyed troubleshooting

Also lists the skill in README.md. No manifest changes needed (skills are discovered by directory).

Design notes

  • Two hosts: api.fastly.com (historical) vs rt.fastly.com (real-time).
  • Three response shapes: classic flat {status, meta, msg, data}; Inspector dimensions+values with cursor pagination; real-time Data[].{recorded, aggregated, datacenter}.
  • Fixes an inaccuracy in the existing CLI docs: the inspector subcommands use --downsample/--metric/--group-by/--limit/--cursor (not --by/--field).
  • Complements the existing fastly-cli stats reference rather than replacing it (cross-referenced).
  • Copy-paste CLI + curl templates throughout; no bundled scripts to maintain.
  • Emphasizes the non-obvious pitfalls: NDJSON --json (use jq -s), decimal-SI vs binary units and raw-bandwidth-vs-billable-usage, UTC defaults, Origin/Domain Inspector as a paid add-on, real-time Timestamp chaining, cursor pagination, and preferring per-POP (datacenter) granularity (incl. flagging Shield POPs).

Validation

  • make validate ✓ (markdownlint, yamllint, jsonlint, frontmatter checks, plugin/extension validate)
  • make skillscheck (--strict) ✓ — 0 errors, 0 warnings

How it was tested

Developed and evaluated with the skill-creator workflow: with-skill vs. no-skill agent runs across three realistic tasks (cross-service bandwidth, live RPS/5xx watch, origin 5xx/latency), graded against explicit assertions. The skill scored 100% then 95% (against a stricter assertion set) vs. an 82% no-skill baseline, catching concrete errors the baseline made — mishandling NDJSON, missing cursor pagination, unprefixed Inspector metric names, and binary-GiB units.

🤖 Generated with Claude Code

@@ -0,0 +1,135 @@
# Verifying a Change With Stats

@jedisct1 jedisct1 Aug 8, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the purpose of that file.

It looks like a methodology essay on experimental design, with a thin Fastly costume.

It's a generic undergraduate lab-methods handout, telling the agent nothing about what Fastly actually returns., it seems to just tell the agent how to reason.

Figures seem to be based on some random test/example, and are not going to match a real users' account. None can be checked or reproduced.

I just teaches what the model already knows. There's zero unique Fastly content, so as Fastly documentation, it's not useful.

Can you clarify the intended purpose of that file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purpose question answered by deleting it — you were right. It was a methodology essay in a Fastly costume, its figures came from one past investigation and can't be reproduced on anyone else's account, and it taught the model things it already knows. Its actionable residue (baseline first, control POPs, sigma not percent, replicate, sanity-check ranges) is six bullets, which is what survives at the end of references/debugging.md; debugging.md already carried a shorter version of the same list, so nothing unique was lost.

Comment on lines +44 to +50
1. **A zero-to-nonzero transition.** If your change makes a POP start doing something it did not do
before, that metric going `0 → nonzero` needs no baseline model and has no confound. When a new
shield tier came online, its `shield` field went `0 → 209,432` per 120 s — nothing to dispute.
2. **A ratio within one window.** Hit ratio, offload, share-of-service. Immune to window
misjudgement because numerator and denominator come from the same samples.
3. **An absolute rate.** Weakest: needs a correct window *and* a baseline, and is the only form that
broke when a window was misread. If you must report one, print the derived window beside it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an agent skill, for agents, not a markdown document for humans.

There's too much prose (just increasing token usage) and formatting like ** and * is useless to agents.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed and applied across the whole skill, not just this file: all 189 **bold** spans are gone, prose is cut to ~5.0k words from ~10.7k, and the rules that decide correctness are a numbered list on the first screen of SKILL.md rather than emphasis buried in prose.

@jedisct1

jedisct1 commented Aug 8, 2026 •

Copy link
Copy Markdown
Collaborator

Evaluation https://github.com/fastly/fastly-agent-toolkit-evals/tree/main/additional-experiments/stats-pr12

Quoting the verdict:

  1. Cut the size. 11,743 words to 4,465, 7 files to 4. Delete references/verification-method.md, merge the three API reference files into one, drop the DEN/IAD/BOS war stories and all 189 bold spans, and move the eight rules that actually decide correctness to the top of SKILL.md. Today "use decimal SI, not GiB" is a sub-bullet in the tenth section, and Part 2 shows it decides two of the five tasks.

  2. Give every endpoint one owner. A session loads fastly, fastly-cli and fastly-stats together, so three copies of the /stats* table are three copies in the same context window. fastly-stats has the best version, so let it keep the material and cut the other two down to a pointer: observability.md 1,018 to 407 words, fastly-cli/references/stats.md 1,769 to 463. The routing data says the agent already behaves this way, opening fastly-stats and nothing else; the duplicates are paying rent for text nobody reads.

    This includes the unit, window and gauge rules that f6dc310 put into both neighbours. They are there because main has no fastly-stats to point at, and about 85 words of them are duplicated between observability.md and fastly-cli/references/stats.md. Merging PR Add fastly-stats skill #12 removes the reason: collapse them into fastly-stats, which owns the conventions, and revert both neighbours to pointer-only. skills-trim is already in that shape and scores 24/25 on both models, so nothing is lost by doing it.

  3. Lead with the CLI. 15 curl invocations become 5, and the 5 that survive are the ones fastly stats genuinely cannot reach. Every avoidable curl costs a token substitution, a URL-encoded time string and a hand-written envelope walk, and PR Add fastly-stats skill #12 spends a whole debugging section on a failure that only exists because of the substitution.

  4. Fix the three wrong claims in Part 4.4. An unentitled Inspector returns HTTP 200 with an empty array, not an error, and fastly-cli has said so correctly all along. from=1 day ago&by=day, the skill's own first worked call, returns zero rows. The realtime CLI example filters on .Data[]? and silently prints nothing. All three produce confident wrong answers rather than visible failures, which is the worst failure mode for a reference an agent trusts.

  5. Narrow the description so three skills stop competing for the same question.

Recommendation: merge, but merge the trimmed version. The 82%-overlap number in Part 1 is an argument for consolidating ownership.

@jedisct1

jedisct1 commented Aug 8, 2026 •

Copy link
Copy Markdown
Collaborator

Token/wall time usage with Deepseek 4-Flash:

       skills mcp    trials   turns   wall s   prompt tok
       base   nomcp      25      15      182      176,173
       stats  nomcp      25      10      106      109,659
       trim   nomcp      25      11       85       93,445
       base   mcp        25      19      221      212,918
       stats  mcp        25      14      160      170,207
       trim   mcp        25      10       93       97,773

Trimming the fastly-stats skill significantly reduces wall time and price.

zshenker and others added 8 commits August 10, 2026 21:13
New dedicated stats/metrics skill covering the Fastly CLI `stats`
commands and the raw Historical Stats, Real-Time analytics, and
Origin/Domain Inspector HTTP APIs. SKILL.md establishes the
two-hosts/three-response-shapes model, auth + token safety, a
"choose the right API" decision table, an accurate CLI quick
reference (inspector uses --downsample/--metric, NDJSON --json),
raw-URL templates, and common workflows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rences

- historical-stats-api.md: /stats* endpoints, from/to/by params, the
  flat {status,meta,msg,data} envelope, usage/billing, regions, legacy summary
- inspector-api.md: Origin/Domain Inspector historical (start/end/downsample/
  metric/group_by, dimensions+values envelope, cursor pagination) and real-time
- realtime-api.md: rt.fastly.com /v1/channel polling model, Timestamp chaining,
  AggregateDelay, response shape, and a correct poll loop

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… README

- fields.md: measurement/metric catalog (traffic, bytes, status, protocol,
  media, compute, security, real-time-only, meta) with a TOC and the
  all_/compute_/waf_ Inspector prefix explanation
- debugging.md: symptom-keyed troubleshooting (401/403, inspector-not-enabled,
  empty data, NDJSON, cursor pagination, real-time Timestamp misuse, casing)
- README: add fastly-stats to the Available skills list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…trict

Rephrase the frontmatter description away from user-centric trigger
phrasing ("even if the user just says") toward agent-directed wording
so `skillscheck --strict` reports 0 warnings, while keeping it broad
enough to trigger on bare "stats/metrics/analytics/traffic/usage" asks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the skill-creator eval review:
- Add a "Time and units" section: default to UTC when formatting; report
  bytes in decimal SI GB (1e9), not GiB; raw `bandwidth` != billable usage.
- Historical usage/billing: document SI units, request units of 10,000,
  header+body+origin composition, and billable_units; link the billing doc.
- Real-time: surface the `fastly stats realtime` CLI equivalent and use UTC
  (strftime) in examples.
- Inspector + fields: clarify edge processing time (hits_time/miss_time/
  pass_time in classic stats) vs origin latency histograms (Origin Inspector);
  note both are useful for edge-vs-origin diagnosis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From iteration-2 eval feedback: default to pulling the per-POP
(datacenter) breakdown when diagnosing, since a healthy-looking
aggregate hides a single erroring/slow POP. New "Pinpoint issues with
POP-level data" section in SKILL.md, reinforced in the real-time and
inspector references. Also call out Shield POPs — in a datacenter
breakdown the shield POP carries edge-to-shield traffic, not client
traffic, and must be identified (fastly pops SHIELD column) and
presented separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the shield-fields-and-window-traps plan. Every claim below was
verified against the live API on a shielded 3-tier service (DEN edge ->
IAD shield -> BOS shield -> origin) rather than written from docs.

Two of the plan's premises turned out to be wrong:

- Per-POP history DOES exist. `datacenter=` is honored, composes with
  by=minute|hour|day, and reconciles exactly (per-POP sum 392 = unfiltered
  total). Bad codes fail loudly ("invalid datacenter"). So the baseline rule
  is documented as prudence about retention (~1 day at by=minute), not
  impossibility.
- The collapse fields are `request_collapse_usable_count` /
  `_unusable_count`; the plan's suffix-less names return "Unknown field".

Found a worse trap than the plan predicted: sending `region=` alongside
`datacenter=` silently drops the POP filter — HTTP 200, meta.datacenter
null, whole-region numbers. The defense (assert `meta` echoes your filter)
is now documented, since the numbers themselves look fine.

Changes:

- fields.md: new "Shield & tier fields" section. shield_fetches vs
  origin_fetches is the one comparison that reveals tier topology; the
  *_resp_body_bytes direction trap is documented with the adjacency
  invariant that makes it self-checking (DEN.shield_fetch_resp_body_bytes
  == IAD.shield_resp_body_bytes == 339,506).
- fields.md: `Agg` column (counter/gauge/seconds/histogram) so aggregation
  shape travels with the field. Summing gauges is the most silently-wrong
  arithmetic available; origin_offload is confirmed to span header+body
  (documented formula reproduces the API's own value to the digit).
- fields.md: miss_histogram corrected — {millisecond_bucket: count} latency
  buckets, not time buckets.
- realtime-api.md: ts/h on /v1/channel verified working (returns up-to-120
  one-second records). "Never infer a time base from an endpoint name"
  callout; ts/h returns only seconds with traffic, so derive the window
  from `recorded` min/max rather than assuming 120.
- historical-stats-api.md: document GET /datacenters, and the three
  confusable groupings — region != stats_region, and `North-America`
  contains only four Canadian POPs and no US POPs, which is why
  region-level stats cannot isolate Canada.
- debugging.md: silently-wrong-scope entry, baseline-capture rule, and
  before/after attribution (controls, sigma-vs-percent).
- verification-method.md: new methods reference. NB the sigma figures and
  control-POP deltas are transcribed from the plan's session, not measured
  here — the field shapes are first-hand, the operational numbers are not.

Also fixes three bugs found while verifying, all in this skill's own docs:

- The documented auth pattern was broken. Bare `$(fastly auth token)`
  captures 174 bytes / 6 lines when a CLI upgrade is pending (the notice
  goes to stdout, so 2>/dev/null does not help), yielding curl: (43) or a
  spurious 401 while `fastly whoami` succeeds. Now `--quiet` throughout.
- `from=yesterday` resolves to 12:00 UTC, not midnight, silently dropping
  a morning — and it was used in a worked example.
- `fastly stats historical` has no `--datacenter`, so per-POP history is
  API-only; a table row implied a CLI equivalent that does not exist.

make ci passes (0 errors, 0 warnings); SKILL.md kept under the 5k-token
budget by pushing detail into references.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nership

Addresses the PR fastly#12 review. The eval campaign showed the skill's content is
right but its packaging is not: the same pass rate holds on 62% less text, at
17% fewer prompt tokens and 27% less wall clock.

Cut the size. 7 files to 4, ~10.7k words to ~5k. Delete verification-method.md
(its actionable residue is six bullets in debugging.md), merge the three API
reference files into references/api.md, drop the war stories, and remove every
bold span. The eight rules that decide correctness are now a numbered list at
the top of SKILL.md instead of sub-bullets in the tenth section.

Give every endpoint one owner. A session loads fastly, fastly-cli and
fastly-stats together, so three copies of the /stats* material is three copies
in one context window. fastly-stats keeps the endpoints and the unit, window and
gauge conventions; observability.md drops to 407 words and stats.md to 530, both
pointer-only for the material fastly-stats owns. stats.md keeps the flag matrix,
which is the CLI skill's job.

Lead with the CLI. Six curl invocations against eight fastly ones, and the raw
API section is scoped to the five things the CLI genuinely cannot reach.

Fix the three wrong claims, all verified against a live account:

- An unentitled Inspector returns HTTP 200 with "status":"success" and an empty
  data array, not an error. Confirmed on a service with both products off.
  fastly-cli had this right all along; that version is now the only one.
- from=1 day ago&by=day returns zero rows. A bucket is emitted only for a period
  wholly inside the window, so a relative from drops the ragged ends: measured
  live, "8 days ago" loses a day that explicit UTC boundaries return.
- The realtime CLI example filtered on .Data[]? and silently printed nothing.
  fastly stats realtime --json emits flat {recorded, aggregated, datacenter}
  per line with no Data wrapper; the corrected filter reports 17 requests over a
  15 s span where the old one reported nothing.

Every code block in the skill was executed against a live account before commit.

Two findings from that verification beyond the review:

- On a Compute service the traffic lands in compute_requests and requests stays
  0, so summing requests alone reports an idle service that is serving fine.
  Now rule 9, with the worked examples summing both.
- Sending region and datacenter together drops the POP filter and returns a meta
  that omits the datacenter key entirely rather than setting it to null.

Kept two details the trimmed proposal dropped: the publication-lag timings and
the reason datacenter= is unreachable from the CLI (missing from the SDK input
type, not just the flags). Rejected its claim that repeatable flags reject
comma-separated values; the CLI joins them with commas before the request, so
both forms are equivalent, as fastly-cli already documented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zshenker
zshenker force-pushed the stats/add-fastly-stats-skill branch from e830716 to 5377d16 Compare August 11, 2026 03:33
@zshenker

Copy link
Copy Markdown
Contributor Author

Thanks for the campaign — the skills-trim arm made this easy to act on, since it turned "this is too long" into a measured claim with a control. All five points are addressed in 5377d16, rebased onto current main.

1. Size. 7 files to 4, ~10.7k words to ~5.0k. verification-method.md deleted — your read of it was right, it was a methodology essay whose actionable residue is six bullets, and debugging.md already carried a shorter version of the same list. The three API references are merged into references/api.md. All 189 bold spans are gone, and the eight rules are now a numbered list at the top of SKILL.md rather than sub-bullets in the tenth section.

2. One owner. fastly-stats keeps the endpoints and the unit, window and gauge conventions. observability.md is at 407 words, fastly-cli/references/stats.md at 530, both pointer-only for the material fastly-stats owns — including the three rules f6dc310 had put in both neighbours. stats.md keeps the flag matrix, since that is genuinely the CLI skill's job.

3. CLI first. Six curl invocations against eight fastly ones, and the raw API section is scoped to the five things the CLI cannot reach.

4. The three wrong claims. All three reproduced against a live account before fixing:

  • Inspector off on both products → HTTP 200 {"status":"success","data":[]}. fastly-cli had it right; that version is now the only one, with the fastly products -s ID check alongside.
  • The relative-window bucket drop reproduced: from=8 days ago&by=day returns one row where explicit UTC boundaries over the same span return two. The Aug 3 bucket is silently lost.
  • The realtime filter: fastly stats realtime --json emits {aggregated, datacenter, recorded} per line, no Data. The old filter printed nothing; the replacement reports 17 requests over a 15 s span on the same captured stream.

5. Description narrowed to your wording.

Every code block in the skill was executed against a live account before commit, and that turned up two things worth adding:

  • Compute services report traffic in compute_requests and leave requests at 0. Summing requests alone reports an idle service that is serving fine — the same silently-plausible-wrong failure mode as the unit bug. It is now rule 9, and the worked examples sum both counters. Worth noting for the eval tasks too: a requests-only grader would mark a correct Compute answer wrong.
  • region + datacenter together returns a meta that omits the datacenter key entirely rather than setting it to null, so .meta.datacenter == "DEN" is the right assertion but != null is not.

Two places I did not follow skills-trim, both deliberate:

  • Kept the publication-lag timings (minute within 2-15 min, hourly within 15 min, daily ~2am UTC) and the reason datacenter= is unreachable from the CLI — it is missing from the SDK input type, not just the flags, which is what stops someone hunting for a flag combination that works. Both were unique to main's neighbours and would have been lost by reverting them to pointers.
  • Did not take "repeatable flags take one value per occurrence, not a comma-separated list." The CLI joins repeated flags with commas before the request goes out, so the two forms are equivalent — --metric responses --metric status_2xx and --metric responses,status_2xx both produce "metric":"responses,status_2xx" in meta. fastly-cli documented this correctly and I kept its wording.

make ci passes: 0 errors, 0 warnings. The only note is [4c.nesting] on api.md having one link to fields.md, which seems worth the merge.

@zshenker
zshenker requested a review from jedisct1 August 11, 2026 03:37
Comment thread skills/fastly-stats/references/api.md Outdated
Comment on lines +102 to +103
| `downsample` | `minute`, `hour` or `day`. This is the Inspector's `by`. |
| `metric` | Comma-separated metric names. This is the Inspector's `field`. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funky formatting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — Inspector param table, normalized by prettier along with the rest.

Comment thread skills/fastly-stats/references/api.md Resolved
Comment thread skills/fastly-stats/references/api.md Resolved
Comment thread skills/fastly-stats/SKILL.md Resolved
…payload

Run prettier over the fastly-stats files so table pipes align. The ragged
tables were hand-padded by eye and survived CI because .markdownlint.json
checks table structure (MD055/MD056) but not pipe alignment.

Rewrap rule 2 in SKILL.md so its inline code span no longer straddles a
newline; prettier dedents the numbered-list continuation in that case,
which breaks the list rendering.

Re-running the code blocks afterward turned up a real bug: the ts/h block
in api.md aborted with "null (null) cannot be subtracted" on a quiet
service, because max/min over an empty Data array both return null. That
is the exact HTTP 200 + empty-Data payload documented two paragraphs
above it. Guard the empty case and add the missing compute_requests term.

Verified live against a real account: hit ratio, 5xx share, bandwidth
ranking (reconciles exactly with account-wide usage), usage totals,
realtime rps, and ts/h on both a trafficked and a quiet service.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zshenker

Copy link
Copy Markdown
Contributor Author

Pushed e63bdb5 addressing the four formatting comments.

Formatting. Ran prettier@3.3.3 over the four fastly-stats files. SKILL.md and api.md needed changes; fields.md and debugging.md were already clean. Table pipe alignment is now 10 tables / 0 ragged. A word-per-line diff confirms the only content-bearing differences are separator-row dash counts.

These slipped through CI because .markdownlint.json checks table structure (MD055/MD056) but nothing checks pipe alignment. I deliberately did not add a formatter to the pipeline in this PR — it would reformat markdown across the whole repo and there are pre-existing ragged tables on main outside this skill. Happy to open that as a separate PR if you want it.

One prettier gotcha worth knowing for this file format: it dedents a numbered-list continuation line when an inline code span wraps across the newline, which breaks list rendering. That hit rule 2 in SKILL.md; I rewrapped so the span stays on one line.

Re-verification found a real bug, not just cosmetics. Re-running every code block after reformatting, the ts/h example in api.md aborted with null (null) cannot be subtracted on a service with no live traffic — max/min over an empty Data array both return null. That is precisely the HTTP 200 + empty-Data payload the same file documents two paragraphs above the example. Guarded the empty case and added the compute_requests term it was also missing.

Verified live against a real account: hit ratio, 5xx share, bandwidth ranking (its total reconciles exactly with account-wide fastly stats usage), usage totals, realtime rps, and ts/h against both a trafficked and a quiet service. make ci: 9 skills, 0 errors, 0 warnings.

@zshenker
zshenker requested a review from jedisct1 August 11, 2026 14:36
Comment on lines +162 to +163
Guard the empty case first: on a quiet service `max` and `min` over an empty array both return
`null` and the subtraction aborts the filter with `null (null) cannot be subtracted`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a code comment, not useful here.

@jedisct1

Copy link
Copy Markdown
Collaborator

Re-reviewed at e63bdb5. The packaging is now right: 4 files, one owner per endpoint family, rebases onto main cleanly, make ci reports 9 skills / 0 errors / 0 warnings. The three claims you corrected against my version are all correct and I re-measured each one on my account — unentitled Inspector answers 200 + "status":"success" + "data":[], region + datacenter returns a meta that echoes region and omits the datacenter key entirely (507,437 whole-region requests where SJC alone is 4,083), and fastly stats realtime --json emits flat {aggregated, datacenter, recorded} with no wrapper. Same for the new /service/{id}/stats/summary material, the stats_region list, the four Canadian North-America POPs, compute_sandboxes / compute_req_header_bytes, the origin_offload formula (API 0.14530569423201706 against the documented expression's 0.1453056942320171), and the N-1 day-bucket rule (0, 1 and 6 rows for 1, 2 and 7 days ago, against 31 for explicit July boundaries).

I also ran a sixth eval task, since none of the five existing ones can see the case rule 9 introduces: error-share verbatim against a Compute service instead of a VCL one, 5 repeats per arm on the same omlx / Qwopus3.6-27B setup. base 4/5, the PR at e63bdb5 4/5, the PR with the corrections below 5/5, which at n=5 is p = 1.00 and not the interesting part. The traces are:

arm pass turns wall s prompt tok
base 4/5 13 157 15,131
PR at e63bdb5 4/5 12 122 10,792
PR + corrections 5/5 11 108 10,652

All five trials on the PR branch ran the SKILL.md 5xx recipe as their first data call. Four noticed that zero errors was implausible, dumped the row's key list and recovered; the fifth wrote 5xx_responses: 0 and answered "no server errors at all" for a service that returned 168,981. Two of the four that recovered reached for compute_resp_status_5xx, which is the mirror-image trap since it reads 0 on a VCL service — all_status_5xx is the only one of the three that is right on both. Nothing in the corrected arm touched the bare name and all five wrote the exact figure. It lands as Part 6 of the existing campaign once I push the task and grader.

Five things to fix. The first four are one family: rule 9 is right, and the worked examples did not follow it.

1. The 5xx example reports zero errors on a Compute service.

The denominator got .requests + .compute_requests in this commit. The numerator still reads status_5xx, which is 0 on a Compute service for exactly the reason rule 9 gives. Run as written against a Compute service of mine:

{"requests": 227654417, "status_5xx": 0, "pct": 0}

The true count is 168,981, a 0.0742% share. all_status_5xx carries both service types: 168,981 there, and 1,103 on a VCL service where status_5xx is also 1,103. One field name.

2. fields.md presents all_ / compute_ / waf_ as Inspector prefixes, which is why (1) happened.

Classic /stats rows carry ten of them: all_status_2xx through all_status_5xx, all_hit_requests, all_miss_requests, all_pass_requests, all_error_requests, all_edge_hit_requests, all_edge_miss_requests. And GET /stats/service/{id}/field/all_status_5xx answers 200, so the skill's own "ask for it alone to test whether a name is real" recipe contradicts the sentence above it. The Status codes section lists status_1xx..status_5xx without mentioning that they are 0 on Compute or that an all_-prefixed twin exists.

3. The account-usage example under-reports by 8x, with no tell.

It sums [.[].requests] and reports 27,724,178 for an account that handled 255,380,898. Every region object carries compute_requests beside requests. This one worries me more than (1): a 5xx count of 0 looks wrong, whereas 27.7M looks like an answer.

4. The live-rate example never got the guard its ts/h sibling got in the same commit.

A service with no traffic emits no lines at all — fastly stats realtime --json printed nothing in 20 s on a quiet service of mine — so head -20 blocks rather than returning a short sample, and an early return dies on null (null) cannot be subtracted. Worth the same if length == 0 guard plus a line saying to bound the stream or use ts/h.

5. billable_units=true does not give a different figure from raw, only a rescaled one.

For July on my account, /stats/usage bandwidth, the sum of per-service /stats bandwidth, and /stats/usage_by_month bandwidth are all 3,349,138,213,801 bytes, and billable_units=true returns 3,349.138213801 GB with requests 2,772.4178 against 27,724,178. So "That is raw edge bandwidth. For the invoice figure use ..." and api.md's "so they exceed raw edge bandwidth" both promise a difference that is not there. bandwidth already includes the origin-bound request bytes: on one service, 2,442,423,451,498 is exactly resp_header_bytes 24,439,478,239 + resp_body_bytes 2,416,961,117,478 + bereq_header_bytes 1,022,855,781 + bereq_body_bytes 0. What the flag buys is the unit conversion and the month keying.

Two worth adding, since they are the same class of trap the skill already documents:

  • region= is a silent no-op on /stats/usage. ?region=europe returns all eleven regions, byte-identical to the unfiltered response, while fastly stats usage --region europe returns only the europe key — so the CLI and the raw URL disagree, and api.md lists region as a usage parameter without saying it does nothing there.
  • Inspector errors use problem+json: {"type":"https://fastly.help/metrics/validation-error","title":...,"status":400,"errors":[{"property","reason"}],"detail":...}. status is the string "success" on the happy path and the number 400 on the error path, and there is no msg. That matters because the skill tells the agent to assert on the envelope.

Packaging, two small ones:

  • The two neighbouring SKILL.md router tables were not updated. fastly/SKILL.md still sends "Querying stats, viewing real-time analytics, using domain/origin inspectors" to observability.md, which no longer has any of it, and fastly-cli/SKILL.md still sends "Historical/real-time metrics, cache hit ratios, error rates, bandwidth, regional traffic" to stats.md, which is now the flag matrix. Neither table names fastly-stats, so routing from the top of either skill costs a hop through a pointer file.
  • SKILL.md points at fastly-cli/references/stats.md as a path, which resolves nowhere from inside skills/fastly-stats/. Everywhere else in the toolkit a cross-skill reference names the skill.

Nits: "Five things the CLI cannot do" is followed by a paragraph naming two more, so it is seven. debugging.md files "a relative string sent to Inspector" under "Empty data or zero rows", but it fails loudly — 400 failed to parse 'start' time on the raw API, an argument error from the CLI. And --metric (repeatable, max 10) is the one claim I could not confirm: eleven metrics returned 200 with all eleven echoed in meta.metric, though Origin Inspector is off on my account so the request may not be validated.

@jedisct1
jedisct1 merged commit 9e53725 into fastly:main Aug 11, 2026
2 checks passed
@jedisct1

Copy link
Copy Markdown
Collaborator

Let's merge this.

Leaving Claude on its own doesn't work. The remaining issues will be fixed separately.

jedisct1 added a commit that referenced this pull request Aug 31, 2026
jedisct1 added a commit that referenced this pull request Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants