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.