anymo Docs
Core concepts

Architecture

Anymo is an embedded Rust kernel wrapped in a thin desktop shell, with an append-only event log as the single source of truth. This page explains the layers and the three decisions that shape them.

The layers

LayerTechnologyRole
Desktop shellTauri v2 + React + TypeScript + ViteHive graph, chat, approvals, goals, skills, Dreams, automations, plugins, settings
Hive KernelRust, embedded libraryRuns, scheduling, event log, providers, tools, policy, memory, skills, query fabric
ProtocolRust types → generated TypeScript + JSON SchemaOne source of truth for events, runs, agents, tools, skills
App-serverJSON-RPC over stdio / Unix socket / loopback WebSocketHeadless host for the same kernel
WorkersOptional, pooled subprocessesSpecialized capabilities (for example the local browser worker) behind a typed, policy-gated local protocol

The kernel is a plain Rust library with no UI dependency: everything important passes cargo test without the desktop app.

Decision 1: An embedded Rust kernel

The runtime lives inside the app as a plain Rust library, hive-kernel, rather than as a separate daemon the user has to install, run, and keep alive. The kernel owns the run lifecycle, the event-sourced log, the provider abstraction, the tool registry, the policy and approval engine, the memory store, the skill forge, and the hardened workspace.

Why this shape:

  • No daemon to install or manage. A central service would mean a second runtime to bundle, launch, supervise, version, and secure. An embedded library ships and starts with the app.
  • One memory-safe trust boundary. The security-critical code, including path canonicalization, workspace confinement, the policy engine, secret handling, and append-only sequencing, sits in one Rust library with strong typing.
  • Testable core. Because the kernel does not depend on the shell, the whole runtime is exercised by fast, headless cargo test runs.
  • Provider neutrality. A single OpenAI-compatible Provider trait covers local model servers, routers, proxies, and hosted APIs, so the choice of model is a configuration concern, not an architectural one.
  • Clean reuse path. Any future front end links the same library; the protocol crate keeps the types contract stable across them.

Domain types and the event schema live in a separate anymo-protocol crate that generates the TypeScript bindings the UI consumes. One source of truth, no hand-written duplicate types.

Decision 2: An event-sourced runtime

The append-only event log is the single source of truth. Current state is derived, never stored authoritatively.

  • Every meaningful state change is an Event: a uniform envelope (event_id, run_id, sequence, event_type, created_at, schema_version, actor, redaction status, optional causal links) wrapping a typed payload.
  • Events are append-only and gap-free per run: sequence numbers are 0, 1, 2, and so on. An append whose sequence does not match the next expected value is rejected as a conflict, so the log can never develop holes or races.
  • Run state is reconstructed by replay. reconstruct_run(events) folds a run's events into a RunProjection: the run, its agents, its tasks, its status. There is no separate mutable run object to drift.
  • The UI is a projection of events. The desktop app renders from the same event stream, live during a run or replayed from storage afterward.

The log is abstracted behind an EventStore trait with two backends: an in-memory store for tests and recorded mock streams, and a SQLite store for persistence. What this buys you:

  • One truth, no drift. What the user saw is exactly what replays.
  • Free time-travel and audit. Any consumer can rebuild any run's state from its events alone. This underpins restart-and-recover.
  • Integrity by construction. Gap-free sequencing with conflict rejection makes tampering and lost-update races detectable rather than silent.
  • Schema evolution. A schema_version on every event lets the on-disk shape evolve deliberately and append-safely.
Mistakes are corrected, not edited

Because events are append-only, a mistake is corrected by appending a compensating event, never by editing history.

Decision 3: Safe-by-default tools

A tool cannot do anything risky without the policy engine's agreement, and every tool call is recorded in the event log. Tools declare their required permissions and risk level up front; the policy engine evaluates every request and returns allow, require approval, or deny. This is covered in depth in Safety and permissions.

Pooled tool workers

Optional capabilities run as pooled subprocesses behind a typed, policy-gated local protocol, not as a central brain. The local browser worker is the first example: it binds to loopback only, requires a bearer token on every request, and only receives requests after the kernel has evaluated the tool call. The kernel remains the trust anchor.

Alternatives that were rejected

AlternativeWhy it was rejected
Central daemon with a thin UI clientA second runtime to bundle and supervise, an open local port as attack surface, and weaker compile-time guarantees on security-critical paths
All logic in TypeScript inside the webviewCouples the runtime to the UI and puts secret handling and filesystem confinement in the least-isolated layer
Mutable state with a side logThe two representations drift, the log becomes advisory, and audit and restore become unreliable
CRUD database as the source of truthLoses history and causality; what happened, in what order, and why is not recoverable
Microservice split from day oneHeavy operational overhead for a single-user desktop app; pooled workers keep the door open in a bounded way

Read the full decision records

Each decision is recorded with its full context, rationale, and consequences: