How to Contribute¶
We would love to accept your patches and contributions to this project.
Before you begin¶
Sign our Contributor License Agreement¶
Contributions to this project must be accompanied by a Contributor License Agreement (CLA). You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project.
If you or your current employer have already signed the Google CLA (even if it was for a different project), you probably don't need to do it again.
Visit https://cla.developers.google.com/ to see your current agreements or to sign a new one.
Review our Community Guidelines¶
This project follows Google's Open Source Community Guidelines.
Contribution process¶
Code Reviews¶
All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose.
Local development and testing¶
Esmeralda is a uv workspace driven by the root
Makefile. Run make help to list every target. Before opening
a pull request, run at least:
make bootstrap # preflight checks + install all workspace packages with uv
make test-all # Python unit tests (MCP services + both agents), `terraform validate` on every module and the Envoy gateway harness
Envoy for the gateway harness: 1.40.x, the version the gateway image pins
(at least 1.39: the Vertex AI routes need gcp_authn access tokens). On macOS,
brew install envoy. On Linux, copy the binary out of that image, as the
Gateway CI workflow does:
image="$(sed -n 's/^FROM //p' apps/services/envoy/Dockerfile)"
docker create --name envoy-bin "$image" >/dev/null
docker cp envoy-bin:/usr/local/bin/envoy ~/.local/bin/envoy && docker rm envoy-bin >/dev/null
The gateway harness (make test-gateway-unit) renders the Envoy config module and
runs it in a real envoy against local fakes, so it needs envoy and
terraform (or tofu) on your PATH, but no GCP access. The Gateway
workflow runs it, and validates the sandbox Terraform, on every PR and push to
main that touches the Envoy module or image, tests/gateway, the Makefile,
pyproject.toml or uv.lock. (make test-gateway is a different target: it
probes a deployed environment's routes from the test VM.)
If you changed agent or MCP code, also exercise it locally (the targets start and stop the three MCP servers on ports 8001-8003 for you):
make query AGENT=ai-coe-mortgage-specialist # AI CoE specialist (A2A) -> local MCP servers
make query AGENT=cx-mortgage-orchestrator # CX orchestrator -> specialist -> local MCP servers
If you have a deployed dev environment, make test-e2e ENV=dev checks the
specialist and then the orchestrator -> specialist path through the Agent
Gateway and the entry point, and make test-gateway ENV=dev probes every
catalog host through the entry point, including a real orchestrator query.
Working with AI coding agents¶
The repository ships instructions for AI coding agents. The root
AGENTS.md covers
the repo map, layer order, invariants, the commands that matter, testing
expectations and safety rules; nested AGENTS.md files in infrastructure/,
apps/agents/, apps/services/ and packages/esmeralda/ add local rules.
GEMINI.md and CLAUDE.md point to it. The docs site also publishes an
llms.txt index of every page
(source: docs/src/llms.txt). Keep these files accurate when you change a
convention, a command or a docs page.
Documentation¶
The documentation site (https://google.github.io/esmeralda/) is built with
MkDocs Material from
docs/src/, configured in docs/mkdocs.yml.
make docs-serve # live preview on http://127.0.0.1:8000
make docs-build # strict build into site/ (fails on broken links), same as CI
- Add a new page to the
navindocs/mkdocs.yml. - Write plain GitHub Markdown: GitHub alerts (
> [!NOTE]) and relative links to repo files (for example../../../infrastructure/...) are converted for the site at build time, so pages read well on github.com too. - The Make Targets reference is generated from the Makefile's
## ...comments: document a new target there, not in the docs. - The Docs workflow builds every PR that touches the docs and publishes
mainto GitHub Pages.
Dependencies¶
What the lockfile is. All Python dependencies (direct and transitive) of the
workspace are pinned, with hashes, in the root uv.lock. Each
pyproject.toml only declares version ranges; uv.lock records the exact
versions that were tested.
How images use it. make build-images exports each app's exact set from
uv.lock into apps/**/requirements.lock (generated, gitignored) and the
Dockerfile installs it with --require-hashes. An image therefore contains
exactly what is in uv.lock, never whatever is newest on PyPI on build day.
Never run uv lock directly. On machines that route uv through a
corporate package proxy, the proxy can hold back new releases for weeks and
would rewrite every URL in uv.lock. The Makefile sets
UV_FROZEN=1, so uv only installs what uv.lock says; to change the lock,
use make lock.
make lock sends your local pyproject.toml files and uv.lock to Cloud
Build (CI/CD project), runs uv lock there against public PyPI, streams the
log and writes the new uv.lock into your working copy. Nothing is pushed:
review the diff and commit it yourself. It needs gcloud access to the CI/CD
project.
How updates happen:
| Situation | What to do |
|---|---|
| Routine and security updates | Nothing: Dependabot opens weekly PRs (7-day cooldown, OpenTelemetry and google-* grouped) that update pyproject.toml and uv.lock. Review and merge. |
You edited a pyproject.toml (new dependency, new range) |
make lock, then commit uv.lock with your change. |
| A library must be upgraded now | make lock PKG=urllib3 (several: PKG="urllib3 google-adk==2.11.0"), then commit uv.lock. |
Guards against a stale lock:
- git pre-push hook (installed by
make bootstrap, in.githooks/): runsmake lock-checkand blocks the push ifuv.lockis out of date with thepyproject.tomlfiles. The check is offline and takes milliseconds. make test-allruns the same check.- The uv.lock check workflow runs it on every PR, for anyone without the hook.
a2a-sdk major versions are ignored by Dependabot until the A2A 1.x migration
is done.
Conventions:
- Use layer (L0 to L5), never "stage", for the infrastructure levels.
- Keep the team ownership model intact: CX orchestrators live in
apps/agents/cx-*and the CX project; reusable A2A agents live inapps/agents/ai-coe-*and the AI CoE project. - Never commit certificates or private keys (
*.pemand*.crtare gitignored). Certificates are injected at deploy time (see the Central Agent Gateway guide). - Don't hardcode project IDs in code or docs: read them from layer outputs, and
write placeholders such as
esm-<env>-governance-<sfx>in documentation. - Deploy images by digest, never by tag. Cloud Run and Agent Runtime read a
tag only when a revision is created, so a template that keeps saying
:dev-latestnever rolls out a rebuilt image. Every Layer 5 workload resolves the tag to its digest at plan time (services/*/image.tf→local.image_by_digest; thegoogle_artifact_registry_docker_imagedata source inagents/*). Copy that pattern for new workloads:make test-terraformfails if a module deploysimage = var.…directly, anddeploy-workloadsends withmake verify-images, which compares the digest each workload is running with the digest its tag points to.