agentskills.codes
AR

architecture

Electrobun uses a multi-process architecture:

Install

mkdir -p .claude/skills/architecture-mohitkoley && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16576" && unzip -o skill.zip -d .claude/skills/architecture-mohitkoley && rm skill.zip

Installs to .claude/skills/architecture-mohitkoley

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Electrobun uses a multi-process architecture:
45 chars · catalog descriptionno explicit “when” trigger

About this skill

Electrobun Algo Trading Architecture

Process Model

Electrobun uses a multi-process architecture:

┌─────────────────────────────────────────────────┐
│                  Launcher (Zig)                  │
│  Tiny binary → spawns Bun → inits native GUI    │
└──────────────────────┬──────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────┐
│              Bun Main Process                    │
│  ┌─────────────┐  ┌──────────────┐             │
│  │ Broker APIs  │  │ SQLite DB    │             │
│  │ WebSockets   │  │ Order Mgmt   │             │
│  │ Risk Engine  │  │ Strategy     │             │
│  └──────┬──────┘  └──────┬───────┘             │
│         │    Encrypted RPC    │                  │
│         │   (AES-256-GCM)    │                  │
│  ┌──────▼────────────────▼───────┐              │
│  │     RPC Transport Layer       │              │
│  │  WebSocket localhost + FFI    │              │
│  └──────────────┬────────────────┘              │
└─────────────────┼───────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────┐
│           Native Webview (per-window)            │
│  WKWebView (mac) / WebView2 (win) / GTK (linux) │
│  ┌─────────────────────────────────────┐        │
│  │  Electroview + UI (HTML/CSS/TS)     │        │
│  │  Dashboard, Charts, Order Entry     │        │
│  │  NO direct broker/DB access         │        │
│  └─────────────────────────────────────┘        │
└─────────────────────────────────────────────────┘

Layer Responsibilities

Bun Layer (src/bun/) — The Trading Engine

This is where ALL business logic lives:

  • Broker API authentication and session management
  • WebSocket connections to market data feeds
  • Order placement, modification, cancellation
  • Position tracking and P&L calculation
  • Risk management (pre-trade checks, circuit breakers)
  • Strategy execution engine
  • SQLite database operations (trades, logs, config)
  • Encryption and key management
  • Local HTTP/WebSocket server for external tool integration

Webview Layer (src/mainview/) — The Display

Pure presentation:

  • Renders data received via RPC messages
  • Sends user actions (place order, start strategy) via RPC requests
  • Chart rendering, table displays, forms
  • NEVER holds API keys, tokens, or database handles
  • NEVER makes direct HTTP calls to brokers

Shared Types (src/shared/) — The Contract

  • RPC schema type definitions
  • Data model interfaces (Order, Position, OHLC, etc.)
  • Enum definitions (OrderType, OrderStatus, Exchange)

Multi-Window Architecture

Trading apps often need multiple windows:

import { BrowserWindow, BrowserView } from "electrobun/bun";

// Main trading window
const mainWindow = new BrowserWindow({
  title: "Trading Dashboard",
  url: "views://mainview/index.html",
  frame: { x: 0, y: 0, width: 1400, height: 900 },
  rpc: mainRpc,
});

// Separate order book window
const orderbookWindow = new BrowserWindow({
  title: "Order Book",
  url: "views://orderbook/index.html",
  frame: { x: 1400, y: 0, width: 500, height: 900 },
  rpc: orderbookRpc,
});

// Chart window (can load external charting lib in sandboxed webview)
const chartWindow = new BrowserWindow({
  title: "Charts",
  url: "views://charts/index.html",
  frame: { x: 0, y: 900, width: 1900, height: 600 },
  rpc: chartRpc,
});

// Broadcast tick data to all windows
function broadcastTick(data: TickData) {
  mainWindow.webview.rpc?.send.tickUpdate(data);
  orderbookWindow.webview.rpc?.send.tickUpdate(data);
  chartWindow.webview.rpc?.send.tickUpdate(data);
}

Embedded Webviews for Third-Party Content

Use <electrobun-webview> for isolated content like broker login pages:

<!-- In your main HTML -->
<electrobun-webview
  id="broker-login"
  src="https://kite.zerodha.com/connect/login"
  sandbox
  style="width: 100%; height: 500px;">
</electrobun-webview>

The sandbox attribute prevents RPC injection, keeping the broker's login page isolated.

Service Architecture Pattern

// src/bun/services/market-data.ts
export class MarketDataService {
  private ws: WebSocket | null = null;
  private subscriptions = new Map<string, Set<(data: TickData) => void>>();
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 10;
  private reconnectDelay = 1000;

  constructor(private broker: BrokerConnection) {}

  async connect() {
    this.ws = new WebSocket(this.broker.getWebSocketUrl());
    this.ws.onmessage = (event) => this.handleMessage(event);
    this.ws.onclose = () => this.handleDisconnect();
    this.ws.onerror = (err) => this.handleError(err);
    this.reconnectAttempts = 0;
  }

