Monorepo Configuration
Workloom handles monorepos natively through multi-target workspaces. Each target (root, app, service, etc.) gets its own set of providers, packages, and rendered files. Targets resolve independently with no cross-target interference.
Multi-Target Workspaces
Instead of creating separate workspace manifests for each app in your monorepo, define a single workspace with multiple targets:
{
"workspaceId": "wksp_monorepo_01",
"orgId": "org_acme",
"name": "Acme Monorepo",
"binding": {
"type": "git-repository",
"gitRemote": "git@github.com:acme/monorepo.git"
},
"targets": [
{
"id": "root",
"path": ".",
"providers": ["codex", "copilot"],
"packages": [
{ "packageId": "@acme/baseline@1.0.0" }
]
},
{
"id": "frontend",
"path": "apps/web",
"providers": ["claude", "codex"],
"packages": [
{ "packageId": "@acme/baseline@1.0.0" },
{ "packageId": "@acme/react-patterns@2.1.0" }
]
},
{
"id": "backend",
"path": "services/api",
"providers": ["claude"],
"packages": [
{ "packageId": "@acme/baseline@1.0.0" },
{ "packageId": "@acme/backend-guidelines@3.0.0" }
]
}
],
"revision": 1,
"projectionPolicy": {
"claude": "generated_local",
"codex": "generated_local",
"copilot": "committed_projection"
}
}
Each Target Gets Its Own Providers and Packages
Each target is independent:
| Target | Path | Providers | Packages |
|---|---|---|---|
root | . | Codex, Copilot | Baseline |
frontend | apps/web | Claude, Codex | Baseline, React Patterns |
backend | services/api | Claude | Baseline, Backend Guidelines |
When you run wl pull, Workloom renders:
. # Root target
├── AGENTS.md # From @acme/baseline (Codex)
├── .github/
│ └── copilot-instructions.md # (Copilot)
│
apps/web/ # Frontend target
├── .claude/
│ ├── settings.json # From @acme/react-patterns
│ └── CLAUDE.md # (Claude)
├── AGENTS.md # From @acme/baseline + @acme/react-patterns (Codex merge)
│
services/api/ # Backend target
└── .claude/
├── settings.json # From @acme/backend-guidelines
└── CLAUDE.md # (Claude)
How Targets Resolve Independently
Each target:
- Resolves its own packages independently. No cross-target version conflicts.
- Renders to its own path. Files in
apps/web/never overwriteservices/api/files. - Uses its own providers. Root can use Copilot while frontend uses Claude—no interference.
- Has its own drift state.
wl statusreports per-target drift separately.
Independent Package Resolution
Targets can pin different versions of the same package:
{
"targets": [
{
"id": "frontend",
"path": "apps/web",
"packages": [
{ "packageId": "@acme/baseline@1.0.0" } // Version 1.0.0
]
},
{
"id": "backend",
"path": "services/api",
"packages": [
{ "packageId": "@acme/baseline@2.0.0" } // Version 2.0.0
]
}
]
}
Each target renders its own version of the baseline package without conflict. Lock files track the resolved state per target.
No Cross-Target Overwrites
Files rendered for one target never overwrite another:
✓ apps/web/.claude/settings.json (frontend target)
✓ services/api/.claude/settings.json (backend target)
✗ apps/web/.claude/settings.json (frontend) won't overwrite services/api (backend)
How Drift Detection Works Per-Target
When you run wl status, Workloom reports drift per target:
wl status
# Workloom Status Report
# Workspace: Acme Monorepo
# Target: root
# Providers: codex, copilot
# Status: owned_clean
# Drift: None
# Target: frontend
# Providers: claude, codex
# Status: owned_locally_modified
# Files: AGENTS.md (drift), .claude/CLAUDE.md (clean)
# Target: backend
# Providers: claude
# Status: owned_clean
# Drift: None
See details for a specific target:
wl status --target frontend
# Target: frontend (apps/web/)
# Packages: @acme/baseline@1.0.0, @acme/react-patterns@2.1.0
# Drift:
# AGENTS.md (state: owned_locally_modified)
Check differences:
wl diff --target frontend
# apps/web/AGENTS.md differs from desired state
# (unified diff)
Restore a specific target:
wl restore --target frontend
# Restored apps/web/ to desired state
Path Resolution and Precedence
File paths in rendered assets are relative to the target's path:
Claude Asset in Frontend Target
If you have an instruction asset in @acme/react-patterns:
{
"name": "react-tips",
"type": "instruction",
"provider": "claude",
"path": ".claude/CLAUDE.md",
"content": "# React Tips for Claude\n\n..."
}
And the target is:
{
"id": "frontend",
"path": "apps/web"
}
The file renders to:
apps/web/.claude/CLAUDE.md
Not ./.claude/CLAUDE.md at the root.
Nested Asset Paths
If an asset specifies a relative path:
{
"path": ".claude/skills/react/SKILL.md"
}
And the target is services/api/, the asset renders to:
services/api/.claude/skills/react/SKILL.md
Example: Three-Target Monorepo Workspace
A realistic monorepo with shared + target-specific packages:
{
"workspaceId": "wksp_ecommerce_01",
"orgId": "org_shop",
"name": "E-Commerce Monorepo",
"binding": {
"type": "git-repository",
"gitRemote": "git@github.com:shop/platform.git"
},
"targets": [
{
"id": "root",
"path": ".",
"providers": ["codex", "copilot"],
"packages": [
{
"packageId": "@shop/engineering-baseline@3.2.0",
"channel": "stable"
}
]
},
{
"id": "storefront",
"path": "apps/storefront",
"providers": ["claude", "codex"],
"packages": [
{
"packageId": "@shop/engineering-baseline@3.2.0",
"channel": "stable"
},
{
"packageId": "@shop/frontend-patterns@2.0.0",
"channel": "stable"
}
]
},
{
"id": "api",
"path": "services/api",
"providers": ["claude"],
"packages": [
{
"packageId": "@shop/engineering-baseline@3.2.0",
"channel": "stable"
},
{
"packageId": "@shop/backend-guidelines@1.5.0",
"channel": "stable"
}
]
},
{
"id": "admin",
"path": "apps/admin",
"providers": ["claude", "codex"],
"packages": [
{
"packageId": "@shop/engineering-baseline@3.2.0",
"channel": "stable"
},
{
"packageId": "@shop/frontend-patterns@2.0.0",
"channel": "stable"
},
{
"packageId": "@shop/security-policies@1.1.0",
"channel": "stable"
}
]
}
],
"revision": 5,
"projectionPolicy": {
"claude": "generated_local",
"codex": "generated_local",
"copilot": "committed_projection"
},
"ciPolicy": {
"requireCleanSync": true,
"failOnDrift": true
}
}
After wl pull:
. # root target
├── AGENTS.md # Codex: baseline
├── .github/copilot-instructions.md # Copilot: baseline
apps/storefront/ # storefront target
├── .claude/
│ ├── settings.json # Claude: baseline + frontend-patterns
│ └── CLAUDE.md
├── AGENTS.md # Codex: baseline + frontend-patterns merge
services/api/ # api target
└── .claude/
├── settings.json # Claude: baseline + backend-guidelines
└── CLAUDE.md
apps/admin/ # admin target
├── .claude/
│ ├── settings.json # Claude: baseline + frontend-patterns + security
│ └── CLAUDE.md
└── AGENTS.md # Codex: baseline + frontend-patterns + security merge
Common Patterns
Shared Base + Target-Specific Packages
All targets get a baseline package; additional packages depend on role:
"packages": [
{ "packageId": "@org/baseline@1.0.0" }, // Always included
{ "packageId": "@org/frontend-patterns@2.0.0" }, // Only frontend apps
{ "packageId": "@org/backend-guidelines@1.5.0" } // Only backend services
]
Different Provider Strategies Per Target
Root uses Copilot (committed, centralized), frontend uses Claude (local, personalized):
{
"targets": [
{
"id": "root",
"providers": ["copilot"],
"packages": [{ "packageId": "@org/shared@1.0.0" }]
},
{
"id": "frontend",
"providers": ["claude"],
"packages": [{ "packageId": "@org/shared@1.0.0" }]
}
],
"projectionPolicy": {
"copilot": "committed_projection",
"claude": "generated_local"
}
}
Gated Drift Detection Per Target
Require clean sync for production targets but allow drift in development:
{
"ciPolicy": {
"requireCleanSync": true
}
}
Then in CI:
wl sync --check --target api
# Fails if api target has drift (production)
wl sync --check --target experiment
# (experiment target can have drift; not checked in CI)
Troubleshooting
"Package not found in target X"
A workspace includes a package that doesn't specify your target. Check the workspace manifest:
wl status --target my-target
# Error: Package @acme/baseline@1.0.0 does not support target my-target
Add the target path to the package's target list or ensure the target's path matches the package's expectations.
"Files in wrong location"
A file renders to an unexpected path. Check the asset's declared path:
{
"path": ".claude/settings.json" // Relative to target path
}
And the target:
{
"id": "myapp",
"path": "apps/myapp"
}
Result: apps/myapp/.claude/settings.json
"Drift in one target affects others"
Targets are independent—drift in one target doesn't affect others. Run wl status --target <id> to isolate the issue.
CI/CD Integration
In your monorepo CI, check all targets:
# Check all targets
wl sync --check
# Check specific target
wl sync --check --target frontend
# Check multiple targets
wl sync --check --target frontend --target api
Use with --locked for deterministic checks:
wl sync --check --locked
# Only apply locked versions, fail on any drift
Next Steps
- Learn about packages and assets — structure packages for monorepos
- Set up CI/CD integration — drift detection per target
- Understand rendering pipeline — how packages render to targets