Download
Docs · Actions

Actions: a job that runs when a save lands

An action is two files in a project, under .interlinked/actions/: a definition (<name>.yml: when it runs, where, what it may read and write, the steps) and the script its steps run. They travel with the project like any other file. When a save that matches arrives in the vault, the run happens on a machine, and whatever it writes inside its folders comes back as a new save, made by the action.

Two worked examples, each runnable from an empty project by following its page:

  • A measured LOD chain for Roblox: meshes saved into Characters/ come back as a chain of levels, each rendered and scored against the original. Runs on the maker's machine.
  • Web avatars from any image: images dropped into Avatars/ come back in Web/avatars/ at three sizes, WebP. Runs on ours.

The definition, every key

name: Roblox-ready meshes            # what lists show; the file's name is what commands take
on:                                  # when it runs: one or more of the five below
  push:                              # a save lands in the vault
    branches: [main]                 #   on these branches (left out: any)
    paths: [Characters/**/*.obj]     #   touching these paths (globs; left out: any)
    kinds: [added, changed]          #   in these ways: added, changed, deleted (left out: all three)
  manual:                            # someone starts it: Run now, `ifile actions run`, the MCP
    inputs:                          #   named values given at the start, each IFILE_INPUT_<NAME>
      quality: { description: "0 to 100", default: "82", required: false }
  schedule: ["0 6 * * 1-5"]          # cron lines, UTC, on the newest save of the default branch
  api: true                          # an outside system may start it: POST /v1/projects/:id/actions/:name/runs
  action: [meshes]                   # after these actions' saves (an action's save sets off nothing else)
runs-on: maker                       # maker | team | <a machine's name> | hosted (below)
image: ghcr.io/you/tools:1           # hosted only; not built yet: a hosted run uses our standard image
timeout: 60                          # the most minutes a run may take, 1 to 360; past it it is stopped and fails
needs:
  read: [Characters/**]              # all a run can fetch; nothing else of the project reaches it
  write: [Characters/**/*.lod*.obj]  # all it may change; a change outside fails the run, nothing saved
  tools: [node, npm]                 # checked on the machine before the run
  secrets: [DEPLOY_TOKEN]            # set with `ifile secrets set`; reach the steps as environment variables
steps:                               # run in order, in the project's folder; one failing ends the run
  - name: Cut the chain
    run: node .interlinked/actions/lod-chain.mjs
    each: changed                    # once per changed file, its path in FILE
save:
  message: "LOD chain for {count} meshes, worst score {score}"   # {count}: the files that set it off; {key}: a key=value line a step wrote to IFILE_OUTPUT
  to: same-branch                    # the branch it ran on (a branch of the action's own is not built yet)

Every step sees IFILE_ACTION, IFILE_RUN (the run's number), IFILE_BRANCH, IFILE_SAVE and IFILE_SAVE_NUMBER (the save it runs on), IFILE_TRIGGER (push, manual, api, schedule or action), IFILE_CHANGED (the changed paths, one a line), IFILE_OUTPUT (a file for key=value lines) and CI=true. A definition that does not check says what is wrong and on which line: ifile actions --in <project> lists every action with its problems.

Where it runs, and what it costs

runs-onwherewhen to use itcost
maker (the default)the machine that pushed the savea solo developer or a small team: the files are already therenothing for the run
teamany machine the project's team switched onwork that needs a tool or a GPU only some machines have, or should not depend on who pushednothing for the run
a machine's namethat one registered machineexactly one machine holds the licence or the hardwarenothing for the run
hosteda machine we start for the run: Linux, 2 CPUs, 8 GiB, Node, Python, vips, ImageMagick, ffmpeg, gltfpack and more; reads up to 12 GiB of the projectno machine of yours is on, or the run must not touch oneCloudflare's container rates plus 25%, for the time the machine is up: about half a cent a minute, never rounded to minutes

What a run reads and the save it makes are stored and moved like any other bytes of the project, on the organization's storage and transfer.

Nothing runs on a machine its owner did not switch on. A machine takes runs only after ifile runner start --in <project> (or --team <team>), or Run actions here on the app's Flow tab, and stops when told. Opening a link or cloning a project never runs anything.

Or your own pipe: a webhook and a key

Rather run the work on your own servers, in your own CI? Everything an action does can be done from outside with a webhook and a key:

  1. Hear about the save. Point a webhook at your service: ifile auth webhooks add https://you.example/hooks --project <id> --events version.pushed. Each push sends a signed version.pushed (Standard Webhooks: check webhook-signature against the raw body with your secret, answer 2xx within five seconds, ignore a webhook-id you have seen).
  2. Read what you need. With an API key (ifile auth keys new ci --project <id> --can project:read,status:write): GET /v1/projects/<id>/tree?at=<number> lists the files, GET /v1/projects/<id>/archive?at=<number> streams them as a tar.
  3. Say how it went. POST /v1/projects/<id>/commits/<number>/statuses and PUT .../reports/<name> put your result on the save, where the app and the site show it.
  4. Save results back. On your server, with a sign-in key in INTERLINKED_TOKEN (ifile auth login-key new ci --lasts 30d on your own machine), the CLI works unattended: ifile clone <link> (a folder named for the project), change the files, then ifile save <folder> -m "..." and ifile push <folder>.

The routes are on the API page.