/ patterns / integration-over-separation.md
integration-over-separation.md
  1  # Integration Over Separation
  2  
  3  *proto-024 | Architectural pattern for adding functionality to existing systems*
  4  
  5  ---
  6  
  7  - **principle**
  8    - "When adding new functionality, prefer integrating as a layer into existing systems rather than creating separate processes."
  9    - "One daemon with layers beats multiple daemons with coordination overhead."
 10  
 11  - **shape**
 12    - New functionality becomes a layer within an existing component
 13    - Shared event streams and signal queues
 14    - Single operational surface (one thing to start/stop/monitor)
 15    - The integration IS the simplification
 16  
 17  ---
 18  
 19  ## The Insight
 20  
 21  When building new functionality that needs to observe or react to system events, there are two architectural choices:
 22  
 23  ```
 24  SEPARATE DAEMON APPROACH:
 25  ┌─────────────┐     ┌─────────────┐
 26  │ First       │     │ Building    │
 27  │ Officer     │────▶│ Steward     │
 28  │ (daemon 1)  │     │ (daemon 2)  │
 29  └─────────────┘     └─────────────┘
 30       │                    │
 31       │ duplicate watchers │
 32       │ coordination msgs  │
 33       └────────┬───────────┘
 34 35           complexity
 36  
 37  INTEGRATION APPROACH:
 38  ┌─────────────────────────────────┐
 39  │         First Officer           │
 40  │  ┌───────────────────────────┐  │
 41  │  │   BuildingStewardLayer    │  │
 42  │  │   (observes same events)  │  │
 43  │  └───────────────────────────┘  │
 44  │         single daemon           │
 45  └─────────────────────────────────┘
 46 47 48           simplicity
 49  ```
 50  
 51  **The integration approach:**
 52  - No duplicate file watchers
 53  - No inter-process communication
 54  - Unified signal queue
 55  - Single operational surface
 56  - Shared state without serialization
 57  
 58  ---
 59  
 60  ## When to Apply
 61  
 62  **INTEGRATE when:**
 63  
 64  | Condition | Why Integration Wins |
 65  |-----------|---------------------|
 66  | New functionality observes same events | Avoid duplicate watchers |
 67  | New functionality emits to same consumers | Use existing signal path |
 68  | Operational simplicity matters | One thing to run, not two |
 69  | Shared state needed | Avoid serialization overhead |
 70  | Same lifecycle (start/stop together) | Single daemon management |
 71  
 72  **Example:** Building Steward observes all First Officer events to detect pattern references. Instead of running a separate daemon that watches the same files, it's a layer within First Officer that sees every `record_event()` call.
 73  
 74  ---
 75  
 76  ## When NOT to Apply
 77  
 78  **SEPARATE when:**
 79  
 80  | Condition | Why Separation Wins |
 81  |-----------|---------------------|
 82  | Different failure domains | One can crash without killing the other |
 83  | Different scaling needs | Can run multiple instances independently |
 84  | Different teams own them | Organizational boundaries matter |
 85  | Different deployment cycles | Can update independently |
 86  | Fundamentally different concerns | Not actually related functionality |
 87  
 88  **Example:** Mission Control synthesizes across threads and writes daily synthesis. It has a different lifecycle (runs once per synthesis, not continuously) and different failure domain. It's a separate daemon.
 89  
 90  ---
 91  
 92  ## Implementation Pattern
 93  
 94  ```python
 95  class HostComponent:
 96      """The existing component that will host the new layer."""
 97  
 98      def __init__(self):
 99          # ... existing init ...
100  
101          # Add new functionality as a layer
102          self._new_layer = NewFunctionalityLayer()
103  
104      def process_event(self, event):
105          """Existing event processing."""
106          # ... existing logic ...
107  
108          # Let the new layer observe the same event
109          signals = self._new_layer.observe(event)
110          for signal in signals:
111              self._emit_signal(signal)
112  
113  
114  class NewFunctionalityLayer:
115      """The new functionality, designed as a layer not a daemon."""
116  
117      def observe(self, event) -> List[Signal]:
118          """
119          Called by host on every event.
120          Returns signals to emit (or empty list).
121          """
122          signals = []
123  
124          # React to event if relevant
125          if self._is_relevant(event):
126              signals.append(self._create_signal(event))
127  
128          return signals
129  ```
130  
131  **Key design points:**
132  1. Layer has `observe()` method called by host
133  2. Layer returns signals, doesn't emit directly
134  3. Host decides when/how to emit
135  4. Layer has no lifecycle of its own
136  
137  ---
138  
139  ## The Test
140  
141  Before creating a separate daemon, ask:
142  
143  1. **Does it watch the same things?** → Integrate
144  2. **Does it emit to the same consumers?** → Integrate
145  3. **Does it have the same lifecycle?** → Integrate
146  4. **Is operational simplicity important?** → Integrate
147  
148  If all four answers are "yes," integration is the right choice.
149  
150  ---
151  
152  ## Axiom Alignment
153  
154  | Axiom | Alignment |
155  |-------|-----------|
156  | **A0 (Boundary)** | Integration draws ONE boundary around related functionality, not two |
157  | **A1 (Integration)** | Directly embodies "move toward connection, not isolation" |
158  | **A3 (Navigation)** | This IS a navigation decision - sometimes separate is right |
159  | **A4 (Ergodicity)** | Fewer daemons = fewer things that can fail = lower ruin risk |
160  
161  ---
162  
163  ## The Meta-Insight
164  
165  > **A1 (Telos of Integration) applies to architecture, not just content.**
166  >
167  > Satan didn't know he was choosing isolation when he created a separate daemon.
168  > The coordination overhead, the duplicate watchers, the operational complexity—
169  > these are the isolation costs.
170  >
171  > **Integration is the default. Separation requires justification.**
172  
173  ---
174  
175  ## Instances
176  
177  ### Positive Instance: Building Steward
178  - **Context:** Needed pattern lifecycle tracking + architecture guidance
179  - **Decision:** Integrated as `BuildingStewardLayer` within `FirstOfficer`
180  - **Result:** Single daemon, shared event stream, unified signals
181  - **Outcome:** ✓ Simpler operations, no coordination overhead
182  
183  ### Negative Instance (What We Avoided): Separate Building Steward Daemon
184  - **Alternative:** `building_steward_daemon.py` running separately
185  - **Problems it would have caused:**
186    - Duplicate file watchers
187    - Need to serialize events between processes
188    - Two things to start/stop
189    - Race conditions on shared state
190  - **Outcome:** ✗ Avoided this complexity
191  
192  ### Positive Instance: Mission Control (Appropriate Separation)
193  - **Context:** Cross-thread synthesis, different lifecycle
194  - **Decision:** Separate daemon that runs periodically
195  - **Justification:** Different lifecycle (periodic vs. continuous), different failure domain
196  - **Outcome:** ✓ Appropriate separation, each component has single responsibility
197  
198  ---
199  
200  ## Related
201  
202  - [[first-officer-protocol]] - The host component for Building Steward
203  - [[A1-telos-of-integration]] - The axiom this pattern embodies
204  - [[transparent-automation-principle]] - Layer works automatically, CLI for override
205  - [[typed-resonance-principle]] - Another layer integrated into First Officer
206  
207  ---
208  
209  *proto-024 | Integration Over Separation | 2026-01-15*