What Contexo is
Contexo gives a project three things:
.contexo/— a directory of short markdown pages, one topic each.- An MCP server (
ctx mcp) — so your coding agent can read those pages and write new ones itself. - A server-side git repo per project — so a page you push is versioned, attributed, and available to everyone else’s agent.
The unit of sharing is a page: markdown with frontmatter, written by an agent at your request, pushed when you say so. Not a transcript, not a memory blob.
The loop
Section titled “The loop”-
Your agent works. With capture hooks installed, every turn is appended to a local buffer at
.contexo/raw/sessions/_pending/<session-id>.jsonl. No LLM call, no network — nothing leaves the machine. -
You decide something is worth sharing — “push what we worked out about billing”. The agent calls
ctx_push. -
The push pauses to distil. If the batch contains a concept or analysis page and a capture buffer from the last 6 hours exists,
ctx_pushsends nothing. It returns a<PUSH_PAUSED reason=distill_required>directive with the buffer inlined and a template to fill in: Decision, Why this approach, Rejected alternatives, Path of inquiry, Dead-ends, Open questions, Sources. -
The agent writes the source page with
ctx_write_page(type: "source"), then re-invokesctx_pushwithdistill_done: trueandsource_slug. The concept page’ssources:list is patched to point at it, and both go up in the same commit. -
The server commits. Each push is a git commit in that project’s repo, authored as your signed-in identity, and the response carries the new HEAD.
-
A teammate pulls —
ctx pull, or their agent callsctx_pullwhen they start work on the topic. -
Their agent starts informed. It reads
ctx://indexfirst, opens the pages that matter, and drills intoraw/sessions/only when a page cites one. It does not re-derive the decision, and it does not re-walk the dead-ends you already ruled out.
Notice where the human sits in that loop: at step 2, and nowhere else. Capture is local and passive; crossing the boundary into team knowledge is always explicit.
The .contexo/ tree
Section titled “The .contexo/ tree”Directory.contexo/
- config.json server URL, repo id, dashboard URL
- credentials.json written by
ctx login, mode 0600 - index.md generated — every page, grouped by type
- tags.md generated — tag → page
Directorywiki/
Directoryconcepts/
- …
Directoryentities/
- …
Directoryanalyses/
- …
Directoryraw/
Directorysessions/ source pages (
_pending/holds capture buffers)- …
Directory.sync/ sync state — the sha of your last pull, and one per page
- …
ctx init creates all of that except credentials.json (written by ctx login)
and .sync/ (written by the first push or pull). It also appends .contexo/ to
your .gitignore — when the project is a git repo. That is deliberate:
knowledge does not ride along in your project’s git history, it syncs to the
Contexo server’s git repo through ctx push / ctx pull. The two histories stay
separate, so a page can be corrected without touching a code commit.
The four page types
Section titled “The four page types”The type passed to ctx_write_page decides both the meaning and the path —
there is no way to file a page somewhere else.
| Type | Lives at | Use it for |
|---|---|---|
concept |
wiki/concepts/<slug>.md |
How or why something works in this codebase — trials, retries, the auth handshake |
entity |
wiki/entities/<slug>.md |
A named system, service, library, product or database: purpose, where it lives here, gotchas |
analysis |
wiki/analyses/<slug>.md |
A comparison or evaluation across options |
source |
raw/sessions/<slug>.md |
The decision record of a recent team decision — usually written in response to the push handshake above |
Slugs are a single clean path component: letters, digits, -, _, ., up to
200 characters; no /, no .., no leading - or .. A slug that would escape
the store is rejected before anything is written.
What a good page looks like
Section titled “What a good page looks like”This is the stripe-subscription page used in examples throughout these docs,
exactly as ctx_write_page serializes it:
---schema: ctx.page.v1slug: stripe-subscriptiontype: conceptauthor: devagent: claudecreated: 2026-07-20T10:04:00Zupdated: 2026-07-20T10:04:00Zparent_sha: ""related: - webhook-retriestags: - stripe - billingreasoning_summary: How trials, proration and webhooks fit together in acme-api.---## Concepts
Trials are created with `trial_period_days` on the subscription, not with a100%-off coupon. Proration is disabled on downgrades.
## Rationale
Considered modelling trials as 100%-off coupons so the billing code had onepath; rejected because refunds then reconcile against a zero-amount invoiceand the finance export double-counts them.
## Open Questions
Whether to prorate on upgrade-then-immediate-downgrade inside one period.Worth noticing:
- There is no
titlefield. The index derives a display title from the slug (stripe-subscription→ “Stripe Subscription”), so the slug is the name. reasoning_summaryis the index entry. It is the one line a teammate’s agent sees inindex.mdbefore deciding whether to open the page, so write it as a claim, not a topic: “Rejected Connect (negative-balance ownership); chose Billing + metered” beats “notes on billing”. With no summary the index falls back to the page’s first sentence.- Required frontmatter is small:
schema,slug,type,author,created. Everything else —agent,updated,parent_sha,sources,related,tags,reasoning_summary— is optional, and you never type any of it.ctx_write_pagefills it in. - The page is short. Concept and entity pages are meant to be a couple of KB so an agent can afford to read several of them.
The Rationale section
Section titled “The Rationale section”A page that only says “we use Stripe Billing” saves a teammate five minutes. A page that also says why Connect was rejected saves their agent from re-evaluating Connect, getting confused by the fee-split docs, and burning an afternoon arriving where you already were.
That is what ## Rationale is for: considered → rejected → because. It is
the section ctx_write_page tells the agent to always include, and it is the
reason a distilled page beats a link to a chat log.
What Contexo is not
Section titled “What Contexo is not”- Not a chat-log dump. The capture buffer is
.jsonlunderraw/sessions/_pending/; the page store only ever lists, indexes and pushes.mdfiles. The buffer is raw material for one distilled source page, and it is moved to_pending/_archive/by thedistill_done: truepush that lands that page. - Not a memory feature. Agent memory is private, implicit and unreviewable. Contexo pages are files: you read them, edit them, diff them, and decide to push them. Nothing is remembered on your behalf.
- Not automatic. There is no background sync.
ctx pushandctx pullhappen because you — or your agent, at your request — ran them. - Not global. Contexo is per project. Knowledge that isn’t about this codebase belongs somewhere else.
- Not a code-blame tool. “Which commit broke this?” is
git blame. Contexo answers “what does the team already know about this topic?”
- Install the CLI
- Quickstart — from nothing to a page your teammate’s agent can read
- Connect your agent — Claude Code, Cursor, Codex, anything else that speaks MCP
