Skip to main content

HumanLayer Repository - Architectural Deep Dive

Analysis Date: 2025-10-14
Focus: Technical architecture, design patterns, and implementation details

Architecture Overview​

The HumanLayer/CodeLayer system implements a multi-process, event-driven architecture with clear separation of concerns across four main components. The design prioritizes local-first operation, protocol flexibility, and real-time coordination between AI agents and human oversight.

Core Components Analysis​

1. HLD Daemon (hld/) - The Orchestration Hub​

Technology Stack:

  • Go 1.24.0: Latest Go with modern language features
  • Gin Framework: HTTP server with built-in CORS support
  • SQLite: Embedded database with mattn/go-sqlite3 driver
  • Viper: Configuration management with environment variable precedence
  • Google UUID: Session and entity identification

Core Responsibilities:

// Main daemon structure from /home/halcasteel/humanlayer/hld/daemon/daemon.go
type Daemon struct {
config *config.Config
store store.Store
eventBus *bus.EventBus
approvalMgr *approval.Manager
sessionMgr *session.Manager
httpServer *http.Server
rpcServer *rpc.Server
mcpServer *mcp.Server
}

Multi-Protocol Design:

  1. JSON-RPC over Unix Sockets (/home/halcasteel/humanlayer/hld/PROTOCOL.md:5-14)

    • Primary daemon communication
    • Socket at ~/.humanlayer/daemon.sock with 0600 permissions
    • Line-delimited JSON for message framing
    • Async event subscriptions with heartbeat (30s intervals)
  2. REST API with SSE (/home/halcasteel/humanlayer/hld/api/openapi.yaml)

    • HTTP API for external integrations
    • OpenAPI 3.0 specification with auto-generated handlers
    • Server-Sent Events for real-time updates
    • CORS support for web-based clients
  3. Model Context Protocol (MCP) (/home/halcasteel/humanlayer/hld/mcp/server.go)

    • AI agent integration layer
    • Provides request_permission tool for Claude Code
    • Bridge between MCP and internal JSON-RPC protocols

Event Bus Architecture (/home/halcasteel/humanlayer/hld/bus/events.go:15-45):

type EventBus struct {
subscribers map[EventType][]chan<- Event
mu sync.RWMutex
}

// Event types for cross-component coordination
type EventType string
const (
EventTypeNewApproval EventType = "new_approval"
EventTypeApprovalResolved EventType = "approval_resolved"
EventTypeSessionStatusChanged EventType = "session_status_changed"
)

2. HLYR CLI (hlyr/) - The Integration Layer​

Technology Stack:

  • TypeScript 5.x: Type-safe Node.js implementation
  • Commander.js: CLI framework with subcommand support
  • MCP SDK: Model Context Protocol client implementation
  • Clack Prompts: Interactive command-line interfaces

Key Integration Patterns:

MCP Server Implementation (/home/halcasteel/humanlayer/hlyr/src/mcp.ts:19-89):

export async function startClaudeApprovalsMCPServer() {
const server = new Server(
{ name: 'humanlayer-claude-local-approvals', version: '1.0.0' },
{ capabilities: { tools: {} } }
)

// Register the request_permission tool
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'request_permission',
description: 'Request permission to perform an action',
inputSchema: {
type: 'object',
properties: {
tool_name: { type: 'string' },
input: { type: 'object' },
tool_use_id: { type: 'string' }
}
}
}]
}))
}

Daemon Client Pattern (/home/halcasteel/humanlayer/hlyr/src/daemonClient.ts:25-67):

export class DaemonClient {
private requestId = 0

async request(method: string, params: any): Promise<any> {
const message = {
jsonrpc: '2.0',
method,
params,
id: ++this.requestId
}

// Unix socket communication with error handling
const conn = await this.connect()
await this.send(conn, message)
return await this.receive(conn)
}
}

3. Desktop UI (humanlayer-wui/) - The Human Interface​

Technology Stack:

  • React 19.1.0: Latest React with concurrent features
  • Tauri 2.x: Rust-based desktop framework
  • TypeScript 5.6.2: Full type coverage
  • TailwindCSS 4.1.10: Utility-first styling
  • Zustand: Lightweight state management
  • Radix UI: Unstyled, accessible component primitives

Architectural Patterns:

Tauri Bridge Pattern (/home/halcasteel/humanlayer/humanlayer-wui/src-tauri/src/daemon.rs:15-45):

// Rust-based daemon client for high-performance communication
#[tauri::command]
async fn fetch_sessions(state: State<'_, DaemonState>) -> Result<Vec<Session>, String> {
let client = &state.daemon_client;
match client.request("FetchSessions", serde_json::Value::Null).await {
Ok(sessions) => Ok(sessions),
Err(e) => Err(format!("Daemon request failed: {}", e))
}
}

React State Management (/home/halcasteel/humanlayer/humanlayer-wui/src/AppStore.ts:89-134):

interface StoreState {
sessions: Session[]
approvals: Approval[]
pendingUpdates: Map<string, PendingUpdate>

// Optimistic update pattern
updateSessionOptimistic: (sessionId: string, updates: Partial<Session>) => Promise<void>
}

