Skip to content

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.

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 gotchas
  • source — the reasoning trail behind a recent decision. Usually written because ctx_push asked for it via the PUSH_PAUSED handshake
  • analysis — 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.”

ParameterTypeRequiredDescription
agentstringnoDefaults to 'claude'
authorstringnoDefaults to credentials user_name
bodystringyesMarkdown body (no frontmatter)
reasoning_summarystringnoOne-line distillation for the index
relatedarray<string>no
slugstringyeskebab-case identifier, e.g. 'stripe-subscription'
sourcesarray<string>noraw session slugs cited
tagsarray<string>no
typeconcept | entity | source | analysisyes

body is markdown without frontmatter — the tool builds the frontmatter block. Anything you put at the top of body is body text, not metadata.

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.

A call with slug: "stripe-subscription", type: "concept", tags, and a reasoning_summary writes exactly this shape:

---
schema: ctx.page.v1
slug: stripe-subscription
type: concept
author: dev
agent: claude
created: 2026-07-28T14:02:11.123456789Z
updated: 2026-07-28T14:02:11.123456789Z
parent_sha: ""
sources:
- 2026-07-28-stripe-subscription-reasoning
related:
- stripe
tags:
- stripe
- billing
reasoning_summary: Trials are set on the subscription, not the price.
---
## What this covers
...

Things worth knowing about that block:

  • There is no title field. The slug is the page’s identity and its display name. A title: key is not part of ctx.page.v1 and is dropped on the next round-trip through the parser.
  • parent_sha is always written empty. Nothing in the codebase ever fills it — the real per-page parent tracking lives in .contexo/.sync/state.json under page_shas, which is what ctx_push sends as each file’s parent_sha.
  • author defaults to the logged-in user’s name (from stored credentials), falling back to anonymous. agent defaults to claude — pass agent explicitly from Cursor, Codex, or any other client so the attribution is honest.
  • created and updated are 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 the
subscription. Also considered a local `trials` table mirroring Stripe — rejected as a
second 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.

{
"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.

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.

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.