Skip to content

UTCP over SBI

How Janus does Universal Tool Calling Protocol (UTCP) on the wire – and how it still talks to the rest of the world.

SPEC-244 makes SBI the normative transport for native Janus UTCP. JSON-line and HTTP are interop modes for external or older clients that do not speak SBI. They stay on purpose.

Related: std.utcp API, SBI preamble, tutorial UTCP peer and JPEN.


ModeWho uses itHow
SBI (normative)Janus agents, Libertria peers, new toolingLength-prefixed binary frames: u32 LE size + SBI\0 preamble + fixed LE header
HTTP (interop)Browsers, curl, foreign stacksSame port as SBI by default (dual-stack demux), or janusd --http
JSON-line (interop)Legacy scripts / out-of-date UTCP clientsjanusd --json – line-delimited JSON ops over TCP

Rule of thumb: write new Janus code against std.utcp and SBI only. Use HTTP or JSON when you must integrate something that is not Janus-native.


TCP stream:
[0..4) frame_len u32 LE
[4..) SBI frame
SBI frame:
[0..4) "SBI\0"
[4] version
[5] arch flags
[6..8) cap flags
[8..24) schema fingerprint (16 bytes)
[24..) kind / tool_id / status / n_caps / …
KindValueRole
INVOKE_REQ1Call a tool
INVOKE_RES2Status (and optional result body)
PRESENT3Add capabilities to the connection session
RESET4Clear session caps and staged payload
STAGE5Upload binary payload for the next invoke

The 16-byte fingerprint is BLAKE3-128 in domain-separated KDF mode (Zig std.crypto.hash.Blake3, same engine as std.crypto.blake3):

  • Domain string: janus.utcp.wire.schema.v1
  • Input: a fixed schema descriptor (kinds, result bodies, stage/CHNK rules)
  • Output: first 16 bytes of the digest

Peers that disagree on the fingerprint reject each other’s frames. That is intentional.


Successful lease and discovery invokes may attach a binary body after the INVOKE_RES header (n_caps == 0):

MagicMeaning
LRESLease register / heartbeat / state – entry id, TTL, live entry count
MANLBinary manual – tool list with known indices and required caps

Invoke utcp.manual (zero required caps) to fetch a MANL document without speaking JSON.


  • One STAGE frame carries at most 1024 payload bytes.
  • Raw STAGE replaces the connection’s staged buffer.
  • CHNK (optional inner framing) writes at an offset into a reassembly buffer up to 16 KiB. Use multi-chunk STAGE for larger tools payloads (e.g. split LEAS or future envelopes).

  1. PRESENT – accumulate known capabilities
  2. STAGE (optional) – JPEN, LEAS, or other binary payload
  3. INVOKE – capability check, then tool dispatch
  4. RESET – clear caps and stage

Multiple INVOKEs may run on the same connection until RESET or EOF.


Default janusd listen is SBI + HTTP on the same host:port:

  1. Read the first four bytes.
  2. If they look like an HTTP method (GET, POST, …), handle HTTP.
  3. Otherwise treat them as the SBI length prefix and run the UTCP peer.

Examples:

Terminal window
# Normative + interop on one port
janusd 127.0.0.1 7735
# Curl JSON manual (interop)
curl -sS http://127.0.0.1:7735/utcp
# SBI client: std.utcp.peer from Janus (same port)
FlagMeaning
(none) / --sbiDual-stack SBI + HTTP
--httpHTTP only
--jsonJSON-line only (interop)

  • Do not invent a new text protocol for Janus↔Janus tool RPC.
  • Do not treat feature-file JSON-line BDD as the normative Libertaria wire.
  • Do not remove HTTP or --json without an explicit product decision – they exist so the out-of-date world can still talk to janusd.

From the compiler repo (janus/), always use ./scripts/zb:

Terminal window
./scripts/zb test-utcp-sbi-wire
./scripts/zb test-utcp-peer-session
./scripts/zb test-utcp-manual-sbi
./scripts/zb test-utcp-stage-chunk
./scripts/zb test-janusd-sbi-dogfood # curl /utcp + SBI client

Implementation tip for this surface: origin/unstable @ 402c62e1 (2026-07-21).