Skip to content

ctx_evolution

Returns the page’s commits and what each one changed, in a single call.

ctx_evolution exists because the obvious agent loop is wasteful: call ctx_history, get eight shas, then call ctx_diff eight times, once per adjacent pair. That is nine round-trips and nine chances for the agent to lose the thread halfway. The server already has the git repo open, so it does the loop itself and returns one array of {commit, diff} entries.

As the first call when picking up work on a topic that has been edited more than once. The trajectory answers questions a snapshot cannot: which parts of this page are settled, which section has been rewritten three times (and is therefore contested), what was tried and removed. An agent that has read the trajectory stops re-proposing an approach the team already abandoned.

Reach for ctx_history instead when you only need “who touched this and when”, and ctx_diff when you already know the two shas you care about.

ParameterTypeRequiredDescription
blamebooleannoAnnotate each section in each commit's diff with the commit that originally introduced its heading
limitintegernoMax commits in the evolution (default 20)
slugstringyesPage slug, e.g. 'stripe-subscription'
typeconcept | entity | source | analysisnoOnly needed when the same slug exists under multiple types

limit defaults to 20 — lower than ctx_history’s 50, because every entry drags a full diff behind it.

{
"method": "tools/call",
"params": {
"name": "ctx_evolution",
"arguments": { "slug": "stripe-subscription", "limit": 5 }
}
}

A framing line followed by the raw entries array:

{
"content": [
{
"type": "text",
"text": "Evolution of wiki/concepts/stripe-subscription.md (2 commits, newest first):\n\n[ …entries… ]"
}
]
}

Each entry is a commit plus the SectionDiff against that commit’s parent for this path:

[
{
"commit": {
"sha": "b7e9f204…",
"author": "dev",
"email": "dev@example.com",
"time": "2026-07-28T09:12:44Z",
"message": "ctx push (1 pages)"
},
"diff": {
"from_sha": "a4b2c1d3…",
"to_sha": "b7e9f204…",
"frontmatter": {
"changed": [
{ "field": "updated", "from": "2026-07-20T10:04:00Z", "to": "2026-07-24T16:31:00Z" }
],
"added": [{ "field": "tags", "to": "proration" }]
},
"sections": [
{ "heading": "## Concepts", "status": "modified", "from": "…", "to": "…", "line_diff": "…" },
{ "heading": "## Webhooks", "status": "added", "to": "…" },
{ "heading": "## Agent Reasoning", "status": "unchanged" },
{ "heading": "## Open Questions", "status": "removed", "from": "…" }
]
}
},
{
"commit": { "sha": "a4b2c1d3…", "author": "dev", "…": "…" },
"diff": { "from_sha": "", "to_sha": "a4b2c1d3…", "…": "…" }
}
]

The diff inside each entry is exactly what ctx_diff would return for that pair, so everything on that page — set semantics for list frontmatter fields, the five section statuses, rename detection, line_diff — applies here unchanged.

The oldest entry diffs against nothing. The first commit of a page has no parent for that path, so the server diffs it against an empty document: from_sha is empty and every frontmatter field and every section comes back as added. That is the shape of “this is where the page came from”, and it is why ctx_evolution succeeds on a single-commit page where ctx_diff returns a 400.

The CLI makes it obvious — note the ..a4b2c1d range on the older commit:

ctx evolution stripe-subscription --show-diff
Evolution of wiki/concepts/stripe-subscription.md (2 commits, newest first):

b7e9f204  2026-07-28  dev           ctx push (1 pages)
    diff: a4b2c1d..b7e9f20

    Frontmatter
      ~ updated
          - 2026-07-20T10:04:00Z
          + 2026-07-24T16:31:00Z
      + tags: proration

    Sections
      ~ ## Concepts (2 lines changed)
      + ## Webhooks (2 lines added)
      = ## Agent Reasoning
      - ## Open Questions (1 lines removed)

a4b2c1d3  2026-07-28  dev           ctx push (1 pages)
    diff: ..a4b2c1d

    Frontmatter
      + agent: claude
      + author: dev
      + created: 2026-07-20T10:04:00Z
      + parent_sha:
      + reasoning_summary: How trials, proration and webhooks fit together in acme-api.
      + related: [webhook-retries]
      + schema: ctx.page.v1
      + slug: stripe-subscription
      + tags: [stripe billing]
      + type: concept
      + updated: 2026-07-20T10:04:00Z

    Sections
      + ## Concepts (2 lines added)
      + ## Agent Reasoning (3 lines added)
      + ## Open Questions (1 lines added)

Entries can be skipped. If a commit reports touching the path but the file is absent in that commit’s tree (a rename chain the store could not follow), the entry is dropped rather than failing the whole request. So len(entries) can be smaller than the commit count for the path.

Newest first, same as ctx_history. The default CLI rendering collapses each diff to a summary line: frontmatter when any field changed, then one marker per changed section (+ added, - removed, ~ modified, ~> renamed). Unchanged sections contribute nothing. The line is capped at four entries in total — frontmatter counts as one — with +N more standing in for the rest:

ctx evolution stripe-subscription
Evolution of wiki/concepts/stripe-subscription.md (2 commits, newest first):

b7e9f204  2026-07-28  dev           ctx push (1 pages)
    frontmatter, ~ Concepts, + Webhooks, - Open Questions
a4b2c1d3  2026-07-28  dev           ctx push (1 pages)
    frontmatter, + Concepts, + Agent Reasoning, + Open Questions

Two things make this call expensive, and both are worth being deliberate about:

  • Changed section bodies travel in full, more than once. A modified section carries from, to, and line_diff — three renderings of the same prose. (unchanged sections carry no body at all, which is the one thing keeping this affordable.) Twenty commits on a page with long sections is a lot of tokens landing in the agent’s context at once. Lower limit when you only need the recent trajectory.
  • blame: true re-walks the history per entry. Blame annotation reads up to 200 revisions of the page to find where each heading first appeared, and ctx_evolution runs that walk once per entry. On a page with 20 commits that is 20 walks. Use it when the “who introduced this section” question is the point; leave it off otherwise.

ctx_evolution: sync: page evolution (404): {"error":"path not present in repo"} — the page resolved locally but has never been pushed to this repo.

ctx_evolution: slug "…" not found under .contexo/ …, … is ambiguous …, and ctx_evolution: server not configured … behave exactly as on ctx_history: the slug is resolved against local files, the history comes from the server.

An empty result is not an error — the tool returns No evolution found for <path> as a normal text block.

  • ctx_history — timeline only, no diffs, much cheaper
  • ctx_diff — one pair of shas, and the full anatomy of a SectionDiff
  • ctx evolution — the same call from your shell, with --show-diff
  • Page evolution — the workflow this tool was built for