Skip to content

Architecture

The monorepo splits into a small number of independent deployables that communicate over HTTP, gRPC, and WebSockets. This page lists each service, what it owns, and traces a single request end-to-end.

ServicePathPort(s)Role
Engine librariespackages/weiqi-engine, packages/go-engine, packages/katago-serverPure rules / move-generation logic, no I/O.
Gatewayapps/weiqi-server3002NestJS API: game, bot, analysis, challenges, accounts, agent.
KataGo wrapperapps/katago-service4011 HTTP / 50051 gRPCSpawns the KataGo binary, fans out requests via BullMQ.
Board photo OCRapps/recognition-service4010Runs the moku-v3 ONNX model, sync and async endpoints.
MCP serverapps/mcp-bridge3003Exposes 6 tools (list-levels, start-bot-game, …) over HTTP / stdio.
Auth microserviceapps/auth-app3004Better-Auth + Drizzle, OIDC, magic-link, sessions.
USDC indexerapps/billing-indexer9090Watches USDC.Transfer events, delivers signed webhooks to auth-app.
Player UIapps/game-web3001React 19 + Vite SPA: play, bots, analysis, scan, profile, bot-ladder.
Marketing / blog / landingapps/main-app3003Payload CMS on Next.js 16 (OpenNext → Cloudflare).
Astro landing (legacy)apps/main-app-astro3001Astro 5 marketing site + blog, en/ja/zh/ko.
Admin consoleapps/admin-app3014Internal admin UI, gated by VITE_ADMIN_REQUIRED_ROLE.
Communityapps/comunity-appCommunity features (separate frontend).
flowchart LR
subgraph Client
GW[game-web<br/>React 19 + Vite]
MAA[main-app<br/>Payload CMS]
DOCS[weiqi-docs<br/>Astro + Starlight]
end
subgraph Edge
WS[weiqi-server<br/>NestJS :3002]
AUTH[auth-app<br/>Better-Auth :3004]
MCP[mcp-bridge<br/>MCP :3003]
end
subgraph Workers
KATA[katago-service<br/>HTTP :4011 / gRPC :50051]
REC[recognition-service<br/>ONNX moku-v3 :4010]
BILL[billing-indexer<br/>USDC indexer :9090]
end
subgraph Data
PG[(Postgres)]
RD[(Redis)]
S3[(S3 / MinIO)]
USDC[(USDC on-chain)]
end
GW -->|REST + Socket.IO| WS
GW -->|session cookie| AUTH
MAA -->|session| AUTH
MCP -->|REST| WS
WS -->|gRPC| KATA
WS -->|HTTP| REC
BILL -->|Ed25519 webhook| AUTH
WS --> PG
WS --> RD
WS --> S3
AUTH --> PG
BILL --> USDC

A player presses “play move” in game-web. The request travels through five hops before KataGo returns a chosen move.

  1. Browser → gateway. apps/game-web/src/entities/bot-game/api/bot-game.api.ts POSTs to /api/game/bot/games/:id/move via createApiInstance (raw fetch, session cookie auto-attached). The gateway is NestJS at apps/weiqi-server/src/main.ts:14.
  2. Auth guard. AuthGuard reads better-auth.session_token (the __Secure- prefix is tried first), calls AccountsService.validateSession, attaches {user, session} to the request.
  3. BotGameService. apps/weiqi-server/src/modules/bot/application/bot-game.service.ts validates the move against BotGameService’s authoritative board state and appends it to the botGameMoves row.
  4. KatagoService. KatagoService.getMove picks an IKatagoClient based on configuration: GrpcKatagoClient (default), HttpKatagoClient, the in-process BullMqKatagoClient, or MockKataGoClient for tests.
  5. kata-go binary. KataGoBinaryEngineAdapter.analyze shells out to the KataGo process, streams JSON from stdout, parses the GTP response, and returns {moveInfos, rootInfo, ownership?}.
  6. Persist + emit. The move is written to botGameMoves, the bot’s reply is appended, and BotRealtimeEmitter pushes gameState and botMove to the player’s socket room. The browser reconciles without a refresh.

For an end-to-end analysis after the game ends, the same path is used but with analyzeTurns: [0..N] so KataGo replays the whole game in a single query — see KataGo Review.

  • Engine packages have no I/O. packages/weiqi-engine and packages/go-engine are pure TypeScript. They never read env vars, never hit the network, never touch the DB. Everything they need is passed in via constructor arguments or method parameters.
  • The gateway is the only writer to game state. Bot moves, hint requests, review snapshots, ranking updates — all flow through weiqi-server modules. apps/katago-service is stateless and only sees the request payload.
  • billing-indexer does not share a database with auth-app. They communicate exclusively through signed webhooks (Ed25519, verified via packages/billing-shared/webhook-signature.ts). This keeps the indexer free to redeploy without touching auth.
  • Auth is centralised. Every internal HTTP call to weiqi-server carries the user’s session cookie, which is validated by accounts module against auth-app. There is no service-to-service JWT — only session cookies.