entorin

Quick start

From install to fully-instrumented run in under five minutes — bare loop, no SDK.

The fastest path is the bare loop: a 50-line while that inherits the full harness because the substrate primitives — EventBus, Ledger, RunContext, Capability — emit on their own.

1. Install

uv add entorin
uv add 'entorin[mcp]'    # if you want the MCP tool transport

2. A complete run

import asyncio, uuid
from decimal import Decimal
from pydantic import SecretStr

from adapters.bare_loop import run_qa
from entorin.auth import Capability, Principal
from entorin.budget import BudgetGate, MemoryLedger
from entorin.context import RunContext
from entorin.events import EventBus
from entorin.model.adapters.anthropic import CAPABILITY_KIND, AnthropicModel
from entorin.tools import MCPToolClient

async def main():
    bus    = EventBus()
    ledger = MemoryLedger()
    gate   = BudgetGate(ledger)
    ledger.set_cap("alice", Decimal("5.00"))

    principal = Principal(
        user_id="alice",
        caps=(Capability(kind=CAPABILITY_KIND, value=SecretStr("sk-ant-...")),),
    )
    ctx = RunContext(run_id=str(uuid.uuid4()), principal=principal)
    model = AnthropicModel(bus=bus, ledger=ledger, gate=gate)

    answer = await run_qa(
        model=model, ctx=ctx, bus=bus,
        question="how many roads must a man walk down?",
        tool_clients={},
        max_turns=6,
    )
    print(answer)

asyncio.run(main())

That’s the whole thing. Every harness primitive — events, budget, span, capability, audit — emits because the substrate components emit them, not because the loop knows about them.

3. Inspect the trace

The substrate emits OTel spans on whatever exporter you’ve configured. A bare console exporter is enough to see the shape:

export OTEL_TRACES_EXPORTER=console
python my_bare_loop.py

You’ll see one root span per run, with entorin.run_id / entorin.principal_id attributes, and child spans for each LLM call, tool call, and sandbox exec.

Next steps