Skip to content

Gateway Protocol

The gateway exposes a JSON-RPC-like protocol over WebSocket and HTTP/SSE. The schema method returns the complete protocol contract — any client in any language can discover all methods, events, and error codes at runtime.

Connection

WebSocket: Connect to ws://host:port. Send a connect message:

json
{ "type": "connect", "clientId": "my-client" }

The gateway replies with connected:

json
{
  "type": "connected",
  "gatewayId": "gw-abc",
  "protocolVersion": "1.0",
  "apps": ["coding", "research"],
  "sessions": []
}

HTTP/SSE: GET /events?clientId=my-client for the event stream. RPC calls go to POST /invoke with { method, params }.

RPC Envelope

Every request follows the same shape:

json
{ "type": "req", "id": "req-1", "method": "schema", "params": {} }

Success responses:

json
{ "type": "res", "id": "req-1", "ok": true, "payload": { ... } }

Error responses include a structured error code:

json
{
  "type": "res",
  "id": "req-1",
  "ok": false,
  "error": { "code": "NOT_FOUND_RESOURCE", "message": "..." }
}

Schema Discovery

Call the schema method with no params to discover the full protocol at runtime. The response is the complete contract — every method with full JSON Schema for params and response, every event type with its category, and every error code with its description.

Response Shape

typescript
{
  protocolVersion: "1.0",

  // Every method (built-in and custom) with full JSON Schema
  methods: {
    "send": {
      description: "Send message to session",
      builtin: true,
      params: { /* JSON Schema */ },
      response: { /* JSON Schema */ },
      errors: ["NOT_FOUND_RESOURCE", "VALIDATION_REQUIRED"]
    },
    "my-plugin:analyze": {
      description: "Analyze text",
      builtin: false,
      params: { /* JSON Schema */ },
      response: { /* JSON Schema */ },
      roles: ["analyst"]
    },
    ...
  },

  // Every event type with its category
  events: [
    { type: "content_delta",   category: "model" },
    { type: "execution_start", category: "orchestration" },
    { type: "result",          category: "result" },
    { type: "channel",         category: "gateway" },
    ...
  ],

  // All possible error codes with descriptions
  errors: {
    "NOT_FOUND_RESOURCE":     "Resource not found (session, method, tool)",
    "NOT_FOUND_TOOL":         "Tool not found",
    "GUARD_DENIED":           "Access denied by guard (role, permission)",
    "VALIDATION_REQUIRED":    "Required parameter missing",
    "VALIDATION_TYPE":        "Parameter type mismatch",
    "STATE_ALREADY_COMPLETE": "Operation on closed/completed resource",
    "METHOD_ERROR":           "Unhandled error in method handler",
    "INTERNAL_ERROR":         "Unexpected internal error"
  }
}

Method Entries

Every method in the methods record has these fields:

FieldTypeDescription
descriptionstringHuman-readable purpose
builtinbooleantrue for gateway-provided, false for custom/plugin
paramsJSONSchema?Input parameters schema. Absent if method takes no params.
responseJSONSchema?Response payload schema. Absent if method returns void.
errorsstring[]?Error codes this method can produce. References keys in errors.
rolesstring[]?Required user roles. Absent if unrestricted.

Custom methods are registered via the methods config or plugin registerMethod.

Events

Events are pushed to subscribed clients via the event stream. Each event has a type (the discriminant you match on) and a category:

CategoryDescriptionExamples
modelModel output (streaming content, tool calls)content_delta, tool_call_start, message
orchestrationEngine lifecycle (ticks, executions, tool results)execution_start, tick_end, tool_result
resultFinal execution resultresult
gatewayGateway-specific (channels, streaming methods)channel, method:chunk, method:end

Categories enable efficient filtering. A streaming UI subscribes to model events. A persistence layer subscribes to orchestration. A simple request/response client only waits for result.

Error Codes

Error codes on method entries reference keys in the top-level errors record. A send call might return NOT_FOUND_RESOURCE (bad session ID) or VALIDATION_REQUIRED (missing message). The catch-all METHOD_ERROR covers unhandled exceptions.

Error codes come from the framework's error hierarchy (@agentick/shared):

CodeWhen
NOT_FOUND_RESOURCESession, method, or resource doesn't exist
NOT_FOUND_TOOLTool name not found in session
VALIDATION_REQUIREDRequired parameter missing
VALIDATION_TYPEParameter type mismatch
GUARD_DENIEDRole or custom guard rejected the call
STATE_ALREADY_COMPLETEOperation on a closed/terminal resource
METHOD_ERRORUnhandled error in a method handler
INTERNAL_ERRORUnexpected internal error

Built-In Methods

MethodParamsResponseDescription
sendsessionId, message, attachments?{ messageId }Send user message
abortsessionIdAbort current execution
statussessionId?{ gateway, session? }Get status
historysessionId, limit?, before?{ messages, hasMore }Get history
resetsessionId{ ok }Reset session
closesessionId{ ok }Close session
apps{ apps }List apps
sessions{ sessions }List sessions
subscribesessionId{ ok }Subscribe to events
unsubscribesessionId{ ok }Unsubscribe
channelsessionId, channel, payload?{ ok }Publish to channel
channel-subscribesessionId, channel{ ok }Subscribe to channel
schemaSchemaPayloadProtocol discovery
tool-catalogsessionId{ tools }List session tools
tool-confirmsessionId, callId, confirmed, reason?{ ok }Respond to tool confirmation
tool-dispatchsessionId, tool, input{ content }Dispatch tool directly

Custom Methods

Register via gateway config:

typescript
import { createGateway, method } from "@agentick/gateway";

createGateway({
  apps: { myApp },
  defaultApp: "myApp",
  methods: {
    analyze: method({
      description: "Analyze text",
      schema: z.object({ text: z.string() }),
      response: z.object({ sentiment: z.number() }),
      roles: ["analyst"],
      handler: async (params) => ({ sentiment: 0.8 }),
    }),
  },
});

Or via plugin:

typescript
ctx.registerMethod(
  "analyze",
  method({
    schema: z.object({ text: z.string() }),
    response: z.object({ sentiment: z.number() }),
    handler: async (params) => ({ sentiment: 0.8 }),
  }),
);

Both schema (params) and response accept Zod 3, Zod 4, or any Standard Schema. They are converted to JSON Schema in the schema method response.

Alternative Protocol Surfaces

In addition to the native agentick protocol described above, the gateway ships two plugins that expose sessions via standard interfaces:

  • MCP Server (mcpServerPlugin) — standard MCP tools/list + tools/call via Streamable HTTP. Supports per-session tool filtering via toolFilter callback for multi-user deployments. See the Gateway guide.
  • OpenAI-Compatible (openaiCompatPlugin) — POST /v1/chat/completions + GET /v1/models. See the Gateway guide.

These are thin translation layers — they delegate to the same session and tool dispatch machinery documented here. The native protocol remains the most capable surface (full event streaming, schema discovery, channels, etc.).

Released under the ISC License.