ctx_write_page
ctx_write_page is the only way an agent puts a page into .contexo/. Every other
tool moves pages this one wrote: ctx_push sends them,
ctx_pull brings other people’s back, ctx_diff and
ctx_history read their commits.
It writes the file, fills in the whole ctx.page.v1 frontmatter block itself, and
regenerates index.md + tags.md so the new page is visible through ctx://index
immediately.
When the agent calls it
Section titled “When the agent calls it”The tool description tells the model which type to pick, and that choice is the
only thing that decides where the file lands:
concept— how or why something works (e.g. how Stripe trials work in this codebase)entity— a named system, service, library, product, or database (Stripe, Redis, the billing worker): its purpose, where it lives in the project, and gotchassource— the reasoning trail behind a recent decision. Usually written becausectx_pushasked for it via thePUSH_PAUSEDhandshakeanalysis— a comparison or evaluation across options
The description ends with a standing instruction the model is expected to follow on every call: “Always include reasoning_summary and an Agent Reasoning section in the body explaining what was considered and rejected.”
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
agent | string | no | Defaults to 'claude' |
author | string | no | Defaults to credentials user_name |
body | string | yes | Markdown body (no frontmatter) |
reasoning_summary | string | no | One-line distillation for the index |
related | array<string> | no | |
slug | string | yes | kebab-case identifier, e.g. 'stripe-subscription' |
sources | array<string> | no | raw session slugs cited |
tags | array<string> | no | |
type | concept | entity | source | analysis | yes |
body is markdown without frontmatter — the tool builds the frontmatter block.
Anything you put at the top of body is body text, not metadata.
Where the page lands
Section titled “Where the page lands”type + slug fully determine the path:
type |
Path under .contexo/ |
|---|---|
concept |
wiki/concepts/<slug>.md |
entity |
wiki/entities/<slug>.md |
analysis |
wiki/analyses/<slug>.md |
source |
raw/sessions/<slug>.md |
The slug is validated before anything is written: letters, digits, -, _, .
only; no /, no .., no leading - or .; 200 characters max. A slug that would
escape .contexo/ is rejected, not sanitized.
The frontmatter it produces
Section titled “The frontmatter it produces”A call with slug: "stripe-subscription", type: "concept", tags, and a
reasoning_summary writes exactly this shape:
---schema: ctx.page.v1slug: stripe-subscriptiontype: conceptauthor: devagent: claudecreated: 2026-07-28T14:02:11.123456789Zupdated: 2026-07-28T14:02:11.123456789Zparent_sha: ""sources: - 2026-07-28-stripe-subscription-reasoningrelated: - stripetags: - stripe - billingreasoning_summary: Trials are set on the subscription, not the price.---## What this covers...Things worth knowing about that block:
- There is no
titlefield. The slug is the page’s identity and its display name. Atitle:key is not part ofctx.page.v1and is dropped on the next round-trip through the parser. parent_shais always written empty. Nothing in the codebase ever fills it — the real per-page parent tracking lives in.contexo/.sync/state.jsonunderpage_shas, which is whatctx_pushsends as each file’sparent_sha.authordefaults to the logged-in user’s name (from stored credentials), falling back toanonymous.agentdefaults toclaude— passagentexplicitly from Cursor, Codex, or any other client so the attribution is honest.createdandupdatedare both set to the moment of the call, in UTC with nanosecond precision.
reasoning_summary and the Agent Reasoning section
Section titled “reasoning_summary and the Agent Reasoning section”These are the two fields that make a Contexo page worth more than a README section, and both are easy for a model to skip.
reasoning_summary is a one-line distillation. It is the description the generated
index.md prints next to the page — falling back to the body’s first sentence when it is
empty — and the summary field on every ctx://search hit. So it is what the next
agent reads before deciding whether to open the page at all. Write the conclusion, not
the topic:
Rejected Stripe Connect (negative-balance ownership); chose Billing + metered usage.The ## Agent Reasoning section belongs in body and carries what the summary can’t:
what was considered and rejected, and why. A good one is short and specifically
negative — it stops the next agent from re-litigating a settled question:
## Agent Reasoning
Considered putting `trial_end` on the price so plan changes carry the trial. Rejected:prices are shared across customers, so a per-customer trial has to live on thesubscription. Also considered a local `trials` table mirroring Stripe — rejected as asecond source of truth that drifts whenever a webhook is lost.For type: "source" pages the section layout is dictated by the template that
ctx_push or ctx_capture_session hands you: Decision,
Why this approach, Rejected alternatives, Path of inquiry, Dead-ends, Open questions,
Sources. Both templates end with the same standing instruction — redact any API keys,
tokens, passwords, or PII before writing.
Request and response
Section titled “Request and response”{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "ctx_write_page", "arguments": { "slug": "stripe-subscription", "type": "concept", "tags": ["stripe", "billing"], "related": ["stripe"], "reasoning_summary": "Trials are set on the subscription, not the price.", "body": "## What this covers\n...\n\n## Agent Reasoning\n..." } }}{ "jsonrpc": "2.0", "id": 4, "result": { "content": [{ "type": "text", "text": "Wrote wiki/concepts/stripe-subscription.md" }] }}The response is the relative path — the same path ctx_push will send and the same one
ctx_diff and ctx_history resolve slugs to.
Errors
Section titled “Errors”Failures come back as a normal result with isError: true:
| Text | Cause |
|---|---|
ctx_write_page: slug, type, body are required |
One of the three required arguments was empty |
page: invalid slug "…" |
Slug contains /, .., a leading -/., or an illegal character |
page: invalid type "…" |
type was not one of concept, entity, source, analysis |
One near-miss is not an error: Wrote <path> (warning: index regen failed: …) means
the page was written and will push fine; only index.md/tags.md regeneration failed.
CLI equivalent
Section titled “CLI equivalent”None. There is no ctx write-page command — page authoring is the agent’s job, which
is why this tool exists.
You can write the file by hand; the store only cares that it parses as ctx.page.v1
and sits in the right directory for its type. ctx push and ctx status pick up
hand-written pages on the next run, but index.md and tags.md are regenerated only
by ctx_write_page, ctx_pull, and ctx pull — so a hand-written page stays missing
from ctx://index until one of those runs.
