backend / omni
Omni
An agent harness whose engine streams typed events over a Bun HTTP + WebSocket server, persists sessions, traces and prompt variants in versioned SQLite, and asks the client for tool-permission decisions mid-run.
2026 · paused · open source
- [*]LanguagesTypeScript
- [*]FrameworksBun.serve (HTTP + WebSocket) · AI SDK · Tauri (desktop shell) · MCP client
- [*]StoresSQLite (bun:sqlite, WAL)
- [*]AsyncAsyncIterable event streams · WebSocket push · bounded async queue for merged tool/progress streams
- [*]InfraBun · GitHub Actions (typecheck, test, cross-platform CLI binaries) · npm release scripts
- [*]Third partiesOpenAI · Anthropic · Google Gemini · Ollama · OpenAI-compatible endpoints · Xiaomi MiMo · MCP servers
Omni is a TypeScript/Bun monorepo with 11 packages, built in 98 commits between 14 and 30 May 2026, all by the owner. Its idea is that a strong harness (planner, critic, memory, model probing) makes small open models useful. The backend-relevant parts are the engine loop (packages/core), the SQLite storage layer (packages/storage) and two servers (packages/server). One server is a headless WebSocket server that runs an engine per connection. The other is a desktop server with 18 REST method-routes, used by a Tauri app, for projects, sessions, config, git status/diff and sandboxed file reads.
The engine is an async generator of typed events. Each iteration assembles context to fit the model's window (compacting when needed), runs a model turn inside a bounded retry loop that honours retry-after, and runs tool calls one at a time through a permission gate. Loop detection is a sliding window of order-independent tool-call signatures; when the same signature recurs past a threshold, the run stops.
Storage is bun:sqlite in WAL mode with foreign keys on and 5 forward-only migrations. Each migration runs in a transaction and is recorded in a _migrations table. The tables hold sessions, messages, the event trace, the tool audit log, cached model capability profiles, per-model prompt variants and a vector_memory table of Float32 BLOBs searched by linear cosine scan.
This is the best-tested project in the batch: 69 test files with 600 test() and it() cases, run in a GitHub Actions job that also typechecks.
How it holds up
- Auth
- Optional pre-shared bearer token on both servers. The desktop server also accepts ?token= so browser WebSockets can authenticate. Tool execution is gated by pluggable PermissionGates: allow-all, deny-all, static, ask, and ordered rule lists with a destructive-shell-command predicate. Interactive asks travel over the WebSocket and auto-deny after 30 s.
- Data
- 7 SQLite tables plus _migrations, from 5 versioned migrations in packages/storage/src/db.ts: sessions, messages, events (trace), audit_log, model_profiles, prompt_variants (per model since migration 5) and vector_memory (Float32Array BLOB + dim).
- Consistency
- Each migration and its version stamp commit in one transaction. Tool calls within a turn run strictly one at a time, so permission prompts never interleave. Each WebSocket owns its own Engine and AbortController, and an abort message or socket close aborts the run and denies every pending permission request.
- Failure
- Retryable model errors are retried up to maxRetries per iteration, with retry-after or 1 s delay, an engine.retrying event and abort-aware sleep; non-retryable errors go to onError hooks before a fatal event. Loop detection ends spinning runs. When a model emits no native tool calls, a ReAct text parser recovers the call. An unanswered permission request resolves to deny after the timeout.
- Testing
- 69 test files with 600 test()/it() cases across core, tools, improve, adapters, storage, server and cli. CI runs bun run typecheck and bun test --timeout 30000.
Techniques
- Signature-based loop detectionEach turn's tool calls are reduced to a canonical, order-independent signature (sorted name:stableStringify(args)). The engine keeps a sliding window of recent signatures and stops the run with loop_detected when one recurs past a threshold. That is the real guard when the iteration cap is unbounded.
packages/core/src/util/signature.ts:9packages/core/src/engine.ts:347 - Bounded in-iteration retry with typed error categoriesModel turns retry inside the same iteration, so retries do not use up the iteration budget. The loop honours the provider's retry-after, emits engine.retrying events, sleeps abort-aware and hands non-retryable errors to onError hooks before declaring the error fatal.
packages/core/src/engine.ts:291 - Permission requests over the socket with timeout-denyIn interactive mode the gate sends a permission.request with a ULID to the connected socket and waits in a pending map for the matching response. If the timeout fires first, the request resolves to deny. Socket close or run end denies everything still pending.
packages/server/src/server.ts:248packages/server/src/server.ts:157packages/core/src/permissions.ts:103packages/core/src/permissions.ts:163 - Transactional versioned SQLite migrationsAn append-only MIGRATIONS array is applied from the current _migrations max version. Each step and its version row commit in one transaction under WAL with foreign keys enforced. Migration 5 adds per-model scoping to prompt variants and tags older rows as __legacy__.
packages/storage/src/db.ts:10packages/storage/src/db.ts:131 - Cached model probing feeding strategy adaptationOn first contact the harness probes a model for native tool calls, instruction following and verbosity, and caches the profile in SQLite for 24 h. adapt() then picks prompts and the loop strategy, for example the ReAct fallback when native tool calls are missing.
packages/improve/src/probe.ts:24packages/improve/src/adapt.ts:52packages/core/src/engine.ts:338 - Tournament selection over per-model prompt variantsPrompt variants carry trial counts and cumulative trace scores. Tournament selection picks a variant and a seeded mutator derives new ones, persisted per model in prompt_variants.
packages/improve/src/evolve.ts:66packages/improve/src/evolve.ts:82packages/storage/src/variants.ts
Counted
- 98commits
git rev-list --count HEAD - 1 (Halleluyah Oludele)authors
git shortlog -sne HEAD - 35180TypeScript LOC (all tracked .ts/.tsx)
git ls-files '*.ts' '*.tsx' | xargs wc -l | tail -1 - 11workspace packages
ls packages - 5 / 7SQLite migrations / tables
packages/storage/src/db.ts:10-105 - 18desktop REST method-routes
packages/server/src/desktop/server.ts:153-265 - 69 / 600test files / cases
git ls-files | grep '\.test\.ts$' | xargs grep -cE '^\s*(test|it)\(' | sum - 6 + mockmodel adapters
git ls-files packages/adapters/src - 30000 msdefault permission timeout
packages/server/src/server.ts:89