Contexa is a local-first context engine that runs as a background daemon on your devices. It lets you store, version, and synchronize structured JSON context across machines — with no cloud dependency, no central server, and no web UI required.
It is designed as the persistence and sync layer for AI agents, developer tools, and automation scripts that need shared, durable state.
Licensed under the Apache License 2.0.
- Runs a daemon (
contexad) on each of your devices - Stores versioned, checksummed JSON documents (called contexts) in a local SQLite database
- Exposes a local HTTP API on
127.0.0.1:7474for programmatic access - Provides a CLI (
contexa) for managing contexts, devices, and sync from the terminal - Ships a typed Python SDK (
pip install contexa) for use in agents and scripts - Syncs contexts between your trusted devices — directly over LAN or through an optional relay server
- Encrypts all sync payloads end-to-end using device key pairs; the relay never sees plaintext
- Optionally generates vector embeddings for semantic search over stored contexts
/
├── contexa/ # Main Python package — daemon, API, CLI, SDK
├── relay/ # Relay server — stateless WebSocket broker (self-hostable)
├── LICENSE
└── README.md
- Python 3.11 or later
- uv (recommended) or pip
To set up the development environment:
- Ensure you have
uvinstalled (https://github.com/astral-sh/uv). - Install dependencies and create the virtual environment:
cd contexa uv sync - Activate the virtual environment:
source .venv/bin/activate.fish # or activate (for bash/zsh)
This installs two executables into the virtual environment:
contexa— the CLIcontexad— the daemon entry point
Semantic search requires sentence-transformers. Install the optional dependency group:
uv sync --extra embeddingsThen set a model in your config (see Configuration):
[embeddings]
model = "all-MiniLM-L6-v2"pip install contexa# Start the daemon in the foreground
contexad
# Or via the CLI
contexa daemon startThe daemon listens on http://127.0.0.1:7474 by default. It initializes the SQLite database and all subsystems on first run.
Linux (systemd)
Copy the included unit file and enable it:
cp contexa/contexad.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now contexadmacOS (launchd)
cp contexa/com.contexa.daemon.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.contexa.daemon.plistContexa reads configuration from ~/.config/contexa/config.toml. If the file is absent, all defaults apply.
[daemon]
port = 7474
log_level = "INFO" # DEBUG | INFO | WARN | ERROR
log_output = "stdout" # "stdout" or a file path
[storage]
data_dir = "~/.local/share/contexa"
[sync]
interval_seconds = 60
max_retries = 5
backoff_base_seconds = 2
mode = "hosted" # "local-only" | "self-hosted" | "hosted"
[relay]
endpoint = "" # required when mode = "self-hosted"
[embeddings]
model = "" # e.g. "all-MiniLM-L6-v2"; empty = disabledView the resolved config (including defaults) at any time:
contexa config show# Daemon
contexa daemon start
contexa daemon stop
contexa daemon status
# Contexts
contexa context create --json '{"key": "value"}' --label my-context
contexa context list
contexa context get <context_id>
contexa context label <context_id> "new label"
contexa context delete <context_id>
# Trusted devices
contexa trust list
contexa trust add <device_id> <public_key_file>
contexa trust remove <device_id>
# Sync
contexa sync trigger
contexa sync log --status conflict
# Config
contexa config showfrom contexa.sdk import ContexaClient
client = ContexaClient() # connects to http://127.0.0.1:7474
# Store context
ctx = client.create_context(
content={"task": "refactor auth", "status": "in_progress"},
label="auth-refactor"
)
# Read it back
ctx = client.get_context(ctx["context_id"])
# Update it (creates a new immutable version)
client.update_context(ctx["context_id"], {"task": "refactor auth", "status": "done"})
# Semantic search (requires embeddings to be configured)
results = client.search("authentication tasks")
# Sync
client.trigger_sync()
log = client.get_sync_log(status="conflict")ContexaConnectionError is raised if the daemon is not running.
The relay is a stateless WebSocket broker that lets devices sync when they can't reach each other directly. It never stores or decrypts context data.
# Run with Docker
docker run -p 8765:8765 contexa/relay
# Or run locally
cd relay
uv sync
uvicorn relay.main:app --host 0.0.0.0 --port 8765Point your devices at it:
[sync]
mode = "self-hosted"
[relay]
endpoint = "wss://your-relay-host:8765"~/.config/contexa/config.toml # configuration
~/.local/share/contexa/
├── contexa.db # SQLite database (contexts, sync log, embeddings)
├── device_id # this device's UUID
└── device_key.pem # Ed25519 private key (chmod 600)
cd contexa
uv sync
# Run all tests
pytest tests/ -v
# Unit tests only
pytest tests/unit/ -v
# Property-based tests only
pytest tests/property/ -v --hypothesis-seed=0