证据快照复核于 2026-09-16GitHub 数据核对日期: 2026-08-21
来源已审查资源自动化与智能体rust-developers Profileplugin-framework-authors Profile

Cordis

面向长期运行、插件化 Rust 应用的类型化运行时。

快速了解

它能做什么

面向长期运行、插件化 Rust 应用的类型化运行时。

本站提供的是中文说明,不代表该项目或 Plugin 自身提供中文界面;语言支持请以上游文档为准。

能力
自动化与智能体自动化配置工作流自动化

选择前先看

Cordis 为会随时间变化的 Rust 插件、服务与资源提供统一运行时模型,支持类型化事件、显式插件生命周期、按代归属的确定性清理、作用域事件路由和精确的服务 realm 放置。

适合谁

正在构建守护进程式、多插件、多租户或动态配置系统的 Rust 应用与框架开发者。

常见任务

  • 构建可准备、启动、更新、重启、替换并显式释放的插件。
  • 在全局范围或指定作用域内路由类型化事件。
  • 隔离不同租户的服务,同时选择性共享指标等服务。
  • 在需要时使用可选的 timer 与 loader crate。

权限与数据

提供的源码将其描述为本地 Rust 运行时库;未声明需要外部账户、凭据或遥测。

数据处理
  • 提供的证据未说明运行时数据处理方式。

局限

  • 需要 Rust 1.88 或更高版本,以及 Rust 2024 Edition。
  • 从 cordis-rs 0.6.x 升级到 v3 属于有意的破坏性变更。
  • 丢弃 Fork 不会释放它;需要显式释放才能完成生命周期清理。
  • 尚未到 1.0 的 API 仍可能演进。

DSHub 已核对

  • 固定版本 README 说明了安装方式、公开概念、示例、Rust 版本要求和 MIT 许可证。

DSHub 未核对

  • 未根据所提供证据执行安装、示例或 CI;也未验证运行时行为及与特定 DeepSeek Harness 版本的兼容性。

固定版本安装

主要操作

这个资源没有 DSH Plugin 安装操作,请根据源码文档使用真实交付方式。

访问源码项目

维护者原文

项目 README

查看 commit 406e2ac 对应的 README
维护者编写的上游内容原文于 2026/9/14README.md 获取,正文和仓库相对媒体固定到 commit 406e2acf7284,内容哈希为 436077d7cd9c。以下是未经 DSHub 翻译的上游原文,语言可能与当前页面不同;第三方托管的 badge 可能独立更新。

Cordis

English | 简体中文

CI

Cordis is a typed runtime for long-lived, plugin-oriented Rust applications. It gives application components one model for lifecycle, service dependencies, typed events, resource cleanup, and explicit isolation boundaries.

Cordis is useful when your program is more than a collection of short-lived function calls: plugins can appear and disappear, services can become available or unavailable, configuration can change, and runtime resources must still be cleaned up deterministically.

Install

For applications, keep the historical package and import identity:

[dependencies]
cordis-rs = "0.7"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use cordis::Context;

cordis-rs is now a thin application-facing facade over the v3 runtime contract. Framework and plugin authors may depend on that contract directly:

[dependencies]
cordis-core = "0.1"

Optional capabilities stay explicit semantic dependencies:

cordis-timer = "0.1"
cordis-loader = "0.1"

Cordis v3 requires Rust 1.88 or newer and uses Rust 2024 Edition.

Migrating from 0.6.x

cordis-rs 0.7 is the first release line backed by the v3 runtime architecture. The 0.6.x implementation remains on the legacy/0.6 maintenance branch for critical bug and security fixes. The v3 transition is intentionally breaking; see MIGRATION.md and docs/v3-migration.md.

The mental model

Five types carry most of the public model:

  • Context — a cheap immutable view into one Cordis Runtime.
  • Plugin — reusable behavior with typed source configuration and runtime input.
  • Fork — the lifecycle handle for one admitted non-root Fiber.
  • Service — a typed, named capability published into an exact service realm.
  • Event — a typed runtime communication contract with explicit routing.

A Plugin enters the Runtime through a deliberate boundary:

Config
  │
  │ Plugin::prepare()
  ▼
Input
  │
  │ PreparedPlugin::from_input(...)
  ▼
PreparedPlugin
  │
  │ Context::spawn(...)
  ▼
Fork / Fiber

prepare() runs before lifecycle admission. spawn() is the first operation allowed to create Runtime lifecycle state.

Quick start

The smallest complete flow is: define an Event, define a Plugin, prepare and spawn it, dispatch the Event, then explicitly dispose the returned Fork.

