Skip to content
Back to skills

Context Sync

ASecurity

Drives the `fornix` MCP server (project memory - write/select/compress/isolate over a .fornix/ folder) on a fixed per-turn loop so recall and capture happen every prompt instead of whenever the model happens to think of it. Provides three commands, context-start-sync, context-start-sync-readonly and context-stop-sync. Use this skill whenever the user types any of those names, with or without a leading slash, and also whenever they ask to start or stop syncing context, turn project memory on o...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 5, 2026
ai-agentspythongosqlgitapi

Works with

  • claude code
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add ZGA2519/fornix --skill context-sync --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Context Sync?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Context Sync
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zga2519-context-sync/badge)](https://www.skillsdirectory.com/skills/zga2519-context-sync)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: context-sync
description: Drives the `fornix` MCP server (project memory - write/select/compress/isolate over a .fornix/ folder) on a fixed per-turn loop so recall and capture happen every prompt instead of whenever the model happens to think of it. Provides three commands, context-start-sync, context-start-sync-readonly and context-stop-sync. Use this skill whenever the user types any of those names, with or without a leading slash, and also whenever they ask to start or stop syncing context, turn project memory on or off, sync context without writing to it, recall only, read-only context, "keep context in sync", "remember this for the project", "check the context store first", or when a repo contains a .fornix/ folder and the user wants past decisions honored. Also use it when a session has clearly stopped consulting project memory and needs to be put back on the loop.
---

# context-sync

The `fornix` MCP server is a shared project memory: `memories/<scope>.jsonl` committed to git, a sqlite-vec index rebuilt from it, six tools. It works fine on its own — the problem it does not solve is *when* to call it. This skill supplies the when: two commands that flip a per-turn discipline on and off.

## The server

Launched by `.mcp.json` at the repo root as `uv run --directory .fornix python -m context_store.server mcp`. Six tools:

| tool | args | use |
| --- | --- | --- |
| `select` | `query=""`, `scope="main"`, `k=8`, `tags=[]`, `since=""` | Semantic recall, best first, each with a `score`. Empty query returns the `k` newest. `since` (ISO date) keeps only what was written or corrected from then on. |
| `write` | `text`, `scope="main"`, `tags=[]`, `source=""`, `id=""` | Save one fact. Returns the memory with its `id`. Pass an existing `id` to replace that memory in place. |
| `compress` | `scope="main"`, `ids=[]`, `summary=""`, `threshold=0.92` | `ids` + `summary`: replace those with one summary. Neither: merge near-duplicates, newest kept. |
| `isolate` | `scope`, `seed_from=""`, `query=""`, `k=8`, `tags=[]` | Open a private scope, optionally seeded with the top `k` of another. |
| `forget` | `ids` | Delete those memories, from any scope. For a wrong or obsolete fact; to fix one, `write(id=...)`. |
| `scopes` | | Every scope with its memory count and tag counts. Tags are the topics. |

Scope names are `[a-z0-9._-]`, 64 chars max, one JSONL file each. `main` is the shared default.

## Command: context-start-sync

1. Check the server answers: `select(query="", k=3)`. If the call errors, report that and **do not** turn sync on — say what failed and suggest `/mcp` to reconnect.
2. Prime the session: `select(query=<what this session is about>, k=8)`. If the user gave no topic yet, the empty query from step 1 is enough.
3. If a `.fornix/` directory exists, `echo full > .fornix/.sync-on`. This is the flag the hook reads, and its contents are the mode; harmless without the hook. It's gitignored by the install. In a window opened over several repos, the flag belongs to the repo being synced — `<repo>/.fornix/.sync-on` — never the folder above them.
4. Report in one line what came back, then run the loop below on **every** following prompt until `context-stop-sync`.

## Command: context-start-sync-readonly

Same as `context-start-sync` with capture switched off: recall on every prompt, write nothing, ever. For working in someone else's repo, on a branch whose decisions aren't settled, or any session whose reasoning shouldn't end up in a committed store.

1. Steps 1 and 2 above, unchanged.
2. `echo readonly > .fornix/.sync-on` instead of `full`.
3. Report that sync is on in read-only mode, then run the loop with the **Capture** half skipped.

While read-only:
- `select` and `isolate(seed_from=...)` are fine — the first is a pure read, the second only ever copies into a private throwaway scope.
- `write` and `compress` are off limits. `compress` deletes lines from the JSONL, so it counts as a write no matter how it's framed.
- Something durable turns up and there's nowhere to put it: mention it in one line at the end of the turn and move on. Don't stockpile a list to flush later, and don't ask every turn for permission to write.
- Switching to full capture takes an explicit `context-start-sync` from the user. Nothing said mid-session — "yeah that's worth remembering", "go ahead" — promotes read-only to writable on its own.
- Footer marks the mode: `context: 6 recalled · read-only`.

## The per-turn loop (while sync is on)

Recall runs in both modes. Capture runs only in full mode.

**Recall, before answering.** One `select` per prompt, `k=6`. Build the query from the *intent* of the prompt in your own words, not the prompt verbatim — it's semantic search, so "how do we handle auth token refresh" beats "fix the thing you broke". Then:

- Treat what comes back as project ground truth. It outranks your assumptions about this repo.
- If a memory contradicts what you were about to say or do, say so out loud before proceeding. Never quietly overwrite a recorded decision.
- Low scores across the board means nothing relevant is stored — say nothing and move on. Don't pad the answer with weak hits.
- The one skip: a bare acknowledgement ("yes", "go on", "ok do it") gets no fresh select — reuse the previous turn's recall.

**Capture, after acting.** One fact per `write`, a sentence or two, with tags.

Durable — write these:
- a decision and *why*, including the option that lost
- a constraint that can't be negotiated away (version pin, quota, compliance rule)
- a gotcha and its workaround, especially one that cost real time
- a convention this repo follows that the code alone doesn't reveal
- a stated preference about how work should be done here
- an environment fact that bit you and will bite again

Not durable — don't write these:
- transient state ("the branch is currently rebasing")
- file contents, diffs, or anything re-derivable by reading the repo in seconds
- general knowledge that isn't specific to this project
- a restatement of what the code plainly says

**Correct, don't duplicate.** If `select` surfaced an older version of the fact you're about to record, call `write(id=<that id>, text=<new text>)` to replace it in place. Tags and source carry over unless you pass new ones. Appending a near-duplicate instead is the main way this store rots.

**Never write:** credentials, tokens, API keys, connection strings, passwords, personal data, or raw production config. `memories/` is committed and pushed. If a fact needs one of those to make sense, write the shape without the value — "auth uses a service token from Vault at <path>", not the token.

**Volume.** Zero to three writes a turn is normal. A turn with nothing durable in it gets zero writes, and that is a correct turn, not a missed one.

**Report.** End the turn with one quiet line, nothing more:

```
context: 6 recalled · 1 written
```

## Scopes

Default everything to `main`. For a sub-task or a subagent that will generate a lot of throwaway reasoning, `isolate("task-name", seed_from="main", query=<the sub-task>)`, work with `scope="task-name"`, then fold the useful residue back with `compress("main", ids=[...], summary=<one or two sentences>)`. Occasionally, when a scope feels repetitive, a bare `compress()` merges near-duplicates on its own. In read-only mode the fold-back and the tidy-up are both off — an isolated scope stays a scratchpad and is simply abandoned.

## Command: context-stop-sync

Stops either mode.

1. In full mode, final flush: write anything durable from this session that isn't stored yet. This is the last chance. In read-only mode, skip this — read-only means read-only right through the exit.
2. `rm -f .fornix/.sync-on`, in each repo it was set for.
3. Stop calling `fornix` tools. Don't recall, don't write, don't offer to — until a start command comes again.
4. Report: `context: sync off · N written this session`, or `context: sync off · read-only, nothing written`.

## When it breaks

A tool error or an unreachable server: say it once, treat sync as off, carry on with the actual work. Don't retry the call every turn — a broken memory store shouldn't turn into a per-prompt tax.

## Making it survive a long session

These instructions live in context, so on a long or heavily compacted session the loop can quietly fade. The hard guarantee is the `UserPromptSubmit` hook at `.claude/hooks/context-sync.sh`, which re-injects the loop on every prompt as long as `.fornix/.sync-on` exists. `install.sh` puts it there and registers it in `.claude/settings.json`; if this repo was set up by hand or with `--no-hook`, re-run `./install.sh <this repo>` from the fornix checkout to add it. Claude Code CLI only — Cowork does not fire hooks, so there the skill-only path is all there is.

The three command names are real slash commands when `install.sh` has run (they live in `.claude/commands/`, which is the only place Claude Code looks — command files sitting inside a skill folder are never registered). Without them, typing the command name as plain text works just as well.

## Many repos in one window

One store per repo, but the editor is often open on the folder above several of them. Running `.fornix/setup.sh --set-root <that folder>` inside each repo puts a `fornix-<repo>` server in the folder's `.mcp.json` — absolute path, so it starts from anywhere — copies the skill, the commands and the hook into its `.claude/`, and lists the repo in `.claude/context-sync.repos`, which is how the hook finds the members.

The stores stay separate on purpose: one repo's decisions are not another's. Recall from the server that owns the files a prompt is about and write back to that same one — `fornix-api` for `api/`, `fornix-web` for `web/`. Sync is per repo too. The hook arms exactly the servers whose repo has a `.sync-on` flag, so a session can have one repo capturing, another read-only, and the rest off.

## Tuning

`k=6` on recall and one select per prompt is the default because recall cost is paid on every turn. Raise `k` for a research-heavy session, drop to select-only-on-topic-change if the store is small and the session is long. If the user asks for cheaper syncing, cut recall frequency first, never capture.

Files in this skill

  • SKILL.md9.8 KB
  • commands/context-start-sync-readonly.md475 B
  • commands/context-start-sync.md361 B
  • commands/context-stop-sync.md319 B
  • hooks/context-sync.sh3.1 KB
  • hooks/settings-snippet.json258 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…