Borz agents

Two kinds. Not the same thing.

Borz has two distinct first-class constructs for agentic work. agent is a pure LLM persona — it runs on a model host such as Claude or ollama, it is inference. actor is deterministic compiled code with four compile targets — it may call infer, but it is still an actor. They share one typed message bus.

agent construct — LLM persona runs on: Claude / ollama
agent ReviewAssistant:
    model: claude:sonnet
    memory: rolling(turns: 20)

    system: """
        You are a senior code reviewer.
        Be direct. Flag correctness, then
        style. Include line references.
    """

    tools:
        - CodeRepo.ReadFile
        - CodeRepo.SearchSymbol

    on msg ReviewPR:
        diff:  str
        title: str
actor construct — deterministic, 4 targets native · DEMIX · DENSE · Ephernity
msg GatePR:
    pr_id:  str
    status: str

actor PRGateActor:
    @persistent var approved:  i64 = 0
    @persistent var rejected:  i64 = 0

    @http(path="/gate")
    on msg GatePR:
        if msg.status == "approved":
            approved = approved + 1
            send MergeQueue <- Enqueue(pr_id=msg.pr_id)
        else:
            rejected = rejected + 1
            send NotifyActor <- Alert(pr_id=msg.pr_id)
The agent construct

An agent IS the LLM.

An agent definition is a first-class Borz source construct — written in a .borz file, managed by the compiler, versioned with your code. But it does not lower to any binary target. It is a pure LLM agent: a persona, a model requirement, a toolset, and a typed message interface. The whole agent is inference — it does not "call" an LLM, it runs as one.

You declare which model and host the agent mandates — claude:opus, local:ollama:gemma4:12b — and the Clan manager routes to it at runtime. The agent definition is a contract the Clan can check and enforce.

Identity / persona

Name, kind, system behaviour written as inline prose. The compiler validates it is present.

Model mandate

Required LLM host and model — checked by the Clan at dispatch time, not at compile time.

Typed message interface

The agent declares which message types it handles. The compiler rejects callers that send the wrong shape.

Generic prompt support

An agent may also accept the generic Prompt message type for open-ended input without a schema.

Tools

A declared list of tools the agent is permitted to call. The Clan enforces the list as a policy cap.

Memory

Rolling-window or persistent conversation memory. Declared in source; managed by the Clan runtime.

anomaly-agent.borz — pure LLM agent
# AnomalyNarrator runs on a local ollama host.
# It is NOT a DENSE actor. It does not compile
# to any binary. It is a pure LLM persona with
# a typed input contract.

agent AnomalyNarrator:
    model:  local:ollama:gemma4:12b
    memory: rolling(turns: 5)

    system: """
        You are a financial anomaly analyst.
        Given a transaction record and a risk
        score, write a concise two-sentence
        narrative explaining the anomaly and
        recommending an action (block, hold,
        or approve).
    """

    tools:
        - MerchantDB.Lookup
        - BlacklistDB.Check

    on msg NarrateAnomaly:
        tx_id:       str
        amount_eur:  i64
        merchant:    str
        risk_score:  f64
Never DENSE. Never a binary.

An agent definition declares a host model, not a compile target. DENSE constraints (@dense, memory caps, flat-ELF lowering) do not apply. The compiled artifact is a JSON manifest the Clan runtime provisions.

triage-actor.borz — agentic actor (compiles to any target)
# TriageActor is an ACTOR, not an agent.
# It compiles to native, DEMIX, DENSE, or Ephernity.
# It calls infer — which makes it "agentic" —
# but its core is deterministic Borz code.

msg TriageTicket:
    ticket_id: str
    text:      str

msg Stats: {}

actor TriageActor:
    @persistent var processed: i64 = 0
    @persistent var verdict:   str = ""

    @http(path="/triage")
    on msg TriageTicket:
        infer:
            system: "Classify as: urgent | normal | low. One word only."
            user:   msg.text
            target: verdict    # typed state var — parsed + validated

        processed = processed + 1

        # Route to another deterministic actor — typed send
        send DispatchActor <- Route(
            ticket_id: msg.ticket_id
            priority:  verdict
        )

    @http(path="/stats")
    on msg Stats:
        response(processed=processed)
The actor construct — agentic variant

An actor that calls infer is still an actor.

An actor is deterministic compiled Borz code. It compiles to four targets: native binary, DEMIX microservice, DENSE nanoservice, or Ephernity smart contract. It has typed messages, sequential handlers, isolated state, and the full Borz compiler guarantee set.

An actor can call infer — an LLM call embedded in a handler step with a typed output variable. This makes it an agentic actor. Its deterministic core decides what to infer, what to do with the result, and what to send next. It is not a pure LLM agent. The control logic is Borz code, not a system prompt.

A
Deterministic core

Handler logic is Borz code. The compiler knows exactly what the actor will do except for the infer result.

A
Four compile targets

