Extending a Generated Environment (AGENTS.md)
Every environment generated by ctf gen env ships an AGENTS.md file at its root that teaches
AI coding agents how the environment is structured and how to extend it following the existing
patterns.
This page reproduces that guide for reference — useful if your environment was generated before
AGENTS.md was added, or if you just want to read it here. Names below use the generator defaults
(pcli for the CLI, myproduct for the product); substitute your own if you chose different ones.
If you work with an AI agent, point it at
devenv/AGENTS.md. If that file is missing (older environment), copy the content below intodevenv/AGENTS.md, replacingpcli/myproductwith your CLI and product names.
A generated Chainlink developer environment composes Docker containers (blockchains, CL node sets,
fakes, custom services) and exposes an interactive CLI called pcli with autocompletion. Keep
changes minimal and idiomatic.
Layout
| Path | What it is |
|---|---|
cmd/pcli/pcli.go | CLI commands (cobra). Add commands here. |
cmd/pcli/completion.go | Shell autocompletion suggestions. Mirror every CLI command here. |
environment.go | Wiring: reads Cfg from TOML, spins up all Docker components in order. |
services/svc.go | Template for a custom Docker component (container). Copy this to add services. |
products/myproduct/configurator.go | Product logic: CL node configs, secrets, jobs & contracts. |
products/myproduct/*.toml | Product config profiles (basic.toml, soak.toml). |
fakes/main.go | HTTP fakes for 3rd-party dependencies (separate Docker image + go.mod). |
tests/myproduct/{func,perf}_test.go | System-level functional / performance (WASP) tests. |
env.toml | Default environment config: blockchains, nodesets, services, products. |
Justfile | Build recipes: just cli, just build-fakes. |
dashboards/myproduct.json | Grafana dashboard, auto-uploaded on up. |
Build & run
just cli # rebuild the pcli binary after editing cmd/pcli/*
pcli sh # enter the interactive shell (autocompletion)
up # spin up env (inside shell); or: pcli up
test func # run functional tests; test perf for performance
Config is selected via the CTF_CONFIGS env var, a comma-separated list of TOML files
(e.g. env.toml,products/myproduct/basic.toml). up/restart set this for you.
Add a CLI command
Commands are cobra commands in cmd/pcli/pcli.go.
- Declare a
*cobra.Commandvar withUse,Aliases,Short, and aRunEfunc. - Register it in
init()withrootCmd.AddCommand(myCmd)(orparent.AddCommand(child)for subcommands; set flags viaparent.PersistentFlags()— seebsCmd/obsCmd). - Always add a matching entry in
cmd/pcli/completion.go:- top-level command → add to
getCommands() - subcommands/args → add a
case "<cmd>":ingetSubCommands()
- top-level command → add to
just clito rebuild.
Business logic lives in the framework package (e.g. framework.ObservabilityUp(),
framework.BlockScoutUp()); the CLI is a thin wrapper. Prefer reusing framework helpers.
Wire up a new Docker component (service)
- Copy
services/svc.gotoservices/<name>.go. Each service = anInputstruct (withDefault()), anOutputstruct (with anoutTOML tag), and aNew<Name>(in)deploy func usingtestcontainers. Useframework.DefaultTCLabels(),framework.DefaultNetworkName, and static host ports. - Add the input field to the
Cfgstruct inenvironment.go(with atomltag). - Call
New<Name>(in.<Field>)insideNewEnvironment(), alongsideservices.NewService. - Add its config block to
env.toml. - A deployed service must guard on
in == nil || in.Out != nilto stay idempotent (seeNewService).
Add a product
- Copy
products/myproduct/→products/<newproduct>/andtests/myproduct/→tests/<newproduct>/. - Implement the
Productinterface (interface.go) inconfigurator.go:Load,Store,GenerateNodesConfig,GenerateNodesSecrets,ConfigureJobsAndContracts. - Register it in
newProduct(name)inenvironment.gowith a newcase. - Add a
[[products]]entry (withname,instances) inenv.toml. Multiple instances → yourConfigureJobsAndContractsmust handleproductIdx.
Add / change fakes (3rd-party HTTP)
Edit fakes/main.go (its own Docker image + go.mod), then just build-fakes.
Tests reach fakes via the fake_server output; see tests/myproduct/func_test.go.
Conventions (do not break)
- Docker components: static ports,
framework.DefaultTCLabels(),framework.DefaultNetworkName. - Config is TOML-driven; every new component/product needs a
Cfg/env.tomlentry. - Idempotent deploys: skip if output already set.
- Every CLI command has a completion entry.
- Address any
// TODOcomments the generator left before shipping.