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/:wsIdthat exposes workspace-scoped tools likeinbox_push. ThewsIdis 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:
- Append user message to session
- Resolve provider (may be overridden per-request)
- Compact session if context window is getting full
- Read active session window
- Delegate to provider for streaming generation
- Process provider events (log tool calls, extract media, strip images)
- 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.
- Legacy path: event → AgentCenter → optional
See Async Lifecycle for details.
Composition Root
Everything is wired together in main.ts:
- Load config
- Create EventLog and ToolCallLog
- Create ToolCenter
- Create AccountManager, initialize trading accounts
- Create market data clients (TypeBB or OpenBB API)
- Load symbol index
- Register domain tools with ToolCenter (trading, market-search, equity, news, analysis, thinking, session, ...)
- Create InboxStore and WorkspaceToolCenter (registers
inbox_pushfactory) - Create AI providers (Vercel AI SDK + Agent SDK + Codex)
- Create AgentCenter with ProviderRouter
- Create ConnectorCenter
- Start cron engine, heartbeat, snapshot scheduler, news collector
- Start Workspaces subsystem (template registry, session pool, file/git services)
- Start plugins (MCP server with
/mcp+/mcp/:wsIdroutes, 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.