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.
When your agent reaches for it
Section titled “When your agent reaches for it”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.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
blame | boolean | no | Annotate each section in each commit's diff with the commit that originally introduced its heading |
limit | integer | no | Max commits in the evolution (default 20) |
slug | string | yes | Page slug, e.g. 'stripe-subscription' |
type | concept | entity | source | analysis | no | Only 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.
Request and response
Section titled “Request and response”{ "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.
Reading the entries
Section titled “Reading the entries”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:
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:
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 QuestionsTwo things make this call expensive, and both are worth being deliberate about:
- Changed section bodies travel in full, more than once. A
modifiedsection carriesfrom,to, andline_diff— three renderings of the same prose. (unchangedsections 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. Lowerlimitwhen you only need the recent trajectory. blame: truere-walks the history per entry. Blame annotation reads up to 200 revisions of the page to find where each heading first appeared, andctx_evolutionruns 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.
Errors
Section titled “Errors”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.
See also
Section titled “See also”ctx_history— timeline only, no diffs, much cheaperctx_diff— one pair of shas, and the full anatomy of aSectionDiffctx evolution— the same call from your shell, with--show-diff- Page evolution — the workflow this tool was built for
