Status: Accepted
Date: 2026-05-04
Author: Arthur Silva
Related: ADR-091 (Tractor runtime saga)
Refarm currently has two runtime implementations:
- tractor (Rust binary) — original WASM host; loads agent, drives CRDT storage, exposes WebSocket sync on
:42000. Requirescargo build --release. No HTTP sidecar. - tractor-ts (TypeScript) — reimplementation of the same interfaces, used inside farmhand (Node.js daemon). Exposes the HTTP sidecar on
:42001thatrefarm askrelies on.
They implement the same runtime contract independently. Without cross-conformance tests, they diverge silently. The farmhand flow (refarm ask) routes through tractor-ts, leaving the Rust binary out of the production cycle and at risk of becoming a neglected artifact.
The goal is for refarm to become a daily driver — always-on, fast, low footprint, with real WASM isolation and extensibility to any language that compiles to WASM. tractor-ts cannot be the authoritative runtime for this goal.
tractor (Rust) is the authoritative runtime. All production WASM execution flows through the Rust binary. tractor-ts is retained as a conformance harness and development convenience, not as a production runtime.
The farmhand daemon evolves into an orchestration shell that delegates WASM execution to the tractor binary via its HTTP sidecar, while retaining responsibility for:
- Plugin discovery (
~/.refarm/plugins/) - Task memory (SQLite)
- HTTP sidecar proxying
- Process lifecycle management
@refarm.dev/tractor may continue to be published from packages/tractor-ts
for npm compatibility, browser-native flows, and existing TypeScript consumers,
but that package is a compatibility boundary, not the source of runtime truth.
New runtime behavior lands in Rust first unless it is explicitly browser-only or
npm-compatibility-only.
Runtime-facing TypeScript consumers should depend on narrow host contracts from
@refarm.dev/runtime, not on the full Tractor class. These contracts describe
the minimum surface an orchestrator needs (plugins.get, plugins.load,
registry, storeNode, onNode) and let farmhand, tests, and future Rust
adapters converge without treating tractor-ts as the domain interface.
The TypeScript package should shrink toward a browser/compatibility boundary.
Its transitional scaffold is now represented by hybrid-bindings-package: it
publishes from dist/src/*, emits src/**/* plus test/test-utils.ts, and
keeps ./test/test-utils as an explicit test utility export.
Responsibilities that remain natural in TypeScript:
- Browser entrypoint and browser plugin loading policy.
- OPFS plugin cache and browser runtime module cache.
- Browser runtime descriptor install/verification flows.
- Compatibility exports for existing npm consumers.
- Thin test utilities used by TypeScript packages.
- Pure projection helpers consumed directly by UI/client code, if they remain dependency-light and runtime-neutral.
Responsibilities that should not grow further in tractor-ts:
- Native plugin host orchestration.
- WASI import surface and bridge behavior.
- Trust policy enforcement for native execution.
- Runtime telemetry bus as source of truth.
- Storage/sync lifecycle and daemon lifecycle.
- Core command/identity boot behavior when used as runtime driver.
Rust already owns the target implementations for these areas in
packages/tractor:
host/plugin_host/*host/wasi_bridge/*trust/mod.rsstorage/sqlite.rssync/loro.rstelemetry/mod.rsdaemon/ws_server.rs
As of 2026-05-19, @refarm.dev/tractor consumers fall into these groups:
| Consumer | Current use | Migration pressure |
|---|---|---|
apps/farmhand |
Still boots Tractor during transition, but task execution, plugin loading, reload, and sidecar handlers consume @refarm.dev/runtime host contracts. |
High: next step is replacing the bootstrap with Rust tractor delegation. |
packages/homestead |
Browser runtime boot still uses tractor-ts; plugin handles, Studio host UI contracts, shell i18n/log gating, and stream observation projections are Homestead/runtime-owned. | Medium: keep browser/compat boundary; avoid native runtime ownership. |
apps/me |
Consumes Homestead runtime and @refarm.dev/runtime plugin-handle types; no direct tractor-ts dependency remains. |
Low: browser runtime still arrives through Homestead until that boundary is redesigned. |
apps/dev |
Consumes Homestead runtime and @refarm.dev/runtime plugin-handle/node-store types for diagnostics and stream demos; no direct tractor-ts dependency remains. |
Low/Medium: browser runtime still arrives through Homestead, but app fixtures no longer type against tractor-ts. |
packages/sower |
Plugin class depends on @refarm.dev/runtime host capabilities (emitTelemetry, switchTier) and runtime node types. |
Low/Medium: no direct tractor-ts dependency remains; future work is validating the host contract against Rust tractor. |
packages/scarecrow |
Plugin class and tests depend on @refarm.dev/runtime host capabilities (observe, queryNodes, setPluginState, emitTelemetry). |
Low/Medium: no direct tractor-ts dependency remains; future work is validating the host contract against Rust tractor. |
packages/plugin-courier |
Production code and integration tests depend on @refarm.dev/runtime query capability and a narrow host fixture; no direct tractor-ts dependency remains. |
Low/Medium: future work is validating the host contract against Rust tractor. |
packages/vtconfig and packages/toolbox |
Alias/resolution support for @refarm.dev/tractor and test utils. |
Low: tooling support remains while npm compatibility exists. |
templates/workspace/typescript |
Default public workspace template boots through Homestead runtime and does not declare @refarm.dev/tractor directly. |
Low/Medium: browser runtime still arrives through Homestead until that boundary is redesigned. |
Unused direct dependencies on @refarm.dev/tractor were removed from
packages/barn and packages/plugin-tem; neither imported the package.
apps/refarm status no longer boots tractor-ts for a synthetic status payload;
it now builds the local CLI status snapshot directly.
The TypeScript package remains necessary as an npm and browser compatibility boundary, but it should not continue acting as a second native runtime. The current source tree mixes four categories:
| Category | Current modules | Long-term owner |
|---|---|---|
| Browser-only host surface | index.browser.ts, install-plugin.ts, opfs-plugin-cache.ts, browser runtime descriptor verification/revocation helpers |
Keep in TypeScript while browser runtime support exists. |
| UI/client projections | agent-response-stream.ts, stream-chunk.ts, stream-session.ts, stream-view.ts, selected telemetry diagnostics helpers |
Prefer moving to protocol/contract packages when reused outside Tractor; keep dependency-light reducers available to browser clients. |
| Compatibility host facade | Tractor.boot, CommandHost, graph normalizer, test utilities, compatibility exports |
Keep temporarily for npm compatibility and TS integration tests; shrink toward narrow contracts from @refarm.dev/runtime. |
| Native runtime behavior | plugin-host.ts, plugin-runner.ts, main-thread-runner.ts, worker-runner.ts, wasi-imports.ts, native trust enforcement, native telemetry authority, storage/sync lifecycle |
Rust owns this. Do not grow new behavior here except compatibility shims covered by conformance tests. |
The Rust crate already contains the intended homes for native execution:
- plugin loading and Component Model execution:
packages/tractor/src/host/plugin_host/* - host imports/WASI bridge:
packages/tractor/src/host/wasi_bridge/* - trust policy:
packages/tractor/src/trust/mod.rs - storage and CRDT sync:
packages/tractor/src/storage/*,packages/tractor/src/sync/* - daemon/WebSocket routing:
packages/tractor/src/daemon/* - sidecar protocol:
packages/tractor/src/sidecar/* - streaming observations:
packages/tractor/src/streaming/* - telemetry authority:
packages/tractor/src/telemetry/*
Practical reduction rule:
- If code needs Node-native WASM execution, filesystem/runtime process management, trust enforcement, or host-import policy, it belongs in Rust.
- If code needs browser APIs such as OPFS, dynamic ESM loading, or browser runtime descriptor installation, it can remain in TypeScript.
- If code is a pure reducer/projection over persisted protocol nodes, prefer a
small contract/protocol package over
tractor-ts. - If a TS package only needs
plugins.get/load,registry,storeNode,onNode, or telemetry hooks, it should depend on@refarm.dev/runtimecontracts rather than importing the fullTractorclass.
Near-term shrink targets:
- Replace
apps/farmhand's directTractor.bootpath with Rust sidecar delegation once the Rust HTTP protocol covers the same driver flow. - Extract stream/task/session projection helpers that are not browser-runtime
specific into explicit contract packages, so UI clients do not import
@refarm.dev/tractorfor plain data reduction. - Keep
@refarm.dev/tractornpm name as a compatibility facade until consumers no longer need the legacy class shape.
-
tractor gains an HTTP sidecar (
--http-port, default:42001) — the same/effortsprotocol farmhand exposes today (see ADR-060). This is the unlock that makesrefarm askwork with tractor directly. -
farmhand becomes a thin shell — when a tractor binary is present, farmhand spawns it as a subprocess and proxies the HTTP sidecar. farmhand retains plugin discovery and task memory.
-
tractor-ts conformance tests — the existing
agent_harness.rstests become the canonical conformance suite. Equivalent tests are added to tractor-ts to ensure both implementations satisfy the same behavioral contracts. -
npm run agent:daemonjoins the canonical flow — once tractor has the HTTP sidecar,agent:daemonbecomes a valid alternative tofarmhand:daemon. The long-term canonical flow becomes a single command that starts whichever runtime is available.
- The effort/stream protocol (
POST /efforts, SSE streams) — defined in ADR-060, unchanged from today. - The CRDT storage schema — Loro-based, shared between both runtimes.
- Plugin manifest format (
plugin.json) — unchanged. refarm askCLI — continues to hit:42001, oblivious to which runtime is behind it.
| Concern | tractor-ts | tractor Rust |
|---|---|---|
| WASM sandbox | emulated via wasmtime Node bindings | native wasmtime |
| Memory footprint | ~120MB (Node) | ~20MB |
| Cold start | ~800ms | ~50ms |
| Daily driver fitness | marginal | strong |
| Extension surface | npm ecosystem | any WASM target |
| LSP bridge | partial | complete (harness-tested) |
Phase 1 (immediate): tractor HTTP sidecar — --http-port flag, /efforts endpoints, effort-to-plugin dispatch. Tracked in ADR-060.
Phase 2: farmhand delegates to tractor binary when present. tractor-ts becomes conformance-only.
Phase 3: single canonical boot command; daily driver flow established.
- ADR-056: Unified Refarm Host Boundary
- ADR-058: Context Injection Doctrine
- ADR-060: Tractor HTTP Sidecar Protocol