Skip to main content
Glama
README.md
# Cairn

[![GitHub stars](https://img.shields.io/github/stars/QuarkOS/Cairn?style=social)](https://github.com/QuarkOS/Cairn)
[![npm version](https://img.shields.io/npm/v/%40quarkos%2Fcairn)](https://www.npmjs.com/package/@quarkos/cairn)

Cairn is an append-only store of typed facts. An agent asserts a fact in one session and recalls it, with a freshness verdict, at the start of the next.

Facts persist in SQLite. The desk is optional. The work happens on JSON and MCP.

The npm package is **`@quarkos/cairn`**. Always use the scoped package name; the unscoped `cairn` name is an unrelated 2017 React Native styling package.

## Install

From any empty project folder (Node 20+):

```bash
npx --yes @quarkos/cairn --help
npx --yes @quarkos/cairn init --project
npx --yes @quarkos/cairn dev
```

The first command warms the npx cache. `init --project` creates `.cairn/cairn.db` (empty), wires Cursor (`.cursor/mcp.json`), and wires Pi + Claude Code (`.mcp.json`). The shared `.mcp.json` stores an absolute `CAIRN_HOME`. It does not use Cursor-only `${workspaceFolder}`. `--demo` seeds only an empty store and refuses to overwrite existing facts.

On an ephemeral VM, run the scoped help command once before starting an agent so the MCP process does not pay the package download during client startup. Run project init again for each new workspace, then reload the client's MCP configuration. Claude Code may ask for the expected project-server approval; Pi must be installed in the VM.

Global install without a project file:

```bash
npx --yes @quarkos/cairn init
```

## Commands

| Command | Purpose |
| --- | --- |
| `cairn init --project` | Empty database + Cursor + Pi/Claude MCP config in the current repo |
| `cairn init --project --demo` | Seed sample beliefs only when the project store is empty |
| `cairn dev` | Desk and API on port 4721, using this project's `.cairn` |
| `cairn start` | Production server after `npm run build` |
| `cairn mcp` | Stdio MCP server for agents |
| `cairn recall` | Print live beliefs as JSON |

Invoke via `npx --yes @quarkos/cairn …`, `npx --yes github:QuarkOS/Cairn …`, or `node bin/cairn.mjs` from a clone. `init` writes MCP `npx` args from that install source (`@quarkos/cairn`, `github:QuarkOS/Cairn`, or the local checkout path). Override with `CAIRN_NPX_SPEC` when MCP should use a different package.

## Agent contract

Send JSON to `POST /api/cairn` or use the typed MCP tools:

| MCP tool | Purpose |
| --- | --- |
| `cairn_recall` | Recall live beliefs with freshness and assurance |
| `cairn_assert` | Append a typed fact with explicit conflict behavior |
| `cairn_retract` | Retract a live fact without deleting its history |

The write tools expose the complete input schema and take request fields directly. `cairn_request` remains available for clients using the original nested JSON-contract tool.

Every write carries an `idempotencyKey`. Replay the same key and body after a crash and Cairn returns the original result without writing twice.

Recall returns `freshness` and `assurance` on each belief. Freshness is **age-advisory**: `ttl` is a clock, not a probe. The store never runs commands. When a belief is stale, re-observe outside Cairn, then assert with `onConflict: "supersede"`. Branch on `response.kind`. Rejections include `error.remedy.kind`.

Validity kinds:

| `validity.kind` | Meaning |
| --- | --- |
| `until-superseded` | Live until a later assert supersedes it |
| `ttl` | Advisory stale after `staleAfterSeconds` from `assertedAt` |
| `expires` | `expired` once `at` is reached |

Inbound `validity.kind: "reverify"` is accepted for one minor and stored as `ttl`. The `command` field is dropped and is never executed. Do not send `reverify` on new asserts.

### Example assert

```bash
curl -s http://127.0.0.1:4721/api/cairn \
  -H 'content-type: application/json' \
  -d '{
    "kind": "assert",
    "idempotencyKey": "s-021-deploy",
    "onConflict": "supersede",
    "draft": {
      "entity": "repo:acme/checkout",
      "attribute": "deploy.command",
      "value": { "kind": "text", "text": "bin/ship --env staging" },
      "provenance": {
        "kind": "observed",
        "command": "cat Makefile && bin/ship --help",
        "session": "s-021"
      },
      "validity": { "kind": "until-superseded" }
    }
  }'
```

## Harness plugins

### Cursor

After `init --project`, `.cursor/mcp.json` includes a `cairn` server. Cursor may use `${workspaceFolder}` here:

```json
{
  "mcpServers": {
    "cairn": {
      "command": "npx",
      "args": ["-y", "@quarkos/cairn", "mcp"],
      "env": {
        "CAIRN_HOME": "${workspaceFolder}/.cairn"
      }
    }
  }
}
```

Reload MCP in Cursor (or restart) so the tools appear. After `npx --yes github:QuarkOS/Cairn init --project`, the `args` entry is `github:QuarkOS/Cairn` instead of `@quarkos/cairn`.

### Claude Code

The same init writes project-scoped `.mcp.json`. Claude Code reads that file at session start and prompts once to approve project servers. On this checkout, see `CLAUDE.md` for the agent rule and `DEMO.md` for a two-session walkthrough. Init is still the local first step. `.mcp.json` is gitignored because it stores an absolute `CAIRN_HOME`.

```json
{
  "mcpServers": {
    "cairn": {
      "command": "npx",
      "args": ["-y", "@quarkos/cairn", "mcp"],
      "env": {
        "CAIRN_HOME": "/absolute/path/to/your/project/.cairn"
      }
    }
  }
}
```

`CAIRN_HOME` is an absolute path written at init time. Claude Code does not understand Cursor's `${workspaceFolder}`. The `args` package matches whatever install source ran `init`.

You can also add the server with:

```bash
claude mcp add --scope project cairn -- npx -y @quarkos/cairn mcp
```

Then set `CAIRN_HOME` in the generated entry to your project's `.cairn` directory.

### Pi

Pi also reads `.mcp.json`. Init writes the same portable file used for Claude Code: absolute `CAIRN_HOME`, no `${workspaceFolder}`.

Start Pi from the project root (or any client that loads `.mcp.json`) and open `/mcp` to confirm the `cairn` server is listed.

## Cursor Agent Plugin

This repository is an [Agent Plugin](https://agent-plugins.org/) (`plugin.json` + `mcp.json` + `skills/`) with a Cursor overlay at `.cursor-plugin/plugin.json` only so marketplace **Configure** can collect optional `CAIRN_HOME`. It does not add rules, hooks, agents, or commands.

The npm package is always **`@quarkos/cairn`**. Never install or invoke the unscoped `cairn` package (an unrelated 2017 React Native styling library).

### Usage

1. In the project that should hold beliefs, run:

   ```bash
   npx -y @quarkos/cairn init --project
   ```

   That creates `.cairn/` (the SQLite store) on **your** machine.

2. Install this plugin in Cursor (Customize, or a local copy under `~/.cursor/plugins/local`). Reload the window so the `cairn` MCP server and the `cairn-recall` / `cairn-assert` skills appear.

3. Optionally open **Plugins → Configure** and set `CAIRN_HOME` to the **absolute** path of that project's `.cairn` directory (for example `/Users/you/work/checkout/.cairn`). Leave it unset if you already exported `CAIRN_HOME`, or if you want native resolution.

4. Call `cairn_recall` at session start. Call `cairn_assert` after a decision, deploy, URL, or command actually lands. Call `cairn_retract` when a live fact is no longer true. Do not invent facts.

### Config

| Item | Value |
| --- | --- |
| MCP command | `npx -y @quarkos/cairn mcp` |
| Store | `CAIRN_HOME` on the **installer's** machine |
| Optional plugin variable | `CAIRN_HOME` — absolute path to the project's `.cairn` after `init --project`. Not required. Not a secret. |
| Native resolution if unset | `CAIRN_HOME` if set, else `./.cairn` if that directory exists in the MCP process cwd, else `~/.cairn` |
| Skills | `cairn-recall`, `cairn-assert` |

`mcp.json` does not set `cwd` (and does not use `${PLUGIN_ROOT}`). Spec-compliant Agent Plugins clients still default the subprocess cwd to the plugin root, so do not rely on `./.cairn` inside the plugin install directory. Point `CAIRN_HOME` at **your** project store.

If Configure leaves `CAIRN_HOME` blank, hosts may pass an empty string or the literal `${CAIRN_HOME}`. Cairn treats both as unset and continues native resolution. Never pin a coordinator or cloud-agent path such as `/home/box/cairn-loop/.cairn`.

### Limit

This plugin helps **Cursor IDE users on the installer's machine**. It does not inject facts into Cursor cloud agent VMs. Those remotes cannot see the installer's SQLite.

Marketplace listing is a separate step. This repo is plugin source only; submitting at cursor.com/marketplace/publish is out of scope until someone does that on purpose.

## Desk and canvas

Open **Desk** (`/`) for the beliefs table and Agent API console. Open **Canvas** (`/canvas`) to see each agent or session as a draggable container with the facts they contributed.

Agents group by `provenance.by` for told facts and by `provenance.session` for observed or inferred facts. Drag pods to arrange the board; layout persists in `.cairn/canvas.json`.

## Publish

```bash
npm publish --access public
```

The published tarball ships the desk, canvas, API, CLI, and MCP server. Package and MCP server versions are both `0.5.0`. `Validity` no longer includes `reverify`; that is a public type break from 0.4.x.

## Where the database lives

`CAIRN_HOME` is the one directory that holds `cairn.db` and `canvas.json`.

Resolution, in order:

1. `CAIRN_HOME` if you set it
2. `./.cairn` in the directory you ran the command from, if that folder exists
3. `~/.cairn`

`cairn dev` and `cairn start` run Next.js from the installed package, not from your project. They still pin `CAIRN_HOME` using the rule above, so a throwaway project that ran `init --project` talks to **that** project's database without exporting anything. Set `CAIRN_HOME` only when you want a different store.

`CAIRN_DB_PATH` overrides just the SQLite file.

## Development

```bash
npm install
npm test
npm run dev
```

Database path resolves as described in [Where the database lives](#where-the-database-lives). `npm run dev` from a clone uses the same rule with the repo as cwd.

## Release trailer (Remotion)

A ~30s product trailer for X lives in `cairn-trailer/`. Preview with `npm run dev` inside that folder, or render:

```bash
cd cairn-trailer
npx remotion render CairnTrailer out/cairn-v0.4-trailer.mp4
```

## What Cairn refuses

Free-form JSON values. Semantic search. Editing facts in place. Waiting on a human mid-request.