Contributing
Thanks for poking at the code. Here’s how we usually work.
Setup
You’ll want Go 1.24+ (that’s what the Docker image builds with — see go.mod). Clone the repo and work from the module root.
Before you open a PR
gofmt -l $(go list -f '{{.Dir}}' ./...) # must print nothing
go build ./...
go vet ./...
go test -race ./...
These are exactly what CI runs, so anything red here is red on the PR.
If your Go cache isn’t writable in the sandbox you’re in:
GOCACHE=$PWD/.gocache go test -race ./...
GOCACHE=$PWD/.gocache go vet ./...
GOCACHE=$PWD/.gocache go build ./...
With the server on 127.0.0.1:8088, a quick smoke check:
curl -s http://127.0.0.1:8088/health
curl -s http://127.0.0.1:8088/v1/models | jq .
Docker
docker compose up --build -d
Ngrok overlay for Cursor BYOK:
NGROK_AUTHTOKEN=... docker compose -f docker-compose.yml -f docker-compose.ngrok.yml up -d
Compose mounts auth paths and agent-lock volumes. If you change behavior, keep docker-compose.yml / docker-compose.ngrok.yml and the docs in sync.
CI and release
The Go gate lives in one reusable workflow, .github/workflows/go-checks.yml — gofmt verification (scoped to this module; .docker/pin/ is a separate one), go build ./..., go vet ./..., go test -race ./....
ci.ymlruns it on every pull request and on pushes tomain/v*tags.docker.ymlruns the same workflow as its first job, so PR checks and the release gate cannot drift apart.
docker.yml is four jobs, each blocking the next:
checks— the Go gate. Nothing is built if it fails.build-and-push— buildsghcr.io/teslashibe/open-agent-apiforlinux/amd64and pushes it, stampingBUILD_VERSION(the resolved tag),BUILD_COMMIT=github.sha, and a UTCBUILD_DATEthrough--build-arginto theinternal/buildinfoldflags.verify-provenance— runs the image by digest, waits forGET /health, and assertsstatus == "ok",build.commit == github.sha(neverunknown),build.version != "devel", and abuild.build_datematchingYYYY-MM-DDTHH:MM:SSZ. It boots withGATEWAY_PROVIDERS=codexso no credentials are needed.deploy— bumps the pin inteslashibe/k8s-control:main→ devsha-<short>; tag → prodvX.Y.Z. Flux picks it up from there. A mis-stamped or non-booting image never reaches this job.
CI never sets STRUCTURED_LIVE_REQUIRED — the live upstream test spends real Codex quota and stays opt-in. Run it yourself before cutting a release tag.
Docs
Keep website/docs/ honest against:
internal/openai/models.go— aliases and defaultsREADME.md— behavior people actually rely ondocker-compose*.yml— mounts, env defaults, ngrok overlay
If you add or rename a model alias, update the Model catalog in the same PR.
Secrets
Don’t commit auth.json, OAuth creds, GATEWAY_BEARER_SECRET, ngrok tokens, or k8s tokens. Production secrets belong in env vars or mounted Secrets — not in compose files checked into git. And think twice before binding host ~/.claude.json into a container.
Pull requests
- Keep PRs focused — one logical change when you can.
- Touch the model catalog when aliases, defaults, or
GATEWAY_PROVIDERSbehavior changes. - If you change Agent queue, tool conversion, or streaming, say how you validated it with Cursor / a tunnel.
- Match the Go style already in
internal/andcmd/.