_SH Log's
Back to Root
EST: 5 min read

Building ChessGoddess: Stockfish + LLM on AWS

ChessGoddess analyzes chess games with Stockfish and explains moves with an LLM, deployed on AWS ECS. Here's how I built and deployed the full stack.

#chess#ai#aws#go

ChessGoddess is a chess analysis tool that combines Stockfish's engine precision with an LLM's ability to explain why a move is good (or bad) in plain language. It analyzes PGN game files and produces per-move commentary that a club-level player can actually learn from.

The core insight

Chess engines are brutally precise but opaque. Stockfish can tell you that Nf3 has a +0.4 evaluation advantage, but not why — not in terms a human can internalize. LLMs are great at natural-language explanation but can't play chess at any meaningful level. The combination is what makes ChessGoddess useful.

Architecture

User uploads PGN
  → Go API (parse PGN → move list)
  → Stockfish subprocess (evaluate each position)
  → Claude Haiku (explain interesting moves in natural language)
  → Response JSON (move + eval + explanation)

Everything synchronous for moves under 30 (fast analysis). Temporal workflow for full game analysis > 30 moves (avoids HTTP timeout).

Stockfish integration in Go

Stockfish is a UCI (Universal Chess Interface) engine — it communicates over stdin/stdout. I manage it as a persistent subprocess per analysis session:

type StockfishEngine struct {
    cmd    *exec.Cmd
    stdin  io.WriteCloser
    stdout *bufio.Scanner
    mu     sync.Mutex
}

func NewEngine(skillLevel int) (*StockfishEngine, error) {
    cmd := exec.Command("stockfish")
    stdin, _ := cmd.StdinPipe()
    stdout, _ := cmd.StdoutPipe()

    e := &StockfishEngine{
        cmd:    cmd,
        stdin:  stdin,
        stdout: bufio.NewScanner(stdout),
    }
    cmd.Start()

    // Initialize UCI mode
    e.send("uci")
    e.waitFor("uciok")
    e.send(fmt.Sprintf("setoption name Skill Level value %d", skillLevel))
    e.send("isready")
    e.waitFor("readyok")
    return e, nil
}

func (e *StockfishEngine) Analyze(fen string, moveTime int) MoveAnalysis {
    e.mu.Lock()
    defer e.mu.Unlock()

    e.send(fmt.Sprintf("position fen %s", fen))
    e.send(fmt.Sprintf("go movetime %d", moveTime))

    var bestMove string
    var evaluation float64
    for e.stdout.Scan() {
        line := e.stdout.Text()
        if strings.HasPrefix(line, "bestmove") {
            bestMove = strings.Fields(line)[1]
            break
        }
        if strings.Contains(line, "score cp") {
            // parse centipawn evaluation
            parts := strings.Fields(line)
            for i, p := range parts {
                if p == "cp" {
                    v, _ := strconv.ParseFloat(parts[i+1], 64)
                    evaluation = v / 100.0
                }
            }
        }
    }
    return MoveAnalysis{BestMove: bestMove, Evaluation: evaluation}
}

One engine instance per request; pooled for performance. Stockfish is stateless between positions, so pooling is safe with mutex locking.

Identifying interesting moves for LLM explanation

Explaining every move is expensive and useless. I identify "interesting" moves:

func isInteresting(prev, curr MoveAnalysis) bool {
    delta := math.Abs(curr.Evaluation - prev.Evaluation)
    return delta > 0.5 ||          // significant eval shift
           curr.IsMate ||           // checkmate sequence found
           curr.Depth >= 20 ||      // deep tactical line
           curr.BestMove != curr.PlayedMove // mistake or missed tactic
}

Only interesting moves get LLM explanation. At typical club games, ~30% of moves qualify — cutting LLM calls by 70%.

LLM prompt for chess explanation

const chessPrompt = `You are a chess coach explaining a move to a club player (ELO 1200-1600).

Position (FEN): %s
Move played: %s
Engine evaluation before: %+.2f
Engine evaluation after:  %+.2f
Engine's best move: %s

Explain in 2-3 sentences:
1. Why the played move is good or bad
2. What the key tactical/strategic idea is
3. If a mistake, what should have been played and why

Be concrete. Use piece names, not coordinates. No jargon beyond "fork", "pin", "discovered check".`

Claude Haiku is ideal here — fast (< 1s), cheap ($0.00025/1k tokens), and the explanation quality for simple chess commentary is indistinguishable from Sonnet.

Deployment on AWS ECS

ECS Fargate cluster
├── chessgoddess-api (Go, 512 CPU, 1GB RAM)
│   └── Stockfish binary bundled in Docker image
├── chessgoddess-worker (Go, Temporal worker for long games)
└── chessgoddess-frontend (Next.js, Cloudflare Pages)

RDS PostgreSQL: game storage, user accounts, analysis history

The Docker image bundles the Stockfish binary compiled for Linux x86-64:

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o api ./cmd/api

FROM ubuntu:22.04
RUN apt-get install -y stockfish
COPY --from=builder /app/api /usr/local/bin/api
CMD ["/usr/local/bin/api"]

apt-get install -y stockfish installs Stockfish 15 — good enough for club-level analysis. For master-level, compile Stockfish 16+ with NNUE support.

FAQ

What is ChessGoddess? ChessGoddess is a chess analysis tool that combines Stockfish engine evaluation with LLM natural-language explanations, helping club players understand their mistakes and improve.

What chess engine does ChessGoddess use? Stockfish — the world's strongest open-source chess engine. ChessGoddess uses Stockfish 15 (Ubuntu package) for standard analysis.

How does ChessGoddess explain moves? It identifies moves where the position evaluation changes significantly (mistakes, missed tactics, brilliant moves) and sends those positions to Claude Haiku for natural-language explanation.

What file formats does ChessGoddess accept? PGN (Portable Game Notation) — the standard format exported by Chess.com, Lichess, and most chess platforms.

Is the source code open? Yes — the ChessGoddess backend and analysis logic are open source on GitHub at shihabshahrier/chessgoddess.


Written by Shihab Shahriar Antor — AI Engineer & Founder of Shahriar Labs. See also: Microservices as One Engineer · LLM Cost Optimization: Cut AI API Bills 10x.