Repository navigation
rutis v0.17.2
rutis 0.17.2
0.17.2 fixes problems that people using rutis run into today, all found by the developer-experience evaluation (#663). Plugin code in every language stays as it is, and so does rutis.json. The "Behaviour changes" section lists every difference you can see.
One plugin's problem stays with that plugin
- A bad value fails only that call (#675, #706). A value that cannot cross processes (an object with methods, or a class instance, sent to a language other than Rust) or a message over 16 MiB used to end the whole runtime process, stopping every plugin in that runtime. Now it is refused before it is sent and only that call fails; the error names the method, the argument and the reason:
argument 1 of py_taker.take: an object with methods cannot cross to py; pass plain data or a function instead. For a return value it saysthe result of …. - Sparse arrays cross: the holes in
[1, , 3]are sent asundefinedand arrive asnull/None. They used to end the session. - A non-
Errorvalue thrown by TypeScript no longer ends the session when it passes through Go: the TypeScript caller gets the original value. - A runtime process that exits blames no other plugin (#682, #707). Plugins still loading in that runtime used to be reported as
failed to start. Now the report says which runtime exited and how (exit status or signal), and all its rows wait for that runtime, the ones still loading included; calls in progress that fail say the same. The runtime's own error output and stack still appear in the output. - Providing a name that is taken fails only that row. When a plugin provides a service name the host already has, only its own row fails; plugins using the host's service keep running, in an isolated scope too. Another, innocent plugin in the same runtime used to fail to start as well.
- When the host exits, the runtimes it started exit too, also while a plugin is in a busy loop. Node and Bun runtimes have a watchdog thread: once the connection to the host has ended, if the process is still there one second after the shutdown deadline, it ends the process. The Python runtime likewise counts from the thread that reads the connection. A busy Node runtime used to stay behind as an orphan process.
- When a remote runtime reconnects, all its rows are resolved again with the new description (#701). Rows resolved while it was connected used to keep the old result: a plugin replaced over there (one that now uses another service, say) was still loaded by its old description. Local runtimes are unchanged.
A runtime that is not ready (for example one that never greets because a debugger is attached) still holds up the rows of other runtimes; this moves to 0.18, together with runtime status (#678).
Waiting lines say what is missing
rutis-host's waiting lines name the missing service and why it is missing:weather: waiting for lmm (no row provides it; did you mean llm?),chat: waiting for llm (row model failed: …),waiting for llm (row model's runtime py is down), and two rows thatinjecteach other. When disabled rows that were never resolved might provide it, the line does not claim that no row does:waiting for llm (no enabled row provides it; rutis-host check says whether a disabled row does).- A plugin using a service it does not declare in
injectis told to add it:service tsvc is not available to this plugin, which does not declare it in inject: add 'tsvc' to its inject(the same sentence in Node, Bun, Python, Go, Swift, Kotlin and the testing tools). A service it can get is still given to it. - Rust embedding: when a plugin has waited 10 seconds for a service nothing provides, the error sink gets one
ErrorKind::StillWaitingnaming the missing service;root.report_waiting_after(None)turns it off andSome(duration)changes the delay.rutis-hostdoes not use it (its waiting lines already say this). When a remote link is down for more than 10 seconds, the link's own plugin reports this once too, under an internal name (rutis-bridge/import#main); in 0.18 the platform's own waits show only in status (#678). rutis-host checkwarns about rows thatinjecteach other in a cycle and about several rows providing the same service in one scope; the exit code is unchanged (0.18 makes these errors). When the only row providing a service is disabled, the warning names that row.
rutis-host dev
- Only the row whose file changed reloads, and it says why:
weather: reloaded (src/index.ts changed); rows using it restart with it and say so too:caller: restarted (weather reloaded). - A change to
rutis.dev.jsonapplies while running, by the rules ofrun --watch-config. - When the new code does not import (a syntax error), the row keeps running its previous version:
weather: cannot reload: …; the previous version keeps running. - When a runtime process has stopped, the next change to one of its rows starts it again, instead of printing a false
reloaded. Every time a runtime ends,runtime …: downis printed. - Go and Swift projects are rebuilt only when their own sources change. After a panic in a Go plugin's
applyis fixed, the old failure is no longer printed.
Host authors
- When rows use
py,bunornodeandruntimesdoes not list it, it is enabled with its defaults and a note is printed once. If a runtime enabled this way cannot start, the host does not exit; only its rows fail. - When the Python environment does not exist, or has no
rutisinstalled, the error gives the exact commands to create it and install it. - The rutis-host guide opens with a quick start (one TypeScript plugin, one Python plugin) and has a new section on how a host installs plugins.
- The templates of
rutis-host newdeclareprovides, so a new project passescheck --strictas it is. rutis-hostinstalled from npm: the launcher forwards SIGUSR1 torutis-host(ExecReload=/bin/kill -USR1 $MAINPIDworks) and no longer opens Node's debugger port.
Plugins' dependency on the SDK
- Python plugins declare only a lower bound:
rutis>=0.17, no longer<0.18. A plugin and the Python runtime share the onerutisin the host's environment, and an upper bound kept the host from upgrading. While the plugin API version stays the same, Python'srutisdoes not remove or rename names plugins import. - npm and Bun plugins keep a
^range: each plugin carries its own copy of the SDK, and several copies work side by side.
Rust embedding
- The docs now say exactly what
view.awaitwaits for: it returns once what was queued for the plugin has been handled; a plugin whose dependencies are missing staysPendingand is not running. To wait until it runs, watch its state withview.watch(). - New:
rutis_bridge::session::call_service(&ctx, name, method, args)calls any service by name from Rust (one provided by a row in any language, a#[rutis::service]type, or a hand-written one). - Every example in the rust-host guide compiles and the first one runs; the loader example now includes
Builtins,Chainand absolute paths.
Fixes
- Small mistakes in templates and guides (#668): the TypeScript template builds before
npm pack; the Python template's test command; the Go guide's test andrutis.dev.jsonare complete;rutis-host typesis in the command table. - A reload that removes a local runtime while it is starting returns only once its process has ended (#716). A stopping link used not to wait for the step that starts the process, so the reload could return before the process started; the process then started and was ended shortly after. The same at host shutdown.
Behaviour changes
What you can see differently after upgrading; none needs a code change:
| Where | Before | Now |
|---|---|---|
Kernel FiberView::restart / update |
On a failed plugin whose dependencies are gone, returned the last load's error | Returns success and the plugin waits for its dependencies (this restart loaded nothing, so nothing failed) |
| Kernel error sink | Nothing reported while a plugin waits for a service nothing provides | One StillWaiting after 10 seconds; report_waiting_after(None) turns it off |
| A bad value crossing processes | The runtime process exited | Only that call fails; the error starts with argument N of service.method: or the result of service.method: |
Text of rutis_bridge::session::Error::Value |
Started with invalid binding value: |
No such prefix |
| Sparse arrays | Ended the session | Holes cross as undefined |
use of a service not in inject (when it is not available) |
service x is not available |
Adds that it should go into inject |
rutis-host waiting lines |
waiting for its services |
Name the missing service and the reason (unchanged when no reason can be told) |
rutis-host check |
— | Warns about cycles, same-name provides, and a disabled only provider; exit code unchanged |
rutis-host: rows use an undeclared runtime |
The row failed: no plugin named "py:llmbox" |
The runtime is enabled with its defaults, with a note |
rutis-host dev |
One changed file reloaded every row of that language; rutis.dev.json changes did not apply |
Only that row reloads, saying why; rutis.dev.json applies; SIGUSR1 is handled |
Under dev, a runtime row restarting for another reason |
Read the file as it is now | Runs the code of its last reload (run is unchanged) |
| The Python runtime failing to re-import a module | Left the module half changed | Puts back what it was before |
| The npm launcher receiving SIGUSR1 | Node opened its debugger port | Forwarded to rutis-host |
| Python plugin template | rutis>=0.17.x,<0.18 |
rutis>=0.17.x |
| Rows still loading when their runtime process exits | Failed with failed to start |
Wait for that runtime; fail only if the runtime's end has not reached the row within 30 seconds |
| Node runtime: a host service whose name a row holds | Threw, failing the rows using that service | Registered once that row's service is withdrawn |
| Node and Bun runtime processes | — | One more watchdog thread: once the connection has ended, a process still alive one second after the shutdown deadline is ended, with a line on stderr |
| The Python runtime's shutdown deadline | Counted from when the main thread saw the connection end | Counted from when the thread reading the connection saw it end; the give-up message adds "or a plugin keeps the runtime busy" |
| A remote runtime reconnecting | Rows resolved while connected were not resolved again | All its rows are resolved again |
rutis-bridge RuntimePlugin |
Provided the Runtime service, then set the state to Ready |
Sets Ready, then provides the service (back to Starting if providing fails) |
Compatibility
- No API is removed or renamed, and the formats of
rutis.jsonandrutis.dev.jsonare unchanged; nothing to migrate. - The protocol is unchanged. 0.17.2 connects to 0.17.1, 0.17.0 and 0.16.0 as usual. The host checks values before forwarding them, so with an older runtime under a new host a bad value also fails only that call; a sparse array sent by an older Node runtime still ends the session.
- New public API: in rutis
CtxHost::report_waiting_after,CtxHost::name_keys_with,KeyNamer,ErrorKind::StillWaiting; in rutis-bridgesession::call_service,RuntimeHandle::starts,Sender::max_message(with a default),Wire::no_caller(with a default); in rutis-loaderLoader::waits,Loader::waits_of,report::Wait,report::WaitReason,CheckReport'scyclesandclashes,Requirement::like,Supply::OnlyDisabled,BinaryRuntimesHandle::states_started.
Packages
All of these are version 0.17.2: on crates.io rutis, rutis-macros, rutis-bridge, rutis-loader, rutis-host, rutis-sdk, rutis-dylib, rutis-dylib-meta, rutis-dylib-launcher, rutis-migrate; on npm @arcships/rutis, @arcships/rutis-runtime, @arcships/rutis-bun, @arcships/rutis-host and the five platform packages; on PyPI rutis, rutis-host; the Go module github.com/arcships/rutis/go/rutis (tag go/rutis/v0.17.2); the Swift package (v0.17.2); Kotlin io.github.arcships:rutis and io.github.arcships:rutis-ksp; the rutis-host binaries on the GitHub release page.