Version Control and Promote AI Agent Config - Zian AI

Version Control and Promote AI Agent Config

Export the running configuration to files, commit it unchanged, then promote by pipeline rather than by copy. Only the logical half of an agent travels: prompt, tool definitions, model and voice identifiers, eval fixtures. Credentials, phone numbers and resource UUIDs are rebound per environment. OpenAI has v1/prompts scheduled to shut down on 30 November 2026, so prompts-in-code is now the documented path.

The question people type is how do I version control my agent’s prompt and config and promote it from staging to production, and it is harder than it looks because an agent is not one artefact: a prompt, tool schemas, a voice and model selection, a phone-number binding, a credential binding and eval fixtures, each with its own rules about crossing an environment boundary. Vapi maintains a public repository for exactly this, VapiAI/gitops, described as its best practices guide on how to support git tracking for teams owning and updating resources on their Vapi Org — Apache-2.0, created 15 January 2026, a template you fork and own. LiveKit Cloud solves a narrower version with named non-production deployments; OpenAI is retiring server-side prompt objects entirely.

What is the promotable unit for an AI agent?

Call it the two-envelope rule: every key belongs either to the logical envelope, promoted verbatim, or to the binding envelope, resolved separately in each environment and never copied. A promotion that carries a value out of the binding envelope is not a deploy, it is an incident. Vapi documents the mechanism: promotion copies logical references, not physical UUIDs, and existing credentials and phone numbers use the destination org’s binding; their secret material is never copied or provisioned.

The table below is the surface you have to classify before you write a single pipeline file. Every source in the final column was read on 18 September 2026. Two rows — tool server URL and voiceId — are marked inference: Vapi documents the field, but not what happens to it on promotion, and the final column gives the reasoning so you can check it rather than take it.

Config element Envelope Promote verbatim? How the destination gets its value Stated at
System prompt / instructions Logical Yes The file itself: assistants are Markdown with YAML frontmatter, prompt below the fold gitops README, File Formats
Tool definitions (name, parameters, description) Logical Yes Copied, and pushed before assistants: Tools, Structured Outputs, Assistants, Squads, Personalities, Scenarios, Simulations, Suites, Evals gitops README, Processing Order
Tool server URL Looks logical, behaves environment-bound (inference) Copied as written on our reading — the trap server.url sits inside the tool YAML, so on our reading a staging endpoint promotes into production unchanged unless you template it Inference, not a Vapi statement. The gitops README Tools example shows server.url inside the tool file, and the README names only credentials, phone numbers and resource UUIDs as rebound per org; managed variables remain open PR #45 (24 June 2026). Vapi does not document this behaviour either way
Model and model ID Logical Yes Same provider identifier in every org gitops README, assistant frontmatter
Voice provider and voiceId Logical string, bound resolution (inference) Yes as a string; resolution inferred, so check it The identifier copies; on our reading it resolves only if the destination org credential reaches that voice Inference, not a Vapi statement. The gitops README assistant frontmatter carries voice.provider and voice.voiceId, and Credential Management rebinds provider credentials per org by name; the README does not state how a voiceId resolves on the destination
Credential IDs Binding No Files use human-readable credential names; the per-org state file maps each name to an org-specific UUID gitops README, Credential Management
Phone numbers Binding No Bound or omitted per org. A number needs a dashboard name; unnamed or duplicate-name matches are omitted with a warning gitops README, Bootstrap State Sync
Resource UUIDs Binding No Resolved through .vapi-state.<destination>.json at push time gitops README, Promoting Across Orgs
Eval and simulation fixtures Logical Yes Evals, personalities, scenarios, simulations and suites are file-backed resources gitops README, Supported Resources
Pronunciation dictionaries Not in the unit yet No Assistants reference a dictionary ID, but the dictionaries are managed outside the GitOps flow gitops issue #37, open since 15 May 2026
Runtime secrets (LiveKit Cloud) Binding, shared across deployments No All deployments of an agent share one secret set; branch on LIVEKIT_AGENT_DEPLOYMENT and prefix the keys LiveKit docs, Non-production deployments

Day one: get today’s running config into git without changing it

The first action is not to write a pipeline. It is to prove that files can describe the system you already have. Do this before you touch a single prompt.

  1. Create one org slug per environment and pull each one. In the Vapi flow: npm run setup per org, then npm run pull -- <org> --bootstrap, which refreshes the state file and the generated credential and phone-number bindings without writing resource files. Commit every .vapi-state.<org>.json; never commit .env.<org>.
  2. Pull the resources themselves, then stop. Change nothing. The point of day one is a diff of zero.
  3. Run the read-only checks. npm run validate -- <org> is a schema check with no network call; npm run audit -- <org> is a drift detector that exits 1 on any finding, which is what makes it safe in CI on day one.
  4. Commit the export as a single reviewed change with no edits mixed in. That commit is your baseline; every later diff is a real change rather than an artefact of the export.

