Skip to content

Resources

A resource file is YAML; one file holds any number of documents separated by ---. Each document is a resource:

kind: port
name: web
spec:
preferred: 3010
---
kind: process
name: web
needs: ['${ process.build.status.exit_code === 0 }']
spec:
cmd: ./node_modules/.bin/rsbuild
args: [dev, --port, "${ port.web.status.value }"]
env:
PORT: ${ port.web.status.value }
restart: always
watch:
include: ["src/**"]
field
kind one of the kinds: process, port, folder, worktree, watcher
name unique per kind in its namespace. The id is <namespace>/<kind>/<name>; kinds may share a name (port/web and process/web)
spec the kind’s input. Any string in it may hold expressions
needs optional: expressions that must all be truthy before the resource is applied

status is never written in YAML. The kind publishes it at runtime: a process has pid, state, restarts, healthy; a port has value and url. Other resources read it through expressions.

A duplicate id, an unknown kind, a YAML parse error or an unknown key in a spec is a diagnostic: shown per workspace in runy ns and the console’s workspace journal, and the file’s other documents still load.

Before a resource is applied, every expression in its spec and needs is evaluated. The result is the resource’s condition:

condition meaning
Resolved every expression evaluated; the rendered spec is what the kind runs
Pending an expression read a status that is not published yet, or a needs entry is falsy. waiting_on lists what it waits for (web/port/web.status, or a needs expression). The resource is not applied
Error an unknown resource, a misspelled field, an evaluation error or a reference cycle; the message names it

runy ps prints the condition next to the status; the console shows it in the tree and on the resource’s Overview tab.

A resource whose render turns Pending or Error after it was Resolved keeps running on its last applied spec. The condition is shown and the process keeps running.

The daemon owns one actor per resource id. After any change (a file saved, a status published, a namespace switched) it re-renders every resource and compares each with what that actor last got:

  • a new id spawns an actor;
  • a changed rendered spec is applied: a process whose command, args, env, cwd, restart policy, watch set or health checks changed is stopped and started on the new spec, and one whose only change is journal.max_lines keeps its child; a port keeps its value while it still fits the spec; folder, worktree and watcher re-apply idempotently;
  • an id that left the active set (file edited, namespace deactivated) is stopped.

Rendering is memoized with early cutoff: a status change re-renders its readers, but a reader whose rendered spec came out equal is not re-applied, so a port republishing the same value does not touch the process that uses it.

With watchman installed the daemon subscribes to the resource globs and applies every save. Without it, the files are read when the workspace starts and on runy signal workspace restart.

Terminal window
runy signal <namespace>/<kind>/<name> <signal>
runy signal workspace restart

A process takes restart and stop. The workspace takes restart, which re-reads every resource file and re-snapshots the environment. Other kinds ignore signals. The console offers the same actions from a row’s … menu.