Embed Yoke in your product
The intended way to use Yoke — embed the runtime in your own server. Your users never see Yoke.
The intended production path is:
- Write the internal actor package in TypeScript (
packages/runtimeor similar). yoke compile --crateemits a Rust library with that package baked in (include_dir).- Your product workspace has more than one crate (CLI, server, …). Each depends on that library. Users never see Yoke.
yoke dev ./packages/runtime is only for iterating on actors. Do not ship it.
CLI is for authors, not users
yoke dev / yoke start exist so you can iterate on the internal actor package. Production is yoke compile --crate plus your own crates depending on the generated library.
Intended shape
End users
│ your brand, your UX
▼
Your product (CLI crate, server crate, web app)
│
▼
Your crates ← auth, workspaces, billing, public HTTP
│ depend on
▼
generated crate (yoke compile --crate)
├── baked-in package (yoke.ts) ← actors, agents, tools — private
├── yoke-runtime ← kernel
├── Postgres
└── Deno workerA coding harness is the usual example. You would not expose filesystem utilities as a Yoke feature to end users. Your server already owns the workspace. Agents inside the compiled package get tools that operate on that workspace. Users open a session in your product; your crates call start_colocated / append on the generated library.
Product layout (Cargo + JS workspaces)
A product that embeds Yoke is its own workspace: a Cargo workspace for Rust and a JS workspace (Turborepo or similar) for TypeScript.
your-product/
Cargo.toml # [workspace] members = ["crates/*"]
package.json # npm/pnpm workspaces + turbo
turbo.json
crates/
yoke/ # GENERATED: yoke compile --crate -o crates/yoke
server/ # depends on the generated library
cli/ # depends on the generated library (and/or server)
packages/
runtime/ # internal actor package (Deno, yoke.ts)
deno.json
yoke.ts
web/ # optional UI — turbo pipeline, not YokeCompile the actor package into a crate other members can depend on:
yoke compile ./packages/runtime -o ./crates/yoke --crateThe generated Cargo.toml is a library ([lib]) only — no binary. TypeScript is baked in with include_dir; dependents never take a path to yoke.ts.
# crates/server/Cargo.toml
[dependencies]
# crate name comes from packages/runtime/deno.json "name"
my_product_yoke = { path = "../yoke" }use my_product_yoke::{start_colocated, EventInput};
let rt = start_colocated(database, data_dir, "default", 8090).await?;
// handlers on YOUR server call into the library — they do not proxy Yoke portsCLI and server can both depend on crates/yoke. That is why --crate emits a library rather than a deploy binary. For a standalone executor CLI (run / control-plane / worker), use yoke compile without --crate.
Two surfaces
| Surface | Crate / package | Who calls it | Role |
|---|---|---|---|
| Generated crate | yoke compile --crate output | Your CLI, server, and other crates | start_colocated, start_worker, materialize_package, append via YokeRuntime |
| Author SDK | @yoke/sdk | packages/runtime only | defineRuntime, actors, portals, tools |
@yoke/sdk is not your public SDK. It is the language the internal package uses. Your public SDK is whatever you design for your product.
The kernel crates (yoke-runtime, yoke-core) are dependencies of the generated crate, not something every product crate has to take a direct dep on. Product crates depend on the compiled package crate.
Generated library API
The crate written to crates/yoke exposes:
| Item | Use from CLI / server crates |
|---|---|
start_colocated(database, data_dir, tenant, cp_port) | Boot CP + worker with the baked-in package |
start_worker(tenant, database, data_dir, port) | Worker only |
materialize_package() | Path to the extracted package tree (if you need YokeRuntime::start yourself) |
YokeRuntime / Worker / EventInput | Re-exported host types |
After start_colocated / start_worker, drive work from your handlers (append, conversation_ops, create_actor on Worker::runtime()). Keep portal HTTP and :8090 on loopback.
--crate does not emit a binary. yoke compile (no --crate) writes a separate CLI crate with run / control-plane / worker for a standalone executor.
Authoring the TypeScript package
packages/runtime/yoke.ts follows Your first package. Coding tools (defineTool for read, write, shell) live here because the agent needs them — not as user-facing APIs.
Iterate without compiling the crate:
yoke dev ./packages/runtime --database "$DATABASE_URL"A turbo task on that package can wrap yoke compile --crate -o ../../crates/yoke.
Product crates should depend on the compiled library from yoke compile --crate, not on a loose yoke.ts folder at runtime.
What this is not
| Don't | Do instead |
|---|---|
Ship yoke start ./package as the product | yoke compile --crate and depend on the library from your crates |
One product main.rs that is the only crate | Cargo workspace: generated yoke crate + CLI + server (at least) |
Give end users @yoke/sdk or Yoke HTTP | Give them your CLI / web / API |
| Put product UX on portal routes and call that the app | Keep portal HTTP private; your crates own public HTTP |
| Add product features (billing, workspaces, FS sandboxes) to the Yoke kernel | Keep them in your crates; promote a primitive to Yoke only when many products need it |