If step 3 reports orphan files or state ghosts on an untouched export, that is the finding, and it is the correct outcome of day one: the dashboard and your files already disagree, and no pipeline built on top of that will behave.

The promotion sequence, staging to production

The documented flow uses a promotion.yml listing orgs in forward-only release order, each pipeline owning a set of resource globs. Those globs are both the copy boundary and the deletion boundary, which is why the documentation warns against **/* unless the destination org is intentionally a mirror.

  1. Declare the pipeline. List the orgs in one-way order, set the correct base URL per region, and give each pipeline the narrowest globs it owns.
  2. Choose bind or omit for credentials and phone numbers in every target org. This is the step people skip, and it is the step that decides whether production dials on a production number.
  3. Run the plan, not the apply. npm run promote -- --pipeline release --from staging --to prod performs no file or API writes. Read every create, update and delete before you add --apply.
  4. Apply from a reviewed branch. The merged promotion.yml plus the resource diff is the reviewed plan, which is why the bundled GitHub Actions workflow passes --allow-new-files deliberately: the pull request already names the pipeline and limits which files may become new resources.
  5. Verify on the destination before you call it done (next section).

The LiveKit Cloud equivalent is shorter, because the deployable unit is a container image rather than a resource graph: lk agent deploy --deployment staging, dispatch to it, then lk agent promote --deployment staging, which promotes the staging image to production with no rebuild. Two constraints bind first. Non-production deployments need the Ship plan or higher (documented quota: 0 on Build, 2 per agent on Ship, 5 on Scale), and they need livekit-agents 1.6.0 or later in Python, or the Node.js agents package 1.7.1 or later. LiveKit documents that earlier versions register the worker as production instead, so dispatches never reach the staging deployment and that worker serves production traffic.

How do I know the promotion actually worked?

The finish state is not a zero exit code from the apply. It is the empty plan: re-run the promotion in plan mode immediately after applying it, and it must report no creates, no updates and no deletes. Anything else means the destination did not converge on what the source describes, and you have found that out before a caller does.

# Acceptance check Command (Vapi GitOps flow) Pass condition A failure means
1 Schema is valid offline npm run validate -- prod No errors, no network call made A YAML shape error. Stop before touching the API
2 No drift on the destination npm run audit -- prod Exit code 0 Orphan local files, state ghosts, UUID collisions or content-identical clusters
3 The plan is empty npm run promote -- --pipeline release --from staging --to prod (no --apply) Zero creates, updates and deletes The two environments have not converged
4 A rollback point exists npm run rollback -- prod --list A snapshot timestamped after your apply. One is written before every push and apply You have no local restore point for this deploy
5 The agent still behaves npm run sim -- prod --suite <suite> --target <assistant> The suite you promoted alongside the config passes on the destination A binding resolved to something different in production

Checks 1 to 4 are structural and cheap. Check 5 carries a caveat: a passing suite is evidence about the artefacts your harness produced, not about real calls. That argument, and the pre-launch testing ladder it points back to, belongs to our page on why voice agent evals pass but real calls fail, and is ceded here. This page asks only whether the thing you promoted is the thing now running.

Expect thinner observability on a staging copy. LiveKit documents that only the production deployment emits metrics to Agent Observability, so a non-production deployment is debugged through lk agent logs --deployment staging. A framework-owned record of which provider attempts ran in a session, including failures and fallbacks, is still an open request: livekit/agents issue #7119, opened 4 September 2026 and open as at 18 September 2026. What those traces, tool logs and outcome records should contain is set out in our guide to AI agent observability; the only question here is whether the promoted config is the config now running.

Three things that break a config round trip

A pipeline reads configuration out of a platform and writes it back; anything that makes that round trip lossy is a defect. All three below were re-read on 18 September 2026 and were open on that date.

  • A redacted secret can make an entire response undecodable. VapiAI/server-sdk-php issue #4 (opened 1 September 2026, open) reports that the PHP SDK declares credential fields as non-nullable strings while the API redacts secrets on read and returns null, so deserialising a response containing one raises a TypeError. List endpoints decode in one pass, so the report states that a single affected record fails the whole call: an exception, not a partial list. It puts the scope at 93 of 1,784 type files. Test your export against an account that actually holds a credential, not a clean fixture.
  • Part of the surface is not in the unit yet. VapiAI/gitops issue #37 (opened 15 May 2026, open) asks for first-class GitOps support for pronunciation dictionaries, with multiple local versions and an explicit activeVersion, across ElevenLabs and Cartesia. Until that lands, a lexicon fix is a manual step outside the promotion you just reviewed. Write it down as one rather than assume the pipeline carried it.
  • The CI-shaped voice regression layer is still being assembled in the open. VapiAI/example-voice-test-script issue #2 (opened 6 May 2026, open) is an external contributor asking whether a Voice Agent TestOps regression suite could be trialled against a Vapi endpoint, checking missed lead fields, wrong handoff intent and latency over threshold. Budget for building your gate rather than adopting one.

A count with its scope stated: listing every issue and pull request in VapiAI/gitops through the GitHub REST API on 18 September 2026 returns 52 items in all states, of which 6 are open — two issues (#8 and #37) and four pull requests (#5, #6, #40 and #45). That endpoint counts pull requests as issues, so the 6 is not 6 issues, and it is one repository, not a Vapi-wide total.

Do I need a promotion pipeline at all?

Often, no. The crossover is not company size; it is how many people can change the running config, and how many environments your plan entitles you to. The organisational half of a rollout — scoping the first workflow, the gates between phases, when to widen — is a separate question answered in the Discover, Deploy, Scale playbook; this section is only about the config pipeline.

Your situation Do this Do not do this
One agent, one editor, one environment Export to git on a schedule and commit: history, blame and disaster recovery, with no pipeline to maintain Build a promotion pipeline. There is nothing to promote to
Two or more people editing the same dashboard Adopt drift detection first: a read-only audit in CI that exits non-zero on divergence Start with automated applies. Detect drift before you automate writes
A second environment carries real calls Declare the pipeline, classify every key into the two envelopes, gate on the empty-plan test Copy assistants in the dashboard: that mints new UUIDs and leaves no reviewable diff
On LiveKit Build (free) plan Use a separate project as your staging boundary, or a local worker Plan around non-production deployments: the documented quota on Build is 0, Ship is 2 and Scale is 5 per agent
Regulated workload, or config that cannot leave your infrastructure Move the environment boundary to infrastructure you control Assume vendor org separation satisfies data residency. Check the contract, not the docs

What it costs to run: the Vapi flow is a repository you fork and maintain, and its main branch had last received a commit on 6 August 2026 when we checked on 18 September 2026, so you own the merge when the API moves. Expect an engineer comfortable with YAML, CI secrets and dependency ordering, and a first week spent on bindings rather than prompts. The dashboard alternative offers, in Vapi’s own comparison table, limited visibility of who changed what and rollback by manual recreation. Which prompt variant deserves promoting is a separate discipline, worked through in our post on split-testing sales scripts with AI; and secrets get their own envelope because they end up in log sinks, the subject of auditing a voice platform for leaked keys and PII.

Zian AI supports private model deployment on customer infrastructure, which changes this problem rather than removing it: the environment boundary becomes yours, the state files describe systems you control, and the binding envelope is filled from your own secret store rather than a vendor org.

Frequently asked questions

Can I promote an agent by copying it in the vendor dashboard?

You can, and it is the fastest way to lose the audit trail. A dashboard copy mints new resource UUIDs in the destination and leaves no reviewable record of what changed or who changed it. The Vapi GitOps README sets out the alternative in one line: full git history with blame, pull request review before deploy, and rollback by git revert plus push.

Which parts of an agent config must not be promoted verbatim?

Credential IDs, phone number IDs and resource UUIDs. The Vapi GitOps documentation states that promotion copies logical references rather than physical UUIDs, and that existing credentials and phone numbers use the destination org binding while their secret material is never copied or provisioned. Tool server URLs are the trap, because they sit inside the logical file and are copied exactly as written.

Is a reusable prompt object still a safe place to version a prompt?

Not for new work. OpenAI announced on 3 June 2026 that reusable prompt objects are deprecated, and the v1/prompts API is scheduled to shut down on 30 November 2026. The published guidance is to move prompt content into application code, keep each prompt as a named builder function in a small prompts module with lightweight eval fixtures, move versioning to your repo using git commits and pull request review, and manage rollout through your own config or feature flags. The OpenAI deprecations page carries both dates.

Do staging and production share secrets on LiveKit Cloud?

Yes. The LiveKit documentation on non-production deployments states that all deployments of an agent share the same secrets and that isolated per-deployment secrets are planned for a future release. The documented workaround is to read the LIVEKIT_AGENT_DEPLOYMENT environment variable at runtime and branch on it, using a prefix convention so that each deployment picks up its own key.

How do I roll back a promotion that went wrong?

There are two mechanisms and they are not interchangeable. A snapshot restore rolls back one org from a local pre-deploy snapshot, which is written before every push and apply. A promotion rollback is a new auditable change: revert the source configuration commit, then run the same forward promotion again, which keeps the destination inside the scoped mirror boundary of the pipeline.

Where to go next

If you would rather the promotion story, the bindings and the private deployment path were handled as one system than assembled from a template repository, Apply For Partnership.

Related Blogs

Related from Zian AI