Skip to content
Pilotoo
v0.1

Local backend

@pilotoo/local-server is the backend for a user’s own machine. It speaks the same REST and socket.io protocol as a shared gateway, so a chat-core client works unchanged, with no PostgreSQL, no remote identity and no shared gateway. Use it to build a desktop app. The repository’s apps/desktop is one, and any app that can run a Node 22 process can do the same (Hosting it from another app).

One call starts it. It listens on 127.0.0.1 and hands back the address.

apps/desktop/sidecar/launcher.mjs
import { startLocalServer } from '@pilotoo/local-server';
const server = await startLocalServer({
configDir: '/path/to/app-data/config', // agents/, profiles/, providers/, skills/, templates/
dataDir: '/path/to/app-data/data', // local.db
workspaceDir: '/path/to/app-data/workspace', // where the agent's shell works
execution: 'local-unsandboxed', // required
defaultAgentId: 'orchestrator', // recommended
allowedOrigins: ['tauri://localhost'], // the page's origin(s)
host: { tools: myToolRegistry }, // optional: PilotooModule options a host may set
});
// server.url 'http://127.0.0.1:51234'
// server.token 'per-launch secret'
await server.close(); // stops listening, then closes the SQLite file; safe to call twice

It resolves to { url, token, close() }. Everything that can be refused is refused before anything is opened: a missing execution, a missing directory, a bad port, a weak token.

Pass the base URL and the token to chat-core. It sends the token as Authorization: Bearer on REST and as auth.authorization in the socket handshake.

<ChatProvider baseUrl={server.url} getToken={() => server.token}>

A relaunch gets a new port and a new token, so hand the address over again each launch. The desktop app does this with a {url, token} hand-off: see Hosting it from another app.

Without defaultAgentId, a conversation starts on the agent AGENT_DEFAULT_ID names (the built-in seed agent when it is unset), if the folder has it, and otherwise on the first active agent in the folder, without any error.

Server-side tools are registered in code through host.tools. The desktop sidecar registers web_search and an opt-in ask_human this way (apps/desktop/sidecar/host-tools.mjs). Tools the webview serves are client tools and need nothing here.

A skill is <skillsDir>/<name>/SKILL.md with a name and a description in its frontmatter.

More on skills: Skills.

Option Meaning
configDir, dataDir, workspaceDir Required, made absolute, never searched for. Nothing is derived from a repository layout, so it works from node_modules. dataDir and workspaceDir are created when missing.
skillsDir Where skills are read from. Default <configDir>/skills.
globalSkillsDir A read-only skills folder beside it, for a shared layer a host syncs onto the machine. It ranks after skillsDir, so a local skill wins a name both hold. The skill config tools see skillsDir only.
configLocation How a client names configDir in the path of a profile, provider or agent record. Default .pilotoo; a host whose folder is the user’s own passes e.g. ~/.myapp/assistant. Never derived from configDir.
execution Required, no default. The only value today is 'local-unsandboxed'. See Security.
defaultAgentId The agent a conversation without one starts on (AGENT_DEFAULT_ID).
port Default 0: the OS picks a free port, so several apps or instances never collide.
token Default: 256 random bits per launch (base64url). A host token under 16 characters is refused. There is no option to turn the token off.
allowedOrigins Required. See Security.
printTo A stream. When given, one line {"url":"...","token":"..."} is written once the server listens, for a host that starts this in a child process.
env The environment providers read API keys from. Default process.env; copied, never modified. DATABASE_URL and AUTH_IDENTITY_URL are always dropped.
logger Nest’s logger. Default errors and warnings (Nest writes warnings to stdout). false silences it.
host tools, serviceName, usageSink, modelProviders, skillSources, knowledge, attachmentStore, modelProfileSource, modelGateway of PilotooModule.forRoot, unchanged.