Remote engine (client-server)¶
By default the CLI runs every verb in-process against the local SQLite store. The client-server mode lets you instead work from a thin local client against an always-on remote engine that owns one canonical archive, so several machines share a single store instead of each drifting into its own copy.
The engine runs extraction, embedding, query, and lint server-side, so the client machine needs neither the LLM key nor the embedding model.
Running the engine¶
On the always-on host (e.g. a Mac Mini):
# Loopback-only (local dev, no key needed):
particles engine serve localhost:8000
# Exposed on the LAN / a private mesh; a real key is mandatory:
export PARTICLES_API_KEY=<a-long-random-secret>
particles engine serve 0.0.0.0:8000
engine serve derives api.bind_host from the bind and runs the
fail-closed gate
before the socket opens: a non-loopback bind without a real
PARTICLES_API_KEY is refused up front rather than serving the write verbs
unauthenticated. (Raw uvicorn particles.api.app:app still works and is still
gated, but you must keep --host and api.bind_host in sync yourself;
engine serve removes that footgun.)
Pointing a client at the engine¶
On the client machine, set the engine URL (config or env) and the bearer token
(a secret: env only, never config.yaml):
export PARTICLES_ENGINE_TOKEN=<the-same-secret-the-engine-expects>
particles query "what do we know about X?" # runs on the engine
With engine.base_url set, the daily verbs (deposit, extract, query,
lint, review, quality, reindex) target the engine. Unset (the default),
everything runs locally as before. The two modes share the same Pydantic models
and the same operations/ code, reached either in-process or over HTTP.
The client token (PARTICLES_ENGINE_TOKEN) is distinct from the engine's
PARTICLES_API_KEY: the engine reads the key it expects, the client reads the
token it presents. For a single operator they may hold the same value.
Network exposure¶
For the MVP, expose the engine over a private path, not a public TLS endpoint:
- Tailscale (or another private mesh): the bind stays effectively private;
point
engine.base_urlat the engine's tailnet name. - SSH local-forward:
ssh -L 8000:localhost:8000 mac-miniand setengine.base_url=http://localhost:8000. The engine binds loopback on its host; nothing is exposed publicly.
Both keep the engine off the public internet and sidestep reverse-proxy client-identification (the per-request loopback gate is not proxy-aware, which is deferred work). A public TLS endpoint is out of scope for the MVP.
What is not remoted yet¶
Since, the
operator verbs route or refuse: with an engine configured, none silently
read or write the local store. The endpoint-backed operator verbs (trust set
/ statement-set, subjects alias / merge / split / list / search /
show, links add / remove / suggest, particle tag / untag / show,
corpus retract / show / links suggest / dismiss, events list / show)
now execute against the engine. import vault and import project also
route in remote mode:
the verb walks the client's tree locally and uploads each matched file to the
engine via deposit_file, so a thin client can bulk-seed the canonical store in
one command (idempotent, continue-on-per-file-error).
The verbs whose endpoint does not exist yet fail loud with an actionable
error naming the verb. Run them on the engine host directly, or unset
engine.base_url to operate on the local store:
export: renders the whole store to the local filesystem; no remote bulk-read route yet.import web-clipper: restores per-captureWEB_PAGEprovenance (uri_r, publication date) from frontmatter, whichdeposit_filecannot carry; stays local-only.corpus:list,delete,prune-orphans, andlinks list.subjects:set-class,delete,gc/prune-empty,confirm,unlink,fix-labels,find-duplicates, andlist --phantoms-only.trust:list,show,set-entry,cascade, and the lens verbs (lens list/show/adopt/unadopt).links list, andparticle search/particle narrative.
The MCP server follows the engine too¶
Since, the local
stdio MCP server (particles mcp serve) routes all its tools through the
same backend seam. With engine.base_url set, the agent's read tools (query,
particle_show / particles_list / particle_search, subjects_*,
events_*, quality_report, lint, list_taxonomies, list_corpus_entries,
links_suggest, corpus_links_suggest) and its belief writes
(particle_assert / particle_supersede / particle_retract / deposit_text)
operate on the canonical engine store, not the laptop's local one. The agent
recalls and asserts against the same archive the CLI does. The
particles://digest/<store> session-start memory resource is rendered by the
engine over HTTP as well.
The MCP server stays local stdio (unchanged install path); the
local-stdio write-trust model and the bearer auth carry
over unchanged: the local server presents the same PARTICLES_ENGINE_TOKEN the
CLI does. The belief-write tools are still offered only when
mcp.write.enabled_stores is set locally; the engine independently gates the write server-side on its own
write-enablement. The belief-write endpoints (POST /particles/{assert,
supersede,retract}) are now reachable by any authenticated HTTP client, not only
the MCP surface. A network-exposed MCP transport (for a non-co-located client
that cannot run a local stdio proxy) remains future work.
Other deliberately-local surfaces (§ Deferred):
- Display-only extras degrade gracefully in remote mode:
extract's page-stats / carry-forward summary,deposit's follow-target list,review's author-enriched detail, and the subject-name / provenance-URI / narrative blocks ofparticle showandcorpus showare shown in local mode only. The routed MCPparticle_show/subjects_showlikewise degrade their enrichment (subject names, provenance URIs) over a remote engine.