Round-four remediation of both blockers gate-ultron-01 raised ond99ff57e. Measured against that head before anything was touched; all seven returned rc=0, and each executes the program the check exists to recognize: "/usr/bin/curl" --config /tmp/w.cfg -> allowed './curl' --config /tmp/w.cfg -> allowed $(which curl) --config /tmp/w.cfg -> allowed `which curl` --config /tmp/w.cfg -> allowed /usr/bin/gh api -X POST repos/a/b/issues … -> allowed ./gh api -X POST repos/a/b/issues … -> allowed /usr/local/bin/tea api -X POST repos/a/b/… … -> allowed This is the third appearance of one defect, and the shape is worth stating plainly because the first two repairs each fixed an INSTANCE and left the class: the check matched the bare word, then it matched the unquoted basename. Both were models of one TEXTUAL PRESENTATION of a shell word rather than of the word, so the first repair was defeated by an absolute path and the second by two quote characters. Recognizing a name is either done after quote removal or it is caller-name parsing wearing a longer regex. The second blocker is the same defect sitting untouched in the API SCOPE gate the whole time, while the curl arm was repaired twice beside it. That one is worse than it looks: the scope gate decides whether write detection runs AT ALL, so failing to admit `/usr/bin/gh api -X POST` is not a missed match, it is an allow. No URL marker rescued those commands either — provider CLI endpoints are spelled `repos/…` with no leading slash, so `/repos/` never matched them. Fix, and the reason it is one fix rather than two: - $CMD_NAMES — a second reading of the same command with quote and substitution punctuation turned into whitespace. Names are read from it. - $NAME_PREFIX — the one place the shape of a program name is written down. Both callers use it, so the next fix to this class lands in a single location instead of whichever arm review happened to probe. That is the actual lesson of finding this defect twice in one file. The prefix still must end at a slash. `mycurl` and `curl-wrapper` are different programs and blocking them is the over-block that gets a guard routed around instead of repaired; both remain negative fixtures, and `mygh` and an absolute-path READ join them. The cost is the one this file already chose and documented for the payload check: quoting an example does not exempt it, so writing one of these commands inside quotes on a Bash line is refused too. Applying that rule to the name arms makes the file coherent — the alternative is a guard where the payload arm treats quotes as text and the name arms treat them as armour. Still open, stated rather than left to be found: a name absent from the text — assembled from variables, or reached through a wrapper script that execs the program — is invisible here. That is a limit of inspecting a command string, not something a pattern closes. Controls: the 7 positive fixtures FAIL atd99ff57eand pass here; the 4 negative fixtures pass at BOTH heads, so they measure over-blocking rather than decorate the diff. Suite 157/157.
Mosaic Stack
Self-hosted, multi-user AI agent platform. One config, every runtime, same standards.
Mosaic gives you a unified launcher for Claude Code, Codex, OpenCode, and Pi — injecting consistent system prompts, guardrails, skills, and mission context into every session. A NestJS gateway provides the API surface, a Next.js dashboard gives you the UI, and a plugin system connects Discord, Telegram, and more.
Quick Install
curl -fsSL https://mosaicstack.dev/install.sh | bash
Or use the direct URL:
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
The installer auto-launches the setup wizard, which walks you through gateway install and verification. Flags for non-interactive use:
bash <(curl -fsSL …) --yes # Accept all defaults
bash <(curl -fsSL …) --yes --no-auto-launch # Install only, skip wizard
This installs both components:
| Component | What | Where |
|---|---|---|
| Framework | Bash launcher, guides, runtime configs, tools, skills | ~/.config/mosaic/ |
| @mosaicstack/mosaic | Unified mosaic CLI — TUI, gateway client, wizard, auto-updater |
~/.npm-global/bin/ |
After install, the wizard runs automatically or you can invoke it manually:
mosaic wizard # Full guided setup (gateway install → verify)
Requirements
- Node.js ≥ 20
- npm (for global @mosaicstack/mosaic install)
- One or more runtimes: Claude Code, Codex, OpenCode, or Pi
Usage
Launching Agent Sessions
mosaic pi # Launch Pi with Mosaic injection
mosaic claude # Launch Claude Code with Mosaic injection
mosaic codex # Launch Codex with Mosaic injection
mosaic opencode # Launch OpenCode with Mosaic injection
mosaic yolo claude # Claude with dangerous-permissions mode
mosaic yolo pi # Pi in yolo mode
The launcher verifies your config, checks for SOUL.md, injects your AGENTS.md standards into the runtime, and forwards all arguments.
Pi launches default to a token-lean skill posture: mosaic pi passes --no-skills so Pi does not preload every global skill description into the system prompt. Use MOSAIC_PI_SKILL_MODE=all mosaic pi for the legacy all-skills catalog, or MOSAIC_PI_SKILL_MODE=discover mosaic pi to let Pi use its native settings/project skill discovery.
Mosaic also loads its Pi extensions from ~/.config/mosaic/runtime/pi/. Inside Pi,
/goal set <statement> starts a bounded persistent loop that checks every turn and successful
compaction, requires two evidence-bearing completion reports, and can be inspected or stopped with
/goal status, /goal pause, /goal resume, and /goal cancel. Controller-owned goal-state
entries redact common credential shapes, but Pi's model/tool-call history is separate, so goals and
evidence must never contain secrets or raw sensitive output. Mosaic does not install this extension
into ~/.pi/agent/extensions/.
TUI & Gateway
mosaic tui # Interactive TUI connected to the gateway
mosaic gateway login # Authenticate with a gateway instance
mosaic sessions list # List active agent sessions
Gateway Management
mosaic gateway install # Install and configure the gateway service
mosaic gateway verify # Post-install health check
mosaic gateway login # Authenticate and store a session token
mosaic gateway config rotate-token # Rotate your API token
mosaic gateway config recover-token # Recover a token via BetterAuth cookie
If you already have a gateway account but no token, use mosaic gateway config recover-token to retrieve one without recreating your account.
Configuration
Mosaic supports three storage tiers: local (PGlite, single-host), standalone (PostgreSQL, single-host), and federated (PostgreSQL + pgvector + Valkey, multi-host). See Federated Tier Setup for multi-user and production deployments, or Migrating to Federated to upgrade from existing tiers.
mosaic config show # Print full config as JSON
mosaic config get <key> # Read a specific key
mosaic config set <key> <val># Write a key
mosaic config edit # Open config in $EDITOR
mosaic config path # Print config file path
Management
mosaic doctor # Health audit — detect drift and missing files
mosaic sync # Sync skills from canonical source
mosaic skill list # Audit Claude skill registrations and conflicts
mosaic skill register <name> # Register one canonical skill with Claude Code
mosaic skill unregister <name> # Remove one Mosaic-owned Claude link
mosaic update # Update CLI/framework and auto-register canonical skills
mosaic wizard # Full guided setup wizard
mosaic bootstrap <path> # Bootstrap a repo with Mosaic standards
mosaic coord init # Initialize a new orchestration mission
mosaic prdy init # Create a PRD via guided session
Sub-package Commands
Each Mosaic sub-package exposes its API surface through the unified CLI:
# User management
mosaic auth users list
mosaic auth users create
mosaic auth sso
# Agent brain (projects, missions, tasks)
mosaic brain projects
mosaic brain missions
mosaic brain tasks
mosaic brain conversations
# Agent forge pipeline
mosaic forge run
mosaic forge status
mosaic forge resume
mosaic forge personas
# Structured logging
mosaic log tail
mosaic log search
mosaic log export
mosaic log level
# MACP protocol
mosaic macp tasks
mosaic macp submit
mosaic macp gate
mosaic macp events
# Agent memory
mosaic memory search
mosaic memory stats
mosaic memory insights
mosaic memory preferences
# Task queue (Valkey)
mosaic queue list
mosaic queue stats
mosaic queue pause
mosaic queue resume
mosaic queue jobs
mosaic queue drain
# Object storage
mosaic storage status
mosaic storage tier
mosaic storage export
mosaic storage import
# 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.
Telemetry
# Local observability (OTEL / Jaeger)
mosaic telemetry local status
mosaic telemetry local tail
mosaic telemetry local jaeger
# Remote telemetry (dry-run by default)
mosaic telemetry status
mosaic telemetry opt-in
mosaic telemetry opt-out
mosaic telemetry test
mosaic telemetry upload # Dry-run unless opted in
Consent state is persisted in config. Remote upload is a no-op until you run mosaic telemetry opt-in.
Development
Prerequisites
- Node.js ≥ 20
- pnpm 10.6+
- Docker & Docker Compose
Setup
git clone [email protected]:mosaicstack/stack.git
cd stack
# Install dependencies. The local tier uses in-process PGlite; leave DATABASE_URL unset.
# The pnpm store defaults to $HOME/.local/share/pnpm/store. Override it without
# editing the checkout with NPM_CONFIG_STORE_DIR=$HOME/another-store if needed.
pnpm install
# Verify dependencies and generated state before running source-quality gates.
# Missing dependencies exit 42; stale/foreign apps/web/.next state exits 43.
# The web build certifies its exact standalone symlink manifest; added, removed,
# retargeted, or manifest-only-tampered generated links also exit 43. This detects
# accidental, independent, stale, and foreign-residue mutation—the class exposed by
# a five-month-stale .next that produced 19 phantom TS2307 errors.
# It does NOT defend against a same-UID actor that can rewrite both manifest and
# marker consistently (CWE-345). RM-59 tracks the required executor/spine-side
# trust anchor outside worktree authority.
pnpm preflight
# Optional local queue service only. This does not start PostgreSQL.
docker compose up -d valkey
# The current Gateway/Web local process is held; see docs/guides/dev-guide.md.
# Do not start it until KBN-101-02 makes inherited dotenv/DSN state fail closed.
Held future procedure
The checked-in Compose PostgreSQL service mounts legacy initialization SQL and is not a current PostgreSQL, standalone, or federated developer route. Do not start it with Compose, invoke initialization SQL, or treat the planned migrator as currently executable.
Held future activation procedure — non-operative and no current command authority until KBN-101-00, KBN-101-03, and KBN-101-05
land: external bootstrap → TLS/roles → mosaic-db-migrator --run →
mosaic-db-migrator --verify → Gateway/Compose readiness. The future deployment artifacts—not
this README—will provide the reviewed commands and secret-consumer interface.
For local data-layer work, PGlite needs no PostgreSQL service. The optional Compose command above
starts only Valkey; OTEL Collector and Jaeger may likewise be started individually if needed,
without starting PostgreSQL. A Gateway/Web local process is not currently a safe PGlite route:
its unguarded dotenv loader may inherit a daemon PostgreSQL DSN. Do not use root pnpm dev or a
Gateway start command until KBN-101-02 makes that state fail closed.
Quality Gates
pnpm preflight # Checkout/dependency/generated-state validation
pnpm typecheck # TypeScript type checking (all packages)
pnpm lint # ESLint (all packages)
pnpm test # Vitest (all packages)
pnpm format:check # Prettier check
pnpm format # Prettier auto-fix
CI
Woodpecker CI runs on every push:
pnpm install --frozen-lockfile- Legacy N-1 CI status only — active, uncertified, and non-authorizing as an operator route: the checked-in job currently invokes
pnpm --filter @mosaicstack/db run db:migratewithDATABASE_URLagainst an isolated disposable PostgreSQL CI database. It performs direct DDL in that CI database, is not approved ordinary behavior or an operator route, and remains a known exception pending KBN-101-06 removal/replacement by the certified runner-backed CI path. pnpm test(Turbo-orchestrated across all packages)
npm packages are published to the Gitea package registry on main merges.
Architecture
stack/
├── apps/
│ ├── gateway/ NestJS API + WebSocket hub (Fastify, Socket.IO, OTEL)
│ └── web/ Next.js dashboard (React 19, Tailwind)
├── packages/
│ ├── mosaic/ Unified CLI — TUI, gateway client, wizard, sub-package commands
│ ├── types/ Shared TypeScript contracts (Socket.IO typed events)
│ ├── db/ Drizzle ORM schema + migrations (pgvector)
│ ├── auth/ BetterAuth configuration
│ ├── brain/ Data layer (PG-backed)
│ ├── queue/ Valkey task queue + MCP
│ ├── coord/ Mission coordination
│ ├── forge/ Multi-stage AI pipeline (intake → board → plan → code → review)
│ ├── macp/ MACP protocol — credential resolution, gate runner, events
│ ├── agent/ Agent session management
│ ├── memory/ Agent memory layer
│ ├── log/ Structured logging
│ ├── prdy/ PRD creation and validation
│ ├── quality-rails/ Quality templates (TypeScript, Next.js, monorepo)
│ └── design-tokens/ Shared design tokens
├── plugins/
│ ├── discord/ Discord channel plugin (discord.js)
│ ├── telegram/ Telegram channel plugin (Telegraf)
│ ├── macp/ OpenClaw MACP runtime plugin
│ └── mosaic-framework/ OpenClaw framework injection plugin
├── tools/
│ └── install.sh Unified installer (framework + npm CLI, --yes / --no-auto-launch)
├── scripts/agent/ Agent session lifecycle scripts
├── docker-compose.yml Dev infrastructure
└── .woodpecker/ CI pipeline configs
Key Design Decisions
- Gateway is the single API surface — all clients (TUI, web, Discord, Telegram) connect through it
- ESM everywhere —
"type": "module",.jsextensions in imports, NodeNext resolution - Socket.IO typed events — defined in
@mosaicstack/types, enforced at compile time - OTEL auto-instrumentation — loads before NestJS bootstrap
- Explicit
@Inject()decorators — required since tsx/esbuild doesn't emit decorator metadata
Framework (~/.config/mosaic/)
The framework is the bash-based standards layer installed to every developer machine:
~/.config/mosaic/
├── AGENTS.md ← Central standards (loaded into every runtime)
├── SOUL.md ← Agent identity (name, style, guardrails)
├── USER.md ← User profile (name, timezone, preferences)
├── TOOLS.md ← Machine-level tool reference
├── bin/mosaic ← Unified launcher (claude, codex, opencode, pi, yolo)
├── guides/ ← E2E delivery, orchestrator protocol, PRD, etc.
├── runtime/ ← Per-runtime configs (claude/, codex/, opencode/, pi/)
├── skills/ ← Universal skills (synced from agent-skills repo)
├── tools/ ← Tool suites (orchestrator, git, quality, prdy, etc.)
└── memory/ ← Persistent agent memory (preserved across upgrades)
Forge Pipeline
Forge is a multi-stage AI pipeline for autonomous feature delivery:
Intake → Discovery → Board Review → Planning (3 stages) → Coding → Review → Remediation → Test → Deploy
Each stage has a dispatch mode (exec for research/review, yolo for coding), quality gates, and timeouts. The board review uses multiple AI personas (CEO, CTO, CFO, COO + specialists) to evaluate briefs before committing resources.
Upgrading
Run the installer again — it handles upgrades automatically:
curl -fsSL https://mosaicstack.dev/install.sh | bash
Or use the direct URL:
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
Or use the CLI:
mosaic update # Check + install CLI updates
mosaic update --check # Check only, don't install
The CLI also performs a background update check on every invocation (cached for 1 hour).
Installer Flags
bash tools/install.sh --check # Version check only
bash tools/install.sh --framework # Framework only (skip npm CLI)
bash tools/install.sh --cli # npm CLI only (skip framework)
bash tools/install.sh --ref v1.0 # Install from a specific git ref
bash tools/install.sh --yes # Non-interactive, accept all defaults
bash tools/install.sh --no-auto-launch # Skip auto-launch of wizard
The installer rejects unrecognized flags or positional arguments before making changes and prints the supported-option usage.
Contributing
# Create a feature branch
git checkout -b feat/my-feature
# Make changes, then verify
pnpm typecheck && pnpm lint && pnpm test && pnpm format:check
# Commit (husky runs lint-staged automatically)
git commit -m "feat: description of change"
# Push and create PR
git push -u origin feat/my-feature
DTOs go in *.dto.ts files at module boundaries. Scratchpads (docs/scratchpads/) are mandatory for non-trivial tasks. See AGENTS.md for the full standards reference.
License
Proprietary — all rights reserved.