Files
stack/docs/guides/dev-guide.md
jason.woltje e3f64c79d9
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
chore: move gateway default port from 4000 to 14242 (#375)
2026-04-04 20:17:40 +00:00

17 KiB

Mosaic Stack — Developer Guide

Table of Contents

  1. Architecture Overview
  2. Local Development Setup
  3. Building and Testing
  4. Adding New Agent Tools
  5. Adding New MCP Tools
  6. Database Schema and Migrations
  7. API Endpoint Reference

Architecture Overview

Mosaic Stack is a TypeScript monorepo managed with pnpm workspaces and Turborepo.

mosaic-mono-v1/
├── apps/
│   ├── gateway/          # NestJS + Fastify API server
│   └── web/              # Next.js 16 + React 19 web dashboard
├── packages/
│   ├── agent/            # Agent session types (shared)
│   ├── auth/             # BetterAuth configuration
│   ├── brain/            # Structured data layer (projects, tasks, missions)
│   ├── cli/              # mosaic CLI and TUI (Ink)
│   ├── coord/            # Mission coordination engine
│   ├── db/               # Drizzle ORM schema, migrations, client
│   ├── design-tokens/    # Shared design system tokens
│   ├── log/              # Agent log ingestion and tiering
│   ├── memory/           # Preference and insight storage
│   ├── mosaic/           # Install wizard and bootstrap utilities
│   ├── prdy/             # PRD wizard CLI
│   ├── quality-rails/    # Code quality scaffolder CLI
│   ├── queue/            # Valkey-backed task queue
│   └── types/            # Shared TypeScript types
├── docker/               # Dockerfile(s) for containerized deployment
├── infra/                # Infra config (OTEL collector, pg-init scripts)
├── docker-compose.yml    # Local services (Postgres, Valkey, OTEL, Jaeger)
└── CLAUDE.md             # Project conventions for AI coding agents

Key Technology Choices

Concern Technology
API framework NestJS with Fastify adapter
Web framework Next.js 16 (App Router), React 19
ORM Drizzle ORM
Database PostgreSQL 17 + pgvector extension
Auth BetterAuth
Agent harness Pi SDK (@mariozechner/pi-coding-agent)
Queue Valkey 8 (Redis-compatible)
Build pnpm workspaces + Turborepo
CI Woodpecker CI
Observability OpenTelemetry → Jaeger
Module resolution NodeNext (ESM everywhere)

Module System

All packages use "type": "module" and NodeNext resolution. Import paths must include the .js extension even when the source file is .ts.

NestJS @Inject() decorators must be used explicitly because tsx/esbuild does not support emitDecoratorMetadata.


Local Development Setup

Prerequisites

  • Node.js 20+
  • pnpm 9+
  • Docker and Docker Compose

1. Clone and Install Dependencies

git clone <repo-url> mosaic-mono-v1
cd mosaic-mono-v1
pnpm install

2. Start Infrastructure Services

docker compose up -d

This starts:

Service Port Description
PostgreSQL 17 + pgvector 5433 (host) Primary database
Valkey 8 6380 (host) Queue and cache
OpenTelemetry Collector 4317, 4318 OTEL gRPC and HTTP
Jaeger 16686 Distributed trace UI

3. Configure Environment

Create a .env file in the monorepo root:

# Database (matches docker-compose defaults)
DATABASE_URL=postgresql://mosaic:mosaic@localhost:5433/mosaic

# Auth (required — generate a random 32+ char string)
BETTER_AUTH_SECRET=change-me-to-a-random-secret

# Gateway
GATEWAY_PORT=14242
GATEWAY_CORS_ORIGIN=http://localhost:3000

# Web
NEXT_PUBLIC_GATEWAY_URL=http://localhost:14242

# Optional: Ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODELS=llama3.2

The gateway loads .env from the monorepo root via dotenv at startup (apps/gateway/src/main.ts).

4. Push the Database Schema

pnpm --filter @mosaic/db db:push

This applies the Drizzle schema directly to the database (development only; use migrations in production).

5. Start the Gateway

pnpm --filter @mosaic/gateway exec tsx src/main.ts

The gateway starts on port 14242 by default.

6. Start the Web App

pnpm --filter @mosaic/web dev

The web app starts on port 3000 by default.


Building and Testing

TypeScript Typecheck

pnpm typecheck

Runs tsc --noEmit across all packages in dependency order via Turborepo.

Lint

pnpm lint

Runs ESLint across all packages. Config is in eslint.config.mjs at the root.

Format Check

pnpm format:check

Runs Prettier in check mode. To auto-fix:

pnpm format

Tests

pnpm test

Runs Vitest across all packages. The workspace config is at vitest.workspace.ts.

Build

pnpm build

Builds all packages and apps in dependency order.

Pre-Push Gates (MANDATORY)

All three must pass before any push:

pnpm format:check && pnpm typecheck && pnpm lint

A pre-push hook enforces this mechanically.


Adding New Agent Tools

Agent tools are Pi SDK ToolDefinition objects registered in apps/gateway/src/agent/agent.service.ts.

1. Create a Tool Factory File

Add a new file in apps/gateway/src/agent/tools/:

// apps/gateway/src/agent/tools/my-tools.ts
import { Type } from '@sinclair/typebox';
import type { ToolDefinition } from '@mariozechner/pi-coding-agent';

export function createMyTools(): ToolDefinition[] {
  const myTool: ToolDefinition = {
    name: 'my_tool_name',
    label: 'Human Readable Label',
    description: 'What this tool does.',
    parameters: Type.Object({
      input: Type.String({ description: 'The input parameter' }),
    }),
    async execute(_toolCallId, params) {
      const { input } = params as { input: string };
      const result = `Processed: ${input}`;
      return {
        content: [{ type: 'text' as const, text: result }],
        details: undefined,
      };
    },
  };

  return [myTool];
}

2. Register the Tools in AgentService

In apps/gateway/src/agent/agent.service.ts, import and call your factory alongside the existing tool registrations:

import { createMyTools } from './tools/my-tools.js';

// Inside the session creation logic where tools are assembled:
const tools: ToolDefinition[] = [
  ...createBrainTools(this.brain),
  ...createCoordTools(this.coordService),
  ...createMemoryTools(this.memory, this.embeddingService),
  ...createFileTools(sandboxDir),
  ...createGitTools(sandboxDir),
  ...createShellTools(sandboxDir),
  ...createWebTools(),
  ...createMyTools(), // Add this line
  ...mcpTools,
  ...skillTools,
];

3. Export from the Tools Index

Add an export to apps/gateway/src/agent/tools/index.ts:

export { createMyTools } from './my-tools.js';

4. Typecheck and Test

pnpm typecheck
pnpm test

Adding New MCP Tools

Mosaic connects to external MCP servers via McpClientService. To expose tools from a new MCP server:

1. Run an MCP Server

Implement a standard MCP server that exposes tools via the streamable HTTP transport or SSE transport. The server must accept connections at a /mcp endpoint.

2. Configure MCP_SERVERS

In your .env:

MCP_SERVERS='[{"name":"my-server","url":"http://localhost:3001/mcp"}]'

With authentication:

MCP_SERVERS='[{"name":"secure-server","url":"http://my-server/mcp","headers":{"Authorization":"Bearer token"}}]'

3. Restart the Gateway

On startup, McpClientService (apps/gateway/src/mcp-client/mcp-client.service.ts) connects to each configured server, calls tools/list, and bridges the results to Pi SDK ToolDefinition format. These tools become available in all new agent sessions.

Tool Naming

Bridged MCP tool names are taken directly from the MCP server's tool manifest. Ensure names do not conflict with built-in tools (check apps/gateway/src/agent/tools/).


Database Schema and Migrations

The schema lives in a single file: packages/db/src/schema.ts

Schema Overview

Table Purpose
users User accounts (BetterAuth-compatible)
sessions Auth sessions
accounts OAuth accounts
verifications Email verification tokens
projects Project records
missions Mission records (linked to projects)
tasks Task records (linked to projects and/or missions)
conversations Chat conversation metadata
messages Individual chat messages
preferences Per-user key-value preference store
insights Vector-embedded memory insights
agent_logs Agent interaction logs (hot/warm/cold tiers)
skills Installed agent skills
summarization_jobs Log summarization job tracking

The insights table uses a vector(1536) column (pgvector) for semantic search.

Development: Push Schema

Apply schema changes directly to the dev database (no migration files created):

pnpm --filter @mosaic/db db:push

Generating Migrations

For production-safe, versioned changes:

pnpm --filter @mosaic/db db:generate

This creates a new SQL migration file in packages/db/drizzle/.

Running Migrations

pnpm --filter @mosaic/db db:migrate

Drizzle Config

Config is at packages/db/drizzle.config.ts. The schema file path and output directory are defined there.

Adding a New Table

  1. Add the table definition to packages/db/src/schema.ts.
  2. Export it from packages/db/src/index.ts.
  3. Run pnpm --filter @mosaic/db db:push (dev) or pnpm --filter @mosaic/db db:generate && pnpm --filter @mosaic/db db:migrate (production).

API Endpoint Reference

All endpoints are served by the gateway at http://localhost:14242 by default.

Authentication

Authentication uses BetterAuth session cookies. The auth handler is mounted at /api/auth/* via a Fastify low-level hook in apps/gateway/src/auth/auth.controller.ts.

Endpoint Method Description
/api/auth/sign-in/email POST Sign in with email/password
/api/auth/sign-up/email POST Register a new account
/api/auth/sign-out POST Sign out (clears session cookie)
/api/auth/get-session GET Returns the current session

Chat

WebSocket namespace /chat (Socket.IO). Authentication via session cookie.

Events sent by the client:

Event Payload Description
message { content, conversationId?, provider?, modelId? } Send a message

Events emitted by the server:

Event Payload Description
token { token, conversationId } Streaming token
end { conversationId } Stream complete
error { message } Error during streaming

HTTP endpoints (apps/gateway/src/chat/chat.controller.ts):

Endpoint Method Auth Description
/api/chat/conversations GET User List conversations
/api/chat/conversations/:id/messages GET User Get messages for a conversation

Admin

All admin endpoints require role = admin.

Endpoint Method Description
GET /api/admin/users GET List all users
GET /api/admin/users/:id GET Get a single user
POST /api/admin/users POST Create a user
PATCH /api/admin/users/:id/role PATCH Update user role
POST /api/admin/users/:id/ban POST Ban a user
POST /api/admin/users/:id/unban POST Unban a user
DELETE /api/admin/users/:id DELETE Delete a user
GET /api/admin/health GET System health status

Agent / Providers

Endpoint Method Auth Description
GET /api/agent/providers GET User List all providers and their models
GET /api/agent/providers/models GET User List available models
POST /api/agent/providers/:id/test POST User Test provider connectivity

Projects / Brain

Endpoint Method Auth Description
GET /api/brain/projects GET User List projects
POST /api/brain/projects POST User Create a project
GET /api/brain/projects/:id GET User Get a project
PATCH /api/brain/projects/:id PATCH User Update a project
DELETE /api/brain/projects/:id DELETE User Delete a project
GET /api/brain/tasks GET User List tasks
POST /api/brain/tasks POST User Create a task
GET /api/brain/tasks/:id GET User Get a task
PATCH /api/brain/tasks/:id PATCH User Update a task
DELETE /api/brain/tasks/:id DELETE User Delete a task

Memory / Preferences

Endpoint Method Auth Description
GET /api/memory/preferences GET User Get user preferences
PUT /api/memory/preferences PUT User Upsert a preference

MCP Server (Gateway-side)

Endpoint Method Auth Description
POST /mcp POST User (session cookie or Authorization header) MCP streamable HTTP transport
GET /mcp GET User MCP SSE stream reconnect

Skills

Endpoint Method Auth Description
GET /api/skills GET User List installed skills
POST /api/skills POST Admin Install a skill
PATCH /api/skills/:id PATCH Admin Update a skill
DELETE /api/skills/:id DELETE Admin Remove a skill

Coord (Mission Coordination)

Endpoint Method Auth Description
GET /api/coord/missions GET User List missions
POST /api/coord/missions POST User Create a mission
GET /api/coord/missions/:id GET User Get a mission
PATCH /api/coord/missions/:id PATCH User Update a mission

Observability

OpenTelemetry traces are exported to the OTEL collector (OTEL_EXPORTER_OTLP_ENDPOINT). View traces in Jaeger at http://localhost:16686.

Tracing is initialized before NestJS bootstrap in apps/gateway/src/tracing.ts. The import order in apps/gateway/src/main.ts is intentional: import './tracing.js' must come before any NestJS imports.