// Implementation with conflict resolution
updateSessionOptimistic: async (sessionId, updates) => {
// Apply immediately to UI
set(state => ({
sessions: state.sessions.map(s =>
s.id === sessionId ? {...s, ...updates} : s
)
}))

// Track pending changes
const pendingUpdate = { updates, timestamp: Date.now() }
set(state => ({
pendingUpdates: new Map(state.pendingUpdates).set(sessionId, pendingUpdate)
}))

// Sync with daemon and handle conflicts
try {
await daemonClient.updateSession(sessionId, updates)
// Remove from pending on success
set(state => {
const newPending = new Map(state.pendingUpdates)
newPending.delete(sessionId)
return { pendingUpdates: newPending }
})
} catch (error) {
// Revert optimistic update on failure
this.refreshSessions()
}
}

4. Go SDK (claudecode-go/) - The Programmatic Interface​

Design Philosophy:

// Clean, idiomatic Go API from /home/halcasteel/humanlayer/claudecode-go/client.go:15-45
type Client struct {
daemonClient DaemonClient
config Config
}

// Builder pattern for configuration
func New(options ...Option) (*Client, error) {
config := DefaultConfig()
for _, opt := range options {
opt(&config)
}

daemonClient, err := NewDaemonClient(config.SocketPath)
if err != nil {
return nil, fmt.Errorf("failed to create daemon client: %w", err)
}

return &Client{daemonClient: daemonClient, config: config}, nil
}

Data Flow Architecture​

1. Session Lifecycle Management​

Session Creation Flow:

2. Event Sourcing Pattern​

Conversation Events (/home/halcasteel/humanlayer/hld/rpc/types.go:18-45):

type ConversationEvent struct {
ID int64 `json:"id"`
SessionID string `json:"session_id"`
ClaudeSessionID string `json:"claude_session_id"`
EventType string `json:"event_type"` // message, tool_call, tool_result
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
ApprovalStatus string `json:"approval_status,omitempty"`
ToolName string `json:"tool_name,omitempty"`
ToolInput string `json:"tool_input,omitempty"`
CreatedAt time.Time `json:"created_at"`
}

Audit Trail Benefits:

  • Complete conversation reconstruction
  • Approval decision tracking
  • Performance analytics (token usage, timing)
  • Debugging and troubleshooting support

3. Real-time Synchronization​

Subscription Management (/home/halcasteel/humanlayer/hld/rpc/subscription_handlers.go:35-89):

type SubscriptionManager struct {
subscriptions map[string]*Subscription
eventBus *bus.EventBus
mu sync.RWMutex
}

type Subscription struct {
ID string
EventTypes []EventType
EventChan chan Event
HeartbeatTimer *time.Timer
LastSeen time.Time
}

// Heartbeat mechanism prevents dead subscriptions
func (sm *SubscriptionManager) startHeartbeat(sub *Subscription) {
sub.HeartbeatTimer = time.AfterFunc(30*time.Second, func() {
if time.Since(sub.LastSeen) > 35*time.Second {
sm.removeSubscription(sub.ID)
}
})
}

Advanced Design Patterns​

1. Multi-Source Configuration Management​

Hierarchical Configuration (/home/halcasteel/humanlayer/hld/config/config.go:67-123):

// Priority: CLI flags > env vars > config file > defaults
func Load() (*Config, error) {
v := viper.New()

// XDG Base Directory support
v.AddConfigPath(filepath.Join(xdg.ConfigHome, "humanlayer"))
v.AddConfigPath(filepath.Join(xdg.Home, ".humanlayer"))
v.AddConfigPath(".")

// Environment variable mapping
v.SetEnvPrefix("HUMANLAYER")
v.AutomaticEnv()
_ = v.BindEnv("socket_path", "HUMANLAYER_DAEMON_SOCKET")
_ = v.BindEnv("database_path", "HUMANLAYER_DATABASE_PATH")

setDefaults(v)

// Graceful config file handling
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("error reading config file: %w", err)
}
// Continue with env vars and defaults if no config file
}
}

2. Error Recovery Architecture​

Structured Error Hierarchy (/home/halcasteel/humanlayer/hld/store/errors.go:12-45):

// Domain-specific errors with context
type NotFoundError struct {
Type string // "approval", "session", "conversation"
ID string
}

func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s not found: %s", e.Type, e.ID)
}

func (e *NotFoundError) Unwrap() error {
return ErrNotFound
}

// Error wrapping preserves context while enabling Is() checks
func GetApproval(id string) (*Approval, error) {
approval, err := store.GetApproval(id)
if err != nil {
return nil, &NotFoundError{Type: "approval", ID: id}
}
return approval, nil
}

React Error Boundaries (/home/halcasteel/humanlayer/humanlayer-wui/src/components/error-boundary.tsx:45-89):

