ADR-057: Refactor prism-launcher to prism-launcher as General Control Plane Launcher
Context
The current prism-launcher is narrowly focused on pattern process lifecycle management (RFC-035). However, the control plane architecture (ADR-055, ADR-056) reveals the need for a more general launcher capable of managing multiple types of Prism components:
Current Limitations:
- Name "prism-launcher" implies it only launches patterns
- Architecture assumes all managed processes are pattern implementations
- Process management logic tightly coupled to pattern-specific concepts
- Cannot easily launch other Prism components (proxies, backends, utilities)
- prismctl local command manually launches each component separately
Emerging Requirements:
- Launch prism-proxy instances dynamically
- Launch backend drivers as separate processes (not just patterns)
- Launch auxiliary services (monitoring agents, log collectors)
- Unified process lifecycle for all Prism components
- Control plane coordination for all managed processes (not just patterns)
Control Plane Evolution:
- ADR-055: Proxies register with admin via control plane
- ADR-056: Pattern-launcher registers with admin via control plane
- Need: General launcher that can register ANY managed process type with admin
- Goal: Single launcher binary managing entire Prism stack
Decision
Refactor prism-launcher to prism-launcher as a general-purpose control plane launcher capable of managing any Prism component:
Naming Changes:
- Binary:
prism-launcher→prism-launcher - Package:
pkg/launcher(existing) →pkg/launcher(generalized) - Process types:
Pattern→ManagedProcesswith type field
Architecture Changes:
- Process Type Abstraction:
type ProcessType string
const (
ProcessTypePattern ProcessType = "pattern"
ProcessTypeProxy ProcessType = "proxy"
ProcessTypeBackend ProcessType = "backend"
ProcessTypeUtility ProcessType = "utility"
)
type ManagedProcess struct {
ID string
Type ProcessType
Binary string
Args []string
Env map[string]string
Config interface{} // Type-specific config
IsolationLevel IsolationLevel
HealthCheck HealthCheckConfig
RestartPolicy RestartPolicy
}
- Unified Control Plane Registration:
// Register launcher with admin (not just prism-launcher)
type LauncherRegistration struct {
LauncherID string
Address string
Region string
Capabilities []string // ["pattern", "proxy", "backend"]
MaxProcesses int32 // Not just max_patterns
ProcessTypes []string // Types this launcher can manage
}
- Process Assignment Protocol:
// Admin assigns any process type, not just patterns
type ProcessAssignment struct {
ProcessID string
ProcessType ProcessType // pattern, proxy, backend, utility
Namespace string
Config ProcessConfig // Type-specific configuration
Slots map[string]BackendConfig // Only for patterns
}
type ProcessConfig struct {
// Common fields
Binary string
Args []string
Env map[string]string
Port int32
HealthPort int32
// Type-specific payloads
PatternConfig *PatternConfig // Non-nil if ProcessType=pattern
ProxyConfig *ProxyConfig // Non-nil if ProcessType=proxy
BackendConfig *BackendConfig // Non-nil if ProcessType=backend
}
- Process Manager Generalization:
// pkg/procmgr stays mostly the same but concepts generalize
type ProcessManager struct {
processes map[string]*ManagedProcess // Not just patterns
isolationMgr *isolation.IsolationManager
healthChecker *HealthChecker
}
func (pm *ProcessManager) Launch(proc *ManagedProcess) error {
// Works for any process type
// Pattern-specific logic only fires if proc.Type == ProcessTypePattern
}
- Launcher Command Structure:
prism-launcher \
--admin-endpoint admin.prism.local:8981 \
--launcher-id launcher-01 \
--listen :7070 \
--max-processes 50 \
--capabilities pattern,proxy,backend \
--region us-west-2
Process Type Capabilities:
| Process Type | Description | Examples |
|---|---|---|
| pattern | Pattern implementations | keyvalue-runner, pubsub-runner, multicast-registry |
| proxy | Prism proxy instances | prism-proxy (control + data plane) |
| backend | Backend driver processes | redis-driver, kafka-driver, nats-driver |
| utility | Auxiliary services | log-collector, metrics-exporter, health-monitor |
Backward Compatibility:
- Existing prism-launcher configs continue to work
- ProcessType defaults to "pattern" if not specified
- Pattern-specific fields (slots, isolation) only apply when type=pattern
- Admin can gradually migrate to new ProcessAssignment messages
Rationale
Why Generalize Beyond Patterns:
- prismctl local needs unified launcher for entire stack (admin, proxy, patterns)
- Launching proxy instances dynamically enables horizontal scaling
- Backend drivers may run as separate processes (not in-proxy)
- Monitoring/utility processes need same lifecycle management
- Single launcher binary simplifies deployment
Why Keep pkg/procmgr Intact:
- Process manager is already general-purpose (manages any process)
- Isolation levels work for any process type (not just patterns)
- Health checks, restarts, circuit breakers apply universally
- Only process assignment logic needs generalization
Why Type-Specific Config Payloads:
- Patterns need slot configurations (backends for pattern slots)
- Proxies need admin-endpoint, control-port, data-port
- Backends need connection strings, credentials
- Type-safe configs prevent mismatched assignments
Why Single Binary (not multiple launchers):
- Simplifies deployment (one launcher, many process types)
- Unified control plane protocol (not pattern-specific)
- Easier operational reasoning (one launcher process to monitor)
- Enables mixed workloads (patterns + proxies + backends on same launcher)
Alternatives Considered
-
Separate Launchers per Process Type
- prism-launcher for patterns
- proxy-launcher for proxies
- backend-launcher for backends
- Pros: Clean separation, type-specific code
- Cons: 3+ binaries, 3+ control plane connections, operational complexity
- Rejected because: Single launcher is simpler
-
Keep prism-launcher Name, Generalize Internally
- Pros: No renaming required
- Cons: Misleading name (doesn't launch only patterns), confusing documentation
- Rejected because: Name should reflect capability
-
Launcher Plugins (Launcher launches launchers)
- Pros: Extensible, type-specific launch logic pluggable
- Cons: Over-engineered, unnecessary indirection
- Rejected because: Process types are finite and known
-
Admin Directly Launches Processes (No Launcher)
- Pros: Simpler control plane (no launcher)
- Cons: Admin needs SSH/exec access to hosts, security risk, no local process management
- Rejected because: Launcher provides local process lifecycle management
Consequences
Positive
- Unified Process Management: Single launcher for all Prism components
- Simplified Deployment: One binary instead of multiple launchers
- Flexible Workloads: Mix patterns, proxies, backends on same launcher
- Control Plane Simplicity: One registration protocol for all process types
- prismctl local Integration: Single launcher manages entire local stack
- Horizontal Scaling: Admin can launch proxy instances dynamically
- Backend Process Support: Backend drivers can run as managed processes
- Operational Visibility: All processes visible in admin UI/API
Negative
- Increased Complexity: ProcessConfig becomes type-discriminated union
- Backward Compatibility: Must maintain prism-launcher compatibility
- Testing Surface: Must test all process types (patterns, proxies, backends)
- Type Safety: Config type mismatches possible (pattern config sent to proxy)
- Documentation: Must document all supported process types
Neutral
- Binary renamed from prism-launcher → prism-launcher
- ProcessType enum extensible (add new types in future)
- Admin must validate ProcessType before assignment
- Launcher capabilities advertised in registration (not all launchers support all types)
Implementation Notes
Phase 1: Rename and Generalize Types (Week 1)
- Rename binary:
# Makefile
build/binaries/prism-launcher: pkg/launcher/*.go cmd/prism-launcher/*.go
go build -o $@ ./cmd/prism-launcher
- Introduce ProcessType enum:
// pkg/launcher/types.go
type ProcessType string
const (
ProcessTypePattern ProcessType = "pattern"
ProcessTypeProxy ProcessType = "proxy"
ProcessTypeBackend ProcessType = "backend"
ProcessTypeUtility ProcessType = "utility"
)
- Rename Process → ManagedProcess:
// pkg/launcher/process.go
type ManagedProcess struct {
ID string
Type ProcessType // NEW
Binary string
Args []string
Config ProcessConfig // Generalized
// ... rest stays same
}
Phase 2: Generalize Control Plane Protocol (Week 2)
Update ADR-056 protobuf messages:
message LauncherRegistration {
string launcher_id = 1;
string address = 2;
repeated string capabilities = 3; // ["pattern", "proxy", "backend"]
int32 max_processes = 4; // Renamed from max_patterns
repeated string process_types = 5; // Process types this launcher supports
}
message ProcessAssignment {
string process_id = 1;
string process_type = 2; // "pattern", "proxy", "backend", "utility"
string namespace = 3;
ProcessConfig config = 4;
}
message ProcessConfig {
// Common
string binary = 1;
repeated string args = 2;
map<string, string> env = 3;
int32 port = 4;
int32 health_port = 5;
// Type-specific configs
PatternConfig pattern = 10;
ProxyConfig proxy = 11;
BackendConfig backend = 12;
UtilityConfig utility = 13;
}
message PatternConfig {
string pattern_type = 1; // keyvalue, pubsub, etc.
string isolation_level = 2; // none, namespace, session
map<string, BackendConfig> slots = 3;
}
message ProxyConfig {
string admin_endpoint = 1;
int32 control_port = 2;
int32 data_port = 3;
string proxy_id = 4;
}
message BackendConfig {
string backend_type = 1; // redis, kafka, nats, postgres
string connection_string = 2;
map<string, string> credentials = 3;
}
message UtilityConfig {
string utility_type = 1; // log-collector, metrics-exporter
map<string, string> settings = 2;
}
Phase 3: Update Process Manager (Week 2)
Minimal changes required:
// pkg/procmgr/process_manager.go
func (pm *ProcessManager) Launch(proc *ManagedProcess) error {
// Validate process type
if !isValidProcessType(proc.Type) {
return fmt.Errorf("unsupported process type: %s", proc.Type)
}
// Type-specific validation
switch proc.Type {
case ProcessTypePattern:
if err := validatePatternConfig(proc.Config.PatternConfig); err != nil {
return err
}
case ProcessTypeProxy:
if err := validateProxyConfig(proc.Config.ProxyConfig); err != nil {
return err
}
// ... other types
}
// Existing launch logic works for all types
return pm.launchProcess(proc)
}
Phase 4: Update prismctl local (Week 3)
Simplify to use single prism-launcher:
// cmd/prismctl/cmd/local.go
components := []struct {
name string
binary string
args []string
}{
{
name: "prism-admin",
binary: filepath.Join(binDir, "prism-admin"),
args: []string{"serve", "--port=8980"},
},
{
name: "prism-launcher",
binary: filepath.Join(binDir, "prism-launcher"),
args: []string{
"--admin-endpoint=localhost:8980",
"--launcher-id=launcher-01",
"--listen=:7070",
"--max-processes=50",
"--capabilities=pattern,proxy,backend",
},
},
}
// After launcher starts, admin can dynamically provision:
// - 2 proxy instances (proxy-01, proxy-02)
// - keyvalue pattern with memstore backend
// - Any other components
Phase 5: Admin-Side Assignment Logic (Week 3)
// cmd/prism-admin/process_provisioner.go
func (s *ControlPlaneService) AssignProcess(
ctx context.Context,
req *pb.ProcessAssignment,
) (*pb.ProcessAssignmentAck, error) {
// Select launcher based on capabilities
launchers, err := s.storage.ListLaunchersByCapability(ctx, req.ProcessType)
if err != nil {
return nil, err
}
if len(launchers) == 0 {
return nil, fmt.Errorf("no launchers support process type: %s", req.ProcessType)
}
// Choose launcher with most available capacity
launcher := selectLauncherByCapacity(launchers)
// Send assignment to launcher
if err := s.sendProcessAssignment(launcher.LauncherID, req); err != nil {
return nil, err
}
return &pb.ProcessAssignmentAck{
Success: true,
LauncherId: launcher.LauncherID,
ProcessId: req.ProcessId,
}, nil
}
Phase 6: Documentation Updates (Week 4)
- Update RFC-035 to reflect generalized launcher
- Update MEMO-034 quick start guide
- Create new MEMO for prism-launcher usage patterns
- Update prismctl local documentation
- Migration guide from prism-launcher → prism-launcher
Migration Strategy
Backward Compatibility:
- Keep
prism-launcheras symlink toprism-launcherfor 1-2 releases - Default ProcessType to "pattern" if not specified
- Admin recognizes both old PatternAssignment and new ProcessAssignment
- Gradual migration: existing deployments continue working
Migration Steps:
- Release prism-launcher with backward compatibility
- Update admin to support both protocols
- Documentation shows prism-launcher (note prism-launcher deprecated)
- After 2 releases, remove prism-launcher symlink
References
- ADR-055: Proxy-Admin Control Plane Protocol - Proxy registration
- ADR-056: Launcher-Admin Control Plane Protocol - Pattern-launcher registration
- RFC-035: Pattern Process Launcher - Original prism-launcher design
- MEMO-034: Pattern Process Launcher Quick Start - Usage guide
- pkg/procmgr - Process manager implementation
Revision History
- 2025-10-15: Initial draft - Refactoring prism-launcher to prism-launcher