Contexo in the wild — six bootstrapping scenarios for ctx migrate and ctx generate
Six situations teams actually hit when they adopt Contexo on a project that already exists — a mature repo with docs, a codebase with none, an overgrown CLAUDE.md, a wiki in another repo — plus what happens after the import, when a generated scaffold meets reality and a teammate corrects an imported page.
Nobody adopts a knowledge tool on a greenfield project. You adopt it on the five-year-old service that’s confusing enough to need one — which is the exact moment the tool is emptiest, and the exact moment it’s least convincing.
Contexo’s normal loop fills up session by session: your agent works, the Stop hook captures, the push handshake distills. That’s the right steady state and a terrible first week. So there are two on-ramps that skip it: ctx migrate for knowledge that already exists, ctx generate for a codebase where none does.
Here are six situations teams hit, walked through step by step — four about getting to a non-empty knowledge base, and two about what happens next, when what you imported meets reality.
1. You’re adopting Contexo on a repo that already has docs
The problem. The service has been running for five years. There’s a docs/ tree, a pile of ADRs, a README that’s mostly accurate. All of it is invisible to your agent, which will cheerfully re-derive a decision your team already made and wrote down in 2023.
How Contexo solves it, step by step:
- Initialize.
ctx initscaffolds.contexo/and writes the MCP config. Migration needs this — without it you getmigrate: no .contexo/ here — run 'ctx init' first. - Look before you import.
ctx migrate --listscans and prints a numbered catalog, grouped by detector, and stages nothing. Each row is[id] suggested-type path ~tokensplus the first real paragraph of the file. - Pick. Either type a selection (
1,2,5-7,all) at the CLI prompt, or just tell your agent “migrate our docs into contexo” — it callsctx_migratewith no arguments and gets the same catalog back with instructions to show you the list and ask. - The agent distills. For each item you picked it reads the file, then calls
ctx_write_pagewith a slug, tags, a one-linereasoning_summary— and the best-fit type, which it may override from the actual content. The suggested type is a guess from a directory name; the agent has read the thing. - It writes provenance and pushes. One dated
sourcepage lists every import asoriginal path -> new slug, thenctx_pushwithno_distill=true— these pages were just distilled by hand, so the usual distill handshake is skipped.
docs:
[1] entity README.md ~21t
Acme payments service — billing, entitlements and the webhook fan-out.
[2] concept docs/architecture.md ~1840t
The service is split into four deployables behind a single ALB…
[3] analysis docs/adr/0004-queue-choice.md ~610t
We chose SQS over Kafka for the outbox drain. Context: two engineers…
agentfiles:
[4] concept CLAUDE.md ~430t
Always run `make lint` before committing. The billing module is…
What you get. A useful knowledge base in an afternoon instead of a month — and critically, your ADRs land as analysis pages. The why behind decisions is the part your agent never gets from reading code, and it’s the part most likely to already be written down.
2. The codebase nobody ever documented
The problem. Real service, meaningful revenue, two-line README. The people who wrote it left. There is nothing to migrate, because nothing was ever written down — the knowledge is in the code and in nobody’s head.
How Contexo solves it, step by step:
- Your agent asks before installing anything.
ctx generateneeds Graphify, an external Python tool. If it isn’t onPATH, the MCP tool returns aGENERATE_NEEDS_GRAPHIFYdirective that explicitly tells the agent not to install silently — ask permission, then re-invoke withinstall=true. Or install it yourself:uv tool install graphifyy. - Contexo maps the code. Two keyless local passes —
graphify extract --code-onlybuilds the graph,cluster-only --no-viznames the clusters — and the output is relocated into.contexo/.generate-cache/so your repo root stays clean. - The graph becomes a pick-list. Communities become candidate
conceptpages (subsystems), the most-connected symbols becomeentitypages (components), and the overview report becomes ananalysispage. Capped at 15 and 15, so a big repo doesn’t explode into hundreds of pages. - The agent verifies before it writes. This is the step that matters. Each candidate is a markdown slice citing
path:line, and the directive requires the agent to open the cited source and check the claim — preferringEXTRACTEDfacts, treatingINFERREDedges as guesses, and taking the page’s name from the real code, not from the clustering label. - Push, then clean up.
ctx_pushwithno_distill=true, thenctx_generate(done=true)deletes the manifest and the whole.generate-cache.
[2] concept slices/community-billingpipeline-0.md ~740t
Subsystem with 34 nodes (hub: BillingPipeline)
[3] concept slices/community-webhookintake-3.md ~610t
Subsystem with 28 nodes (hub: WebhookIntake)
[4] entity slices/component-billing-stripe-go-stripeclient.md ~180t
/home/dev/payments-svc/billing/stripe.go:41
What you get. A map of a codebase nobody can explain — subsystem by subsystem, component by component, each one checked against real source — plus a shared vocabulary for the parts, which is most of what onboarding actually is.
3. Your CLAUDE.md is 900 lines and only you have it
The problem. You’ve been grooming one instruction file for months. It’s genuinely good. It’s also a single untyped blob, it lives on your laptop, your teammate’s copy diverged in March, Cursor can’t read it, and nothing records why any line is in there.
How Contexo solves it, step by step:
- Scan just those files.
ctx migrate --detector=agentfilespicks upCLAUDE.md,AGENTS.mdandGEMINI.mdat the root, plus.cursor/rules/*.mdand*.mdc. Thedocsdetector deliberately skips these so they’re never swept up as generic docs. - The agent splits the blob. One 900-line file is not one page. It reads through and writes several typed pages — the billing gotcha as a
concept, the deploy runbook as its ownconcept, the “we moved off Kafka because…” note as ananalysis. - Push. Now it’s in git, versioned, and diffable section by section.
- Everyone pulls it. Your teammate runs
ctx pulland their agent has it. So does Cursor, so does anything else that speaks MCP — the knowledge stopped being one editor’s file.
What you get. The thing you’d been maintaining by hand becomes structured, shared, versioned and tool-agnostic — without you rewriting it.
4. The knowledge lives in a different repo entirely
The problem. The stuff worth importing isn’t in this project. It’s in the company eng-wiki repo, or a teammate’s Obsidian vault, or the personal notes directory you’ve been keeping for two years. Nobody opens it, and no agent has ever read it.
How Contexo solves it, step by step:
- Point at it.
--fromtakes a local directory or a git URL — anything startinghttps://,http://,git@, or ending.git. - A git URL is cloned shallow (
git clone --depth 1) into.contexo/.migrate-cache/<hash>and scanned there. That’s a dot-directory, so the page store’s walk skips it and its files can never leak into a push. - The
kbtreedetector recognizes the shape. Awiki/tree mapsconcepts/,entities/,analyses/andraw/onto Contexo’s types directly. An Obsidian vault (anything with a.obsidian/directory) imports every*.mdas aconcept. - A clone changes the flow slightly. Because there’s now something to clean up,
ctx_migratestages a manifest and returns a resume directive rather than the usual stateless catalog — precisely so the closingctx_migrate(done=true)has something to delete. - Import, then the clone disappears.
done=trueremoves the manifest and the cache together.
# a directory on this machine
ctx migrate --from ~/notes/llm-wiki
# a repository
ctx migrate --from https://github.com/acme/eng-wiki.git
What you get. Two years of notes nobody reads become context your agent uses on every task — and the borrowed copy cleans itself up rather than quietly living inside your project forever.
That’s four ways to stop being empty. The more interesting question is what happens to that imported knowledge once it’s in contact with real work.
5. The generated scaffold turns out to be half wrong
The problem. ctx generate gave you a billing-pipeline concept page. It’s roughly right — right enough to be useful, wrong enough to matter. Then you actually spend a day in that module and discover the retry path is nothing like the graph implied. A static import that can’t be corrected is just a different kind of stale doc.
How Contexo solves it, step by step:
- You just work. The Stop hook appends each turn to the local session buffer. No action from you.
- You push. The batch contains a
conceptpage and there’s a session buffer less than six hours old — so the push pauses and hands your agent the distill handshake instead of committing. - The agent writes what it learned. A
sourcepage with the decision, the why, the rejected alternatives, the dead-ends — from its own context, not from a transcript. - The push links it automatically. On the second call, Contexo appends that new source slug to the
sources:frontmatter of everyconceptandanalysispage in the batch, and commits the whole thing atomically. Yourbilling-pipelinepage now cites both the generate provenance page and the session where a human and an agent worked out how retries really behave. - You can see the correction. Because each push is a real commit,
ctx diffshows what changed section by section, and--blametells you which commit introduced which section.
What you get. The scaffold was never meant to be the answer — it was meant to be a starting point good enough to argue with. What you end up with is a page whose provenance shows the whole arc: extracted from code, verified against source, then corrected by contact with reality.
6. A teammate fixed an imported page and your agent hasn’t noticed
The problem. Bulk-imported pages are, by construction, the ones most likely to be wrong — a 2023 ADR, a scaffold, a wiki entry nobody had checked in a year. Which means they’re the ones teammates correct first. The days right after an import are exactly when your local copy is most likely to be behind, and a stale page steers decisions silently.
How Contexo solves it, step by step:
- A teammate corrects an imported page and pushes. The server’s version moves ahead of your last pull.
- Your agent opens that page over MCP (
ctx://wiki/<slug>). - Contexo compares your last-pulled SHA against the server’s current one, on the read path, before the agent sees the body.
- If they differ it prepends a drift notice — with a per-section summary of what moved, so the agent knows not just that the page is stale but where:
<DRIFT_NOTICE>
This page changed on the server since your last pull.
your version: a3f91c2
server now: 7d40e18
What changed:
~ frontmatter changed
~ ## Retry policy
+ ## Idempotency keys
Consider `ctx pull` before editing this page. If you push without
pulling, the server will 409 unless your local parent_sha matches.
Call ctx_diff(slug=...) for the full per-section diff.
</DRIFT_NOTICE>
ctx statusflags it too, warning how many pages drifted on the server so you can pull before acting. (--no-driftskips the check when you’re offline or in a hurry.)- And if you push anyway, the server returns a 409 and hands your agent a merge directive rather than clobbering your teammate’s correction.
What you get. A bulk import doesn’t calcify into a pile of wrong pages nobody dares touch. It gets corrected — and nobody’s agent spends a week acting on the version from before the correction.
The thread through all six
The first four scenarios are about getting to a non-empty starting point in an afternoon. The last two are the reason that’s worth doing at all: an imported page isn’t a document, it’s a starting position — one that gets verified, corrected, diffed, and pulled by everyone else’s agent.
That’s also why neither command tries to be clever. Discovery is deterministic Go: it walks directories, groups candidates, and hands your agent a numbered list. Every judgement call — what this file actually is, what to keep, what to name it, whether the code really does what the graph claims — belongs to the model that can read the thing. Contexo just makes sure the answer lands somewhere versioned, attributed, and shared.
If your project already has docs:
ctx init && ctx migrate --list
If it only has code:
ctx init && ctx generate --list
Then ask your agent to finish it. Full references for every flag and MCP argument are at docs.contexo.live, the architecture behind both is in the under-the-hood post, and it’s open source and free to start at contexo.live.
FAQ
Which one do I run — ctx migrate or ctx generate? +
If anything is written down — a docs/ tree, ADRs, a README worth reading, a CLAUDE.md, a wiki — run ctx migrate. If the repo is code and a two-line README, run ctx generate. On a project that has a little of both, run migrate first (it imports what humans already verified) and then generate for the subsystems the docs never covered.
What if the docs I'm importing are already out of date? +
Migrate preserves rather than verifies — the directive says preserve information, do not invent — so a stale ADR imports as a stale page. That's deliberate: it lands as a dated analysis page citing the original file, so the age is visible, and the first real session in that area corrects it. ctx diff and ctx history then show exactly what was corrected and when. If you want verification against real code, that's the generate path — its directive makes checking each claim against the cited source a required step.
How much of my agent's context does a bulk import burn? +
The catalog is metadata and a 200-character preview per item, capped at 100 rendered rows — so discovery costs roughly the same whether your knowledge base is 5 files or 5,000. Only the items you actually pick get read in full, by the agent, one at a time. Use ctx migrate --list, --type and --detector to narrow before you hand anything over.
Does ctx generate need an API key? +
No. Contexo runs graphify extract with --code-only, a local AST pass that needs no key. The optional --backend flag reaches only the clustering step, where an LLM produces nicer subsystem names — and the directive tells your agent to distrust those names and take the real one from the code anyway.
Can I import a huge monorepo in batches? +
Yes, and you should. --detector and --type filter the catalog and renumber it from 1, so the IDs you type always match the list in front of you. Anything already imported is flagged (already imported — will overwrite), so a second pass over the same tree is safe and obvious.