Compiles to native binary, DEMIX microservice, DENSE nanoservice, or Ephernity smart contract — without changing source.

A
infer is one step

infer embeds an LLM call with a typed target variable. The result is parsed and validated before the next line runs.

A
Not the LLM

The actor controls the infer call — its inputs, its target, and what happens after. The actor is the author, not the persona.

Side by side.

The same question answered for each construct.

Property
agent
actor
Keyword
agent
actor
What it fundamentally is
A pure LLM persona + toolset
Deterministic compiled Borz code
Execution host
Claude, ollama, or any LLM host
native binary, DEMIX, DENSE (KVM), or Ephernity smart contract
Control logic language
Natural language (system prompt)
Borz source code
Can use infer?
IS inference — the whole agent is LLM
Yes (infer step) → agentic actor
Compiled binary
No — artifact is a JSON manifest
Yes — binary for each compile target
State persistence
memory: (rolling or persistent turns)
@persistent var (survives restarts)
Type-checked interface
Yes — typed message handlers
Yes — typed message handlers
Accepts generic prompts?
Yes — generic Prompt message type
Only declared message types
Audit receipt
Yes — Clan-level per-dispatch
Yes — target-level per-dispatch
Agent ↔ Actor messaging

One typed message bus. Two distinct kinds.

Agents and actors communicate via the same typed message system. An actor sends a typed message to an agent — the agent processes it as an LLM call and sends a typed reply back. The compiler checks both sides of the message contract.

Step 1 — actor (DENSE)

RiskActor runs a deterministic risk model on a transaction. Score exceeds threshold → sends a typed NarrateAnomaly message to AnomalyNarrator.

typed NarrateAnomaly message
Step 2 — agent (ollama)

AnomalyNarrator receives the message, runs the LLM call, and sends a typed AnomalyReport reply to AuditActor.

risk-actor.borz — sends to agent
msg ScoredTransaction:
    tx_id:      str
    amount_eur: i64
    merchant:   str
    risk_score: f64

actor RiskActor:
    @persistent var flagged: i64 = 0

    on msg ScoredTransaction:
        if msg.risk_score > 0.85:
            flagged = flagged + 1

            # Send typed message to the agent.
            # The compiler checks NarrateAnomaly
            # matches what AnomalyNarrator declares.
            send AnomalyNarrator <- NarrateAnomaly(
                tx_id:      msg.tx_id
                amount_eur: msg.amount_eur
                merchant:   msg.merchant
                risk_score: msg.risk_score
            )
        else:
            send ClearanceActor <- Approve(tx_id=msg.tx_id)
anomaly-agent.borz — the pure LLM agent
agent AnomalyNarrator:
    model:  local:ollama:gemma4:12b
    memory: rolling(turns: 5)

    system: """
        Financial anomaly analyst.
        Given a transaction and risk score,
        write a two-sentence narrative and
        recommend: block | hold | approve.
    """

    tools:
        - MerchantDB.Lookup
        - BlacklistDB.Check

    # Typed message — compiler-checked
    on msg NarrateAnomaly:
        tx_id:      str
        amount_eur: i64
        merchant:   str
        risk_score: f64
audit-actor.borz — receives agent's reply
msg AnomalyReport:
    tx_id:         str
    narrative:     str
    recommendation: str   # block | hold | approve

actor AuditActor:
    @persistent var blocked: i64 = 0

    on msg AnomalyReport:
        if msg.recommendation == "block":
            blocked = blocked + 1
            send BlockList <- Add(tx_id=msg.tx_id)

        # Typed audit receipt — immutable log
        send AuditLog <- Record(
            tx_id:     msg.tx_id
            narrative: msg.narrative
            action:    msg.recommendation
        )
Design rationale

Why not merge them?

Keeping them separate preserves what each is good at — and prevents each from inheriting the constraints of the other.

Actors need compile-time guarantees

Targets like DENSE enforce memory caps, flat-ELF binary size, Ed25519 receipts, and deterministic dispatch latency. Ephernity enforces byte-identical execution. These constraints are incompatible with running an LLM. If agent were a subclass of actor, agents would inherit incompatible constraints.

Agents need flexibility actors cannot have

A pure agent may accept arbitrary prompt shapes, maintain a rolling conversation window, and be generalist. Actors must have a fixed, declared message interface. Forcing agents into the actor model would break the open-ended input pattern.

Typed boundary between the two

By keeping them separate with a shared typed message bus, the compiler can check that every agent↔actor handoff is well-typed. The boundary is explicit — not hidden inside an inheritance chain.

Separate routing + deployment

Actors deploy to binary targets (native, DEMIX, DENSE, Ephernity). Agents route to LLM hosts (Claude API, local ollama). The Clan manages both routing paths, but they are fundamentally different infrastructure.

Deploy in a Clan

Run agents and actors together.

A Clan runs both kinds side by side. Clan Control routes dispatches to binary actors and to LLM hosts, enforcing policy at every tool call and message boundary.