class SentryErrorBoundaryCore extends React.Component<Props, State> {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
const componentName = this.props.componentName || 'Unknown'
const variant = this.props.variant || 'default'

// Structured error logging with React context
logger.error(`Error in ${componentName} (${variant})`, error, errorInfo)

// Error reporting with context tags
Sentry.captureException(error, {
contexts: { react: { componentStack: errorInfo.componentStack } },
tags: {
errorBoundary: true,
errorBoundaryVariant: variant,
componentName
}
})
}

render() {
if (this.state.hasError) {
// Variant-specific error UI
switch (this.props.variant) {
case 'session-detail':
return <SessionErrorFallback error={this.state.error} onRetry={this.handleReset} />
case 'response-editor':
return <editorErrorFallback error={this.state.error} onRefresh={this.props.handleRefresh} />
default:
return <GenericErrorFallback error={this.state.error} />
}
}
return this.props.children
}
}

3. Performance Optimization Patterns​

Database Connection Pooling (/home/halcasteel/humanlayer/hld/store/sqlite.go:34-67):

type SQLiteStore struct {
db *sql.DB
config StoreConfig
}

func NewSQLiteStore(dbPath string, config StoreConfig) (*SQLiteStore, error) {
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_synchronous=NORMAL")
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}

// Connection pool tuning for SQLite
db.SetMaxOpenConns(1) // SQLite performs best with single connection
db.SetMaxIdleConns(1) // Keep connection alive
db.SetConnMaxLifetime(0) // No connection expiry

return &SQLiteStore{db: db, config: config}, nil
}

Frontend Performance (/home/halcasteel/humanlayer/humanlayer-wui/src/components/internal/ConversationStream/ConversationStream.tsx:67-123):

// Virtual scrolling for large conversations
const ConversationStream: React.FC<Props> = ({ sessionId }) => {
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 })
const [events, setEvents] = useState<ConversationEvent[]>([])

// Incremental loading with pagination
const loadMoreEvents = useCallback(async () => {
const newEvents = await daemonClient.getConversationEvents(
sessionId,
{ offset: events.length, limit: 50 }
)
setEvents(prev => [...prev, ...newEvents])
}, [sessionId, events.length])

// Only render visible events to maintain performance
const visibleEvents = useMemo(() =>
events.slice(visibleRange.start, visibleRange.end),
[events, visibleRange]
)

return (
<VirtualizedList
items={visibleEvents}
renderItem={({ item }) => <ConversationEventRow event={item} />}
onRangeChange={setVisibleRange}
/>
)
}

Security Architecture​

1. Local-First Security Model​

Unix Socket Permissions:

# Socket created with restricted permissions
# From /home/halcasteel/humanlayer/hld/daemon/daemon.go:123
socketPath := config.SocketPath
listener, err := net.Listen("unix", socketPath)

# Set socket permissions to owner-only (0600)
if err := os.Chmod(socketPath, 0600); err != nil {
return fmt.Errorf("failed to set socket permissions: %w", err)
}

No Network Authentication Required:

  • Security via filesystem permissions
  • All communication local to machine
  • No API keys or tokens stored
  • User data never leaves local system

2. Data Protection​

Database Isolation (/home/halcasteel/humanlayer/hld/config/config.go:89-123):

// User-specific database locations
func getDefaultDatabasePath() string {
homeDir, err := os.UserHomeDir()
if err != nil {
return "humanlayer.db" // Fallback to current directory
}
return filepath.Join(homeDir, ".humanlayer", "daemon.db")
}

// Environment-specific database isolation for development
func getTicketDatabasePath(ticket string) string {
homeDir, _ := os.UserHomeDir()
return filepath.Join(homeDir, ".humanlayer", fmt.Sprintf("daemon-%s.db", ticket))
}

Sensitive Data Handling:

  • No API keys stored in database
  • Configuration masking for logs
  • Temporary file cleanup on shutdown
  • Session data encrypted at rest (planned feature)

Scalability Considerations​

1. Horizontal Scaling Patterns​

Multi-Instance Support:

  • Each daemon instance manages independent socket/database
  • Port allocation prevents conflicts during development
  • Session isolation by daemon instance
  • Load balancing via client configuration

2. Performance Monitoring​

Built-in Metrics (/home/halcasteel/humanlayer/hld/rpc/types.go:58-89):

type Session struct {
ID string `json:"id"`
Status string `json:"status"`
CostUSD string `json:"cost_usd,omitempty"`
InputTokens int `json:"input_tokens,omitempty"`
OutputTokens int `json:"output_tokens,omitempty"`
CacheCreationTokens int `json:"cache_creation_tokens,omitempty"`
CacheReadTokens int `json:"cache_read_tokens,omitempty"`
StartTime time.Time `json:"start_time"`
EndTime *time.Time `json:"end_time,omitempty"`
}

Observability Integration Points:

  • Event bus provides metrics collection hook
  • Structured logging with context preservation
  • Performance timing for all RPC operations
  • Error rates and recovery metrics

This architectural analysis reveals a sophisticated, production-ready system with clear separation of concerns, robust error handling, and extensive consideration for performance and security. The multi-protocol design provides flexibility while maintaining type safety across all components.