Operator Architecture
The Construct Operator is a Go-based sidecar process that orchestrates AI agents, manages tool execution, handles state persistence, and coordinates with external LLM providers. It runs as a separate process communicating with the frontend via TCP.
Technology Stack
- Language: Go 1.23
- Dependencies: Minimal (google/uuid, yaml.v3, and standard library)
- Architecture: Modular 29-package system
- Protocol: Newline-delimited JSON over TCP on port 60100
- Concurrency: Goroutines and channels for async operations
- Storage: File-backed JSON with RWMutex synchronization
Project Structure
operator/
├── main.go # Entry point and TCP server
├── agent/ # Agent execution logic
├── runner/ # Agent runner implementation
├── tools/ # Built-in tool definitions
├── mcp/ # MCP client integration
├── providers/ # LLM provider abstractions
├── hooks/ # Hook execution system
├── skills/ # Skill management
├── sessions/ # Session persistence
├── store/ # State store management
├── config/ # Configuration handling
├── utils/ # Utility functions
└── server/ # TCP server and protocolCore Concepts
Agent System
An agent is a configurable AI execution context:
type Agent struct {
ID string // Unique identifier
Model string // Model name (claude-3-opus, etc.)
Provider string // Provider type
SystemPrompt string // System message
Tools []ToolReference // Available tools
Config AgentConfig // Execution parameters
Hooks []HookDefinition // Lifecycle hooks
}
type AgentConfig struct {
Temperature float32
MaxTokens int
TopP float32
TopK int
StopSequences []string
}Execution Flow:
- Receive request with agent ID and content
- Load agent configuration
- Initialize session with history
- Build messages with system prompt and context
- Call LLM provider API
- Process streaming response
- Handle tool calls via tool runner
- Emit events back to frontend
Agent Runner
The runner implements the core execution loop:
type Runner struct {
agent *Agent
provider Provider
toolRegistry ToolRegistry
hooks HookExecutor
state StateStore
}
func (r *Runner) Run(ctx context.Context, req *Request) error {
// Pre-agent hooks
r.hooks.ExecutePre("agent", req)
// Build message context
messages := r.buildMessages(req)
// Stream LLM response
for event := range r.provider.StreamCompletion(messages) {
r.processEvent(event)
}
// Handle tool calls if present
for _, toolCall := range response.ToolCalls {
r.executeTool(toolCall)
}
// Post-agent hooks
r.hooks.ExecutePost("agent", response)
return nil
}Turn Model
A turn represents a single interaction:
type Turn struct {
ID string // Unique turn ID
Request *RequestBlock // User request
Response *ResponseBlock // Agent response
ToolCalls []ToolCall // Tool executions
Usage TokenUsage // Token counts
Timestamp int64 // Creation time
}
type RequestBlock struct {
Content string // User message
Files []string // File references
Metadata map[string]any // Custom metadata
}
type ResponseBlock struct {
Content string // AI response text
ToolCards []ToolCard // Tool execution details
FinalResponse string // Processed response
}Provider Abstraction
Providers handle communication with different LLM backends:
type Provider interface {
// Configuration validation
Validate() error
// Complete sends a request and returns streaming response
Complete(ctx context.Context, req *CompletionRequest) (<-chan CompletionEvent, error)
// Count tokens in message
CountTokens(messages []Message) (int, error)
// Get model info
GetModelInfo(modelID string) (*ModelInfo, error)
}Supported Providers
- Anthropic: Claude models with full streaming support
- OpenAI: GPT-4 and GPT-3.5 with streaming
- DeepSeek: DeepSeek-V3 and other models
- Ollama: Local model support
- OpenRouter: Multi-model routing via OpenRouter API
Provider Configuration:
type ProviderConfig struct {
Type string // "anthropic", "openai", "ollama", etc.
APIKey string // Provider API key
BaseURL string // Custom endpoint (optional)
Model string // Model identifier
MaxTokens int // Token limit
Parameters map[string]any // Provider-specific params
}Tool System
Tool Registry
22+ built-in tools for common development tasks:
File Operations:
read: Read file contentswrite: Write or create filesedit: Edit file content with precisiondelete: Remove fileslistdir: List directory contents
Search and Navigation:
glob: Find files matching patternsgrep: Search file contentsfind: Find files by criteria
Code Execution:
bash: Execute shell commandsnode: Run JavaScriptpython: Run Python scripts
Project Management:
git: Version control operationsnpm: Package management
System:
getenv: Get environment variablessetenv: Set environment variablespwd: Get current working directory
Tool Definition:
type Tool struct {
ID string // Unique identifier
Name string // Display name
Description string // Purpose and behavior
InputSchema JSONSchema // Parameter specification
OutputSchema JSONSchema // Result format
Handler ToolHandler // Execution function
Enabled bool // Available flag
Hooks []HookDefinition // Tool lifecycle hooks
}
type ToolHandler func(ctx context.Context, input map[string]any) (map[string]any, error)Tool Execution
Tools execute within the agent runner:
func (r *Runner) executeTool(toolCall ToolCall) error {
// Pre-tool hooks
r.hooks.ExecutePre("tool", toolCall)
// Get tool from registry
tool := r.toolRegistry.Get(toolCall.Name)
if tool == nil {
return fmt.Errorf("tool not found: %s", toolCall.Name)
}
// Execute tool
result, err := tool.Handler(ctx, toolCall.Input)
// Post-tool hooks
r.hooks.ExecutePost("tool", ToolResult{
Name: toolCall.Name,
Result: result,
Error: err,
})
return err
}MCP Integration
The operator supports external tools via MCP (Model Context Protocol):
type MCPClient struct {
serverURL string
client *mcp.Client
tools []mcp.Tool
}
func (m *MCPClient) GetTools(ctx context.Context) ([]mcp.Tool, error) {
// Connect to MCP server
// Retrieve available tools
// Parse tool definitions
return m.tools, nil
}
func (m *MCPClient) CallTool(ctx context.Context, name string, args map[string]any) (string, error) {
// Format request for MCP server
// Execute tool remotely
// Return result
}MCP Features:
- Tool discovery and registration
- Remote tool execution
- Resource access (files, data)
- Protocol compliance
Session Management
Sessions persist agent interactions:
type Session struct {
ID string // Unique session identifier
AgentID string // Associated agent
CreatedAt int64 // Creation timestamp
UpdatedAt int64 // Last update
Turns []Turn // Conversation history
Metadata map[string]any
}Persistence:
- Sessions stored as JSON files
- One file per session in
data/sessions/ - Automatic save after each turn
- Compression for large histories
Session Store:
type SessionStore interface {
Get(sessionID string) (*Session, error)
Save(session *Session) error
List(agentID string) ([]*Session, error)
Delete(sessionID string) error
}State Store
File-backed JSON storage with RWMutex synchronization:
type StateStore struct {
dataDir string
mu sync.RWMutex
data map[string]any
}
func (s *StateStore) Get(key string) (any, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.data[key], nil
}
func (s *StateStore) Set(key string, value any) error {
s.mu.Lock()
defer s.mu.Unlock()
s.data[key] = value
return s.saveToFile(key)
}State Files
storage.json: General application state
{
"workspaces": [...],
"recentProjects": [...],
"agents": [...],
"version": "1.0"
}settings.json: User configuration
{
"theme": "dark",
"language": "en",
"autoSave": true,
"defaultModel": "claude-3-opus",
"providers": {...}
}pinned.json: Pinned items (tools, skills, spaces)
{
"tools": ["bash", "read", "write"],
"skills": ["code-review", "documentation"],
"spaces": ["architect", "chat"]
}designs.json: Design and template definitions
{
"templates": [...],
"designs": [...],
"customComponents": [...]
}project-settings.json: Per-project configuration
{
"projectName": "construct",
"root": "/path/to/project",
"settings": {...}
}skill-states.json: Skill metadata and state
{
"skills": {
"code-review": {
"enabled": true,
"version": "1.0",
"lastUsed": 1234567890
}
}
}hook-states.json: Hook configuration and state
{
"hooks": {
"pre-tool": [...],
"post-tool": [...],
"pre-agent": [...],
"post-agent": [...]
}
}mcp-states.json: MCP server state and configuration
{
"servers": {
"server-name": {
"url": "...",
"enabled": true,
"tools": [...]
}
}
}Hook System
Hooks allow injection of custom logic at lifecycle points:
type Hook struct {
ID string // Unique identifier
Name string // Display name
Type HookType // "pre-tool", "post-tool", etc.
Target string // Target tool/agent (or "*" for all)
Handler HookHandler // Execution function
Enabled bool // Active flag
Config map[string]any // Custom configuration
}
type HookHandler func(ctx context.Context, data map[string]any) errorHook Types
- pre-tool: Before tool execution (validation, logging, modification)
- post-tool: After tool execution (result processing, cleanup)
- pre-agent: Before agent turn starts
- post-agent: After agent turn completes
Hook Executor:
type HookExecutor struct {
hooks map[string][]Hook
}
func (he *HookExecutor) ExecutePre(hookType string, data map[string]any) error {
for _, hook := range he.hooks[hookType] {
if !hook.Enabled {
continue
}
if err := hook.Handler(context.Background(), data); err != nil {
return err
}
}
return nil
}Skill System
Skills are reusable prompt templates:
type Skill struct {
ID string // Unique identifier
Name string // Display name
Description string // Purpose
Metadata SkillMetadata // YAML frontmatter
Prompt string // Markdown content
Variables []SkillVariable // Template variables
Enabled bool // Available flag
}
type SkillMetadata struct {
Name string
Description string
Model string
Temperature float32
Tools []string
Tags []string
}Skill Files:
Stored as markdown with YAML frontmatter:
---
name: CodeReview
description: Review code for quality issues
model: claude-3-opus
temperature: 0.7
tools:
- read
- grep
tags:
- code
- review
---
# Code Review Skill
You are an expert code reviewer...
## Input
The user will provide code to review.
## Review Focus
- Code quality
- Performance
- Security
- MaintainabilitySkill Execution:
- Load skill template
- Substitute variables
- Create agent with skill tools
- Execute with skill prompt
- Return results
TCP Server
The operator runs a TCP server on port 60100:
func (s *Server) Serve(addr string) error {
listener, err := net.Listen("tcp", addr)
if err != nil {
return err
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go s.handleConnection(conn)
}
}
func (s *Server) handleConnection(conn net.Conn) {
defer conn.Close()
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
var req RequestMessage
if err := json.Unmarshal(scanner.Bytes(), &req); err != nil {
// Send error response
continue
}
// Process request
s.processRequest(conn, &req)
}
}Protocol
Request Format (JSON, newline-terminated):
{
"id": "request-uuid",
"type": "dispatch",
"agentID": "architect",
"sessionID": "session-uuid",
"content": "Deploy the application",
"files": ["src/main.ts"],
"metadata": {}
}Response Format (Streaming, newline-delimited JSON):
{"type": "turn-started", "turnID": "turn-uuid"}
{"type": "response-block", "content": "I'll help..."}
{"type": "tool-call", "name": "bash", "input": {...}}
{"type": "tool-result", "output": "..."}
{"type": "response-chunk", "content": "more text"}
{"type": "turn-completed", "usage": {"input": 100, "output": 200}}Concurrency Model
Uses goroutines and channels for async operations:
- Connection Handling: One goroutine per client connection
- Request Processing: Goroutine pool for parallel execution
- Tool Execution: Spawned goroutines for long-running tools
- Event Streaming: Channel-based event propagation
- Shutdown: Context cancellation for graceful shutdown
Error Handling
Structured error propagation:
type ErrorResponse struct {
Code string `json:"code"`
Message string `json:"message"`
Details map[string]any `json:"details,omitempty"`
Stack string `json:"stack,omitempty"` // Dev mode only
}Error Codes:
INVALID_REQUEST: Malformed requestAGENT_NOT_FOUND: Agent doesn't existTOOL_ERROR: Tool execution failedPROVIDER_ERROR: LLM API errorSESSION_ERROR: Session operation failedINTERNAL_ERROR: Unexpected server error
Configuration
Configuration from environment and files:
type Config struct {
DataDir string // Operator data directory
OperatorPort int // TCP server port (60100)
LogLevel string // debug, info, warn, error
Providers map[string]ProviderConfig
DefaultModel string
Concurrency int
}Loading:
- Environment variables (CONSTRUCT_*)
- config.yaml in data directory
- Defaults built-in
Database and Persistence
Flat File Storage:
- Uses JSON files in
data/directory - RWMutex for concurrent access
- Automatic backups before write
- Hot reload for config changes
Data Directory Structure:
data/
├── sessions/ # Session JSON files
│ ├── session-uuid-1.json
│ └── session-uuid-2.json
├── storage.json # General state
├── settings.json # User settings
├── pinned.json # Pinned items
├── designs.json # Design definitions
├── project-settings.json
├── skill-states.json
├── hook-states.json
└── mcp-states.jsonStartup Sequence
- Parse Config: Load from environment/files
- Initialize Store: Load all state files
- Register Tools: Built-in and MCP tools
- Load Agents: From configuration
- Initialize Providers: Validate API keys
- Start TCP Server: Listen on port 60100
- Wait for Connections: Accept and handle requests