Operations
Deployment
From a working local integration to one that auto-syncs on every push, without breaking the first deploy.
5 min read
AgentGraft is config-as-code. That principle is great for code review and rollback, but it creates one operational wrinkle: how do you make sure the config that's running in production is the same as what's checked in? The answer is to wire agentgraft_sync into your deploy pipeline so it runs on every push, automatically, without anyone needing to remember it.
#The pre-deploy chain
On Railway, the cleanest place is the preDeployCommand in your railway.json. It runs after the build but before the new container takes over, so a sync failure blocks the deploy and the old container keeps serving uninterrupted. The same pattern works for any platform with a release-phase hook (Heroku, Fly, render.com).
{
"$schema": "https://railway.com/railway.schema.json",
"build": {
"builder": "RAILPACK",
"buildCommand": "uv sync --frozen --no-dev && python manage.py collectstatic --noinput"
},
"deploy": {
"preDeployCommand": "python manage.py migrate && python manage.py agentgraft_sync --skip-if-unconfigured",
"startCommand": "gunicorn myapp.wsgi --bind 0.0.0.0:$PORT"
}
}
#Why --skip-if-unconfigured
Without this flag, the very first deploy of a new project is broken by design: the Railway environment doesn't have any AGENTGRAFT_* env vars set yet, the sync command crashes on the missing credentials, the deploy fails, the service never starts, you can't get into the dashboard to mint credentials in the first place. Chicken and egg.
--skip-if-unconfigured makes the sync command quietly no-op on profiles that don't have credentials yet, logging a warning to the deploy log so the operator can see exactly what was skipped. The first deploy succeeds, the service comes up without a working chat widget (acceptable transitional state), the operator signs up via /accounts/signup/, copies the credentials into Railway env vars, and the next deploy syncs the config.
- 1First push: build succeeds, migrations run, sync runs and skips both profiles (no credentials), service starts, the chat widget renders as an HTML comment instead of a script tag.
- 2Sign up at
https://yourapp.com/accounts/signup/. The welcome page shows your default graft's credentials exactly once. Save them. - 3Paste the four credentials into Railway env vars:
AGENTGRAFT_GRAFT_ID,AGENTGRAFT_PUBLIC_KEY,AGENTGRAFT_API_KEY,AGENTGRAFT_WEBHOOK_SECRET. - 4Click Redeploy in Railway (no code change required). The same
preDeployCommandre-runs, the sync this time finds credentials, pushes your config, and the chat widget appears on the next page load.
#Force a re-sync without a code change
Sometimes you want to push the current config without shipping new code — most commonly when you've edited a policy in the Policy Shop and want the change to apply to your graft, or when you're rolling back a problematic config change.
- **The Redeploy button**: Railway's dashboard has a Redeploy action that re-runs the entire build + release phases against the current commit. Same
preDeployCommand, same sync. Zero git operations. - **Manual sync from your dev machine**:
AGENTGRAFT_API_BASE=https://yourapp.com python manage.py agentgraft_sync. The credentials in your local.envpush to your production graft, no deploy at all.
#Railway secrets you'll need
Beyond the AgentGraft credentials, the host app needs the standard Django production secrets. Here's the minimum set for a typical deploy:
SECRET_KEY=... # generated, never reused
DATABASE_URL=postgres://... # provided by your db addon
REDIS_URL=redis://... # provided by your redis addon
ALLOWED_HOSTS=yourapp.com
AGENTGRAFT_GRAFT_ID=018f3a7c-...
AGENTGRAFT_PUBLIC_KEY=pk_live_...
AGENTGRAFT_API_KEY=sk_live_...
AGENTGRAFT_WEBHOOK_SECRET=whsec_...
If your host app dogfoods AgentGraft against itself (rare; mostly relevant if you're running a multi-tenant platform that itself uses agents), pin AGENTGRAFT_API_BASE=https://yourapp.com so the sync POSTs to the right host. For most setups, the default https://agentgraft.com is correct and you don't set this var at all.
#Verifying a deploy
After the deploy completes, the Railway logs for the preDeploy step show exactly what synced. A healthy run looks like this:
— Profile 'default' —
Sync complete.
Tools:
added: list_datasets, get_dataset
updated: search_assets
Agents:
added: helper
updated: router
webhook_url updated
graft fields updated: identity, voice
The webhook_url updated line is the one to grep for on a brand-new graft — it confirms the SDK pushed the host app's webhook URL to the server, which is what the runtime needs to dispatch tool calls back to your code. If you're seeing 'tool not found' or 'webhook URL missing' errors in the chat, that line is what's missing — re-run the sync.