GitHubBlog

Search Documentation

Search for a page in the docs

Architecture Overview

OpenAlice has five collaborating zones. Each one has a clear responsibility and communicates with adjacent ones through well-defined interfaces.

┌──────────────────────────────────────────────────────────────────┐
│  Surfaces       Web UI · Inbox tab · Telegram · MCP Server       │
├──────────────────────────────────────────────────────────────────┤
│  Workspace      claude / codex / shell session                   │
│                 (dir + git + native CLI, .mcp.json plumbed in)   │
├──────────────────────────────────────────────────────────────────┤
│  Core           AgentCenter · ProviderRouter                     │
│                 ToolCenter · WorkspaceToolCenter                 │
│                 InboxStore · ConnectorCenter · EventLog          │
├──────────────────────────────────────────────────────────────────┤
│  Domain         UTA (TradingGit · Guards · Brokers)              │
│                 Market Data · Analysis · News                    │
├──────────────────────────────────────────────────────────────────┤
│  Scheduling    Cron · Heartbeat · Webhook                        │
│                 (fires events → legacy AgentCenter path          │
│                  or workspace-resident execution)                │
└──────────────────────────────────────────────────────────────────┘

The layered picture is a simplification. A more accurate read is: Surfaces are where users meet Alice; Workspace is where the agent lives when running on a native CLI; Core wires everything together; Domain is the business logic; Scheduling decides when an AI call fires, with two possible execution targets.

Surfaces

External places where users interact with Alice.

  • Web UI — chat, sub-channels, portfolio dashboard with equity curve, config management, the Workspaces activity, and the Inbox tab.
  • Inbox tab — workspace-to-user push channel. Entries arrive when an agent inside a workspace calls inbox_push. See Inbox.
  • Telegram — bot integration via grammY. Commands, inline keyboards, push notifications. Auth-gated by allowed chat ID whitelist.
  • MCP Server — exposes Alice's tools via the Model Context Protocol. Used internally by the Claude Agent SDK provider and reachable by external MCP clients.

ConnectorCenter tracks the last-interacted channel for delivery routing.

Workspace

A per-task directory + git repo + persistent terminal session running a native agent CLI (claude, codex, or shell). It's the recommended substrate for non-trivial AI work — native prompt cache, native CLI rendering, no protocol shim between you and the model.

Wired to OpenAlice via two MCP servers in .mcp.json:

  • A global endpoint exposing the full tool catalog.
  • A per-workspace endpoint at /mcp/:wsId that exposes workspace-scoped tools like inbox_push. The wsId is carried in the URL path, so the agent never traffics its own identity — forgery surface is zero.

See Workspaces for the full picture.

Core

The orchestration layer.

AgentCenter

Top-level orchestration. All AI interactions (in the legacy chat path) flow through AgentCenter, which manages the pipeline:

  1. Append user message to session
  2. Resolve provider (may be overridden per-request)
  3. Compact session if context window is getting full
  4. Read active session window
  5. Delegate to provider for streaming generation
  6. Process provider events (log tool calls, extract media, strip images)
  7. Persist intermediate messages and final response

ask(prompt) is stateless; askWithSession(prompt, session, opts) runs the full pipeline.

ProviderRouter

Reads ai-provider.json on each call and resolves to the active profile's backend. Three interchangeable AI backends:

  • Claude Agent SDK@anthropic-ai/claude-agent-sdk, OAuth login (via Claude Code CLI) or API key. Tools delivered via in-process MCP server.
  • Vercel AI SDK — Direct API calls to Anthropic, OpenAI, or Google. Tools via Vercel's native tool system.
  • Codex — OpenAI Codex backend, OAuth or API key. Tools via the same in-process mechanism.

Switchable at runtime via the Web UI or by editing the config file. Per-channel and per-workspace overrides are supported.

ToolCenter & WorkspaceToolCenter

ToolCenter is the global registry — concrete tool instances registered once at bootstrap by domain modules. Consumers pull tools in the format they need (getVercelTools() or getMcpTools()).

WorkspaceToolCenter is parallel but inverted: it holds factories that take a workspace identity (wsId, label, shared deps) at request time and return tools whose execute() closes over that identity. This is how workspace-scoped tools like inbox_push get plumbed into the per-workspace MCP endpoint without ever asking the AI agent to traffic its own workspaceId.

