Skip to content

Instantly share code, notes, and snippets.

@vaurdan
Created May 6, 2026 12:52
Show Gist options
  • Select an option

  • Save vaurdan/f25bbcbb5d4a840b8978663309dd3071 to your computer and use it in GitHub Desktop.

Select an option

Save vaurdan/f25bbcbb5d4a840b8978663309dd3071 to your computer and use it in GitHub Desktop.
SpritesDoc API update 2026-05-06 — start_and_probe action + behavior fixes

SpritesDoc API — update 2026-05-06

This patch covers behavior fixes and one new action since the last agent-facing gist. Read this alongside the existing gists (full API spec, mutations flow, sprite.lookup additions). Nothing here renames or removes any existing endpoint or arg; the patch is additive.


1. New action: start_and_probe

probe alone fails when the sprite is cold — there's no channel to send the manual probe to, so the action returns {:error, :not_connected}. The common pattern (wake → confirm health) is now one approval.

Definition

field value
name start_and_probe
destructive false (no typed-name confirm prompt)
description "Wake a cold sprite and, once its channel reconnects, dispatch a manual health probe."

Args

{
  "wait_ms": 15000   // optional, integer 1000–30000, default 15000
}

wait_ms is the budget for the channel-reconnect poll after start succeeds. Polled every 500ms internally.

Propose

Same POST /internal/spritesdoc/propose flow as every other action:

curl -X POST "$SPRITES_API/internal/spritesdoc/propose" \
  -H 'authorization: Bearer …' \
  -H 'content-type: application/json' \
  -d '{
    "action": "start_and_probe",
    "target": { "org_id": 77, "sprite_name": "demo-1" },
    "args": { "wait_ms": 15000 },
    "reason": "Diagnosing cold-start regression"
  }'

Response is the standard proposal envelope with approval_url. The operator clicks Authorize in the popup. No typed-name confirmation because the action is non-destructive.

Result shapes

The agent polls GET /internal/spritesdoc/proposals/<id> until status is terminal. On executed, the result field is one of:

// happy path: machine started AND channel reconnected in time
{
  "machine_id": "",
  "channel_pid": "<pid>",
  "message": "machine started and health probe dispatched"
}

// partial: machine started, but the channel didn't reconnect within
// wait_ms. The result is still {:ok}, NOT a failure — the agent should
// poll sprite.lookup until the channel is up, then call `probe` again.
{
  "machine_id": "",
  "probe_dispatched": false,
  "message": "machine started; channel did not reconnect within 15000ms — probe skipped"
}

A hard start failure (Fly API error, etc.) still surfaces as status: "failed" with an error string — same as the existing start action.

When to use which

Sprite state Use
Channel currently connected (runtime_status: running / warm) probe — fast, no Fly API call
Cold (runtime_status: cold / unknown and no channel) start_and_probe — wake then probe
You only need to wake (don't care about probe right now) start

2. start was already available

If your agent didn't have start in its action vocabulary, it does now (it's been there since the mutations branch first shipped — just flagging in case the local docs were stale).

{
  "action": "start",
  "target": { "org_id": 77, "sprite_name": "demo-1" },
  "args": {}
}

Result: { "machine_id": "…", "message": "machine started or already running" }.

Non-destructive, no confirmation prompt.


3. Behavior fixes (no API shape changes)

These are bugs that produced spurious 500s or wrong field values. No calling code needs to change; just expect more useful responses.

a. sprite_not_found now returns 404 (was 500)

sprite.lookup and evidence-pack previously returned 500 when the target sprite didn't exist. They now return:

HTTP/1.1 404 Not Found
content-type: application/json

{ "query": "sprite.lookup", "error": "sprite_not_found" }

Same change for org_not_found. If the agent's retry policy treats 5xx as "transient, retry" and 4xx as "permanent, surface to user," that classification is now correct.

b. runtime_status no longer returns null

Two paths in sprite.lookup and org.sprite_list could return runtime_status: null when the sprite was cold and its DB runtime_state column was nil. Root cause: is_atom(nil) is true in Elixir, so a normalize_runtime/1 clause matching atoms was returning nil instead of falling through to :unknown.

After the fix, runtime_status is always one of: "running", "warm", "cold", "error", "unknown".

If the agent had defensive code treating null as "unknown," that's fine to leave in place — it just won't fire anymore.

c. sprite.lookup machines list no longer crashes

The machine list was raising Fly.Machines.API.Machine.fetch/2 is undefined because the code was using machine["id"] map-access on a struct. Fixed to use struct field access. The shape of each machine entry is now:

{
  "id": "",
  "app": "",
  "state": "started" | "stopped" | ,
  "region": "iad",
  "version": "",
  "image_ref": "registry.fly.io/…"
}

This is slimmer than the previous documented shape because the Fly API client only maps a subset of fields onto its struct. name, private_ip, instance_id, created_at, updated_at are no longer returned (they were never reliably populated anyway — the previous shape was aspirational). If your agent prompt referenced any of those, remove them.

d. evidence-pack accepts sprite_id targets

POST /internal/spritesdoc/evidence-pack previously only worked with { "target": { "org_id": …, "sprite_name": "…" } }. A target shaped as { "target": { "sprite_id": "sprite-…" } } would FunctionClauseError under the hood and return Phoenix's bare 500 page.

Both shapes now work, matching sprite.lookup. The endpoint also now returns the standard SpritesDoc error envelope on any unexpected crash:

{ "error": "internal_error", "detail": "<exception message>" }

4. Updated action vocabulary (full list)

After this patch, the action registry has:

name destructive confirm? summary
probe no Manual health probe; requires channel up.
start no Wake a cold sprite.
start_and_probe no Wake then probe (single approval).
upgrade yes typed sprite name Run upgrade to a target sprite-env version.
recover yes typed sprite name Destroy and reprovision the Fly machine; data preserved via Litestream.

There is no runtime-discovery endpoint for actions today (the existing /catalog only lists queries). The action vocabulary is what's in this table — check the "what's new" gists for additions before assuming an action exists.


5. Decision matrix the agent should know

When the agent has decided "I want to wake sprite X and confirm it's healthy":

  1. Call sprite.lookup first. Don't propose anything if it's already runtime_status: running.
  2. If cold / unknown: propose start_and_probe. Wait for the operator to authorize.
  3. If start_and_probe returns with probe_dispatched: false, poll sprite.lookup until runtime_status flips to running or warm. Then propose a follow-up probe.
  4. Surface the final health result (from sprite.lookup after probe) to the user.

Do not propose recover automatically. recover is destructive and should only be proposed when the diagnosis explicitly indicates the machine is broken (e.g., repeatedly fails to start, lease stuck, Litestream lag suggests data corruption that a fresh provision will clear). The operator will see the typed-confirm prompt regardless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment