The actor is the unit. Deterministic. Typed. Four targets.
Each Borz actor is an isolated process with its own memory and message queue. Handlers run one at a time. No shared variables, no locks, no global state. The same source compiles to four targets — native binary, DEMIX microservice, DENSE nanoservice, or Ephernity smart contract. The compiler enforces these properties — they are not conventions.
msg Deposit:
amount: i64
msg Withdraw:
amount: i64
msg Balance: {}
actor Account:
@persistent var balance: i64 = 0 # survives restarts
@persistent var owner: str = ""
@http(path="/deposit")
on msg Deposit:
balance = balance + msg.amount
response(ok=true, balance=balance)
@http(path="/withdraw")
on msg Withdraw:
if msg.amount > balance:
response(ok=false, error="insufficient funds")
else:
balance = balance - msg.amount
response(ok=true, balance=balance)
@http(path="/balance")
on msg Balance:
response(balance=balance) Each actor owns its memory. No actor can read or write another's state directly. Communication happens only through typed messages.
Handlers run one at a time per actor. No data races, no deadlocks, no mutexes. Concurrency comes from having many actors, not from threads inside one.
Mark any state variable @persistent and the compiler wires up save/restore automatically. Actors survive restarts, hot-reloads, and target changes.
Every message is a named struct with typed fields. The compiler rejects a handler that reads a field that does not exist on the message type.
One actor. Four places to run it.
The same .borz source compiles to four distinct targets.
No source changes between targets — change the compiler flag, not the program.
Native binary
A single self-contained Go binary with a built-in HTTP server and CLI. No runtime dependencies. Ideal for local tooling, CI workers, and serverless functions.
DEMIX microservice
Each actor runs in its own supervised Linux micro-VM. Full network stack, hardware isolation, millisecond cold starts, and rolling hot-reload without dropped connections.
DENSE nanoservice
Flat ELF binary on bare KVM — no OS, no runtime. ~50 µs p50 dispatch (single no-op handler benchmark), 16 KB binary, 2 MiB memory cap enforced at compile time. Ed25519 receipt per dispatch.
Ephernity smart contract
Deterministic actors compile to smart contracts on the Epher Compute Chain — attested, append-only, and auditable. Write the actor once; the chain enforces invariants at execution.
# Compile once — change the target flag, not the program.
borz compile account.borz --target native # → ./account (Go binary, built-in HTTP + CLI)
borz compile account.borz --target demix # → DEMIX micro-VM package (supervised lifecycle)
borz compile account.borz --target dense # → account.elf (flat KVM binary, ~50 µs no-op dispatch)
borz compile account.borz --target ephernity # → account.ecc (Epher Compute Chain contract) Messages carry typed data.
A message is a named struct. Fields have types. Actors send messages to each other;
handlers pattern-match on message type and access fields directly via
msg.field.
# scalar fields
msg Greet:
name: str
lang: str = "en" # default value
# sum type (enum with payload)
msg Result:
Ok: { value: i64 }
Err: { reason: str }
# generic message
msg Pair[A, B]:
first: A
second: B
# empty message (trigger / ping)
msg Tick: {} actor Router:
# handle multiple message types
on msg Greet:
response(reply="hello " + msg.name)
on msg Tick:
# no-op tick handler
# match on sum type variant
on msg Result:
match msg:
Ok(v):
response(value=v.value)
Err(e):
response(error=e.reason) Decorators attach capabilities.
Decorators wire observability, routing, testing, and runtime contracts onto actors and handlers without changing the handler body. The compiler checks them at build time.
@http(path=…) Expose a handler as an HTTP endpoint. Method is inferred from the message name.
@cli(name=…) Expose a handler as a CLI subcommand with auto-generated --flag arguments.
@grpc(service=…) Expose a message handler as a gRPC RPC method.
@persistent Persist a state variable across restarts and hot-reloads. Stored as structured JSON.
@dense(max_state_bytes=N) Declare the DENSE memory budget for this actor. Enforced at compile time.
@test Mark a handler as a unit test. Runs in isolation via `borz test`.
@table_test(file=…) Drive a handler with rows from a CSV or JSON table. One test per row.
@bench Mark a handler as a benchmark. Reports p50/p99 via `borz bench`.
@migrate_from(v=…) Declare a state migration handler for rolling hot-reload without data loss.
@trace Emit a structured trace span on every invocation. Exported as OpenTelemetry.
@metric(name=…) Increment a named counter or histogram on every invocation.
@deterministic Enforce at compile time that this handler calls no wall clocks, floats, or non-deterministic FFI.
@canonical Enforce byte-identical encoding across all targets. For actors that sign or hash their output.
@meter_budget(cpu_us=N, mem_kb=N) Declare the per-call resource envelope. The DELIGHT runtime enforces it at dispatch time.
An actor can call infer — it is still an actor.
When an actor uses the infer statement
to embed an LLM call, it becomes an agentic actor.
Its core is still deterministic Borz code: it decides what to infer, handles the typed result,
and routes the outcome — all in source. It is not a pure LLM agent.
enum Priority: Urgent | Normal | Low
msg ClassifyTicket:
text: str
ticket_id: str
actor ClassifierActor:
@persistent var total: i64 = 0
@persistent var verdict: Priority = Normal
on msg ClassifyTicket:
# infer embeds the LLM call.
# target must be a typed state var —
# the result is parsed + validated here.
infer:
system: "Classify: Urgent | Normal | Low."
user: msg.text
target: verdict
total = total + 1
# deterministic routing after the infer step
send DispatchActor <- Route(
ticket_id: msg.ticket_id
priority: verdict
) infer specifies the system prompt, the user input,
and the typed target variable. The Borz compiler generates the provider call and validates the response.
The actor decides what to do next — the LLM produces one typed value, not an open conversation.
An agentic actor compiles to any of the four targets — native, DEMIX, DENSE, or Ephernity —
without changing the source. On DENSE, the
infer call exits the guest to reach the LLM host
and returns a typed value; the guest resumes deterministically after.
DENSE constraints (flat-ELF, memory cap, Ed25519 receipt) still apply.
A Borz agent is the LLM — a persona that processes
an entire message as an LLM call. An agentic actor is Borz code that invokes a single typed LLM step.
See the agent construct for the distinction.
Read the full spec.
The language reference covers actors, messages, traits, generics, expressions, imports, and the full decorator surface in detail.