Skip to main content
Product updates for the scomp protocol, Rust SDK, and embedded sandbox runtime.

Week of May 18, 2026

A huge week. The TypeScript SDK is here, spec v0.2 brings first-class cancellation and timeouts, and two rounds of fuzzing closed dozens of bugs across both SDKs.

New features

TypeScript SDK. The full scomp SDK now ships for TypeScript and Node.js — @scomp/core, @scomp/sdk, @scomp/transport-ws, and @scomp/runtime-qjs. Feature parity with the Rust SDK: WebSocket transport, QuickJS runtime, symmetric peer model, function references, and the same wire format. Use it anywhere you’d reach for Node. Cancellation and timeouts (spec v0.2). Long-running evals and invokes can now be cancelled mid-flight. The protocol gained a $/cancel notification (§3.6.2), capabilities advertisement at handshake time, and two new error codes — TimeoutError (-32011) and CancelledError (-32012). QuickJS is wired through to actually interrupt on cancel, not just stop awaiting. See Wire format. Per-call options with cascading defaults. Set timeouts and other options per call, per session, or globally. Rust gets EvalRequest / InvokeRequest builders via IntoFuture; TypeScript takes Partial<EvalOptions> / Partial<InvokeOptions>. Six timeout knobs in total: dial, handshake, and idle on the transport; eval, invoke, and reverse-invoke on the session. server.* namespace. Server-provided bindings now live under a server.* namespace, matching the symmetric peer model where clients can declare bindings too. Dual-mode for backwards compatibility — old call sites keep working. TypeScript DX overhaul. A four-phase DX pass landed for TS:
  • binding<A,R>() — typed binding builder with full inference.
  • @scomp/transformer — TypeScript transformer that lifts plain functions into bindings with schemas derived from their signatures.
  • ScompFunction<A,R> — typed function-reference helper, with a clear diagnostic when you forget to mark a raw function.
  • scomp init — new CLI scaffolds a working TS project in one command.
scomp init CLI. New scaffolder generates a TypeScript scomp project with sensible defaults — transport, runtime, and a sample binding wired up. See the TS quickstart. Session lifecycle hardening. New Drop / dispose() semantics release session leases deterministically, a default 256 MB memory cap protects QuickJS runtimes, and RuntimeScope.deactivate() was renamed to unsafeDeactivate() to make the isolation-bypass risk explicit.

Updates

Capabilities handshake. The handshake now carries a capabilities slot so peers can advertise what they support (starting with cancel). Forward-compatible — unknown capabilities are ignored. Richer error metadata. Eval and invoke errors now carry structured metadata (taxonomy, hints, original error context) instead of stringified blobs. Cross-binding rejections are collapsed to a clean EvalError shape with the original details preserved in metadata. Stricter handshake validation. JSON-RPC handshake validation reached parity across both SDKs — bad payloads are rejected at the door with explicit errors instead of leaking through. LLM-friendly error messages. QuickJS runtime errors continue to use LLM-readable descriptions, now with consistent error codes across Rust and TypeScript. Outbound payload size cap. WebSocket sends are bounded to prevent runaway payloads from taking down a peer. Typed handshake rejection. WsInput::Dial carries an explicit session_id field with a structured rejection path for unsupported values.

Bug fixes

The fuzz-2 and fuzz-3 sweeps closed dozens of issues across both SDKs:
  • Fixed observer exceptions being able to kill a session — WireObserver errors are now isolated.
  • Fixed a deadlock in WebSocket close: tracked connections are now closed before wss.close().
  • Fixed function-reference identity loss when invokeByRef returned a function at the top level (Rust QuickJS runtime).
  • Fixed QuickJsRuntime.dispose() to be terminal — pending operations are rejected before the runtime is freed.
  • Fixed TransportError to thread the underlying cause instead of dropping it.
  • Fixed accept-loop corruption after a per-connection transport failure.
  • Fixed activate() to preserve server bindings instead of clobbering them.
  • Fixed handler panics taking down the dispatcher — catch_unwind now wraps handler dispatch and returns InternalError.
  • Fixed capability input schemas to require properties on object types.
  • Fixed empty eval strings being accepted; they’re now rejected with a clear error.
  • Fixed Set, Map, Symbol, and NaN slipping into binding-argument serialization — all now rejected.
  • Fixed the bind! macro to produce a targeted error on bad destructuring instead of a wall of compiler noise.
  • Fixed a session-slot leak on send error (RAII lease guard).
  • Fixed several panic error codes to match the spec.
  • Fixed stdio transport EOF handling.
  • Fixed BindScompFns to reject enums with a clear error.

Week of May 11, 2026

Two major redesigns landed this week: an API idiomacy pass that reshaped the SDK surface, and a developer-experience pass that overhauls how you declare and call bindings.

New features

Ergonomic binding macros. Define a binding with a single attribute or a bind! macro — no more hand-rolling handler structs. The #[binding] attribute turns any function into a binding, picks up its /// doc comment as the description, supports an effects = [...] annotation, and emits a unit struct so you can pass server.bind(add) without parens. The same binding gains a .call(...) inherent method for use from Rust. The bind! macro (formerly bind_fn!) covers the closure form, with both sync and async arms. See Runtimes and bindings. Client-declared bindings. Clients can now declare their own bindings that the server can invoke — bringing the protocol’s symmetric peer model fully to the SDK. The server can call back into the client over the same connection. Session resumption. Reconnect to an existing session after a transport drop without losing state. The server keeps your session, replays what it owes, and resumes from where you left off. Client-side binding introspection. Clients can now query the server’s declared bindings (client_bindings) to discover what’s available before invoking — useful for agents that want to inspect a runtime’s capabilities at connection time. Native client.invoke. Clients can call server bindings directly with a typed invoke method, no more hand-building eval strings. Matches the spec’s §8.3 invoke shape. Builder-style construction with sensible defaults. Spin up a server with WebSocket + QuickJS preconfigured — ScompServer::builder() gives you a working setup in a few lines. Runtime and transport are now feature-gated, so you only pay for what you use. Binding hints field. Bindings can now advertise hints — kind, latency, and an open key/value map — so agents can make better decisions about when and how to call them. Defined in spec §7.1.1. See Wire format.

Updates

Typestate client API. ScompClient now splits into ScompClient (unconnected) and ConnectedClient (connected), so you can’t accidentally call connection-only methods on a disconnected client. The compiler catches misuse at build time. Unified binding registry. Client and server now share a single binding registry and a single inbound-invoke parser. Strict validation on both sides, no more divergent code paths. Peer<R> typestate. The peer type is now generic over its runtime, making custom runtimes a first-class extension point. Idiomatic SDK surface. Renamed and tightened several public types for consistency: ServerBindingHandlerBindingHandler, WireObserver trait replaces the set_on_send / set_on_recv pair, and the prelude now re-exports bind and binding for one-line imports. Slimmer dependencies. scomp_core no longer depends on tokio or thiserror, and scomp drops thiserror entirely — smaller compile times, fewer transitive deps. Better error reporting. QuickJS runtime errors now use LLM-readable messages, with cross-connection function references properly scoped. Error codes are aligned with the spec. JS eval ergonomics. The runtime now accepts bare object literals and zero-argument binding calls (add() instead of add({})) — matching what an agent naturally writes.

Bug fixes

  • Fixed non-deterministic compliance failures caused by a close-handshake race.
  • Function-reference lifecycle now uses proper refcounting; references with shared lifetime no longer leak.
  • BindScompFns derive now rejects enums with a clear error instead of producing broken code.
  • default_bindings::help is sync (it always was — the signature now matches).
  • The scomp-agent extends chat history across turns instead of replacing it.
  • QuickJS console binding registration fixed; rebind no longer leaves stale handlers.