AI/workspace-harness.md
Table of Contents
Workspace Harness
An opinionated workflow for building within a connected set of repositories, leveraging AI agents with cross-repo context.
Philosophy
A workspace harness (not a monorepo build — you're not bundling these together) keeps related repos discoverable, bootable, and consistent. Each repo keeps its own identity, build, and deploy config; the workspace is just the folder that holds them plus shared glue:
- One init script to clone everything and set up tooling.
- One CLI to start/stop/log all dev servers.
- One
.gitignorethat hides repo folders from the editor's file-finder. - One
AGENTS.mdthat tells AI agents how the pieces fit together. - One
docs/folder for cross-repo documentation.
Directory Layout
workspace/
├── README.md # Workspace overview, quick-start, app table
├── AGENTS.md # Instructions for AI agents
├── .gitignore # Ignores cloned repos — keeps file-finder clean
├── scripts/
│ ├── init.sh # Clone all repos, install tooling
│ └── app.sh # CLI: start/stop/log/status dev servers
├── docs/ # Cross-repo documentation
│
├── repo-a/ # Cloned repos — gitignored at workspace level
├── repo-b/
├── repo-c/
│
├── _internal/ # Shared libs, internal packages (gitignored)
├── _deprecated/ # Archived repos — read-only
└── _private/ # Local-only config, logs, scratch
Underscore convention
Prefix _ signals special scope to both humans and agents:
| Prefix | Purpose |
|---|---|
_internal/ |
Shared libraries, design system, internal packages |
_deprecated/ |
Archived repos — don't edit, read-only reference |
_private/ |
Local-only logs, env overrides, scratch (not committed) |
_agent/ |
Agent session data, scratch pads |
Quick Start
# 1. Clone the workspace meta-repo
git clone <workspace-url> && cd workspace
# 2. Bootstrap — clones all repos, installs tooling
./scripts/init.sh
# 3. Link the CLI into your PATH
ln -sf "$(pwd)/scripts/app.sh" ~/bin/app
# 4. Start everything
app all start
# 5. Check health
app all status
Init Script (scripts/init.sh)
A single idempotent script run once on a fresh machine. It should:
- Clone repos — skip if
.gitalready exists. - Install tooling — nvm, Node, pm2 (or Docker, etc.).
- Be safe to re-run — every step checks "already done?" before acting.
# Pattern (simplified):
clone_if_missing() {
local dir="$1" url="$2"
if [ -d "$dir/.git" ]; then
echo " [SKIP] already cloned"
else
echo " [CLONE] $url -> $dir"
git clone "$url" "$dir"
fi
}
clone_if_missing "$ROOT/repo-a" "git@github.com:org/repo-a.git"
clone_if_missing "$ROOT/repo-b" "git@github.com:org/repo-b.git"
App CLI (scripts/app.sh)
A thin shell script that wraps pm2 to manage all dev servers from one command.
Usage
app <app-name> <command>
app: repo-a | repo-b | all
command: start | stop | restart | log | status
| Command | Description |
|---|---|
start |
Launch the app's dev server via pm2 |
stop |
Kill the pm2 process |
restart |
Restart (useful after pulling new code) |
log |
Tail live logs |
status |
Show pm2 status |
Canonical names
If users might refer to the same app by different names (e.g. api and api-python), normalise them internally so pm2 never gets duplicate processes.
Logs
Logs write to _private/server-logs/{app}.log by default. Override with an env var:
APP_LOG_DIR=/tmp/logs app all start
Agent Context (AGENTS.md)
The file AI agents read first. Structure:
1. Repo table
Quick-reference for every repo:
| Repo | Description | Stack | Port |
|---|---|---|---|
repo-a |
Customer portal | Next.js, MongoDB | 3000 |
repo-b |
Backend API | FastAPI, Supabase | 8000 |
Note the underscore convention so agents know _deprecated/ isn't active work.
2. Commands per repo
### repo-a
\`\`\`bash
npm run dev # Next.js dev server
npm run build # Production build
npm test # Vitest
\`\`\`
3. Architecture notes
Key patterns to save agents from rediscovering: auth approach, API route conventions, folder layout, testing setup.
Editor & .gitignore
The .gitignore lists every cloned repo directory. This means:
- Cmd+P / Ctrl+P file-finder — ignores those folders by default. Only workspace-level files show up.
- Sidebar — folders still visible when expanded, so collapse repos you're not actively working on.
- Git status — workspace stays clean; each repo manages its own git.
# .gitignore — ignore cloned repos so they don't clutter the workspace
repo-a/
repo-b/
repo-c/
_internal/
server-logs/
VS Code / code-server override
If you want gitignored repos to still appear in Quick Open (Cmd+P), add a .vscode/settings.json in the workspace root:
{
"search.useIgnoreFiles": false,
"search.exclude": {
"_deprecated/**": true,
"_internal/**": true,
"_private/**": true
}
}
search.useIgnoreFiles— whenfalse, VS Code ignores.gitignorepatterns for its file indexer, making repos visible in Cmd+P.search.exclude— selectively re-hide folders you don't need in the file finder (typically the_-prefixed ones).
This only affects the editor's file index, not git — your .gitignore still works as expected.
Docs
A docs/ folder in the workspace holds cross-repo documentation that no single repo owns: architecture overviews, runbooks, meeting notes, ADRs.
For a richer docs experience, run a lightweight markdown server:
# e.g. a simple Node.js server rendering markdown to HTML
cd docs-server && npm run dev
Conventions
- Files use lowercase kebab-case (
deployment-runbook.md). - Internal links update when a doc is renamed or moved.
App Table (in README.md)
Every workspace README.md should have a table listing each repo:
| App | Directory | Stack | Port | Purpose |
|---|---|---|---|---|
| app-a | repo-a/ |
Next.js, MongoDB | 3000 | Customer portal |
| app-b | repo-b/ |
FastAPI, Postgres | 8000 | Backend API |
Environment Variables
Each app manages its own .env / .env.local. Keep a .env.example at each app root. Shared secrets go through the deployment platform (Vercel, etc.), not checked in.
Deployment
Each repo deploys independently — no shared build step. Each has its own platform config (vercel.json, fly.toml, etc.). The workspace CLI is dev-only.
Common Patterns
| Pattern | Recommendation |
|---|---|
| Shared UI library | Place in _internal/, publish as @org/ui |
| Deprecated code | Move to _deprecated/ — don't delete outright |
| Version consistency | .nvmrc per repo; init.sh installs one workspace-level Node |
| Running tests | Per-repo; no workspace-wide test runner |
| Adding a new repo | Clone it → add to init.sh → .gitignore → README.md table → app.sh |