Skip to content

rutis v0.9.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 10:56
· 203 commits to main since this release
d4999aa

rutis 0.9.0

中文 · Migration guide

0.9.0 adds Bun and Go as plugin runtimes. The host refuses a synchronous call that would close a wait cycle across runtimes, rutis-host check finds services nothing provides, and stopping and local channels are more reliable. The minimum Python goes down to 3.10 and the minimum Node to 22.

Packages

All of these are at 0.9.0:

Registry Packages
crates.io rutis (the core), rutis-bridge, rutis-loader, rutis-host; the dylib toolchain rutis-sdk, rutis-dylib, rutis-dylib-meta, rutis-dylib-launcher
npm @arcships/rutis, @arcships/rutis-runtime, @arcships/rutis-bun (first release), @arcships/rutis-host, and the five platform host packages
PyPI rutis, rutis-host
Go github.com/arcships/rutis/go/rutis (first release, tag go/rutis/v0.9.0)
GitHub rutis-host binaries for Linux / macOS (x64 / arm64) and Windows (x64), with SHA-256 checksums

rutis-cli keeps its own version.

What's new

  • Bun runtime (@arcships/rutis-bun, Bun 1.4+): JavaScript and TypeScript plugins can run in Bun; plugin code still uses @arcships/rutis. The host takes runtimes.bun, rutis-host new --lang bun creates a project, and rutis-host dev recognizes Bun projects and reloads them. In Rust: Launcher::bun / LocalRuntime::bun (feature bun) and the loader's bun:<module> rows. Synchronous calls are re-entrant. A plugin with a syntax error is reported with file:line:column. Remote listening and Windows come in a later release.
  • Go runtime (go/rutis, Go 1.24+): a Go plugin is compiled into a binary, and each binary is one runtime with only the standard library as a dependency. Write plugins with Define / Plugin[C] / Serve; the config struct gives the JSON Schema, --rutis-manifest prints the manifest, and rutistest tests plugins. The host takes runtimes.go (dir / binaries / start / idle) and starts a runtime when a row needs it and stops it when idle; rutis-host new --lang go, rutis-host go add <module>@<version>; dev builds Go projects and restarts only the Go runtime. In Rust: Launcher::go / LocalRuntime::go and the loader's GoResolver / GoRuntimes (feature go).
  • Wait cycles across runtimes: before forwarding a synchronous call, the host checks who waits for whom; a call that would close a cycle (for example Python → Node → Python, each waiting synchronously) fails with SyncWaitCycle instead of deadlocking. The Node, Python and Bun runtimes declare sync-wait. A nested call's origin carries a session label, so nested calls such as Python → Node → Python now complete.
  • rutis-host check: without running plugins, it checks that every service a row injects is provided by some row, and exits with 1 when one is not (when the provider is on another node, it only notes it). Disabled rows are no longer resolved. In Rust: Loader::check. It also prints each runtime's implementation and engine, and the Go binaries.
  • Stopping and signals: run / dev handle SIGINT / SIGTERM / SIGHUP (Ctrl-C / Ctrl-Break / closing the console on Windows), unload the rows and exit. --shutdown-timeout <seconds> (or RUTIS_SHUTDOWN_TIMEOUT, 10 by default) bounds the cleanup, and the exit code is 2 when it did not finish. Runtime processes run in their own process group, so Ctrl-C in the terminal reaches only the host.
  • A runtime process's standard streams: stdin / stdout / stderr on Launcher / LocalRuntime can each be Stdio::Null or Stdio::Inherit, for example to give stdin to a plugin that reads the terminal.
  • A plugin that disposes itself: when a plugin in a Node row unloads itself, the host gets SelfDisposed and the row becomes disabled (a copy in an instance becomes Stopped) instead of staying shown as running. New: the rows.ended message and Process::on_row_ended.
  • Local channels: one message is at most 16 MiB (the same in Rust and every language runtime); a larger one closes the channel, and a stream that ends in the middle of a message is a failure everywhere.
  • Configuration: with rutis.json given as a relative path, rows such as ./plugin.mjs now resolve against the configuration file; unknown fields in rows are warned about; a listener with only cert or only key is an error instead of silently running without TLS; runtime names must be unique.
  • Python (3.10 minimum): on 3.10 / 3.11 rutis starts plugin methods eagerly itself, the same way as the native implementation on 3.12+. A cancel that arrives during a plugin method's first step is no longer ignored; a WebSocket listener whose accept() fails ends as a failure; python -m rutis prints an error and exits with 1; Listener.close() works with websockets older than 17. The optional network dependency needs websockets 15+.
  • Other fixes:
    • Child processes inherit only fds 0–3, which fixes occasional Broken pipe errors and hangs when runtimes start concurrently on macOS.
    • A process that ends has its exit status reported, including one that closed its channel but has not exited after 10 s, and how it ended when a send fails.
    • A protocol version mismatch reliably gives Handshake::Incompatible on both sides.
    • Calling the root's dispose() and Ctx::shutdown() at the same time no longer makes dispose() return Closed now and then.
    • A crashed Go runtime is Down instead of staying "Starting".

Compatibility

  • Upgrade the host and the language runtimes to 0.9 together: protocol versions are unchanged (local sessions use protocol 2, sessions with endpoint identity protocol 3) and PLUGIN_API is still 1, but a 0.9 host forwards call origins with a session label that 0.8 runtimes do not accept; they end the session.
  • Rust API: Launcher and Spawn in rutis-bridge are #[non_exhaustive], and RowSchema has a new field version; rutis and rutis-loader have no incompatible changes. Dylib plugins must be rebuilt with the 0.9.0 SDK.
  • rutis-host: check fails on a service nothing provides; exit code 2 is new.
  • Minimum versions: Rust 1.88 (was 1.85), Node 22 (was 24), Python 3.10 (was 3.12), Bun 1.4, Go 1.24.

See the migration guide.

Getting started

npx @arcships/rutis-host@0.9.0 new greeter --lang node
npx @arcships/rutis-host@0.9.0 new greeter-bun --lang bun
npx @arcships/rutis-host@0.9.0 new greeter-go --lang go
uvx --from rutis-host==0.9.0 rutis-host new greeter-py --lang python

Then read TypeScript plugins, Bun plugins, Python plugins, Go plugins, embedding in Rust or connecting nodes.