Backend services and workers
This page covers the day-to-day loop for elitea-main, elitea-llm-gateway, elitea-scheduler and the two agent workers: running against a real database, writing a migration, editing the OpenAPI spec, and testing a worker in isolation.
Run elitea-main against the compose database
Section titled “Run elitea-main against the compose database”Bring up just the infrastructure services from the standalone stack (certs, then up), then run elitea-main from source against them:
cd services/elitea-mainDATABASE_URL="postgres://elitea:elitea@localhost:${STANDALONE_PG_PORT:-15433}/elitea?sslmode=disable" \ go run ./cmd/elitea-maintask run does the same thing without a custom DATABASE_URL, against whatever the process environment already points at.
Integration tests against a real database
Section titled “Integration tests against a real database”Tests gated on ELITEA_TEST_DATABASE_URL skip themselves (with a message naming the variable) when it is unset, so task test stays green without a database and exercises the real thing once you supply one:
ELITEA_TEST_DATABASE_URL="postgres://elitea:elitea@localhost:${STANDALONE_PG_PORT:-15433}/elitea?sslmode=disable" \ go test ./migrations/... -run TestMigrationCorpusPoint it at the standalone stack’s PostgreSQL, or any disposable PostgreSQL (plain PostgreSQL: no extension is needed) — most of these tests provision and drop their own per-run database or schema, so they do not need an empty server.
Migrations
Section titled “Migrations”Migration files live under services/elitea-main/migrations/, in three independently ordered scopes: shared/, tenant/ and agentstate/ (embedded and loaded by the internal/infra/db/migrate package). A separate, single-file directory — internal/infra/db/migrations/001_initial.sql — carries only the legacy initial schema, applied first when a database doesn’t already have it. Each versioned file is named NNNN_description.sql and identified by a SHA-256 checksum of its contents — a migration is checksum-immutable once it exists: never edit an applied file, always add a new one with the next free number.
Pick a free version number
Terminal window task migrations:check -- mainReads the base branch as
originholds it now and fails if your new migration’s number is not above its highest. This catches two authors picking the same free number independently — a collision that is only visible once both branches merge.Add the file to the right scope
shared/for cross-tenant tables,tenant/for anything created once per project schema (p_<id>),agentstate/for the separate agent-state database. Mixing scopes in one file is not supported —LoadManifestrejects a nested directory or a filename that doesn’t matchNNNN_name.sql.Update the pinned head, if the test names one
TestEmbeddedHistoriesHaveExpectedHeadsininternal/infra/db/migrate/manifest_test.gopins the expected head version per scope. A new highest-numbered migration in a scope that test already covers needs the pin updated in the same change.
Editing the OpenAPI spec
Section titled “Editing the OpenAPI spec”services/elitea-main/api/openapi/v2.yaml is the single source both the Go server and the TypeScript client generate from. A change to it ripples through several checked gates, in this order:
Regenerate the Go server interface
Terminal window task openapiRuns
oapi-codegenintoservices/elitea-main/internal/api/generated/api.gen.go.ci-go.yml’s “Verify generated OpenAPI server is current” step re-runs the same command and fails the build on anygit diff.Regenerate the TypeScript client
Terminal window cd apps/elitea-web && npx orvalWrites
src/shared/api/generated/**.scripts/check-generated-client.mjsfails a stale client — see elitea-web development.Update the endpoint manifest
Add or amend the corresponding entry in
apps/elitea-web/src/shared/api/endpoints.manifest.jsonby hand — it is asserted against the generated client, not generated from the spec itself.Update the route's public/auth rule if its path changed
TestCurrentMainRoutePublicRulesMatchPinnedCatalog(services/elitea-main/internal/api/main_public_rules_test.go) pins every route pattern’s authentication rule against a fixed catalog. A renamed or added public path needs an entry there too.
The LLM gateway
Section titled “The LLM gateway”services/elitea-llm-gateway is a separate Go module (Go 1.27), deliberately outside the root go.work. The workspace modules use Go 1.26. With the default GOTOOLCHAIN=auto, a local Go 1.26 toolchain downloads Go 1.27.3 for this module; with GOTOOLCHAIN=local it refuses. Build and test it from inside its own directory, with the workspace explicitly disabled:
cd services/elitea-llm-gatewayGOWORK=off go build ./...GOWORK=off go test ./...ci-gateway.yml runs its own pipeline for this module; task test/task vet/task lint at the repository root never open it, and a green root-level run is not evidence about it.
Worker test recipe
Section titled “Worker test recipe”The agent worker is the native Rust runtime:
cd services/elitea-worker-rustcargo test --locked --all-targets --all-featuresThe toolchain is pinned in rust-toolchain.toml; rustup picks it up automatically inside the directory. ci-rust.yml runs the same command, plus cargo build --locked --release.
Add a new service
Section titled “Add a new service”- Create
services/<name>/with itsContainerfile. A Go service joins the rootgo.work(unless, like the LLM gateway, it pins its own toolchain); a Rust service is its own Cargo project. - Add its target to
docker-bake.hcl, and the service to the compose stacks that need it (deploy/docker-compose.standalone-full.ymlat least). - Add it to
publish.yml: thebuild,scanandpublish-imagematrices and the cleanup step’s image list.image-matrix.yml(scripts/ci/image-matrix.sh) fails a pull request whose copies disagree, so a new image can’t ship unscanned. - Give it a CI path: a workflow of its own, or an entry in the one that already covers its language.
- If it runs in a cluster, add it to the Helm chart (
deploy/helm/elitea) and document its settings under Deployment. - Internal calls to or from it use gRPC over mTLS with the contract in
libs/proto(seeAGENTS.md).