Skip to main content
Archived

This project has been archived by its maintainers, and is no longer receiving any updates.

context system

Project memory for AI coding sessions. Lives in the repo, travels with the code, shared by every model that opens it, consulted on every prompt.

Every AI session starts from zero. The decision you argued out yesterday gets re-argued today, the gotcha that cost an afternoon gets hit again next week, and the convention nobody wrote down gets broken by the next model to touch the code. This fixes that with three small pieces:

  • a store: .context/memories/*.jsonl, one fact per line, committed. Git is the history.
  • a server: an MCP server over that store with four tools and a sqlite-vec index for semantic recall.
  • a discipline: a skill and a hook that make the session recall before it answers and capture after it acts, every prompt, until told to stop.

Quick start

cd your-repo
uvx context-system -y

Restart Claude Code, approve the context-system server when asked, then:

/context-start-sync

That is the whole setup. Needs uv and git. Drop the -y to be asked about the target, the hook and the other clients instead of taking the defaults.

What landed in the repo:

path what
.context/ the store and the MCP server, with an empty memories/main.jsonl
.mcp.json the context-system server entry, merged into whatever was there
.claude/skills/context-sync/ the skill that drives the per-prompt loop
.claude/commands/context-*.md /context-start-sync, /context-start-sync-readonly, /context-stop-sync
.claude/hooks/context-sync.sh + settings.json a UserPromptSubmit hook that re-arms the loop every prompt, so it survives compaction
.agent/ the same skill and commands under the vendor-neutral tree some agents read

Commit .context/memories/. Everything else under .context/ is gitignored. Re-run the command any time to update an install; memories/ is never touched.

What a session looks like

With sync on, every prompt ends with one quiet line:

context: 6 recalled · 1 written

Before answering, the model ran one select with a query built from the intent of your prompt and treated the hits as ground truth for this repo. If a stored decision contradicts what it was about to do, it says so instead of quietly overwriting it. After acting, it wrote the durable facts it learned, one per call, correcting older memories in place rather than appending near-duplicates.

A memory is one JSON line:

{"id": "9f3c1a7be2d4", "ts": "2026-09-09T07:20:05+00:00", "text": "Session tokens are refreshed by the gateway, never by services. Tried per-service refresh first; it raced under load.", "tags": ["auth", "decision"], "source": ""}

Durable, so it gets written: a decision and why, the option that lost, a hard constraint, a gotcha and its workaround, a convention the code does not reveal, a stated preference. Not durable, so it does not: transient state, file contents, anything re-derivable by reading the repo, general knowledge. Never: credentials, tokens, personal data. The store is committed and pushed.

Three commands:

command does
/context-start-sync recall and capture on every prompt
/context-start-sync-readonly recall only, writes nothing, ever. For someone else's repo, or a branch whose decisions are not settled
/context-stop-sync final flush of anything durable, then off

The four operations

Any MCP client gets the same four tools. Scopes are named JSONL files; main is the shared default.

tool args does
select query, scope=main, k=8, tags Semantic search, best first, each with a score. Empty query lists the k newest.
write text, scope=main, tags, source, id Save one fact. Pass an existing id to replace that memory in place.
compress scope, ids, summary, threshold=0.92 ids + summary: fold those memories into one. Neither: merge near-duplicates, newest kept.
isolate scope, seed_from, query, k, tags Open a private scope, optionally seeded with the top k hits from another.

A sub-task or a sub-agent that will generate a lot of throwaway reasoning gets isolate("task-x", seed_from="main", query=...), works in that scope, then compress("main", ids, summary) folds the useful residue back.

Why it is shaped like this

Memory is part of the repo. A branch has its own memory. A pull request reviews the memory along with the code. git blame on a decision works. Merge conflicts in a JSONL are trivial: keep both sides, ids are unique, run compress().

No model inside the server. Summaries in compress come from whichever model is calling. Claude writes a memory, Codex reads it, Gemini corrects it. Same store, same file.

The index is disposable. index.db is a sqlite-vec index rebuilt from the JSONL whenever they drift. Delete it freely. Embeddings come from BAAI/bge-small-en-v1.5 via fastembed, about 30 MB, downloaded on first run; CONTEXT_EMBED_MODEL picks another.

The skill solves when, the hook makes it stick. A memory server on its own gets called whenever the model happens to think of it, which on a long session means less and less. The skill turns it into a fixed per-turn loop. The hook re-injects that loop on every prompt for as long as .context/.sync-on exists, so compaction cannot erode it.

Many repos in one window

One store per repo, but the editor is usually open on the folder above several. From that folder:

uvx context-system --set-root        # finds every repo with a .context/ up to 5 levels down, asks which join

or inside each repo, .context/setup.sh --set-root ... Either way the repo adds itself to that folder's .mcp.json as context-system-<repo> with an absolute path, and copies the skill, commands and hook into the folder's .claude/. Stores stay separate on purpose: one repo's decisions are not another's. Sync is per repo too, so one repo can be capturing while another is read-only and the rest are off.

Other clients

Claude Code reads .mcp.json and is done. Everything else is one command away, and setup.sh ships inside .context/ so teammates without this repo have it:

.context/setup.sh                      # asks: which clients, which workspace folder
.context/setup.sh --codex --gemini     # also --claude --agy --vscode
.context/setup.sh --print              # just list the commands

Clients configured by file (Cursor, Windsurf, Cline, Zed, Claude Desktop) take this under mcpServers:

"context-system": {"command": "uv", "args": ["run", "--directory", "/abs/path/to/repo/.context", "python", "-m", "context_store.server", "mcp"]}

As a service, a JSON API with docs at /docs and MCP at /mcp:

uv run --directory .context python -m context_store.server serve     # http://127.0.0.1:8765
curl -s localhost:8765/select -d '{"query":"how is auth done"}' -H 'content-type: application/json'

Other ways to install

All of these are the same installer with the same flags. uvx fetches the release tag from GitHub, so it needs git; the rest run from a checkout.

uvx context-system /path/to/repo -y                 # a repo other than the current one
uvx context-system --no-hook                        # skill only, no hook
uvx context-system --codex --vscode                 # register those clients as you go
uvx --from git+https://github.com/ZGA2519/context-system context-system   # track main

git clone https://github.com/ZGA2519/context-system && cd context-system
./install.sh /path/to/repo        # POSIX sh, the original
python3 setup.py /path/to/repo    # the same wizard in Python, runs in cmd.exe too
uv run context-system --help      # everything the installer accepts

Under the hood

.context/
  memories/<scope>.jsonl    the store, committed
  index.db                  sqlite-vec index, gitignored, rebuilt when it drifts
  context_store/store.py    the four operations
  context_store/server.py   MCP over stdio, or HTTP with a JSON API
  setup.sh                  client registration and workspace folders
skills/context-sync/        the skill, commands and hook the installer copies
install.sh · setup.py       the installer, twice

Tests: uv run --group dev pytest inside .context/ for the store, python3 test_setup.py at the root for the installer.

Docs: .context/README.md for the store and server, skills/context-sync/SKILL.md for the full per-turn protocol.

Releasing

Bump version in pyproject.toml, push to main. The workflow in .github/workflows/publish.yml tags v<version>, builds and publishes to PyPI with trusted publishing. A push that does not change the version does nothing.

License

MIT

Metadata

Release files for context-system 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for context-system 0.1.3
File Size Uploaded Provenance Actions
context_system-0.1.3.tar.gz 15.8 kB Without provenance Details for context_system-0.1.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for context-system 0.1.3
File Interpreter ABI Platform Actions
context_system-0.1.3-py3-none-any.whl Python 3 none any Details for context_system-0.1.3-py3-none-any.whl

Total release size: 29.5 kB

Release files / context_system-0.1.3.tar.gz

Download URL context_system-0.1.3.tar.gz
Size 15.8 kB
Tags Source
SHA-256 checksum
How to use checksums
18bc9e5cb303661a0fab8432e3c903e32bbb7dac1880a5b4c87bb9d7c506ff41
BLAKE2b-256 checksum
How to use checksums
507a1e80da67830ecf37fd9c652ae3d839c4148e3d6a8b49b82d629ff0a7f52e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.11 {"installer":{"name":"uv","version":"0.12.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / context_system-0.1.3-py3-none-any.whl

Download URL context_system-0.1.3-py3-none-any.whl
Size 13.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05e7b1e8b161fa0a91e6cffe4aab0210ceab2d98ea1c4fc622f0f5520616e843
BLAKE2b-256 checksum
How to use checksums
8ca2a56273f8965dbf7807a5fd403a66cc3e87f16544d7cf79f83ccc8135a88c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.11 {"installer":{"name":"uv","version":"0.12.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for context-system 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page