How Bytebase ships campaign changes in minutes, not days.
Bytebase is an open-source database DevSecOps platform — schema migration, SQL review, and access management for engineering teams. Thousands of teams rely on it to ship database changes through review instead of ad-hoc scripts, and the company reaches most of them the same way they found the product: search. So Bytebase moved its Google Ads account into Adjar config and put Claude Code to work — proposing campaign changes as reviewable diffs that Ryan Lockhart, its growth team of one, reviews and applies without digging through the ad console.
- Website
- bytebase.com
- Industry
- Developer tools
- Ad platform
- Google Ads
- AI agent
- Claude Code
Faster velocity tuning and applying campaign changes.
Cost saving from an optimized ads strategy — same performance.
All campaigns codified as version-controlled, agent-readable text.
The challenge
Bytebase runs paid search with a growth team of one. Ryan Lockhart owns every campaign, keyword, and creative — alongside the rest of the growth function — while a steady stream of product launches keeps the ads needing updates. Every change meant a session in the Google Ads console, clicking through campaigns, ad groups, and assets one screen at a time.
Most of the company already leans heavily on AI agents — coding, customer support, and sales projections all run with an agent in the loop. Ads were the exception: the console is built for human clicks, so an agent couldn't help. Changes queued up behind whatever else demanded Ryan's week, and a round of campaign tuning could stretch across days.
The solution
With Adjar, Bytebase exported its entire Google Ads account into declarative config and checked it into the website repo. A root file composes the account from focused pieces: shared sitelinks and callouts in one file, conversion actions with their assigned values in another, and one file per campaign — budget, bid strategy, negative keywords, ad groups, and responsive search ads down to every headline, description, and UTM-tagged landing URL.
.claude/skills/ads/
├── SKILL.md # teaches the agent the workflow
├── config/
│ ├── google.toml # account root — composes the rest
│ └── google/
│ ├── assets.toml # shared sitelinks & callouts
│ ├── conversions.toml # conversion actions & values
│ └── campaigns/
│ ├── db-change-mgmt.toml
│ ├── db-change-mgmt-jp.toml
│ └── db-access-control.toml
└── reports/ # perf pulled from Google Ads daily
├── 2026-04.google.md # totals, daily, conversions by keyword
├── 2026-05.google.md
└── 2026-06.google.mdInside a campaign file, the whole funnel is explicit — spend caps, targeting, negative keywords, and creative — in plain TOML an agent can read and edit:
# config/google/campaigns/db-access-control.toml [[campaigns]] name = "DB Access Control | SEARCH | 2026-Q2" status = "ENABLED" channel_type = "SEARCH" bid_strategy = "TARGET_SPEND" max_cpc_ceiling_usd = 10 daily_budget_usd = 200 sitelinks = [ "sl-pricing", "sl-data-masking", "sl-audit-logging" ] [campaigns.targeting] include_geos = [ "US" ] include_languages = [ "en" ] include_devices = [ "DESKTOP" ] [[campaigns.negative_keywords]] text = "cybersecurity" match_type = "BROAD" # … 27 more negative keywords [[campaigns.ad_groups]] name = "JIT access keywords" status = "ENABLED" [[campaigns.ad_groups.keywords]] text = "just in time database access" match_type = "PHRASE" [[campaigns.ad_groups.ads]] name = "Just-in-Time DB Access" type = "RESPONSIVE_SEARCH_AD" final_urls = [ "https://www.bytebase.com/database-access-control/?utm_…" ] [[campaigns.ad_groups.ads.headlines]] text = "Just-in-Time DB Access" [[campaigns.ad_groups.ads.descriptions]] text = "Grant just-in-time, auto-expiring database access with approval workflows & audit." # … 3 more ad groups: Access Control, Data masking, CyberArk
The directory doubles as a Claude Code Skill. A SKILL.md at its root teaches the agent the workflow — where the config lives, how to propose and apply a change, what the guardrails are — so every session starts with full context instead of a prompt-engineering exercise. Ryan invokes the skill and asks.
# bootstrap to file adjar import --account <id> -o config/google.toml # diff TOML vs platform; print plan (read-only) adjar plan --config config/google.toml # fresh diff → confirm → execute → write ids back adjar apply --config config/google.toml # execute a previously-reviewed plan file adjar apply --config config/google.toml -f plans/2026-06-04.md
Now the workflow looks like the rest of the company's agent-driven work. Ryan tells Claude Code what he wants in plain language: launch a campaign for a new feature, rebalance budgets, tighten match types, add a negative keyword that's burning spend. The agent edits the config, Adjar turns the diff into a reviewable plan, flags anything that would reset learning, and applies it once he approves.
We define all our ads in a config and let Claude Code work with it — instead of digging through the ad console. Campaign changes and tuning that took days now ship in minutes.
Ryan Lockhart
Lead Growth Marketer, Bytebase
Adjar closes the loop with reporting. Every day, Ryan pulls fresh Google Ads performance into the repo as plain markdown — one file per month, with account totals, per-campaign numbers, a daily breakdown, and which keyword drove each conversion. Because the reports sit next to the config, Claude Code reads both: it spots the keyword that spends without converting, the campaign whose CTR is sliding, the search terms worth promoting to exact match — then proposes the config change in the same session. Analysis and action, one workflow.
# reports/2026-06.google.md ## Daily breakdown | Date | Campaign | Impr. | Clicks | CTR | Avg CPC | Cost | Conv. | | ---------- | -------------------------------------- | ----: | -----: | ----: | ------: | ------: | ----: | | 2026-06-02 | DB Change Mgmt · SEARCH · 2026-Q2 | 1,204 | 38 | 3.16% | $1.84 | $69.92 | 2 | | | DB Access Control · SEARCH · 2026-Q2 | 2,871 | 52 | 1.81% | $1.42 | $73.84 | 1 | | | DB Change Mgmt · SEARCH · JP · 2026-Q2 | 310 | 6 | 1.94% | $0.96 | $5.76 | 0 | | | Subtotal | 4,385 | 96 | 2.19% | $1.56 | $149.52 | 3 | … one block per day — plus account totals, per-campaign, and conversions-by-keyword sections
In practice it looks like this: reading the report, Claude Code noticed two ad groups paying for CyberArk support queries — people looking for the product's help pages, not an alternative — and proposed the fix itself:
⏺ Adding ad-group "cyberark" negatives on the two groups that
were catching CyberArk support queries — routing that traffic
to the dedicated comparison group:
⏺ Update(config/google/campaigns/db-access-control.toml)
⎿ Added 5 lines
194 cpc_bid_usd = 0.01
195
196 [[campaigns.ad_groups.keywords]]
197 + text = "cyberark"
198 + match_type = "BROAD"
199 + negative = true
200 +
201 + [[campaigns.ad_groups.keywords]]
202 id = "331027653585"
203 text = "data access control"
204 match_type = "EXACT"The results
Campaign changes and tuning that took days now ship in minutes. The ad account is version-controlled, so every change has an author, a review, and a way back. And one person is enough: with an agent doing the heavy lifting, Ryan runs the entire paid search program solo — with the output of a full growth team.
Paid search stopped being a separate discipline with its own tooling — it became one more thing Bytebase ships through review.
Run your ads the way Bytebase does.
Book a demo — we’ll get your ad account into config and your agents working.