8.3. Templates
In one glance
- You will: Decide between forking once and maintaining a generator, then bound exactly what a derivative is allowed to change.
- You need: Nothing beyond a terminal.
- Time: about 12 minutes, concept.
Forking once versus maintaining a project generator
A fork is a one-time copy of this repository. You keep the history, replace one domain seam at a time, and keep every check green — the milestone walk that 8.7. Capstone owns. A generator, a template that emits new repositories, is a second product with its own release cadence, reviewed parameters, several tested outputs, versioned migrations, support for the repositories other people generated last quarter, and bugs that only appear in outputs you cannot see. The choice starts costing money when a second team wants this agent for their queue: for one derivative, that burden dwarfs the cost of a careful fork.
Fork once, and build a generator only when several independent agents genuinely need the same maintained structure. Pick it deliberately, with an owner’s name attached. This page supplies a parameter-to-owner map read out of the repository, the values that must never become parameters, and the acceptance run that proves an output works with the template absent.
What this repository does ship is smaller and more reusable than a generator: structured intake, forms that make a reporter supply required fields before review. .github/ISSUE_TEMPLATE/ routes bugs, features, documentation changes, and recurring freshness review through required fields and labels, config.yml disables blank public issues and points security reports at a private contact, and .github/PULL_REQUEST_TEMPLATE.md asks for What, Why, How, and a Test Plan. Copy the shape rather than the wording: collect reproducible commands, expected behavior, affected surface, source revision, and removed secrets.
Which parameters a derivative may change, and who owns them
A derivative changes what it is called and where it runs, never the contracts that make its checks mean anything. That splits the parameter surface into four groups:
- Identity: module path, executable name, ADK application name, display name, audit actor, and OTel service name.
- Model: provider, model id, and OpenAI-compatible base URL.
- Domain: the read tools, guarded writes, runtime skills, and seed the derivative owns, plus the A2A advertised address and request bounds.
- Deployment: OCI image name, Kubernetes namespace, registry, and any optional cloud coordinates.
The list looks manageable until you trace one value. Ask where the application name lives:
rg -l 'agentops-agent' agents/ | sortagents/data/sql/schema.sql
agents/go/cmd/agent/main_test.go
agents/go/config/config_test.go
agents/go/config/env.example.tmpl
agents/go/mcpserver/mcpserver.go
agents/go/memory/helper_test.go
agents/go/platformdrill/drill.go
agents/go/policy/policy.go
agents/go/state/state.go
agents/go/telemetry/telemetry.go
agents/go/tools/tools.goEleven files under agents/, and the spread matters: one application name reaches the audit schema, the MCP server identity, the policy actor, the state directory, the telemetry service name, and the tool layer, plus the tests that pin each of those. A generator that rewrites five of those eleven produces a project that boots and lies about who performed a write. Write the map first:
| Parameter | Current owners |
|---|---|
| Go module and binary | agents/go/go.mod, agents/go/cmd/agent |
| ADK and audit identity | agents/go/compose, agents/go/telemetry, agents/go/tools |
| model provider and endpoint | .env.example, agents/go/config, agents/go/model |
| A2A address and call cap | agents/go/config, agents/go/a2aserver |
| domain and runtime skills | agents/data, agents/go/domain, agents/go/tools, agents/go/memory |
| evaluation expectations | evals/*.evalset.json, evals/judge-calibration.json, evals/mise.toml |
| OCI and Kubernetes identity | mise.toml, infra/skaffold.yaml, infra/k8s |
| optional cloud coordinates | infra/gcp, the rendered GKE overlay |
A generator should rewrite exactly its declared cells, and fail loudly when the source layout grows an identity copy nobody declared.
Every cell is a path a reviewer can open, so “what does this parameter touch?” is verifiable rather than remembered.
What is never a parameter: secrets, state, instruction version
Git owns the instruction version. A mutable prompt-registry parameter looks like flexibility and is actually a way to change an agent’s behavior without a diff, a review, or a revision anyone can name — so a derivative gets a different instruction by editing the file, not by pointing at a registry.
Nothing that identifies a machine or a person is a parameter either. Template one developer’s .env and every repository the generator produces ships that person’s API key; rotating it later breaks all of them at once, and the key was in git history the whole time. So copy .env.example, never .env, keeping two things intact. The non-secret local marker OPENAI_API_KEY=local-ollama is a placeholder the OpenAI SDK requires and Ollama ignores. The Workload Identity Federation pattern lets a deployed workload borrow a cloud identity at runtime, so no key file exists to copy. Leave runtime databases, traces, evaluation results, cloud keys, and existing workload identities out of the parameter list entirely.
The properties below stay fixed across every derivative, because changing one changes what the project can honestly claim. Two matter most: separate modules keep evaluations from being self-graded, and the audited confirmed write makes “a human approved this” checkable.
- Separate Go modules, with the evaluation harness unable to import the agent it grades.
- Typed configuration and enums, with external input parsed into trusted types at the edge.
- Immutable seed on one side, writable runtime state on the other, never mixed in one file.
- Six governed remote reads, in-process confirmed writes, an idempotent audit, and a write kill switch.
- Telemetry with content capture off by default and two layers of in-process PII defence always on.
- A non-root distroless image with health checks, resource bounds, and a network policy.
- One root
misevocabulary that local terminals, hooks, and CI all call. - Source-backed includes, plus the strict page frame, link, and accessibility checks that 8.4. Documentation owns.
Coverage is the nuanced case. The Go suite enforces an 80% per-package line-coverage floor, and a generator may reproduce that gate while leaving the threshold to the new owner. It must not copy this repository’s number into a project whose owner never chose it — an inherited floor that nobody agreed to is the first thing a team disables, and then they have no floor at all.
Prove a generated project without its parent
Every generator fails the same way: outputs that only work on the machine that has the source workspace beside them. So validate in disposable directories, with no access to the template repository, for at least two materially different parameter sets.
flowchart LR
Params["reviewed parameters"] --> Render["render disposable output"]
Render --> Checks["install · format · check<br/>test · scan · docs"]
Checks --> Search["search delimiters, source identity,<br/>domain and secret residue"]
Search --> Independent["run without source workspace"]
Independent --> Pass["supported generated project"]
Diagram in words: Reviewed parameters render into a disposable checkout. The output must pass every local check, contain no unresolved delimiter and no residue of the source project’s identity, domain, or secrets, and still work with the template repository absent, before anyone calls it supported.
Concretely, for each parameter set:
- Render it, and confirm no template delimiter survived.
- Run
mise run install,format,check,test,scan, andbuild:docsfrom a clean checkout, with every Go module resolving from its committed locks. - Grep for source identity, the reference domain’s incident ids, service names, runbook slugs, personal paths, and credentials.
- Confirm the derivative ships a complete replacement seed with matching eval assets rather than an empty
agents/data.
For a fork, this page is much shorter: copy only the contribution forms you intend to maintain and go finish the capstone. For a generator, run two different outputs through that complete list, and treat publication as blocked until update and migration behavior are tested too.
What you can do now
- You can choose fork or generator, and state the maintenance cost each choice accepts.
- You can say how far
agentops-agentreaches — eleven files — and name every owner it touches. - You can list what is never a parameter: credentials, runtime state, personal paths, and the instruction version.
- You can describe the acceptance run that shows a generated project works with its parent repository absent.
Return to 8. Community when reuse no longer means copying identity and state that nobody owns.