Commit Graph

63 Commits

Author SHA1 Message Date
Justin d0c4a7b2a1 feat(runpod-migrate): GraphQL / REST v1 → REST v2 migration skill (#46)
* feat(runpod-migrate): add GraphQL / REST v1 -> REST v2 migration skill

Users migrating off REST v1 / GraphQL hand the job to a coding agent rather
than reading docs, so the skill is the surface that reaches them. It inventories
a codebase by API version, rewrites the call sites, and flags what breaks.

- SKILL.md: scope -> inventory -> brief breaking changes -> plan -> migrate ->
  verify -> summarize. Summary separates "required" from "cleanup".
- scripts/rp_api_inventory.py: stdlib-only scanner. Buckets call sites by
  generation (GraphQL / v1 / v1 field names / already-v2 / serverless job API /
  SDK wrappers), suppresses hits that carry v2 context, and keeps prose and
  rp-migrate:keep-v1 rollback paths out of the plan so --fail-on-legacy works
  in CI.
- reference/: v1->v2 and GraphQL->v2 mapping tables, breaking changes split
  into loud renames vs same-name-different-behavior vs removed capability,
  v2 unlocks keyed to what the code already does, the RUNPOD_API_V1 rollback
  flag, and a worked before/after migration.
- Catalog reads always request include=AVAILABILITY.

Mappings and error shapes were checked against the live v2 API; resources
created for verification were deleted. Two spec-vs-behavior gaps are documented
as observed: `timeout` comes back 0 rather than the documented 300000 default,
and a bad image is 500 {"error"} on v1 but 422 {title,status,detail} on v2.

* fix(runpod-migrate): unblock the inventory step and correct audit findings

Review pass over the skill: an independent accuracy audit against freshly
fetched specs plus read-only live calls, and execution of the commands the
docs tell users to run.

Blocking:
- The documented scanner invocation could not work. `python3
  scripts/rp_api_inventory.py .` assumes the script is in the user's repo,
  but it ships in the installed skill directory while the agent's working
  directory is the customer's project. Step 1 failed with "No such file",
  and every later step depends on it. Now resolved via CLAUDE_PLUGIN_ROOT,
  the skill's own directory, or a search, with a self-check. Verified from a
  simulated plugin install into a simulated customer repo, and asserted in
  the inventory eval.
- The GPU pool lookup one-liner was a Python SyntaxError (escaped quotes in
  an f-string expression), on the command that resolves a GPU type to its
  serverless pool.

Corrections, each re-verified against the specs before applying:
- Template `category` was already an enum with the same values and default
  in v1; it is not a v2 tightening.
- `minCudaVersion` and `volumeEncrypted` were listed as dropped from v1 pod
  create; neither was ever in PodCreateInput.
- The 422 table omitted `gpu` from the endpoint-create required list — the
  field most likely to be lost in a gpuTypeIds -> gpu.pools rewrite.
- globalNetworking was framed as a v2 unlock; the flag existed in v1, the
  response object is what is new.
- Unknown-field rejection is `unevaluatedProperties: false` on the composed
  Create/Update schemas, not `additionalProperties: false` everywhere.
- Rate-limit headers are omitted for rate-limit-exempt callers.
- The 400-vs-422 rule conflated schema and resource-level errors.

Added, both confirmed:
- Class 2: GraphQL `stockStatus` is High/Medium/Low/None but v2
  `availability` is HIGH/MEDIUM/LOW/NONE. A rank table carried across stops
  matching silently.
- Class 2: v2 `mounts` is far more restricted on PATCH than v1's
  volumeInGb/volumeMountPath — kind fixed at create, volumeId immutable,
  clearing rejected, partial entries rejected.
- `mounts.persistent` is deprecated and host-pinned, so a literal
  volumeInGb translation inherits data-loss-on-host-failure.

Rejected one audit finding: the GraphQL network-volume mutations are real —
rphttp2, the v2 REST server, issues them — so the mapping row and scanner
signal stay. The public schema page simply omits them, as it omits
saveEndpoint and saveTemplate; graphql-to-v2.md now warns readers not to
treat that page as complete.

* fix(runpod-migrate): repair the verify gate and the gaps a cold run exposed

A fresh agent was given only the skill and the terse prompt "migrate my
codebase from runpod v1 to v2", ran the whole workflow on a fixture, and
reported back. Findings, verified and fixed.

--fail-on-legacy reported "4 files need migration" on a correctly migrated
tree. A gate that fails green builds gets deleted, and it teaches the agent
to stop trusting step 5. Two causes, both fixed:
- Comment detection only matched whole-line comments, so the skill's own
  house style -- `"image": image,  # was imageName` -- scored as a live v1
  field. Comment detection is now quote-aware and handles trailing comments
  (and does not mistake the `//` in `https://` for one).
- v2 context was matched per line, so `f"{BASE}/pods"` with `BASE` defined
  eleven lines up read as a leftover v1 path. Base-URL constants are now
  derived per file. The hardcoded name guesses (V2_BASE, v2_base, V2_URL)
  are gone -- the migrated fixture had been passing only because it happened
  to use one of them, which meant the corpus was written to match the
  scanner rather than the other way round.

Shipped-code fixes:
- The GPU fallback snippet treated any non-201 as scarce capacity, so a 422
  walked the entire preference list and raised "no GPU available" -- maximally
  misleading during a migration, when 422 is the expected failure. Now fast-
  fails on 422 and keeps the response body in the error.

Missing mappings, both silent failures:
- Billing `amount` -> `totalAmount`. The naive port is a KeyError, and it sits
  one line from the far quieter /billing/endpoints trap.
- `dockerStartCmd` -> `args` joins an argv array into one string. Joining
  ["bash","-lc","python x.py"] naively runs python as the -lc script text.
  No 422, no crash -- just the wrong command. Added as Class 2.
- `volumeInGb: 0` has no `mounts` translation; omit `mounts` entirely.

Workflow:
- Step 3 gains a third bucket, "decisions the user must make", so Class 3
  capability loss has somewhere to land. Without it the cold run dropped
  minRAMPerGPU and minVcpuCount with a code comment -- the exact silent drop
  breaking-changes.md forbids.
- The scope instruction told the agent to ask when unclear, then supplied a
  table resolving every phrasing, so it never fired. Now: infer, state, move
  on; the real question is the step 3 fork.
- File order is dependency-blind (fewest call sites first), which put a
  consumer ahead of the transport helper it imports. Shared helpers first.
- Commit hygiene forbade mixing behavior and version changes while the
  opening pitch advertised one of those behavior changes as the payoff.
  Reconciled: improvements land in the cleanup bucket.
- `rp-migrate: ignore` existed in the scanner but appeared nowhere in
  SKILL.md, so the only documented escape hatch was `keep-v1` -- which
  records a lie when used on a false positive. Both markers now documented
  with the distinction, plus the residual cases the gate can still misread.

Placement: the 25-line benefits pitch opened the file but only pays off at
step 6; trimmed to four lines pointing at unlocks.md. The GPU fallback
rebuild -- the largest piece of new code a migration needs -- is now
signposted from step 4 where it is used, not only from step 2 where it lives.

* test(runpod-migrate): add scanner corpora and wire them into CI

The inventory scanner is the only executable in this repo, and its signal
table is 60+ interacting regexes with `unless` clauses — a one-line edit
breaks a distant case silently. Until now the only thing catching that was
me running fixtures by hand on one laptop, and those fixtures were not in
the repo.

Adds testdata/runpod-migrate/ (4 corpora, 68 KB) plus
hooks/check_migrate_scanner.py, run by CI alongside the other four hooks.
15 assertions, every one corresponding to a defect that actually shipped
during review:

- provision.js contains no "runpod" string, only GraphQL operation names.
  A hand-written prefilter skipped it entirely.
- report.py has no URL, no import, no operation name — only a renamed
  *response* field. Same prefilter bug, and the class of file a v2 rename
  breaks silently in the callers of a wrapper.
- submit_job.py calls api.runpod.ai/v2 — the job API, one letter from the
  control plane. Flagging it makes an agent rewrite working job submission.
- correct-v2/ is the load-bearing corpus: the scanner once reported "4 files
  need migration" on a correctly migrated tree. A verify gate that fails on
  correct code gets deleted, and teaches agents to distrust the verify step.

Deliberately at repo root, not under skills/runpod-migrate/: marketplace.json
installs that directory, so anything inside it ships to every user who
installs the plugin.

Mutation-tested rather than assumed — reverting the trailing-comment fix, the
per-file base-URL derivation, or the job-API bucket each turns the hook red.
live_verify.py stays out of the repo: it rents a real GPU and must never run
in CI.

* docs(runpod-migrate): teach the agent to check the spec itself

The skill handed the agent mapping tables and no way to question them. Both
OpenAPI documents are public and need no auth, but SKILL.md never said so —
the URLs appeared once, in a reference file, with no instruction to use them.

That matters more than a missing link. v2 is actively developed, so the
tables are a snapshot with a verification date; and the reference docs
already record two places where the running API contradicts its own spec.
An agent that hits a 422 naming a field it cannot place had no documented
recourse except guessing.

Adds a "Ground truth" section with commands verified by running them
verbatim:
- fetch either spec
- print a request body's fields with required ones starred, resolving allOf
  composition (a naive read of the raw JSON misses it, which is how `gpu`
  went missing from the documented endpoint-create required list)
- find which schemas mention a field name, for decoding a 422

And a precedence rule: observed live behavior > spec > these tables. Where
the API contradicts the spec, trust the API and say so in the summary, so
the user learns a documented default cannot be relied on. Drift in the
tables should be reported, not silently worked around.

Notes that GraphQL cannot be checked this way — introspection is disabled
and the public reference page is incomplete.

* docs(runpod-migrate): cover every resource with an inline before/after

Example coverage was uneven, and concentrated in the wrong file. The cold run
reported never opening worked-example.md at all -- "never opened unlocks.md or
worked-example.md and didn't miss them" -- so 12 of the skill's 30 code blocks
did no work in the one real trial. What did fire were examples sitting inline
in the mapping tables at the moment of need.

So these go where the agent already is: next to the table row they explain,
in rest-v1-to-v2.md and graphql-to-v2.md rather than in the narrative.

Newly covered, each a real gap:
- Network volumes had zero worked code despite being the flagship rename --
  /networkvolumes -> /network-volumes is the example the ticket itself leads
  with, and it carries three simultaneous changes (hyphenated path,
  dataCenterId -> dataCenter, and the list envelope). Plus attaching one to a
  pod, where `path` is now mandatory.
- CPU pods were one table mention. computeType + cpuFlavorIds + vcpuCount ->
  a single `cpu` object, with the two CPU-only traps: mounts.persistent is
  rejected outright, and vcpuCount must be a power of two inside the flavor's
  advertised range.
- Pod lifecycle actions: four v1 paths collapsing into one body verb, with
  reset having no equivalent and `actions` telling you what is legal now.
- Registries, standalone templates, and list-with-filters.
- A curl pair, so codebases that are not Python have an anchor. Examples were
  13 Python to 1 JS.
- GraphQL: a full podFindAndDeployOnDemand conversion showing the four shape
  changes a field-by-field rename misses -- comma-string ports to array, env
  pair-list to map, dockerArgs to args, and string interpolation disappearing.

Every v2 body in the docs is now machine-checked against the live spec: 22
assertions covering required fields, unknown fields, the six enums, and the
nested config objects. All pass. The CPU example was checked against the live
catalog too -- cpu3c exists and vcpuCount 2 is inside its 2..32 range.

* docs(runpod-migrate): name the GraphQL volume mutations in the mapping table

A coverage audit over every v1 REST operation (37) and every customer-facing
GraphQL operation (23) found one real hole: createNetworkVolume,
updateNetworkVolume and deleteNetworkVolume existed only as a scanner signal
and as the prose "network volume mutations". An agent grepping the reference
for the operation name it found in the user's code got nothing back. Each is
now its own row with its v2 verb and path.

Also notes the /networkvolumes/{id}/update alias removal, which the pods,
endpoints and templates rows already called out.

The other 8 operations the audit flagged were a false alarm from the checker
normalizing {podId} while the docs use {id}; all are mapped.

* fix(runpod-migrate): report `ignore` and `keep-v1` as the distinct things they are

The scanner recorded only *whether* a line carried a suppression marker, not
which one, then rendered every marked hit as `keep-v1` / _(kept on purpose)_.
A repo whose remaining hits were all false positives was therefore described
as deliberately retaining v1 — the exact claim SKILL.md's marker table warns
against. Migrating a real codebase produced a report asserting "19 legacy call
sites kept on purpose" when it had none.

`ignore` was also documented as accepting `file` and `start`/`end` scope but
only ever matched at line scope: MARK_FILE/START/END were hardcoded to
`keep-v1`, and MARK_LINE's lookahead rejected `ignore file`, so it silently
did nothing.

- intentional_lines returns which marker applies, per line and per file.
- Hits carry `marker` instead of a boolean `intentional`.
- The verdict counts the two separately; rows label them differently.
- JSON `intentional_legacy` splits into `kept_on_purpose` and
  `marked_false_positive`.
- keep-v1 wins a tie, since leaving a hit visible as legacy is the safer error.

Both still drop out of the plan and out of --fail-on-legacy, which was never
the broken part.

Adds a testdata/runpod-migrate/markers corpus covering both markers at line,
region and file scope, plus seven assertions including one that reads the
rendered report and fails if an ignore-marked row is labelled kept on purpose.

* docs: show how to point an agent at the worked examples

Plain-English prompts route fine, but golden paths carry no SKILL.md and are
never auto-loaded, and each skill's reference/*.md is progressive-disclosure —
so an agent planning a build has no reason to open either unless asked.

Adds an 'Ask for it by name' section to the repo README: six copyable prompts
covering planning a new service, migrating to REST v2, deploying existing code,
GPU choice, debugging, and cold starts, each paired with the specific paths and
reference docs it pulls in. Reduces the pattern to
[skill] + [read the examples first] + [task].

The plugin README's Usage list gains the same treatment, split into one-off
actions versus work you are designing.

* docs: simplify the example call-out to 'look at the examples'

The prompt table named a specific golden path per row, which made the reader
responsible for knowing which of 24 paths applied. Pointing at the examples in
general is enough — the agent picks. Keeps the one thing worth explaining: golden
paths have no SKILL.md and reference/*.md is progressive-disclosure, so neither
loads unless asked for.

* docs(runpod-migrate): correct what v2 /billing/endpoints actually returns

The reference said an unchanged /billing/endpoints call returns 'the wrong
numbers'. It does not — v2's /billing/endpoints is the Runpod public endpoints
billing route and reports that product's spend correctly.

The trap is narrower and worth stating precisely: the path survives the
migration but now answers a different question, so a caller that carries it
across gets an accurate total for a product it did not ask about — and 0 if it
runs no public endpoints, which reads as 'no serverless spend'.

Links the two reference tables to golden path 11 so the product is identifiable.

* feat: add /runpod:migrate slash command for the migration flow

* feat: bare /runpod:migrate defaults to full scope on current directory

* fix(runpod-migrate): correct four v2 claims the live spec contradicts

The reference tables were verified on 2026-08-10 and four claims no longer
hold. Three of them were Class-3 rows — the class SKILL.md tells the agent to
stop and ask the user about — so being wrong there does not surface as a 422.
It buys an interruption over a decision that does not exist, followed by a
deliberate workaround for a capability the user still has.

- `templateId` is accepted on pod and endpoint create *and* update. It is not a
  422. What actually changed is the semantics: v2 resolves the template once at
  request time and retains no link, so editing a template no longer rolls
  changes out to existing resources. Moved to Class 2 as §13, since a quiet
  behavior change is exactly that class.
- `allowedCudaVersions` / `minCudaVersion` moved under `gpu` rather than being
  removed, nested there so they are unrepresentable on a CPU workload. Dropped
  from Class 3, recorded as a rename in both mapping tables.
- CPU endpoints are writable via `cpu: [{id, vcpuCount}]`. Row removed.
- `/v2/billing/networkvolumes` is a 404; the path is `/v2/billing/network-volumes`.
  The table warned about this exact hyphenation two rows earlier for the
  resource path.

Two rows narrowed rather than removed, both of which overstated a removal:

- GPU fallback applies to pods only. An endpoint's `gpu.pools` is a list and v2
  places workers on whichever listed pool has capacity, so the client-side loop
  this skill teaches is rebuilding something v2 kept.
- `countryCodes` survives as a `/v2/catalog/gpus` read filter. It still cannot
  constrain a create, so the row stands, but it can choose a target.

Also corrects the 422 table's required-field list: `image` is not
schema-required, because `templateId` can supply it.

The scanner's signal table carried the same three wrong claims as remediation
advice, so those rules move with the docs. All 22 scanner assertions still pass.

* ci: gate runpod-migrate's v2 claims against the spec

The four corrections in the previous commit were all found by diffing the docs
against the live spec, mechanically. Nothing in CI did that, which is why a
2026-08-10 verification pass could go stale unnoticed — catching it required
re-reading ~1,200 lines of tables by hand.

Two hooks, both stdlib-only:

- `check_migrate_tables.py` — every `METHOD /v2/...` claim in the skill's
  markdown must exist in the spec with that method. Also checks method-less path
  mentions, which the tables abbreviate as `…/v2/templates`. Paths the docs name
  as counter-examples (`/v2/networkvolumes`, cited as the 404) need an entry in
  EXPECTED_ABSENT with a reason, and the check fails if one of those starts
  existing — an allowlist that silently rots is worse than no allowlist.
- `check_migrate_class3.py` — every identifier in the Class-3 "capability
  removed" table is an assertion of absence, so existence alone falsifies the
  row and no parsing of the prose column is needed. Request-body fields and
  routes fail. Two softer outcomes are reported without failing: survives-as-a-
  read-filter (the row is true, just incomplete) and GraphQL-only identifiers,
  whose absence from a REST spec proves nothing and which are listed as
  unchecked rather than counted as verified.

Both default to a vendored snapshot at `testdata/runpod-migrate/v2-openapi.json`
so the PR gate only fails on what a PR changed. `--live` runs the same checks
against api.runpod.io, wired to a weekly non-blocking `spec-drift` workflow:
upstream moving is real news, but not a reason to red-X an unrelated PR.

Current state: 45/49 path claims verified, 9/11 Class-3 rows verified absent,
1 read-filter note, 2 GraphQL-only rows left for a human.

* docs(runpod-migrate): give countryCodes a migration instead of a dead end

The Class-3 row and the scanner both stopped at "no v2 equivalent." That is
true about the *field* and misleading about the *task*: a country restriction
is rebuildable in v2, as a catalog lookup that resolves countries to data
centers, those IDs passed as `dataCenterIds` on create, and a check that the
resource landed where it was supposed to.

Presenting it as a removal is not a neutral omission. SKILL.md routes Class 3
to stop-and-ask, so a dead-end description hands the user a choice between
accepting a behavior change and keeping the call on v1 — when the actual answer
is a dozen lines of code. Keeping a v1 call they did not need to keep is a worse
outcome than the one the bucket exists to prevent.

- breaking-changes.md gains a worked recipe next to the GPU-fallback one.
- The recipe includes the post-create placement assert, because v2 documents
  `dataCenterIds` as *preferred* and the spec does not say whether the scheduler
  may override it. Unresolved, and called out as unresolved: if the requirement
  is data residency, that distinction is the whole point, so the code verifies
  `pod["dataCenterId"]` rather than trusting the request.
- SKILL.md reframes the stop-and-ask. The question is no longer "what should I
  do here" but "was this a preference or a compliance requirement," which is the
  part only the user can answer. Where a rebuild exists, show it while asking.
- The scanner message carries the recipe, not just the diagnosis, and
  `countryCodes` moves out of the lumped "no equivalent" rule that hid it.
- Fixture and assertion added: the corpus never contained `countryCodes`, so
  nothing would have caught this rule regressing to a dead end. Verified the
  assertion fails when the advice is reverted.

* docs(runpod-migrate): dataCenterIds is enforced — verified against the live API

The previous commit hedged: v2 documents `dataCenterIds` as *preferred* data
centers, the spec does not say whether that is binding, and the recipe worked
around not knowing by asserting placement after create. Tested it instead.

Method: request an RTX 4090 — offered only in five EU data centers — while
naming `["US-KS-2","US-IL-1"]`, where it has no presence at all. That forces the
scheduler to choose between obeying the list and placing the pod, which a
happy-path test cannot do: a pod that lands where you asked proves nothing when
capacity was never scarce there.

Result: `400`, no pod. The control — the same body naming `EU-RO-1` — created
successfully and was terminated immediately. So the list is a hard restriction,
the scheduler will not place outside it to satisfy capacity, and it is a sound
basis for a data residency requirement. "Preferred" is misleading wording.

The post-create assert is dropped; it was insurance against an answer we now
have. Two things replace it:

- The 400 is documented, because the refusal presents as a capacity error and
  never mentions data centers. Over-narrow the list and the message sends people
  hunting for stock when the fix is widening the country list. That is the
  second-likeliest failure in this migration after a malformed body.
- The stop-and-ask is sharpened. Since the restriction holds either way, the
  preference-vs-compliance answer no longer changes the create — it changes the
  *fallback*. A preference widens the list when capacity runs out; a compliance
  requirement fails closed, which is the opposite of the GPU-fallback reflex the
  same document teaches two sections earlier.

Also points compliance cases at `GET /v2/catalog/datacenters`, whose per-DC
`compliance` array (GDPR, ISO_IEC_27001, SOC_2_TYPE_2, HIPAA) is a sounder basis
for an allowed list than a country code. v1 gave users no way to say
"certified", so some of them said "in this country" and meant this.

* fix(runpod-migrate): address review findings at 089816f

Four of five findings from @runpod-Henrik's review, each reproduced before
fixing. The fifth is answered in the PR thread rather than changed.

1. `--fail-on-legacy` failed on correct v2 code. The `networkvolumes` and
   `containerregistryauth` rules matched the bare token, unlike every other
   path rule, so Go packages, Python modules and import paths all read as v1
   call sites — 61 of 61 hits on one real repo. Both now require a leading `/`
   and defer to V2_CONTEXT. Import and module-path lines are suppressed
   separately, because `github.com/acme/internal/networkvolumes` carries the
   slash that otherwise marks a real path.

2. We taught a call that 400s, in 17 places. `include=AVAILABILITY` requires
   `product` and is invalid without it — 400 either way, no default, because
   the same GPU can be scarce for POD and plentiful for SERVERLESS. Every call
   site now names a product chosen for its context. This was the same failure
   this PR set out to fix, one layer down: a claim the spec contradicts, in the
   half of the URL nothing checked.

3. `.claude` was missing from SKIP_DIRS. Agent worktrees are full repo copies,
   so findings were multiplied by however many exist — 10,763 reported against
   1,821 real on runpod-flash. This ships as a Claude Code plugin, so its users
   are exactly the people with that directory. Added, with `.worktrees`,
   `.cache`, `.ruff_cache`, `.pnpm-store` and `bower_components`.

4. Nothing tested traversal, which is why 3 existed. Emptying SKIP_DIRS and
   removing the filter outright both survived a mutation audit, because no
   corpus contained a single skipped directory. New `skipped-dirs` corpus: one
   real v2 file beside seven directories that each hold obvious v1 code,
   asserting zero findings and a clean --fail-on-legacy. Both mutations now
   die; the audit is 8/8.

5. The availability eval asserted the 400 form, so it would have re-certified
   2 after the fix. Now asserts `product=` separately from the include.

Root cause of 2, fixed rather than papered over: check_migrate_tables.py only
verified paths. It now also verifies query parameters — that each one exists on
the path it is used with, and that required-together pairs appear together. The
pairs are derived from the spec's own prose ("Required with include=AVAILABILITY")
rather than hardcoded, so a new co-requirement is enforced as soon as v2
documents one. Verified by reverting a fixed line and watching the check fail.
2026-08-19 12:31:18 -04:00
github-actions[bot] b669407688 chore(main): release 1.1.2 (#43)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-03 13:57:57 -04:00
Justin dbdaaf45a4 docs(runpodctl): document coded json errors, invoke urls, gpu pricing (#41)
runpodctl v2.8.0 made every failure a flat json object on stderr with a
stable `code` and a non-zero exit, added a `urls` object to serverless
create/get/list/update, and put pricing + per-datacenter availability on
`gpu list`. The skill said none of it.

- SKILL.md: new "Output & errors" section — branch on `code` not `status`
  (graphql not-founds carry no status), `network_error` is the only
  retryable code, `not_found` is always server-side, v2.8.0 floor.
- reference/output-and-errors.md: full code table, error field shapes,
  exit codes, the surfaces still printing plaintext, env-var table incl.
  `RUNPOD_INVOKE_URL`, `urls` object, gpu pricing/availability shape.
- gpu-selection.md dropped the now-false claim that `gpu list` can't
  report $/hr.
- Two evals: error branching, and reading urls/pricing from output.

Error shapes, `urls` and the gpu fields verified against a v2.8.0 build.
CON-752
2026-08-03 13:56:56 -04:00
github-actions[bot] 3fbcd5d7d1 chore(main): release 1.1.1 (#40)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-29 17:56:47 -04:00
Justin d060d4ea6a docs(runpod): cover LOAD_BALANCER endpoints and the v2 scaling shape (#38)
Documents the reshaped v2 serverless surface across the MCP skill and two
golden paths:

- `endpointType: QUEUE | LOAD_BALANCER` on create, fixed at creation, with
  `requestUrls` as the source for an endpoint's invoke URLs.
- The v1-flat to v2-nested scaling mapping: `scalerType`/`scalerValue` become a
  per-scaler `scaling` object (`queueDelay` float >= 0.5 / `requestCount` integer
  >= 1), and `idleTimeout` moves under `workers` (1-3600). On a queue endpoint
  scaling on `requestCount`, `idleTimeout` is rejected on create and update, not
  ignored — with a warning against translating golden path 13's own runpodctl
  walkthrough config to v2, since GraphQL accepts that pair and v2 REST does not.
- Golden path 14 now leads with v2 REST / MCP and keeps GraphQL `saveEndpoint`
  as the second route. Prod and dev serve a byte-identical spec, so the earlier
  "dev is ahead, prefer GraphQL on prod" framing was inverted.

Neither CLI makes you write the nested shape, but for different reasons, and the
doc now separates them: runpodctl never talks to v2 REST at all (create goes
through GraphQL `saveEndpoint`, update through v1 REST, both flat natively — and
`--scale-threshold` is an integer, so it cannot express a fractional
`queueDelay`), whereas the MCP server keeps the flat parameters and maps them
onto the v2 body.

Squashed onto main after #39 merged. Both PRs edited the same Serverless
endpoints bullet; replaying five commits would have meant resolving that same
region repeatedly, and three of the five only corrected the other two. Resolved
as a union — kept #39's GPU-SKU sentence and added this PR's endpointType and
requestUrls facts — and split the bullet into sub-points, since it now carries
four unrelated facts and a single line is what let the file drift out of sync
with itself in the first place.
2026-07-29 17:54:59 -04:00
Justin 92a89969c6 docs(runpod): drop tags from the MCP tool surface, add ECR delegations (#39)
* docs(runpod): drop tags from the MCP tool surface, add ECR delegations

Mirrors the tool-surface resync in runpod/runpod-mcp#59.

The Tags bullet described seven tools that can no longer work: /v2/tags was
removed from the API (404 on both v2-rest.runpod.io and the dev host, verified
2026-07-27) and the tools are being removed from the server. Replaced with a short
note that they are gone with no replacement, since an older installed MCP version
still advertises them.

Adds the three ECR delegation tools, and spells out how they differ from
container-registry auth — ECR-only, stores no credentials, returns a
dockerRegistryUri — because the names alone don't convey that. Also notes the
delegation route in the "private image won't pull" gotcha, which is where someone
hitting an ECR pull failure actually lands, alongside the headless equivalent of
the Console credential fix.

* docs(runpod): drop the tags bullet, point at the server for tool specifics

Removes the Tags entry outright rather than documenting it as broken — the skill
should describe the surface that exists, and the tool's own 404 is self-evident.

Trims the ECR delegation entries to what a routing doc needs (ECR-only, no stored
credentials, prefer it for ECR) and defers the field-level detail to the tool's own
parameter descriptions instead of restating the request shape here. Same for the
gotchas entry. Hardcoding API schema into the plugin is what went stale in the
first place.

* docs(runpod): finish the 3.0.0 tool-surface sync

Dropping tags covered only half of what 3.0.0 changed. This adds the tools it
introduced and removes the negative capability claims it invalidated.

Added to the tool surface: Hub (`list-hub-repos`, `deploy-hub-repo`), public
endpoints (`list-public-endpoints`), and — on the serverless bullet —
`stream-worker-logs` plus `set-endpoint-gpus`, the only route to pinning a
specific GPU SKU.

Corrected, all verified against the registered tools on runpod-mcp main:

- `create-network-volume` does take a storage tier. It has `volumeType`
  (STANDARD | HIGH_PERFORMANCE) and a 10-4096 GB range. The old warning sent
  agents to the console or a hand-rolled REST call to avoid a tool that works.
  Fixed in the three other files carrying the same claim (runpod-usage
  storage.md, runpodctl SKILL.md, golden path 21) so the plugin doesn't
  contradict itself.
- Hub is no longer runpodctl-only; removed from the routing list and the
  frontmatter.
- `create-pod` takes `templateId` (imageName then optional) and
  `computeType: "CPU"`, so neither needs handing to runpodctl. The multi-GPU
  priority list still does — extra `gpuTypeIds` are dropped with a `_warning` —
  and that one is kept.
- Named the delegation tools. "ECR delegations - list, create, delete" derives
  to `create-ecr-delegation` under this file's own convention; the real name is
  `create-registry-delegation`. Also pointed at the tool description rather than
  its parameter descriptions, since `dockerRegistryUri` (the value you actually
  deploy with) is only in the former.
- Refreshed the `serverInfo.version` example from 2.0.0 to 3.0.0.

* docs(runpod): kill the stale negative-capability claims plugin-wide

Follow-up to 908aede, which corrected these claims in runpod-mcp/SKILL.md but
left the same falsehoods asserted in six other files — including the router table
that decides which skill loads and two evals that graded the correct 3.0.0
answer as a failure.

Two errors in 908aede itself, both verified wrong:

- `set-endpoint-gpus` was called "the only way to pin a specific GPU SKU". It
  isn't — `deploy-hub-repo`'s `gpuIds` takes the same `POOL,-<gpu type id>`
  exclusion syntax and pins SKUs at deploy time (hub.ts:381). Narrowed the claim
  to existing endpoints and named the Hub path.
- "Template-based and CPU pods do not need runpodctl" is true of each alone and
  false together: create-pod returns 400 for that combination, because a
  template deploy is GPU-and-v2-only (pods.ts:256). Reworded, and added the two
  caveats the sentence had swallowed — templateId is v2-only, and a field you
  pass replaces the template's whole value rather than merging.

Fixed elsewhere:

- golden path 21 contradicted itself three times after 908aede (lines 9, 77, 89
  plus the Lane(s) header still said runpod-mcp was attach-only). The Skill-gaps
  entry asked for an upstream `type` param that has already shipped.
- runpod/SKILL.md routing table: template and CPU pods no longer route away from
  MCP; Hub no longer claims MCP has no tools. Split the pod row so the genuine
  runpodctl cases (multi-GPU list, template+CPU) stay.
- runpod-mcp/evals/when-to-use-mcp and runpod/evals/mcp-vs-runpodctl-tiebreak
  asserted MCP lacks Hub/templateId/CPU. The tiebreak eval reached the right
  verdict for the wrong reason; it now cites the template+CPU restriction.
- 03-whisper-endpoint quoted the old router rule verbatim.

Also corrected a claim 908aede left standing two files over: MCP does NOT handle
CPU serverless endpoints. v2 create-endpoint requires gpuPoolIds and the v2 spec
has no computeType/cpuFlavor field at all, so that work is runpodctl-only —
runpod-mcp/SKILL.md said the opposite, and runpodctl/SKILL.md offered MCP as an
alternative in two places.
2026-07-29 17:51:36 -04:00
Justin 00d6903c8b Merge pull request #37 from runpod/docs/separate-context-from-constraints
Separate binding rules from context in agent instructions
2026-07-17 15:01:07 -04:00
github-actions[bot] d64f4ca2ef chore(main): release 1.1.0 2026-07-17 18:34:06 +00:00
Brian Connelly 0ab44267da docs: fix two internal-consistency nits from Copilot review
- flash Gotcha #10: the intro claimed "three rules, by endpoint type" but
  the list also carries a QB-specific never-empty rule and a context bullet;
  reword to "match the request shape to the endpoint type".
- runpodctl "Serverless facts (context, not rules)": rephrase two imperative
  bullets as neutral observations so the section matches its label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:19:54 -07:00
Brian Connelly 91dfc48445 docs: separate binding rules from context in agent instructions
Apply a "separating context from constraints" audit across the repo's
agent-consumed instruction docs. Behavioral rules that were buried in
run-on narrative prose are pulled into distinguishable list items,
multi-obligation sentences are split into atomic rules, and load-bearing
facts are moved out of rule sections into labeled context/facts blocks.

Semantics preserved throughout; the one deliberate strength change
(CONTRIBUTING.md "Validate before pushing" -> advisory) was approved by
the maintainer. Codex reviewed the diff and its four semantic-preservation
catches were incorporated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:15:05 -07:00
Justin c9e7416f09 docs: rename repo references runpod/skills -> runpod/runpod-plugins-official
Prep for the repo rename to runpod/runpod-plugins-official. Updates
install/marketplace refs and plugin.json homepage/repository URLs.
Filesystem paths (plugins/runpod/skills/) are unchanged.
2026-07-17 10:42:37 -04:00
Justin 197df46405 docs: add one-shot 'install all Runpod tools' block + README pointer
getting-started 'Get the tools' had a per-lane table but no install-everything
option. Add a copy-paste block (skills + runpodctl + flash + hosted MCP; drop
any line you won't use), with companion CLIs called out as separate/OS-specific.
Link it from the README Quick start's optional-upgrades note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 16:06:06 -04:00
Justin 4ee1eaae27 docs(getting-started): name the config.toml 'apikey' field + raw-key extraction
The key-resolution order already lists ~/.runpod/config.toml, but never named
the field, so an agent needing the raw key for a direct API call (Authorization:
Bearer) had to reverse-engineer it. Name the 'apikey' field and give the
$RUNPOD_API_KEY-first extraction one-liner (matches GP23's invoke).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:52:43 -04:00
Justin fd30188a3b fix(gp25): correct ssh command (no double 'ssh') + mark observed output as trimmed
command-lint review: the inspect block said 'paste ssh_command verbatim' then
prefixed it with 'ssh ', yielding 'ssh ssh -i ...' (connects to host 'ssh').
Show a standalone 'ssh -i <key> root@<ip> -p <port>' form like GP22. Also
label the observed-output block as trimmed (real ls lists SOURCE.txt; df|awk
prints a header row) so a reader diffing against live output isn't surprised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:48:28 -04:00
Justin 9b385fd873 docs: "Official Runpod Agent Skills" + easier get-started + per-path prerequisites
README:
- Rebrand title to "Official Runpod Agent Skills".
- Add a 3-step "Quick start (Claude Code)" with plain-English example prompts.
- Restructure Authentication around "auth in two places, one API key covers
  both" (table + numbered fastest-path), leading with OAuth as the easy MCP
  option instead of burying it under the Bearer-header case.

Golden paths (onboarding review — a newcomer couldn't start unaided):
- GP22/23/25: add a "Prerequisites" section (Runpod auth, docker login +
  push namespace, SSH key for pods) linking the shared "Before you run any
  path" block + getting-started + companion-clis — the template contract's
  Prerequisites section was missing and the shared block was unlinked.
- GP23 invoke: use $RUNPOD_API_KEY (the recommended setup) with config.toml
  as fallback — the old config.toml-only read 401'd for anyone who followed
  `export RUNPOD_API_KEY`.
- GP22: add "no key yet? ssh-keygen -t ed25519" fallback before add-key.
- Fix companion-clis relative link depth (../../ -> ../../../).

CI hooks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:46:36 -04:00
Justin 8ff601e0db fix(golden-paths): address gp-review findings on 22/23/25
Third reviewer (golden-paths) found the docs had drifted from the actual
live runs; fixed:

- HIGH (GP25): the "inspect" command was condensed and dropped the mkdir +
  writes, so it couldn't produce the pasted "observed" output (and failed as
  written — no /workspace/mounted-model). Restored the real command that was
  run (mkdir, write note + weights, df -hT|awk, stat -f) so the evidence is
  reproducible from the documented steps.
- MED (GP22, GP25): stop hardcoding `-i ~/.runpod/ssh/runpodctl-ssh-key` (the
  key filename varies per account) — tell the reader to paste the
  `ssh_command` that `runpodctl ssh info` prints, matching GP06's convention.
- MED (GP25): add `--volume-mount-path /workspace` to the pod-create, matching
  every other volume-attaching path (was relying on the platform default).
- LOW (GP25): prose said `df -T`, command uses `df -hT` — aligned.
- LOW (GP23): add `--workers-min 0` for consistency with sibling endpoints.
- LOW (README): document the ✅ live-verified / 📘 documented badges the table
  actually uses (legend previously only defined stub/drafted/spec/covered).

No dangling GP24 refs / broken links (reviewer confirmed). CI hooks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:37:22 -04:00
Justin 1dda72b61e docs(runpod): route the new build/storage golden paths from runpod/SKILL.md
The main skill's "Worked examples" task->path router didn't list the new
paths. Added two rows — "build a minimal image for a target (pod vs queue)"
-> 22/23 (+ building-images concepts) and "bake into image vs mount a
network volume" -> 25 — and refreshed the stale scenario count.

All CI hooks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:14:53 -04:00
Justin 1c8d685644 docs(runpod): drop GP24 (redundant with GP14) — point LB references to 14
GP24 was a framing/pointer doc for the load-balanced image contract, but
GP14 already live-verifies the identical minimal LB image (stdlib HTTP
worker, type:"LB", /ping+/echo+/stats 200). Removed the redundant path and
rewired all references (golden-paths table + intro + complementary-pairs
note, GP23 contrast link, building-images.md contract link) to GP14.

All 4 CI hooks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:12:18 -04:00
Justin 2fbe470971 docs(runpod): apply context/constraints audit findings (building-images + GP23/24)
Second agent-friendliness audit (separating-context-from-constraints)
returned 8 findings; applied all:

- building-images.md base-image guidance: signal strength + reachable
  exceptions — "FROM runpod/pytorch for GPU workloads" with explicit CPU-slim
  and non-Runpod-base(reproduce-SSH) exceptions, so it no longer reads as an
  absolute that the shipped examples contradict (R2/R5).
- SSH section: promote the load-bearing obligation to a marked Rule ("custom
  CMD must invoke /start.sh; serverless exempt") instead of stating it only as
  a consequence in prose (R1/R2); give pattern 2 its trigger condition (R5).
- Best-practices bullets: demote the unverifiable "keep it small" goal to
  context with the concrete steps as the rules (R3); split bundled obligations
  (pin-tag vs platform; PYTHONUNBUFFERED vs pin-tag) and drop the duplicate
  (R4).
- GP24 LB rule 2: split "set PORT + PORT_HEALTH + expose port" into an (a)(b)(c)
  checklist so each is independently checkable (R4).
- GP23: align base-image note with building-images.md as the single source (R2).

All 4 CI hooks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:53:08 -04:00
Justin e2e075c22c docs(runpod): GP23 — verify local --test_input path, add --platform + output
Agent-friendliness audit (runnable-examples criterion) flagged the local
handler-test command as the one snippet not actually run. Ran it against the
pushed image: `--test_input` works and returns the handler output. Added the
required `--platform linux/amd64` (image is amd64) and the observed output so
the command is runnable-as-written.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:51:03 -04:00
Justin 2e44973da6 docs(runpod): golden path 25 — bake-vs-mount (live-verified)
Runnable proof of the storage model behind building-images.md: a baked-in
file rides the image on the host's LOCAL disk (overlay fs), while a mounted
network volume at /workspace is a NETWORK filesystem — observed live as
`mfs#euro-3.runpod.net:9421` (FUSE / MooseFS) via `df -T` on a CPU pod with a
standard volume attached. Built, pushed, launched, inspected, torn down
(pod + volume deleted).

Cross-linked from building-images.md (bake-vs-mount) and registered in the
golden-paths table + intro note. All 4 CI hooks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:46:19 -04:00
Justin a960cfaa9c docs(runpod): minimal image-per-target golden paths 22-24 + SSH section
Add the "minimal image per contract" trio, doubling as evals for
building-images.md:

- GP22 minimal pod image — live-verified: built, pushed, launched a CPU
  pod, SSH'd in (PUBLIC_KEY -> authorized_keys, sshd up), HTTP proxy 200,
  torn down. Demonstrates the from-scratch SSH-preservation pattern.
- GP23 minimal serverless queue image — live-verified: built, pushed,
  deployed a CPU endpoint, /runsync -> COMPLETED, torn down.
- GP24 minimal serverless load-balanced image — the image-contract framing
  of the already-live-verified GP14 (no redundant re-deploy).

building-images.md: add "Don't clobber the base image's startup (SSH /
start.sh)" — the #1 pod footgun — with three safe patterns; cross-link the
trio from the image-contract section. Register 22-24 in the golden-paths
table + complementary-pairs note.

All 4 CI hooks (links/branding/versions/marketplace) pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:38:54 -04:00
Justin f4d460cf56 docs: image-building guide + storage-tiers golden path (21)
- runpod-usage/reference/building-images.md: official runpod/pytorch base (host-cached)
  + layering, bake-in vs mount, pod/queue/load-balanced image contracts, local testing.
- golden-paths/21-storage-tiers.md: launch standard vs high-performance network volumes
  (standard via runpodctl; high-performance via v2 REST type:HIGH_PERFORMANCE or the
  console — CLI/MCP cannot set the tier), attach to pod/serverless, storage recommendation
  for training.
- Document the tier caveat consistently in runpodctl, runpod-mcp, and storage.md; link the
  new guide from runpod-usage and register GP21 in the golden-paths index.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:58:16 -04:00
Justin eb1a23bcf7 fix: golden-path 10 CPU endpoint (REST computeType:CPU silently gives GPU)
Path 10's multi-region fallback created the endpoint via POST rest.runpod.io/v1/endpoints
with computeType:CPU — the known footgun that silently provisions a GPU endpoint. Mirror
path 19: omit computeType from the REST create and express CPU via instanceIds + empty
gpuIds in the GraphQL saveEndpoint step. Also make docker-setup.md's WSL2 note
self-contained (dangling 'WSL2 setup' reference after the install extraction).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:51:28 -04:00
Justin 0815056161 docs: Official branding + extract one-time CLI install steps
Addresses second-round review from @jeremyjacobs-runpod and @briandconnelly.

- Reword plugin/marketplace descriptions to lead with 'Official Runpod agent skills
  and MCP server' (skills-first, not MCP-led); displayName -> 'Runpod (Official)'.
- Move one-time CLI install out of the companion-CLI reference docs into per-CLI
  reference/*-setup.md, leaving a one-line pointer; github-setup.md also holds SSH-key
  generation + registration. Drop the inline hf install from the always-loaded
  companion-clis/SKILL.md (the file that loads every invocation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:32:10 -04:00
Justin 3751fef038 docs: apply agent-friendliness self-audit fixes (docs/cli/scc lenses)
Ran briandconnelly's agent-friendly-docs, agent-friendly-cli, and
separating-context-from-constraints skills against this repo and resolved the findings:

- AGENTS.md: fix stale golden-path status (defer to golden-paths/README); add a
  Contributor rules section; de-duplicate the capability-matrix maintenance rule.
- CLAUDE.md: use an @AGENTS.md include so the instructions are always loaded.
- runpodctl: promote the CPU-endpoint REST footgun into Decision Rules; split the
  GPU-pool PATCH into its own rule; make the SSH rule verifiable (no interactive
  shell); replace 'actively-maintained worker' with observable hub signals; clarify
  'active worker' = --workers-min 1 (not max) + its continuous-billing cleanup caveat.
- model-caching: reframe the runtime-path note as an agent-facing 'don't guess' rule;
  disambiguate the documented model-caching path from the undocumented Model Repo
  upload path; drop an internal ticket reference.
- networking: make the public-URL rule actionable (tell the user it's unauthenticated).
- docker: give the --platform/never-latest rule one authoritative home.
- CHANGELOG: mark it release-please-generated. Add a PR template + CODEOWNERS as a
  doc-freshness gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:05:06 -04:00
Justin 11d21adaa1 fix: bound serverless/pod polling loops so an agent can't hang
The wait-for-ready loops used unbounded `until …; do sleep N; done`, which
contradicts the skills' own "bound any poll loop" rule and can spin forever on a
genuinely broken pod/worker. Converted to bounded `for i in $(seq …)` loops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:04:54 -04:00
Justin f32e5ae9c6 chore: pre-normalize JSON manifests to release-please format
Reformat plugin manifests with JSON.stringify(obj,null,2)+newline so the
release-please json updater only ever changes the version line (no array
reformat churn in future release PRs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 23:05:12 -04:00
Justin 012cf91ff3 feat: automate releases with release-please and apply PR #31 review fixes
- release-please workflow + config: bumps all version-bearing files in lockstep
  (4 manifests, version.txt, .release-please-manifest.json, 6 SKILL.md) and
  regenerates the CHANGELOG from Conventional Commits
- check_versions.py CI guard (extended to skills) prevents version drift
- apply review fixes (#32 rule splits, #33 versioning, #34 refs, #35 template)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:50:33 -04:00
Justin bc0c311998 refactor(context): trim runpodctl commands to reference; tighten router auth
Two progressive-disclosure trims from the audit (#1, #2):

- runpodctl/SKILL.md 307 -> 229 lines: moved the exhaustive per-resource
  command/flag menus + the send/receive runbook into a new
  reference/command-reference.md (nothing lost). SKILL keeps the 80%
  essentials per resource plus the decision-critical prose (CPU-serverless,
  model-cache, multi-region, create-from-hub). Registry/billing/SSH-key
  management now live in the reference.
- runpod/SKILL.md auth section ~60 -> ~28 lines: deduped the "prefer key
  over OAuth" message (was restated ~5x) into one block — check → get a key
  (unlocks all; OAuth is MCP-only, a half-setup) → ordered options → then.
  Kept inline (not relocated) so the agent still reads it; made the
  getting-started ref a real link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:58:40 -04:00
Justin 7e2dd52b96 fix(audit): resolve verified findings from structure+consistency swarm
Correctness/consistency (verified against the live tree before editing):
- C1 (GP10): GraphQL saveEndpoint CAN make a CPU endpoint via instanceIds +
  empty gpuIds (live-verified in GP19) — GP10 wrongly said "defaults to GPU,
  use runpodctl for CPU," which could leave a reader silently GPU-billed.
  Fixed both notes + cross-linked GP19.
- C2 (aws.md): the S3 --endpoint-url host must be LOWERCASED
  (s3api-eu-ro-1…), while --region takes the DC id as-is. The doc reused one
  uppercase DATACENTER placeholder for both -> a bad host. Fixed the rule,
  examples, and preamble.
- C4: noted --compute-type casing (pods `cpu`, serverless `CPU`).

Structure (progressive-disclosure fixes):
- P4 (runpod-mcp): was over-split — Connect had no runnable command. Inlined
  the 80% Bearer-key connect one-liner.
- P2 (router): pod-workflows.md / endpoint-workflows.md were bare filename
  mentions; made them explicit links to runpod-usage/reference/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:45:28 -04:00
Justin 7459cfdebc docs(auth): lead with the API key (unlocks all tools); flag MCP OAuth as half-setup
Feedback from a fresh install test: the agent defaulted to MCP "Sign in with
Runpod" OAuth, which auths the MCP only — runpodctl/flash stayed blocked, and
it hit a wall when a CLI-only capability was needed.

Fix the bias across the auth guidance:
- runpod/SKILL.md "First run": lead hard with getting a RUNPOD_API_KEY (flash
  login or Console) — it unlocks runpodctl + flash + hosted MCP (Bearer).
  Explicit ⚠️ that OAuth-only is a half-setup; reordered preference (key first,
  OAuth last resort); step 2 now recommends key-as-Bearer even when the MCP is
  the unauthed lane present.
- runpod-mcp SKILL + reference/connect.md: present hosted+Bearer key as the
  recommended full-access connect; frame OAuth as MCP-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:15:42 -04:00
Justin c189c5acbc docs(runpod): session-start auth check + flash-login-saves-key path
Make the router tell the agent to confirm auth BEFORE the first infra
action in a session, and what to do if nothing is set up.

- runpod/SKILL.md "First run": explicit check (RUNPOD_API_KEY env > .env >
  ~/.runpod/config.toml; `runpodctl user`; MCP /mcp Connected), and if
  nothing's set up, stop and ask the user to either `flash login` or provide
  an API key. Clarify one key = full control (runpodctl + flash + hosted MCP
  via Bearer); OAuth sign-in is an MCP-only convenience.
- Document that `flash login` does browser OAuth and saves a real Runpod API
  key to ~/.runpod/config.toml (shared with runpodctl) — verified in flash's
  credentials.py (save_api_key + resolution order). Noted in getting-started.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 11:55:06 -04:00
Justin 447f15f962 docs(runpodctl): always update to latest CLI before use (STO-370)
STO-370 added a requirement after a Model Repo user test where an agent
started on an old runpodctl (2.1.9), hit errors, and flip-flopped between
versions. Add an explicit up-front instruction: run `runpodctl update` +
`runpodctl version` first, pin one recent build for the whole task, and
don't switch old<->new mid-task. Noted in the Install section, the
Quick-start sequence, and reference/install.md. Matches the real trap hit
verifying golden path 20 (local 2.3.0 lacked --model-reference until update
to 2.7.1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 11:34:16 -04:00
Justin 9143860ba6 docs(gp20): mark model-caching endpoint live-verified via MCP worker logs
Third live run (2026-07-15), diagnosed through the Runpod MCP
stream-worker-logs. The --model-reference flow works end to end:

- Deploy pinned :main to commit 7ae557604adf; no baked image, no volume.
- Async /run returned COMPLETED with real output ("Hello. That's all there
  is to it."), delayTime ~105s, executionTime ~1.5s.
- Cache hit confirmed in worker logs: vLLM loaded the 0.92 GiB checkpoint in
  0.55s at the pinned revision (already present, not a fresh HF pull).

The earlier failures were infra timing, not the feature: first-ever ~10 GB
image pull (>20 min) and a sync /openai 524 (>100s edge timeout on cold
load). Reworked the gotcha: "ready" can precede vLLM being up — read worker
logs (MCP) to tell slow boot from a real stall; use async /run for cold
calls. Status ⚠️ partial -> ✅; router count 19 -> 20 live-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 11:29:23 -04:00
Justin 8ceb70917d docs(gp20): fold in two live-run findings (sync 524, ready-but-not-dispatching)
Second live attempt at golden path 20. Deploy + model-reference pinning
confirmed again; inference still not captured, but with richer real data:

- Worker readied in 162s once the worker-vLLM image was warm on the pool
  (vs >20 min first-ever pull in run 1).
- Sync /openai and /runsync hit Cloudflare 524 on cold load (>100s edge
  timeout during first-request model load) — switch step 4 to async
  /run + /status.
- A ready worker left an async job IN_QUEUE ~15 min (inProgress:0), only
  reaching running at teardown — the documented "ready but mis-dispatching"
  symptom; no COMPLETED output, so the on-host cache path stays unconfirmed.

Status stays partial with these gotchas recorded. Total GPU spend ~$0.64;
all test endpoints torn down.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 18:23:16 -04:00
Justin ea2b9db4eb docs(skills): standardize API-key settings URL to console.runpod.io host
Use https://console.runpod.io/user/settings everywhere (was mixed with
https://runpod.io/console/user/settings). Both resolve to the same page;
picking the console.* host matches the other console links (storage, hub,
container registry). Resolves the last swarm consistency finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:29:38 -04:00
Justin 927ffc8bf9 docs(skills): reconcile flash-SDK vs platform defaults and large-model delivery
Two consistency fixes from the contradiction swarm:

- Note that serverless defaults differ by layer: flash SDK (idle_timeout 60s,
  workers (0,1), execution_timeout unlimited) vs platform/runpodctl (5s, 3,
  600s). Added cross-referenced notes in flash/reference/api.md and
  runpod-usage/reference/concepts.md so a default is read against its layer.
- Clarify large-HF-model delivery: naming/streaming works at any size but
  re-downloads on every cold start; for reused large models cache via
  NetworkVolume / --model-reference / bake. Same tradeoff as the
  model-caching delivery table — not a contradiction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:27:04 -04:00
Justin afb5bde7c2 refactor(skills): co-locate golden paths, slim SKILLs, add model-caching, fix CPU-endpoint bug
Architecture pass on the runpod plugin:

- Move golden-paths/ under skills/runpod/ so they travel with the router
  skill on single-skill installs; rewrite all cross-links (link check green).
- Slim always-loaded SKILL.md files (~1558 -> ~854 lines) by extracting
  one-time setup and exhaustive reference into on-demand reference/*.md:
  companion-clis (per-tool hf/gh/docker/aws), flash (setup-and-cli/api/
  patterns), runpod-mcp (connect), runpodctl (install).
- Add runpodctl/reference/model-caching.md covering all four model-delivery
  methods, --model-reference HF cache, and the Model Repository (STO-370);
  expand the Models section; add the 4th row to storage.md; add golden
  path 20 (host-cached HF model endpoint).
- Fix CPU serverless endpoint bug (verified live): public REST
  POST /v1/endpoints with computeType:CPU silently provisions a GPU;
  use `runpodctl serverless create --compute-type CPU` or MCP instead.
  Fixed golden paths 16 & 18 and added a canonical note in the runpodctl skill.
- Document REST v1 (control, buggy for CPU) vs the v2 runtime/invoke API,
  and how to read the MCP server version from the initialize handshake
  (serverInfo.version = "2.0.0 [RUNPOD_REST_VERSION=v2]").
- Fix datacenter IDs in the AWS reference to region-prefixed form
  (EU-CZ-1, US-CA-2, ...) per `runpodctl datacenter list`.

Golden path 20 is marked partial: deploy + model-reference resolution
verified live; inference not confirmed (worker cold start exceeded 20 min).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:20:11 -04:00
Justin 6a91ea8faf feat(plugin): commit-track updates (drop pinned version) + README Updating section
Remove the pinned "version":"1.0.0" from both plugin.json manifests so the plugin is
commit-tracked — per Claude Code marketplace docs, omitting version makes every git
commit a new version, so users get fixes on the next update/background pull instead of
being stuck until a manual version bump. Add a README 'Updating' section with the
per-client update commands (Claude Code /plugin marketplace update, Codex plugin
marketplace upgrade, skills.sh re-add) and the rationale.
2026-07-13 15:50:59 -04:00
Justin 4f70ab7d40 feat(skills): push proper control-plane setup, lead with one RUNPOD_API_KEY
Refine the router 'First run' guidance per feedback: proactively get the user set up
rather than silently limping on a partial fallback.
- Lead with the single unifying step: export RUNPOD_API_KEY unlocks runpodctl + flash
  + local-stdio MCP at once; the hosted MCP is the one exception (its own /mcp OAuth,
  or same key as Bearer). Corrects the 'MCP auth = everything authed' misconception
  (OAuth keeps no key on disk, so it doesn't set up the CLIs, and vice-versa).
- Reframe: use a working lane to stay unblocked, but don't quietly drop to a fallback
  when a quick setup gives the intended lane — nudge toward proper setup; if nothing is
  connected, stop and set them up.
- getting-started: same one-key-covers-CLIs + hosted-MCP-is-separate clarification.
2026-07-13 15:44:35 -04:00
Justin 0d4fe478fb feat(skills): detect + guide control-plane (MCP) setup on first run
- router SKILL: add 'First run — is a control plane connected?' — the agent checks
  whether MCP tools are present / installed-but-unauthed / runpodctl available, and
  GUIDES the user through setup (auth the bundled MCP, or install runpodctl) instead
  of silently failing. Falls back to runpodctl when MCP isn't configured.
- runpod-mcp SKILL: add a 'verify it's live' step (/mcp shows Connected vs Needs
  authentication; confirm with list-endpoints) — the plugin registers the server but
  it's inert until sign-in.
- README: control-plane MCP heads-up — the plugin installs a hosted MCP that needs a
  one-time sign-in; the runpod skill auto-detects + guides; runpodctl is the fallback.
2026-07-13 15:34:46 -04:00
Justin 9aa2b36378 docs(golden-paths): reproducibility polish across 04/07/08/09/13/14/17/18/19
Address the reproducibility audit's remaining findings so every path is turnkey:
- 04/07: spell out the elided `runpodctl ssh info` parse (use 06's proven pattern);
  04 now writes train.py to the pod + runs it detached with a log to poll.
- 08: detached training launch is now a real runnable command (write script, setsid,
  log, poll) instead of narration.
- 09: add the explicit step that copies the template files up to the pod /app.
- 10/13/17: add ## Prerequisites sections (link the shared note + getting-started);
  17 gains explicit 17→14 prerequisite-reading cross-links; 10 gets a buildable image
  source (points to 19's reusable handler+Dockerfile).
- 14: add 14→17 see-also (shared type:"LB" substrate).
- 18: create the serial-contrast endpoint/template the walkthrough compares against
  (was only in cleanup) — create/cleanup now consistent.
- 19: placeholder coherence across sync loops + GraphQL + cleanup; capture-the-id note.

Doc-only (no new live runs); all live-verified results/observed output preserved.
Hooks: links + branding + marketplace all pass.
2026-07-13 12:27:54 -04:00
Justin e66f50e9c8 docs(golden-paths): fix 10 placeholder mismatch + add shared prerequisites note
- 10: unify the CLI-path verify to <vol-ro>/<vol-cz> (was <vol-a>/<vol-b> from a
  different sub-run) so a reader tracks one consistent set of ids.
- README: add 'Before you run any path' shared prerequisites — auth/SSH (link
  getting-started), docker login + companion creds (link companion-clis), the
  placeholder + '<your-registry> is yours, you can't push to the live-run namespace'
  convention, and the runpodctl >=v2.4.0 note. Complements the registry/ID
  genericization so readers know what to substitute.
2026-07-13 12:21:45 -04:00
Justin 1f1b8c340c docs(golden-paths): replace hardcoded live-run IDs with placeholders
Genericize per-run template/endpoint/volume ids (e.g. p8b3v1prf1, d4qw56wbx9,
ie59vtopn776j2, ...) to readable placeholders (<template-id>, <endpoint-id>,
<vol-ro>/<vol-cz>/<vol-is>, ...) across paths 05/10/13/14/16/17/18/19 so commands are
copy-paste-safe on a fresh account. Kept: the official reusable ComfyUI template id
(02), worker-hash + public-endpoint job/worker ids (observed-run evidence), image tags.
2026-07-13 12:19:58 -04:00
Justin 3c520afa43 docs(golden-paths): replace justinrunpod/ image refs with <your-registry>/
Genericize all Docker image references in the golden paths (build/push commands +
prose) so a new agent uses their own registry instead of a hardcoded account name.
2026-07-13 12:17:07 -04:00
Justin e12b92924e docs(golden-paths): add 11-19, all live-verified 2026-07-13
Nine new end-to-end golden paths, each run live on a real account and cleaned up:
- 11 Public Endpoints (qwen3-32b-awq, native + OpenAI-compatible routes)
- 12 Serverless streaming (/stream, generator handler)
- 13 Autoscaling tuning (queue-delay vs request-count, watched scale-up live)
- 14 Load-balancing endpoint (GraphQL saveEndpoint type:LB + HTTP worker)
- 15 Monitor & debug (/health, worker logs, job lifecycle)
- 16 Serverless webhooks (real callback captured + retry behavior)
- 17 Serverless WebSocket (worker-lb-websocket, wss://<ep>.api.runpod.ai/ws)
- 18 Concurrent handler (4x throughput on one worker via concurrency_modifier)
- 19 3-region same-file endpoint (32/32 across 3 DCs, identical payload)

Also: router SKILL 'Worked examples' table expanded to all 19; retired the
'planned/to-be-done' backlog in the golden-paths README (all done); README intro +
TOC updated; endpoint-workflows.md fold-backs. All test resources torn down; only
pre-existing whisper endpoints remain.
2026-07-13 11:58:38 -04:00
Justin 3c0d9c6f4a docs(golden-paths): add planned-paths backlog (11-19)
Backlog of candidate golden paths not yet written: Public Endpoints, serverless
streaming (/stream), autoscaling tuning, load-balancing endpoint, monitor/debug +
alerts, webhooks, WebSocket worker, concurrent handler, and a 3-region same-file
endpoint (extends 10). Each with what it should prove + key references.
2026-07-13 11:26:35 -04:00
Justin 1ba162d33b docs(golden-paths): multi-volume attach via runpodctl >=v2.4.0; keep platform bug notes out
- 10: headless multi-volume attach documented as runpodctl >=v2.4.0
  `--network-volume-ids v1,v2` (verified live), with the GraphQL saveEndpoint
  object-shape path + Console as fallbacks. Reframed CLI guidance to a clean version
  requirement (check runpodctl version; install from GitHub releases if the Homebrew
  tap lags). Removed transient platform-status notes from the skill per maintainer
  request (those are tracked/flagged separately with the team).
- runpodctl SKILL: add multi-region `--network-volume-ids` note (>=v2.4.0).
- README: TOC row 10 updated to the runpodctl lane.
2026-07-10 18:03:42 -04:00
Justin 4d24ca2f50 docs(golden-paths): live-verify 09 + 10; router links golden paths; fold gaps
- 09 (custom dual-mode dev loop): live-verified end to end — RTX 4090 pod transcribed
  the JFK sample via python handler.py, built image (runpod/pytorch base) reproduced it
  on clean /app, serverless flip returned identical transcription (pod<->serverless
  parity). Template fixes hit live: PEP-668 --break-system-packages, and a two-step
  install (--ignore-installed cryptography runpod) because runpod pulls a newer
  cryptography the base can't uninstall. Base pinned to the exact runpod-torch-v280 image.
- 10 (multi-region HA serverless): live-verified — 2 volumes (EU-RO-1 + EU-CZ-1),
  S3-synced identical, 16/16 requests COMPLETED across both DCs with identical output.
  Real correction: headless multi-volume attach is GraphQL saveEndpoint with
  networkVolumeIds:[{networkVolumeId}] OBJECTS — runpodctl --network-volume-ids and REST
  networkVolumeIds:[strings] both reject bare strings. Also: api.runpod.io/graphql needs
  a browser User-Agent; computeType is REST-only (GraphQL round-trip reset to GPU).
- router (skills/runpod/SKILL.md): add 'Worked examples (golden paths)' table (audit P1)
  so a cold agent discovers the 10 worked paths and the HA journey stops dead-ending.
- gaps folded into docker.md / endpoint-workflows.md / gotchas.md.
- All test resources torn down (pods/volumes empty; only pre-existing whisper endpoints).
2026-07-10 17:48:44 -04:00