← osss tools

Agent Workspace Template

A meta-repo skeleton that gives your pile of side-by-side checkouts a spine for agent sessions.

A guided tour for someone setting up a multi-repo agent workspace from this skeleton: the problem it solves, the one idea it's built around, how you stand it up, the day-to-day loop of running coding-agent sessions across many repos, and the two mechanisms — path-triggered briefs and wt-managed checkouts — that make it work. For the file-by-file breakdown, see the README; this guide is the "why" and the "how it fits together."

The problem it solves

You keep a dozen-plus repos side by side and start your agent sessions (Claude Code, or any agent that reads AGENTS.md) from the directory that holds them all. That directory has no spine. Every session opens cold: the agent doesn't know what the repos are, how they relate, which conventions apply, or which one this task even touches — and no single session can hold all of them in context at once. So you re-explain the same things, or the agent guesses.

The naive fix — one giant AGENTS.md listing every repo's rules — rots fast. It becomes a stale second copy of conventions that really live inside each repo, and it dumps every project's context into every session whether relevant or not.

agent_workspace_template gives that top directory a spine without the rot. It's a meta-repo that sits above your project checkouts and acts purely as the orientation layer: a workspace overview, one short self-contained brief per project, and a mechanism that auto-loads only the brief a session is actually touching. It carries no authority over how you write code — that stays in each repo — so it can't drift into a stale rulebook.

Is this for you? With 1–3 repos it's overkill; a single AGENTS.md per repo is enough. The payoff starts when you have enough repos that a session can't see them all at once, and you want each session to load just the project it's working on.

The one idea: orientation, not authority

Everything follows from one separation:

Keeping prescriptive rules out of the meta-layer is the whole trick. A brief answers "what is this, how does it relate, where do I look?" and links down to the repo's real docs rather than copying them. That's what stops the workspace from rotting into everyone's stale rules.

The directory tree makes the split physical. The meta-repo and the checkouts share one tree, but the meta-repo's .gitignore is an allowlist that tracks only the orchestration files:

