Move old docs into _old_structure dir. Create blank GETTING_STARTED.md document

This commit is contained in:
Jason Woltje
2026-08-10 14:53:21 -05:00
parent 1655b1579a
commit cd4409abc3
273 changed files with 278 additions and 247 deletions
+376
View File
@@ -0,0 +1,376 @@
# Mosaic Stack — Admin Guide
## Table of Contents
1. [User Management](#user-management)
2. [System Health Monitoring](#system-health-monitoring)
3. [Provider Configuration](#provider-configuration)
4. [MCP Server Configuration](#mcp-server-configuration)
5. [Environment Variables Reference](#environment-variables-reference)
6. [Local Fleet Canary](./fleet-local-canary.md)
---
## User Management
Admins access user management at `/admin` in the web dashboard. All admin
endpoints require a session with `role = admin`.
### Creating a User
**Via the web admin panel:**
1. Navigate to `/admin`.
2. Click **Create User**.
3. Enter name, email, password, and role (`admin` or `member`).
4. Submit.
**Via the API:**
```http
POST /api/admin/users
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "securepassword",
"role": "member"
}
```
Passwords are hashed by BetterAuth before storage. Passwords are never stored in
plaintext.
### Roles
| Role | Permissions |
| -------- | --------------------------------------------------------------------- |
| `admin` | Full access: user management, health, all agent tools |
| `member` | Standard user access; agent tool set restricted by `AGENT_USER_TOOLS` |
### Updating a User's Role
```http
PATCH /api/admin/users/:id/role
Content-Type: application/json
{ "role": "admin" }
```
### Banning and Unbanning
Banned users cannot sign in. Provide an optional reason:
```http
POST /api/admin/users/:id/ban
Content-Type: application/json
{ "reason": "Violated terms of service" }
```
To lift a ban:
```http
POST /api/admin/users/:id/unban
```
### Deleting a User
```http
DELETE /api/admin/users/:id
```
This permanently deletes the user. Related data (sessions, accounts) is
cascade-deleted. Conversations and tasks reference the user via `owner_id`
which is set to `NULL` on delete (`set null`).
---
## System Health Monitoring
The health endpoint is available to admin users only.
```http
GET /api/admin/health
```
Sample response:
```json
{
"status": "ok",
"database": { "status": "ok", "latencyMs": 2 },
"cache": { "status": "ok", "latencyMs": 1 },
"agentPool": { "activeSessions": 3 },
"providers": [{ "id": "ollama", "name": "ollama", "available": true, "modelCount": 3 }],
"checkedAt": "2026-03-15T12:00:00.000Z"
}
```
`status` is `ok` when both database and cache pass. It is `degraded` when either
service fails.
The web admin panel at `/admin` polls this endpoint and renders the results in a
status dashboard.
---
## Provider Configuration
Providers are configured via environment variables and loaded at gateway startup.
No restart-free hot reload is supported; the gateway must be restarted after
changing provider env vars.
### Ollama
Set `OLLAMA_BASE_URL` (or the legacy `OLLAMA_HOST`) to the base URL of your
Ollama instance:
```env
OLLAMA_BASE_URL=http://localhost:11434
```
Specify which models to expose (comma-separated):
```env
OLLAMA_MODELS=llama3.2,codellama,mistral
```
Default when unset: `llama3.2,codellama,mistral`.
The gateway registers Ollama models using the OpenAI-compatible completions API
(`/v1/chat/completions`).
### Custom Providers (OpenAI-compatible APIs)
Any OpenAI-compatible API (LM Studio, llama.cpp HTTP server, etc.) can be
registered via `MOSAIC_CUSTOM_PROVIDERS`. The value is a JSON array:
```env
MOSAIC_CUSTOM_PROVIDERS='[
{
"id": "lmstudio",
"name": "LM Studio",
"baseUrl": "http://localhost:1234",
"models": ["mistral-7b-instruct"]
}
]'
```
Each entry must include:
| Field | Required | Description |
| --------- | -------- | ----------------------------------- |
| `id` | Yes | Unique provider identifier |
| `name` | Yes | Display name |
| `baseUrl` | Yes | API base URL (no trailing slash) |
| `models` | Yes | Array of model ID strings to expose |
| `apiKey` | No | API key if required by the endpoint |
### Testing Provider Connectivity
From the web admin panel or settings page, click **Test** next to a provider.
This calls:
```http
POST /api/agent/providers/:id/test
```
The response includes `reachable`, `latencyMs`, and optionally
`discoveredModels`.
---
## MCP Server Configuration
The gateway can connect to external MCP (Model Context Protocol) servers and
expose their tools to agent sessions.
Set `MCP_SERVERS` to a JSON array of server configurations:
```env
MCP_SERVERS='[
{
"name": "my-tools",
"url": "http://localhost:3001/mcp",
"headers": {
"Authorization": "Bearer my-token"
}
}
]'
```
Each entry:
| Field | Required | Description |
| --------- | -------- | ----------------------------------- |
| `name` | Yes | Unique server name |
| `url` | Yes | MCP server URL (`/mcp` endpoint) |
| `headers` | No | Additional HTTP headers (e.g. auth) |
On gateway startup, each configured server is connected and its tools are
discovered. Tools are bridged into the Pi SDK tool format and become available
in agent sessions.
The gateway itself also exposes an MCP server endpoint at `POST /mcp` for
external clients. Authentication requires a valid BetterAuth session (cookie or
`Authorization` header).
---
## Environment Variables Reference
### Required
| Variable | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
| `BETTER_AUTH_SECRET` | Secret key for BetterAuth session signing. Must be set or gateway will not start. |
| `DATABASE_URL` | Runtime-only PostgreSQL connection injected from the dedicated deployment secret; no default or inline DSN. |
### Gateway
| Variable | Default | Description |
| --------------------- | ------------------------ | ---------------------------------------------- |
| `GATEWAY_PORT` | `14242` | Port the gateway listens on |
| `GATEWAY_CORS_ORIGIN` | `http://localhost:3000` | Allowed CORS origin for browser clients |
| `BETTER_AUTH_URL` | `http://localhost:14242` | Public URL of the gateway (used by BetterAuth) |
### SSO (Optional)
| Variable | Description |
| --------------------------- | ---------------------------------------------------------------------------- |
| `AUTHENTIK_CLIENT_ID` | Authentik OAuth2 client ID |
| `AUTHENTIK_CLIENT_SECRET` | Authentik OAuth2 client secret |
| `AUTHENTIK_ISSUER` | Authentik OIDC issuer URL |
| `AUTHENTIK_TEAM_SYNC_CLAIM` | Optional claim used to derive team sync data (defaults to `groups`) |
| `WORKOS_CLIENT_ID` | WorkOS OAuth client ID |
| `WORKOS_CLIENT_SECRET` | WorkOS OAuth client secret |
| `WORKOS_ISSUER` | WorkOS OIDC issuer URL |
| `WORKOS_TEAM_SYNC_CLAIM` | Optional claim used to derive team sync data (defaults to `organization_id`) |
| `KEYCLOAK_CLIENT_ID` | Keycloak OAuth client ID |
| `KEYCLOAK_CLIENT_SECRET` | Keycloak OAuth client secret |
| `KEYCLOAK_ISSUER` | Keycloak realm issuer URL |
| `KEYCLOAK_TEAM_SYNC_CLAIM` | Optional claim used to derive team sync data (defaults to `groups`) |
| `KEYCLOAK_SAML_LOGIN_URL` | Optional SAML login URL used when OIDC is unavailable |
Each OIDC provider requires its client ID, client secret, and issuer URL together. If only part of a provider configuration is set, gateway startup logs a warning and that provider is skipped. Keycloak can fall back to SAML when `KEYCLOAK_SAML_LOGIN_URL` is configured.
### Agent
| Variable | Default | Description |
| ------------------------ | --------------- | ------------------------------------------------------- |
| `AGENT_FILE_SANDBOX_DIR` | `process.cwd()` | Root directory for file/git/shell tool access |
| `AGENT_SYSTEM_PROMPT` | — | Platform-level system prompt injected into all sessions |
| `AGENT_USER_TOOLS` | all tools | Comma-separated allowlist of tools for non-admin users |
### Providers
| Variable | Default | Description |
| ------------------------- | ---------------------------- | ------------------------------------------------ |
| `OLLAMA_BASE_URL` | — | Ollama API base URL |
| `OLLAMA_HOST` | — | Alias for `OLLAMA_BASE_URL` (legacy) |
| `OLLAMA_MODELS` | `llama3.2,codellama,mistral` | Comma-separated Ollama model IDs |
| `MOSAIC_CUSTOM_PROVIDERS` | — | JSON array of custom OpenAI-compatible providers |
### Memory and Embeddings
| Variable | Default | Description |
| ----------------------- | --------------------------- | ---------------------------------------------------- |
| `OPENAI_API_KEY` | — | API key for OpenAI embedding and summarization calls |
| `EMBEDDING_API_URL` | `https://api.openai.com/v1` | Base URL for embedding API |
| `EMBEDDING_MODEL` | `text-embedding-3-small` | Embedding model ID |
| `SUMMARIZATION_API_URL` | `https://api.openai.com/v1` | Base URL for log summarization API |
| `SUMMARIZATION_MODEL` | `gpt-4o-mini` | Model used for log summarization |
| `SUMMARIZATION_CRON` | `0 */6 * * *` | Cron schedule for log summarization (every 6 hours) |
| `TIER_MANAGEMENT_CRON` | `0 3 * * *` | Cron schedule for log tier management (daily at 3am) |
### MCP
| Variable | Description |
| ------------- | ------------------------------------------------ |
| `MCP_SERVERS` | JSON array of external MCP server configurations |
### Plugins
| Variable | Description |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `DISCORD_BOT_TOKEN` | Discord bot token (enables Discord plugin) |
| `DISCORD_SERVICE_TOKEN` | Required high-entropy service credential used to authenticate and sign Discord ingress; inject through the approved secret mechanism only |
| `DISCORD_SERVICE_USER_ID` | Required Mosaic service-principal user ID that owns persisted Discord conversations; the original Discord user ID remains audit metadata |
| `DISCORD_GUILD_ID` | Discord guild/server ID |
| `DISCORD_GATEWAY_URL` | Gateway URL for Discord plugin to call (default: `http://localhost:14242`) |
| `DISCORD_ALLOWED_GUILD_IDS` | Required comma-separated Discord guild snowflake allowlist; default-deny |
| `DISCORD_ALLOWED_CHANNEL_IDS` | Required comma-separated Discord channel snowflake allowlist; default-deny |
| `DISCORD_ALLOWED_USER_IDS` | Required comma-separated Discord user snowflake allowlist; default-deny |
| `DISCORD_INTERACTION_BINDINGS` | Required JSON bindings from guild/channel to logical agent and paired Discord users with `viewer`, `operator`, or `admin` roles |
| `DISCORD_MESSAGE_RATE_LIMIT_PER_MINUTE` | Optional positive integer; authorized turns per guild/channel/user each minute (default: `30`) |
| `DISCORD_THREAD_RATE_LIMIT_PER_MINUTE` | Optional positive integer; mention-thread routes per guild/channel/user each minute (default: `5`) |
| `TELEGRAM_BOT_TOKEN` | Telegram bot token (enables Telegram plugin) |
| `TELEGRAM_GATEWAY_URL` | Gateway URL for Telegram plugin to call |
### Discord ingress security
When `DISCORD_BOT_TOKEN` is configured, `DISCORD_SERVICE_TOKEN`, `DISCORD_SERVICE_USER_ID`, and all three Discord allowlists are required. Gateway startup fails rather than enabling a broad or unauthenticated remote-control surface. The service user ID identifies a provisioned Mosaic service principal for persistence; the original Discord user ID is retained in ingress audit metadata. The service token is a secret supplied by the approved runtime secret mechanism and is never committed or logged.
Inbound Discord messages must originate from an allowed guild and configured parent channel, come from an allowed and paired user whose role permits sending, and carry a signed envelope containing the native Discord message ID and a generated correlation ID. Attachment references are limited in count, metadata size, field length, and declared size; only query-free HTTPS URLs without credentials or fragments are accepted, so bearer or presigned URLs never reach persistence or an agent prompt. The gateway validates the service identity, envelope signature, allowlists, pairing, and role again before dispatching. Replayed Discord message IDs are rejected during the bounded ingress replay window. Durable inbox/idempotency retention is introduced with Tess durable state.
Configured channels are dedicated agent interaction surfaces. An authorized untagged message routes to the bound logical agent and the response returns in that channel. Mentioning the bot on a normal channel message creates a public Discord thread, or reuses the thread already attached to that same message; the response and later thread messages stay in that thread without repeated mentions. A normal channel's category is not an authorization parent—only a Discord thread inherits authorization from its configured parent channel. Runtime control commands such as `/approve` and `/stop <approval>` remain on the current channel/thread because they target that durable session rather than opening a new topic.
Authorization and per-user/channel rate limits are evaluated before thread creation, so an unlisted guild/channel/user, unpaired user, `viewer`, or rate-limited sender cannot create bot threads or dispatch gateway work. The bot needs Discord permissions to view/send in configured channels and create/send in public threads. If thread creation fails, the turn is not dispatched because the requested response destination cannot be honored.
Conversation handles use the configured logical agent plus Discord channel/thread identity. They do not contain a Claude, Codex, Pi, OpenCode, model, process, or runtime-provider identifier; changing the runtime behind the logical session therefore does not require reconnecting the Discord bot.
#### Interaction binding format
`DISCORD_INTERACTION_BINDINGS` must be a non-empty JSON array. Each item requires `instanceId`, trusted `agentConfigId`, `guildId`, `channelId`, and a non-empty `pairedUsers` object. `instanceId` is the configured logical-agent name; its trusted database `agentConfigId` must resolve to an agent configuration with exactly that name, preserving provider/model/prompt/tool selection per binding. The guild/channel must also appear in their corresponding allowlists. IDs below are placeholders:
```json
[
{
"instanceId": "interaction-agent",
"agentConfigId": "agent-config-id",
"guildId": "guild-id",
"channelId": "channel-id",
"pairedUsers": {
"discord-user-id": {
"role": "operator",
"mosaicUserId": "mosaic-user-id"
}
}
}
]
```
| Pairing role | Send message | Create/continue thread | Approve | Stop |
| ------------ | ------------ | ---------------------- | ------- | ---- |
| `viewer` | No | No | No | No |
| `operator` | Yes | Yes | No | No |
| `admin` | Yes | Yes | Yes | Yes |
A legacy role-only value such as `"discord-user-id": "operator"` remains valid for non-privileged ingress. Approval and stop require the object form with a provisioned `mosaicUserId`; gateway policy checks that Mosaic identity and consumes one exact-action approval once. Do not make the Discord service principal an approving administrator.
After changing bindings or allowlists, restart the gateway/plugin through the normal service manager and verify both Discord and gateway connectivity. The adapter reports `connected` only when both links are ready, `degraded` when one is ready, and `disconnected` when neither is ready. Test one authorized untagged channel turn, one mention-created thread, one thread follow-up, and one unauthorized user denial without using production credential values in logs or evidence.
### Session retention and garbage collection
Session cleanup is scoped to one session identifier and only removes that session's Valkey keys and demotes that session's hot logs. Gateway startup and scheduled jobs do not perform global session cleanup; startup removes legacy repeatable `session-gc` schedules created by older deployments. The `/gc` command is intentionally disabled until a distinct global-retention job supplies explicit authorization and audit evidence. This prevents one tenant or session's cleanup from changing another's retained data.
### Observability
| Variable | Default | Description |
| ----------------------------- | ----------------------- | -------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318` | OpenTelemetry collector endpoint |
| `OTEL_SERVICE_NAME` | `mosaic-gateway` | Service name in traces |
### Web App
| Variable | Default | Description |
| ------------------------- | ------------------------ | -------------------------------------- |
| `NEXT_PUBLIC_GATEWAY_URL` | `http://localhost:14242` | Gateway URL used by the Next.js client |
### Coordination
| Variable | Default | Description |
| ----------------------- | ----------------------------- | ------------------------------------------ |
| `MOSAIC_WORKSPACE_ROOT` | monorepo root (auto-detected) | Root path for mission workspace operations |
+67
View File
@@ -0,0 +1,67 @@
# Deployment Guide
> **Status: non-operative for PostgreSQL, federated, and bare-metal production.** The checked-in
> Compose PostgreSQL service mounts legacy initialization SQL and the KBN-101 bootstrap, runner,
> secret-renderer, and process-exec interfaces do not exist yet. This page does not authorize a
> production deployment, database initialization, manual DDL, secret provisioning, or service
> activation.
## Current safe local route
Use PGlite only for current in-process data-layer work; it requires no PostgreSQL. A Gateway/Web
local process is held because its unguarded dotenv loader can inherit a daemon PostgreSQL DSN and
reach runtime DDL. If a local queue service is useful, start only Valkey:
```bash
docker compose up -d valkey
```
This command intentionally does not start PostgreSQL. Do not run a broad Compose start, use its
PostgreSQL initialization mount, infer that current Compose is a production/federated route, or
start Gateway/Web until KBN-101-02 supplies fail-closed local-tier/DSN isolation.
## Held future procedure
PostgreSQL local, federated, Compose, and bare-metal production activation are held until these
artifacts land and pass their independent gates:
1. **KBN-101-00** external privileged bootstrap artifact;
2. **KBN-101-03** sole `mosaic-db-migrator` runner and verified-readiness artifact; and
3. **KBN-101-05** Vault/secret-renderer-backed deployment and consumer-isolation artifact.
The required future order is external bootstrap → TLS/roles → `mosaic-db-migrator --run` → `mosaic-db-migrator --verify` → Gateway/Compose readiness.
This is a held, non-operative future activation specification with no current command authority. Do not invoke the named
runner, start PostgreSQL, or substitute a Compose/init/manual-SQL route until the owned artifacts
are implemented and reviewed.
## Future production secret and unit boundary (schematic only)
No current bare-metal production unit or command is published. KBN-101-05 must supply a reviewed,
generation-pinned Vault renderer and a process-exec or systemd `LoadCredential` interface before
production units can exist. The interface must preserve these exact consumer boundaries:
| Consumer | May receive | Must never receive |
| ----------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Gateway/runtime | Its own runtime URL and DB client CA at process exec | Migrator URL, importer URL/version, attestation material, signing key, PostgreSQL private key |
| One-shot migrator | Its own migration URL, DB client CA, and runner-only signing capability | Runtime URL, importer consumer copy, Gateway/private PostgreSQL keys |
| Data importer | Its own immutable URL/version copies, importer CA, pinned public key, and sealed attestation | Runtime/migrator URLs, signing key, shared writable mount |
| PostgreSQL | Its own server certificate/key and only its approved server material | Application, migrator, importer, or Gateway secrets |
A future unit specification is non-executable until KBN-101-05 supplies it. It must obtain
credentials through the renderer’s Vault generation and process-exec/`LoadCredential` boundary;
it must not place credentials in a production environment file, a monorepo auto-load path, a shell
export, command arguments, logs, or a manual secret-activation lifecycle instruction. Rotation and
process replacement semantics must be delivered by the reviewed renderer/interface with generation,
consumer-isolation, mode/owner, and no-mixed-generation evidence—not improvised in this guide.
## Readiness and troubleshooting status
Until the future procedure is implemented, do not diagnose PostgreSQL with ad hoc SQL, connection
strings, or initialization scripts. The future sanitized runner-verification readiness artifact is
the required PostgreSQL readiness authority after its bootstrap/TLS prerequisites pass.
For local PGlite development, diagnose application behavior without introducing a PostgreSQL
connection.
Non-database local services may be inspected with their ordinary local health/log tools. Those
checks do not certify PostgreSQL, federated deployment, or production readiness.
+518
View File
@@ -0,0 +1,518 @@
# Mosaic Stack — Developer Guide
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Local Development Setup](#local-development-setup)
3. [Building and Testing](#building-and-testing)
4. [Adding New Agent Tools](#adding-new-agent-tools)
5. [Adding New MCP Tools](#adding-new-mcp-tools)
6. [Database Schema and Migrations](#database-schema-and-migrations)
7. [Claude Code Skill Bridge](#claude-code-skill-bridge)
8. [API Endpoint Reference](#api-endpoint-reference)
9. [Local Fleet Canary](./fleet-local-canary.md)
---
## 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/ # Infrastructure configuration (for example, OTEL collector)
├── 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
```bash
git clone <repo-url> mosaic-mono-v1
cd mosaic-mono-v1
pnpm install
```
### 2. Use the local PGlite tier
The supported local tier is in-process PGlite and requires no PostgreSQL service. Leave
`DATABASE_URL` unset for this route. Its default local configuration uses PGlite and performs no
external database probe.
If a local queue service is useful, start only that non-PostgreSQL service:
```bash
docker compose up -d valkey
```
Do not use the current Compose PostgreSQL service: it mounts legacy `infra/pg-init` SQL and is
not qualified for KBN-101. Start OTEL Collector or Jaeger individually only when needed and
without starting PostgreSQL.
### 3. Gateway/Web local process (held)
Do not start the current Gateway or web process as a local PGlite route. Gateway first loads the
daemon configuration and then project environment files without a tier guard; a pre-existing
`DATABASE_URL` can select PostgreSQL, where current startup still reaches runtime DDL/migrations.
Creating a root `.env` that omits `DATABASE_URL` does not make this safe, so neither a local
credential file nor a web environment file is a current developer procedure.
PGlite remains the supported in-process data-layer implementation, and the optional Valkey command
above remains safe because it does not start PostgreSQL. A safe Gateway/Web local procedure is held
until KBN-101-02 rejects a daemon, inherited, root, or app-local PostgreSQL DSN and any non-local
tier before connection or DDL; KBN-101-05 then supplies the production renderer/Vault process-exec
or `LoadCredential` boundary.
### Held future procedure
PostgreSQL local and federated deployment are held until KBN-101-00 (external bootstrap),
KBN-101-03 (runner), and KBN-101-05 (renderer-backed deployment) land. The following is the
**held, non-operative future activation order with no current command authority**:
external bootstrap → TLS/roles → `mosaic-db-migrator --run` →
`mosaic-db-migrator --verify` → Gateway/Compose readiness.
Neither current Compose nor this development guide authorizes PostgreSQL initialization SQL,
manual DDL, or a pre-runner start.
### 5. Gateway/Web start (held)
No Gateway/Web start command is currently authorized for the local PGlite route. Do not use root
`pnpm dev` as a workaround: it additionally starts configured integrations and cannot establish the
required local-tier/DSN isolation. Resume this section only after KBN-101-02 provides its
fail-closed local-startup evidence.
---
## Building and Testing
### TypeScript Typecheck
```bash
pnpm typecheck
```
Runs `tsc --noEmit` across all packages in dependency order via Turborepo.
### Lint
```bash
pnpm lint
```
Runs ESLint across all packages. Config is in `eslint.config.mjs` at the root.
### Format Check
```bash
pnpm format:check
```
Runs Prettier in check mode. To auto-fix:
```bash
pnpm format
```
### Tests
```bash
pnpm test
```
Runs Vitest across all packages. The workspace config is at
`vitest.workspace.ts`.
### Build
```bash
pnpm build
```
Builds all packages and apps in dependency order.
### Pre-Push Gates (MANDATORY)
All three must pass before any push:
```bash
pnpm format:check && pnpm typecheck && pnpm lint
```
A pre-push hook enforces this mechanically.
### CI Publish Channels
Woodpecker `.woodpecker/publish.yml` keeps stable and integration-line artifacts separate:
| Source | npm packages | Gateway image |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `main` push/manual or release tag | committed package versions published to Gitea npm without changing the dist-tag workflow | `gateway:sha-<short>` plus `gateway:latest` on `main`, and the release tag on tag events |
| `next` push/manual | CI-computed prereleases, `<target-stable>-next.<CI_PIPELINE_NUMBER>`, published with `npm publish --tag next` | `gateway:sha-<short>` only |
`next` never publishes npm `latest` or Docker `latest`. The next npm publish step verifies that `@mosaicstack/mosaic@next` resolves to the computed prerelease before the pipeline can pass.
---
## 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/`:
```typescript
// 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:
```typescript
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`:
```typescript
export { createMyTools } from './my-tools.js';
```
### 4. Typecheck and Test
```bash
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. Gateway MCP configuration (held)
Do not configure MCP endpoint credentials, write them to a local environment file, or restart the
Gateway from this guide. Gateway/Web startup is held until KBN-101-02 supplies fail-closed
local-tier/DSN isolation and KBN-101-05 supplies the renderer/Vault process-exec or
`LoadCredential` secret-consumer interface. The future authenticated MCP route requires verified
HTTPS and certificate validation; plaintext bearer-token examples are forbidden.
### 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.
### PostgreSQL schema work (held)
Do not prepare or run a PostgreSQL target from this branch. The sole runner, bootstrap, and
renderer are future KBN-101 artifacts, not current commands. When KBN-101-00/-03/-05 land, the
owned activation documentation will require external bootstrap → TLS/roles → runner `--run` →
runner `--verify` → Gateway/Compose readiness.
### Generating migration artifacts
`pnpm --filter @mosaicstack/db db:generate` is an offline artifact-generation command. It does
not authorize connecting to or initializing PostgreSQL. A future reviewed PostgreSQL procedure
will determine when its output is applied.
### 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. Generate the offline artifact with `pnpm --filter @mosaicstack/db db:generate`.
4. Do not apply it to PostgreSQL until the future KBN-101 activation artifacts and their owned
procedure are available. Direct schema push is not a production-like workflow.
---
## Claude Code Skill Bridge
The framework's canonical skill root is `~/.config/mosaic/skills/`; Claude Code
requires registrations under `~/.claude/skills/`. The implementation in
`packages/mosaic/src/commands/skill.ts` owns only direct-child symlinks whose
resolved target remains inside the canonical root.
Security invariants:
1. Validate the user-supplied name before filesystem access against
`[A-Za-z0-9][A-Za-z0-9._-]*`. Separators, control characters, whitespace,
`..`, absolute paths, and leading `-` are invalid; filesystem-derived invalid
names are escaped before terminal output.
2. Never replace a real file, directory, foreign symlink, or live misdirected
symlink in the Claude skill directory.
3. Repair a dangling link only when its lexical target is inside the canonical
Mosaic skills root.
4. Unregister only a symlink pointing inside that root.
5. Enumerate canonical directories at runtime; never hardcode framework skill
names.
`finalizeStage` reconciles after wizard/framework synchronization, and
`runFrameworkReseed` reconciles after the sync-only `mosaic update` path. A
foreign conflict is reported but does not prevent unrelated canonical skills
from registering. Filesystem tests use injected temporary roots in
`skill.spec.ts`, `finalize-skills.spec.ts`, and `update-checker.reseed.spec.ts`.
M1 intentionally manages Claude Code only. Pi's Mosaic launcher can discover the
canonical root directly. Codex still relies on the existing full skill-sync
linker and needs separate parity analysis before this lifecycle API is extended.
## 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.
@@ -0,0 +1,177 @@
# Local Fleet Canary
The local fleet canary runs a small tmux-backed Mosaic agent fleet on an
isolated tmux socket. The default socket is `mosaic-fleet`; the commands do
not use or stop the default tmux server.
## Files
Product-owned defaults:
- `packages/mosaic/framework/fleet/roster.schema.json`
- `packages/mosaic/framework/fleet/examples/minimal.yaml`
- `packages/mosaic/framework/fleet/examples/local-canary.yaml`
- `packages/mosaic/framework/systemd/user/mosaic-tmux-holder.service`
- `packages/mosaic/framework/systemd/user/[email protected]`
- `packages/mosaic/framework/tools/fleet/start-agent-session.sh`
- `packages/mosaic/framework/tools/tmux/agent-send.sh`
- `packages/mosaic/framework/tools/tmux/send-message.sh`
These files are published through `packages/mosaic/package.json`, whose `files`
allowlist includes `framework` along with `dist`.
Site-owned local roster:
```text
~/.config/mosaic/fleet/roster.yaml
```
Do not put a host-specific full roster into product defaults. Start from an
example and edit the local roster after `mosaic fleet init --write`.
## Install
Minimal canary:
```bash
mosaic fleet init --profile minimal --write
# If a site-owned roster already exists, inspect it first; overwrite only explicitly:
# mosaic fleet init --profile minimal --write --force
mosaic fleet install-systemd
systemctl --user daemon-reload
mosaic fleet start
mosaic fleet verify
```
Small dogfood roster:
```bash
mosaic fleet init --profile local-canary --write
# Use --force only after preserving any site-owned roster changes.
mosaic fleet install-systemd
systemctl --user daemon-reload
mosaic fleet start
mosaic fleet status
```
## Agent Operations
```bash
mosaic agent roster
mosaic agent status
mosaic agent status canary-pi
mosaic agent send canary-pi --message "status check"
mosaic agent reset canary-pi --new
mosaic agent tail canary-pi -n 80
```
These commands read the roster and target the configured tmux socket. The
generated systemd agent services use `start-agent-session.sh`; message delivery
uses the tmux send tools with `-L mosaic-fleet`.
`mosaic agent send` is operator-origin traffic unless a caller explicitly says
otherwise. The CLI always passes a deterministic source label to
`agent-send.sh` with `-S`, defaulting to `<hostname>:operator`, so it does not
query the target tmux socket and accidentally identify as an active agent pane.
Use `--source-label <label>` or `--source <label>` only when deliberately
impersonating a known handoff lane. The lower-level inter-agent wrapper
`agent-send.sh -S <label>` remains the explicit source override for scripts.
## Verification
Use these checks before expanding the roster:
```bash
tmux -L mosaic-fleet ls
tmux ls
mosaic fleet verify
systemctl --user status mosaic-tmux-holder.service
```
Expected results:
- `tmux -L mosaic-fleet ls` shows `_holder` and roster agent sessions.
- `tmux ls` shows only the default tmux server sessions and is not changed by
fleet start/stop operations.
- `mosaic fleet verify` checks exact session targets on the isolated socket.
- `systemctl --user status ...` may show `active (exited)` for oneshot units;
that means the unit ran, not that an agent pane is live. Treat tmux
`has-session`, `list-panes`, process tree, and logs as the liveness evidence.
## Recovery — rebuild generated env projections
Each agent's `~/.config/mosaic/fleet/agents/<name>.env.generated` is a
deterministic projection of `roster.yaml` (the SSOT) that the launcher
(`start-agent-session.sh`) sources at start. If an upgrade or a manual mistake
wipes or diverges those projections, rebuild them from the roster with
`mosaic fleet regen` — do NOT restart the affected unit first.
```bash
mosaic fleet regen # dry-run (default): show create/rebuild plan per agent
mosaic fleet regen --json # same plan, machine-readable
mosaic fleet regen --write # rebuild fleet/agents/<name>.env.generated on disk
```
`regen` is projection-only and **never restarts an agent** — it has no path to
systemd lifecycle. It is dry-run by default, deterministic/idempotent, uses the
same roster→env mapping as `mosaic fleet reconcile`, and emits paths and counts
only (never the projected `KEY=value` body). After `--write`, verify each unit
resolves the intended values before restarting one unit at a time. The unit sets
no `EnvironmentFile=` — `start-agent-session.sh` sources `.env.generated` itself —
so verify the generated file directly and the launcher path, not a nonexistent
`EnvironmentFile` property:
```bash
test -f ~/.config/mosaic/fleet/agents/<name>.env.generated
systemctl --user cat mosaic-agent@<name> | grep ExecStart
systemctl --user restart mosaic-agent@<name>
```
Full recovery runbook and the three-layer #791 protection model (manifest
ownership → pre-update snapshot/restore → regen): see
[Upgrade Safety & Recovery](./upgrade-safety-and-recovery.md).
## Release Preflight
Run this checklist before cutting or dogfooding a fleet release:
- Real AI dogfood: send at least one task through `mosaic agent send`, then
confirm the agent accepted/responded using pane, process, or log evidence.
- Restart/stop/idempotency: run `mosaic fleet start`, `restart`, `stop`, and a
repeated `start` against the named socket; verify the default tmux server is
unchanged.
- Liveness verification: run `mosaic fleet verify` and confirm roster sessions
with `tmux -L mosaic-fleet ls` or exact `has-session` checks.
- Package dry-run: run `npm pack --dry-run --json` from `packages/mosaic` and
confirm `framework/fleet`, `framework/systemd/user`,
`framework/tools/fleet`, and `framework/tools/tmux` assets are included.
- Mosaic update test: install or upgrade from the packed artifact in a temporary
Mosaic home and confirm `mosaic update` or the release upgrade path does not
remove local roster/config files.
## Rollback
Stop the local canary:
```bash
mosaic fleet stop
systemctl --user disable [email protected]
systemctl --user disable mosaic-tmux-holder.service
systemctl --user daemon-reload
```
For a full local cleanup of generated canary files:
```bash
rm -f ~/.config/systemd/user/[email protected]
rm -f ~/.config/systemd/user/mosaic-tmux-holder.service
rm -rf ~/.config/mosaic/fleet
rm -rf ~/.config/mosaic/tools/fleet
```
This rollback leaves the default tmux server untouched. If a canary session is
still present after service stop, remove only the isolated socket server:
```bash
tmux -L mosaic-fleet kill-server
```
@@ -0,0 +1,44 @@
# Lease broker operations
Place the socket and state file in a dedicated directory with mode `0700`. Start the packaged daemon with:
```bash
python3 "$MOSAIC_HOME/tools/lease-broker/daemon.py" \
--socket /run/user/1000/mosaic-lease/broker.sock \
--state /run/user/1000/mosaic-lease/state.json
```
The broker refuses an existing parent directory whose mode is not exactly `0700`, an existing state file not at `0600`, corrupt/incompatible state, or an already-existing socket path. After bind it sets the socket to `0600`. It never silently unlinks a pre-existing socket. On normal termination it unlinks only the socket inode it created, so it does not remove a replacement path.
Before launching Claude, Claudex, or Pi, export the socket path; `mosaic` then runs the runtime through the packaged register-and-exec wrapper:
```bash
export MOSAIC_LEASE_BROKER_SOCKET=/run/user/1000/mosaic-lease/broker.sock
mosaic claude # or: mosaic claudex, mosaic yolo claudex, mosaic pi
```
The wrapper obtains a broker-minted session ID, creates a private `generation-<session>.state` file beside the socket, and `exec`s the runtime without changing its PID/starttime anchor. The all-tools Claude `PreToolUse` hook and Pi `tool_call` handler inherit that identity and read the current generation from the file. Claudex retains its isolated proxy environment and config directory; Mosaic merges the mandatory all-tools and compaction-lifecycle hooks into that isolated `settings.json` before invoking the same wrapper. PRDY init/update, QA remediation, coord, orchestrator, and fleet launchers also converge on this boundary. Broker registration failure, unsafe isolated settings, unsafe generation state, or missing identity denies launch/tool execution fail-closed; broker timeout/unavailability and malformed replies also block tools.
Claude `PreCompact` and `SessionStart(compact)` hooks and Pi pre-/post-compaction handlers invoke `revoke-lease.py`. Pi `session_start` reload/new/resume/fork and Claude resume/clear advance the locked generation before revocation, so a replacement session inherits no lease even when PID/starttime stay unchanged. Do not invoke the revoker manually as a way to restore authority; it only removes authority. If a lifecycle hook reports failure, stop consequential work and repair broker/generation-state availability before re-verification.
Run the permanent launch inventory locally with:
```bash
python3 packages/mosaic/framework/tools/lease-broker/check-runtime-launches.py --root .
```
The same check runs in the Mosaic package test suite and therefore in root CI. Any direct Claude/Pi binary launch must be replaced with `launch-runtime.py`, `execLeaseGatedRuntime`, or the gated `mosaic` runtime command; do not add static allowlist exceptions.
Clients must complete the request boundary before waiting for a reply. After sending the single JSON object and its terminating newline, the client **MUST half-close the socket's write side** (`shutdown(SHUT_WR)` in POSIX clients; `socket.end()` in Node) and only then await the response. Merely calling `write()` and waiting is invalid: the broker waits for EOF to enforce the exact-one-frame contract and fails closed at its one-second deadline. Do not replace `end()` with `write()` in client helpers. A delayed second frame remains malformed and is rejected.
`mosaic_context_recover` is the only unverified mutator class. Its durable `mosaic-context-refresh` skill is a thin wrapper over `tools/lease-broker/recover-context.py`: `begin` has the broker rebuild the validated `B_payload`/`H_payload`, revoke first, and mint a new `PENDING_DELIVERY` receipt challenge; `complete` accepts neither receipt text nor a challenge argument. Claude maps only the exact direct recovery executable/validated arguments to this exempt tool identity; ordinary `Bash` remains gated. Pi exposes only the `mosaic_context_recover` custom tool; ordinary `bash` and all other tools remain gated. A normal-path receipt cannot be replayed through recovery because each retry begins a distinct recovery cycle and recovery completion cannot receive caller-presented evidence.
Production daemon startup creates a separate private observer socket unless a test-only `--test-observer-file` fixture is selected. Claude's Stop hook sends its exact latest assistant entry and Pi's `message_end` handler sends only finalized assistant content to that authenticated transport; the broker public socket never accepts message text. This is byte-build and private out-of-process harness wiring only: do not activate it against a live daemon, live socket, systemd service, tmux session, or model-output stream outside the controlled integration procedure.
Receipt honesty is load-bearing: absent, malformed, prefix-truncated, and observable adapter-mutated terminal receipts do not promote. A tail-only case is non-promoting only where the concrete terminal payload is malformed or observably incomplete. A tail-preserving middle drop is **not receipt-detectable**; it is the disclosed T-C injection-contract residual deferred to WI-7 server-side evidence. The receipt remains a T-A delivery/liveness prerequisite, never a safety, obedience, or residency proof. The framework skill is source-resident and bridge-projected on install/upgrade; do not hand-create a live runtime symlink.
After a runtime exits, its `generation-<session>.state` file may be removed only after verifying that no process for that broker-minted session remains; stale files carry no lease authority but should be retained during incident analysis. After a broker crash, preserve the protected state file and restart only after verifying that no broker owns the socket. Restart intentionally clears all volatile VERIFIED leases. A leftover socket requires an operator to verify the owning service is stopped and remove that exact socket deliberately. Corrupt, oversized, symlinked, or non-regular state fails closed; do not overwrite it. Preserve it for incident review and establish new state only through an explicit operational decision, which invalidates prior sessions and tokens.
## Security posture
Directory `0700` plus socket/state `0600` is built-in same-principal hardening only: it excludes other UIDs but does **not** stop the same UID from unlinking and counterfeiting the socket. It therefore does not close T-C same-UID replacement. WI-1 does not provide a distinct-principal boundary. A stronger distinct-principal deployment requires an external protected proxy, ACL, or service boundary that clients cannot unlink or rebind and that preserves the authenticated client identity required by the broker's `SO_PEERCRED` and ancestry checks. Server-side branch protection remains the irreducible backstop.
@@ -0,0 +1,98 @@
# Migrating to the Federated Tier
> **KBN-101-07 ownership:** This active documentation is a **non-operative KBN-101
> contract** with no current command authority until KBN-101-00, KBN-101-02, KBN-101-03, KBN-101-05, and KBN-101-06 land and
> KBN-101-08 activates an exact reviewed release. The commands below describe the produced interface only. Do not run them on the
> current branch or replace them with direct PostgreSQL, raw SQL, legacy storage migration, or
> credential-on-argv procedures.
## Held future procedure
This section is non-operative and grants no current command authority until KBN-101-00, KBN-101-03, and KBN-101-05 land.
The deployment control plane executes the complete held future procedure, in order: external bootstrap → TLS/roles → `mosaic-db-migrator --run` → `mosaic-db-migrator --verify` → Gateway/Compose readiness. The
runner is the only attestation producer after its verified TLS, identity, manifest, and schema
checks. A data importer is never a schema bootstrap, extension installer, repair command, or DDL
consumer.
## Target material contract
KBN-101-05 obtains the target URL from Vault KV-v2
`secret-{env}/mosaic-stack/database/importer`, key `url`, and reads its authenticated version from
the same successful response `data.metadata.version`. A hash or DSN byte sequence is not a
provider version. The renderer treats URL bytes and provider version as one generation, writes a
temporary generation directory with fsync plus atomic rename, and creates separate immutable
consumer mounts. Swarm uses distinct versioned secret/config references. A deployment cannot mix
generations.
| Consumer | Permitted material |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Migrator-attestation producer (`10003:10003`) | Its own migration URL/CA; read-only `/run/secrets/mosaic-migrate-target-url` and `/run/secrets/mosaic-migrate-target-version`, each `0400`, solely to bind; producer-only attestation output at `/run/mosaic-attestations-producer/migrate-target.v1.json`; root-wrapper-only signing key. It never connects with, uses, exports, logs, or forwards the importer URL/version. |
| Privileged deployment handoff controller | After runner success and before importer creation, it receives only root-owned non-secret expected provider-version/URL-SHA-256/generation descriptor and pinned public verifier key—not URL bytes or private key. It safe-opens/verifies descriptor and producer artifact, copies exact bytes to a new importer-only mount with fsync/atomic rename, sets `10002:10002` `0400`, seals it read-only, and refuses importer start on any partial/wrong-generation/wrong-owner/mode result. |
| Importer (`10002:10002`) | Its own immutable `0400` copies at the same URL/version paths; CA at exact `DATABASE_TLS_CA_CERT_PATH=/run/secrets/mosaic-db-ca.crt`; pinned Ed25519 public key; read-only `/run/mosaic-attestations/migrate-target.v1.json` supplied only by the sealed handoff. |
| Gateway/runtime/unrelated container | No importer URL/version, importer artifact, attestation private key, or unrelated CA mount. |
The migrator and importer safe-open URL, provider-version, attestation, and public-key files only
with `O_RDONLY|O_CLOEXEC|O_NOFOLLOW`; they validate from the opened fd that the file is regular,
has its expected owner/mode and link count one. The migrator digests only that URL fd for binding,
then zeroizes/closes it. The importer reads URL bytes once into protected memory, validates the
signed binding and exact CA before connecting from those same bytes, then zeroizes/closes every
fd. It neither logs nor exposes a URL/version/attestation/key oracle.
## Produced command interface
After activation and only after approved target preparation, the future interface is:
```bash
# Deployment control plane has already completed the held runner procedure above.
mosaic storage migrate-tier --to federated \
--target-url-file /run/secrets/mosaic-migrate-target-url \
--target-attestation-file /run/mosaic-attestations/migrate-target.v1.json \
--dry-run
```
The provider-version file is fixed deployment material, not argv. This connecting dry-run consumes its nonce; before an actual copy, the deployment control plane must provide fresh runner verification and a new sealed handoff. The runner uses its migration
identity; the importer connects only as non-DDL `mosaic_data_importer` and only after all
pre-connect validation. After verified TLS and before DML it compares PostgreSQL system ID,
database OID, `current_user`, CA/SPKI, and manifest/schema fingerprints to the artifact.
## Required refusals and evidence
KBN-101-02/-03/-05/-06 must prove, with stable sanitized errors, that no target connection occurs
for missing/unsafe URL/version/attestation/public-key files; symlink, hardlink, owner, mode, or
TOCTOU violations; mixed URL/version generations; missing/wrong CA mount; stale/replayed/tampered
or revoked-key artifacts; provider rotation/revocation; wrong TLS/server/database/role/manifest
binding; raw `--target-url`; `DATABASE_URL` fallback; runtime/owner identity; consumer leakage;
or any DDL attempt. Post-connect identity mismatch closes with zero DML/DDL. Tests also prove no
forwarding, child environment, logging, or error oracle leaks URL/version/key/artifact contents.
The attestation is credential-free JCS with detached Ed25519 signature and binds issued/expiry,
nonce, authenticated provider version, exact URL-fd SHA-256, TLS host/port/database, CA/SPKI,
PostgreSQL system ID/database OID, importer role, manifest/schema, and producer identity. Provider
version rotation invalidates an old artifact and requires a fresh rendered generation plus runner
verification.
## Actual copy after dry-run
After reviewed dry-run, obtain the required fresh verification/attestation generation, then use:
```bash
# Deployment control plane has supplied fresh runner verification and attestation.
mosaic storage migrate-tier --to federated \
--target-url-file /run/secrets/mosaic-migrate-target-url \
--target-attestation-file /run/mosaic-attestations/migrate-target.v1.json \
--yes
```
The dry-run artifact is terminally replayed and must be rejected; `--yes` bypasses no file,
generation, signature, TLS, identity, or DDL control.
## Data boundary and recovery
The importer has only an allowlisted mutable-table DML registry. It has no grant for immutable KBN
relations, schemas, roles, memberships, extensions, catalogs, or the Drizzle ledger. Source PGlite
uses its explicit local directory and does not make a PostgreSQL URL fallback valid.
A failed or ambiguous migration is a control-plane incident: preserve sanitized evidence, retain
the approved backup/rollback state, and retry only after independent review. Never inspect,
unlock, repair, or initialize the target with ad hoc SQL or copied credentials.
@@ -0,0 +1,43 @@
# Mos Connector Lease Operations — M1
## Operational status
M1 installs the durable schema and gateway policy/adapter boundary. It does **not** activate a connector, expose a lease administration endpoint, or cut over a channel. The default gateway connector-lease policy is deny-all until a later work package supplies an authorized server-side policy and concrete adapter.
## Events to monitor
Use correlation IDs to follow `connector_lease_audit_log` events:
| Event | Meaning |
| ---------- | --------------------------------------------------------------------- |
| `acquire` | First holder inserted for an unused binding |
| `renew` | Current holder heartbeat extended the TTL |
| `takeover` | Authorized CAS replaced the holder and incremented epoch |
| `release` | Current holder explicitly relinquished authority |
| `expiry` | An expired current lease was observed |
| `reject` | Policy, CAS, expiry, scope, or fencing validation denied an operation |
Audit data is metadata-only. Raw grant objects, connector payloads, scopes, tokens, approval references, and credentials must never be added to audit output.
## Incident checks
For suspected duplicate/stale connector effects:
1. Correlate the attempted operation with its `reject`, `takeover`, or `expiry` event.
2. Compare the current row's connector ID, lease UUID, epoch, expiry, and release time with the adapter's normalized execution context.
3. Treat an old epoch, old lease UUID, expired lease, or released lease as non-authoritative. Do not retry it as the old holder.
4. Recovery uses the authorized takeover path with the observed expected epoch. Ordinary acquire is intentionally rejected for expired/released rows.
5. If an external effect may already have happened, preserve evidence and do not assume lease fencing provides exactly-once replay safety.
## Migration and rollback safety
Migration `0016_salty_morlocks.sql` is additive: it creates two new tables and indexes without modifying existing authorization/session tables. Before rollout, normal database backup and migration verification still apply. Rolling application code back leaves unused additive tables in place; dropping tables is not part of automated rollback because it would destroy lease/audit evidence.
## Security constraints
- Tenant comes from authenticated gateway context, never a connector request field.
- Logical agent, binding, connector, and scope identifiers use normalized constrained forms.
- Takeover requires explicit gateway policy authorization and an expected epoch.
- Default defense-in-depth TTL caps are 5 minutes for leases and 30 seconds for grants; policy may enforce stricter limits.
- Validation and rejection audit complete before adapter side effects.
- Existing authz and exact-action approval controls remain additional required gates; a valid connector lease does not bypass them.
@@ -0,0 +1,147 @@
# Upgrade Safety & Recovery
How Mosaic protects operator-owned configuration under `~/.config/mosaic` across
framework upgrades, and how to recover if a projection is ever lost.
A framework upgrade runs `install.sh` in keep-mode (`MOSAIC_INSTALL_MODE=keep`,
`MOSAIC_SYNC_ONLY=1`) to refresh framework-owned files in place. The incident
this hardening addresses: an upgrade that silently overwrites or deletes a file
the operator owns — credentials, personas, a roster, or a generated agent env —
with no snapshot to fall back to.
Protection is layered. Each layer is independent; a later layer catches what an
earlier one misses.
## Layer 1 — Manifest-owned sync (prevention)
The single source of truth for ownership is
[`framework-manifest.txt`](../../packages/mosaic/framework/framework-manifest.txt).
Both the bash installer and the TypeScript sync path resolve every path against
this one file (parity is enforced by test), so they can never drift.
- Ownership is **allow-list, deny-wins**: a path is framework-owned only if a
`[framework]` glob matches and no `[operator]` carve-out overrides it.
- **Unknown paths default to operator** (fail-safe): a file the manifest never
anticipated is treated as operator-owned and is never pruned.
- Keep-mode does a non-deleting copy plus an explicit, manifest-scoped prune that
only ever iterates framework globs — operator and unknown paths are
structurally unreachable by the prune.
Result: a correct upgrade cannot touch operator config at all.
## Layer 2 — Durable pre-update snapshot + verify net (safety + rollback)
Before **any** mutation, the installer snapshots the operator-owned surface that
exists into:
```
${XDG_STATE_HOME:-~/.local/state}/mosaic/backups/pre-update-<UTC-timestamp>/
```
- `0700` directories / `0600` files (`umask 077`, scoped and restored),
outside `~/.config/mosaic` and outside any repo.
- **Fail-open**: a snapshot failure warns but never aborts the upgrade it
protects.
- Retention is `MOSAIC_BACKUP_RETENTION` snapshots (default 5).
After the sync, a **verify net** compares each snapshot file against its target
and restores (with a loud warning) any operator file the upgrade diverged or
removed — a divergence means a manifest bug slipped through Layer 1.
Inspect and restore snapshots with the CLI:
```bash
mosaic restore --list # dry-run: enumerate snapshots by timestamp
mosaic restore --from <UTC-timestamp> # restore the operator surface from one snapshot
mosaic restore --from <ts> --dry-run # preview a specific restore without writing
```
`mosaic restore` reports **counts and relative paths only** — it never emits file
contents, so a secret in `tools/_lib/credentials.json` is never echoed. Restores
are confirmation-gated (`--yes` or `MOSAIC_ASSUME_YES`) and write each leaf
atomically with `O_NOFOLLOW` (a symlink swapped in after the snapshot fails
closed rather than following out of the managed tree).
## Layer 3 — Regeneration from roster SSOT (recovery)
Some operator files are **derived** and do not need a byte-for-byte snapshot to
recover — they can be rebuilt from their source of truth. The fleet's per-agent
generated env projections are the prime case:
- `~/.config/mosaic/fleet/agents/<name>.env.generated` is a deterministic
projection of `~/.config/mosaic/fleet/roster.yaml`.
- The launcher (`start-agent-session.sh`, invoked by
`mosaic-agent@<name>.service`) sources that generated projection to establish
each agent's identity, runtime, model, and working directory. If it is missing
or wrong, the agent cannot launch with its intended identity.
`mosaic fleet regen` rebuilds those projections from the roster SSOT:
```bash
mosaic fleet regen # dry-run (default): show what would be rebuilt
mosaic fleet regen --json # same, machine-readable
mosaic fleet regen --write # rebuild the projections on disk
```
- **Dry-run by default.** Nothing is written until you pass `--write`.
- **Deterministic and idempotent** — the projection is a pure function of the
roster, so repeated `--write` runs produce byte-identical files.
- **Projection-only. It never restarts an agent.** Recovery order forbids
restart-before-verify; `regen` has no path to systemd lifecycle at all.
- **It rebuilds only `<name>.env.generated`** — it never writes, relocates, or
deletes the operator-owned `.env` / `.env.local` surface.
- It **validates the roster the same way `reconcile` does** (persona resolution
and protected-class tool-policy match), so a hand-edited or corrupt roster is
rejected rather than projected, and a `--write` takes the shared reconcile
lock so it cannot race a concurrent reconcile.
- Output is **paths and counts only** — the rendered `KEY=value` body is never
echoed.
`regen` uses the exact same roster→env mapping as `mosaic fleet reconcile`, so a
recovered projection matches what a normal reconcile would have written.
## Recovery runbook — wiped `fleet/agents/*.env.generated`
If an upgrade (or a manual mistake) has left an agent without its generated
projection, **do not restart the unit first** — a launch against a missing
projection fails closed, and any stale state must be corrected before restart,
not after.
1. **Prefer a snapshot restore if one exists** (byte-exact operator state):
```bash
mosaic restore --list
mosaic restore --from <UTC-timestamp>
```
2. **Otherwise regenerate the derived projections from the roster SSOT:**
```bash
mosaic fleet regen # confirm the plan (create vs rebuild per agent)
mosaic fleet regen --write # rebuild fleet/agents/<name>.env.generated
```
3. **Verify each unit will resolve the intended runtime/workdir _before_ any
restart.** The unit sets **no** `EnvironmentFile=` — it launches from a minimal
environment and `start-agent-session.sh` sources `.env.generated` itself, so
verify the generated file directly and confirm the launcher path:
```bash
# Confirm fleet/agents/<name>.env.generated exists and carries the intended
# MOSAIC_AGENT_* values (name, runtime, model, workdir, socket).
test -f ~/.config/mosaic/fleet/agents/<name>.env.generated
# Confirm the unit launches the session script that reads it.
systemctl --user cat mosaic-agent@<name> | grep ExecStart
```
4. **Only then restart, one unit at a time:**
```bash
systemctl --user restart mosaic-agent@<name>
```
## See also
- Design: [`docs/design/791-upgrade-config-protection.md`](../design/791-upgrade-config-protection.md)
- Fleet operations: [`docs/guides/fleet-local-canary.md`](./fleet-local-canary.md)
- Ownership SSOT: [`packages/mosaic/framework/framework-manifest.txt`](../../packages/mosaic/framework/framework-manifest.txt)
+621
View File
@@ -0,0 +1,621 @@
# Mosaic Stack — User Guide
## Table of Contents
1. [Getting Started](#getting-started)
2. [Chat Interface](#chat-interface)
3. [Projects](#projects)
4. [Tasks](#tasks)
5. [Settings](#settings)
6. [CLI Usage](#cli-usage)
7. [Sub-package Commands](#sub-package-commands)
8. [Telemetry](#telemetry)
9. [Local Fleet Canary](./fleet-local-canary.md)
---
## Getting Started
### Prerequisites
Mosaic Stack requires a running gateway. Your administrator provides the URL
(default: `http://localhost:14242`) and creates your account.
### Logging In (Web)
1. Navigate to the Mosaic web app (default: `http://localhost:3000`).
2. You are redirected to `/login` automatically.
3. Enter your email and password, then click **Sign in**.
4. On success you land on the **Chat** page.
### Registering an Account
If self-registration is enabled:
1. Go to `/register`.
2. Enter your name, email, and password.
3. Submit. You are signed in and redirected to Chat.
---
## Chat Interface
### Sending a Message
1. Type your message in the input bar at the bottom of the Chat page.
2. Press **Enter** to send.
3. The assistant response streams in real time. A spinner indicates the agent is
processing.
### Streaming Responses
Responses appear token by token as the model generates them. You can read the
response while it is still being produced. The streaming indicator clears when
the response is complete.
### Conversation Management
- **New conversation**: Navigate to `/chat` or click **New Chat** in the sidebar.
A new conversation ID is created automatically on your first message.
- **Resume a conversation**: Conversations are stored server-side. Refresh the
page or navigate away and back to continue where you left off. The current
conversation ID is shown in the URL.
- **Conversation list**: The sidebar shows recent conversations. Click any entry
to switch.
### Model and Provider
The current model and provider are displayed in the chat header. To change them,
use the Settings page (see [Provider Settings](#providers)) or the CLI
`/model` and `/provider` commands.
---
## Projects
Projects group related missions and tasks. Navigate to **Projects** in the
sidebar.
### Creating a Project
1. Go to `/projects`.
2. Click **New Project**.
3. Enter a name and optional description.
4. Select a status: `active`, `paused`, `completed`, or `archived`.
5. Save. The project appears in the list.
### Viewing a Project
Click a project card to open its detail view at `/projects/<id>`. From here you
can see the project's missions, tasks, and metadata.
### Managing Tasks within a Project
Tasks are linked to projects and optionally to missions. See [Tasks](#tasks) for
full details. On the project detail page, the task list is filtered to the
selected project.
---
## Tasks
Navigate to **Tasks** in the sidebar to see all tasks across all projects.
### Task Statuses
| Status | Meaning |
| ------------- | ------------------------ |
| `not-started` | Not yet started |
| `in-progress` | Actively being worked on |
| `blocked` | Waiting on something |
| `done` | Completed |
| `cancelled` | No longer needed |
### Creating a Task
1. Go to `/tasks`.
2. Click **New Task**.
3. Enter a title, optional description, and link to a project or mission.
4. Set the status and priority.
5. Save.
### Updating a Task
Click a task to open its detail panel. Edit the fields inline and save.
---
## Settings
Navigate to **Settings** in the sidebar (or `/settings`) to manage your profile,
appearance, and providers.
### Profile Tab
- **Name**: Display name shown in the UI.
- **Email**: Read-only; contact your administrator to change email.
- Changes save automatically when you click **Save Profile**.
### Appearance Tab
- **Theme**: Choose `light`, `dark`, or `system`.
- The theme preference is saved to your account and applies on all devices.
### Notifications Tab
Configure notification preferences (future feature; placeholder in the current
release).
### Providers Tab
View all configured LLM providers and their models.
- **Test Connection**: Click **Test** next to a provider to check reachability.
The result shows latency and discovered models.
- Provider configuration is managed by your administrator via environment
variables. See the [Admin Guide](./admin-guide.md) for setup.
---
## CLI Usage
The `mosaic` CLI provides a terminal interface to the same gateway API.
### Installation
Install via the Mosaic installer:
```bash
curl -fsSL https://mosaicstack.dev/install.sh | bash
```
Or use the direct URL:
```bash
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
```
The installer places the `mosaic` binary at `~/.npm-global/bin/mosaic`.
Install lanes:
| Lane | Command | Source |
| ------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------------- |
| Stable | `bash tools/install.sh` | npm `@mosaicstack/mosaic@latest` + `main` |
| Prerelease integration | `bash tools/install.sh --next` | Fast npm `@mosaicstack/mosaic@next` + `@mosaicstack/gateway@next`; source fallback at `next` |
| Contributor/source build | `bash tools/install.sh --dev --ref X` | Build-from-source at the requested ref |
`--next` is fast-by-default from the Gitea npm `next` dist-tag and falls back to a source build at the permanent `next` branch if the dist-tag is missing or unreachable. Explicit `--ref` or `MOSAIC_REF` still wins and uses the source path.
Flags for non-interactive use:
```bash
--yes # Accept all defaults
--no-auto-launch # Skip auto-launch of wizard after install
```
Unrecognized flags or positional arguments fail before installation starts and print the supported-option usage.
Or if installed globally:
```bash
mosaic --help
```
### First-Run Wizard
After install the wizard launches automatically. You can re-run it at any time:
```bash
mosaic wizard
```
The wizard guides you through:
1. Gateway discovery or installation (`mosaic gateway install`)
2. Authentication (`mosaic gateway login`)
3. Post-install health check (`mosaic gateway verify`)
### Gateway Login and Token Recovery
```bash
# Authenticate with a gateway and save a session token
mosaic gateway login
# Verify the gateway is reachable and responding
mosaic gateway verify
# Rotate your current API token
mosaic gateway config rotate-token
# Recover a token via BetterAuth cookie (for accounts with no token)
mosaic gateway config recover-token
```
If you have an existing gateway account but lost your token (common after a
reinstall), use `mosaic gateway config recover-token` to retrieve a new one
without recreating your account.
### Configuration
```bash
# Print full config as JSON
mosaic config show
# Read a specific key
mosaic config get gateway.url
# Write a key
mosaic config set gateway.url http://localhost:14242
# Open config in $EDITOR
mosaic config edit
# Print config file path
mosaic config path
```
### Signing In (Legacy)
```bash
mosaic login --gateway http://localhost:14242 --email [email protected]
```
You are prompted for a password if `--password` is not supplied. The session
cookie is saved locally and reused on subsequent commands.
### Launching the TUI
```bash
mosaic tui
```
Options:
| Flag | Default | Description |
| ----------------------- | ------------------------ | ---------------------------------- |
| `--gateway <url>` | `http://localhost:14242` | Gateway URL |
| `--conversation <id>` | — | Resume a specific conversation |
| `--model <modelId>` | server default | Model to use (e.g. `llama3.2`) |
| `--provider <provider>` | server default | Provider (e.g. `ollama`, `openai`) |
If no valid session exists you are prompted to sign in before the TUI launches.
### TUI Slash Commands
Inside the TUI, type a `/` command and press Enter:
| Command | Description |
| ---------------------- | ------------------------------ |
| `/model <modelId>` | Switch to a different model |
| `/provider <provider>` | Switch to a different provider |
| `/models` | List available models |
| `/exit` or `/quit` | Exit the TUI |
### Session Management
```bash
# List saved sessions
mosaic sessions list
# Resume a session
mosaic sessions resume <sessionId>
# Destroy a session
mosaic sessions destroy <sessionId>
```
### Other Commands
```bash
# Run the Mosaic installation wizard
mosaic wizard
# PRD wizard (generate product requirement documents)
mosaic prdy
# Quality rails scaffolder
mosaic quality-rails
```
---
### Claude Code Skill Registration
Mosaic stores canonical skills under `~/.config/mosaic/skills/`. Claude Code scans
`~/.claude/skills/`, so Mosaic maintains one symlink per skill between those
directories.
```bash
mosaic skill list
mosaic skill register <name>
mosaic skill unregister <name>
```
- `register` is idempotent and repairs a dangling Mosaic-owned link. Names use
the safe grammar `[A-Za-z0-9][A-Za-z0-9._-]*`; files, directories, foreign
symlinks, path traversal, absolute paths, and names beginning with `-` are
refused.
- `unregister` is idempotent when no entry exists. It removes only symlinks that
point inside `~/.config/mosaic/skills/`; foreign entries are never removed.
- `list` reports `registered`, `unregistered`, `dangling`, `foreign`,
`foreign-dangling`, or `misdirected` for each canonical or Claude entry.
Install, wizard finalization, and `mosaic update` framework re-seeding reconcile
every canonical skill automatically. A skill directory added after initial
setup therefore receives its Claude bridge without a per-skill code change or
manual `ln -s`. If Claude Code is already running, use `/reload-skills` or start
a new session after registration so its in-process skill registry rescans.
This command group is Claude-only in M1. Pi can consume Mosaic's canonical skill
root through its Mosaic launcher configuration and does not need this Claude
bridge. Codex has a separate link path managed by the legacy full skill-sync
script; equivalent lifecycle management remains follow-up scope and is not
changed here.
## Sub-package Commands
Each Mosaic sub-package exposes its full API surface through the `mosaic` CLI.
All sub-package commands accept `--help` for usage details.
### `mosaic auth` — User & Authentication Management
Manage gateway users, SSO providers, and active sessions.
```bash
# List all users
mosaic auth users list
# Create a new user
mosaic auth users create --email [email protected] --name "Alice"
# Delete a user
mosaic auth users delete <userId>
# List configured SSO providers
mosaic auth sso
# List active sessions
mosaic auth sessions list
# Revoke a session
mosaic auth sessions revoke <sessionId>
```
### `mosaic brain` — Projects, Missions, Tasks, Conversations
Browse and manage the brain data layer (PostgreSQL-backed project/mission/task
store).
```bash
# List all projects
mosaic brain projects
# List missions for a project
mosaic brain missions --project <projectId>
# List tasks
mosaic brain tasks --status in-progress
# Browse conversations
mosaic brain conversations
mosaic brain conversations --project <projectId>
```
### `mosaic config` — CLI Configuration
Read and write the `mosaic` CLI configuration file.
```bash
# Show full config
mosaic config show
# Get a value
mosaic config get gateway.url
# Set a value
mosaic config set theme dark
# Open in editor
mosaic config edit
# Print file path
mosaic config path
```
### `mosaic forge` — AI Pipeline Management
Interact with the Forge multi-stage AI delivery pipeline (intake → board review
→ planning → coding → review → deploy).
```bash
# Start a new forge run for a brief
mosaic forge run --brief "Add dark mode toggle to settings"
# Check status of a running pipeline
mosaic forge status
mosaic forge status --run <runId>
# Resume a paused or interrupted run
mosaic forge resume --run <runId>
# List available personas (board review evaluators)
mosaic forge personas
```
### `mosaic gateway` — Gateway Lifecycle
Install, authenticate with, and verify the Mosaic gateway service.
```bash
# Install gateway (guided)
mosaic gateway install
# Verify gateway health post-install
mosaic gateway verify
# Log in and save token
mosaic gateway login
# Rotate API token
mosaic gateway config rotate-token
# Recover token via BetterAuth cookie (lost-token recovery)
mosaic gateway config recover-token
```
### `mosaic log` — Structured Log Access
Query and stream structured logs from the gateway.
```bash
# Stream live logs
mosaic log tail
mosaic log tail --level warn
# Search logs
mosaic log search "database connection"
mosaic log search --since 1h "error"
# Export logs to file
mosaic log export --output logs.json
mosaic log export --since 24h --level error --output errors.json
# Get/set log level
mosaic log level
mosaic log level debug
```
### `mosaic macp` — MACP Protocol
Interact with the MACP credential resolution, gate runner, and event bus.
```bash
# List MACP tasks
mosaic macp tasks
mosaic macp tasks --status pending
# Submit a new MACP task
mosaic macp submit --type credential-resolve --payload '{"key":"OPENAI_API_KEY"}'
# Run a gate check
mosaic macp gate --gate quality-check
# Stream MACP events
mosaic macp events
mosaic macp events --filter credential
```
### `mosaic memory` — Agent Memory
Query and inspect the agent memory layer.
```bash
# Semantic search over memory
mosaic memory search "previous decisions about auth"
# Show memory statistics
mosaic memory stats
# Generate memory insights report
mosaic memory insights
# View stored preferences
mosaic memory preferences
mosaic memory preferences --set editor=neovim
```
### `mosaic queue` — Task Queue (Valkey)
Manage the Valkey-backed task queue.
```bash
# List all queues
mosaic queue list
# Show queue statistics
mosaic queue stats
mosaic queue stats --queue agent-tasks
# Pause a queue
mosaic queue pause agent-tasks
# Resume a paused queue
mosaic queue resume agent-tasks
# List jobs in a queue
mosaic queue jobs agent-tasks
mosaic queue jobs agent-tasks --status failed
# Drain (empty) a queue
mosaic queue drain agent-tasks
```
### `mosaic storage` — Object Storage
Manage object storage tiers and data migrations.
```bash
# Show storage status and usage
mosaic storage status
# List available storage tiers
mosaic storage tier
# Export data from storage
mosaic storage export --bucket agent-artifacts --output ./artifacts.tar.gz
# Import data into storage
mosaic storage import --bucket agent-artifacts --input ./artifacts.tar.gz
# Schema migration is unavailable in this release. The current storage wrapper shells
# directly to `pnpm --filter @mosaicstack/db db:migrate`; it is legacy N-1,
# uncertified, and MUST NOT be invoked pending KBN-101-02/-03/-06/-08 activation.
# Future schema migration is non-operative: external bootstrap → TLS/roles → runner
# --run → runner --verify → readiness.
# Tier copy uses only the separately held secure migrate-tier route. Never use a legacy
# --from/--to storage-migrate command or pass a credential on argv.
```
---
## Telemetry
Mosaic includes an OpenTelemetry-based telemetry system. Local telemetry
(traces, metrics sent to Jaeger) is always available. Remote telemetry upload
requires explicit opt-in.
### Local Telemetry
```bash
# Show local OTEL collector / Jaeger status
mosaic telemetry local status
# Tail live OTEL spans
mosaic telemetry local tail
# Open Jaeger UI URL
mosaic telemetry local jaeger
```
### Remote Telemetry
Remote upload is a no-op (dry-run) until you opt in. Your consent state is
persisted in the config file.
```bash
# Show current consent state
mosaic telemetry status
# Opt in to remote telemetry
mosaic telemetry opt-in
# Opt out (data stays local)
mosaic telemetry opt-out
# Test telemetry pipeline without uploading
mosaic telemetry test
# Upload telemetry (requires opt-in; dry-run otherwise)
mosaic telemetry upload
```