Go Build Tags in AgentHub
ποΈ Go Build Tags in AgentHub
Understanding-oriented: Deep dive into how AgentHub uses Go build tags to create flexible, conditional compilation for different deployment scenarios and feature sets.
The Problem: Conditional Feature Compilation
AgentHub needs to support multiple deployment scenarios:
- Development: Fast builds without observability overhead
- Testing: Minimal dependencies for unit tests
- Production: Full observability with tracing, metrics, and monitoring
- Edge deployments: Lightweight versions with reduced features
Traditional approaches like runtime flags have drawbacks:
- Binary bloat: All code included even when unused
- Runtime overhead: Conditional checks in hot paths
- Dependency complexity: All dependencies must be available
- Security concerns: Observable code included in minimal deployments
The Solution: Go Build Tags
Build tags (also called build constraints) allow Go to conditionally include or exclude files during compilation based on specified tags.
How Build Tags Work
Build tags are special comments at the top of Go files:
//go:build observability
// +build observability
package main
// This file is only compiled when 'observability' tag is specified
Build Tag Syntax
Modern syntax (Go 1.17+):
//go:build observability
Legacy syntax (backwards compatibility):
// +build observability
Complex conditions:
//go:build observability && !minimal
//go:build (observability || debug) && linux
//go:build observability,debug
Boolean Logic in Build Tags
| Syntax | Meaning | Example |
|---|---|---|
tag1 && tag2 |
Both tags required | //go:build observability && debug |
tag1 || tag2 |
Either tag required | //go:build observability || debug |
!tag1 |
Tag must NOT be present | //go:build !minimal |
tag1,tag2 |
Either tag (legacy OR) | // +build observability,debug |
AgentHub Build Tag Architecture
File Organization Strategy
agents/publisher/
βββ main.go # Basic version (no observability)
βββ main_observability.go # Full observability version
βββ shared.go # Common code (no build tags)
βββ config.go # Configuration (no build tags)
broker/
βββ main.go # Basic broker
βββ main_observability.go # Observable broker
βββ server.go # Core server logic (shared)
internal/
βββ grpc/ # Generated code (always included)
βββ observability/ # Observability package
βββ config.go # Build tag: observability
βββ metrics.go # Build tag: observability
βββ tracing.go # Build tag: observability
βββ healthcheck.go # Build tag: observability
Build Tag Usage in AgentHub
1. Observable Components
Files that should only be compiled with observability:
//go:build observability
// +build observability
package main
import (
"context"
"log/slog"
"github.com/owulveryck/agenthub/internal/observability"
)
type ObservableAgent struct {
obs *observability.Observability
traceManager *observability.TraceManager
metricsManager *observability.MetricsManager
logger *slog.Logger
}
2. Basic Components
Files that should be excluded when observability is enabled:
//go:build !observability
// +build !observability
package main
import (
"log"
)
type BasicAgent struct {
// Simple struct without observability
}
func main() {
log.Println("Starting basic agent...")
// Basic implementation
}
3. Shared Components
Files without build tags are always included:
package main
// No build tags - always included
import (
"context"
pb "github.com/owulveryck/agenthub/internal/grpc"
)
// Common functionality used by both versions
func processTask(ctx context.Context, task *pb.TaskMessage) error {
// Shared business logic
return nil
}
Build Commands and Results
Development Builds
Basic agent (default):
go build -o bin/publisher agents/publisher/
# Result: Small binary, no observability dependencies
# Files included: main.go, shared.go, config.go
# Files excluded: main_observability.go, observability/*
Observable agent:
go build -tags observability -o bin/publisher-obs agents/publisher/
# Result: Full-featured binary with observability
# Files included: main_observability.go, shared.go, config.go, observability/*
# Files excluded: main.go
Production Builds
Minimal production:
go build -tags "!observability,!debug" -ldflags="-s -w" -o bin/publisher-minimal
# Ultra-small binary for resource-constrained environments
Full production:
go build -tags "observability,production" -ldflags="-s -w" -o bin/publisher-prod
# Full observability with production optimizations
Advanced Build Tag Patterns
1. Environment-Specific Builds
//go:build development
// +build development
// Development-only features
func enableDebugEndpoints() {
// Debug HTTP endpoints, verbose logging
}
//go:build production
// +build production
// Production-only optimizations
func enableProductionOptimizations() {
// Performance tuning, resource limits
}
2. Platform-Specific Builds
//go:build observability && linux
// +build observability,linux
// Linux-specific observability features
import "syscall"
func getLinuxMetrics() {
// Linux-specific system metrics
}
3. Feature Flags
//go:build experimental
// +build experimental
// Experimental features behind build tags
func experimentalEventProcessing() {
// New algorithms not ready for production
}
Build Tag Testing
Testing Observable Code
//go:build observability
// +build observability
package main
import "testing"
func TestObservableAgent(t *testing.T) {
// Tests that only run with observability enabled
agent := NewObservableAgent()
// Test observability features
}
Run observable tests:
go test -tags observability ./...
Testing Basic Code
//go:build !observability
// +build !observability
package main
import "testing"
func TestBasicAgent(t *testing.T) {
// Tests for basic functionality
agent := NewBasicAgent()
// Test core features without observability
}
Run basic tests:
go test ./... # Default excludes observability tests
Performance Impact Analysis
Binary Size Comparison
| Build Type | Binary Size | Dependencies | Startup Time |
|---|---|---|---|
| Basic | ~8MB | Core gRPC only | 50ms |
| Observable | ~15MB | + OpenTelemetry, Prometheus | 150ms |
| Full Production | ~12MB | + Optimizations | 100ms |
Memory Usage Patterns
Basic Agent:
Heap: 10MB baseline
Goroutines: 5-10
Dependencies: minimal
Observable Agent:
Heap: 10MB + 5MB observability overhead
Goroutines: 5-10 + 3-5 observability routines
Dependencies: OpenTelemetry SDK, Prometheus client
Build Tag Best Practices
1. Consistent Naming Conventions
# Files
main.go # Basic version
main_observability.go # Observable version
main_debug.go # Debug version
# Build tags
observability # Observability features
debug # Debug features
experimental # Experimental features
production # Production optimizations
minimal # Minimal builds
2. Clear Documentation
//go:build observability
// +build observability
// This file contains the observable version of the publisher agent.
// It includes distributed tracing, metrics collection, and structured logging.
//
// Build with: go build -tags observability
//
// Features included:
// - OpenTelemetry tracing
// - Prometheus metrics
// - Health checks
// - Graceful shutdown
package main
3. Makefile Integration
# Basic builds
build-basic:
go build -o bin/broker broker/
go build -o bin/publisher agents/publisher/
go build -o bin/subscriber agents/subscriber/
# Observable builds
build-observable:
go build -tags observability -o bin/broker-obs broker/
go build -tags observability -o bin/publisher-obs agents/publisher/
go build -tags observability -o bin/subscriber-obs agents/subscriber/
# Production builds
build-production:
go build -tags "observability,production" -ldflags="-s -w" -o bin/broker-prod broker/
# Development builds
build-dev:
go build -tags "observability,debug" -o bin/broker-dev broker/
4. CI/CD Integration
# .github/workflows/build.yml
strategy:
matrix:
build-type: [basic, observable, production]
steps:
- name: Build Basic
if: matrix.build-type == 'basic'
run: make build-basic
- name: Build Observable
if: matrix.build-type == 'observable'
run: make build-observable
- name: Build Production
if: matrix.build-type == 'production'
run: make build-production
Troubleshooting Build Tags
Common Issues
-
File not included in build
# Check which files are included go list -f '{{.GoFiles}}' ./agents/publisher/ # Verify build tags go build -tags observability -v ./agents/publisher/ -
Conflicting build tags
// Problem: Both files might be included //go:build observability // File A //go:build !observability // File B // Solution: Use more specific tags //go:build observability && !minimal // File A //go:build !observability || minimal // File B -
Missing dependencies
# Observability build fails due to missing imports go build -tags observability ./ # Fix: Ensure all observability dependencies are available go mod tidy
Debug Build Tag Issues
# Show which files would be compiled
go list -f '{{.GoFiles}}' -tags observability ./agents/publisher/
# Show build constraints
go list -f '{{.Imports}}' -tags observability ./agents/publisher/
# Verbose build output
go build -tags observability -v -x ./agents/publisher/
Design Decisions and Trade-offs
Why Build Tags vs Runtime Flags?
Build Tags Advantages:
- β Zero runtime overhead for excluded features
- β Smaller binary size for minimal deployments
- β Clear separation of concerns
- β Compile-time safety
Build Tags Disadvantages:
- β More complex build process
- β Multiple binaries to maintain
- β Cannot change features at runtime
Runtime Flags Advantages:
- β Single binary for all deployments
- β Runtime feature toggling
- β Simpler deployment process
Runtime Flags Disadvantages:
- β All code included in binary
- β Runtime performance overhead
- β Security concerns (observable code in minimal builds)
AgentHub’s Choice
AgentHub chose build tags because:
- Performance Critical: Event processing requires minimal overhead
- Security Conscious: Minimal deployments shouldn’t include observability code
- Resource Constrained: Edge deployments need smallest possible binaries
- Clear Boundaries: Observability is a distinct architectural concern
Integration with AgentHub Architecture
Event Flow with Build Tags
Basic Flow:
Publisher β Broker β Subscriber
(No tracing, minimal logging)
Observable Flow:
Publisher (+ tracing) β Broker (+ metrics) β Subscriber (+ logging)
β β β
Jaeger Prometheus Structured Logs
Observability Package Architecture
//go:build observability
package observability
type Observability struct {
// Only compiled when observability tag is used
Tracer trace.Tracer
Meter metric.Meter
Logger *slog.Logger
}
func NewObservability(config Config) (*Observability, error) {
// OpenTelemetry initialization
// Only available in observable builds
}
Future Considerations
Planned Build Tag Extensions
-
Cloud Provider Tags:
//go:build observability && aws // AWS-specific observability features //go:build observability && gcp // Google Cloud specific features -
Protocol Tags:
//go:build grpc // gRPC transport only //go:build http // HTTP transport only -
Feature Flags:
//go:build eventstore // Event sourcing capabilities //go:build encryption // End-to-end encryption
Build tags are a powerful tool that enables AgentHub to maintain flexibility while optimizing for different deployment scenarios. They provide compile-time feature selection that ensures optimal performance and minimal resource usage across diverse environments.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.