~/code/                         ← the meta-repo (this template). Tracks ONLY the files below.
├── AGENTS.md                   ← workspace overview + project map. Orientation, not rules.
├── CLAUDE.md                   ← points agents at AGENTS.md.
├── .claude/rules/<proj>.md     ← path-triggered pointer: "touching projects/<proj>/** → read its brief"
├── docs/projects/<proj>.md     ← one self-contained brief per project
├── docs/reference/*.md         ← cross-cutting ops docs (environments, observability, workflow…)
├── docs/conventions.md         ← the maintenance contract + checkout/branch conventions
├── bin/new-project             ← scaffolds a brief + rule pair from a name
├── .worktree/hooks/<proj>/…          ← per-project worktree setup/teardown hooks
│
├── projects/   djttools/   …   ← group dirs holding the ACTUAL checkouts (gitignored here;
                                   each is its own repo / set of wt worktrees)

The group dirs that hold real checkouts — projects/, and any others you add — are each their own git repo and stay gitignored here. To track a new group dir you'd have to re-include it in the allowlist in .gitignore; the default is to leave it ignored.

Getting started

The repo ships a fully worked widget-api example — a fictional HTTP service wired through every file (brief, rule, project-map row, worktree hooks) so you can see each piece in place before deleting it.

# 1. Use this as a template (clone, or "Use this template" on your forge), then:
cd your-workspace

# 2. Trace the widget-api example through every file, then scaffold your own:
bin/new-project payments-api "Handles checkout and billing"

# 3. Edit AGENTS.md: replace the <PLACEHOLDER> prose, add your project-map rows.

# 4. Clone your real repos into the group dirs (projects/, etc.) — they stay
#    gitignored here and keep their own history.

# 5. Delete the widget-api example once your own projects are in.

The repo also includes a .envrc that puts bin/ on your PATH (via direnv), so new-project runs by bare name from anywhere in the workspace. If you don't use direnv, just call bin/new-project directly.

The everyday loop

Day to day you do two things: add/maintain projects in the meta-layer, and run agent sessions across the checkouts.

Add a project. A project is represented by three files that must stay in sync — and that's the whole maintenance burden:

The three filesWhat it is
docs/projects/<name>.mdthe brief — purpose, stack, relations, commands, pointers to the repo's own docs
.claude/rules/<name>.mdthe path-triggered pointer — a paths: glob and one line: MUST read the brief
the map row in AGENTS.mdthe project's row in the workspace project map

bin/new-project scaffolds the first two from a name and reminds you about the third — the one step it can't safely automate, because it can't guess where the row goes or what to say:

bin/new-project <name> [one-line description] [-g <group-dir>]
# <name>          project slug (kebab-case), e.g. payments-api
# description     optional one-liner used in the brief and rule
# -g <group-dir>  group dir the checkouts live under (default: projects)

It writes docs/projects/<name>.md and .claude/rules/<name>.md (both with <PLACEHOLDER> prose to fill in), refuses to overwrite an existing file, and must be run from the workspace root. When you remove a project, delete all three; when you rename one, update the rule's paths: glob and the brief's links. That contract is written down in docs/conventions.md.

Run a session. Start your agent from the workspace root and point it at a file in the project you're touching. Because the orientation lives in plain AGENTS.md + briefs, the agent has what it needs; with Claude Code, the matching rule pulls in exactly the right brief automatically (next section).

Add a cross-cutting reference. Things true across projects — not owned by any one repo — go under docs/reference/. Copy docs/reference/_template.md to a real name like environments.md, observability.md, or workflow.md and fill it in. Keep these concrete and operational: endpoints, commands, exact paths.

Path-triggered briefs (the Claude Code piece)

A brief is only useful while you're working on its project. Rather than load every brief into every session, each project's rule scopes its brief to a subtree. The rule is tiny:

---
name: widget-api
paths:
  - "projects/widget-api/**"
---

**MUST** read [docs/projects/widget-api.md](../../docs/projects/widget-api.md) before working on files matching the above paths.

When a Claude Code session touches a file under projects/widget-api/**, it auto-loads that rule, which pulls in just that brief — and none of the others. So a session sees exactly the project it's working on.

This is the one Claude-Code-specific piece. The content layer — AGENTS.md and everything under docs/ — is plain markdown that any agent or human can read; AGENTS.md links every brief, so an agent without auto-load can read them on demand. If you use a different agent, keep the briefs and AGENTS.md and replace .claude/rules/ with whatever your agent uses to scope context to a subtree. Nothing else depends on Claude Code.

Branch-named checkouts and wt

The template assumes wt for managing checkouts, and the conventions here are written for it. The rule is one checkout per branch under each repo directory, named after the branch (slashes preserved as nested dirs: branch casey/foo<repo>/casey/foo/). The repo directory itself is never a checkout — it holds the canonical clone plus one linked worktree per branch.

Invariant: the directory name always equals the checked-out branch. Never git switch a branch-named checkout onto a different branch — per-checkout setup (databases, env) is keyed to the branch via the directory name, and switching in place silently breaks that mapping. To work on another branch, make or fetch its own checkout:

wt mk <branch>                 # branch off the canonical checkout into a new worktree
wt get <remote-branch> [name]  # fetch a remote branch into a review worktree
wt ls [path]                   # list checkouts with branch, last-commit age, status
wt rm <checkout>               # teardown hook + remove (refuses the canonical checkout)

New project work goes through a branch + MR tracked by a work item, with the work-item ID as a branch prefix (wt mk 4821-hash_cache) and snake_case after it. The meta-repo itself is exempt: it's just workspace docs, so it commits directly to its default branch — no worktrees, work items, or MRs for changes here.

Per-checkout setup via hooks. wt mk / wt get run a worktree-setup hook after creating a checkout; wt rm runs worktree-teardown before removing one. The template ships an example pair under .worktree/hooks/projects/widget-api/ that gives each checkout its own database (named after the branch) and a checkout-scoped .envrc pointing at it — so two branches run side by side without colliding. These live in the meta-repo as a workspace override, which is how you attach setup to a repo you don't control; a repo you do control can instead carry .worktree/hooks/<name> on its own default branch. Each hook gets REPO_DIR, CANONICAL_DIR, WORKTREE_DIR, WORKTREE_NAME (slash-sanitized), BRANCH, and WORKTREE_KIND (new for mk, fetched for get). See docs/conventions.md for the full hook resolution order and contract.

wt is optional in principle — the orientation layer (AGENTS.md + briefs + rules) works with any worktree discipline, including plain git worktree. But the hook examples and branch conventions here are wt-shaped; without it, treat .worktree/ and the checkout-layout sections as inspiration rather than instructions.

Where to go next