Skip to main content
Rust libraries | Entity RuntimeEmbed the deterministic kernel, choose storage providers, and keep IO and trusted context in your shell.Entity Runtimehow-toentity-runtimehow-toadopterdeveloperreference

Rust libraries

Entity Runtime is a workspace of narrow crates. Use only the boundary your application needs.

CratePurpose
entity-coreIO-free definitions, registry, runtime, decisions, events, and replay
entity-yamlYAML text to definition data; register it before execution
entity-storeprovider traits, memory/file stores, envelopes, projections, conformance suite
entity-queryoptional document containment queries and continuation cursors
entity-sqlitetransactional embedded provider
entity-postgrestransactional centralized provider
entity-remoteversioned store protocol, transport trait, and hybrid policy
entity-graphdeterministic lifecycle and reference renderings
entity-surfaceIO-free JSON Schema, OpenAPI, AsyncAPI, and entity documentation projection
entity-shellprovider-backed create/get/list/events/execute shared by shells
entity-mcpsynchronous schema-derived MCP tools over caller-provided stdio

The crates are currently consumed from the tagged repository:

[dependencies]
entity-core = { git = "https://github.com/beyond10x/entity-runtime", tag = "0.18.1" }
entity-yaml = { git = "https://github.com/beyond10x/entity-runtime", tag = "0.18.1" }

Keep all runtime crate dependencies on the same release tag. The system map explains which contracts are authored and which surfaces are generated.

Decide in memory

use entity_core::{CoreError, Registry, Runtime};
use serde_json::json;

let definition = entity_yaml::from_str(&yaml)?;
let mut registry = Registry::new();
registry.register(definition)?;
registry.validate_all()?;

let runtime = Runtime::new(&registry);
let drafted = runtime.create(
"refund",
1,
"refund-104",
json!({
"order_id": "order-88",
"amount_cents": 12_500,
"evidence_count": 2
}),
)?;
let submitted = runtime.execute(&drafted.instance, "submit", json!({}))?;

match runtime.execute(
&submitted.instance,
"approve",
json!({
"actor_role": "agent",
"reason": "customer supplied delivery evidence"
}),
) {
Err(CoreError::PreconditionFailed { rule, .. }) => {
assert_eq!(rule.as_deref(), Some("large_refunds_need_a_human"));
}
other => panic!("expected a policy refusal, got {other:?}"),
}

The caller-owned submitted.instance remains unchanged after the refusal.

Build a trusted shell

Your shell owns all ambient and privileged facts:

The trusted shell loads canonical data, authenticates the actor, reads trusted time, and assigns provenance. Entity Runtime returns a decision or typed refusal. Refusals write nothing. Decisions are recorded, committed with a revision expectation, and only then lead to external side effects.

EntityInstance is serializable data with public fields because providers round-trip it. That is not permission to trust any deserialized instance. Load canonical instances from a trusted provider and let the kernel check definition identity and declared state.

Store atomically

Use RecordedCommit::new to bind a decision to Recording, then call Store::commit_recorded. The provider checks Expect::Absent or Expect::Revision(n) before writing state, history, and events.

For an ordered multi-subject command, use AtomicBatchStore::commit_batch on memory, SQLite, or PostgreSQL providers. All expectations see earlier entries in the same batch; any conflict or provider failure rolls the batch back.

Replay

entity_core::replay reruns complete decision records and compares normalized input, definition, result, changes, and events. rehydrate folds legacy event-only histories against the current definition, refusing any revision no operation could have produced, and is a migration tool rather than equivalent proof: it has no definition snapshot and cannot see a decision that emitted nothing.

Match CoreError and StoreError variants in code. Display strings are for people and may be reworded.

Optional document queries

DocumentQueryProvider is implemented by Memory Store and PostgreSQL. DocumentQuery selects one entity type, applies recursive JSON containment to its fields, and returns an identity-ordered page. The default limit is 100 and the maximum is 1,000. Reuse the returned cursor with the same entity and filter; a cursor for another query is refused. Pagination alone does not hold a snapshot across calls.

PostgreSQL also offers command sessions for transactional reads, writes, queries, events, and identity-range reservation. This is a provider-specific capability; the CLI list verb simply enumerates stored IDs and exposes no query or transaction-session command.