Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Making Botface AI-Ready: Architecture Improvements

Based on Matt Pocock’s “Your codebase is NOT ready for AI” and software architecture best practices.

Core Thesis

“Your codebase, way more than the prompt that you used, way more than your agents.md file, is the biggest influence on AI’s output.”

AI imposes weird constraints on codebases. If the architecture is wrong:

  • AI doesn’t receive feedback fast enough
  • AI finds it hard to make sense of things and find files
  • Leads to cognitive burnout as humans try to hold AI context + codebase together

The Solution: Deep Modules

Deep Module: A component with a simple interface that hides complex implementation.

Why This Matters for AI

AI struggles with:

  1. Scattered logic - Functions spread across files
  2. Wide interfaces - Too many public methods to understand
  3. Implicit dependencies - Hidden coupling between modules
  4. No fast feedback - Can’t validate changes quickly

Deep modules solve all of these.


Current State Analysis

✅ What’s Working

  • Modular structure - Clear separation: audio/, wakeword/, llm/, etc.
  • Trait abstractions - Gpio trait allows mock/real implementations
  • Configuration system - TOML-based config with defaults
  • Async architecture - Non-blocking I/O with tokio

⚠️ What’s Not AI-Ready

  1. Too many public modules - Implementation details exposed
  2. No automated tests - AI can’t validate changes
  3. Scattered configuration - Multiple config structs
  4. Dead code - Unused modules (vision/, ui/) confuse AI
  5. Missing documentation - AI doesn’t understand “why” decisions
  6. No integration tests - Can’t test full pipeline
  7. Implicit state machine - Logic spread across match arms

1. Deep Module Interfaces (Critical)

Current:

#![allow(unused)]
fn main() {
pub mod detector;
pub mod buffer;
// AI sees all implementation details
}

Target:

#![allow(unused)]
fn main() {
// Single public struct, hidden implementation
pub struct WakeWordDetector { inner: Inner }
impl WakeWordDetector {
    pub fn new(config: &Config) -> Result<Self>;
    pub fn predict(&mut self, audio: &[i16]) -> Result<bool>;
    pub fn reset(&mut self);
}
}

Action:

  • Create narrow public interfaces for each module
  • Make implementation modules private (mod inner; not pub mod)
  • Document the “contract” in struct-level docs

2. Comprehensive Testing (Critical)

Current: No tests = AI operates blindly

Target:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn test_wake_word_detects_jarvis() {
        let detector = WakeWordDetector::new(&test_config()).unwrap();
        let audio = load_test_audio("hey_jarvis.wav");
        assert!(detector.predict(&audio).unwrap());
    }
}
}

Action:

  • Add unit tests for each module
  • Create test fixtures (sample audio files)
  • Add cargo test to CI/validation
  • Use mock implementations for tests

3. Single Configuration Entry Point

Current: Multiple config structs scattered

Target:

#![allow(unused)]
fn main() {
//! src/config/mod.rs
//! Single AI-friendly entry point for all configuration

pub struct Config {
    pub audio: AudioConfig,
    pub wakeword: WakewordConfig,
    pub llm: LlmConfig,
    pub tts: TtsConfig,
    pub gpio: GpioConfig,
}

impl Config {
    /// Load with validation
    ///
    /// # Errors
    /// Returns error if config is invalid or files missing
    pub fn load() -> Result<Self>;

    /// Validate all paths exist
    pub fn validate(&self) -> Result<()>;
}
}

Action:

  • Consolidate all config in config/mod.rs
  • Add validation methods
  • Fail fast on invalid config

4. Architecture Decision Records (ADRs)

Create: docs/architecture.md

# Botface Architecture

## Core Principles

1. **Deep Modules**: Each subsystem has a narrow public interface
2. **Platform Abstraction**: Works on Mac (dev) and Pi (prod)
3. **Fail Fast**: Validation at startup, not runtime
4. **Observable**: Structured logging at all transitions

## Module Hierarchy

src/ ├── audio/ # Hardware abstraction (arecord/aplay) ├── wakeword/ # ONNX inference (OpenWakeWord) ├── stt/ # Speech-to-text (whisper.cpp) ├── llm/ # Language model (Ollama HTTP) ├── tts/ # Text-to-speech (Piper) ├── gpio/ # Hardware control (AIY HAT) └── state_machine/ # Orchestration layer


## State Machine

Idle → Listening → Recording → Transcribing → Thinking → Speaking → Idle


## Testing Strategy

- Unit: `cargo test` (fast feedback)
- Integration: Requires Ollama + hardware
- Mock: All hardware calls simulated

Action:

  • Write comprehensive architecture.md
  • Document “why” for each major decision
  • Include testing strategy

5. Feature-Gate Unused Code

Current: vision/, ui/ modules exist but unused

Target:

[features]
default = []
vision = ["opencv", "camera"]  # Only compile when needed
faces = ["eframe", "gui"]      # LCD face animations
advanced = ["vision", "faces"] # Everything

Action:

  • Remove or feature-gate unused modules
  • Document feature flags
  • Keep core lean

6. Integration Tests

Create: tests/integration_test.rs

