← Fastly Agent ToolkitCONTENT HISTORY

Update to Fastly Agent Toolkit

Snapshot Oct 9, 2026 · 18:03 UTC · version 0.1.0

Collection source: downloaded plugin package.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Use when testing VCL against real Fastly edge infrastructure, writing assertion-based Fiddle tests, producing shareable fiddle URLs for bug reproductions, running VCL integration tests in CI, linting VCL remotely via the Fiddle API, or working with clientFetch/events/originFetches test expressions.",
  "included_files": [
    {
      "relative_path": "examples/robots.json",
      "size_in_bytes": 668
    },
    {
      "relative_path": "references/api.md",
      "size_in_bytes": 8386
    },
    {
      "relative_path": "references/falco-vs-fiddle.md",
      "size_in_bytes": 4028
    },
    {
      "relative_path": "references/spec-shape.md",
      "size_in_bytes": 12870
    },
    {
      "relative_path": "references/test-dsl.md",
      "size_in_bytes": 8675
    },
    {
      "relative_path": "scripts/run-fiddle.sh",
      "size_in_bytes": 27433
    }
  ],
  "name": "fastly-fiddle",
  "skill_md_contents": "---\nname: fastly-fiddle\ndescription: \"Use when testing VCL against real Fastly edge infrastructure, writing assertion-based Fiddle tests, producing shareable fiddle URLs for bug reproductions, running VCL integration tests in CI, linting VCL remotely via the Fiddle API, or working with clientFetch/events/originFetches test expressions.\"\n---\n\n# Fastly Fiddle — Real-Edge VCL Testing\n\n## Trigger and scope\n\nTrigger on: Fastly Fiddle, fiddle.fastly.dev URLs, the Fiddle HTTP API, CI testing of VCL services, real-edge VCL tests, shareable VCL reproductions, `clientFetch.*`/`events.*`/`originFetches.*` test expressions, `.test.js` / Mocha specs that target fiddles, SSE `updateResult` / `waitingForSync` events, remote VCL linting via Fiddle, or validating real Fastly behavior (geo, WAF, ESI, clustering, shielding) that local tools cannot simulate.\n\nDo NOT use for: local VCL unit testing (use `falco`), fast TDD loops (Fiddle has a 10-20s edge-sync floor per publish), Fastly Compute/Wasm testing (use `viceroy` or `fastlike`), production service deployment (use `fastly-cli`), or anything requiring authenticated Fastly API access — Fiddle does not use your Fastly API key.\n\nFastly Fiddle is a web-based sandbox at <https://fiddle.fastly.dev> that compiles and runs VCL on real Fastly edge nodes. Because it uses the production VCL compiler and real POPs, it's the only way outside a real service to test VCL features that depend on edge infrastructure — geolocation data, WAF, ESI, clustering, shielding, rate limiting, real TLS, and real cache behavior.\n\n**Official UI**: <https://fiddle.fastly.dev>\n**Demo CI runner**: <https://github.com/fastly/demo-fiddle-ci>\n**API base**: `https://fiddle.fastly.dev` (undocumented but stable; no auth required for public fiddles)\n\n## Prerequisites and public uploads\n\nThe helper requires Bash, `curl` 7.76 or newer, `jq`, and network access to `fiddle.fastly.dev`.\nIf a tool is missing, name it and provide installation guidance before running the helper.\n\nCreating or updating a fiddle uploads its VCL, origins, request headers, request bodies, and tests to a public service, including with `--lint-only`.\nOnly publish when the user has authorized a public Fiddle upload or shareable reproduction.\nFor a request limited to local testing, use Falco and keep the spec local.\nNever include credentials or private project data in a fiddle.\n\nSet `FIDDLE_SKILL_DIR` to the absolute directory containing this `SKILL.md`, using the installed skill location supplied by the client:\n\n```bash\nFIDDLE_SKILL_DIR=/absolute/path/to/fastly-fiddle\n```\n\nThis is a variable you assign, not a client-provided environment variable.\nKeep the user's project as the working directory so inputs such as `fiddle.json` and `spec.json` remain project-relative.\nBundled scripts and examples use `FIDDLE_SKILL_DIR` instead.\n\n## When Fiddle, when Falco\n\n| Need                                                  | Use                                |\n| ----------------------------------------------------- | ---------------------------------- |\n| Fast local iteration (< 1s), watch mode, offline      | `falco test`                       |\n| Real Fastly VCL compiler and semantics                | Fiddle                             |\n| Real `client.geo.*`, WAF, ESI, rate limiting, shield  | Fiddle                             |\n| Shareable URL for bug repros and support tickets      | Fiddle                             |\n| CI against real edge nodes                            | Fiddle                             |\n| Structured lint with line/col (no execution required) | Either                             |\n| Fastly Compute (WASM)                                 | Neither — use `viceroy`/`fastlike` |\n\nCommon workflow: iterate locally with `falco test` for speed, then push edge cases to Fiddle when you need real Fastly behavior or a shareable link. See [falco-vs-fiddle.md](references/falco-vs-fiddle.md) for full trade-offs.\n\n## Workflow: deliverable first, then the cheapest check that answers your question\n\nTwo rules keep you out of the slow path, which is what wastes time and gets\nagents killed by a wall-clock limit:\n\n1. **If your job is to produce a spec file, write it to disk first**, before\n   any network call. The file is the deliverable — it must exist even if a\n   later publish stalls on a cold edge-sync. Don't build the spec only inside\n   a `curl --data` argument and lose it when the call blocks.\n\n2. **Match the check to the question.** \"Does this VCL compile / is the spec\n   shape accepted?\" is answered by a _single_ `POST /fiddle` reading `valid` —\n   ~1-3s, no execution, no edge-sync wait (see gotcha #5). You only need the\n   full publish → execute → SSE round trip when you must observe _runtime_\n   assertion results (actual `clientFetch`/`originFetches`/`events` values),\n   and that pays the 10-120s edge-sync floor per publish. **Executing is the\n   exception, not the default** — reach for it deliberately, and always bound\n   your wait; never block indefinitely on the stream.\n\n### Lint-only (the common case): compile check, no execution\n\n```bash\nbash \"$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh\" --lint-only fiddle.json\n# {fiddle_id, url, valid, lintStatus}. Exit 0 = compiles, 2 = lint error\n# (details on stderr). No /execute, no SSE, no edge-sync wait.\n```\n\nThe raw equivalent is one call — POST and read `valid`:\n\n```bash\nUA='fiddle-skill-example/1.0'\ncurl -sS --max-time 30 -X POST https://fiddle.fastly.dev/fiddle \\\n  -H 'Content-Type: application/json' -H \"User-Agent: $UA\" \\\n  --data @fiddle.json | jq '{valid, lintStatus}'   # valid==true ⇒ it compiles\n```\n\n### Full round trip (only when you need runtime results)\n\nWhen you genuinely need to see assertions pass/fail on the edge, the bundled\nhelper handles publish → execute → SSE → completion-detection in one call,\nwith a bounded `--max-wait` (default 180s per attempt) so it can't hang:\n\n```bash\nbash \"$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh\" \"$FIDDLE_SKILL_DIR/examples/robots.json\"\n# Prints fiddle URL, then pass/fail JSON per assertion (including body_preview\n# and status by default). Exits non-zero on failure. Pass --no-bodies for\n# compact output.\n\n# Iterate against an already-published fiddle without paying edge-sync again:\nbash \"$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh\" --id <fiddle-id>           # re-execute (warmest, ~2s)\nbash \"$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh\" --id <fiddle-id> spec.json # PUT then execute\n```\n\nThe equivalent raw-curl flow, for reference or when the helper isn't available:\n\n```bash\nUA='fiddle-skill-example/1.0'\n\n# 1. Create. Capture the ID and the validity flag.\nRESP=$(curl -sS -X POST https://fiddle.fastly.dev/fiddle \\\n  -H 'Content-Type: application/json' -H 'Accept: application/json' \\\n  -H \"User-Agent: $UA\" \\\n  --data '{\n    \"origins\": [\"https://http-me.fastly.dev\"],\n    \"vcl\": {\n      \"init\": \"# Synthetic robots.txt response\\n# via error restart pattern\",\n      \"recv\": \"if (req.url.path == \\\"/robots.txt\\\") {\\n  error 601;\\n}\",\n      \"error\": \"if (obj.status == 601) {\\n  set obj.status = 200;\\n  synthetic {\\\"User-agent: BadBot\\\"};\\n  return(deliver);\\n}\"\n    },\n    \"requests\": [\n      { \"path\": \"/robots.txt\",\n        \"headers\": \"X-Custom: value\\nX-Other: second\",\n        \"tests\": [\"clientFetch.status is 200\", \"clientFetch.bodyPreview includes \\\"BadBot\\\"\"] }\n    ]\n  }')\nFID=$(echo \"$RESP\" | jq -r '.fiddle.id')\necho \"$RESP\" | jq '{valid, lintStatus}'   # bail here if valid is false\n\n# 2. Execute. Subscribe to the SSE stream IMMEDIATELY — session IDs expire fast.\nSID=$(curl -sS -X POST \"https://fiddle.fastly.dev/fiddle/$FID/execute?cacheID=1\" \\\n        -H 'Accept: application/json' -H \"User-Agent: $UA\" | jq -r '.sessionID')\n\n# 3. Stream results. Emits repeated `event: waitingForSync` (~10-20s on first\n#    publish) then `event: updateResult` with full pass/fail data. ALWAYS cap\n#    the stream with --max-time so a slow/cold sync can't block you forever;\n#    on timeout, re-execute the same $FID (warm, ~2s) rather than re-publishing.\ncurl -sS -N --max-time 120 -H \"User-Agent: $UA\" \"https://fiddle.fastly.dev/results/$SID/stream\"\n```\n\nFull protocol details in [api.md](references/api.md).\n\n## Wire-format gotchas\n\nNon-obvious behavior that will break tools round-tripping fiddles programmatically:\n\n1. **`vcl` on input, `src` on output.** You `POST {\"vcl\": {\"recv\": \"...\"}}` but `GET` returns `{\"src\": {\"recv\": \"...\"}}`. The server renames the key on normalization. Any tool that fetches a fiddle and re-publishes it must map `src` → `vcl` (or send `src` — both work on input). **Update existing fiddles with `PUT /fiddle/:id`** — same body shape as `POST`, but partial updates are not supported; omitted subroutines are cleared.\n\n2. **`tests` is a string on the wire.** You can send `tests: [\"a\", \"b\"]` but a subsequent `GET` returns `tests: \"a\\nb\"`. One assertion per line. Split on `\\n` when reading.\n\n3. **`headers` is a newline-joined string, not an array.** Unlike `tests` (which accepts both), `headers` must be a string: `\"headers\": \"User-Agent: BadBot/1.0\\nX-Custom: value\"`. An array will be rejected with a validation error.\n\n4. **Request fields are auto-defaulted by the server.** A `GET` of a fiddle you just created will include fields you didn't send: `method: \"GET\"`, `connType: \"h2\"` (HTTP/2 by default — matters for tests that depend on protocol), `enableCluster: true`, `enableShield: false`, `useFreshCache: false`, `sourceIP: \"client\"`, `followRedirects: false`, `delay: 0`. Set them explicitly if you care.\n\n5. **Invalid VCL still gets a fiddle ID.** `POST` returns `{valid: false, lintStatus: {...}, fiddle: {id, ...}}` for broken VCL. On a **create/update** (`POST`/`PUT`) response, `valid` is the lint result — `true` if the VCL compiles, `false` if not, with details in `lintStatus`. That's the number to trust for \"does this compile?\", and you get it without executing anything. Don't rely on HTTP status.\n\n    **But `valid` means something different on a `GET`.** There it tracks execution, not compilation: it stays `false` until the fiddle has been executed at least once, then flips to `true`. A fiddle that lints perfectly cleanly still reads back as `valid: false` right after you create it. So judge compilation from the create/update response (or by re-submitting the spec) — never from a `GET`. A `GET` can't distinguish a valid-but-not-yet-run fiddle from a genuinely broken one: both come back `valid: false` with an empty `lintStatus`.\n\n6. **VCL string concat with `+` rejects parenthesized operands.** `set X = \"used=\" + (a - b);` fails with a _misleading_ \"Remove the trailing `+` operator\" suggestion — the `+` is fine, the `(` is what the parser rejects. Compute the sub-expression into a local variable first. See [spec-shape.md](references/spec-shape.md#vcl-string-concatenation).\n\n7. **`error 8NN;` / `error 9NN;` is rejected by Fiddle lint — use 6xx.** Any 800–999 code fails with \"8xx and 9xx error codes are used internally by Fastly. Use 6xx instead.\", in any subroutine and any context (bare or inside `if`/`else`/`switch`). So the classic `error 801 <url>;` redirect idiom trips Fiddle lint even inside an `if` — use `error 602 \"<url>\";` and build the 301 in `vcl_error`. Codes 400–799 are accepted. See [spec-shape.md](references/spec-shape.md#error-codes-use-6xx-for-synthetics).\n\n8. **Some test expressions have built-in delays.** `originFetches.count() is 0` returns `asyncDelay: 2500` — the server waits 2.5s before evaluating \"did nothing happen?\". Client wait time must accommodate this; 45-60s is a safe ceiling.\n\n9. **SSE session IDs are short-lived.** Subscribe to `/results/<sessionID>/stream` within seconds of receiving the ID from `/execute`. Delayed connections get a 404. Always have the stream open before you start waiting on results.\n\n10. **Test DSL has unusual syntax.** No `.first()`, no `reqHeaderValue()`, no `isnt`/`empty` operators. Event objects expose only `url`/`method`/`return`/`status`/`ttl` — not arbitrary `req.http.*` values. **Read [test-dsl.md](references/test-dsl.md) before writing assertions.**\n\n11. **The server assigns a fresh fiddle ID on every `POST /fiddle`** — even for byte-identical input. IDs are not a content hash; back-to-back POSTs of the same body return different IDs, and each new ID needs its own edge-sync pass before `/execute` can produce results. **`PUT /fiddle/<id>`** keeps the URL stable (good for shared bug-repro links) but the new VCL still recompiles and propagates, so PUTs pay the same edge-sync cost as POSTs. The genuinely warm path is **re-executing an unchanged fiddle ID** — same content, repeat `/execute` calls finish in ~2s. Capture the ID from the first POST and reuse it; vary `cacheID` to force cold caches without re-publishing. (PUT is not partial — omitted subroutines are cleared, see #1.)\n\n12. **`originFetches.count() is N` is fragile under retries and shared `cacheID`.** A retry — automatic in `run-fiddle.sh`, or manual via `--id <fid>` — re-executes against the same `cacheID`, so any origin response cached on the previous attempt is now a HIT and `originFetches.count()` drops to 0. Two reliable fixes: set **`useFreshCache: true`** on the request (forces a fresh cache, ignoring the session `cacheID` — see [spec-shape.md `Request objects`](references/spec-shape.md#request-objects)), or assert via **`events.where(fnName=fetch).count()`** (counts subroutine entries, not network calls). The same goes for `originFetches[0].*` assertions whenever the test runs after a possible warmup.\n\n## Authoring conventions\n\nFiddles are read by humans in a browser. These aren't surprises, but they make shared fiddles useful:\n\n- **Set a `title`.** Makes fiddles findable in browser tabs, bookmarks, and shared links. Example: `\"title\": \"fastly_info.state: compound values deep dive\"`.\n- **Use `init` as a header comment.** The `init` subroutine renders first in the UI. Put a short summary there (~55 chars/line): `\"init\": \"# fastly_info.state: compound values\\n# Demonstrates MISS-CLUSTER, HIT-CLUSTER, HIT-SYNTH\"`.\n- **Format VCL with `\\n` and indentation** rather than cramming everything onto one line.\n- **Send `User-Agent: <tool>/<version>` on every API call.** Fiddle is unauthenticated shared infra; default `curl/x.y` or library UAs are bad citizenship. This is the API call's UA, not the simulated request's `headers`.\n\n## Testing in CI\n\nReference implementation: [fastly/demo-fiddle-ci](https://github.com/fastly/demo-fiddle-ci) — a Node + Mocha harness. Clone it and write your `{spec, scenarios[]}`.\n\nThe one non-obvious thing: it publishes the fiddle once, then re-executes the same fiddle ID per scenario with different `requests[]`. Only re-execution is warm (~2s); a fresh publish pays the 10-20s edge-sync floor (see \"Limits\" below and gotcha #11). Keep scenarios sequential.\n\n## References\n\n| Topic               | File                                                | Use when...                                                        |\n| ------------------- | --------------------------------------------------- | ------------------------------------------------------------------ |\n| **Helper script**   | [scripts/run-fiddle.sh](scripts/run-fiddle.sh)      | Publishing + executing + streaming a fiddle in one shell command   |\n| **Example payload** | [examples/robots.json](examples/robots.json)        | Starting from a known-good minimal fiddle spec                     |\n| **HTTP API**        | [api.md](references/api.md)                         | Calling Fiddle endpoints directly, driving it from any language    |\n| Fiddle spec shape   | [spec-shape.md](references/spec-shape.md)           | Building the JSON payload: origins, src, requests, defaults        |\n| Test DSL            | [test-dsl.md](references/test-dsl.md)               | Writing `clientFetch.*`, `events.where(...)`, `originFetches.*`    |\n| Falco vs Fiddle     | [falco-vs-fiddle.md](references/falco-vs-fiddle.md) | Choosing the right tool, or combining them in one workflow         |\n\n## Limits and cautions\n\n- **No auth required, no quota documented.** Be a good citizen: don't hammer the API in tight loops, and always send a descriptive `User-Agent` on API calls (see Authoring conventions). Use `cacheID` consistently across requests that need to share cache, and vary it to force cold caches.\n- **Edge-sync floor is ~10-20s per publish, sometimes much longer.** Applies per fiddle ID, not per unique content — and every `POST /fiddle` mints a new ID (see Wire-format gotchas #11), so re-publishing identical input still pays the full sync cost. Cold publishes regularly take 60-120s in practice. Unusable for TDD. Batch changes; execute once per meaningful delta; reuse IDs via `PUT` when iterating.\n- **Execution hops through a real POP** (tests observed running from IAD on node `kiad7000140`). Geographic assertions reflect wherever the fiddle executor landed.\n- **Fiddles are public by default.** Don't put secrets in VCL you publish.\n- **The API is undocumented.** Field names and behavior can change.\n"
}

SHA-256 of public snapshot: 01808f0aeddcaabc878b716293e8f42744c0ef7df76e72155ba1d7b4426dbead