- Updated all package.json name fields and dependency references - Updated all TypeScript/JavaScript imports - Updated .woodpecker/publish.yml filters and registry paths - Updated tools/install.sh scope default - Updated .npmrc registry paths (worktree + host) - Enhanced update-checker.ts with checkForAllUpdates() multi-package support - Updated CLI update command to show table of all packages - Added KNOWN_PACKAGES, formatAllPackagesTable, getInstallAllCommand - Marked checkForUpdate() with @deprecated JSDoc Closes #391
17 KiB
Mosaic Stack — Developer Guide
Table of Contents
- Architecture Overview
- Local Development Setup
- Building and Testing
- Adding New Agent Tools
- Adding New MCP Tools
- Database Schema and Migrations
- 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 @mosaicstack/db db:push
This applies the Drizzle schema directly to the database (development only; use migrations in production).
5. Start the Gateway
pnpm --filter @mosaicstack/gateway exec tsx src/main.ts
The gateway starts on port 14242 by default.
6. Start the Web App
pnpm --filter @mosaicstack/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 @mosaicstack/db db:push
Generating Migrations
For production-safe, versioned changes:
pnpm --filter @mosaicstack/db db:generate
This creates a new SQL migration file in packages/db/drizzle/.
Running Migrations
pnpm --filter @mosaicstack/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
- Add the table definition to
packages/db/src/schema.ts. - Export it from
packages/db/src/index.ts. - Run
pnpm --filter @mosaicstack/db db:push(dev) orpnpm --filter @mosaicstack/db db:generate && pnpm --filter @mosaicstack/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.