#![allow(unused)]
fn main() {
//! End-to-end test of voice assistant pipeline
//!
//! Run: cargo test --test integration_test

#[tokio::test]
async fn test_full_pipeline_wake_to_response() {
    // Given: Assistant in listening mode
    // When: Wake word detected
    // Then: Recording starts → Transcribe → LLM → TTS → Response
}
}

Action:

  • Create tests/ directory
  • Add integration test for full pipeline
  • Test with mock implementations first

7. Observable State Machine

Current: State transitions logged ad-hoc

Target:

#![allow(unused)]
fn main() {
async fn transition_to(&mut self, new_state: State) {
    tracing::info!(
        state.from = %self.current_state,
        state.to = %new_state,
        activation = self.activation_count,
        "State transition"
    );
    // ...
}
}

Action:

  • Add structured logging to all transitions
  • Include relevant context (activation count, etc.)
  • Use tracing fields for machine-readable logs

8. AI-Context Comments

Add to each module:

#![allow(unused)]
fn main() {
//! Audio capture from microphone
//!
//! ## AI Context
//! - Uses `arecord` subprocess for ALSA compatibility
//! - Handles 48kHz → 16kHz resampling internally
//! - Returns int16 PCM samples (not float32)
//!
//! ## Testing
//! - `check_audio_device()` validates hardware
//! - Mock mode available: `AudioCapture::new_mock()`
//!
//! ## Common Tasks
//! - Change sample rate: Edit `config.audio.sample_rate`
//! - Add resampling: Use `rubato` in `resample.rs`
}

Action:

  • Add “AI Context” section to each module doc
  • Document common modification tasks
  • Include testing guidance

Enforcement Strategies

1. CI/CD Checks

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Check formatting
        run: cargo fmt -- --check

      - name: Run clippy (strict)
        run: cargo clippy -- -D warnings

      - name: Run tests
        run: cargo test --all-features

      - name: Check documentation
        run: cargo doc --no-deps --document-private-items

2. Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: fmt
        name: cargo fmt
        entry: cargo fmt -- --check
        language: system
        pass_filenames: false

      - id: clippy
        name: cargo clippy
        entry: cargo clippy -- -D warnings
        language: system
        pass_filenames: false

      - id: test
        name: cargo test
        entry: cargo test
        language: system
        pass_filenames: false

3. Module Interface Validation

Add to justfile or Makefile:

# Check that modules follow deep interface pattern
check-interfaces:
	@echo "Checking module interfaces..."
	@# Count public items (should be small)
	@find src -name '*.rs' -exec grep -c '^pub ' {} \; | \
	  awk '{sum+=$$1} END {print "Total pub items:", sum}'
	@# Ensure no pub mod of implementation
	@! grep -r "pub mod inner" src/ || \
	  (echo "ERROR: pub mod inner found"; exit 1)
	@echo "✅ Interface check passed"

4. Documentation Requirements

Add to CONTRIBUTING.md:

## Code Requirements

Every module must have:
1. Module-level doc comment with "AI Context" section
2. All public items documented
3. At least one unit test
4. No `pub` on implementation details

## Checklist

- [ ] `cargo fmt` passes
- [ ] `cargo clippy -- -D warnings` passes
- [ ] `cargo test` passes
- [ ] Documentation builds without warnings
- [ ] Module interface is "deep" (few public items)

5. Architectural Fitness Functions

Add to tests/architecture_test.rs:

#![allow(unused)]
fn main() {
//! Tests to enforce architectural constraints

#[test]
fn test_no_wide_modules() {
    // Ensure no module has >5 public items
    // This enforces "deep modules" principle
}

#[test]
fn test_all_modules_documented() {
    // Ensure every module has //! doc comment
}

#[test]
fn test_no_dead_code() {
    // Ensure no #[allow(dead_code)] without justification
}
}

Implementation Roadmap

Phase 1: Foundation (Week 1)

  • Write architecture.md
  • Add comprehensive tests to one module (e.g., gpio)
  • Create tests/integration_test.rs shell
  • Set up CI with strict checks

Phase 2: Deep Modules (Week 2)

  • Audit all pub declarations
  • Convert wide interfaces to deep modules
  • Add module-level “AI Context” docs
  • Feature-gate unused code

Phase 3: Testing (Week 3)

  • Achieve >80% test coverage
  • Add integration tests
  • Add architecture fitness tests
  • Create test fixtures (audio files, etc.)

Phase 4: Observability (Week 4)

  • Structured logging throughout
  • Add metrics (optional)
  • Create debugging guide
  • Document common AI tasks

Measuring Success

Metrics

  1. Test Coverage: Target 80%+
  2. Module Depth: Average <5 public items per module
  3. Documentation: 100% public API documented
  4. CI Pass Rate: 100% (zero tolerance)
  5. AI Success Rate: Can AI add a feature without breaking things?

Test: Can AI Work With This?

Ask AI to:

  1. Add a new sound effect (should be 1 file change, tests pass)
  2. Change wake word threshold (config change, no code)
  3. Add a new state (state_machine.rs only, tests guide)
  4. Swap TTS engine (tts/ module only, interface unchanged)

If AI can do these without breaking anything = Success!


References