Skip to main content

AI Copilot Architecture

For humans and LLMs contributing to Songbird’s AI features.

Overview

Songbird’s AI copilot is a realtime music production assistant that lives inside the DAW. It routes user messages to specialized agents, assembles context from the current project state, and provides suggestions driven by DAW events and learned user preferences. The architecture is organized into six subsystems:

Core Pipeline

Request Flow

Intent Types

Chat Modes

  • copilot — Full tool-use mode. Agent can call tools to modify the project.
  • advisor — Read-only conversational mode. No tools, just musical guidance.

Event-Driven Hooks

File: hooks/copilot-hooks.ts + hooks/store-integration.ts The hook system maps DAW events to copilot behaviors. Hooks are async, non-blocking, and never touch the audio thread.

Hook Events

Architecture

Key design decisions:
  • Hooks are throttled per event type (configurable MIN_INTERVAL_MS) to prevent storms
  • Suggestion queue has TTL — stale suggestions are culled automatically
  • The initialized flag inside the transport subscriber skips the initial C++ hydration diff
  • Store subscriptions use dependency injection (passed as callbacks) to avoid circular imports

Registering Custom Hooks


Per-User/Per-Project Instincts

File: instincts/instinct-engine.ts Tracks learned user preferences with confidence scoring. Inspired by ECC’s continuous learning pattern.

Instinct Categories

Confidence Mechanics

  • Initial confidence: 0.5
  • On acceptance: +0.1 (capped at 0.95)
  • On rejection: -0.15 (floor at 0.1)
  • Prompt threshold: Only instincts >= 0.3 confidence are injected into prompts
  • Global promotion: Instincts appearing in 2+ projects with >= 0.6 confidence are promoted to global scope

Storage

JSON-serialized via juceBridge under key songbird-instincts. Debounced saves (3s).

Pattern Key Matching

When observe() is called with a patternKey, it’s stored as pattern._key in the instinct object. Future observe() calls match by category + _key, enabling stable reinforcement across sessions without needing exact pattern JSON equality.

Tiered Context Loading

File: context/tiered-context.ts Instead of dumping the entire project state on every LLM call, context is assembled in tiers with per-intent token budgets.

Tiers

Token Budgets by Intent

Assembly Logic

  1. Always include hot context
  2. Add warm blocks if required by intent OR if < 50% of budget used
  3. Add cold blocks if required by intent OR if < 30% of budget used
  4. Each block is individually checked against remaining budget before inclusion
  5. tiersUsed accurately reports which tiers had blocks actually included

Generate-Then-Refine Pipeline

File: pipeline/generate-refine.ts A two-pass pipeline for creative content: let the model be creative first, then evaluate against musical constraints.

When It Triggers

Only for bird_edit intent with creative keywords: compose, write, create, fill, extend, generate, add, build, arrange, orchestrate, harmonize, improvise.

Pass 1: Generate (Creative)

Standard copilot call — unconstrained generation using the balanced/pro model.

Pass 2: Refine (Evaluation)

A fast-model call that evaluates the generated content against:

Output


Timeline-Driven Pre-Computation

File: timeline/pre-compute.ts Uses playback position and arrangement structure to predict what the user needs next and pre-generate suggestions before they’re needed.

How It Works

Suggestion Types

Configuration

Cache Invalidation

  • Suggestions are invalidated when a section is edited (invalidateSection())
  • TTL-based expiry for stale suggestions
  • Manual cancelAll() available for cleanup

Integration Points

copilot.ts (Main Orchestrator)

The copilot send() method integrates the subsystems:
  • Step 3b: After building the agent/standard system prompt, appends tiered context from assembleTieredContext() (transport + mixer + instincts)
  • Step 7: After generation, checks shouldUseRefine() and optionally runs the refinement pass

store.ts (Store Subscriptions)

Hook integration is initialized via dynamic import to avoid circular dependencies:
HMR cleanup uses a dedicated module-level variable (not _unsubs array) to avoid race conditions with async imports.

index.ts (Barrel Exports)

All subsystems are exported from @/lib/ai:

Wiring Status

The subsystems are at different stages of integration: Next steps to fully wire:
  1. Call observe() / accept() / reject() on instincts from copilot response handlers
  2. Wire setComputeHandler() to call the copilot for pre-generation
  3. Track recentActions and pass them to assembleContext()
  4. Replace 'current-project' placeholder with actual project ID from .bird filename
  5. Add UI for displaying hook suggestions to the user

Key Files