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-sqlite3driver - 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:
-
JSON-RPC over Unix Sockets (
/home/halcasteel/humanlayer/hld/PROTOCOL.md:5-14)- Primary daemon communication
- Socket at
~/.humanlayer/daemon.sockwith 0600 permissions - Line-delimited JSON for message framing
- Async event subscriptions with heartbeat (30s intervals)
-
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
-
Model Context Protocol (MCP) (
/home/halcasteel/humanlayer/hld/mcp/server.go)- AI agent integration layer
- Provides
request_permissiontool 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.