Customize & deploy
Use this guide when you've run through the wizard, decided the template fits your use case, and now want to fork it into your own GitHub repo + ship a deployable image. If you're still evaluating, stay on the first-agent tutorial — you don't need any of this to run the agent locally.
Why this is a separate step
The setup wizard handles runtime customization — model, tools, persona, auth — without editing code. Everything below is structural: renaming the template throughout the codebase, bending the release pipeline to your repo, baking your fork's identity into the Docker image. Do it once per fork, not every time you tweak a setting.
The operator-fork contract
Fork identity and behavior are config/plugin-driven. If you're editing a core .py to customize, that's a missing seam — file it. A fork that adds (new files: config/, plugins/, new modules) syncs upstream with near-zero conflicts; one that edits upstream-owned core files fights every sync. The template exposes a seam for each common customization so you stay on the add-only side:
| You want to customize… | Seam (declare/add — don't edit core) |
|---|---|
| Advertised skills + card description | a2a.skills / a2a.description in config, or register_a2a_skill (#570) |
| Add / drop tools | register_tools plugin / tools.disabled (no core edit) |
Where a turn's memory lives (thread_id) | register_thread_id_resolver plugin (#571) |
| Lock outbound callbacks / peer consults | security.callback_allowlist CIDRs in config (#572) |
Outbound host allowlist for fetch_url | egress.allowed_hosts in config |
| Subagents | register_subagent plugin / subagents.* config |
| Release pipeline | RELEASE_ENABLED repo variable (no workflow edit) |
The only file a clean fork still edits is the pyproject version line.
1. Fork the template on GitHub
gh repo create protoLabsAI/my-agent \
--template protoLabsAI/protoAgent \
--public --clone
cd my-agentOr: Use this template → Create a new repository from the browser. Pick a short slug (jon, echo-agent, product-director) — it ends up as the image name, metric prefix (AGENT_NAME), and Langfuse tag.
2. Set your name — don't rename protoagent
Do NOT sed-rename protoagent across the tree. The internal protoagent identifier is the logger namespace, the ~/.protoagent data dir, the PROTOAGENT_* env prefix, and the protoagent.plugin.yaml manifest name — renaming it rewrites ~120 files and conflicts on every upstream merge, for zero functional gain. The user-facing name is data, not code:
identity.nameinconfig/langgraph-config.yaml(set by the wizard) — drives the console brand, window title, and agent card. A fork sets this once and the whole UI follows.AGENT_NAMEenv — the short slug for the Prometheus metric prefix, Langfuse tag, and the<AGENT_NAME>_API_KEYauth header.
Leave the internal protoagent identifier alone. See Fork the template for the full no-rename rationale.
3. Un-freeze the release pipeline
The release workflows are opt-in via a repo variable (so a fork enables them without editing the workflow files — upstream changes then don't conflict on re-sync). There is no github.repository == guard to swap. Just set the variable on your fork:
gh variable set RELEASE_ENABLED --body true # in your fork's repoprepare-release.yml and release.yml gate on if: vars.RELEASE_ENABLED == 'true'.
3b. Stay conformant with the fleet workspace-config standard
Every fleet-watched repo carries a shared baseline enforced by protoLabsAI/release-toolsverify-workspace-config, and checks.yml runs it on every PR — so a non-conformant fork goes red in CI. The rules that bite most often:
- Owned runners. Every workflow
runs-on:must benamespace-profile-protolabs-linux, never a GitHub-hosted label (ubuntu-*,macos-*,windows-*). Hosted runners burn metered minutes and may be disabled org-wide. Legit exceptions (cross-platform binaries, npm provenance) get an inline# workspace-config: allow-hosted-runner <reason>. .beads/issues.jsonlcommitted,.beads/beads.dbgitignored..automaker/settings.jsoncommitted; transient.automaker/dirs (features/,checkpoints/,trajectory/) gitignored.
A fresh template clone already conforms. If you add or copy a workflow, keep it on the owned runner. To check (and scaffold) at any time:
npx --yes -p @protolabsai/release-tools verify-workspace-config
npx --yes -p @protolabsai/release-tools init-workspace-config # fills gapsThe release-notes step in release.yml also delegates to the shared protoLabsAI/release-tools@v1 Action (reads GATEWAY_API_KEY + DISCORD_RELEASE_WEBHOOK from CI secrets) rather than a per-fork script — nothing to copy or maintain.
4. Declare the agent card
Your card's advertised skills and description are your agent's identity — declare them in langgraph-config.yaml, don't edit server/a2a.py (#570):
a2a:
description: "Acme Bot — turns support tickets into triaged, drafted replies."
skills:
- id: triage_ticket
name: Triage Ticket
description: Classify a support ticket and draft a reply.
tags: [support]
examples: ["triage ticket #1234"]
# Optional structured output — enforced + emitted as a typed DataPart (#476):
# result_mime: application/vnd.protolabs.triage-v1+json
# output_schema: { type: object, properties: { ... }, required: [ ... ] }A plugin can contribute card skills too, via register_a2a_skill(spec). The name and url already pick up identity.name, so the wizard-set name lands on the card. Omit the a2a: block and the template ships one free-text chat placeholder so a fresh clone stays callable.
5. (Optional) Add domain tools
See Starter tools for the full default set. To drop a core tool, list it in tools.disabled (config — no code edit). To add tools, ship a plugin (register_tools) — that's the no-fork path that survives upstream merges. Editing get_all_tools() in tools/lg_tools.py directly still works but is the legacy core-edit that conflicts on re-sync. Any tool the agent ends up with becomes a checkbox in the wizard and drawer automatically.
The memory tools are dropped automatically when middleware.knowledge: false; the scheduler tools when middleware.scheduler: false. See Schedule future work and Configuration for the toggles.
6. (Optional) Configure subagents
graph/subagents/config.py ships with one researcher. Register more SubagentConfig instances in SUBAGENT_REGISTRY and add matching fields in graph/config.py::LangGraphConfig — or ship a subagent as a plugin (register_subagent) with no core edit. The lead agent delegates via the task tool; the subagent delegation rules are built from the registry.
7. Build and ship the image
docker build -t ghcr.io/my-org/my-agent:local .
# local test — mount the config volume so wizard completions persist
docker run --rm -p 7870:7870 \
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
-v my-agent-config:/opt/protoagent/config \
ghcr.io/my-org/my-agent:localThe Dockerfile declares VOLUME /opt/<agent>/config so even without -v the wizard writes persist across container runs on the same Docker host — they live in an anonymous volume. For production, use a named volume or host mount so you can back it up.
Once the local build is happy, merge a PR to trigger the release pipeline (Deploy via GHCR).
8. Delete TEMPLATE.md
Once the checklist is done, rm TEMPLATE.md and rewrite README.md to describe your specific agent — its purpose, its skills, its operators.
Canonical reference implementation
protoLabsAI/roxy is a fork built on this template, running in production as an autonomous ProtoMaker portfolio manager. When this guide doesn't cover a specific decision, Roxy is the filled-in example — worth a skim before you invent something new.