  subscribe(symbol: string, callback: (data: TickData) => void) {
    if (!this.subscriptions.has(symbol)) {
      this.subscriptions.set(symbol, new Set());
      this.ws?.send(JSON.stringify({ action: "subscribe", symbol }));
    }
    this.subscriptions.get(symbol)!.add(callback);
  }

  private handleMessage(event: MessageEvent) {
    const data = this.parseTickData(event.data);
    const callbacks = this.subscriptions.get(data.symbol);
    if (callbacks) {
      for (const cb of callbacks) cb(data);
    }
  }

  private async handleDisconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error("Max reconnection attempts reached");
      return;
    }
    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
    this.reconnectAttempts++;
    await Bun.sleep(delay);
    await this.connect();
    // Re-subscribe to all symbols after reconnect
    for (const symbol of this.subscriptions.keys()) {
      this.ws?.send(JSON.stringify({ action: "subscribe", symbol }));
    }
  }
}

Application Menu for Trading

ApplicationMenu.setApplicationMenu([
  {
    label: "Trading App",
    submenu: [
      { role: "about" },
      { type: "separator" },
      { label: "Preferences", action: "preferences", accelerator: "," },
      { type: "separator" },
      { role: "hide" },
      { role: "hideOthers" },
      { role: "quit" },
    ],
  },
  {
    label: "Trading",
    submenu: [
      { label: "New Order", action: "new-order", accelerator: "n" },
      { label: "Cancel All Orders", action: "cancel-all", accelerator: "Shift+x" },
      { type: "separator" },
      { label: "Start Strategy", action: "start-strategy" },
      { label: "Stop All Strategies", action: "stop-all-strategies" },
      { type: "separator" },
      { label: "Square Off All", action: "square-off-all", accelerator: "Shift+q" },
    ],
  },
  {
    label: "View",
    submenu: [
      { label: "Dashboard", action: "view-dashboard", accelerator: "1" },
      { label: "Order Book", action: "view-orderbook", accelerator: "2" },
      { label: "Positions", action: "view-positions", accelerator: "3" },
      { label: "Charts", action: "view-charts", accelerator: "4" },
      { type: "separator" },
      { role: "toggleFullScreen" },
    ],
  },
  {
    label: "Edit",
    submenu: [
      { role: "undo" }, { role: "redo" },
      { type: "separator" },
      { role: "cut" }, { role: "copy" }, { role: "paste" }, { role: "selectAll" },
    ],
  },
]);

Tray for Background Trading

const tray = new Tray({
  title: "Trading",
  template: true, // Adapts to macOS light/dark mode
});

function updateTrayMenu(state: AppState) {
  tray.setMenu([
    { label: `P&L: ${state.totalPnL >= 0 ? "+" : ""}${state.totalPnL.toFixed(2)}`, enabled: false },
    { label: `Open Positions: ${state.openPositions}`, enabled: false },
    { label: `Pending Orders: ${state.pendingOrders}`, enabled: false },
    { type: "separator" },
    { label: "Show Dashboard", action: "show-dashboard" },
    { label: "Quick Order", action: "quick-order" },
    { type: "separator" },
    {
      label: "Emergency",
      submenu: [
        { label: "Cancel All Orders", action: "cancel-all" },
        { label: "Square Off All", action: "square-off" },
      ],
    },
    { type: "separator" },
    { label: "Quit", action: "quit" },
  ]);
}

Error Handling Strategy

// Wrap all RPC handlers with consistent error handling
function wrapHandler<P, R>(
  name: string,
  handler: (params: P) => Promise<R>
): (params: P) => Promise<R> {
  return async (params: P) => {
    try {
      return await handler(params);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      console.error(`[RPC:${name}] Error:`, message);
      // Log to DB for audit
      db.run(
        "INSERT INTO error_log (handler, message, params, timestamp) VALUES (?, ?, ?, ?)",
        name, message, JSON.stringify(params), Date.now()
      );
      throw error; // Re-throw so webview gets the error
    }
  };
}

Updater Integration

// Check for updates on startup (non-blocking)
async function checkUpdates() {
  try {
    const info = await Updater.checkForUpdate();
    if (info.updateAvailable) {
      mainWindow.webview.rpc?.send.connectionStatus({
        broker: "app",
        status: "update-available",
      });
      // Download in background
      await Updater.downloadUpdate();
      // Notify user, don't force-apply during trading hours
      Utils.showNotification({
        title: "Update Available",
        body: `Version ${info.version} is ready to install.`,
      });
    }
  } catch (e) {
    console.error("Update check failed:", e);
  }
}

// Only apply updates when user explicitly requests (not during trading)
async function applyUpdate() {
  const positions = await broker.getPositions();
  const openPositions = positions.filter(p => p.quantity !== 0);
  if (openPositions.length > 0) {
    Utils.showNotification({
      title: "Cannot Update",
      body: "Close all positions before updating.",
    });
    return;
  }
  await Updater.applyUpdate();
}

Search skills

Search the agent skills registry