How Bytebase ships campaign changes in minutes, not days.

Bytebase

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.

Industry
Developer tools
Ad platform
Google Ads
AI agent
Claude Code
8x

Faster velocity tuning and applying campaign changes.

32%

Cost saving from an optimized ads strategy — same performance.

1 config

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.md

Inside 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.

Book a demo