An intelligent middleware for capturing thoughts and filing them as Soma rows.
Synapse eliminates the friction of manual data entry. It accepts unstructured, natural-language text, uses a multi-step AI chain (parse → classify → extract) to understand and structure the content, and then writes it to the right Soma table: tasks, groceries, media, bookmarks, ideas, things to do. Every capture also writes an execution log row. The entire project is written in Python and deployed on Modal.
- Modal hosts everything.
app.pyis the single deployment shim — image, secrets, endpoints. All business logic lives insrc/core/as plain Python with no Modal imports. webhook(operator, Modal proxy auth) andcapture(Receptor, per-device bearer token) validate the payload andspawn()the worker — spawn IS the queue (no Pub/Sub).process— the background worker (timeout=600,memory=512,max_containers=1: one serialized worker owns the capture journals, and name/url dedupe is query-then-write; retries with backoff). Runscore.pipeline.run.- Inference uses structured JSON for parsing, classification and extraction.
AI_PROVIDERdefaults togemini;cloudflareselects Workers AI with@cf/google/gemma-4-26b-a4b-itby default. Gemini model names remain configurable throughGEMINI_MODELandGEMINI_FALLBACK_MODEL. - The Soma catalog is the contract. Each column's type, required flag, select options and their meanings, defaults and enforced invariants come from the hub's
POST /v1/catalog/projectionat run time (cached per workspace in thesynapse-stateVolume for an hour, reprojected after expiry, dropped when the hub rejects a write). Synapse carries no copy of the schema;src/core/template/prompts.yamlonly says how to read text into it. - App secrets are env vars only: the Modal secret
synapsein the cloud,op runlocally..env.tplis the canonical manifest (op:// refs, committed) — the app's own provider keys only. - Workspaces hold everything that belongs to one user: their workflow table bindings, wording overrides, place tags and Soma hub credentials. They live in the
synapse-stateVolume, edited withjust workspace ...; each device token files its captures into one workspace. The repo carries only the generic template.
flowchart LR
R["Receptor<br/>(iOS/macOS app)"] -->|"POST {raw_text, capture_id}<br/>device bearer token"| C["capture<br/>(Modal fastapi_endpoint)"]
O["just recept / agents"] -->|"Modal-Key / Modal-Secret"| W["webhook"]
C -->|"process.spawn()"| P["process worker<br/>(core.pipeline.run)"]
W -->|"process.spawn()"| P
P -->|"parse / classify / extract"| G["Selected inference provider"]
P -->|"enrichment"| X["Spotify · YouTube · TMDB ·<br/>web scrape"]
P -->|"POST /v1/catalog/projection"| K["Soma catalog"]
P -->|"rows: tasks, categories,<br/>execution log"| H["Soma hub tables"]
uv sync # install everything
just test # unit tests (no network)
just check # ruff lint + format check
just dev # live-reload deploy against real Modal infra (modal serve)
just test-integration # real selected-provider calls (keys via 1Password)
just recept "Buy eggs $ groceries" # send one thought to the deployed webhookLocal runs against real services use 1Password injection — never plaintext on disk:
op run --env-file=.env.tpl -- uv run <cmd>Server operators select AI_PROVIDER=cloudflare and supply
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID through the app secret
manifest. CLOUDFLARE_MODEL optionally overrides the default Gemma model;
validate any replacement before using it. Device enrollment is unchanged:
Receptor holds only its Synapse URL and app-issued credential.
Cloudflare requests use temperature zero, thinking disabled, and a 2048-token completion limit. Incomplete responses and provider errors fail explicitly; Cloudflare errors never invoke Gemini. The selected provider's daily allowance still applies. Workers Free rejects requests beyond its allocation instead of billing overages; see the provider pricing.
just test-integration exercises the selected provider's semantic cases.
SYNAPSE_CLOUDFLARE_LIVE=1 additionally enables
tests/test_cloudflare_live.py, which runs real inference through the complete
journal into a synthetic hub and verifies duplicate delivery. It writes no
production rows.
Everything else is code; these are one-time console/dashboard actions:
- Modal auth (local):
uv run modal token new. - Modal Proxy Auth Token: Modal dashboard → Settings → Proxy Auth Tokens → mint a token for the operator
webhook(just recept). The webhook rejects requests withoutModal-Key/Modal-Secretheaders. - Google API key: mint a YouTube Data API v3 key in the Google Cloud console (APIs & Services → Credentials) and put it on the 1Password item that
.env.tplreferences. - CI secret:
gh secret set OP_SERVICE_ACCOUNT_TOKENwith a 1Password service-account token that can read the project's vault (the one.env.tplreferences). - A workspace's Soma hub: enroll a hub credential for the workspace (tables read/write on its tables, files read/write on its execution
files_prefix) and store it withjust workspace set-secrets <id>.
prompts.yaml holds the prompt templates and one stanza per category. A stanza says where the category writes and how to read text into it; the catalog says what the column accepts. A workspace's overlay supplies its own workflow bindings and may reword anything (just workspace pull <id> <dir> / push <id> <dir> round-trip it).
| Field | Usage |
|---|---|
description |
The classifier prompt: decides whether a capture belongs to this category. |
table |
The Soma table it writes (tasks take theirs from the workspace's workflow.tasks binding). |
columns |
Field name → catalog column for fields written on create. |
capture_columns |
Media fields the user may edit on an existing row (never forced, no default). |
constants, match_on, review_if_missing, saved_column |
Fixed columns, the natural key a repeat capture updates, unfillable fields that flag needs_review, the explicit-save flag. |
properties |
The fields the extractor fills, in prompt order. |
enabled |
Boolean, default true. Set false in a workspace overlay to remove a category from classification, catalog projection and media-gateway routing. Tasks must remain enabled as the fallback. |
confirm_before_save |
Movie/TV/YouTube boolean, default false. Create a confirmation task with the resolved media link and requested changes instead of inserting or editing a media row. Gateway-only captures return needs_review. |
instruction: how to read this field from the text. Placeholders:{current_date}(Eastern time, or the workspace's task day),{raw_text},{place_tags}.allowlist: narrows the catalog's options to the values Synapse may choose; the instruction then owns their meaning. A value the catalog lacks is dropped. On a field with no column (Capture Intent) it is the field's whole vocabulary.required:truemakes the extractor always answer a field the catalog leaves optional.
Type, catalog required, options with their meanings and defaults come from the catalog; the store's enforced invariants are stated in the prompt.
- Core Syntax
@splitter: separate multiple distinct items in one message.$context: define the Project, Date, Status, or category hint.
- Defaults (if not specified)
- Tasks: Status
To Do| TagChore| PriorityHigh| DateToday(the workspace's task day) - Every other default is the Soma catalog's: a new movie, show, video or podcast starts
Not Started, an activitySomeday, a groceryOn List
- Tasks: Status
- Category cheatsheet
- Tasks (default):
Update my profile photo - Projects:
Refactor code $ Synapse(strict: must name the project in context; always a task linked to the project) - URLs: auto-route to YouTube, Podcasts (Spotify/TAL), or Bookmarks
- Dates/status:
Cancel the gym trial $ Jan 1·The Matrix $ movie priority
- Tasks (default):
- Batch example:
Renew passport @ https://youtu.be/xyz @ Buy eggs $ groceries
app.py # the ONLY file that imports modal
src/core/ # business logic (pipeline, ai_engine, handlers, workflow, ...)
src/core/template/prompts.yaml # prompts + per-category table wiring and phrasing
src/core/catalog.py # the Soma catalog slice prompts are built from
src/core/workspace.py # a user's overlay + credentials, the active workspace
store.py # durable key -> JSON store on the synapse-state Volume
tests/ # pytest suite (unit + test_integration.py for real inference)
scripts/ # operator clients: workspaces, device tokens, the webhook
.env.tpl # secrets manifest (op:// refs)
Push to main → GitHub Actions runs pytest -m "not integration", syncs the app secrets and runs modal deploy app.py (Modal tokens loaded from 1Password).
Synapse uses a dedicated Google Cloud project and a dedicated API key restricted
to generativelanguage.googleapis.com. Enable that API, attach the project
to the intended billing account, and store the key as GEMINI_API_KEY in
this project's environment. Never copy an API key from another application.
Gemini request quotas are per project, so separate keys in one project do
not isolate quota (Gemini rate limits).
Verify a small generateContent request with the configured model before
syncing a replacement key; listing models does not verify billing credits.
A workspace binds its Soma tasks, executions and projects tables through its
workflow mappings; tasks and executions are required. Every capture carries
a canonical UUID capture_id, persisted by the caller across retries.
Authentication fixes the workspace. The server rejects a workspace mismatch or
reuse of a capture ID with changed input. Two separately submitted captures may
have identical text and still produce distinct output identities.
The operational journal freezes parser/extractor decisions, output mappings, row IDs and bodies before delivery. Retry uses checked insert-only writes, so completed, reviewed and tombstoned output rows remain unchanged. Logging failure after a task write retains the task ID for recovery. Execution text is never truncated.
Execution bindings may specify retained_fields (logical mapped property names),
max_inline_bytes (default 131072 UTF-8 bytes), and a dedicated files_prefix.
Oversized selected text is retained through the hub file API using a
content-addressed key and If-None-Match: *. The complete original is frozen in
the journal before upload. Every upload, including an already-existing result,
is read back and checked byte-for-byte before a row can reference it. The row
contains a readable file link, byte count and SHA-256. Upload failure leaves the
output pending; a retry cannot silently replace the original or create a
reference to an unverified file. The workspace token needs read/write grants for
only its configured file prefix in addition to the required workflow tables.