# How we keep our content from going stale

The content-refresh agent we run on Kortix — a weekly agent that finds decaying marketing and blog pages via Search Console, refreshes the copy, stats, and internal links, and opens a PR for review.

Canonical page: https://kortix.com/use-cases/content-refresh

Most published content doesn't die all at once. A comparison page that used to
rank on page one slides to page two as a competitor updates their pricing. A
blog post cites a stat from two years ago and a reader notices. A guide links
to a feature we renamed six months back. None of it is broken enough to
trigger an alert, so it just sits there, quietly losing impressions, until
someone happens to look.

We run a content-refresh agent on Kortix that watches for that decay every
week and does something about it. It reads Search Console for pages losing
impressions and clicks, cross-references them against our content repo,
refreshes the copy, the stats, and the internal links, and opens a PR. It
never publishes anything itself — a human reviews and merges.

- **Team:** Kortix
- **Runs on:** Weekly cron
- **Connected systems:** Google Search Console · GitHub
- **Mode:** Reusable session · PR only, never publishes

## The problem

Nobody schedules time to re-read a two-year-old blog post. Content gets
written once, published, and then left alone unless it's actively broken.
Meanwhile the world underneath it keeps moving: prices change, product names
change, competitors update their own pages, and search intent drifts. Search
Console sees all of that as a slow decline in impressions and clicks per page,
but nobody is watching that report on a schedule, and even when someone is,
"this page is declining" doesn't tell you what to fix.

Doing it manually doesn't scale either. A content team can audit a handful of
pages a quarter if they're disciplined about it, but a site with hundreds of
pages generates decay faster than any manual process can keep up with, and the
same handful of high-traffic pages get all the attention while the long tail
quietly rots.

## What we built

On Kortix, a weekly cron re-prompts a **persistent session** — the same agent,
picking up where last week left off. It pulls the pages losing the most
impressions and clicks from Search Console, cross-references them against our
content repo, and rotates through them so the same three pages don't get all
the attention while the rest decay untouched. For each page it picks that
week, it refreshes the copy that's gone stale, updates any numbers or stats
that have aged out, fixes internal links that point at renamed or superseded
pages, and opens one PR per run. It never pushes to the live branch and never
merges its own work.

## How it works

### Run on a weekly reusable session

A **cron trigger** fires the agent once a week, and it re-prompts a **reusable
session** rather than starting fresh. That matters here: refreshing every
decaying page at once would be disruptive, so the agent works through a
rotation, and the only way to rotate fairly is to remember what it already
touched.

### Find what's decaying

Through a scoped **connector**, the agent reads Google Search Console for the
site: impressions and clicks per page over the trailing window compared to the
window before it. Pages with the sharpest decline, weighted by how much
traffic they still carry, become candidates.

### Cross-reference the content repo and rotate coverage

The agent checks its **ledger** — which pages it refreshed and when — before
picking this week's targets. A page that was refreshed three weeks ago drops
in priority even if it's still declining; a page that's never been touched
moves up. This is what keeps the rotation from fixating on the same few
high-traffic pages every week.

### Refresh the copy, stats, and internal links

For each page in this week's batch, the agent opens the source in
**GitHub**, updates copy that reads as dated, refreshes numbers and stats that
have gone stale, and repoints internal links that reference renamed or
retired pages. It works on an isolated branch, never on the live one.

### Open a PR and stop

The agent opens one PR per run summarizing what changed on which pages and
why, using the **`gh` CLI** authenticated with a scoped `GH_TOKEN`. It never
publishes and never merges. A human reviews the diff and decides what ships.

> **The pattern**
> A weekly cron re-prompts a **reusable session** that reads decay signals
> from Search Console, rotates coverage using its own **ledger**, and edits
> content on an isolated branch. The only output is a PR — never a push to the
> live branch.

## Guardrails

The agent edits marketing and blog copy, so what it can do without a human is
tightly bounded:

- **PR only, never publish.** The agent opens a pull request and stops. It
  never pushes to the live branch and never merges its own work.
- **Isolation.** Every run happens in its own sandbox, on its own branch. The
  live content branch is never touched directly.
- **Scoped secrets.** The `GH_TOKEN` used by the `gh` CLI is encrypted in the
  Secrets Manager and injected into the sandbox at runtime, scoped to the agents you grant them to.
- **Read-only search data.** Search Console access is read-only; the agent
  can see decay signals but cannot change anything in Search Console itself.
- **Everything is code.** The rotation ledger, the refresh rules, and the
  agent's permissions are files in the repo, changed through a reviewed
  **change request** rather than a dashboard setting.

## The outcome

- **Every week:** A new batch of decaying pages picked up and refreshed
- **PR only:** Nothing published without a human review
- **0 repeats:** Ledger-driven rotation so coverage doesn't fixate on the same pages

Pages that used to decay silently for months now get caught within weeks of
losing traction, refreshed on a rotation that reaches the whole site instead
of just the pages someone happened to remember. The agent finds the decay and
does the editing; the team still decides what goes live.