Tools can be disabled via data/config/tools.json or per-channel via sub-channel config. The disable list is read on each call.

InboxStore

Append-only JSONL at data/inbox/entries.jsonl. Entries carry pointers to workspace files (rendered live at view time, never snapshotted) and the agent's markdown commentary. Deletes are atomic. Live append / removed events feed the Inbox tab's polling and SSE updates.

ConnectorCenter

Manages outbound message delivery. Single-tenant, multi-channel. Tracks which channel the user last interacted through. When the heartbeat or a cron job needs to send a message via the legacy path, it goes to that channel automatically. If no interaction has happened yet, it falls back to the first registered connector.

EventLog

Persistent append-only JSONL event bus with an in-memory ring buffer (500 entries). Trade submissions, heartbeat results, cron fires, errors — everything flows through. Dual-write to disk + memory; subscribable; recoverable on startup.

Domain

Business logic modules, each owning its state and persistence. The tool/ layer is a thin bridge that registers domain capabilities as AI tools.

Trading (UTA)

The largest domain module, centered on the Unified Trading Account. Each UTA bundles:

  • A broker connection (IBroker) — abstract interface implemented by CCXT, Alpaca, IBKR, and others.
  • A git-like operation history (TradingGit) — stage, commit, push workflow.
  • A guard pipeline — pre-execution safety checks.
  • A snapshot scheduler — periodic account state capture.

The AccountManager owns the full UTA lifecycle: init, reconnect, enable/disable, remove. AI and the UI interact with UTAs exclusively — brokers are internal implementation details.

See Unified Trading Account for the design rationale, Trading as Git for the workflow, and Accounts & Brokers for configuration.

Market Data

Structured data layer with two backends:

  • TypeBB (default) — TypeScript-native OpenBB engine running in-process.
  • OpenBB API — Remote HTTP API if you prefer a separate process.

Covers equity, crypto, currency, commodity, and macro data with unified clients per asset class.

Analysis

Technical indicator calculator with an Excel-like formula syntax. Supports SMA, EMA, RSI, Bollinger Bands, MACD, ATR, and more. See Technical Analysis.

News

Background RSS collector with configurable feeds and a persistent JSONL archive. Three search tools follow the Unix philosophy: globNews (find by title), grepNews (search content), readNews (read full article).

Scheduling

Cron, heartbeat, and webhook ingest fire events on a schedule. The scheduling layer is stable and shared across execution targets.

Two-layer execution model

What fires the event and how the event lands are separable concerns:

  • Scheduling layer — a typed append-only event log + cron engine emits events on a schedule (cron expression, interval, one-shot timestamp, periodic heartbeat with active-hours filtering, or inbound webhook).
  • Execution layer — two options:
    • Legacy path: event → AgentCenter → optional notify_user → NotificationsStore → connectors. Today's heartbeat and cron jobs use this. Fine for "one-in, one-out" pings.
    • Workspace-resident path: event triggers a one-shot task inside a Workspace, or drives continued dialog on a persistent workspace session. The direction the project is moving in.

See Async Lifecycle for details.

Composition Root

Everything is wired together in main.ts:

  1. Load config
  2. Create EventLog and ToolCallLog
  3. Create ToolCenter
  4. Create AccountManager, initialize trading accounts
  5. Create market data clients (TypeBB or OpenBB API)
  6. Load symbol index
  7. Register domain tools with ToolCenter (trading, market-search, equity, news, analysis, thinking, session, ...)
  8. Create InboxStore and WorkspaceToolCenter (registers inbox_push factory)
  9. Create AI providers (Vercel AI SDK + Agent SDK + Codex)
  10. Create AgentCenter with ProviderRouter
  11. Create ConnectorCenter
  12. Start cron engine, heartbeat, snapshot scheduler, news collector
  13. Start Workspaces subsystem (template registry, session pool, file/git services)
  14. Start plugins (MCP server with /mcp + /mcp/:wsId routes, Web UI, Telegram, MCP Ask)

The composition root is the only place that knows about all components. Each component is decoupled and depends only on its direct collaborators via interfaces.