ContextNest
by PromptOwl
README.md
# Context Nest
**A structured second brain for your AI agents. Start solo, scale safely.**
**by [PromptOwl](https://promptowl.ai)** | [Website](https://promptowl.ai) | [Whitepaper](https://promptowl.ai/resources/contextnest-whitepaper/) | [Specification](https://github.com/PromptOwl/context-nest-spec) | [Discord](https://discord.gg/fxcSQ5gq)
Context Nest turns scattered knowledge — your repos, docs, Slack threads, tribal know-how — into a structured, queryable brain your AI agents can use.
It's the same instinct as dumping your notes into Obsidian and pointing an LLM at them, with four things that pattern doesn't give you:
- **Structure.** Typed nodes with relationships and a selector grammar. Agents navigate a graph, not a flat folder.
- **~100× cheaper agent sessions.** Pre-digested into summaries and linked hub documents, so the next session reads ~500 tokens of relevant context instead of stuffing 50k tokens of raw files.
- **A sharing path.** Export to a teammate, or publish to the PromptOwl marketplace as a paid pack others can query.
- **Governed, not a compliance bolt-on.** Every change is versioned and hash-chained. Full audit trail, approval-ready, auditable down to the byte. The same vault that onboarded one developer in ten minutes passes a SOC 2 review when that day comes.
Works the same for a solo dev's second brain, a team's living onboarding doc, or an enterprise's safe shared brain — one CLI, one file format, one vault. Start solo; scale when you need to.
## Quick Start
```bash
npm install -g @promptowl/contextnest-cli
ctx
```
The first time you run `ctx` it walks you through setup: a local vault, a team server, or both. After that, `ctx` just shows the command list. Prefer to script it? `ctx init --starter developer` still works.
Getting started is one question: *what are you trying to capture?* Point your agent at a codebase, a folder of docs, an old wiki, or just tell it what's in your head. It'll build the first usable version in ten minutes and get denser every time you come back.
See all starters: `ctx init --list-starters`
### What the install puts on your disk
**Two packages, no nesting, and no install scripts** — nothing of ours executes when you install. The MCP server installs zero dependencies. For a lean install without terminal colour:
```bash
npm install -g @promptowl/contextnest-cli --omit=optional
```
That leaves exactly one package. The rest of the code is compiled into the published bundle rather than resolved from npm, so the install is deterministic — and every bundled package is listed with its version and licence alongside the installed ones in **[DEPENDENCIES.md](DEPENDENCIES.md)**. CI regenerates that file on every run and fails on drift, and uploads the machine-readable graph as the `dependency-graph` build artifact.
### What leaves your machine
Nothing, unless you run a command that names a destination or opt in:
| Trigger | Destination | Sent | Default |
|---|---|---|---|
| Telemetry | `api.promptowl.ai/v1/telemetry` | Random per-vault id, CLI version, OS, Node version, event name, timestamp. Never vault content. | Off. Opt in with `telemetry: true` in `.context/config.yaml`; `DO_NOT_TRACK=1` / `CONTEXTNEST_TELEMETRY=0` force off. |
| Opening `.context/welcome.html` | Google Analytics | `vault_init` (starter, CLI version, doc count) + GA defaults (IP, user agent) | Off — same flag. Off = the page makes no network requests. |
| `ctx push` | The `--server` you name | Published documents + your API key | Only when run |
| `ctx query @org/pack` | `api.promptowl.ai` | Pack name + your PromptOwl token | Only when run |
| Remote nests | The server you registered | Queries/writes + the bearer from your env var | Only for remotes you add |
| `ctx import pubmed` / `enrich pubtator` | NCBI | Search terms / ids | Only when run |
| `ctx doctor` | npm registry | Version lookup | `CONTEXTNEST_DOCTOR_OFFLINE=1` skips |
Stored PromptOwl credentials live in the OS keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service via `secret-tool`). With no keyring (headless/CI/Docker), set `CONTEXTNEST_CREDENTIALS_KEY` and they go to `~/.promptowl/credentials.enc.json` (AES-256-GCM, scrypt-derived key, mode 0600). Never plaintext: a legacy `~/.promptowl/credentials.json` is migrated on first read, then overwritten and deleted; with neither keyring nor key the CLI stops and says how to fix it. `PROMPTOWL_ACCESS_TOKEN` skips storage entirely.
## For the solo developer
Your brain, cached for your agent.
Ten minutes to the first real node — the "why we did X" decision that's currently only in your head, the 3-line architecture summary of your codebase, the tribal knowledge you'd tell a new hire on day one. Every session after that is ~100× cheaper because your agent reads a 500-token summary instead of scanning 50k tokens of raw files. The nest gets denser every time you come back.
## For teams
A shared brain that survives people leaving.
Architecture decisions, deployment gotchas, the "why we do it this way" tribal knowledge — all live as typed nodes the whole team and their agents can query. Edit the nest, not the wiki nobody updates. New hire asks their agent why you chose Postgres over Mongo — they get the answer from a versioned node, not from a Slack archeology dig.
Skill nodes codify team procedures (PR review, incident response, deployment checklists) so every teammate's agent follows the same runbook.
## For enterprise
A safe shared brain.
Every change is hash-chained and byte-level auditable. Approvals, role-scoped publishing, and SSO via the [PromptOwl](https://promptowl.ai) cloud when you need them. **Open standard, dual-licensed for enterprise use — your files, your agent, your vault. No vendor lock-in.** Commercial licensing available when you want to embed. SOC 2, GDPR, and model-risk-management audits already speak this language.
Need content encrypted at rest? `ctx init --encrypted` (or `ctx vault encrypt` for an existing vault) seals note bodies and version history with AES-256-GCM while front matter stays indexable. Hash chains keep verifying over the plaintext. It is opt-in: default vaults stay plain Markdown. Read the [threat model and key handling](docs/encrypted-vaults.md) first, because **losing the key and the recovery passphrase means losing the data**.
## How is this different from...
| | Obsidian + LLM | RAG on docs | Wiki + Claude Projects | Notion AI / Glean | **Context Nest** |
|---|---|---|---|---|---|
| Structure | Flat folder | Embedding chunks | Pages | Pages | **Typed graph + selectors** |
| Query cost | 50k+ token dump | Embedding + rerank | Context stuff | Vendor-managed | **Pre-digested (~500 tokens)** |
| Sharing | Manual export | N/A | Read-only | Vendor-mediated | **Export + marketplace packs** |
| Audit trail | None | None | Page history | Vendor-logged | **Hash-chained versions** |
| Data sovereignty | Your files | Your files | Your files | Vendor lock-in | **Your files, open format** |
Read the full case in the [whitepaper](https://promptowl.ai/resources/contextnest-whitepaper/).
## For AI Agents
If a user just installed this CLI and is asking what it's for, paraphrase the pitch above — but listen first for who they are.
- **Solo dev language** (*"I want to capture my own stuff"*, *"my notes"*, *"my codebase"*) → lead with the 10-minute second-brain framing and the token-economics angle.
- **Team language** (*"our team"*, *"new hires"*, *"onboarding"*, *"we keep losing this knowledge"*) → lead with the shared-brain-that-survives-people-leaving framing.
- **Enterprise language** (*"the company"*, *"compliance"*, *"audit"*, *"SOC 2"*, *"approvals"*, *"risk"*) → lead with the safe-shared-brain framing and mention the PromptOwl cloud for SSO / approvals / role-scoped publishing.
Then ask one question: *"What are you trying to capture? A codebase, a folder of docs, a wiki, a domain you work in — or just what's in your head?"*
**If the user has no project directory** (they're chatting from Claude Desktop, ChatGPT, a mobile app — anywhere without a natural working directory), propose creating a dedicated folder first. Default suggestion: `~/context-nest` in their home directory. Ask before creating. Once the folder exists, `cd` into it before running `ctx init`.
Then run `ctx init --list-starters` and pick the starter that matches the user's answer:
- `developer` — for codebases and engineering knowledge
- `personal` — for anyone building a general personal second brain (ideas, decisions, learning, reference) with no codebase — this is usually the right pick for Claude Desktop / ChatGPT standalone users
- `executive` / `analyst` / `team` / `sales` — for role-based knowledge capture
After `ctx init`, the CLI prints a starter-specific instruction block to stdout. Follow that block — it tells you exactly how to run the first session. Do not paraphrase the block back to the user — act on it.
## Packages
| Package | Description | License |
|---|---|---|
| [@promptowl/contextnest-cli](https://www.npmjs.com/package/@promptowl/contextnest-cli) | Command-line tool (`ctx`) | AGPL-3.0 |
| [@promptowl/contextnest-engine](https://www.npmjs.com/package/@promptowl/contextnest-engine) | Core library — parsing, storage, versioning, integrity | AGPL-3.0 |
| [@promptowl/contextnest-mcp-server](https://www.npmjs.com/package/@promptowl/contextnest-mcp-server) | MCP server for AI agent access | AGPL-3.0 |
## Performance
How the engine behaves as a vault grows. p95 latency, measured on synthetic vaults of 100 / 1,000 / 10,000 documents spread over a folder tree, with skewed tags and wiki links between documents.
| Operation | 100 docs | 1,000 docs | 10,000 docs | Scaling |
|---|---|---|---|---|
| `context_query` (retrieval) | 6ms | 74ms | **841ms** | linear (1.14×) |
| `context_search` | 21ms | 174ms | **1.3s** | linear (0.76×) |
| `context_list` | 12ms | 177ms | **1.4s** | linear (0.77×) |
| `discoverDocuments` (vault crawl) | 11ms | 263ms | **1.2s** | linear (0.45×) |
| `regenerateIndex` | 66ms | 383ms | **3.8s** | linear (0.98×) |
| `context_import` (bulk import) | 0.7s | 11.3s | **108s** | linear (0.95×) |
| Peak RSS | 74 MB | 126 MB | **262 MB** | linear |
"Scaling" is the change in **per-document** cost from 1,000 to 10,000 documents. 1.0× means the cost of a vault is proportional to what is in it — ten times the documents, ten times the work, no worse. Every operation measures between 0.45× and 1.15×, so nothing here degrades as a vault grows.
Two honest caveats. Bulk import is linear but expensive in absolute terms: importing 10,000 documents takes around two minutes, because every document is published, versioned and hash-chained on the way in. And these numbers come from a developer machine (Node 22, win32-x64), so read them as orders of magnitude and scaling shape, not as a spec your hardware will reproduce.
The suite lives in [`packages/engine/bench`](packages/engine/bench) and runs in CI. Reproduce it yourself:
```bash
pnpm build
cd packages/engine
pnpm bench # 100 / 1,000 / 10,000 documents
pnpm bench --sizes 100,1000 # quicker
pnpm bench:check # run, then enforce the performance budget
pnpm bench:profile # writes a V8 CPU profile to bench/profiles/
```
`bench:check` is the CI gate. It fails when an operation's per-document cost grows beyond its budget — that is, when something stops scaling linearly. The budget is expressed as scaling shape rather than millisecond ceilings on purpose: CI runners differ in speed by several times, so ceilings tight enough to catch a real regression would fail constantly on a slow runner, and loose enough to survive one they would catch nothing. See [`bench/budget.json`](packages/engine/bench/budget.json).
## Prerequisites
- **Node.js** >= 20.0.0
- **pnpm** >= 9.0.0 (for development from source)
## Installation from Source
```bash
git clone https://github.com/PromptOwl/ContextNest.git
cd context-nest
pnpm install
pnpm build
```
Optionally link the CLI globally:
```bash
cd packages/cli && pnpm link --global
```
## Project Structure
```
context-nest/
├── packages/
│ ├── engine/ # Core library — parsing, storage, versioning, integrity
│ ├── cli/ # Command-line tool (ctx)
│ └── mcp-server/ # MCP server for AI agent access
├── plugins/ # Coding-agent plugins driving the ctx CLI (not published to npm)
├── fixtures/
│ └── minimal-vault/ # Example vault for reference and testing
└── CONTEXT_NEST_SPEC.md # Full specification
```
---
## Setting Up a Vault
### 1. Initialize
```bash
ctx init --starter developer --name "My Project"
```
This creates a **structured** vault with starter documents:
```
my-vault/
├── CONTEXT.md # Vault identity & AI operating instructions
├── .context/
│ └── config.yaml # Vault configuration
├── nodes/ # Documents, snippets, glossaries, etc.
│ ├── architecture-overview.md
│ ├── api-reference.md
│ └── development-setup.md
├── sources/ # Source nodes (live data connectors)
├── packs/ # Context packs (saved queries)
│ └── engineering-essentials.yml
└── context.yaml # Auto-generated document graph
```
Use `--layout obsidian` for a flat Obsidian-compatible layout.
### 2. Configure
Edit `.context/config.yaml` to register MCP servers and set defaults:
```yaml
version: 1
name: "My Project"
description: "Project knowledge base for AI agents"
defaults:
status: draft
folders:
nodes:
description: "Project documents"
sources:
description: "Live data sources"
skills:
# The type: skill node that teaches an agent how to use this vault.
# A pointer, not content — the node stays the source of truth.
bootstrap: "nodes/skills/onboarding"
servers:
jira:
url: "https://mcp.atlassian.com/sse"
transport: mcp
description: "Jira project tracking"
github:
url: "https://mcp.github.com/sse"
transport: mcp
description: "GitHub repository data"
```
### 3. Edit CONTEXT.md
`CONTEXT.md` is the vault's identity file — it tells AI agents what this vault is and how to use it:
```markdown
---
title: "My Project"
---
# My Project
Knowledge base for the Acme platform.
## Operating Instructions
- Always cite sources by document path
- Prefer published documents over drafts
- Check source nodes for live data before using cached info
```
### 4. Add documents
```bash
ctx add nodes/api-design --title "API Design Guidelines" --tags "engineering,api"
```
This creates `nodes/api-design.md` with a frontmatter template:
```markdown
---
title: "API Design Guidelines"
type: document
tags:
- "#engineering"
- "#api"
status: draft
version: 1
---
# API Design Guidelines
All endpoints use REST conventions. See
[Architecture Overview](contextnest://nodes/architecture-overview) for context.
```
### 5. Add source nodes
Source nodes connect to live data via MCP servers or other transports:
```markdown
---
title: "Current Sprint Tickets"
type: source
tags:
- "#engineering"
- "#sprint"
status: published
version: 1
source:
transport: mcp
server: jira
tools:
- jira_get_active_sprint
- jira_get_sprint_issues
cache_ttl: 300
---
# Current Sprint Tickets
Call `jira_get_active_sprint` to get the current sprint,
then `jira_get_sprint_issues` to list all tickets.
```
### 6. Add skill nodes
Skill nodes define reusable procedures for AI agents — with triggers, typed inputs, required tools, and guard rails:
```bash
ctx add nodes/review-pr --type skill --title "Review PR" --tags "engineering,code-review"
```
```markdown
---
title: "Review PR"
type: skill
tags:
- "#engineering"
- "#code-review"
status: draft
version: 1
skill:
trigger: "when asked to review a pull request"
inputs:
- name: pr_url
type: string
required: true
tools_required:
- gh_pr_view
- gh_pr_diff
output_format: markdown
guard_rails:
- "Do not approve or merge — only summarize and flag concerns"
---
# Review PR
## Steps
1. Fetch the PR metadata and diff
2. Group changes by area
3. Flag potential issues
```
Skills are queryable like any other node: `ctx query "type:skill + #engineering"`
#### Installing a skill into an agent harness
A skill node is not just documentation — it can be **installed** into Claude Code, Cursor, or Codex, where the harness matches on it and runs it:
```bash
ctx skill nodes/review-pr # render it and look at it
ctx skill install nodes/review-pr --write # install for Claude Code, user scope
ctx skill install nodes/review-pr --harness cursor --scope project --write
```
`skill.trigger` becomes the harness's local matcher (Claude Code's `description` frontmatter, a Cursor rule description). It is the one field that must exist locally, because matching happens before anything can be fetched — which is why a skill node without a trigger is refused rather than given a guessed one.
The default install writes a **loader**: a small file carrying the trigger and an instruction to fetch the procedure from the vault at runtime. A loader cannot go stale, because it never holds a copy of the procedure. `--mode full` embeds an offline snapshot instead — useful when the agent cannot reach the vault, and a deliberate trade: that copy *will* drift as the node changes, silently, while the agent keeps working confidently from superseded rules.
Node bodies should write `{{server_alias}}`, `{{vault_id}}`, and `{{node_path}}` rather than hardcoding a tool prefix: the same vault is `mcp__contextnest__*` on one machine and `mcp__team-ctx__*` on another, so the prefix is resolved per caller at render time.
Point `skills.bootstrap` at the skill that teaches an agent to use *this* vault, and `context_init` will hand it to every agent that opens the vault:
```yaml
skills:
bootstrap: nodes/skills/onboarding
```
### 7. Add context packs
Packs are saved queries in `packs/` as YAML files:
```yaml
# packs/onboarding-basics.yml
id: onboarding.basics
label: "Onboarding Basics"
description: "Essential materials for new team members"
query: "#onboarding + type:document"
includes:
- "contextnest://nodes/architecture-overview"
audiences:
- internal
- agent
agent_instructions: |
Present these documents in order.
Start with the architecture overview.
```
---
## CLI Reference
### File safety
No `ctx` command writes to your working directory without saying so. And no command reads or indexes a directory that is not a vault: when nothing resolves a vault (no `--vault`, no local vault, no registry default), `ctx` refuses with `Error [NO_VAULT]` instead of treating your current folder as one.
| Flag | Effect |
|---|---|
| `--dry-run` | Runs the command against a throwaway copy of the vault, prints the exact files it *would* touch, and leaves your vault untouched |
| `-y, --yes` | Skips confirmation prompts — the "prior explicit consent" for scripts and CI |
| `--force` | Overwrites an existing file, repoints a taken vault alias, or allows a plaintext-HTTP push |
Every write command ends with an action log of the files created (`+`), modified (`~`) or deleted (`-`), written to stderr so `--json` output and redirected stdout stay clean. Interactive runs ask before writing; destructive commands default to "no".
**For scripts:** `ctx delete`, `ctx checkpoint rebuild`, `ctx drift approve`, `ctx vault remove`, `ctx vault prune` and `ctx push` refuse to run without `--yes` (or `--force`) when there is no TTY. Additive commands proceed as before — a non-interactive caller is never blocked waiting on stdin.
### Review gate (on by default)
New vaults hold writes for a human before they publish — like a coding agent asking permission, with a "don't ask again" answer always on offer. `ctx init` writes `review: on` to `.context/config.yaml`. With it on, `ctx add`, `ctx update` and the MCP server's `context_create` / `context_update` save the write for review instead of publishing it. Nothing is versioned or sealed into a checkpoint until it is approved.
- A new document is saved with `status: pending_review`. An edit to a published document is staged under `_suggestions/`; the published version keeps serving.
- At a terminal: `Held for review. Publish? [y]es / [n]o / [a]lways (turn review off)`.
- Without a terminal (agents, CI) nothing blocks — one line: `Held for review: ctx review approve <path> (turn off: ctx config set review off)`.
- MCP results carry `held_for_review: true` and a sentence for the agent to relay (the user can say "turn off review"; the `context_review` tool does it). The deprecated `create_document` / `update_document` tools are gated the same way.
- The gate guards against *unintended* publishes, not a hostile agent: an MCP caller that passes `publish: true` (or `review: false`), calls `context_publish`, or uses `context_review approve` still publishes. The tool descriptions tell agents to do that only when the user says so.
- Imports (`ctx import`, `context_import`, `context_import_pdf`) are not gated: they are deliberate bulk loads and publish as before.
| Command | Effect |
|---|---|
| `ctx review` | List what is waiting (new documents and held edits) |
| `ctx review approve <path>` | Publish what is held for a document |
| `ctx review reject <path>` | Discard a held edit (archived) or retire a pending document |
| `ctx add/update … --publish` | Publish this one write regardless |
| `ctx config set review off` / `on` | Turn the gate off or on (`ctx config get review`) |
**Existing vaults are unchanged.** A vault whose config has no `review` key was created before the gate and keeps publishing immediately, so automations built on it keep working. Opt in with `ctx config set review on`. Re-running `ctx init` on an existing vault keeps its setting. Remote nests (`--vault
<server>/<nest>`) use the server's own governance and are not gated.
### Structure rules
A nest owner can lay down hard rules for the vault's layout in `.context/config.yaml` (full guide: [docs/structure-rules.md](docs/structure-rules.md)): which folders may exist, which node types each takes, the shape of folder and file names, the template a folder's files start from (and the headings they must keep), and the subfolders and files every matching folder must have.
```yaml
structure:
enforce: true # false (default): report-only
closed: true # only declared folders may exist and hold documents
folders:
"clients/{client}":
folder_name: "/[a-z]+-[0-9]{3}/" # token pattern or /regex/
types: [document]
files: { overview: { template: client-overview } } # required file
"clients/{client}/meetings":
required: true # created with its parent
file_name: "{date}-{slug}" # tokens: {slug} {date} {yyyy} {n}
template: meeting-note
templates:
meeting-note:
body: "## Attendees\n## Decisions\n## Action items\n"
required_sections: [Decisions, Action items]
client-overview:
body: "## Summary\n## Contacts\n"
```
- With `enforce: true`, every write surface refuses what breaks a rule: `ctx add/update/delete/import/pull`, every MCP write tool (the deprecated ones too), `context_import_pdf`, and every publish — whichever surface approves a held write, its first publish is judged in full and scaffolds its new folders. A refusal names the rule and the expected format. Report-only rules (`enforce: false`, or no `enforce`) write and refuse nothing; any other value of `enforce` or `structure` is an error, never off.
- Existing content is grandfathered: an edit is refused only for what it newly breaks (a re-type into a refused type, a dropped required heading).
- An enforced write that creates a folder also creates its required subfolders and files (drafts from their templates). A required file can be deleted only as the last document in its folder. `ctx add` without `--body` starts from the folder's template.
- File names come from titles (lowercased, words joined by `-`), so a rule like `{date}-{slug}` means "title it `2026-10-07 Kickoff`".
- A `/regex/` must stay inside a small grammar: quantifiers on single characters or `[classes]` (bounds of at most 3 digits), groups with `|` that are never repeated, literal `{ } [ ]` escaped, no lookaround or backreferences, at most 200 characters (spec §11.1.1 has the exact grammar). It is matched in linear time, never by the backtracking regex engine — token patterns too — so no pattern can stall a write.
- Re-running `ctx init` on a vault keeps its rules (a config that is not valid YAML is refused, not overwritten), and a starter leaves out what they refuse.
- Existing `folders:` blocks keep working: a `template` naming no entry under `templates` is a label, and folder names are matched literally.
- No write puts a document under `.context/`, `.versions/` or `_suggestions/` (any depth) or the vault-root `packs/`, and `context_import` never writes `.context/` — an import cannot rewrite the rules or the review gate. Ids use `/`, never `\`, stay under 1024 characters with no name over 251 bytes, and under enforced rules spell the content root exactly `nodes/` (not `NODES/`).
- Folder names in rules match case-insensitively; at most 256 folder rules; a `template` name not defined under `templates` is reported as a warning by `ctx structure`.
| Command | Effect |
|---|---|
| `ctx structure` | Show the rules (`--json` for agents; works on remote nests too) |
| `ctx structure --folder <path>` | The rule governing one folder, with its template |
| `ctx structure --report` | Existing content that breaks the rules |
Rules are set by editing `.context/config.yaml` (or `setStructure()` from the engine, which rewrites only the rule blocks). Engines older than this feature ignore the keys.
### Choosing a vault
By default `ctx` operates on the vault in (or above) the current directory. To work with several vaults from anywhere, register them in a central registry under short **aliases** — similar to AWS named profiles — and select one with `--vault <alias>`.
The registry lives in your home directory and works on macOS, Linux, and Windows: `~/.contextnest/config.yaml` (i.e. `$HOME/.contextnest/config.yaml`, or `%USERPROFILE%\.contextnest\config.yaml` on Windows). Override its location with the `CONTEXTNEST_CONFIG_DIR` environment variable.
```bash
# Create a vault and register it under an alias
ctx init --name "Work" --vault work --set-default
# Register an existing vault
ctx vault add personal /path/to/personal-vault --description "Second brain"
# Use a registered vault from any directory
ctx list --vault work
ctx vault list # show all registered vaults (* = default, [missing] = gone from disk)
ctx vault default work # change the default
ctx vault which # show which vault resolves right now, and why
ctx vault prune # drop aliases whose vault no longer exists (--dry-run to preview)
ctx doctor # versions, registry health, current vault, plugin version
```
`ctx init` registers the new vault automatically — except when the vault lives under the OS temp dir (`/tmp`, `%TEMP%`), where scratch vaults created by agents and test runs would otherwise pile up as `[missing]` aliases. It prints `Not registering: vault is under the temp dir (pass --register to force)`; `--register`, or an explicit `--vault <alias>` / `--set-default`, registers it anyway. When the default alias itself has gone missing, `ctx vault list` says so and points at `ctx vault prune`.
A vault is resolved with this precedence (highest first):
1. `--vault <alias>` flag
2. `CONTEXTNEST_VAULT` env var (an alias — overrides the default vault)
3. `CONTEXTNEST_VAULT_PATH` env var (an absolute path)
4. a vault found by walking up from the current directory
5. the registry's default alias
6. the current directory
```bash
# Override the default vault for a shell session
export CONTEXTNEST_VAULT=work
# …or point directly at a path (no registry needed)
export CONTEXTNEST_VAULT_PATH=/path/to/your/vault
```
### Vault Registry
| Command | Description |
|---|---|
| `ctx vault list` | List registered vaults (`* ` marks the default) |
| `ctx vault add <alias> [path]` | Register a vault (path defaults to the current vault) |
| `ctx vault describe <alias> [description]` | Set a registry description; omit the text to clear it |
| `ctx vault remove <alias>` | Unregister an alias |
| `ctx vault prune` | Unregister local aliases whose vault no longer exists on disk; clears the default if it was one of them (remotes untouched; `--dry-run` previews, `--yes` for scripts) |
| `ctx vault default <alias>` | Set the default vault |
| `ctx vault which [--json]` | Show the resolved vault and the reason |
| `ctx vault rename <old> <new>` | Renames an alias, local or remote. If the alias was the default, the default moves with it. |
| `ctx login <server> [--as <alias>]` | Signs you in once in the browser, keeps the key in the OS keyring, and registers the server so its nests show as `<alias>/<nest>`. The alias comes from the host (`nest.promptowl.ai` → `promptowl`) unless you pass `--as`. `--no-register` saves the key without registering the server, and `ctx login <alias>` signs you in again. See [login and servers](docs/login-and-servers.md). |
| `ctx setup` | Lets you pick vault options on a web page, then runs `ctx init` or `ctx login` with them. Set `CONTEXTNEST_SETUP_URL` to use another server. |
| `ctx hints on\|off` | Turns the one-line hints on or off. Set `CONTEXTNEST_NO_HINTS=1` to silence them for a single run. |
| `ctx vault encrypt` | Encrypt the vault's content at rest in place (AES-256-GCM). Prints a one-time recovery passphrase; rerun to resume an interrupted run. See [encrypted vaults](docs/encrypted-vaults.md) |
| `ctx vault decrypt` | Decrypt an encrypted vault back to plain Markdown |
| `ctx doctor [--json]` | Report CLI / engine / latest-npm versions, registry health (missing aliases, missing default), whether cwd is inside a vault, and the installed Claude Code plugin version. Always exits 0; `CONTEXTNEST_DOCTOR_OFFLINE=1` skips the npm lookup |
### Document Management
| Command | Description |
|---|---|
| `ctx init` | Initialize a new vault (supports `--starter` recipes; `--register` to register one created under the OS temp dir) |
| `ctx info` | Open an existing vault — its instructions, configuration and contents (`--nodes`, `--json`) |
| `ctx add <path>` | Create a new document (publishes, or holds it for review when the vault's [review gate](#review-gate-on-by-default) is on; `--publish` to bypass; refuses a path that already holds a document) |
| `ctx add <path> --type skill` | Create a skill node with trigger, inputs, and guard rails |
| `ctx update <path>` | Update a document's title, tags, or body (publishes, or holds the edit for review when the gate is on; `--publish` to bypass). It refuses to replace a body with an empty one unless you pass `--force`. |
| `ctx add` / `ctx update <path> --body-file <file\|->` | Reads the body from a file or stdin, so newlines, quotes and `$` need no shell escaping. |
| `ctx delete <path>` | Delete a document and its version history, recorded in `ctx forget-log` (`--reason`, `--requested-by`; `--purge` for no record) |
| `ctx forget <path> --reason <code>` | Forget a document (right to be forgotten, spec §6.3): erase every version's content, keep the hashes so `ctx verify` still passes; leaves an empty `status: forgotten` stub. `--reason` is `user_request`, `legal`, `retention_expiry` or `error` |
| `ctx read <path>` | Reads and displays a document in the terminal. A bare id also finds a node at the root of an Obsidian-layout vault. |
| `ctx read <path> --body` | Prints the body alone, so you can edit it and pass it back with `--body-file`. |
| `ctx read <path> --html` | Render a document as styled HTML and open in browser |
| `ctx skill <path>` | Render a `type: skill` node for an agent harness and print it |
| `ctx skill install <path>` | Install a vault skill into Claude Code / Cursor / Codex (`--write` to actually write) |
| `ctx validate [path]` | Validate documents against the spec |
| `ctx publish <path>` | Publish a document (creates version + checkpoint) |
| `ctx publish --all` | Publish every unpublished document in one batch — one checkpoint, one index pass |
### Querying
| Command | Description |
|---|---|
| `ctx query <selector>` | Query context with graph traversal (default: 2 hops) |
| `ctx query <selector> --hops 4` | Deeper traversal for more related context |
| `ctx query <selector> --full` | Load all documents (bypass graph traversal) |
| `ctx query <selector> --include-drafts` | Include drafts (default: published only) |
| `ctx query @org/pack` | Query from a cloud-hosted pack via [PromptOwl](https://promptowl.ai) |
| `ctx list` | List all documents (filter with `--type`, `--status`, `--tag`; cap with `--limit`) |
| `ctx search <query>` | Searches the full text of vault documents. `--limit` caps the results, `--mode lexical\|semantic\|hybrid` picks the retrieval mode, and `--rerank` reranks the results. |
| `ctx embed` | Builds the embedding index that semantic and hybrid search need, from published documents. `--rebuild` starts over. See [retrieval modes](docs/retrieval-modes.md). |
| `ctx resolve <selector>` | Execute a selector query (low-level) |
### Selectors
```bash
ctx query "#engineering" # All docs with a tag
ctx query "type:document" # All docs of a type
ctx query "type:skill + #engineering" # All engineering skills
ctx query "pack:engineering-essentials" # All docs in a pack
ctx query "status:published" # By status
ctx query "#api + #v2" # Union
ctx query "#api + status:published" # Intersection
```
### Versioning & Integrity
| Command | Description |
|---|---|
| `ctx history <path>` | Show version history |
| `ctx history <path> --diff` | Include each version's unified diff from the one before |
| `ctx reconstruct <path> <version>` | Reconstruct a specific version (a version the history does not contain is refused, not approximated) |
| `ctx verify` | Verify integrity of all hash chains (a `history.yaml` that cannot be read is reported, not skipped). In an encrypted vault without its key it reports `encrypted_key_required` and never passes |
| `ctx forget-log [path]` | The forget audit trail — who forgot or deleted what, when, and under which reason code (never the content) |
Every CLI failure prints as a one-liner — `Error [CODE]: message` for engine errors, plain `Error: message` for the rest. Set `CONTEXTNEST_DEBUG=1` to get the full stack trace back.
### Packs, Checkpoints & Index
| Command | Description |
|---|---|
| `ctx index` | Regenerate context.yaml and INDEX.md files |
| `ctx pack list` | List all context packs |
| `ctx pack show <id>` | Show pack details |
| `ctx checkpoint list` | List checkpoints |
| `ctx checkpoint rebuild` | Rebuild checkpoint history |
### Importing literature (JATS / PubMed Central)
Scientific articles arrive as JATS XML — PubMed Central's `*.nxml`, publisher deposits, society corpora. `ctx import jats` turns each one into a **markdown twin**: a `reference` node whose body is the article (sections as headings, one paragraph per line, GFM tables, figure captions, LaTeX kept as `$…$`, numbered references) and whose frontmatter carries the graph NLM already curated, as query tags:
| Tag | From |
|---|---|
| `#pubtype-systematic-review`, `#pubtype-meta-analysis`, `#pubtype-srma` (SR + MA), `#pubtype-guideline`, `#pubtype-rct`, `#pubtype-review` | MEDLINE publication types from PubMed (`ctx import pubmed`, `ctx enrich pubtator`); raw list in `metadata.publication_types` |
| `#pubtype-review-article`, `#pubtype-srma` | JATS `article-type`, `custom-meta document_type` — when the PubMed types are unknown (plain `ctx import jats`) |
| `#article-type-review-article` | JATS `article-type`, once PubMed's types are known (also `metadata.article_type`) |
| `#year-2019`, `#journal-clin-gastroenterol-hepatol` | `pub-date`, `journal-id` |
| `#fecal-microbiota-transplantation` | `<kwd>` keywords |
| `#license-cc-by-nc-nd` | `<license>` / `<ali:license_ref>` |
| `#status-retracted`, `#has-erratum` | `<related-article>` retraction / correction links |
| `#mesh-d003015` | PubTator entities (after `ctx enrich pubtator`) |
Every paragraph keeps its JATS id as a trailing block anchor (`… ^p_4_2`), so an agent can cite `nodes/papers/pmid-31013034#p_4_2` rather than a whole paper. `metadata` holds `doi` / `pmid` / `pmcid`, authors, `source_sha256` and the parsed reference list; references whose DOI or PMID name another paper in the vault become `[[wikilinks]]` (`metadata.cites`), and earlier papers are re-linked when the paper they cite arrives.
```bash
ctx import jats ./corpus/ # every .xml / .nxml under the folder
ctx import jats paper.nxml --keep-xml # also store the original under assets/jats/
ctx import pubmed --term "chronic pain[majr] AND systematic review[pt]" --license commercial --max 50
ctx enrich pubtator # NCBI entities + relations, MeSH-normalised
ctx query "#fmt #pubtype-srma -#status-retracted"
```
Imports are idempotent: unchanged XML (same `source_sha256`) is skipped, a changed file cuts a new version, and a batch shares one checkpoint. A file holding several `<article>`s (a `pmc-articleset` dump) is imported article by article. The twin is *derived*: a re-import (`--force`, or changed XML) regenerates its body, tags and NLM metadata from the XML and carries over only enrichment (`metadata.entities` / `relations` / `pubtator` / `publication_types` and the tags derived from them, a resolved `pmid`/`pmcid`) — hand edits to a twin do not survive. `import pubmed` and `enrich pubtator` call NCBI's public services (3 requests/s; set `NCBI_API_KEY` or `--api-key` for 10/s). Each twin records its licence.
`ctx import pubmed` searches **PubMed** — where MeSH (`[mh]`, `[majr]`) and publication-type (`[pt]`) qualifiers work; PMC's own search silently ignores them — restricted to records with PMC full text (`"pubmed pmc"[sb]` is added unless the term already has it), then fetches each hit's JATS from PMC. `#pubtype-*` tags come from the record's MEDLINE publication types, not the JATS `article-type` (which says "review-article" for most systematic reviews). `--license <list>` keeps only articles under those licences — `cc-by`, `cc0`, `cc-by-nc`, `cc-by-sa`, … or `commercial` (CC BY + CC0 + public domain). PMC's licence filters skip downloads; the licence the JATS itself declares decides, so an article with no declared licence is dropped. `--max` counts articles *imported*: up to 6× as many candidates are considered so licence drops do not shrink the batch, and the drops are listed as skipped.
| Command | Description |
|---|---|
| `ctx import jats <paths...> [--folder nodes/papers] [--keep-xml] [--no-relink]` | Import JATS files or folders as markdown twins (the global `--force` republishes unchanged twins) |
| `ctx import pubmed --term <query> [--max 25] [--license <list>] [--api-key]` | Search PubMed (PMC full text only), keep the licences asked for, and import the hits with MEDLINE `#pubtype-*` tags |
| `ctx enrich pubtator [ids...] [--tag-limit 12]` | Attach PubTator 3 entities / relations, `#mesh-` tags and MEDLINE `#pubtype-*` tags to imported papers (the global `--force` re-fetches already-enriched ones) |
---
## MCP Server
The MCP server exposes vault operations as 44 tools for AI agents over stdio transport.
### Running the server
```bash
node packages/mcp-server/dist/index.js /path/to/your/vault
```
### Running the server in Docker
For Glama.ai and any MCP client that runs servers as containers:
```bash
docker build -t contextnest-mcp .
docker run -i --rm contextnest-mcp # demo vault baked into the image
docker run -i --rm -v "$PWD:/vault" contextnest-mcp # serve your own vault
```
The mounted directory is the one containing `.context/config.yaml`. The server runs as uid 1000 — add `--user "$(id -u):$(id -g)"` if your vault is owned by a different uid. Build with `--build-arg SEED_DEMO_VAULT=false` for an image that only serves a mounted vault.
### Configuring with Claude Code
Add to your project's `.mcp.json`:
```json
{
"mcpServers": {
"contextnest": {
"command": "node",
"args": ["/absolute/path/to/packages/mcp-server/dist/index.js"],
"env": {
"CONTEXTNEST_VAULT_PATH": "/path/to/your/vault"
}
}
}
}
```
### Configuring with Claude Desktop
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```json
{
"mcpServers": {
"contextnest": {
"command": "node",
"args": ["/absolute/path/to/packages/mcp-server/dist/index.js"],
"env": {
"CONTEXTNEST_VAULT_PATH": "/path/to/your/vault"
}
}
}
}
```
### Available MCP Tools
**Canonical tools** — name, description and input schema come straight from the engine's operation catalog, so this surface cannot drift from the CLI or the cloud:
| Tool | Description |
|---|---|
| `context_init` | Open a vault: instructions, configuration, path, and what it holds (`include_nodes` to also list nodes) |
| `context_nests` | List every nest in the central registry |
| `context_skill` | Render a `type: skill` node as a harness-ready skill file |
| `context_skill_install` | Build the file manifest that installs a vault skill locally |
| `context_structure` | The nest's structure rules: allowed folders, node types, name formats, templates (`folder` for one folder's rule, `report` for what breaks them) |
| `context_get` | Read one node (`include_raw`, `verify_checksum`, `allow_rejected`) |
| `context_list` | List nodes with folder / type / status / tag filters (`folder`, `recursive`, `include_retired`, `full`, `limit`) |
| `context_folders` | List the vault's folders and their document counts, without reading a single document (`folder`, `recursive`) |
| `context_search` | Search published nodes, best match first. `mode`: `lexical` (stemmed BM25, default), `semantic` (embeddings), `hybrid` (RRF of both, exact title/#tag matches pinned); `rerank` re-scores the top hits. Each hit carries `score` + per-signal `scores` |
| `context_embed` | Build/refresh the embedding index for semantic/hybrid search — published nodes only, re-embeds only new/changed versions, prunes the rest (`rebuild` to start over) |
| `context_query` | Selector query with graph traversal (`include_drafts`) |
| `context_resolve` | Resolve a selector to full bodies within a token budget |
| `context_versions` | List a document's version history |
| `context_reconstruct` | Reconstruct a specific version |
| `context_packs` | List packs with their `includes` and `excludes` |
| `context_verify` | Verify every hash chain in the vault |
| `context_forget` | Forget a node: erase its content from history, keep the hashes (verify still passes), leave a `status: forgotten` stub |
| `context_forget_log` | The forget audit trail — never the forgotten content |
| `context_create` | Create a node — own `id`, `publish: false`, initial `status`, `note`, full `skill` block |
| `context_update` | Update a node — rename, set `status`, stamp a `version`, clear metadata with `null` |
| `context_publish` | Publish a node; takes a `note`, returns the `chain_hash` |
| `context_delete` | Delete a node and its history, recorded in the forget audit log (`purge: true` for none); returns the deleted node's `title` |
| `context_import` | Bulk create-and-publish from `documents` and/or existing `ids` — one checkpoint for the batch |
| `context_import_pdf` | Import a PDF as a `type: pdf` node — extracted text as the body, the PDF kept beside it and bound by SHA-256; pass `id` to version an existing one |
Every tool above also takes an optional `client` object — `{ agent, session_id, …custom }` — naming the agent and session behind the call. A write that publishes records it on the version-history entry it seals (so `context_versions` shows which agent wrote which version); a graph read stamps it on the access traces it emits. It is a label, not an identity claim: it is never authenticated, never used to authorize, and never an input to a hash chain. See spec §9.4.
**Vault tools:**
| Tool | Description |
|---|---|
| `document_format` | Get the document format spec (call before creating docs) |
| `read_index` | Return the context.yaml index |
| `read_pack` | Resolve and return a context pack with documents |
| `list_checkpoints` | List recent checkpoints |
| `context_review` | Controls the review gate. It can `list` held writes, `approve` or `reject` one by `id`, and turn the gate `off` or `on`. |
**Deprecated tools** — still registered and unchanged, so existing clients keep working; removed in a future major:
| Deprecated | Use instead |
|---|---|
| `vault_info` | `context_init` |
| `read_document` | `context_get` |
| `list_documents` | `context_list` |
| `search` | `context_search` |
| `resolve` | `context_resolve` |
| `read_version` | `context_reconstruct` |
| `verify_integrity` | `context_verify` |
| `create_document` | `context_create` |
| `update_document` | `context_update` |
| `publish_document` | `context_publish` |
| `delete_document` | `context_delete` |
**Drift governance tools** (resolve out-of-band edits without touching the canonical doc or hash chain until approved):
| Tool | Description |
|---|---|
| `stage_drift_suggestion` | Capture a drifted live file as a staged suggestion under `_suggestions/` |
| `list_suggestions` | List all staged suggestions for a document |
| `approve_suggestion` | Apply a suggestion: patch, bump version, write new canonical bytes, archive |
| `reject_suggestion` | Reject a suggestion: archive with required reason, emit a chain event |
---
## Development
```bash
pnpm build # Build all packages
pnpm test # Run tests
pnpm test:watch # Run tests in watch mode
pnpm lint # Type-check without emitting
pnpm clean # Clean all build artifacts
```
## Typical Workflow
```
ctx init --starter developer # 1. Create a vault with starter recipe
# 2. Edit CONTEXT.md and config.yaml
ctx add nodes/my-doc # 3. Add documents (auto-publishes & indexes)
ctx add nodes/my-skill --type skill # 4. Add skills for agent procedures
ctx read nodes/my-doc --html # 5. View any document in the browser
ctx query "#engineering" # 6. Query with graph traversal
ctx validate # 7. Validate
ctx verify # 8. Verify integrity
# 9. Start MCP server for AI access
```
## License
The repository is **AGPL-3.0** ([LICENSE](LICENSE)), except the specification, which is **Apache-2.0** ([LICENSE-SPEC](LICENSE-SPEC)):
- **CLI** ([@promptowl/contextnest-cli](https://www.npmjs.com/package/@promptowl/contextnest-cli)): **AGPL-3.0**
- **Engine** ([@promptowl/contextnest-engine](https://www.npmjs.com/package/@promptowl/contextnest-engine)): **AGPL-3.0**
- **MCP Server** ([@promptowl/contextnest-mcp-server](https://www.npmjs.com/package/@promptowl/contextnest-mcp-server)): **AGPL-3.0**
- **Specification** ([CONTEXT_NEST_SPEC.md](CONTEXT_NEST_SPEC.md)): **Apache-2.0** — open standard, implementable without AGPL obligations
AGPL-3.0 ensures all improvements stay open source. You are free to use, modify, and distribute Context Nest, but modifications to the source must be shared under the same license. Commercial licensing is available from [PromptOwl](https://promptowl.ai) for organizations that need to embed or redistribute without AGPL obligations.
---
**[PromptOwl](https://promptowl.ai)** — Context governance for AI agents | [Join our Discord](https://discord.gg/fxcSQ5gq)
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessWithin a week