Working on Synaptane
Requirements
Section titled “Requirements”- uv, which installs Python 3.12 if needed.
- Docker, for PostgreSQL and pgvector in development.
- Git 2.28 or later.
Get started
Section titled “Get started”uv sync # environment and dependenciesdocker compose -f compose.dev.yaml up -d # PostgreSQL on 127.0.0.1:5433uv run synaptane --helpRun the app locally
Section titled “Run the app locally”Against the PostgreSQL of compose.dev.yaml (user, password and database: synaptane), from
the root of the repository. synaptane.toml, data/ and local/ are ignored by Git.
uv run synaptane init --database-url postgresql+psycopg://synaptane:synaptane@127.0.0.1:5433/synaptaneecho 'allow_local_repos = true' >> synaptane.toml # file:// repositories: development onlyuv run synaptane db upgradeuv run synaptane admin create --username admin --display-name "Admin"uv run synaptane doctorinitwrites./synaptane.toml(mode 600) and creates the data folder./data/, with the master keydata/master.keyand the repository copiesdata/repos/. Defaults:--data-dir ./data(relative to the folder of the configuration file) and--public-url http://127.0.0.1:8000.- For another file:
--configbefore the command (uv run synaptane --config local/dev.toml init …), orSYNAPTANE_CONFIG, to give again with every command. - Each key of the file can come from a
SYNAPTANE_<KEY>variable, which takes precedence: for exampleSYNAPTANE_DATABASE_URL, orSYNAPTANE_ALLOW_LOCAL_REPOS=trueinstead of theallow_local_reposline. admin createasks for the password (12 characters at least), or reads it fromSYNAPTANE_ADMIN_PASSWORD.- Locally,
doctorshows warnings for the public URL in HTTP and for missing optional tools (Tesseract, catdoc, PostgreSQL client, age, rclone): harmless for development.
Then, in two terminals:
uv run synaptane serve # web, REST and MCP on http://127.0.0.1:8000uv run synaptane worker # background jobs: Git sync, indexing, tidy-upserve and worker refuse to start on a database that is behind: run db upgrade again after
an update that brings a migration. For another port, serve --port 8001, and
public_url = "http://127.0.0.1:8001" in synaptane.toml.
Link a space to a local repository
Section titled “Link a space to a local repository”No Git host needed: a bare repository on disk is enough.
git init --bare -b main local/notes.gitecho "file://$PWD/local/notes.git" # the address to give the space- Sign in at http://127.0.0.1:8000 with the
adminaccount. - Administration → New organization, for example
acme→ Create organization. - In the organization: New space, Git repository address: the
file://…address above, branchmain→ Create space.
A file:// repository needs no access to set up. The worker prepares the empty repository
(first commit with content/README.md and .synaptane/inbox/): the space is ready. A
git clone local/notes.git then lets you push pages into it as a person would.
allow_local_repos is for development and tests only: on a server, it would let an
organization admin make the worker read any Git repository on the machine. Without it, syncing
a file:// repository fails.
./scripts/check.sh # lockfile, ruff, format, strict mypy, pytest./scripts/check.sh tests/unit # arguments are passed to pytesttests/unit: no dependencies.tests/integration: a real PostgreSQL, the one fromcompose.dev.yamlby default. Each session creates a throwaway database, then drops it. To target another instance:SYNAPTANE_TEST_DATABASE_URL.tests/eval: tidy-up and search evaluation bench, outside the normal suite, run by hand before changing an instruction, a model or the ranking (./scripts/eval.sh, see eval.md). Its self-check with the simulated model runs in CI.
CI (.github/workflows/ci.yml) runs the same script, then builds the image and runs
scripts/e2e-compose.sh: the full Compose stack, from installation to restore after a
down -v. Locally, without the CLIs to go faster:
docker build --build-arg AGENT_CLIS=false -t synaptane:e2e .SYNAPTANE_IMAGE=synaptane:e2e ./scripts/e2e-compose.shNew migration
Section titled “New migration”uv run python -c "from alembic import command; from synaptane.storage.db import alembic_config; \ command.revision(alembic_config('postgresql+psycopg://synaptane:synaptane@127.0.0.1:5433/synaptane'), \ message='…', autogenerate=True, rev_id='NNNN')"The synaptane database must be up to date (uv run synaptane db upgrade, see
Run the app locally): Alembic compares the models with its schema.
rev_id: the number after the last migration in storage/migrations/versions/
(ls src/synaptane/storage/migrations/versions | tail -1).
Always review the generated file, and name constraints with op.f(…): otherwise the naming
convention would be applied twice. tests/integration/test_migrations.py downgrades all
migrations, upgrades again, then checks that the schema matches the models.
Translations
Section titled “Translations”Interface texts are written in English, in the templates (_("…")) and in the code
(business errors, redirect messages, Label). web/locales/fr.json translates them into
French ("English text": "texte français"); English needs no catalog, the text is its own
translation. web/i18n.py also handles contexts (_("Enabled", context="routine"), entry
"Enabled|routine"), French plurals of invariant English texts (a pair of forms) and French
typography (no-break spaces before : ; ? !). Tests check that every text of the
templates and of the translating calls is in fr.json, with the same placeholders.
Texts computed by the services and shown later are Labels (template and values): stored
(Label.dumps, connection test result) or sent by email (mail.compose translates them for
each recipient), they are translated when shown. Texts stored before this switch, in French,
are still recognized (Git errors, tidy-up log, commit subjects). REST errors keep their error
code and translate their message according to Accept-Language (English by default); MCP
answers stay in English. See CONTRIBUTING.md to
add a text or a language.
Code layout
Section titled “Code layout”src/synaptane/:
| Package | Role |
|---|---|
core/ |
Pure rules: permissions (access.can, the only authority), identifiers, security, errors. |
services/ |
Use cases: permission checks, database writes, audit. The only entry point for the interfaces. |
storage/ |
PostgreSQL: models, connection, Alembic migrations (storage/migrations). |
git/ |
Space repositories: clone, commits, push, fetch, conflicts. |
ai/ |
AI providers, behind a single tool-calling conversation: Anthropic API, OpenAI-compatible APIs (OpenAI, Mistral, Ollama…), subscription CLIs (Claude Code, Codex) and the contract that checks their isolation; embeddings, public prices and estimated cost, subscription quota. |
web/, api/, mcp/ |
Web, REST and MCP interfaces. They contain no business rules. |
server.py |
FastAPI assembly: routes, static files, errors, security headers. |
worker/ |
Background jobs: sync, indexing, nightly tidy-up. |
cli.py |
The synaptane command line: init, db upgrade, admin create, serve, worker, doctor, backup, restore… |
config.py |
Settings: TOML file (synaptane.toml), overridden by SYNAPTANE_* variables. |
extract.py |
Text of PDFs, Office documents and images (OCR with Tesseract, old .doc and .xls with catdoc, old .ppt with extract_ppt.py), read in a separate process with bounded resources. |
In web/, pages.py holds the foundations (request context, templates, redirects, errors);
each group of pages has its own module and router (login_pages, org_pages, space_pages,
admin_pages, account_pages, wiki, inbox_pages…). The tidy-up is split between
services/tidy.py (run, scheduling) and services/tidy_tools.py (working view, the agent’s
tools and instructions).
System tools for tests
Section titled “System tools for tests”-
rg(ripgrep) is required. -
pg_dumpandpg_restoreversion 17 (packagepostgresql-client-17): without them, the tests intests/integration/test_backup.pythat make backups are skipped. -
age(withage-keygen) andrclone: without them, the off-site copy tests that encrypt or copy (tests/unit/test_offsite.py,tests/integration/test_offsite.py) are skipped. CI does not install them:scripts/e2e-compose.shexercises the off-site copy in the image, which contains them. -
tesseract, withfra, is needed for the OCR test (tests/fixtures/scan-fr.pdf), which is skipped if it is missing. CI installs it. The PDF, a page of French text with no text layer, is made bytests/fixtures/scan_fr.py(uv run python tests/fixtures/scan_fr.pyregenerates it). -
catdoc(packagecatdoc:catdoc,xls2csv) reads the old.docand.xlsfiles oftests/fixtures/legacy/(made byscripts/legacy_files.py) and of the demo; the tests that need it are skipped if it is missing. CI installs it. The.pptfiles are read in Python (extract_ppt.py, with olefile): their tests always run. -
Without a system package, you can borrow Tesseract (or catdoc) from a Docker image that contains it, with a small script named after the tool (
tesseract; for catdoc, the same script under the two namescatdocandxls2csv) placed first in thePATH:Terminal window exec docker run --rm -i --network none -u "$(id -u):$(id -g)" -v /tmp:/tmp \--entrypoint "$(basename "$0")" <image> "$@"
© 2026 René Cotton and the Synaptane contributors.