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 into devenv/AGENTS.md, replacing pcli/myproduct with 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

PathWhat it is
cmd/pcli/pcli.goCLI commands (cobra). Add commands here.
cmd/pcli/completion.goShell autocompletion suggestions. Mirror every CLI command here.
environment.goWiring: reads Cfg from TOML, spins up all Docker components in order.
services/svc.goTemplate for a custom Docker component (container). Copy this to add services.
products/myproduct/configurator.goProduct logic: CL node configs, secrets, jobs & contracts.
products/myproduct/*.tomlProduct config profiles (basic.toml, soak.toml).
fakes/main.goHTTP fakes for 3rd-party dependencies (separate Docker image + go.mod).
tests/myproduct/{func,perf}_test.goSystem-level functional / performance (WASP) tests.
env.tomlDefault environment config: blockchains, nodesets, services, products.
JustfileBuild recipes: just cli, just build-fakes.
dashboards/myproduct.jsonGrafana 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.

  1. Declare a *cobra.Command var with Use, Aliases, Short, and a RunE func.
  2. Register it in init() with rootCmd.AddCommand(myCmd) (or parent.AddCommand(child) for subcommands; set flags via parent.PersistentFlags() — see bsCmd/obsCmd).
  3. Always add a matching entry in cmd/pcli/completion.go:
    • top-level command → add to getCommands()
    • subcommands/args → add a case "<cmd>": in getSubCommands()
  4. just cli to 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)

  1. Copy services/svc.go to services/<name>.go. Each service = an Input struct (with Default()), an Output struct (with an out TOML tag), and a New<Name>(in) deploy func using testcontainers. Use framework.DefaultTCLabels(), framework.DefaultNetworkName, and static host ports.
  2. Add the input field to the Cfg struct in environment.go (with a toml tag).
  3. Call New<Name>(in.<Field>) inside NewEnvironment(), alongside services.NewService.
  4. Add its config block to env.toml.
  5. A deployed service must guard on in == nil || in.Out != nil to stay idempotent (see NewService).

Add a product

  1. Copy products/myproduct/products/<newproduct>/ and tests/myproduct/tests/<newproduct>/.
  2. Implement the Product interface (interface.go) in configurator.go: Load, Store, GenerateNodesConfig, GenerateNodesSecrets, ConfigureJobsAndContracts.
  3. Register it in newProduct(name) in environment.go with a new case.
  4. Add a [[products]] entry (with name, instances) in env.toml. Multiple instances → your ConfigureJobsAndContracts must handle productIdx.

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.toml entry.
  • Idempotent deploys: skip if output already set.
  • Every CLI command has a completion entry.
  • Address any // TODO comments the generator left before shipping.