Gateway & Sessions
The Gateway manages multiple sessions, routes messages, and provides a method-based API for external access. It exposes a JSON-RPC-like protocol over WebSocket and HTTP/SSE that any client in any language can consume.
Creating a Gateway
import { createGateway, method } from "@agentick/gateway";
import { z } from "zod";
const gateway = createGateway({
apps: { myApp },
defaultApp: "myApp",
methods: {
chat: {
send: method({
description: "Send a chat message",
schema: z.object({
message: z.string(),
sessionId: z.string(),
}),
handler: async ({ message, sessionId }) => {
const ctx = Context.get();
const session = await ctx.session({ id: sessionId });
return session.send({
messages: [{ role: "user", content: message }],
}).result;
},
}),
},
},
});Session Management
The gateway creates and manages sessions on demand:
// Sessions are created via the app
const session = await app.session({ id: "user-123" });
// Each session has its own component tree, state, and timeline
await session.send({ messages: [...] });Custom Methods
Gateway methods are typed RPC endpoints with schema validation, role guards, and ALS context:
methods: {
namespace: {
methodName: method({
description: "Do something useful",
schema: z.object({ /* params */ }),
response: z.object({ /* response */ }),
roles: ["admin"],
handler: async (params) => {
const ctx = Context.get();
return { result: "value" };
},
}),
},
}Both schema (params) and response accept Zod 3, Zod 4, or any Standard Schema. They appear as JSON Schema in the protocol's schema method.
Protocol & Schema Discovery
The gateway's schema method returns the complete protocol contract at runtime — every method with full JSON Schema for params and response, every event type with its category, and every error code. A client in any language can build a full SDK from this single response.
See the Gateway Protocol reference for the full specification.
Plugins
Plugins extend the gateway with methods, HTTP routes, and event handlers:
const plugin: GatewayPlugin = {
id: "my-plugin",
async initialize(ctx) {
// Register RPC methods
ctx.registerMethod(
"analyze",
method({
schema: z.object({ text: z.string() }),
response: z.object({ sentiment: z.number() }),
handler: async (params) => ({ sentiment: 0.8 }),
}),
);
// Mount HTTP routes
ctx.registerRoute("/webhook", async (req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
});
ctx.on("session:created", ({ sessionId }) => {
console.log("New session:", sessionId);
});
},
async destroy() {
// Routes and methods are cleaned up automatically on plugin removal
},
};
gateway.use(plugin);Plugin methods appear alongside built-in and config methods in the schema discovery response with builtin: false. Routes use longest-prefix matching — /v1 catches /v1/models, /v1/chat/completions, etc.
Built-In Protocol Plugins
The gateway ships two protocol plugins that expose sessions via standard interfaces:
MCP Server — any MCP client (Claude Desktop, Cursor, etc.) can connect and use session tools:
import { mcpServerPlugin } from "@agentick/gateway";
gateway.use(
mcpServerPlugin({
sessionId: "default",
path: "/mcp",
include: ["search", "read_file"], // optional: only expose these tools
exclude: ["dangerous_tool"], // optional: hide these tools
}),
);Discovers tools at init via tool-catalog, registers each on an MCP McpServer with StreamableHTTPServerTransport. Tool calls dispatch through the gateway's tool-dispatch method.
For multi-user deployments, use toolFilter to customize tools per MCP client based on the incoming HTTP request:
gateway.use(
mcpServerPlugin({
sessionId: "default",
path: "/mcp",
toolFilter: async (tools, req) => {
const user = await authenticate(req);
return tools.filter((t) => user.allowedTools.includes(t.name));
},
}),
);Each MCP client handshake creates its own McpServer with the filtered tool set. Sessions are tracked by mcp-session-id header and cleaned up automatically.
OpenAI-Compatible — any OpenAI SDK client can send chat completions:
import { openaiCompatPlugin } from "@agentick/gateway";
gateway.use(
openaiCompatPlugin({
pathPrefix: "/v1",
modelMapping: { "gpt-4o": "coding", "gpt-4": "research" },
}),
);Serves POST /v1/chat/completions (streaming + non-streaming) and GET /v1/models. Model names route to gateway apps via modelMapping. Unmatched names fall back to the gateway's default app.
Configuration
The gateway has a built-in configuration system. Config is loaded from a JSON file, validated, and made available to all plugins and application code.
Config File
Create agentick.config.json in your project root:
{
"gateway": {
"port": 8080,
"host": "0.0.0.0"
},
"connectors": {
"telegram": {
"token": "${env:TELEGRAM_BOT_TOKEN}",
"allowedUsers": [12345678]
}
}
}Loading Config
import { loadConfig, bindConfig, createGateway } from "@agentick/gateway";
const configStore = await loadConfig({
path: "./agentick.config.json",
overrides: { gateway: { port: 9999 } }, // CLI flags, etc.
});
bindConfig(configStore);
const gateway = createGateway({
apps: { myApp },
defaultApp: "myApp",
configStore, // pass to gateway
});loadConfig returns a ConfigStore — it does not have side effects. Call bindConfig() yourself to make it globally available.
Reading Config
import { getConfig } from "@agentick/gateway";
const port = getConfig().get("gateway")?.port; // typed
const telegram = getConfig().get("connectors")?.telegram; // typed if augmentedExtending Config Types
Packages declare their config shape via module augmentation. This makes store.get() fully typed without the gateway knowing about every consumer:
// In your package:
declare module "@agentick/gateway" {
interface FileConfig {
myFeature?: {
enabled: boolean;
maxRetries?: number;
};
}
}Connector plugins augment ConnectorConfigs:
declare module "@agentick/gateway" {
interface ConnectorConfigs {
telegram?: {
token: string;
allowedUsers?: number[];
};
}
}Environment Variables and Secrets
String values matching ${env:VAR_NAME} resolve from process.env. String values matching ${secret:KEY} resolve from a SecretStore. Both are resolved before validation. Missing values throw ConfigValidationError.
{
"connectors": {
"telegram": {
"token": "${env:TELEGRAM_BOT_TOKEN}",
"apiKey": "${secret:MY_API_KEY}"
}
}
}Secret-interpolated values are tracked internally. The config RPC method returns a redacted version with secrets replaced by "***".
Schema Validation
Packages register schema fragments that validate their config section:
import { registerConfigSchema } from "@agentick/gateway";
registerConfigSchema("myFeature", {
parse: (data) => mySchema.parse(data),
_output: {} as MyFeatureConfig,
});Fragments are merged at startup. Keys without registered schemas pass through.
Plugin Access
Plugins receive config via PluginContext:
const plugin: GatewayPlugin = {
id: "my-plugin",
async initialize(ctx) {
const myConfig = ctx.config.get("myFeature");
},
};Protocol
The built-in config method returns the redacted configuration:
// Client call:
const response = await client.call("config");
// { config: { gateway: { port: 8080 }, connectors: { telegram: { token: "***" } } } }The config:changed event type is reserved for future hot-reload support.
With Express
import { createExpressMiddleware } from "@agentick/express";
const app = express();
app.use("/api", createExpressMiddleware({ gateway }));This mounts SSE endpoints for streaming and method endpoints for RPC.