use std::convert::Infallible;

use cordis::event::{ListenerRegistrationError, observer_sync};
use cordis::{BoxError, Context, Event, Plugin, PreparedPlugin, Routing};

struct Ping;

impl Event for Ping {
    const NAME: &'static str = "ping";
    type Args = String;
    type Output = ();
}

struct Echo;
struct EchoInput;

impl Plugin for Echo {
    type Config = ();
    type Input = EchoInput;
    type PrepareError = Infallible;
    type ApplyError = ListenerRegistrationError;

    fn prepare(&self, (): ()) -> Result<Self::Input, Self::PrepareError> {
        Ok(EchoInput)
    }

    async fn apply(
        &self,
        ctx: Context,
        _input: &Self::Input,
    ) -> Result<(), Self::ApplyError> {
        let _listener = ctx.on::<Ping, _>(observer_sync(|_, name| {
            println!("hello, {name}");
            Ok::<_, Infallible>(())
        }))?;
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), BoxError> {
    let ctx = Context::new();

    let plugin = Echo;
    let input = plugin.prepare(())?;
    let prepared = PreparedPlugin::from_input(plugin, input);
    let fork = ctx.spawn(prepared).await?;

    ctx.emit::<Ping>(Routing::Unscoped, "world".into()).await?;

    fork.dispose().await?;
    Ok(())
}

Run the repository's complete version with:

cargo run -p hello_plugin

Lifecycle and convergence

A successful Context::spawn() returns a Fork only after the new Fiber has settled for the current service snapshot. The stable result is normally:

  • Active — all required Services are available and apply() succeeded.
  • Pending — a required Service is currently unavailable; apply() has not run.

Requirements are declared with InjectSpec. They are lifecycle prerequisites, not constructor injection. When an exact required Service publication appears or disappears, Cordis converges affected Fibers toward their new stable state.

A Fork exposes the main lifecycle operations:

  • ready() waits for the current stable state.
  • restart() reapplies the current committed input on the same Fiber.
  • update(PreparedChange) attempts a precommit-controlled typed input replacement; a committed update keeps the same Fiber.
  • era_swap(PreparedChange) performs identity-breaking replacement; a successful successor has a fresh Fiber identity.
  • dispose() ends the Fiber and runs its cleanup.

Dropping a Fork does not dispose the Fiber. Lifecycle ownership is explicit.

Resources registered through a Plugin's apply Context are owned by that apply generation. Listener registrations, Service publications, tasks, effects, and timer operations can therefore be cleaned up with the generation instead of being manually threaded through application code.

Services: exact placement, not fallback lookup

A Service is identified by its semantic Service name and resolved from one exact slot:

(Service, ServiceRealm)

By default a Context uses the Runtime's default realm. Isolation changes the realm selected for specific Service names.

To give one Service a fresh private slot:

let tenant_a = root.with_isolated_service(Database::NAME);

To isolate several Services, chain the operation:

let tenant_a = root
    .with_isolated_service(Database::NAME)
    .with_isolated_service(Cache::NAME)
    .with_isolated_service(Ledger::NAME);

Each call changes only that Service's placement. Other Service mappings are inherited.

For explicit sharing and joining, allocate opaque realms and map Service names to them:

let shared_metrics = root.new_service_realm();
let tenant_a_db = root.new_service_realm();
let tenant_b_db = root.new_service_realm();

let tenant_a = root.with_service_realms([
    (Database::NAME, tenant_a_db),
    (Metrics::NAME, shared_metrics.clone()),
])?;

let tenant_b = root.with_service_realms([
    (Database::NAME, tenant_b_db),
    (Metrics::NAME, shared_metrics),
])?;

Now the tenants resolve different Databases but the same Metrics slot.

A ServiceRealm is only an opaque Runtime-local placement identity. It has no hierarchy, parent lookup, textual rendezvous, or fallback rule. If a Context maps Database to a private realm and that realm has no visible Database publication, lookup is unavailable; Cordis does not fall back to the default realm.

Events: typed communication with explicit Scope routing

An Event declares a Runtime-local semantic name together with typed Args and Output. Listener adapters make the listener role explicit:

  • Observer — notification side effect.
  • Responder — may answer a query.
  • Mapper — transforms a waterfall payload.
  • Around — onion-style middleware with a consuming Next.

Dispatch always chooses routing explicitly:

ctx.emit::<Ping>(Routing::Unscoped, payload).await?;

ctx.emit::<Ping>(Routing::Scoped(request_scope), payload).await?;

Routing::Scoped(scope) reaches scoped registrations on the target Scope itself and its ancestors, plus global registrations. Siblings and descendants are not reached. Routing::Unscoped does not apply Scope eligibility filtering.

This makes Scope useful for questions such as:

Which behavior should be able to hear this Event?

Typical Scope boundaries are a tenant, request, workflow, session, or plugin-local event pipeline.

Scope and Service isolation are independent

A Context carries independent axes:

Context
  ├─ current Fiber
  ├─ isolate   → which exact Service realm each Service resolves from
  ├─ Scope     → which listeners are eligible for scoped Event dispatch
  └─ intercept → ordered ConfigurableService configuration layers

Use Scope for Event reachability. Use Service isolation for Service placement.

Question Use
Which listeners may receive this Event? Scope
Keep tenant A events out of tenant B's event subtree? Scope
Which Database should this Plugin resolve? Service isolation
Give two tenants different Caches? Service isolation
Share Metrics while isolating Database? explicit ServiceRealm mappings

The axes do not imply one another. Two Contexts may share the same Service realm while living in different Scopes, or share the same Scope while resolving a Service from different realms.

When a Plugin is spawned, its Service dependency edges are resolved against the spawning Context's isolate mapping, while the new Fiber receives its own child Scope. This lets sibling Plugins share exact Services without accidentally sharing one Event seat.

Crates

Crate Role
cordis-rs application facade preserving the historical cordis import
cordis-core canonical Context, Plugin/Fork lifecycle, Services, Events, effects, logging, runtime observation
cordis-timer generation-owned sleep, interval, and timeout operations
cordis-loader immutable declarative load plans and synchronous typed target resolution

cordis-core deliberately does not depend on serde/serde_json or Tokio's time driver. Declarative loading and time operations stay in optional leaf crates.

Examples

Every example is standalone, headless under CI, and exits on its own.

cargo run -p hello_plugin
cargo run -p gateway
cargo run -p worker_daemon
cargo run -p scopes_tenants
cargo run -p logging_exporters
cargo run -p chat_capstone

What they demonstrate:

Example Focus
hello_plugin smallest correct Plugin/Event lifecycle
gateway declarative JSON boot, scoped routing, typed updates, timeout
worker_daemon failure/recovery, Context::run, restart/update, sleep/interval
scopes_tenants Service realms and Event Scope as independent axes
logging_exporters logging and runtime observation
chat_capstone the full composition, including Pending convergence, update, and era replacement

Start with hello_plugin; use the other examples as focused tours of the public surface.

Design boundaries worth knowing

Cordis intentionally does not model a general Context hierarchy or a nested DI container. Context derivation changes explicit axes only.

That means:

  • Scope ancestry is Event routing, not lifecycle ownership.
  • Service realms are placement identities, not namespaces.
  • InjectSpec declares lifecycle requirements, not lookup fallback.
  • spawn origin records provenance, not parent/child ownership.
  • update preserves Fiber identity; era replacement deliberately does not.

These boundaries keep event routing, service placement, and lifecycle semantics independent instead of letting one hidden tree control all three.

Project status

The v3 semantic crates begin at 0.1.0; the historical application package moves to cordis-rs 0.7.0. The workspace uses Rust 2024 Edition with MSRV 1.88. As a pre-1.0 project, the public API may still evolve.

Cordis is a Rust port and redesign in the lineage of cordiverse/cordis.

License

MIT. See LICENSE.

有意识地管理

安装与管理

前置条件与目标 Profile

目标 rust-developers Profile, plugin-framework-authors Profile

交付方式 仅文档 — https://raw.githubusercontent.com/dshbox/cordis-rs/406e2acf728453ec2f4fe7cda9f040eca52c32f5/README.md

兼容性与访问范围

Rust 1.88+; Rust 2024 Edition Not declared in supplied evidence

检查兼容性证据

风险事实

license

MIT licensed; provided without warranty.

证据
api-stability

Pre-1.0 semantic crates may still have evolving public APIs.

证据
证据与编辑审查Manifest、Bundle patch、分发与新鲜度

不可变证据

审查状态与源码活动

AI 已审查

这是 Rust 库的源码级参考资料,不是可直接安装的 DSHub 插件包。

AI 审查于 2026/9/14 UTC 13:53GitHub 事实核对日期: 2026/9/14 UTC 13:53

自当前证据基线以来,没有记录到重要源码变化。

下一步

比较生态 Artifact 类型

订阅重要变化: Cordis