Operator Protocol Reference
The Operator Protocol defines the wire format and interaction model between the Construct frontend and the Go operator backend over TCP.
Connection Details
- Port: 60100
- Protocol: TCP
- Wire Format: Newline-delimited JSON (NDJSON)
- Encoding: UTF-8
Message Format
All messages are JSON objects terminated with a newline character (\n). Each line is a complete, independent JSON object.
Request Types
send_context_request
Synchronously sends context to an agent and waits for a response. Used for one-shot requests with minimal state overhead.
{
"type": "send_context_request",
"context": {
"project": { "name": "my-project", "path": "/path/to/project" },
"tools": ["bash", "read", "write"],
"userMessage": "analyze this code"
}
}query
Sends a request and retrieves the complete dispatch result synchronously.
{
"type": "query",
"sessionID": "sess_abc123",
"messages": [
{
"role": "user",
"content": [{ "type": "text", "text": "What does this function do?" }]
}
]
}dispatch
Sends a request and returns immediately with a sessionID for streaming results.
{
"type": "dispatch",
"messages": [
{
"role": "user",
"content": [{ "type": "text", "text": "Run the tests" }]
}
],
"tools": ["bash"],
"agentID": "architect"
}stream
Opens a streaming connection for a session. Messages arrive as DispatchResult objects with sessionID matching the requested session.
{
"type": "stream",
"sessionID": "sess_abc123"
}Response Format
All responses are DispatchResult objects with the following structure:
interface DispatchResult {
sessionID: string; // Unique session identifier
agentID: string; // Agent that produced this response
content: ResponseBlock[]; // Response content blocks
turns: Turn[]; // Complete turn history
usage: {
inputTokens: number; // LLM input token count
outputTokens: number; // LLM output token count
};
stopReason: string; // "end_turn" | "tool_use" | "max_tokens"
}Turn Structure
A turn represents a complete request-response cycle:
interface Turn {
request: Request;
response: Response;
toolCalls: ToolExecution[];
}
interface Request {
role: "user" | "assistant";
content: RequestBlock[];
}
interface Response {
role: "assistant";
content: ResponseBlock[];
}
interface ToolExecution {
id: string;
name: string;
input: Record<string, unknown>;
result: string;
error?: string;
executedAt: string; // ISO 8601 timestamp
}Block Types
RequestBlock
Represents input provided by the user or system:
type RequestBlock =
| { type: "text"; text: string }
| { type: "image"; url: string; mimeType: string }
| { type: "file"; path: string; size: number };ResponseBlock
Represents output generated by the agent:
type ResponseBlock =
| { type: "text"; text: string }
| { type: "tool"; id: string; name: string; input: Record<string, unknown> }
| { type: "code"; language: string; source: string }
| { type: "svg"; content: string; width?: number; height?: number }
| { type: "image"; url: string; alt?: string }
| { type: "error"; message: string; code?: string };Stream Events
When using stream request type, events arrive as complete DispatchResult objects. The frontend useAgentSession composable handles:
- Stream Start: First event with
sessionIDand emptycontent - Content Streaming: Events with incremental
contentblocks - Tool Execution: Events with
toolCallsarray populated - Turn Completion: Events with
stopReasonset to "end_turn" - Stream End: Final event indicating completion
Example stream sequence:
{ sessionID: "sess_abc", agentID: "architect", content: [], turns: [], stopReason: "stream_start" }
{ sessionID: "sess_abc", agentID: "architect", content: [{ type: "text", text: "Analyzing..." }], turns: [] }
{ sessionID: "sess_abc", agentID: "architect", content: [{ type: "tool", ... }], turns: [...] }
{ sessionID: "sess_abc", agentID: "architect", content: [...], turns: [...], stopReason: "end_turn" }Connection Lifecycle
Connect
const socket = new WebSocket("ws://localhost:60100");
socket.addEventListener("open", () => {
// Connection established
socket.send(JSON.stringify({ type: "stream", sessionID: "sess_abc" }));
});Reconnect
If connection drops during a stream:
- Detect disconnect via
closeevent - Reconnect to the server
- Send
streamrequest with samesessionID - Continue receiving from last received turn
socket.addEventListener("close", () => {
setTimeout(() => {
const newSocket = new WebSocket("ws://localhost:60100");
// Resume streaming
}, 1000);
});Error Handling
Errors are communicated as ResponseBlock objects with type "error":
{
"sessionID": "sess_abc",
"agentID": "architect",
"content": [
{
"type": "error",
"message": "Tool 'bash' not available in this context",
"code": "TOOL_NOT_AVAILABLE"
}
],
"turns": [],
"stopReason": "error"
}Common error codes:
TOOL_NOT_AVAILABLE: Requested tool not in available tools listTOOL_EXECUTION_FAILED: Tool executed but returned errorAGENT_NOT_FOUND: Specified agent ID does not existSESSION_NOT_FOUND: Session ID does not exist or has expiredINVALID_REQUEST: Request JSON is malformed or missing required fieldsRATE_LIMIT_EXCEEDED: Too many requests in short time windowCONTEXT_TOO_LARGE: Context or message size exceeds limits
Frontend Integration
The useAgentSession composable from @construct-space/sdk handles the protocol details:
import { useAgentSession } from "@construct-space/sdk";
const { dispatch, stream, loading, result } = useAgentSession();
// For immediate response
const result = await dispatch({
messages: [{ role: "user", content: [{ type: "text", text: "help" }] }],
agentID: "architect"
});
// For streaming
const sessionID = await stream({
messages: [...],
agentID: "architect"
});See SDK Reference for complete composable documentation.