Olbos

Architecture

Olbos is a TypeScript monorepo (pnpm + Turborepo). This doc maps the components, explains the two ideas that everything hangs off of — event sourcing and the adapter/rail seams — and traces how a request flows through the system.

The two big ideas

Event sourcing

The engine's state is not a database you mutate. It is an append-only log of events, and everything else is a projection of that log:

  • the audit trail is the log, verbatim;
  • the engine's current view (which strategies exist, their custody, their kill state) is a fold over the log;
  • a replay of the log reconstructs the engine from nothing;
  • the dashboard's narration feed is the log rendered to English.

If it isn't an event, it didn't happen.

This is why audit isn't a feature bolted on — it's the storage model. The log lives in node:sqlite (a single-writer, append-only workload SQLite is ideal for). See Events for the full vocabulary.

Seams: adapters and rails

Two things in this system are guaranteed to change — the venues (protocols come and go, SDKs drift) and the payment facilitator (x402 is young). Both are isolated behind interfaces so the core never depends on a specific one:

  • VenueAdapter — every yield source implements list / APY / position-math / deposit / withdraw. The engine only ever sees this interface. See Venues.
  • PaymentRailDevRail (localnet) and X402Rail (real x402) implement the same collect() method. See Payments.

Component map

packages/
  shared/      event schemas · policy types · validation · alert()
  rails/       PaymentRail seam: DevRail | X402Rail
  sdk/         @olbos/sdk — the 5-line agent integration (auto-pays x402)
  mcp/         @olbos/mcp — Olbos as MCP tools (wraps the SDK)
  cli/         @olbos/cli — `olbos init` onboarding wizard

services/
  engine/      the heart: event log, custody, venue adapters, scorer,
               risk gate, executor, strategy runtime, the CLI/watch loop

apps/
  api/         x402-metered HTTP seller; multi-tenant session auth; hardening
  dashboard/   Next.js mission control; wallet sign-in; read-only + kill ring

programs/
  mock-venue/  Anchor lending program for localnet (the delphi/olympia venues)

The engine, in detail (services/engine/src/)

ModuleResponsibility
eventlog.tsthe append-only node:sqlite log; append / read / latestOfType
custody.tsper-strategy Swig: create, activate, payout, revoke, sign-wrap
venues/the VenueAdapter interface, VenueRegistry, and the mock/Kamino/Marginfi adapters
scorer.tsnet = headline − cost − risk haircut; utilization & TVL screens
risk.tsthe pure-function risk gate — caps, buffer, allowlist
executor.tssimulate → send → confirm, with deterministic intent IDs
strategy.tsStrategyRuntime: read → plan → gate → execute one cycle
dataClient.tsengine-as-buyer: pays for premium data over x402 (spend-capped)
cli.tsstatus / deploy / rebalance / plan / watch / kill / log

Request flow: a metered write

Tracing POST /v1/strategies/:id/rebalance:

agent ──x402 payment──▶ apps/api
                          │  rail.collect()  ── settle via facilitator
                          │  log.append(x402.payment.received)

                       StrategyRuntime.rebalanceOnce()
                          │  registry.list()        ── live venue snapshots
                          │  scoreVenues()           ── risk-adjusted ranking
                          │  plan()                  ── greedy fill + gates
                          │  checkAction()           ── pure risk gate
                          │  custody.wrap(ixs)       ── Swig sign-wrap

                       Executor.execute(intentId, ixs)
                          │  simulate ── reject on failure
                          │  send · confirm
                          │  log.append(tx.simulated / tx.sent / tx.confirmed)

                       log.append(rebalance.executed)

The same shape holds for deploy, fund, and withdraw — collect payment, mutate through the runtime, log every step.

Custody integration point

The StrategyRuntime doesn't know about Swig. It asks its injected StrategyCustody two things: whose positions to read (the Swig wallet PDA) and how to sign venue instructions (wrap them in the engine's scoped role). When custody is absent (legacy/localnet), it falls back to the engine keypair. This keeps the planner and executor custody-agnostic. See Custody.

Determinism & idempotency

Every executed action carries an intent ID: a SHA-256 over the canonical decision inputs (strategy, kind, from/to venue, amount, and the sampled venue indexes that pin the decision context). Same decision → same ID across restarts. Before broadcasting, the executor checks the log:

  • already tx.confirmed for this intent? → return it, don't re-send.
  • tx.sent but not confirmed? → check the signature's on-chain status before rebuilding.

This gives at-most-once execution: a crash mid-cycle never double-spends.

Multi-tenancy

A single engine instance serves many owners. Isolation is enforced at two layers:

  • Custody — each strategy has its own Swig wallet; funds are isolated by construction, not by ledger discipline.
  • API — in hosted mode (OLBOS_REQUIRE_AUTH=1), reads are scoped to the signed-in owner via a stateless wallet signature; the audit log is filtered by correlation ID so one tenant never sees another's payments or transactions. See Security — multi-tenancy.

Reliability posture

  • Simulate-first execution — nothing failing simulation is broadcast.
  • At-most-once intents — crash-safe, no double-spend.
  • Fail-safe kill — N consecutive cycle failures, or a venue health-invariant violation, halts the engine and alerts.
  • Single-writer event log + a watch lockfile — two live planners is a bug, not redundancy; the lock prevents it.
  • Restart-safe — kill state lives in the log, so a restarted engine holds after a kill instead of resuming.

See Operations for how these are configured and what's still on the hardening list.

Next: Custody · Security · Venues.

On this page