MemQL in Visual Studio Code and Cursor (offline language server)
Author .memql files with syntax highlighting, live diagnostics, completion,
hover, signature help, and go-to-definition. The bundled language server runs
locally and uses MemQL Sense, the engine's language-intelligence implementation.
Editing local files works offline, without sign-in.
Connect a trusted workspace to a cluster when you want to inspect definitions and data, run constructs, or train them. Clusters, Deployments, Constructs, Data, and Runs are separate views in the MemQL panel. Saving a file does not deploy it.
Get the extension
The reproducible installation path from this repository is a local VSIX build.
You need Git, Make, Go (the toolchain in go.mod), Node.js 20+, npm, unzip, and
VS Code 1.91+ or a compatible Cursor version, with its code or cursor CLI
on your PATH. Both use the same extension package. Choose the install command
for your editor below.
git clone https://github.com/znasllc-io/memql.gitcd memqlmake vscode-install # Visual Studio Codemake vscode-install EDITOR_CMD=cursor # CursorThen run Developer: Reload Window in the editor you installed into. This rebuilds and replaces your
installed MemQL extension. To create the archive without installing it, use
make vscode-package; the script prints the .vsix path. Packaging defaults
to your host's OS and architecture.
The repository also contains a release workflow targeting Marketplace and Open VSX for Linux x64/ARM64 and macOS x64/ARM64. A configured workflow does not prove that a particular version is available in a registry; this guide uses the source-build path rather than an unverified listing or download link.
Appearance
Open Preferences: Color Theme in Visual Studio Code or Cursor and choose MemQL Light or MemQL Dark. Light uses neutral paper with green accents; Dark uses brighter charcoal surfaces with mint and amber syntax colours. These themes are optional.
memql.appearance controls the extension panels separately: system follows
your editor theme; light and dark select a fixed palette. High-contrast editor
themes take precedence. See the appearance reference.
First session
- Open the reading-list example in a folder. Try completion and hover; read any diagnostics before connecting.
- To run it, trust the workspace and choose MemQL: Add Cluster. Enter an existing cluster's domain and sign in, or choose local installation on a supported host. Docker is needed for a local cluster, not for offline editing.
- Select the cluster. Constructs shows definitions, Data shows authorized rows, and a runnable declaration's CodeLens opens its argument form.
- Follow Your first MemQL program to add and query a row.
What each action changes
| Action | Effect |
|---|---|
| Edit and save | Changes local source only |
| Dry-run | Validates authoring against the cluster without promoting |
| Try in session | Makes supported definitions available to the current authoring session |
| Run | Executes a query, mutation, logic, tool, or automation; writes and other side effects are real |
| Stage | Persists a supported construct for its author; concepts cannot use this private staging tier |
| Promote | Persists and activates a definition on the cluster, subject to authorization and validation |
See training for seeded, trained, staged, and drifted states; runtime panel for connection and execution details; and MemQL OS for the browser workspace opened by Open Console.
The rest of this page is technical reference for the language server and its packaging. The extension guide provides a short product tour.
Productivity tools
MemQL Productivity Tools is a separate extension using the same icon. MemQL owns the cluster connection, authentication, language tooling, and lifecycle; Productivity Tools owns document and template interactions. Cockpit remains the machine worker and one-way folder backup client. Remote editor saves create MemQL revisions and do not overwrite a watched original on another machine.
Both extensions have desktop and browser entry points for macOS and Linux. Windows is unsupported. The browser connection surface uses device approval; native cluster installation and the bundled language server run on desktop. Connected operations retain the cluster's existing editor sign-in role floor.
Productivity Tools supports Markdown source, rendered reading, and split modes. Select a passage in a saved MemQL document's reading view to add feedback; the comment records its author and original revision. Later edits mark old anchors as outdated. Local Markdown remains usable offline, without claiming that local feedback was synchronized. PDF tools view pages, rotate them, add text, and save new revisions. Recovered PDF edits cannot overwrite a newer remote revision; Save As preserves a local copy for comparison.
Create Email from Examples starts with an organization and reference images,
files, or a ZIP of resources, followed by a short brief. MemQL Materializer
captures the sources, uses its model router for visual understanding and
composition, and records the work in Nexus. The output is an editable
*.email.json containing a subject, HTML, and plain-text alternative. Source,
preview, and split modes share the same buffer. Reference processing is explicit;
opening a ZIP in Files still downloads it intact. Draft generation does not
publish a template to Campaigns or send an email.
The source package is in editors/productivity. Build the shared SDK and core
extension, then run npm ci, npm run compile, and npm run package in that
directory to create a local VSIX. Install it alongside MemQL. Packaging does
not publish either extension to a marketplace. References are limited to eight
images, 64 files, 16 MiB expanded data, and 256 KiB of text per composition.
PDF and Word references are not offered by this creation flow yet. Campaign
publication, discussion replies, and jobs that apply approved feedback remain
under development.
Architecture
Visual Studio Code / Cursor (editors/vscode, TypeScript) - vscode-languageclient -- spawns --> memql-lsp (stdio) - memql.tmLanguage.json (baseline offline highlighting, generated) - language-configuration.json (comments / brackets / brace expansion + indentation via onEnterRules + indentationRules -- no longer reliant on the built-in bracket heuristic, and firing even when the `{` carries trailing text; VS Code gates onEnterRules at editor.autoIndent "advanced"+ and indentationRules at "full", the default; generated) - package.json "memql" block (the edition and grammar it was built from, compared with the cluster's at connect) | LSP over stdio (JSON-RPC) v cmd/memql-lsp (Go binary) - LSP front end (tliron/glsp) - workspace loader: BuildOfflineSense(os.DirFS(root)) -- DB-free - per request: buffer text -> sense.{Tokenize,Diagnose,Complete,Hover,SignatureHelp} - after Diagnose publishes: buffer text + tree path -> sense.DiagnoseLoad | in-process Go calls (no gRPC, no network) v component/memql/sense (the UNCHANGED brain) projects vocabulary from component/language/dslspec + .../annotationscmd/memql-lspspeaks LSP over stdio viatliron/glsp. Atinitializeit builds an offline Sense service from the workspace withmemql.BuildOfflineSense-- the DB-free construction the boot-time validation tier already uses (no database, no network). It advertises incremental text sync, push diagnostics, semantic tokens, completion, hover, signature help, go-to-definition, and code actions: the rewrite to edition 2026, as a quick fix and assource.fixAll.- Positions. Sense is 1-based (line and column-in-runes); LSP is 0-based
(line and character-in-UTF-16-code-units). The server converts on every
request (
cmd/memql-lsp/internal/position); the two diverge only on supplementary-plane runes (surrogate pairs). - Registry refresh. On save / watched-file change the server rebuilds Sense (debounced) and atomically swaps it, so a concept or shape added in one file becomes visible to completion/hover in the others.
- Load refusals. Beside Diagnose's syntax and rule diagnostics, the
server runs the engine's load over the open buffer and shows what the load's
lowering refuses -- a field the concept does not declare, a context spec
applied to the row -- as errors on the refused node, coded with the rule id
(
lower_unknown_field). The load runs off the request path after Diagnose has published, so a syntax error never waits on it, and its last result is carried across an edit until the next one answers. A file outside the DSL tree gets no load, because the load needs the file's place in the tree to resolve names two domains share. See load refusals. - The extension (
editors/vscode) is a thinvscode-languageclient. It resolves the server binary in order: thememql.lsp.serverPathuser setting, then a bundledbin/<platform>-<arch>/memql-lsp, thenmemql-lsponPATH.serverPathis machine/user-scoped only, so an untrusted workspace cannot redirect it -- and a workspace-scoped value is refused out loud, with a warning naming the setting, rather than silently ignored. When no binary resolves at all, only the language features are lost: the runtime surface (Clusters / Concepts / Runs) is registered independently and keeps working.
Baseline grammar and language configuration (generated)
editors/vscode/syntaxes/memql.tmLanguage.json colors a file the instant it
opens -- before the server attaches, and in diffs / on GitHub. It is
generated from dslspec (the same source of truth Sense projects from),
never hand-written, so it cannot drift from the language. Semantic tokens from
the server then refine the baseline coloring.
editors/vscode/language-configuration.json -- comment toggling, bracket
matching, auto-closing and surrounding pairs, and the Enter and re-indent
rules -- is generated beside it (memql#5362). The comment tokens, the bracket
pairs and the string quote come from dslspec's punctuation table
(component/language/dslspec/punctuation.go, pinned against the lexer); the
shape of the Enter and indentation patterns is structured data in
cmd/memql-lsp/internal/grammar/languageconfig.go, and a pair added to the
table reaches every pattern on the next regeneration. One command writes both
files:
make vscode-grammar # memql-lsp gen-grammar ... and memql-lsp gen-language-config ...Two staleness tests fail the build when either committed file differs from
what the generator produces -- because the tables moved, or because the file
was edited by hand, which the next regeneration would undo. Regenerate on every
GrammarVersion bump.
Which MemQL the extension speaks, and which the cluster does
The extension ships its own language server, so its completion and diagnostics are the grammar it was packaged with, whatever grammar the cluster it connects to was built with. Three pieces keep the two from drifting apart silently (D25 of the language-freeze program, memql#5362):
- The extension declares what it was built from.
editors/vscode/package.jsoncarries a top-level"memql": {"edition": "2026", "grammarVersion": "..."}, andparser.EditorRelease(component/language/parser/edition.go) names the first extension release that carries the currentGrammarVersion.cmd/memql-lsp/editorparity_test.gofails when the pin differs fromparser.GrammarVersionorparser.Edition, whenparser.EditorReleaseis newer than the extension'sversion, or whenCHANGELOG.mdhas no## <EditorRelease>section naming the grammar and the edition. Its messages name each edit, and one about a moved grammar or edition opens withHas <EditorRelease> been published?, because the answer decides the fix. - The cluster states its language on connect.
ServerHellocarriesedition,grammar_versionandeditor_release, and the extension compares them with its own pin on every connect. A cluster on a newer grammar raises a warning naming the release to install, with Open in Extensions; a cluster on an older grammar raises a notice that completion may offer forms it refuses until the cluster is updated; two grammars that cannot be ordered raise a notice naming both and pointing at the release built for the cluster's grammar. Grammar versions are labels, never ordered: the only orders the extension states are one edition against another andeditor_releaseagainst its own version. A cluster that predates the fields reports nothing, and nothing is shown. Each cluster is mentioned once per grammar per session, and the details are in the MemQL Connection output channel. - Any other client asks the same question. The
DslSpecexport over the stream (spec version1.1.0) carrieseditionandgrammarVersion.
When the grammar moves, the change that moves it carries the extension's
side too, and whether parser.EditorRelease has been published decides how:
- Not published -- that release carries the new grammar: set
memql.grammarVersion, name the new grammar in its## <EditorRelease>section ofCHANGELOG.md, and runmake vscode-grammar. - Published -- a published release cannot change, so the grammar needs a
new one: set
memql.grammarVersion, raise the extension'sversionandparser.EditorReleaseto the new release, give it its own changelog section naming the grammar and the edition, and runmake vscode-grammar.
Setup and development
# Build the server binary.make memql-lsp # -> bin/memql-lsp # Develop the extension.make vscode-depscd editors/vscodenpm cinpm run compile# Press F5 (Extension Development Host), set memql.lsp.serverPath to the built# binary, and open a folder of .memql files.Install / update locally
The language intelligence lives in the bundled memql-lsp binary, so refreshing
your editor after a server or extension change is a build + reinstall + reload
loop. One command does the first two:
make vscode-install # build a fresh .vsix and (re)install it into VS Codemake vscode-install EDITOR_CMD=cursor # ... into Cursor / code-insiders / codiumThen run Developer: Reload Window so the new server process spawns.
scripts/vscode/install.sh wraps package.sh (the build) + code --install-extension --force (the reinstall, which overwrites the currently
installed build); --no-build reinstalls the last-built .vsix, --help lists
the flags. Re-run it whenever you change the server or extension -- there is no
marketplace auto-update for a locally-built extension, so a stale editor is
always a locally-installed VSIX, never a repo artifact.
Packaging
make vscode-package # build for this host, compile the client, package -> .vsixThe offline LSP embeds the engine, so the binary is bundled per platform;
the default build targets the current host. Pass --goos, --goarch, and
--target to scripts/vscode/package.sh for a platform-specific release archive. The vscode-extension
CI lane runs every drift guard under cmd/memql-lsp -- the grammar against
dslspec, language-configuration.json, and package.json's engines.vscode
/ engines.node floors -- then runs this packaging flow. It gates on the
vscode bucket, and because go-checks gates on the go bucket instead, an
editors/vscode-only change (the shape every dependabot bump to the extension
takes) skips go-checks entirely and this lane is the only place those guards
fire (memql#2792). Release targets the VS Code Marketplace and OpenVSX. The
extension's version tracks the GRAMMAR, not the engine release: a grammar
change ships in an extension release that carries it, and
cmd/memql-lsp/editorparity_test.go refuses the change otherwise (see above).
Nothing bundled is tracked in git: editors/vscode/bin/ and *.vsix are
ignored (editors/vscode/.gitignore), so the platform binary is cross-built
fresh at package time. A stale extension on a developer machine is therefore
a locally-installed VSIX, not a stale artifact in the repository -- reinstall
from a fresh make vscode-package to pick up server changes.
0.2.0 -- the editor-intelligence epic (#2600)
Packaged against GrammarVersion 2026.07-null-coalescing-operator, this
release carries the epic end to end:
@offers only the enclosing construct's legal annotations, and body completion is scoped to that construct's blocks and verbs (#2626, #2627).- Member completion after a dot:
actor.,event.,args.,payload.(#2624), driven by the canonical actor envelope (#2623). - Block-specific completion inside
args/filter/insert/update/shape, teaching the post-grammar-epoch short forms (#2628). - Real snippet completions -- body blocks and construct skeletons (#2629).
- Edit-time errors mirroring the engine's load rules:
actor-undeclared(#2622) andactor-unknown-property(#2625).
0.3.0 -- the cross-reference resolution epic (#2728)
This release teaches Sense to RESOLVE cross-references, not just complete them,
backed by a workspace symbol graph built from the dslimports tree (#2729):
- Import diagnostics:
use fylo.concept.{ oder }now flags the wrong kind segment and the undeclared id (#2730), and amutate/query/shape/seed <Concept>bound to a concept that exists nowhere is an error (#2731) -- both conservative, so a legitimate external/global reference never squiggles. - Segment-aware
use-line completion: typingfylo.offers the module kinds, and the brace list offers that module's importable ids, instead of dumping the whole symbol table (#2732). - Kind-filtered invocation completion: in a behavioral body,
query <name>offers only queries,logic <name>only logic, etc. (#2733). - A syntax<->Sense parity gate keeps these tables in step with the grammar (#2734).
0.4.0 -- editor parity (#5362)
The first release that declares the language it was built from: edition 2026,
and the GrammarVersion its section of editors/vscode/CHANGELOG.md names
(the parity test keeps that line, the memql pin and the parser in step).
- At connect, the cluster's edition and grammar are compared with the extension's own, and a cluster on a newer grammar names the release to install (see above).
- The language configuration is generated from
dslspec, like the grammar, and gated the same way. - Writing edition 2026 (epic memql#5363): completion and hover by expression
position, every retired spelling reported as the parse error it is, and the
rewrite to edition 2026 as a quick fix and
on save. The 0.4.0 section of
editors/vscode/CHANGELOG.mdlists them.
Updating: the intelligence lives in the bundled memql-lsp binary, so a new
release ships only when you rebuild it. Run make vscode-package, reinstall the
.vsix (code --install-extension editors/vscode/memql-0.4.0.vsix --force), and
reload the window -- a stale editor is a locally-installed VSIX, never a repo
artifact.
Snippet completions
The extension advertises snippetSupport (vscode-languageclient
default), and the server emits real snippets (#2629): body-block
snippets that open a block with the cursor inside, and construct
skeletons with tabstops at the names you fill. Items that are not
snippets declare InsertTextFormat=PlainText explicitly, so nothing
inserts tabstop syntax literally.