Yoke.sh
Getting Started

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:

  1. Write the internal actor package in TypeScript (packages/runtime or similar).
  2. yoke compile --crate emits a Rust library with that package baked in (include_dir).
  3. 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 worker

A 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 Yoke

Compile the actor package into a crate other members can depend on:

yoke compile ./packages/runtime -o ./crates/yoke --crate

The 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 ports

CLI 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

SurfaceCrate / packageWho calls itRole
Generated crateyoke compile --crate outputYour CLI, server, and other cratesstart_colocated, start_worker, materialize_package, append via YokeRuntime
Author SDK@yoke/sdkpackages/runtime onlydefineRuntime, 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:

ItemUse 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 / EventInputRe-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'tDo instead
Ship yoke start ./package as the productyoke compile --crate and depend on the library from your crates
One product main.rs that is the only crateCargo workspace: generated yoke crate + CLI + server (at least)
Give end users @yoke/sdk or Yoke HTTPGive them your CLI / web / API
Put product UX on portal routes and call that the appKeep portal HTTP private; your crates own public HTTP
Add product features (billing, workspaces, FS sandboxes) to the Yoke kernelKeep them in your crates; promote a primitive to Yoke only when many products need it

Next steps

On this page