Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ jobs:
- crates/rutis-bridge/**
- crates/rutis-loader/**
- crates/rutis-host/**
- tests/e2e/tests/dev_loop.rs
# What decides whether the SDK builds reproducibly. Kernel
# changes reach the SDK too, but are checked in full runs
# (Q12.6.3).
Expand Down Expand Up @@ -858,6 +859,14 @@ jobs:
run: |
cargo test -p rutis-loader --features node,python,swift --test swift_rows --test multilang_swift
cargo test -p rutis-loader --features node,python,swift --test caller_rows --test sync_cycles swift
# runtimes.swift in rutis-host (rows, restart, check), and the plugin
# author's loop: new --lang swift, swift test, check, dev rebuilding.
- name: cargo test (rutis-host with Swift, the Swift development loop)
env:
RUTIS_E2E_DIR: /tmp/rutis-e2e
run: |
cargo test -p rutis-host --test swift
cargo test -p rutis-e2e --test dev_loop the_swift_plugin_development_loop
# The events conformance with X a Swift plugin, in the placements a
# pull request runs (written; full runs sample more).
- name: cargo test (events conformance, X Swift)
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
**A plugin runtime for software that iterates on itself**

Agents and long-running programs write, swap and hot-reload their own plugins, and rutis keeps every change safe.<br>
A Rust core · plugins in TypeScript, Python and Go · across processes and machines
A Rust core · plugins in TypeScript, Python, Go and Swift · across processes and machines

[![crates.io](https://img.shields.io/crates/v/rutis.svg?label=crates.io)](https://crates.io/crates/rutis)
[![npm](https://img.shields.io/npm/v/@arcships/rutis.svg?label=npm)](https://www.npmjs.com/package/@arcships/rutis)
Expand Down Expand Up @@ -72,7 +72,7 @@ export default definePlugin<{ city?: string }>({
})
```

`llm` can come from another plugin in the same process, from a Python plugin, or from another machine; this plugin stays the same. The full workflow is in the guides for [TypeScript](docs/guide/typescript-plugin.en.md), [Bun](docs/guide/bun-plugin.en.md), [Python](docs/guide/python-plugin.en.md) and [Go](docs/guide/go-plugin.en.md).
`llm` can come from another plugin in the same process, from a Python plugin, or from another machine; this plugin stays the same. The full workflow is in the guides for [TypeScript](docs/guide/typescript-plugin.en.md), [Bun](docs/guide/bun-plugin.en.md), [Python](docs/guide/python-plugin.en.md), [Go](docs/guide/go-plugin.en.md) and [Swift](docs/guide/swift-plugin.en.md).

### In Rust

Expand Down Expand Up @@ -227,7 +227,7 @@ The [design philosophy](docs/design-philosophy.en.md) explains these choices, an
| Running plugins in your app | [`rutis-loader`](https://crates.io/crates/rutis-loader), [`rutis-bridge`](https://crates.io/crates/rutis-bridge), [`rutis-dylib`](https://crates.io/crates/rutis-dylib) (dylib plugins) | [`@arcships/rutis-runtime`](https://www.npmjs.com/package/@arcships/rutis-runtime) (Node), [`@arcships/rutis-bun`](https://www.npmjs.com/package/@arcships/rutis-bun) (Bun) | [`rutis`](https://pypi.org/project/rutis/) |
| A host without Rust | [`rutis-host`](https://crates.io/crates/rutis-host) | [`@arcships/rutis-host`](https://www.npmjs.com/package/@arcships/rutis-host) | [`rutis-host`](https://pypi.org/project/rutis-host/) |

Go plugins use the module [`github.com/arcships/rutis/go/rutis`](go/rutis): the SDK, and the runtime each plugin binary carries.
Go plugins use the module [`github.com/arcships/rutis/go/rutis`](go/rutis): the SDK, and the runtime each plugin binary carries. Swift plugins (macOS) use the Swift package of this repository, [`Rutis`](swift/README.md) (`.package(url: "https://github.com/arcships/rutis", from: "0.14.0")`), the same way.

All of these form a release train: released together at one version, currently 0.13. Use the same version for every rutis package.

Expand All @@ -236,13 +236,13 @@ All of these form a release train: released together at one version, currently 0
| Project | |
| --- | --- |
| [rutis-agent](crates/rutis-agent) · [rutis-cli](crates/rutis-cli) | A minimal coding agent in which the model service, tools, streaming driver and TUI are all plugins. Try it offline with `cargo run -p rutis-cli -- --scripted`. |
| [rutis-host](crates/rutis-host) | A host without Rust: runs TypeScript, JavaScript, Python and Go plugins from a `rutis.json`, reloads them during development, links machines. |
| [rutis-host](crates/rutis-host) | A host without Rust: runs TypeScript, JavaScript, Python, Go and Swift plugins from a `rutis.json`, reloads them during development, links machines. |
| [rutis-dsh](crates/rutis-dsh) | Runs the full dsh web interface inside a rutis host, with model calls served by aimux in the same process. |
| [aimux-llm](crates/aimux-llm) | Wraps [aimux](https://crates.io/crates/aimux-core) as an LLM service plugin. |

## Documentation

- **[Guides](docs/guide/README.en.md)**: organized by task. Plugins in TypeScript, Bun, Python and Go, running rutis-host, linking nodes, embedding in Rust, working with Cordis.
- **[Guides](docs/guide/README.en.md)**: organized by task. Plugins in TypeScript, Bun, Python, Go and Swift, running rutis-host, linking nodes, embedding in Rust, working with Cordis.
- **[Design philosophy](docs/design-philosophy.en.md)**: why rutis exists, the shape that follows, its costs and principles.
- **[Application design guide](docs/development-guide.en.md)**: splitting an app into plugins, drawing the dependency graph, designing reloads and multiple instances.
- **[Development handbook](docs/development-handbook.en.md)**: API usage, resource cleanup, events, troubleshooting and verification.
Expand All @@ -257,7 +257,7 @@ All of these form a release train: released together at one version, currently 0
rutis is at 0.x and its API is still evolving. Breaking changes are listed in the release notes and come with a migration guide.

- **The core** is pure Rust with tokio, tokio-util, thiserror, serde and serde_json as its only dependencies; it needs Rust 1.88 or later.
- **Language runtimes and rutis-host** (Node, Bun, Python and Go rows, remote runtimes, shared services, peers, Cordis mounts) run on Linux, macOS and Windows x64 (MSVC); they need Node 22+, Bun 1.4+ or Python 3.10+; Go plugins are compiled binaries, built with Go 1.24+. The Bun runtime is not tested on Windows yet.
- **Language runtimes and rutis-host** (Node, Bun, Python and Go rows, remote runtimes, shared services, peers, Cordis mounts) run on Linux, macOS and Windows x64 (MSVC); they need Node 22+, Bun 1.4+ or Python 3.10+; Go plugins are compiled binaries, built with Go 1.24+. Swift plugins are compiled binaries for macOS 14+, built with a Swift 6 toolchain. The Bun runtime is not tested on Windows yet.
- **dylib plugins** load on Linux, macOS and Windows x64 (MSVC).

## Contributing
Expand Down
12 changes: 6 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
**为自我迭代的软件准备的插件运行时**

agent 和长期运行的程序自己写插件、换插件、热重载插件,rutis 保证每一次变化都安全。<br>
Rust 内核 · TypeScript、Python 与 Go 插件 · 跨进程,跨机器
Rust 内核 · TypeScript、Python、Go 与 Swift 插件 · 跨进程,跨机器

[![crates.io](https://img.shields.io/crates/v/rutis.svg?label=crates.io)](https://crates.io/crates/rutis)
[![npm](https://img.shields.io/npm/v/@arcships/rutis.svg?label=npm)](https://www.npmjs.com/package/@arcships/rutis)
Expand Down Expand Up @@ -72,7 +72,7 @@ export default definePlugin<{ city?: string }>({
})
```

`llm` 可以来自同一进程里的另一个插件、一个 Python 插件,或者另一台机器,这个插件都不用改。完整流程见 [TypeScript](docs/guide/typescript-plugin.md)、[Bun](docs/guide/bun-plugin.md)、[Python](docs/guide/python-plugin.md) 和 [Go](docs/guide/go-plugin.md) 插件指南。
`llm` 可以来自同一进程里的另一个插件、一个 Python 插件,或者另一台机器,这个插件都不用改。完整流程见 [TypeScript](docs/guide/typescript-plugin.md)、[Bun](docs/guide/bun-plugin.md)、[Python](docs/guide/python-plugin.md)、[Go](docs/guide/go-plugin.md) 和 [Swift](docs/guide/swift-plugin.md) 插件指南。

### 在 Rust 中

Expand Down Expand Up @@ -227,7 +227,7 @@ stateDiagram-v2
| 在应用中运行插件 | [`rutis-loader`](https://crates.io/crates/rutis-loader)、[`rutis-bridge`](https://crates.io/crates/rutis-bridge)、[`rutis-dylib`](https://crates.io/crates/rutis-dylib)(dylib 插件) | [`@arcships/rutis-runtime`](https://www.npmjs.com/package/@arcships/rutis-runtime)(Node)、[`@arcships/rutis-bun`](https://www.npmjs.com/package/@arcships/rutis-bun)(Bun) | [`rutis`](https://pypi.org/project/rutis/) |
| 不写 Rust 的宿主 | [`rutis-host`](https://crates.io/crates/rutis-host) | [`@arcships/rutis-host`](https://www.npmjs.com/package/@arcships/rutis-host) | [`rutis-host`](https://pypi.org/project/rutis-host/) |

Go 插件用模块 [`github.com/arcships/rutis/go/rutis`](go/rutis):SDK,以及每个插件二进制自带的运行时。
Go 插件用模块 [`github.com/arcships/rutis/go/rutis`](go/rutis):SDK,以及每个插件二进制自带的运行时。Swift 插件(macOS)同样用本仓库的 Swift 包 [`Rutis`](swift/README.zh-CN.md)(`.package(url: "https://github.com/arcships/rutis", from: "0.14.0")`)。

以上所有包组成发布列车,一起发布、版本相同,当前为 0.13。各个 rutis 包请使用同一个版本。

Expand All @@ -236,13 +236,13 @@ Go 插件用模块 [`github.com/arcships/rutis/go/rutis`](go/rutis):SDK,以
| 项目 | |
| --- | --- |
| [rutis-agent](crates/rutis-agent) · [rutis-cli](crates/rutis-cli) | 最小的 coding agent:模型服务、工具、流式驱动和 TUI 都是插件。`cargo run -p rutis-cli -- --scripted` 可以离线体验。 |
| [rutis-host](crates/rutis-host) | 不写 Rust 的宿主:按 `rutis.json` 运行 TypeScript、JavaScript、Python 和 Go 插件,开发时自动重载,连接多台机器。 |
| [rutis-host](crates/rutis-host) | 不写 Rust 的宿主:按 `rutis.json` 运行 TypeScript、JavaScript、Python、Go 和 Swift 插件,开发时自动重载,连接多台机器。 |
| [rutis-dsh](crates/rutis-dsh) | 在 rutis 宿主里运行 dsh 的完整 web 界面,模型调用由同进程的 aimux 提供。 |
| [aimux-llm](crates/aimux-llm) | 把 [aimux](https://crates.io/crates/aimux-core) 包装成一个 LLM 服务插件。 |

## 文档

- **[指南](docs/guide/README.md)**:按任务组织。写 TypeScript / Bun / Python / Go 插件、运行 rutis-host、连接节点、在 Rust 中嵌入、与 Cordis 互通。
- **[指南](docs/guide/README.md)**:按任务组织。写 TypeScript / Bun / Python / Go / Swift 插件、运行 rutis-host、连接节点、在 Rust 中嵌入、与 Cordis 互通。
- **[设计哲学](docs/design-philosophy.md)**:rutis 为什么存在,由此而来的形态、代价和原则。
- **[应用设计指南](docs/development-guide.md)**:如何拆分插件、画依赖图、设计重载与多实例。
- **[开发手册](docs/development-handbook.md)**:API 用法、资源清理、事件、排障与验证。
Expand All @@ -257,7 +257,7 @@ Go 插件用模块 [`github.com/arcships/rutis/go/rutis`](go/rutis):SDK,以
rutis 仍处于 0.x,API 还会演进。不兼容的变化会写进发布说明,并附迁移指南。

- **内核** 是纯 Rust,依赖只有 tokio、tokio-util、thiserror、serde 和 serde_json;需要 Rust 1.88 或更高。
- **语言运行时与 rutis-host**(Node、Bun、Python 与 Go 行、远程运行时、跨语言共享服务、节点、Cordis 挂载)支持 Linux、macOS 和 Windows x64(MSVC);需要 Node 22+、Bun 1.4+ 或 Python 3.10+;Go 插件是编译好的二进制,构建时需要 Go 1.24+。Bun 运行时还没有在 Windows 上测试。
- **语言运行时与 rutis-host**(Node、Bun、Python 与 Go 行、远程运行时、跨语言共享服务、节点、Cordis 挂载)支持 Linux、macOS 和 Windows x64(MSVC);需要 Node 22+、Bun 1.4+ 或 Python 3.10+;Go 插件是编译好的二进制,构建时需要 Go 1.24+;Swift 插件是 macOS 14+ 的编译好的二进制,构建时需要 Swift 6 工具链。Bun 运行时还没有在 Windows 上测试。
- **dylib 插件** 支持 Linux、macOS 和 Windows x64(MSVC)。

## 参与
Expand Down
6 changes: 3 additions & 3 deletions crates/rutis-host/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rutis-host"
description = "A rutis host that needs no Rust: run plugins written in TypeScript, JavaScript, Python and Go (in Node, Bun, Python or Go binaries) from a rutis.json, develop them with live reload, link machines"
description = "A rutis host that needs no Rust: run plugins written in TypeScript, JavaScript, Python, Go and Swift (in Node, Bun, Python, Go or Swift binaries) from a rutis.json, develop them with live reload, link machines"
# The release train.
version = "0.13.0"
edition.workspace = true
Expand All @@ -20,8 +20,8 @@ path = "src/main.rs"

[dependencies]
rutis = { path = "../rutis", version = "0.13.0" }
rutis-bridge = { path = "../rutis-bridge", version = "0.13.0", features = ["node", "python", "bun", "go", "websocket"] }
rutis-loader = { path = "../rutis-loader", version = "0.13.0", features = ["node", "python", "bun", "go", "peer"] }
rutis-bridge = { path = "../rutis-bridge", version = "0.13.0", features = ["node", "python", "bun", "go", "swift", "websocket"] }
rutis-loader = { path = "../rutis-loader", version = "0.13.0", features = ["node", "python", "bun", "go", "swift", "peer"] }
# To say which file is not PEM (the transport only sees the contents).
rustls-pki-types = { version = "1", features = ["std"] }
serde = { version = "1", features = ["derive"] }
Expand Down
63 changes: 42 additions & 21 deletions crates/rutis-host/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
//! "runtimes": {
//! "node": { "project": "." },
//! "py": { "project": ".", "python": ".venv/bin/python", "restart": "on-failure" },
//! "go": { "dir": "plugins/go", "start": "on-demand", "idle": 60 }
//! "go": { "dir": "plugins/go", "start": "on-demand", "idle": 60 },
//! "swift": { "dir": "plugins/swift", "restart": "on-failure" }
//! },
//! "listen": [{ "name": "public", "address": "0.0.0.0:7443", "cert": "server.pem", "key": "server.key" }],
//! "rows": [
Expand Down Expand Up @@ -113,6 +114,9 @@ pub struct Runtimes {
pub bun: Option<BunRuntime>,
/// Go binaries, one runtime each, started when a row uses them.
pub go: Option<GoRuntime>,
/// Swift binaries (macOS), one runtime each, started when a row uses
/// them: the same settings as Go's.
pub swift: Option<SwiftRuntime>,
/// Runtimes on other machines: reached through a `rutis-bridge/peer`
/// row with `"runtime": "<name>"`, or dialing in to a listener, each
/// connection an instance of a group (`listen` and `instances`).
Expand Down Expand Up @@ -210,20 +214,24 @@ pub struct PythonRuntime {
pub restart: Restart,
}

/// `runtimes.go` and `runtimes.swift`: the binaries of a compiled language,
/// one runtime each.
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GoRuntime {
/// Every Go binary in this directory (trusted as a whole: its files
/// that contain the Go SDK's marker are run to read their manifests).
pub struct BinaryRuntime {
/// Every binary of the language in this directory (trusted as a whole:
/// its files that contain the SDK's marker are run to read their
/// manifests).
#[serde(default)]
pub dir: Option<PathBuf>,
/// Go binaries, each a runtime named after its file (`go-<name>`).
/// Binaries, each a runtime named after its file (`go-<name>`,
/// `swift-<name>`).
#[serde(default)]
pub binaries: Vec<PathBuf>,
/// `on-demand` (the default): a runtime starts when a row uses it;
/// `eager`: every one starts with the host and keeps running.
#[serde(default)]
pub start: GoStart,
pub start: BinaryStart,
/// Seconds an on-demand runtime stays idle before it stops (60).
#[serde(default)]
pub idle: Option<u64>,
Expand All @@ -239,8 +247,14 @@ pub struct GoRuntime {
pub dev: Option<(PathBuf, String)>,
}

/// `runtimes.go`: Go binaries.
pub type GoRuntime = BinaryRuntime;

/// `runtimes.swift`: Swift binaries.
pub type SwiftRuntime = BinaryRuntime;

/// What the host does when a local runtime's process ends on its own
/// (`runtimes.<node|py|bun|go>.restart`; `docs/guide/rutis-host.md`, "When
/// (`runtimes.<node|py|bun|go|swift>.restart`; `docs/guide/rutis-host.md`, "When
/// a runtime ends").
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -276,24 +290,28 @@ impl Restart {
}
}

/// When a compiled language's runtimes start (`runtimes.<go|swift>.start`).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum GoStart {
pub enum BinaryStart {
#[default]
OnDemand,
Eager,
}

impl GoRuntime {
/// Refuse settings that cannot work together.
pub fn validate(&self) -> Result<(), String> {
/// `runtimes.go.start`.
pub type GoStart = BinaryStart;

impl BinaryRuntime {
/// Refuse settings that cannot work together, as `runtimes.<key>`.
pub fn validate(&self, key: &str) -> Result<(), String> {
if self.dir.is_none() && self.binaries.is_empty() && self.dev.is_none() {
return Err("runtimes.go needs a dir or binaries".into());
return Err(format!("runtimes.{key} needs a dir or binaries"));
}
if self.start == GoStart::Eager && self.idle.is_some() {
return Err(
"runtimes.go: eager runtimes keep running, so idle does not apply to them".into(),
);
if self.start == BinaryStart::Eager && self.idle.is_some() {
return Err(format!(
"runtimes.{key}: eager runtimes keep running, so idle does not apply to them"
));
}
Ok(())
}
Expand Down Expand Up @@ -649,16 +667,19 @@ mod tests {
fn go_runtimes_are_checked() {
let parse =
|value: serde_json::Value| -> GoRuntime { serde_json::from_value(value).unwrap() };
assert!(parse(json!({ "dir": "plugins/go" })).validate().is_ok());
assert!(parse(json!({ "dir": "plugins/go" })).validate("go").is_ok());
assert!(
parse(json!({ "binaries": ["bin/netkit"], "start": "eager" }))
.validate()
.validate("go")
.is_ok()
);
let none = parse(json!({})).validate().unwrap_err();
assert!(none.contains("needs a dir or binaries"), "{none}");
let none = parse(json!({})).validate("swift").unwrap_err();
assert!(
none.contains("runtimes.swift needs a dir or binaries"),
"{none}"
);
let both = parse(json!({ "dir": "d", "start": "eager", "idle": 30 }))
.validate()
.validate("go")
.unwrap_err();
assert!(both.contains("idle does not apply"), "{both}");
let mut config: HostConfig = serde_json::from_value(json!({
Expand Down
Loading
Loading