CLAUDE.md 8.8 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

SmartBotic is a workflow automation and execution platform with a microservices architecture. The backend is C++20 with gRPC communication, and the frontend is React/TypeScript.

Build Commands

C++ Backend

The existing build/ directory is configured with Ninja, not Make. Running make in it prints "Nothing to be done" and builds nothing at all - it does not fail, so an edit looks built when it has not been. Always build through cmake, which uses whichever generator the directory was configured with:

# Everything
cmake --build build -j8

# One target - much faster when only one service changed
cmake --build build --target smartbotic-webserver -j8
cmake --build build --target smartbotic-runner -j8

Configuring a build directory from scratch:

# Debug
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug

# Release
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release

# With Address Sanitizer
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON

After building a service, restart it to actually run the new binary. Everything runs on zeus, which is Void Linux and uses runit, not systemd:

sudo sv restart smartbotic-webserver smartbotic-runner
sudo sv status  smartbotic-webserver smartbotic-runner
tail -f /var/log/smartbotic-webserver/current
tail -f /var/log/smartbotic-runner/current

There is no systemctl on zeus at all - it answers "command not found", which reads like a typo rather than the wrong init system. The systemd/user/*.service units in this repo are leftovers from when the services ran on mulan.

Tests

./scripts/run-node-tests.sh      # every node, through a running webserver + runner
./scripts/run-cpp-tests.sh       # pure C++ logic, compiled in the build image

Node tests need the services up. When the runner and webserver are on different hosts, a case whose NODE fetches the API needs SMARTBOTIC_RUNNER_API set to an address the runner can reach - see the {{API}} note under Relocating a service.

Frontend (webui/)

cd webui
npm install
npm run dev      # Dev server (port 3000, proxies to localhost:8090)
npm run build    # Production build
npm run lint     # ESLint

Running Services

The database is not built from this repo - it is the standalone upstream smartbotic-database daemon. It runs on zeus as the Docker container smartbotic-db (moved there 2026-08-09; it and the SD.cpp REST API could not share mulan's memory), so it is neither an APT package nor a system service on the host any more. It is already up; the in-repo services connect to it:

# 1. Database daemon - Docker container `smartbotic-db` on 9004. Not from this repo.
docker ps --filter name=smartbotic-db

# 2. WebServer (HTTP 8090, WebSocket 8091, gRPC 9012 + 9013)
# 3. Runner (gRPC 9011)
sudo sv status smartbotic-webserver smartbotic-runner

Relocating a service

No service may assume another is on the same host. Every address a service needs is an env override, and both runit run scripts (/etc/sv/smartbotic-*/run) state all of them explicitly even where the value is the local default - so moving a service to another machine for load balancing or failover is an edit of one file, not a hunt through the code.

variable service what breaks when it is wrong
DATABASE_ADDRESS both no persistence at all; fails loudly
WEBSERVER_ADDRESS runner the runner never registers
NODE_SYNC_ADDRESS runner no node updates reach it
CREDENTIAL_SERVICE_ADDRESS runner credentials and workflow-control fail
ADVERTISE_ADDRESS runner fails silently - the runner registers as localhost:<port>, reports online, and every dispatch goes to the webserver's own host
NODES_PATH runner no built-in nodes
WEBUI_PATH webserver the UI 404s, the API still works

config/*.json is tracked in git and its defaults are all localhost, so it must not be edited per host. config/*.local.json is gitignored but nothing reads it - the mechanism is ${VAR:default} substitution.

Tests must not assume co-location either. A case whose node fetches the API writes {{API}}, which the harness substitutes from SMARTBOTIC_RUNNER_API. Three cases hardcoded http://localhost:8090 and broke the day the runner moved.

SD.cpp stays on mulan (https://mulan:8077) because that is where the GPU is. zeus needs its self-signed certificate in two stores - the trusted-certificate store for host mulan (for the runner) and the system CA store (for the test harness's Python probe).

Both the database address and the project namespace are configurable everywhere, via config/webserver.json and config/runner.json:

Key Default Env override
database_address localhost:9004 DATABASE_ADDRESS
database_project smartbotic-automation DATABASE_PROJECT

One smartbotic-database instance is shared by several projects. database_project is the multi-tenant namespace (upstream client >= 2.3): the client transparently sends every collection as <project>:<collection>, so our data stays isolated from other services on the same daemon. Never set it to default - that is the shared back-compat namespace where projects collide.

Architecture

Two In-Repo Services + External Database

  1. WebServer Service (src/webserver/) - HTTP REST API, WebSocket for real-time updates, JWT auth, runner registry
  2. Runner Service (src/runner/) - Workflow execution engine with QuickJS for JavaScript node evaluation

Both persist through the external smartbotic-database daemon (upstream repo: ssh://git@git.smartbotics.ai:10022/fszontagh/smartbotic-database.git), consumed as the libsmartbotic-db-client-dev APT package. cmake/FindPackages.cmake locates it via find_package(smartbotic-db-client CONFIG REQUIRED) → target smartbotic::db-client. lib/storage/storage_client.{hpp,cpp} is a thin PIMPL adapter over smartbotic::database::Client, so callers never touch the upstream API directly.

Data Flow

WebUI (React) → HTTP/WebSocket → WebServer → gRPC → smartbotic-database (external)
                                     ↓
                              gRPC → Runner(s) → executes workflows

Key Directories

  • lib/ - Shared libraries (common utilities, config loader, logging, storage client)
  • proto/ - Protocol Buffer definitions for gRPC services
  • src/ - Microservice implementations
  • nodes/ - Built-in workflow node definitions (JavaScript modules)
  • webui/ - React frontend
  • config/ - Runtime JSON configuration files

Node System

Workflow nodes are JavaScript modules in nodes/ with this interface:

module.exports = {
  configSchema: { /* JSON Schema */ },
  inputSchema: { /* JSON Schema */ },
  outputSchema: { /* JSON Schema */ },
  execute: async (config, input, context) => { /* returns output */ }
}

Nodes are loaded by the Runner service and executed in QuickJS. Hot-reload is supported.

See docs/nodes.md for complete node creation guide.

See docs/node-roadmap.md for nodes worth building next.

Migrating Nodes to Database

After creating or modifying nodes in nodes/, migrate them to the running database:

# 1. Login to get JWT token
TOKEN=$(curl -s http://localhost:8090/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin"}' | jq -r '.accessToken')

# 2. Migrate nodes from filesystem to database
curl -X POST http://localhost:8090/api/v1/nodes/migrate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"nodesPath": "./nodes"}'

# 3. Verify nodes are loaded
curl -s http://localhost:8090/api/v1/nodes \
  -H "Authorization: Bearer $TOKEN" | jq '.nodes[] | "\(.id) - \(.name)"'

Runners automatically receive node updates via gRPC streaming - no restart required.

Configuration

Services load JSON configs from config/ directory:

  • webserver.json - HTTP port, JWT settings, runner load balancing, database_address
  • runner.json - Runner ID, max concurrent executions, hot reload settings, database_address

The database daemon has its own config at /etc/smartbotic-database/config.json, owned by the upstream package - not by this repo.

Environment variables can override config values using ${VAR_NAME:default} syntax.

C++ Standards

  • C++20 required (strict compliance, no extensions)
  • Compiler warnings: Wall, Wextra, Wpedantic
  • Key libraries: gRPC, Protobuf, cpp-httplib, QuickJS, spdlog, nlohmann/json, bcrypt

Frontend Stack

  • React 18.2 + TypeScript + Vite
  • State: Zustand + React Query
  • UI: TailwindCSS + Lucide icons
  • Workflow editor: ReactFlow