Skip to content
Pilotoo
v0.1

The server

The server side of the kit is two packages. @pilotoo/server is a NestJS module, PilotooModule.forRoot(options), that serves REST routes and a streaming gateway. @pilotoo/agent-runtime is the framework-free core it is built on: it runs an agent, resolves models and tools, and defines the ports your app can implement. Most apps only touch the module.

This is the whole server of the demo API (apps/api):

src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { createAskHumanTool, createWebSearchTool } from '@pilotoo/agent-runtime';
import { PilotooModule } from '@pilotoo/server';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
PilotooModule.forRoot({
env: process.env,
tools: (registry) => {
registry.register(createWebSearchTool(process.env));
registry.register(createAskHumanTool(), { optIn: true });
},
}),
],
})
export class AppModule {}

Agents, model profiles and providers are YAML files in .pilotoo/, so this module needs no other code. See Agents and Models.

For the install steps and the Nest peer dependencies, follow the quickstart.

Two lines in main.ts belong to your app, not the module:

src/main.ts
import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { configureBodyLimit } from '@pilotoo/server';
const app = await NestFactory.create<NestExpressApplication>(AppModule);
configureBodyLimit(app);
app.enableCors({ origin: true, credentials: false });
await app.listen(4317);

Mounting the module serves /health, /protocol, /agents (the catalog), /agent-admin (the agent editor API), /attachments, /usage, /schedules and /model-gateway, plus the websocket gateway that streams runs to a client.

You provide Why
Tools The package registers none. Agents can only call what you register.
Authentication Who is calling. Without configuration, everyone is a local owner.
Model credentials API keys in the environment, read when a model is built.
PostgreSQL (optional) Default storage. You can replace each port instead.

PostgreSQL is decided by which ports you supply. If you supply all five of agentDefinitionSource, agentDefinitionStore (null counts), conversationStore, checkpointer and runEventStorage, no connection is made. A mix is valid when the database is there; without it, boot fails naming the ports that still need it. Background subagent tasks then run only if you also supply backgroundTaskStorage.

To run on a user’s own machine with SQLite instead, see the local backend.

IdentityGuard is registered app-wide, so your own controllers are guarded too. Mark a public route with @Anonymous(), and read the caller in a handler with @CallerIdentity(). Both are exported by @pilotoo/server.

An option left out keeps its default. The ones a minimal server sets are marked (minimal).

Option Default Meaning
usageStore PostgreSQL; null keeps nothing and /usage answers 501 Where every model call is kept. See Usage.
attachmentStore S3 when ATTACHMENTS_S3_BUCKET is set, else off; null is off whatever env says Where uploads live. See Attachments.
scheduleStorage PostgreSQL; null means no schedules Where schedules and runs are kept.
conversationStore, checkpointer, runEventStorage, backgroundTaskStorage PostgreSQL Persistence ports.
database { connectionString } from DATABASE_URL in env The PostgreSQL connection.
Option Default Meaning
identityResolver chosen from env: AUTH_IDENTITY_URL, else everyone is the local owner Who each caller is. See Authentication.
identityHeaders none Request header names handed to the identity resolver as Credentials.headers.
Option Default Meaning
agentDefinitionSource system agents from files, in front of the tenant’s agents in PostgreSQL Every tenant’s agents. See Agents.
agentDefinitionStore the PostgreSQL store; null has no agent editor and /agent-admin answers 501 The write side of the definitions.
modelProfileSource the profile files in .pilotoo/profiles (PILOTOO_CONFIG_DIR) The profiles an agent may name.
modelProviders the registry built from env and the provider files The providers a profile’s model runs on. See Models.
modelGateway none How pilotoo-gateway providers sign in. See Model gateway.
claudeLogin none The Claude sign-in that claude-subscription providers run on. Only a host on the person’s own machine supplies one.
execution the runtime default: the unsandboxed local shell Where the agent’s execute tool runs.
Option Default Meaning
tools (minimal) none (registry: ToolRegistry) => void, or a plain StructuredTool[]. See Tools.
configTools none Tools that let an agent read and change configuration. See Config tools.
skillSources none beyond the skills directory (AGENT_SKILLS_DIR) Host skill sources. See Skills.
skillLayers { globalDir, tenantDir } from AGENT_GLOBAL_SKILLS_DIR and AGENT_TENANT_SKILLS_DIR, each absent when unset The global and per-tenant skill layers.
knowledge none: no agent gets passages The retrieval behind an agent’s knowledge ids. See Knowledge.
Option Default Meaning
scheduleTools false Registers the opt-in schedule_* tools. See Schedules.
usageSink a debug log line per run Where every run’s usage goes.
usagePrices USAGE_PRICES_FILE; null reports tokens only The prices /usage computes costs from.
scheduleCredentials none The credentials a scheduled fire runs with.
Option Default Meaning
env (minimal) process.env Where every provider reads its configuration.
serviceName SERVICE_NAME from env, then 'api' The name /health reports.
configLocation .pilotoo How a client names the configuration folder in the path of every profile, provider and agent record. A shared server keeps the default, so a client never learns a server path.

Each replacement takes the place of the provider with the same injection token, so Test.createTestingModule(...).overrideProvider(token) keeps working.

  • env is read lazily, when a provider is built, so loading a .env file with ConfigModule stays in your app and only has to happen before boot.
  • A replacement modelProfileSource must hold every profile the shipped system agents and templates name, or boot fails.
  • The package does not declare sideEffects: false: Nest decorators and reflect-metadata run at import.

PilotooModule wires the runner for you. A host without Nest does the same wiring:

import { AgentRunner, createDeepAgentGraphFactory } from '@pilotoo/agent-runtime';
const runner = new AgentRunner({ requiredSaver: checkpointer }, models, {
definitions,
profiles,
defaultAgentId: 'assistant',
registry,
createGraph: createDeepAgentGraphFactory(process.env),
warn: (message) => logger.warn(message),
});
Argument What it is
checkpointer { requiredSaver }, any LangGraph BaseCheckpointSaver.
models A ModelResolver. See Models.
definitions, profiles Required. Every run resolves its agent (AgentDefinitionSource), then the profile it names (ModelProfileSource).
defaultAgentId, registry, createGraph Required.
skillSources, knowledge, usage, attachments, tasks, warn Optional. AgentRunnerOptions in packages/agent-runtime/src/agent-runner.ts is the authority.