diff --git a/.agents/rules/code-style.md b/.agents/rules/code-style.md
new file mode 100644
index 0000000..ea09168
--- /dev/null
+++ b/.agents/rules/code-style.md
@@ -0,0 +1,80 @@
+# Code Style Rules
+
+## General Principles
+
+- Follow standard Go conventions and idioms
+- Keep code simple, readable, and maintainable
+- Prefer composition over inheritance
+- Use short, descriptive variable names in small scopes
+
+## Formatting
+
+- Use `go fmt` for all code formatting (enforced via `make fmt`)
+- Use tabs for indentation (Go standard)
+- Keep line length reasonable (no hard limit, but use good judgment)
+
+## Naming Conventions
+
+### Variables
+- Use short names in small scopes: `ctx`, `g`, `fn`, `err`
+- Use descriptive names for package-level variables and types
+- Boolean variables should read naturally: `started()`, `setup()`
+
+### Functions
+- Private helper functions: lowercase (e.g., `signalCtx`, `block`)
+- Public API functions: PascalCase (e.g., `NewSignals`, `Liveness`)
+- Function types: suffix with `Fn` (e.g., `runFn`)
+
+### Types
+- Exported types: PascalCase (e.g., `Group`)
+- Private types: camelCase (rare in this codebase)
+
+## Package Organization
+
+- Keep related functionality in the same file
+- File naming should reflect the main type or concept:
+ - `group.go` - Group type and lifecycle
+ - `probes.go` - Health check handlers
+ - `signal.go` - Signal handling utilities
+- Test files mirror source files: `group_test.go`, `probes_test.go`
+
+## Error Handling
+
+- Return errors, don't panic (unless truly exceptional)
+- Don't ignore errors in production code
+- In tests, use `t.Error()` or `t.Fatal()` appropriately
+- Error returns should be the last return value
+
+## Context Usage
+
+- Always accept `context.Context` as the first parameter
+- Use context for cancellation and deadline propagation
+- Don't store contexts in structs (store cancel functions if needed)
+- Check `ctx.Done()` in long-running operations
+
+## Concurrency
+
+- Use channels for coordination: `startedCh`, `setupCh`
+- Use `sync.WaitGroup` or `errgroup.Group` for goroutine management
+- Document goroutine lifecycle and ownership
+- Prefer `errgroup.WithContext` for concurrent error handling
+
+## Comments
+
+- Document all exported functions, types, and methods
+- Keep comments concise and up-to-date
+- Use godoc conventions (package comment, function comments)
+- Don't comment obvious code
+
+## Testing Conventions
+
+See `.agents/rules/testing.md` for detailed testing guidelines.
+
+## Linting
+
+All code must pass:
+- `go vet ./...` - Standard Go static analysis
+- `golangci-lint run` - Additional linters
+- `go mod tidy` - Module dependencies are clean
+
+Run `make lint` to check, `make fmt` to auto-fix issues.
diff --git a/.agents/rules/security.md b/.agents/rules/security.md
new file mode 100644
index 0000000..c74c5d0
--- /dev/null
+++ b/.agents/rules/security.md
@@ -0,0 +1,176 @@
+# Security Rules
+
+## General Security Principles
+
+- This is a library package with no network exposure or secrets management
+- Security concerns focus on safe API design and dependency management
+- Follow secure Go coding practices
+
+## Context Security
+
+### Proper Context Cancellation
+- Always respect context cancellation to prevent resource leaks
+- Check `ctx.Done()` in long-running operations
+- Never ignore context cancellation signals
+
+```go
+// Good: Respects cancellation
+func process(ctx context.Context) error {
+ for {
+ select {
+ case <-ctx.Done():
+ return nil // Clean shutdown
+ default:
+ // Do work
+ }
+ }
+}
+```
+
+### Context Storage
+- Never store contexts in structs (Go best practice)
+- Pass contexts as function parameters
+- Store cancel functions if needed, not contexts themselves
+
+## Goroutine Safety
+
+### Prevent Goroutine Leaks
+- Every goroutine must have a clear termination path
+- Use contexts for goroutine lifecycle management
+- Always provide cleanup mechanisms
+
+```go
+// Good: Goroutine respects context
+go func() {
+ <-ctx.Done()
+ _ = srv.Close() // Cleanup
+}()
+```
+
+### Race Conditions
+- Run tests with `-race` flag (enforced in Makefile)
+- Protect shared state with proper synchronization
+- Use channels for goroutine communication
+
+## Signal Handling
+
+### Safe Signal Processing
+- Signal handlers must not panic
+- Clean up resources before exit
+- Use `signal.Stop()` to prevent leaks
+
+```go
+defer signal.Stop(c) // Always cleanup signal notifications
+```
+
+### Signal Channel Buffering
+```go
+c := make(chan os.Signal, len(sig)) // Buffered to prevent blocking
+signal.Notify(c, sig...)
+```
+
+## Error Handling Security
+
+### Don't Leak Sensitive Information
+- Error messages should be descriptive but not expose internals
+- In this library, errors are returned as-is (no sensitive data)
+- Downstream users should wrap errors appropriately
+
+### Fail Safely
+- Default to stopping the service group if any component fails
+- Don't continue processing if setup fails
+- Ensure cleanup happens even on error paths
+
+## Dependency Security
+
+### Minimal Dependencies
+- Only one external dependency: `golang.org/x/sync/errgroup`
+- Review dependency updates carefully
+- Keep `go.mod` and `go.sum` in sync
+
+### Dependency Updates
+```bash
+# Check for updates
+go list -u -m all
+
+# Update dependencies
+go get -u ./...
+go mod tidy
+
+# Verify go.sum
+go mod verify
+```
+
+## HTTP Probe Security
+
+### Probe Endpoints
+- Liveness and readiness probes expose minimal information
+- Only return HTTP status codes (200 or 503)
+- No request body parsing or authentication needed
+- Suitable for exposure to orchestration systems
+
+### No Sensitive Data in Probes
+```go
+// Good: Only status code, no details
+func (g Group) Liveness() http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ if !g.started() {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ }
+ }
+}
+```
+
+## Testing Security
+
+### Cleanup in Tests
+- Always cleanup resources (contexts, goroutines, processes)
+- Use `defer` for cleanup to ensure it happens
+- Kill test processes properly to avoid zombies
+
+```go
+defer func() { _ = cmd.Process.Kill() }()
+```
+
+### Test Isolation
+- Each test should be independent
+- Don't rely on test execution order
+- Clean up global state (signal handlers, etc.)
+
+## Production Usage Security
+
+### Library Consumer Responsibilities
+When using this library, consumers should:
+- Not expose probe endpoints publicly without authentication
+- Handle secrets and configuration outside this library
+- Implement proper logging without leaking sensitive data
+- Use TLS for production HTTP servers
+
+### Example Server Security
+The `examples/server/main.go` is for demonstration only:
+- No authentication or authorization
+- No TLS
+- Listens on all interfaces by default
+- **Not production-ready without hardening**
+
+## Resource Exhaustion Prevention
+
+### Bounded Operations
+- Use contexts with timeouts for bounded operations
+- Don't create unbounded goroutines
+- errgroup limits concurrent operations naturally
+
+### Graceful Degradation
+- Service group stops all components if one fails
+- Prevents partial operation states
+- Clean shutdown on errors
+
+## Code Review Checklist
+
+When reviewing changes:
+- [ ] Are contexts propagated correctly?
+- [ ] Are goroutines guaranteed to terminate?
+- [ ] Is the race detector passing?
+- [ ] Are resources cleaned up on all paths?
+- [ ] Do error messages avoid leaking sensitive data?
+- [ ] Are dependencies necessary and up-to-date?
diff --git a/.agents/rules/testing.md b/.agents/rules/testing.md
new file mode 100644
index 0000000..406dac4
--- /dev/null
+++ b/.agents/rules/testing.md
@@ -0,0 +1,207 @@
+# Testing Rules
+
+## Test Philosophy
+
+- Tests should be fast, deterministic, and isolated
+- Use table-driven tests for multiple similar cases
+- Test both success and failure paths
+- Integration tests verify real binary behavior
+
+## Test Organization
+
+### Unit Tests
+- Located alongside source: `group_test.go`, `probes_test.go`
+- Test package naming: `package service_test` (black-box testing)
+- Each test file tests its corresponding source file
+
+### System Tests
+- Located in `system/` directory: `system/system_test.go`
+- Test compiled binary behavior (integration tests)
+- Verify signal handling and HTTP endpoints work end-to-end
+
+## Running Tests
+
+```bash
+# All tests (lint + unit + system)
+make test
+
+# Unit tests only
+make test-unit
+
+# System tests only
+make test-system
+```
+
+All tests run with:
+- Race detector enabled (`-race`)
+- 1-minute timeout (`-timeout 1m`)
+
+## Test Naming
+
+### Test Functions
+- Start with `Test`: `TestGroup`, `TestProbes`, `TestBinary`
+- Use subtests for variations: `t.Run("success", func(t *testing.T) {...})`
+- Describe what is being tested, not implementation details
+
+### Helper Functions
+- Use descriptive names: `block`, `verifyStatusCode`, `mustGetStatus`
+- Mark test helpers with `t.Helper()`
+- Keep helpers in the same file or `_test.go` file
+
+## Test Structure
+
+### Subtests Pattern
+```go
+func TestGroup(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ // Test happy path
+ })
+
+ t.Run("setup before register", func(t *testing.T) {
+ // Test specific behavior
+ })
+}
+```
+
+### Cleanup Pattern
+```go
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel() // Clean up resources
+
+// Or use Close() method
+defer g.Close()
+```
+
+## Assertions
+
+- Use clear, descriptive error messages
+- Include expected vs actual values: `"expected %d, got: %d"`
+- Use `t.Helper()` in assertion functions
+- Prefer `t.Error()` for non-fatal assertions
+- Use `t.Fatal()` for setup failures that prevent test continuation
+
+## Testing Concurrency
+
+### Channels for Synchronization
+```go
+done := make(chan struct{})
+go func() {
+ defer close(done)
+ // Async work
+}()
+
+select {
+case <-done:
+ // Success
+case <-time.After(timeout):
+ t.Fatal("timeout")
+}
+```
+
+### Testing Blocking Behavior
+```go
+select {
+case <-done:
+ t.Error("expected not done")
+default:
+ // Still blocking, as expected
+}
+```
+
+### Eventually Pattern
+For asynchronous operations that need time to settle:
+```go
+func eventually(t *testing.T, attempts int, f func() bool) {
+ t.Helper()
+ for i := 0; i < attempts; i++ {
+ if f() {
+ return
+ }
+ <-time.After(500 * time.Millisecond * time.Duration(i+1))
+ }
+ t.Fatal("expected success")
+}
+```
+
+## System Tests
+
+### Binary Compilation
+- System tests require building `examples/server` first
+- Build target: `build/server`
+- Command: `make build-test`
+
+### Testing Binaries
+```go
+cmd := exec.Command("../build/server")
+cmd.Env = []string{"EXAMPLE_HTTP_ADDR=:4444"}
+if err := cmd.Start(); err != nil {
+ t.Fatalf("starting command: %v", err)
+}
+defer cmd.Process.Kill()
+```
+
+### HTTP Endpoint Testing
+Use retry logic for services that need startup time:
+```go
+func mustGetStatus(t *testing.T, url string, attempts int, status int) {
+ t.Helper()
+ var got int
+ for i := 0; i < attempts; i++ {
+ resp, err := http.Get(url)
+ if err == nil && resp.StatusCode == status {
+ return
+ }
+ if err == nil {
+ got = resp.StatusCode
+ }
+ <-time.After(time.Duration(500*(i+1)) * time.Millisecond)
+ }
+ t.Errorf("url: %q, expected: %d, got: %d", url, status, got)
+}
+```
+
+## Race Detector
+
+- Always enabled via `-race` flag
+- Catches data races in concurrent code
+- May slow tests significantly (acceptable trade-off)
+- Fix all race conditions immediately
+
+## Test Coverage
+
+- Aim for high coverage of public API
+- Test error paths and edge cases
+- Don't obsess over 100% coverage
+- Focus on testing behavior, not implementation
+
+## Common Patterns
+
+### Testing Probes
+```go
+// Verify status code changes over time
+verifyStatusCode(t, "live", g.Liveness(), http.StatusServiceUnavailable)
+go func() { _ = g.Start() }()
+verifyStatusCode(t, "live", g.Liveness(), http.StatusOK)
+```
+
+### Testing Context Cancellation
+```go
+ctx, cancel := context.WithCancel(context.Background())
+g := service.NewCtx(ctx)
+g.Register(func(ctx context.Context) error {
+ <-ctx.Done()
+ return nil
+})
+
+cancel() // Trigger shutdown
+if err := g.Start(); err != nil {
+ t.Error(err)
+}
+```
+
+## Avoiding Flaky Tests
+
+- Use proper synchronization (channels, contexts)
+- Avoid hardcoded sleeps (use retry loops instead)
+- Make timeouts generous enough for CI environments
+- Test cleanup should always happen (use defer)
diff --git a/.gitignore b/.gitignore
index 567609b..e2a330a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
build/
+CLAUDE.local.md
+.claude/settings.local.json
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..ce121e3
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,142 @@
+# AGENTS.md
+
+Single source of truth for AI agents working on this repository.
+
+## Project Summary
+
+`service` is a lightweight Go library that simplifies running and managing Go services. It provides a group-based lifecycle management system with built-in signal handling, graceful shutdown, and health check probes (liveness and readiness).
+
+The library enables developers to:
+- Register multiple service components that run concurrently
+- Define setup functions that run before service components start
+- Handle OS signals (SIGINT, SIGKILL) for graceful shutdown
+- Expose HTTP liveness and readiness probes for orchestration systems
+- Automatically stop all components if any one fails
+
+## Tech Stack
+
+| Category | Technology |
+|----------|-----------|
+| Language | Go 1.13+ |
+| Concurrency | `golang.org/x/sync/errgroup` |
+| Testing | Go standard testing, race detector |
+| Build | Make, Go toolchain |
+| Linting | `golangci-lint`, `go vet` |
+
+## Repository Layout
+
+```
+service/
+├── group.go # Core Group type and lifecycle management
+├── signal.go # OS signal handling utilities
+├── probes.go # HTTP liveness and readiness handlers
+├── group_test.go # Unit tests for Group
+├── probes_test.go # Unit tests for probes
+├── examples/
+│ └── server/
+│ └── main.go # Example HTTP server implementation
+├── system/
+│ └── system_test.go # Integration tests against compiled binary
+├── go.mod # Go module definition
+├── Makefile # Build and test commands
+├── README.md # User-facing documentation
+└── .gitignore # Git ignore rules
+```
+
+## Key Entry Points
+
+### Public API (`service` package)
+
+**Core Types:**
+- `Group` - Main service orchestration type
+- `NewSignals(sig ...os.Signal) *Group` - Create group with signal handling
+- `NewCtx(ctx context.Context) *Group` - Create group with custom context
+
+**Lifecycle Methods:**
+- `Setup(fn func(ctx context.Context) error)` - Register setup function (runs before processes)
+- `Register(fn func(ctx context.Context) error)` - Register a service process
+- `Start() error` - Start all setup functions, then all registered processes
+- `Close() error` - Cancel context to trigger graceful shutdown
+
+**Health Probes:**
+- `Liveness() http.HandlerFunc` - Returns 503 until group started
+- `Readiness() http.HandlerFunc` - Returns 503 until setup complete and group started
+
+### Example Usage
+
+See `examples/server/main.go` for a complete working example of an HTTP server with liveness/readiness probes.
+
+## Development Workflow
+
+### Setup
+```bash
+# Install dependencies
+go mod download
+
+# Install golangci-lint (required for linting)
+# See: https://golangci-lint.run/usage/install/
+```
+
+### Testing
+```bash
+# Run all tests (lint + unit + system tests)
+make test
+
+# Run only unit tests
+make test-unit
+
+# Run only system tests (requires building example binary first)
+make test-system
+
+# Build example server binary
+make build-test
+```
+
+### Linting and Formatting
+```bash
+# Run linters (go vet + golangci-lint + go mod tidy)
+make lint
+
+# Auto-fix linting issues and format code
+make fmt
+```
+
+### Running Tests Manually
+```bash
+# Run tests with race detector
+go test -race -timeout 1m ./...
+
+# Run tests excluding system tests
+go test -race -timeout 1m $(go list ./... | grep -v /system)
+
+# Run only system tests
+go test -race -timeout 1m ./system
+```
+
+## Agent Rules Reference
+
+This repository follows specific coding conventions, testing practices, and security guidelines:
+
+- **Code Style**: See `.agents/rules/code-style.md`
+- **Testing**: See `.agents/rules/testing.md`
+- **Security**: See `.agents/rules/security.md`
+
+## Architecture
+
+For detailed architecture documentation including sequence diagrams and component relationships, see `docs/ARCHITECTURE.md`.
+
+## Updating This File
+
+When making changes that affect repository structure, workflows, or core concepts:
+
+1. Update the relevant section in this file
+2. If adding new agent rules, create a new file in `.agents/rules/`
+3. Update `CLAUDE.md` if new rule files are added
+4. Keep this file as the single source of truth - avoid duplicating information in README.md
+
+This file should be updated when:
+- New major features are added
+- Directory structure changes
+- Development workflow changes
+- Dependencies are added or significantly updated
+- New conventions or rules are established
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..dcdfe7a
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,12 @@
+@AGENTS.md
+@.agents/rules/code-style.md
+@.agents/rules/testing.md
+@.agents/rules/security.md
+@docs/ARCHITECTURE.md
+
+
+### Response Preferences
+- Be concise. Prefer code over prose.
+- When uncertain about architecture, read `docs/ARCHITECTURE.md` before assuming.
+- When a change would invalidate any section of `AGENTS.md` or `README.md`,
+ flag it and offer to update them per the instructions in AGENTS.md.
diff --git a/README.md b/README.md
index db46a03..180cf23 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,181 @@
-## service
+# service
-_make it easy to run go services_
+A lightweight Go library for managing service lifecycles with built-in signal handling, graceful shutdown, and health check probes.
-### How it works
+## Features
-The service groups allows registration of the different parts of a service.
-These components are run in a group, if any one them fails, the group is stopped.
+- **Simple API**: Register setup and service functions with minimal boilerplate
+- **Graceful Shutdown**: Automatic context cancellation on OS signals or errors
+- **Health Probes**: Built-in HTTP liveness and readiness handlers for orchestration systems
+- **Concurrent Execution**: Run multiple service components concurrently with `errgroup`
+- **Two-Phase Initialization**: Setup phase runs before service components start
+
+## Installation
+
+```bash
+go get github.com/Attest/service
+```
+
+## Quick Start
```go
-group := service.NewSignals(os.Interrupt)
+package main
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "os"
+
+ "github.com/Attest/service"
+)
+
+func main() {
+ // Create a service group that handles SIGINT
+ group := service.NewSignals(os.Interrupt)
+
+ // Register setup functions (run sequentially before processes start)
+ group.Setup(func(ctx context.Context) error {
+ log.Println("Initializing database connection...")
+ // Setup code here
+ return nil
+ })
+
+ // Register service processes (run concurrently)
+ group.Register(func(ctx context.Context) error {
+ srv := &http.Server{Addr: ":8080"}
+
+ // Graceful shutdown on context cancellation
+ go func() {
+ <-ctx.Done()
+ srv.Close()
+ }()
+
+ return srv.ListenAndServe()
+ })
+
+ // Start the service group
+ log.Fatal(group.Start())
+}
+```
+
+## Health Check Probes
+
+The service group exposes HTTP handlers for liveness and readiness probes:
+
+```go
+http.HandleFunc("/live", group.Liveness())
+http.HandleFunc("/ready", group.Readiness())
+```
+
+### Probe Behavior
+
+**Liveness Probe** (`/live`):
+- Returns `503 Service Unavailable` until the group has started
+- Returns `200 OK` once `Start()` is called
+- Indicates the service process is alive
+
+**Readiness Probe** (`/ready`):
+- Returns `503 Service Unavailable` until setup is complete
+- Returns `200 OK` once all setup functions have succeeded and processes are running
+- Indicates the service is ready to accept traffic
+
+## API Reference
+
+### Creating a Group
+
+```go
+// Create group with OS signal handling
+group := service.NewSignals(os.Interrupt, os.Kill)
+
+// Create group with custom context
+ctx := context.WithTimeout(context.Background(), 30*time.Second)
+group := service.NewCtx(ctx)
+```
-group.Setup(func(ctx context.Context) error) {
- // register a setup function.
- // all setup functions are run before registered functions.
- // if it returns an error, the group is stopped.
+### Registering Functions
+
+```go
+// Setup: runs sequentially before processes start
+group.Setup(func(ctx context.Context) error {
+ // Initialization logic
+ return nil
})
+// Register: runs concurrently after setup completes
group.Register(func(ctx context.Context) error {
- // register a function that accepts a context
- // if it returns an error, the group is stopped
+ // Service logic
+ <-ctx.Done() // Wait for cancellation
+ return nil
})
+```
-// the group exposes two http probes
-// liveness reports service unavailable until all the setup functions have completed
-// readiness reports service unavailable until all the registered functions have started
-group.Liveness()
-group.Readiness()
+### Starting and Stopping
-group.Register(httpServer())
+```go
+// Start the group (blocks until all processes complete)
+if err := group.Start(); err != nil {
+ log.Fatal(err)
+}
-// run the service
-log.Fatal(group.Start())
+// Manually trigger shutdown
+if err := group.Close(); err != nil {
+ log.Fatal(err)
+}
```
+
+## How It Works
+
+1. **Setup Phase**: All registered setup functions run sequentially. If any fails, the group stops immediately.
+2. **Process Phase**: All registered processes run concurrently using `errgroup`. If any process returns an error, the context is canceled and all processes are stopped.
+3. **Shutdown**: When the context is canceled (via signal, error, or manual close), all processes receive the cancellation signal and should clean up gracefully.
+
+## Examples
+
+See the [examples/server](examples/server/main.go) directory for a complete working example of an HTTP server with health check probes.
+
+## Development
+
+### Requirements
+
+- Go 1.13 or later
+- `golangci-lint` for linting
+
+### Running Tests
+
+```bash
+# Run all tests (lint + unit + system)
+make test
+
+# Run only unit tests
+make test-unit
+
+# Run only system tests
+make test-system
+```
+
+### Code Formatting
+
+```bash
+# Check linting
+make lint
+
+# Auto-fix and format code
+make fmt
+```
+
+## Architecture
+
+For detailed architecture documentation including sequence diagrams and design decisions, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
+
+## Contributing
+
+This repository is maintained by [@Attest/audience-accounts](CODEOWNERS).
+
+For AI agents and automated tools:
+- See [AGENTS.md](AGENTS.md) for comprehensive repository documentation
+- See [CLAUDE.md](CLAUDE.md) for Claude-specific instructions
+
+## License
+
+Copyright © Attest
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..c56e33e
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,357 @@
+# Architecture
+
+## Overview
+
+The `service` package provides a lightweight orchestration layer for Go services. It manages the lifecycle of multiple concurrent components with support for graceful shutdown, setup phases, and health check probes.
+
+## Core Concepts
+
+### Service Group
+The `Group` is the central orchestration type that:
+- Manages setup and runtime phases
+- Coordinates multiple concurrent processes
+- Handles context cancellation and signal propagation
+- Provides health check endpoints
+
+### Lifecycle Phases
+
+1. **Created**: Group initialized, functions registered
+2. **Started**: `Start()` called, setup functions begin
+3. **Setup Complete**: All setup functions finished successfully
+4. **Running**: All registered processes running concurrently
+5. **Shutting Down**: Context canceled (signal or error)
+6. **Stopped**: All processes terminated
+
+## Component Diagram
+
+```mermaid
+graph TB
+ subgraph "service Package"
+ Group[Group
Lifecycle Manager]
+ Signal[signalCtx
Signal Handler]
+ Probes[Health Probes
Liveness/Readiness]
+ end
+
+ subgraph "Dependencies"
+ ErrGroup[errgroup.Group
golang.org/x/sync]
+ Context[context.Context
Go stdlib]
+ HTTP[net/http
Go stdlib]
+ end
+
+ subgraph "User Code"
+ Setup[Setup Functions]
+ Processes[Service Processes]
+ Server[HTTP Server]
+ end
+
+ Signal --> Context
+ Group --> Context
+ Group --> ErrGroup
+ Group --> Signal
+ Probes --> Group
+ Server --> Probes
+ Setup --> Group
+ Processes --> Group
+
+ style Group fill:#4a90e2
+ style Signal fill:#7ed321
+ style Probes fill:#f5a623
+```
+
+## Data Flow
+
+### Group Structure
+
+```mermaid
+classDiagram
+ class Group {
+ -context.Context rootCtx
+ -func() rootCancel
+ -chan struct{} startedCh
+ -chan struct{} setupCh
+ -[]runFn setups
+ -[]runFn processes
+ +Setup(fn)
+ +Register(fn)
+ +Start() error
+ +Close() error
+ +Liveness() HandlerFunc
+ +Readiness() HandlerFunc
+ -started() bool
+ -setup() bool
+ }
+
+ class runFn {
+ <>
+ +func(context.Context) error
+ }
+
+ Group --> runFn : contains
+```
+
+### State Machine
+
+```mermaid
+stateDiagram-v2
+ [*] --> Created: NewSignals() or NewCtx()
+ Created --> Created: Setup() / Register()
+ Created --> Started: Start()
+ Started --> SetupRunning: Run setup functions
+ SetupRunning --> SetupComplete: All setups succeed
+ SetupRunning --> Error: Setup fails
+ SetupComplete --> Running: Start all processes
+ Running --> ShuttingDown: Context canceled or process error
+ ShuttingDown --> Stopped: All processes terminated
+ Error --> [*]
+ Stopped --> [*]
+
+ note right of Created
+ Liveness: 503
+ Readiness: 503
+ end note
+
+ note right of Started
+ Liveness: 200
+ Readiness: 503
+ end note
+
+ note right of SetupComplete
+ Liveness: 200
+ Readiness: 200
+ end note
+```
+
+## Sequence Diagrams
+
+### Service Startup
+
+```mermaid
+sequenceDiagram
+ participant U as User Code
+ participant G as Group
+ participant S as Setup Functions
+ participant P as Processes
+ participant E as errgroup
+
+ U->>G: NewSignals(os.Interrupt)
+ U->>G: Setup(fn1)
+ U->>G: Setup(fn2)
+ U->>G: Register(process1)
+ U->>G: Register(process2)
+
+ U->>G: Start()
+ G->>G: close(startedCh)
+ Note over G: Liveness = 200
+
+ loop Each setup
+ G->>S: fn(ctx)
+ S-->>G: nil
+ end
+
+ G->>G: close(setupCh)
+ Note over G: Readiness = 200
+
+ G->>E: WithContext(rootCtx)
+
+ par Concurrent Processes
+ G->>P: process1(ctx)
+ and
+ G->>P: process2(ctx)
+ end
+
+ E-->>G: Wait() returns error
+ G-->>U: error or nil
+```
+
+### Signal Handling
+
+```mermaid
+sequenceDiagram
+ participant OS as Operating System
+ participant SH as signalCtx
+ participant Ctx as Context
+ participant G as Group
+ participant P as Processes
+
+ OS->>SH: SIGINT
+ SH->>Ctx: cancel()
+ Ctx->>G: ctx.Done() closed
+ G->>P: ctx.Done() closed
+
+ par Process Cleanup
+ P->>P: Detect ctx.Done()
+ P->>P: Cleanup resources
+ P-->>G: return nil/error
+ end
+
+ G->>G: errgroup.Wait()
+ G-->>G: All processes stopped
+```
+
+### Health Check Probes
+
+```mermaid
+sequenceDiagram
+ participant K8s as Orchestrator
+ participant L as /live endpoint
+ participant R as /ready endpoint
+ participant G as Group
+
+ Note over G: State: Created
+ K8s->>L: GET /live
+ L->>G: started()?
+ G-->>L: false
+ L-->>K8s: 503 Service Unavailable
+
+ K8s->>R: GET /ready
+ R->>G: started() && setup()?
+ G-->>R: false
+ R-->>K8s: 503 Service Unavailable
+
+ Note over G: Start() called
+ Note over G: State: Started
+
+ K8s->>L: GET /live
+ L->>G: started()?
+ G-->>L: true
+ L-->>K8s: 200 OK
+
+ K8s->>R: GET /ready
+ R->>G: started() && setup()?
+ G-->>R: false (setup not done)
+ R-->>K8s: 503 Service Unavailable
+
+ Note over G: State: Setup Complete
+
+ K8s->>R: GET /ready
+ R->>G: started() && setup()?
+ G-->>R: true
+ R-->>K8s: 200 OK
+```
+
+## Key Design Decisions
+
+### 1. Errgroup for Process Management
+**Decision**: Use `golang.org/x/sync/errgroup` for concurrent process execution.
+
+**Rationale**:
+- Automatic context propagation on first error
+- Simplified error collection from multiple goroutines
+- Well-tested, official Go extension library
+
+### 2. Two-Phase Initialization
+**Decision**: Separate setup phase from process execution phase.
+
+**Rationale**:
+- Allows initialization work before service starts accepting traffic
+- Setup functions run sequentially, reducing race conditions
+- Processes run concurrently only after setup succeeds
+- Enables accurate readiness probes
+
+### 3. Channel-Based State Tracking
+**Decision**: Use closed channels for state signaling (`startedCh`, `setupCh`).
+
+**Rationale**:
+- Non-blocking state checks via `select`
+- Multiple readers can check state simultaneously
+- Channel closure is a one-time, thread-safe signal
+- No need for mutexes
+
+### 4. No Restart Logic
+**Decision**: Group stops permanently if any process errors or context cancels.
+
+**Rationale**:
+- Simplicity: restart logic belongs at orchestration layer (systemd, k8s)
+- Predictability: clear failure semantics
+- Safety: prevents cascading failures or partial states
+
+### 5. Context-First Design
+**Decision**: All functions accept `context.Context` as first parameter.
+
+**Rationale**:
+- Standard Go idiom
+- Enables cancellation, deadlines, and value propagation
+- Works naturally with errgroup
+- Testable with custom contexts
+
+## Error Handling Strategy
+
+### Setup Phase
+- Errors stop execution immediately
+- No processes start if setup fails
+- Error returned directly to caller
+
+### Process Phase
+- First error cancels context for all processes
+- errgroup waits for all goroutines to complete
+- First error is returned (others are discarded)
+
+### Signal Handling
+- Signal causes context cancellation
+- Processes should return nil on graceful shutdown
+- User code determines error handling for abrupt termination
+
+## Testing Architecture
+
+### Unit Tests
+- Test each component in isolation
+- Mock contexts and channels for state verification
+- Test concurrency with goroutines and channels
+
+### System Tests
+- Build real binary (`examples/server`)
+- Test actual signal handling (SIGINT)
+- Verify HTTP probe endpoints work correctly
+- Integration test of full lifecycle
+
+## Performance Considerations
+
+### Goroutine Usage
+- One goroutine per registered process
+- One goroutine for signal handling (if using NewSignals)
+- No unbounded goroutine creation
+
+### Memory
+- Fixed allocations after Start()
+- Channel cleanup via garbage collection after closure
+- No internal buffering or queuing
+
+### Latency
+- Health probes: O(1) channel select
+- Start(): Sequential setup then concurrent processes
+- Shutdown: Bounded by slowest process cleanup
+
+## Extension Points
+
+### Custom Context Creation
+```go
+ctx := context.WithTimeout(context.Background(), 30*time.Second)
+g := service.NewCtx(ctx)
+```
+
+### Custom Signal Handling
+```go
+g := service.NewSignals(os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
+```
+
+### Wrapping Existing Servers
+```go
+g.Register(func(ctx context.Context) error {
+ srv := &http.Server{Addr: ":8080"}
+ go func() {
+ <-ctx.Done()
+ srv.Close()
+ }()
+ return srv.ListenAndServe()
+})
+```
+
+## Limitations
+
+- No built-in retry logic
+- No process dependencies (all start concurrently)
+- No partial failure handling (one fails, all stop)
+- No built-in metrics or logging
+- No configuration management
+
+These are intentional design choices to keep the library focused and composable.